TimeBack Platform - QTI 1EdTech

Data dictionary for the shared QTI persistence schema

This dictionary documents every QTI 1EdTech persistence table, field, allowed value, provenance label, range, nullability rule, relationship, invalid-value test, and architecture decision link. It is written for a staff engineer who needs to write migrations and queries without reading source code.

Tables16
Fields176
Allowed value sets19
ArchitectureApproved
Reading rule. Each row is labeled either 1EdTech pass-through, Platform gap fill, Gap fill row with 1EdTech pass-through values, Inherited platform table, or QTI compatibility view. Pass-through values come from QTI XML, IMS manifests, bundled XSDs, VDEX vocabularies, Schematron assertions, or generated traceability. Gap fills are QTI persistence decisions and link back to an architecture ITD. Inherited platform tables come from the approved Platform data dictionary; compatibility views are transitional bridge shapes, not new source-of-truth storage.
API boundary note. Migration/query examples in this dictionary describe durable storage and the exact shipped root enumeration reads. The approved architecture now ships listPackages, listArtifacts, and listArtifactVersions with cursor + limit paging under ITD-026, plus operation-specific reads by known identifiers: getDeliveryJson, getAuthoringJson, exportXml, and getCandidateRuntimeData. ITD-027 adds the timed-delivery contract: delivery JSON surfaces stock QTI timeLimits, while submitAttempt uses the server clock to decide timing_status. Filter, sort, modifiedSince, runtime/result collection lists, sub-collection browsing, and eventing remain deferred under ITD-018 API Boundary and the API axis ledger.
Schema navigation

Object index

Each table card and field row has a stable anchor for migration notes, code review, and implementation traces.

platform.tenant Inherited Tenant 7 fields - The shared school, district, publisher, application, or workspace boundary used by every module. QTI inherits this table unchanged from the Platform 1EdTech surface. platform.idempotency_key Inherited Idempotency key 19 fields - Shared retry ledger for customer-visible create, import, upload, export-job, and asynchronous command operations. QTI inherits this table unchanged from the Platform 1EdTech surface. platform.audit_log Inherited Audit log 17 fields - Append-only cross-module record of high-risk writes, privileged reads, learner-runtime deletion, denied authorization, and trust changes. QTI inherits this table unchanged from the Platform 1EdTech surface. qti.tenant Tenant compatibility view 4 fields - Backward-compatible QTI tenant shape exposed over platform.tenant for older QTI migrations and queries. qti.content_package Content package 11 fields - One imported IMS Content Package or loose XML bundle, scoped to a tenant. qti.package_resource Package resource 7 fields - IMS manifest resource row with resource identifiers, type, href, dependencies, and metadata. qti.package_file Package file 10 fields - Original file bytes from an imported IMS/QTI package. qti.artifact Artifact 10 fields - Stable logical QTI document or package artifact across immutable versions. qti.artifact_version Artifact version 16 fields - Immutable XML, generated object graph, projections, and trace for one saved artifact edition. qti.component Component 14 fields - Lossless relational projection of generated QTI object-graph nodes. qti.variable_declaration Variable declaration 11 fields - Typed query projection for QTI response, outcome, template, and context variables. qti.processing_rule Processing rule 9 fields - Executable QTI processing and expression tree projection. qti.delivery_session Delivery session 12 fields - Candidate delivery snapshot, including the server-authoritative timing window when the delivered QTI content declares qti-time-limits. qti.attempt Attempt 14 fields - Candidate response, template, outcome, timing, and processing trace snapshot inside a delivery session. qti.conformance_run Conformance run 8 fields - Release evidence for a QTI conformance/profile run. qti.conformance_assertion Conformance assertion 7 fields - Per-example and per-feature conformance evidence.
Table

platform.tenant

The shared school, district, publisher, application, or workspace boundary used by every module. QTI inherits this table unchanged from the Platform 1EdTech surface.

Inherited platform table Primary key tenant_id

Purpose

A tenant is the platform-wide owner of content, learner runtime records, integrations, and API access. QTI used qti.tenant as the first-module pattern; this table promotes that boundary so QTI, OneRoster, Caliper, CASE, and future modules all point to the same customer/workspace row. QTI references this table rather than redefining a module-local primitive.

Lifecycle

Created in provisioning before any module writes tenant-scoped data. Moves to active when authentication and operational setup are complete, suspended when writes must pause, and archived when the workspace is retained for history but closed to ordinary writes. Learner-runtime deletion is handled by module tables, not by deleting the tenant row. QTI uses the Platform lifecycle exactly; compatibility views or bridge migrations are transitional mechanics only.

Trace

Module Schemas And Shared Platform Schema, Shared Tenant Model, Authentication, Authorization, And Tenant Scope, ITD-025 Platform Substrate Inheritance

PITD-003 makes platform.tenant the cross-module tenant truth and PITD-002 says shared concepts live in platform.*. QTI ITD-025 requires this platform primitive to remain the public data-contract truth. Canonical Platform dictionary: https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/data_dictionary/

Relationships

  • Parent of platform.idempotency_key through platform.idempotency_key.tenant_id.
  • Parent of platform.audit_log through platform.audit_log.tenant_id.
  • Future module tenant-scoped tables must reference platform.tenant(tenant_id) rather than creating module-local tenant tables.
  • QTI predecessor: qti.tenant(tenant_id) is reconciled to this table by migration or compatibility view.
  • QTI inherited use: qti.content_package.tenant_id, qti.artifact.tenant_id, and qti.delivery_session.tenant_id reference platform.tenant(tenant_id).

Integrity rules

  • tenant_key is unique and stable across platform.tenant.
  • status must be one of tenant_status.
  • metadata must be a JSON object and must not contain direct learner/parent PII, credentials, Bearer tokens, raw JWTs, or contact details.
  • updated_at must be greater than or equal to created_at.

Invalid examples

  • tenant_key = 'North Valley' because keys must be lowercase slug values.
  • metadata contains a student email, parent phone number, raw JWT subject, access token, or SIS identifier.
  • status = 'enabled' because the only active state is active.
  • A module table references qti.tenant instead of platform.tenant after the platform compatibility bridge exists.

Example row

{
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "tenant_key": "north-valley",
  "display_name": "North Valley School District",
  "status": "active",
  "metadata": {
    "region": "us-east",
    "externalRefs": [
      {
        "system": "crm",
        "ref": "acct_7x9"
      }
    ]
  },
  "created_at": "2026-05-21T12:00:00Z",
  "updated_at": "2026-05-21T12:00:00Z"
}

Common queries

select tenant_id, status from platform.tenant where tenant_key = $1;
insert into platform.tenant (tenant_key, display_name, status, metadata) values ($1, $2, 'provisioning', '{}'::jsonb) returning tenant_id;
select t.tenant_key, count(a.audit_log_id) from platform.tenant t left join platform.audit_log a using (tenant_id) where t.status = 'active' group by t.tenant_key;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
tenant_id Primary key
uuid
Required; default gen_random_uuid()

Stable database identifier for one customer/workspace boundary. This is the value module tables reference and tenant-scoped JWTs must match.

Edge case: QTI reconciliation must preserve existing qti.tenant tenant_id values or provide a deterministic mapping table during migration.

Must be a valid PostgreSQL UUID and unique as the primary key.

Invalid when: Not parseable as UUID, reused by another tenant, copied into learner-facing content as identity text, or replaced by tenant_key in foreign keys.

Inherited platform table

PITD-003 required tenant_id in platform.tenant. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Referenced by platform.idempotency_key.tenant_id, platform.audit_log.tenant_id, and future module tenant_id fields. One tenant has many module records.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

tenant_key Unique
text
Required; no default

Human-stable lookup key for routes, local tooling, logs, and examples. It is a convenience key, not an authorization secret.

Edge case: A renamed school may keep its tenant_key stable and update display_name instead; changing tenant_key is a migration because URLs and examples may depend on it.

3 to 64 characters. Lowercase ASCII letters, digits, and hyphens only. Must start and end with a letter or digit. Unique across platform.tenant.

Invalid when: Blank, uppercase, contains spaces, contains an email/domain secret, duplicates another tenant, or is used as proof of authorization.

Inherited platform table

QTI's qti.tenant.tenant_key pattern promoted into platform tenant lookup. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign keys should point at tenant_key; use tenant_id for joins.

Example: north-valley

display_name
text
Required; no default

Customer-facing label shown in admin tools and documentation examples.

Edge case: The name may contain a school or district name. Do not store student, parent, or teacher contact details here.

1 to 160 visible characters after trimming. Required. Must not be used for uniqueness, routing, authorization, or joins.

Invalid when: Null, blank, over 160 characters, used as an authorization key, or copied into module records instead of joining to platform.tenant.

Inherited platform table

PITD-003 required display_name in platform.tenant. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

None. It describes the tenant row and can change without changing tenant_id.

Example: North Valley School District

status
text
Required; default 'provisioning'

Tenant lifecycle state that tells modules whether ordinary tenant-scoped writes may proceed.

Edge case: Suspended and archived tenants can still be read by authorized support or export flows if the customer website documents that behavior.

Must satisfy tenant_status_allowed: provisioning, active, suspended, or archived.

Invalid when: Outside tenant_status, null, or manually changed without an audit row explaining the administrative action.

Allowed values: Platform tenant_status

Inherited platform table

PITD-003 left status to the data dictionary; PITD-005 and PITD-009 require auditable tenant scope. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Modules read this before accepting customer writes.

Example: active

metadata
jsonb
Required; default '{}'::jsonb

Small redacted operational facts about the tenant that do not deserve first-class columns yet.

Edge case: If a metadata key becomes required for authorization, billing, or module behavior, promote it to a typed column through a new data-dictionary attempt and migration.

Must be a JSON object. Recommended top-level keys are region, externalRefs, notes, and featureFlags. Must not contain secrets, raw JWTs, direct learner/parent PII, raw package bytes, IP addresses, or user agents.

Invalid when: Null, non-object JSON, stores a student email/phone/name/SIS ID, stores credentials, or becomes the only place a required module relationship is recorded.

Inherited platform table

PITD-003 required metadata; PITD-008 limits PII in shared tables. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

None. External references inside metadata are diagnostic and cannot replace tenant_id joins.

Example: {"region":"us-east","externalRefs":[{"system":"crm","ref":"acct_7x9"}]}

created_at
timestamptz
Required; default now()

Timestamp when the tenant row was inserted.

Edge case: Bulk migrations from qti.tenant may preserve an older source created_at only when the migration notes explain the source.

Required timestamp with time zone. Stored in UTC by PostgreSQL/Supabase conventions.

Invalid when: Null, manually backdated without migration evidence, or compared as local wall time.

Inherited platform table

PITD-017 requires DDL comments and lifecycle timestamps. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

None.

Example: 2026-05-21T12:00:00Z

updated_at
timestamptz
Required; default now()

Timestamp when the tenant row was last changed.

Edge case: Audit rows remain the detailed history; updated_at is only the latest-row freshness indicator.

Required timestamp with time zone. Must be greater than or equal to created_at. Updated on every display_name, status, or metadata change.

Invalid when: Null, earlier than created_at, or left unchanged after a tenant status or metadata mutation.

Inherited platform table

PITD-017 requires forward-only migrations and observable shared state. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

None.

Example: 2026-05-21T12:00:00Z

Table

platform.idempotency_key

Shared retry ledger for customer-visible create, import, upload, export-job, and asynchronous command operations. QTI inherits this table unchanged from the Platform 1EdTech surface.

Inherited platform table Primary key idempotency_key_id

Purpose

This table records the first request to claim an Idempotency-Key within a precise tenant/module/surface/operation scope. Later requests with the same scope and request hash replay the original safe result; later requests with the same key but a different request hash return 409 instead of duplicating work. QTI references this table rather than redefining a module-local primitive.

Lifecycle

Inserted as in_progress before a mutation performs side effects. Updated to completed or failed_permanent when a replayable final outcome exists, failed_transient when no stable result can be replayed, and expired after the documented replay window. Cleanup may retain expired rows for audit while making them non-replayable. QTI uses the Platform lifecycle exactly; compatibility views or bridge migrations are transitional mechanics only.

Trace

Idempotency And Optimistic Concurrency, HTTP Envelope, Status, And Problem Errors, Cross-Module Audit Log, ITD-025 Platform Substrate Inheritance

PITD-007 promotes QTI's package idempotency pattern into a platform-wide ledger. QTI ITD-025 requires this platform primitive to remain the public data-contract truth. Canonical Platform dictionary: https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/data_dictionary/

Relationships

  • Belongs to one platform.tenant.
  • May be referenced by many platform.audit_log rows through platform.audit_log.idempotency_key_id.
  • Module-specific rows may store their own resource identifier; this table records retry state and safe replay data, not the source of record for module resources.
  • QTI inherited use: qti.content_package.platform_idempotency_key_id references platform.idempotency_key(idempotency_key_id) for package-ingest retry replay.

Integrity rules

  • Unique scope: tenant_id, module, surface, method, route_template, operation_id, idempotency_key.
  • request_hash must be sha256:<64 lowercase hex characters>.
  • response_status must be null while status is in_progress and between 100 and 599 when present.
  • response_body must be null or a redacted JSON object safe to replay to the same tenant.
  • expires_at must be later than created_at.

Invalid examples

  • Same scope and key with a different request_hash is a 409 idempotency conflict, not a second mutation.
  • response_body includes raw package bytes, JWTs, headers, IP addresses, user agents, learner PII, or unredacted Problem details.
  • route_template stores a concrete path like /tenants/0d4.../qti/packages instead of /tenants/{tenant_id}/qti/packages.
  • expires_at is null or earlier than created_at.

Example row

{
  "idempotency_key_id": "b81db6f4-c7ea-4757-b5aa-87570f7ad119",
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "module": "qti",
  "surface": "1edtech",
  "method": "POST",
  "route_template": "/tenants/{tenant_id}/qti/packages",
  "operation_id": "qti.packages.import",
  "idempotency_key": "pkg-upload-2026-05-21-001",
  "request_hash": "sha256:3d9f7a3a4e3c7ff4a2dcb2de9339f3c5a9b4f7d58f0a20d6f127f452e4f7e8b1",
  "status": "completed",
  "response_status": 201,
  "response_body": {
    "packageId": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
    "importStatus": "imported"
  },
  "resource_type": "qti.content_package",
  "resource_id": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
  "first_request_id": "req_20260521_0001",
  "locked_until": null,
  "expires_at": "2026-05-22T12:01:00Z",
  "created_at": "2026-05-21T12:01:00Z",
  "updated_at": "2026-05-21T12:01:08Z"
}

Common queries

select idempotency_key_id, request_hash, status, response_status, response_body from platform.idempotency_key where tenant_id = $1 and module = $2 and surface = $3 and method = $4 and route_template = $5 and operation_id = $6 and idempotency_key = $7 for update;
-- Conflict branch after the lookup above: if this returns a row, return HTTP 409 idempotency_conflict and do not run the mutation.
select idempotency_key_id, request_hash, 409 as http_status, 'idempotency_conflict' as problem_code from platform.idempotency_key where tenant_id = $1 and module = $2 and surface = $3 and method = $4 and route_template = $5 and operation_id = $6 and idempotency_key = $7 and request_hash <> $8 for update;
insert into platform.idempotency_key (tenant_id, module, surface, method, route_template, operation_id, idempotency_key, request_hash, first_request_id, locked_until, expires_at) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, now() + interval '30 seconds', now() + interval '24 hours') returning idempotency_key_id;
update platform.idempotency_key set status = 'completed', response_status = $2, response_body = $3, resource_type = $4, resource_id = $5, locked_until = null, updated_at = now() where idempotency_key_id = $1;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
idempotency_key_id Primary key
uuid
Required; default gen_random_uuid()

Stable identifier for the retry ledger row. Audit rows refer to this value rather than repeating replay internals.

Edge case: This is an internal row id; the externally supplied key is idempotency_key.

Must be a valid PostgreSQL UUID and unique as the primary key.

Invalid when: Not a UUID, reused across rows, or exposed as the customer Idempotency-Key header value.

Inherited platform table

PITD-007 requires a shared retry ledger. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Referenced by platform.audit_log.idempotency_key_id. One idempotency row can explain many audit rows for retries and final outcomes.

Example: b81db6f4-c7ea-4757-b5aa-87570f7ad119

tenant_id Foreign key
uuid
Required; no default

Tenant that owns the retry scope and the mutation being protected.

Edge case: Platform-wide maintenance operations that do not have a tenant must not use this table unless the operation is deliberately scoped to a tenant row.

Must reference platform.tenant(tenant_id).

Invalid when: Null, not found in platform.tenant, or different from the tenant_id claim in the Bearer JWT for a tenant-scoped route.

Inherited platform table

PITD-007 defines tenant_id as part of idempotency scope. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Many idempotency rows belong to one platform.tenant.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

module Scope key
text
Required; no default

Module namespace whose operation claimed the idempotency key.

Edge case: module=qti remains valid identity for QTI-owned retry scopes while qti/1edtech is under_reconciliation. Public registries must separately expose module_release_status and must not advertise QTI 1EdTech as approved until loop/qti/state.json says the surface has re-approved.

Must satisfy module_key_allowed: platform, qti, oneroster, caliper, case, nweamap, ed_fi, people_and_orgs, curriculum, content, events, results, analytics.

Invalid when: Null, outside module_key, used to store a route group instead of the module namespace, or used by /platform/modules as a release-status substitute.

Allowed values: Platform module_key

Inherited platform table

PITD-002 requires module schemas and shared platform operational tables. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with surface, route_template, and operation_id to define replay scope.

Example: qti

surface Scope key
text
Required; no default

Surface whose API contract produced the retryable operation.

Edge case: The platform surface uses module=platform and surface=platform.

Must satisfy surface_code_allowed: platform, 1edtech, or alpha.

Invalid when: Null, outside surface_code, or used to hide whether an Alpha divergence changed operation behavior.

Allowed values: Platform surface_code

Inherited platform table

PITD-015 defines platform, 1EdTech, and Alpha surfaces. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with module so qti/1edtech and qti/alpha operations can have different customer contracts over shared persistence.

Example: 1edtech

method Scope key
text
Required; no default

HTTP method for the mutation protected by the key.

Edge case: PUT/PATCH operations that can overwrite user work still need If-Match or an equivalent validator; idempotency does not replace optimistic concurrency.

Must satisfy mutation_http_method_allowed: POST, PUT, PATCH, or DELETE.

Invalid when: Null, GET, lowercase if the implementation normalizes to uppercase, or method does not match the documented endpoint.

Allowed values: Platform mutation_http_method

Inherited platform table

PITD-007 defines retryable create, upload, import, export-job, and command operations. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Part of the unique retry scope. Same route and key under different mutation methods are distinct scopes.

Example: POST

route_template Scope key
text
Required; no default

Stable route pattern from the customer website or OpenAPI operation, with variable path segments expressed as braces.

Edge case: If the Alpha route names the tenant as workspaceId, the template still uses the public Alpha route name documented by that surface.

Must start with /. Must not contain a query string. Path variables use {name}. Must not include concrete tenant IDs, resource IDs, learner refs, or secrets.

Invalid when: Contains concrete UUIDs, query strings, raw learner refs, access tokens, or an undocumented internal route.

Inherited platform table

PITD-006 requires endpoint-local contracts and PITD-007 requires route template scope. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Part of the unique retry scope; links the row back to a documented endpoint.

Example: /tenants/{tenant_id}/qti/packages

operation_id Scope key
text
Required; no default

Stable operation identifier used by docs, OpenAPI, logs, audit, and idempotency middleware.

Edge case: If two routes intentionally share replay behavior, they still need separate operation_id values unless the architecture explicitly declares them equivalent.

1 to 120 characters. Lowercase letters, digits, dots, underscores, and hyphens. Must be stable across wording-only documentation edits.

Invalid when: Blank, generated from a localized title, contains spaces, or changes without a customer-site and implementation update.

Inherited platform table

PITD-010 and PITD-009 require shared operation_id fields. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Part of the unique retry scope and repeated in audit rows.

Example: qti.packages.import

idempotency_key Scope key
text
Required; no default

Opaque customer-supplied Idempotency-Key header value for one retryable operation.

Edge case: Same key with same request_hash replays; same key with different request_hash returns 409.

1 to 128 printable ASCII characters after trimming. Unique within the tenant/module/surface/method/route_template/operation_id scope. Must not be a token, password, email, phone number, student identifier, or raw request hash.

Invalid when: Blank, reused for different request_hash in the same scope, contains credentials or direct learner PII, or exceeds the documented length.

Inherited platform table

QTI package ingest used Idempotency-Key; PITD-007 promotes the rule. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Part of the unique retry scope. Not a foreign key.

Example: pkg-upload-2026-05-21-001

request_hash
text
Required; no default

Digest of the canonical replay identity for the first request. It lets middleware distinguish a safe retry from key reuse with different content.

Edge case: Large uploads hash normalized bytes or a precomputed payload hash, never raw bytes stored in this table.

Must be sha256:<64 lowercase hex characters>. Hash input is method, normalized route template, canonical JSON/body digest, tenant_id, module, surface, operation_id, and any idempotency-relevant headers documented by the endpoint.

Invalid when: Missing algorithm prefix, not sha256, uppercase/malformed hex, computed from non-canonical JSON, or includes raw secrets in a way that would be logged.

Inherited platform table

PITD-007 requires request hash conflict detection. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Compared with later requests in the same unique scope.

Example: sha256:3d9f7a3a4e3c7ff4a2dcb2de9339f3c5a9b4f7d58f0a20d6f127f452e4f7e8b1

status
text
Required; default 'in_progress'

Replay lifecycle state of the idempotency row.

Edge case: failed_transient rows can be taken over after locked_until if no side effect committed; failed_permanent rows replay the safe Problem response.

Must satisfy idempotency_status_allowed.

Invalid when: Outside idempotency_status, null, or incompatible with response_status/locked_until, such as completed with no final status.

Allowed values: Platform idempotency_status

Inherited platform table

PITD-007 requires original outcome replay and conflict behavior. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Determines whether middleware replays, rejects, waits, or allows takeover.

Example: completed

response_status
integer
Nullable; no default

HTTP status originally returned for a final replayable outcome.

Edge case: 204 outcomes store response_status=204 and response_body=null.

Nullable while in_progress or failed_transient. When present, must be an integer from 100 through 599 and match the stored response_body/resource outcome.

Invalid when: Present while status is in_progress, outside 100-599, or does not match the Problem/status or resource creation outcome.

Inherited platform table

PITD-006 defines HTTP status policy and PITD-007 defines replay. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Used with response_body to replay the original result.

Example: 201

response_body
jsonb
Nullable; no default

Redacted JSON body safe to replay to the same tenant for completed or failed_permanent outcomes.

Edge case: For async operations, response_body can be a 202 job resource even though final module processing continues elsewhere.

Nullable. When present, must be a JSON object or array that matches the documented response schema. Must not include secrets, raw package bytes, headers, access tokens, IP addresses, user agents, direct learner PII, or unredacted processing traces.

Invalid when: Stores raw request body, file bytes, secrets, PII, non-JSON text, or a body that cannot be returned to the authenticated tenant.

Inherited platform table

PITD-006 requires redacted Problem/errors; PITD-007 requires replayable outcomes. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

May contain resource identifiers that point into module tables, but the module table remains source of record.

Example: {"packageId":"7f2e4dd2-c147-49ea-af77-41a6fdd70980","importStatus":"imported"}

resource_type
text
Nullable; no default

Optional type of resource created, imported, deleted, or accepted by the operation.

Edge case: For operations that create multiple resources, store the primary customer-visible resource and put redacted counts in response_body or audit metadata.

Nullable. When present, use a stable table or API object path such as qti.content_package, qti.delivery_session, platform.tenant, or alpha.activity.

Invalid when: Contains a display title, localized wording, raw URL, or path with tenant/resource IDs.

Inherited platform table

PITD-009 uses resource_type in audit and replay support. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with resource_id for audit and support lookup. Does not enforce a foreign key because target tables vary by module.

Example: qti.content_package

resource_id
text
Nullable; no default

Optional identifier of the primary resource associated with the replayable outcome.

Edge case: Async accepted work can use a job/run id here while the final content resource appears later in module tables.

Nullable. When present, must be the canonical resource identifier for resource_type and tenant_id.

Invalid when: Contains a student name, email, phone number, raw package path, or ID outside the tenant.

Inherited platform table

PITD-009 requires resource identifiers for operational traceability. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with resource_type; target cardinality is many idempotency rows may point at one resource only when retries or aliases are documented.

Example: 7f2e4dd2-c147-49ea-af77-41a6fdd70980

first_request_id
text
Required; no default

Request identifier of the first request that claimed this key.

Edge case: Retry requests have their own request_id in access logs, but this field stays fixed to the first attempt.

Required non-empty text, 1 to 120 characters. Must be safe for logs and customer support. Must not contain tokens, IP addresses, user agents, or PII.

Invalid when: Null, blank, regenerated on replay, or includes sensitive request details.

Inherited platform table

PITD-010 requires request_id in structured operational evidence. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Should match platform.audit_log.request_id for the original attempt when that attempt is audited.

Example: req_20260521_0001

locked_until
timestamptz
Nullable; no default

Temporary lock deadline used while an in-progress operation is executing.

Edge case: Long-running async jobs should complete the idempotency row with a 202 response rather than hold this lock for the whole job.

Nullable. When status is in_progress, should be a future timestamp. Must be null for completed and failed_permanent rows.

Invalid when: Expired while status remains in_progress without takeover logic, set on completed rows, or earlier than created_at.

Inherited platform table

PITD-007 centralizes replay and conflict behavior. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Used by middleware to decide whether another worker may take over a stale in-progress row.

Example: 2026-05-21T12:01:30Z

expires_at
timestamptz
Required; no default

Timestamp after which the key is no longer promised to replay the original response.

Edge case: Expired rows may remain queryable for audit but must not silently replay after the documented window.

Required timestamp with time zone. Must be later than created_at. Default policy is at least 24 hours after first claim; modules may document longer windows for async jobs or exports.

Invalid when: Null, before created_at, shorter than the customer website promises, or extended without retention/audit reason.

Inherited platform table

PITD-007 requires a shared idempotency policy rather than per-module drift. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Indexed for cleanup and expiry marking.

Example: 2026-05-22T12:01:00Z

created_at
timestamptz
Required; default now()

Timestamp when the first request claimed the idempotency key.

Edge case: Use created_at plus expires_at for replay-window reporting.

Required timestamp with time zone.

Invalid when: Null, changed after insert, or compared without timezone normalization.

Inherited platform table

PITD-017 requires lifecycle timestamps. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

None.

Example: 2026-05-21T12:01:00Z

updated_at
timestamptz
Required; default now()

Timestamp when the row last changed status, replay body, lock, or expiry.

Edge case: Retries that merely read/replay do not need to update updated_at unless the implementation records replay counts elsewhere.

Required timestamp with time zone. Must be greater than or equal to created_at.

Invalid when: Null, earlier than created_at, or left unchanged after finalizing the row.

Inherited platform table

PITD-017 requires DDL discipline and auditability. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

None.

Example: 2026-05-21T12:01:08Z

Table

platform.audit_log

Append-only cross-module record of high-risk writes, privileged reads, learner-runtime deletion, denied authorization, and trust changes. QTI inherits this table unchanged from the Platform 1EdTech surface.

Inherited platform table Primary key audit_log_id

Purpose

This table gives support, compliance, release, and future AI agents one durable account of who did what, under which tenant and module, with what result. Module tables remain the source of record for domain state; audit rows are the redacted narrative that explains sensitive platform actions across modules. QTI references this table rather than redefining a module-local primitive.

Lifecycle

Inserted once by shared audit middleware at the end of a high-risk operation or authorization decision. Rows are append-only: corrections are represented by a later compensating audit row, not by updating or deleting the original. Retention cleanup requires a named maintenance action and its own audit row. QTI uses the Platform lifecycle exactly; compatibility views or bridge migrations are transitional mechanics only.

Trace

Cross-Module Audit Log, Student Data Privacy And PII Handling, Observability, Metrics, And SLOs, ITD-025 Platform Substrate Inheritance

PITD-009 defines platform.audit_log as the shared append-only operational table. QTI ITD-025 requires this platform primitive to remain the public data-contract truth. Canonical Platform dictionary: https://platform3-andymontgomery-9773s-projects.vercel.app/platform/1edtech/data_dictionary/

Relationships

  • Belongs to one platform.tenant.
  • May reference one platform.idempotency_key row when the audited operation used Idempotency-Key.
  • Refers to module resources by resource_type and resource_id instead of foreign keys because target tables vary by module.
  • One request_id or trace_id can appear in multiple audit rows when a command touches multiple resources.
  • QTI inherited use: QTI writes, learner-runtime deletion, conformance/trust mutations, idempotency outcomes, and authorization denials append redacted platform.audit_log rows.

Integrity rules

  • Rows are append-only; ordinary application roles cannot update or delete them.
  • tenant_id must reference platform.tenant. Cross-tenant maintenance writes one row per affected tenant.
  • module, surface, action, and outcome must use documented allowed values.
  • redacted_metadata must be a JSON object and must not include raw request bodies, tokens, learner names, emails, phone numbers, package bytes, IP addresses, or user agents.
  • http_status must be an integer from 100 through 599.

Invalid examples

  • actor_subject = 'teacher@example.org' because actor subjects must be pseudonymous or hashed.
  • redacted_metadata stores raw QTI package bytes, processing traces with student identity, request headers, IP address, or access token.
  • A service-role tenant suspension changes platform.tenant.status without an audit row.
  • action = 'changed' or outcome = 'ok' because allowed values must be behaviorally explained.

Example row

{
  "audit_log_id": "a597b7de-b1dd-4c9e-93a3-9623cb90bc34",
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "module": "qti",
  "surface": "1edtech",
  "operation_id": "qti.packages.import",
  "actor_subject": "userhash:1bd7b5bd0d77",
  "actor_roles": [
    "teacher",
    "content-admin"
  ],
  "resource_type": "qti.content_package",
  "resource_id": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
  "action": "import",
  "outcome": "succeeded",
  "http_status": 201,
  "request_id": "req_20260521_0001",
  "trace_id": "trace_20260521_a1f4",
  "idempotency_key_id": "b81db6f4-c7ea-4757-b5aa-87570f7ad119",
  "occurred_at": "2026-05-21T12:01:08Z",
  "redacted_metadata": {
    "packageHash": "sha256:6a8c1f58c16f4b0b4f0a0d8c6be8d226e3d5d80f0b2f7d2f49e6d7d9e9d4f1cb",
    "resourceCount": 18,
    "fileCount": 42
  }
}

Common queries

insert into platform.audit_log (tenant_id, module, surface, operation_id, actor_subject, actor_roles, resource_type, resource_id, action, outcome, http_status, request_id, trace_id, idempotency_key_id, redacted_metadata) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);
select occurred_at, actor_subject, action, outcome, http_status from platform.audit_log where tenant_id = $1 and resource_type = $2 and resource_id = $3 order by occurred_at desc;
select module, surface, operation_id, outcome, count(*) from platform.audit_log where tenant_id = $1 and occurred_at >= now() - interval '7 days' group by module, surface, operation_id, outcome order by count(*) desc;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
audit_log_id Primary key
uuid
Required; default gen_random_uuid()

Stable identifier for one append-only audit event.

Edge case: Corrections never update this row; write a later audit event that references the corrected resource.

Must be a valid PostgreSQL UUID and unique as the primary key.

Invalid when: Not a UUID, reused, or generated outside the database without collision safeguards.

Inherited platform table

PITD-009 required audit_log_id. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No child table in this dictionary; support tools and future evidence exports may reference it.

Example: a597b7de-b1dd-4c9e-93a3-9623cb90bc34

tenant_id Foreign key
uuid
Required; no default

Tenant affected by the audited action.

Edge case: Authenticated authorization denials should use the route tenant after checking it is a real platform.tenant row; missing-auth events stay in security logs, not tenant audit rows.

Must reference platform.tenant(tenant_id). Cross-tenant maintenance must emit one row per affected tenant rather than a tenantless row.

Invalid when: Null, not found, copied from an untrusted path without JWT tenant validation, or used to record a resource outside the tenant.

Inherited platform table

PITD-009 required tenant_id and PITD-005 requires tenant scope enforcement. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Many audit rows belong to one platform.tenant.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

module Audit dimension
text
Required; no default

Module responsible for the operation being audited.

Edge case: Service-role maintenance that changes QTI records still uses module=qti even while qti/1edtech is under_reconciliation; release readiness belongs to module_release_status in the registry. CASE standards-browser maintenance still uses module=case unless the action changes only platform.* rows.

Must satisfy module_key_allowed: platform, qti, oneroster, caliper, case, nweamap, ed_fi, people_and_orgs, curriculum, content, events, results, analytics.

Invalid when: Null, outside module_key, set to platform for module resource changes, or used by support tooling as proof that the module surface is approved.

Allowed values: Platform module_key

Inherited platform table

PITD-002 defines module boundaries and PITD-009 requires module in audit rows. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with surface and operation_id for operational reporting.

Example: case

surface Audit dimension
text
Required; no default

Surface through which the action was initiated or exposed.

Edge case: Background jobs spawned by an Alpha request keep surface=alpha if the customer contract and audit trail originate there.

Must satisfy surface_code_allowed.

Invalid when: Null, outside surface_code, or set to alpha for expert-only conformance mutation.

Allowed values: Platform surface_code

Inherited platform table

PITD-015 defines surface model and derivation. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with module and operation_id.

Example: 1edtech

operation_id Audit dimension
text
Required; no default

Stable operation identifier from the customer website, OpenAPI, or platform maintenance command.

Edge case: Maintenance operation IDs should be named, such as platform.tenants.backfill-qti-view, not generic maintenance.

1 to 120 characters. Lowercase letters, digits, dots, underscores, and hyphens. Must match the documented operation whenever an API route triggered the row.

Invalid when: Blank, derived from localized prose, inconsistent with the endpoint that produced the event, or changed without docs and tests.

Inherited platform table

PITD-009 and PITD-010 require operation_id for audit and observability. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Same naming convention as platform.idempotency_key.operation_id.

Example: qti.packages.import

actor_subject
text
Required; no default

Pseudonymous actor identifier for the user, service, or release process that attempted the action.

Edge case: For service-role operations use service:<operation-or-system>, and record narrow roles/scopes in actor_roles.

Required non-empty text. Use an HMAC/hash or stable opaque subject such as userhash:<hex> or service:<name>. Must not be a name, email, phone number, raw JWT subject, access token, SIS ID, or parent/student contact detail.

Invalid when: Contains @, phone-like text, direct student/parent/teacher name, raw JWT sub, Bearer token, or service credentials.

Inherited platform table

PITD-008 requires pseudonymous learner/customer data in logs and audit rows. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Correlates with auth logs only through redacted identity systems.

Example: userhash:1bd7b5bd0d77

actor_roles
text[]
Required; default '{}'::text[]

Roles or scopes that justified the action or explain why authorization failed.

Edge case: An empty array is allowed only when actor_subject is a known service identity and operation_id explains the maintenance path.

Required array. Values should be lowercase scope/role slugs. Must not include tokens, emails, names, or tenant secrets.

Invalid when: Null, contains raw JWT claims with PII, includes access tokens, or omits the service-role scope for privileged operations.

Inherited platform table

PITD-005 requires explicit roles/scopes for privileged operations. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key in this dictionary; auth systems remain outside durable audit scope.

Example: {teacher,content-admin}

resource_type Resource lookup
text
Required; no default

Stable resource class affected by the action.

Edge case: For collection-level denials use a collection type such as qti.content_package with resource_id='unresolved'.

Required text. Use a table path or public object path such as platform.tenant, qti.content_package, alpha.activity, or qti.conformance_run. Must not include concrete IDs.

Invalid when: Blank, localized title, raw URL, or includes tenant/resource IDs.

Inherited platform table

PITD-009 required resource_type. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with resource_id. No database foreign key because target resource tables vary.

Example: qti.content_package

resource_id Resource lookup
text
Required; no default

Identifier of the primary resource affected by the action, or a documented sentinel when authorization failed before resource resolution.

Edge case: For import operations, resource_id may be the accepted package/job id while module tables later attach child resources.

Required text, 1 to 160 characters. Use the canonical resource ID for resource_type. Use unresolved only for authorization denials before a resource can be safely looked up.

Invalid when: Blank, direct learner PII, access token, raw path with query secrets, or an ID from another tenant.

Inherited platform table

PITD-009 required resource_id. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Pairs with resource_type for support queries.

Example: 7f2e4dd2-c147-49ea-af77-41a6fdd70980

action Audit dimension
text
Required; no default

Behavioral category of the audited action.

Edge case: When one request performs multiple high-risk actions, write multiple audit rows rather than collapsing them into a generic action.

Must satisfy audit_action_allowed.

Invalid when: Null, outside audit_action, or too vague to distinguish import from create, delete from runtime_delete, or read from read_privileged.

Allowed values: Platform audit_action

Inherited platform table

PITD-009 defines action semantics. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Used with outcome for reporting and incident review.

Example: import

outcome Audit dimension
text
Required; no default

Final result category for the action.

Edge case: Async operations start with accepted; later completion can be represented by a module state change and, when high-risk, another audit row.

Must satisfy audit_outcome_allowed.

Invalid when: Null, outside audit_outcome, inconsistent with http_status, or hides authorization failure as validation failure.

Allowed values: Platform audit_outcome

Inherited platform table

PITD-009 requires outcome and PITD-006 defines status/error policy. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Should align with http_status.

Example: succeeded

http_status
integer
Required; no default

HTTP status returned for the request or the HTTP-equivalent status assigned to a background/service operation.

Edge case: For non-HTTP maintenance, use the status the platform would return from the equivalent command API.

Required integer from 100 through 599. Must align with outcome: 2xx for accepted/succeeded, 403 for failed_authorization, 400 for failed_validation, 409/412/428 for failed_conflict, 404 for failed_not_found, and 5xx for failed_server.

Invalid when: Null, outside 100-599, or inconsistent with outcome.

Inherited platform table

PITD-006 defines HTTP status policy; PITD-009 requires http_status. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. Matches customer-visible Problem/status when the action came from an API route.

Example: 201

request_id Correlation
text
Required; no default

Per-request identifier exposed in Problem responses and support logs.

Edge case: Retries have separate request_id values even when they reuse an idempotency row.

Required non-empty text, 1 to 120 characters. Must be safe to return to customers. Must not include IP addresses, user agents, auth headers, or PII.

Invalid when: Null, blank, contains request headers/secrets, or changes within the same request flow.

Inherited platform table

PITD-006 requires requestId in Problem errors and PITD-010 requires structured logs. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

May match platform.idempotency_key.first_request_id for first attempts.

Example: req_20260521_0001

trace_id Correlation
text
Required; no default

Trace identifier that links logs, metrics, audit rows, and downstream spans for one request or job.

Edge case: If no distributed tracer is present, set trace_id equal to request_id until tracing is installed.

Required non-empty text, 1 to 160 characters. Use W3C traceparent-derived or platform trace ids. Must not include secrets or direct identity data.

Invalid when: Null, blank, contains auth tokens, or is regenerated per row inside the same request flow.

Inherited platform table

PITD-010 requires trace_id in structured logs and audit evidence. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Can appear on multiple audit rows and logs in the same request/job.

Example: trace_20260521_a1f4

idempotency_key_id Foreign key
uuid
Nullable; no default

Optional link to the idempotency row that governed the audited operation.

Edge case: On idempotency conflict, the audit row may point at the existing idempotency_key_id while outcome=failed_conflict.

Nullable. When present, must reference platform.idempotency_key(idempotency_key_id). Set null for operations that do not use Idempotency-Key.

Invalid when: Not a UUID, points to a different tenant, or stores the customer-supplied header value instead of the internal row id.

Inherited platform table

PITD-007 and PITD-009 connect retry behavior to audit. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Many audit rows may refer to one platform.idempotency_key row.

Example: b81db6f4-c7ea-4757-b5aa-87570f7ad119

occurred_at Ordering
timestamptz
Required; default now()

Timestamp when the audited action reached the recorded outcome.

Edge case: For async accepted work, occurred_at is acceptance time; final job completion has its own module state and possible audit row.

Required timestamp with time zone. Stored in UTC by database convention.

Invalid when: Null, manually backdated without maintenance evidence, or compared as local time.

Inherited platform table

PITD-009 required occurred_at. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

Used with tenant_id and resource lookup indexes.

Example: 2026-05-21T12:01:08Z

redacted_metadata
jsonb
Required; default '{}'::jsonb

Small, safe, structured context that helps explain the audit event without storing raw sensitive data.

Edge case: For validation failures, store safe error codes and field names, not the full rejected payload.

Must be a JSON object. Allowed content includes counts, hashes, profile names, version numbers, safe problem codes, and redacted summaries. Must not include raw request bodies, tokens, learner names, emails, phone numbers, SIS IDs, raw package bytes, IP addresses, user agents, or direct PII.

Invalid when: Null, non-object JSON, stores secrets/PII/raw bodies, or becomes the only place a required relationship is stored.

Inherited platform table

PITD-008 and PITD-009 require redacted audit metadata. Inherited into QTI by ITD-025; do not redefine this field in qti.*.

Platform shared, ITD-025 Platform Substrate Inheritance

No foreign key. If a metadata field becomes query-critical, promote it to a typed column in a later dictionary attempt.

Example: {"packageHash":"sha256:6a8c1f58c16f4b0b4f0a0d8c6be8d226e3d5d80f0b2f7d2f49e6d7d9e9d4f1cb","resourceCount":18}

Table

qti.tenant

Backward-compatible QTI tenant shape exposed over platform.tenant for older QTI migrations and queries.

QTI compatibility view Primary key tenant_id

Purpose

This is not the QTI tenant source of truth. It is the predecessor qti.tenant shape retained as a compatibility view or bridge while QTI downstream deliverables migrate to platform.tenant. New foreign keys and protected tenant routes use platform.tenant(tenant_id).

Lifecycle

Rows are created, updated, suspended, archived, and audited through platform.tenant. A qti.tenant compatibility view may expose tenant_id, tenant_key, display_name, and created_at for old QTI queries; new writes should target platform.tenant and let the bridge expose the old projection.

Trace

ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-025 Platform Substrate Inheritance

QTI ITD-025 supersedes the pre-Platform qti.tenant table and allows only a rollback-approved compatibility view or migration bridge.

Relationships

  • Compatibility projection of platform.tenant.
  • QTI-owned tenant_id foreign keys point to platform.tenant, not to this compatibility view.
  • The demo alias maps to the seeded demo row in platform.tenant and is not persisted as tenant_id.

Integrity rules

  • tenant_id, tenant_key, display_name, and created_at must match the backing platform.tenant row.
  • tenant_key follows platform.tenant format and uniqueness rules.
  • No new QTI table may reference qti.tenant as its source-of-truth foreign key.

Invalid examples

  • Creating a module-local qti.tenant table as the tenant truth.
  • A qti.tenant projection with a tenant_id that is missing from platform.tenant.
  • Using tenant_key or display_name as authorization evidence.

Example row

{
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "tenant_key": "north-valley",
  "display_name": "North Valley School District",
  "created_at": "2026-05-20T12:30:00Z"
}

Common queries

select tenant_id from qti.tenant where tenant_key = 'north-valley'; -- compatibility read; source row is platform.tenant
insert into platform.tenant (tenant_key, display_name, status, metadata) values ($1, $2, 'provisioning', '{}'::jsonb) returning tenant_id; -- preferred write target
create or replace view qti.tenant as select tenant_id, tenant_key, display_name, created_at from platform.tenant; -- compatibility bridge

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
tenant_id Compatibility primary key
uuid
Required; no default

Stable platform.tenant identifier exposed through the legacy qti.tenant shape.

Must match one platform.tenant(tenant_id). Do not create a separate qti.tenant-owned UUID sequence.

Invalid when: Not parseable as UUID, absent from platform.tenant, generated independently by QTI, or copied into QTI XML as assessment content.

QTI compatibility view

Compatibility projection of platform.tenant.tenant_id.

ITD-008 Tenant Boundary, ITD-025 Platform Substrate Inheritance

One qti.tenant compatibility row maps to exactly one platform.tenant row.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

tenant_key Compatibility unique
text
Required; no default

Human-stable platform.tenant lookup key exposed through the legacy QTI projection.

Must follow platform.tenant.tenant_key rules: 3 to 64 lowercase ASCII letters, digits, and hyphens; starts and ends with a letter or digit; unique in platform.tenant.

Invalid when: Duplicated, empty, uppercase, contains spaces, treated as an authorization secret, treated as a QTI identifier, or confused with the route-level demo tenantId alias.

QTI compatibility view

Compatibility projection of platform.tenant.tenant_key.

ITD-008 Tenant Boundary, ITD-025 Platform Substrate Inheritance

No foreign keys should point at tenant_key; use tenant_id against platform.tenant.

Example: north-valley

display_name
text
Required; no default

Customer-facing platform.tenant label exposed for older QTI tools.

Must match platform.tenant.display_name: required 1 to 160 visible characters after trimming.

Invalid when: Null, blank, over 160 characters, used as authorization, or used in place of tenant_id for joins.

QTI compatibility view

Compatibility projection of platform.tenant.display_name.

ITD-008 Tenant Boundary, ITD-025 Platform Substrate Inheritance

None. Join by tenant_id; display_name may change without changing tenant identity.

Example: North Valley School District

created_at
timestamptz
Required; no default

Timestamp when the backing platform.tenant row was inserted.

Required timestamp with time zone inherited from platform.tenant. It is read through qti.tenant, not owned by QTI.

Invalid when: Null, manually backdated without platform audit evidence, or compared as local time without timezone normalization.

QTI compatibility view

Compatibility projection of platform.tenant.created_at.

ITD-022 Operational DDL Discipline, ITD-025 Platform Substrate Inheritance

None.

Example: 2026-05-20T12:30:00Z

Table

qti.content_package

One imported IMS Content Package or loose XML bundle, scoped to a tenant.

Gap fill row with 1EdTech pass-through values Primary key package_id

Purpose

Records the package-level identity, import lifecycle, manifest identifier, hash, idempotency evidence, and metadata for an uploaded QTI package. This is where package ingest becomes durable before resources, files, artifacts, and versions are projected, and it is the storage source for the listPackages row shape.

Lifecycle

Created by package ingest. The row starts as importing, becomes imported after validation/projection, rejected after validation failure, or superseded when later content replaces it operationally. listPackages enumerates these tenant-owned package rows in stable ascending (imported_at, package_id) order with cursor + limit paging.

Trace

ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes, ITD-020 Validation And Rejection Policy, ITD-025 Platform Substrate Inheritance, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

The table is a persistence gap fill. manifest_identifier preserves IMS manifest data when present; package hashes, tenant scope, import state, listPackages recovery fields, and package evidence are QTI decisions that inherit platform.tenant and platform.idempotency_key.

Relationships

  • Belongs to one platform.tenant.
  • Optionally references one platform.idempotency_key row for package-ingest retry replay.
  • Parent of qti.package_resource, qti.package_file, and imported qti.artifact rows.
  • Enumerated by listPackages as {packageId, manifestIdentifier, qtiProfile, importStatus, packageHash, idempotencyKey, importedAt}.

Integrity rules

  • Unique (tenant_id, idempotency_key) preserves the package key copy when non-null.
  • platform_idempotency_key_id must reference platform.idempotency_key for public package ingest.
  • Unique (tenant_id, package_hash).
  • import_status must be one of the documented package lifecycle values.
  • listPackages must filter by tenant_id, return 200 with an empty {items: [], nextCursor: null} page for a fresh tenant, and use a cursor created from stable ascending (imported_at, package_id) order.

Invalid examples

  • Same tenant imports different bytes with the same idempotency_key.
  • Public API ingest has idempotency_key but no platform_idempotency_key_id.
  • package_hash is missing or not the normalized package hash.
  • import_status outside importing/imported/rejected/superseded.
  • A listPackages implementation returns 404 for a fresh tenant or omits idempotency_key from the row shape.

Example row

{
  "package_id": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "source_uri": "publisher/grade-6-math/qti.zip",
  "idempotency_key": "pkg-upload-2026-05-20-001",
  "platform_idempotency_key_id": "b81db6f4-c7ea-4757-b5aa-87570f7ad119",
  "package_hash": "sha256:6a8c1f58c16f4b0b4f0a0d8c6be8d226e3d5d80f0b2f7d2f49e6d7d9e9d4f1cb",
  "manifest_identifier": "MANIFEST-G6-MATH-2026",
  "qti_profile": "qti-3.0",
  "import_status": "imported",
  "metadata": {
    "resourceCount": 18,
    "fileCount": 42
  },
  "imported_at": "2026-05-20T13:00:00Z"
}

Common queries

select package_id as "packageId", manifest_identifier as "manifestIdentifier", qti_profile as "qtiProfile", import_status as "importStatus", package_hash as "packageHash", idempotency_key as "idempotencyKey", imported_at as "importedAt" from qti.content_package where tenant_id = $1 order by imported_at asc, package_id asc limit $2; -- raw path for listPackages
select package_id, import_status from qti.content_package where tenant_id = $1 and package_hash = $2;
select package_id from qti.content_package where tenant_id = $1 and platform_idempotency_key_id = $2;
select cp.package_id, ik.status from qti.content_package cp join platform.idempotency_key ik on ik.idempotency_key_id = cp.platform_idempotency_key_id where cp.tenant_id = $1 and ik.idempotency_key = $2;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
package_id Primary key
uuid
Required; no default

Stable identifier assigned to one package ingest record.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused across package rows.

Platform gap fill

Platform package identity.

ITD-009 Package Resource And File Ingest

None.

Example: 7f2e4dd2-c147-49ea-af77-41a6fdd70980

tenant_id Foreign key
uuid
Required; no default

Tenant that owns the package and all extracted resources.

Must reference platform.tenant(tenant_id). The route tenant, JWT tenant claim, package row, and all child rows must agree.

Invalid when: References a missing tenant or disagrees with the tenant path in the API request.

Platform gap fill

Tenant boundary inherited from Platform.

ITD-008 Tenant Boundary, ITD-009 Package Resource And File Ingest, ITD-025 Platform Substrate Inheritance

Belongs to exactly one platform.tenant; not nullable. The qti.tenant compatibility view may expose the same tenant_id for old queries.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

source_uri
text
Nullable; no default

Original filename, URI, or content-addressable reference supplied by the ingest caller.

Nullable text. It is diagnostic only and must not be used as a trusted package locator after ingest.

Invalid when: Used as a primary identity, contains secrets, or points outside tenant authorization.

Platform gap fill

Operational ingest evidence.

ITD-009 Package Resource And File Ingest

None.

Example: publisher/grade-6-math/qti.zip

idempotency_key Unique with tenant_id when non-null
text
Nullable; no default

Package-specific copy of the customer-supplied Idempotency-Key, retained for explainable QTI package rows while replay ownership lives in platform.idempotency_key. listPackages exposes this value so a caller who lost the original ingest response can recover the key needed for same-key replay.

Unique with tenant_id when non-null. PostgreSQL allows multiple nulls for non-API repair/import rows; the public package-ingest API requires a non-empty Idempotency-Key and matching platform_idempotency_key_id. listPackages must return it as idempotencyKey when present.

Invalid when: Same tenant reuses the key for different package bytes, it differs from the referenced platform.idempotency_key.idempotency_key, the public API omits it on ingest, listPackages hides it when present, or a secret/token is stored here.

Platform gap fill

QTI package evidence for inherited platform idempotency behavior.

ITD-010 Idempotency And Hashes, ITD-018 API Boundary, ITD-025 Platform Substrate Inheritance, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

Copies platform.idempotency_key.idempotency_key for package queries; the authoritative retry row is platform_idempotency_key_id.

Example: pkg-upload-2026-05-20-001

platform_idempotency_key_id Foreign key
uuid
Nullable; no default

Reference to the shared Platform retry ledger row that claimed the package-ingest Idempotency-Key and can replay the original response when the caller persisted the key.

Must reference platform.idempotency_key(idempotency_key_id) for public package ingest. Nullable only for rollback-approved legacy, repair, or non-API import rows that predate the Platform ledger.

Invalid when: References a missing platform idempotency row, references another tenant/module/surface/operation, is null for public package ingest, or contradicts idempotency_key/request_hash.

Platform gap fill

Inherited Platform idempotency linkage required by QTI ITD-010 and ITD-025.

ITD-010 Idempotency And Hashes, ITD-018 API Boundary, ITD-025 Platform Substrate Inheritance, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

Belongs to zero or one platform.idempotency_key row; for public API ingest it must be present and must have module=qti, surface=1edtech, method=POST, operation_id=ingestContentPackage, and the same tenant_id.

Example: b81db6f4-c7ea-4757-b5aa-87570f7ad119

package_hash Unique with tenant_id
text
Required; no default

Cryptographic hash of the normalized package payload used to identify repeated imports.

Required text and unique with tenant_id. Store the algorithm prefix with the digest, such as sha256:<hex>. listPackages returns it as packageHash so enumeration recovery can locate a package when the caller did not persist the Idempotency-Key.

Invalid when: Missing, not reproducible from the normalized package, or reused for different bytes in one tenant.

Platform gap fill

Idempotent persistence and audit.

ITD-010 Idempotency And Hashes, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: sha256:6a8c1f58c16f4b0b4f0a0d8c6be8d226e3d5d80f0b2f7d2f49e6d7d9e9d4f1cb

manifest_identifier
text
Nullable; no default

IMS manifest identifier copied from imsmanifest when the package has one.

Nullable because loose XML ingest may not include imsmanifest. If present, preserve the source value without Alpha renaming.

Invalid when: Invented when no manifest exists, changed to a platform name, or used as a database primary key.

1EdTech pass-through

IMS content package manifest identifier.

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

None.

Example: MANIFEST-G6-MATH-2026

qti_profile
text
Required; default 'qti-3.0'

Conformance profile asserted for this import.

Required text. Current default and target profile is qti-3.0; optional feature sets must be explicit and supported by conformance evidence.

Invalid when: Unsupported profile string, null, or used to imply the live network spec changed the accepted bundle.

Platform gap fill

Platform conformance profile label tied to the offline QTI 3.0 bundle.

ITD-001 Offline 1EdTech Source Bundle, ITD-017 Conformance Evidence

None.

Example: qti-3.0

import_status
text
Required; default 'imported'

Current lifecycle state of package ingest.

Must satisfy content_package_import_status_ck. listPackages reports the value as importStatus; only imported rows are authoritative for current delivery and artifact recovery.

Invalid when: Outside the enum set or inconsistent with resource/artifact projection state.

Allowed values: Package import status

Platform gap fill

Platform package import lifecycle.

ITD-009 Package Resource And File Ingest, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: imported

metadata
jsonb
Required; default '{}'::jsonb

Generated package-level import evidence such as manifest facts, QTI metadata summaries, counts, validation diagnostics, and vocabulary projections.

Required JSON object. Do not store package bytes or secrets here; store original bytes in qti.package_file.

Invalid when: Null, non-object JSON, direct learner PII, access tokens, or duplicated source XML bytes.

Platform gap fill

Generated import evidence envelope.

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy

None.

Example: {"resourceCount":18,"fileCount":42}

imported_at
timestamptz
Required; default now()

Timestamp when the package row was inserted and the timestamp component of the stable listPackages ordering.

Required timestamp with time zone. listPackages orders by imported_at then package_id and returns this value as importedAt.

Invalid when: Null or used as the source of content version ordering instead of artifact_version.version_number.

Platform gap fill

Import audit metadata.

ITD-022 Operational DDL Discipline, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: 2026-05-20T13:00:00Z

Table

qti.package_resource

IMS manifest resource row with resource identifiers, type, href, dependencies, and metadata.

Gap fill row with 1EdTech pass-through values Primary key resource_id

Purpose

Indexes manifest resources from an imported package without rewriting IMS/QTI names. The row lets delivery, authoring, export, diagnostics, and PCI client rendering find the primary XML, JavaScript module, or asset files associated with each manifest resource.

Lifecycle

Created during package ingest after manifest parsing and package-closure checks. Deleted when the owning content package is deleted.

Trace

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-032 QTI Results Reporting And Caliper Boundary

The row is a persistence gap fill; resource_identifier, resource_type, href, dependencies, and manifest metadata preserve IMS/QTI package values, including PCI interaction-module resource references and QTI Usage Data resources.

Relationships

  • Belongs to one qti.content_package.
  • May be referenced by qti.package_file and qti.artifact.
  • PCI module resources point to qti.package_file bytes for client rendering; Usage Data resources become artifact_kind=usage-data artifacts when they are QTI root documents.

Integrity rules

  • Unique (package_id, resource_identifier).
  • resource_type should be one of the IMS/QTI package resource vocabulary values.
  • Package hrefs and dependencies must stay inside the package closure.

Invalid examples

  • A resource_identifier duplicated in one package.
  • An href that escapes the package root with ../.
  • A resource_type not recognized by the source bundle and not marked extension.

Example row

{
  "resource_id": "aafcefe4-9814-4d43-93be-556dc38dace0",
  "package_id": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
  "resource_identifier": "item-RESPONSE-001",
  "resource_type": "imsqti_item_xmlv3p0",
  "href": "items/response-001.xml",
  "dependencies": [
    "stimulus-READING-PASSAGE-1"
  ],
  "metadata": {
    "files": [
      "items/response-001.xml",
      "media/chart.png"
    ]
  }
}

Common queries

select resource_identifier, resource_type, href from qti.package_resource where package_id = $1 order by resource_identifier;
select resource_id from qti.package_resource where package_id = $1 and resource_identifier = $2;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
resource_id Primary key
uuid
Required; no default

Stable identifier for this manifest resource row.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused by another package_resource row.

Platform gap fill

Platform row identity.

ITD-009 Package Resource And File Ingest

None.

Example: aafcefe4-9814-4d43-93be-556dc38dace0

package_id Foreign key
uuid
Required; no default

Owning content package.

Must reference qti.content_package(package_id). Cascades on package delete.

Invalid when: Missing package, cross-tenant package/resource mixture, or null.

Platform gap fill

Package ownership boundary.

ITD-009 Package Resource And File Ingest

Belongs to exactly one qti.content_package.

Example: 7f2e4dd2-c147-49ea-af77-41a6fdd70980

resource_identifier Unique with package_id
text
Required; no default

IMS manifest resource identifier copied from imsmanifest.

Required text and unique within the package. Preserve source spelling.

Invalid when: Duplicated within a package, rewritten to a platform UUID, or missing when the manifest resource has an identifier.

1EdTech pass-through

IMS manifest resource identifier.

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

None.

Example: item-RESPONSE-001

resource_type
text
Required; no default

IMS/QTI resource type from the package manifest.

Required text. Values should match the package resource vocabulary or be explicitly preserved as extension.

Invalid when: Blank, invented by Alpha naming, or used to bypass validation.

Allowed values: IMS/QTI package resource type

1EdTech pass-through

IMS/QTI package resource type value.

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

None.

Example: imsqti_item_xmlv3p0

href
text
Nullable; no default

Package-relative path to the resource's primary file.

Nullable text. If present, must be normalized and must not escape the package root.

Invalid when: Absolute URL for a packaged file, path traversal, unnormalized slashes, or missing package_file for a required primary file.

1EdTech pass-through

IMS package resource href.

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy

Usually corresponds to one qti.package_file.package_path.

Example: items/response-001.xml

dependencies
jsonb
Required; default '[]'::jsonb

Manifest dependency references and variant resource links generated from the package manifest.

Required JSON array. Each referenced resource identifier should resolve within the same package.

Invalid when: Null, non-array JSON, unresolved dependency, or cross-package dependency not represented as allowed external metadata.

1EdTech pass-through

IMS package resource dependency references.

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy

References other qti.package_resource.resource_identifier values within the same package.

Example: ["stimulus-READING-PASSAGE-1"]

metadata
jsonb
Required; default '{}'::jsonb

Manifest-derived metadata, file list, and resource facts generated at ingest.

Required JSON object. Keep generated manifest/resource evidence here; keep raw bytes in qti.package_file.

Invalid when: Null, non-object JSON, direct learner PII, auth tokens, or package bytes.

1EdTech pass-through

IMS manifest resource metadata projection when copied from manifest; generated file inventory remains gap-fill metadata.

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

None.

Example: {"files":["items/response-001.xml","media/chart.png"]}

Table

qti.package_file

Original file bytes from an imported IMS/QTI package.

Gap fill row with 1EdTech pass-through values Primary key package_file_id

Purpose

Preserves the exact package file payloads needed for export, audit, validation diagnostics, media delivery, and Portable Custom Interaction client rendering. This includes imsmanifest XML, QTI XML, media, stylesheets, pronunciation lexicons, metadata XML, PCI JavaScript module files, and other package assets.

Lifecycle

Created during package ingest for each normalized package path. Deleted when the owning content package is deleted. resource_id is set when a manifest resource first listed the file.

Trace

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy, ITD-030 Portable Custom Interaction Persistence And Execution Boundary

The row and file hash are gap fills. package_path and content_bytes preserve package source values, including Portable Custom Interaction JavaScript module files and dependencies.

Relationships

  • Belongs to one qti.content_package.
  • Optionally points to the qti.package_resource that first listed the file.
  • PCI JavaScript module files are ordinary package files; they are served to the delivery client but never executed server-side.

Integrity rules

  • Unique (package_id, package_path).
  • package_path must be normalized and package-relative.
  • byte_length must match content_bytes length.
  • PCI module files remain bytes indexed by manifest/resource links; do not normalize them into executable server code.

Invalid examples

  • Two files with the same normalized package_path in one package.
  • package_path escapes package root.
  • content_hash does not match content_bytes.
  • A PCI JavaScript module is executed by the server or promoted into a custom-interaction table.

Example row

{
  "package_file_id": "30225f0a-949d-49d5-a7fc-e2ea43863e96",
  "package_id": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
  "resource_id": "aafcefe4-9814-4d43-93be-556dc38dace0",
  "package_path": "items/response-001.xml",
  "media_type": "application/xml",
  "byte_length": 18422,
  "content_hash": "sha256:92fb3e7a6e6d1df0e5759bda79b9c5a0459dfb7ccf2e0b9eb81227759df8c371",
  "metadata": {
    "listedInManifest": true
  },
  "created_at": "2026-05-20T13:00:01Z"
}

Common queries

select package_path, media_type, byte_length from qti.package_file where package_id = $1 order by package_path;
select content_bytes from qti.package_file where package_id = $1 and package_path = $2;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
package_file_id Primary key
uuid
Required; no default

Stable identifier for one original package file row.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform file row identity.

ITD-009 Package Resource And File Ingest

None.

Example: 30225f0a-949d-49d5-a7fc-e2ea43863e96

package_id Foreign key
uuid
Required; no default

Owning content package.

Must reference qti.content_package(package_id). Cascades on package delete.

Invalid when: References a missing package or mixes tenants.

Platform gap fill

Package ownership boundary.

ITD-009 Package Resource And File Ingest

Belongs to exactly one qti.content_package.

Example: 7f2e4dd2-c147-49ea-af77-41a6fdd70980

resource_id Foreign key
uuid
Nullable; no default

Manifest resource that first listed this file, when applicable.

Nullable. If present, must reference qti.package_resource(resource_id). Set null if the resource row is deleted.

Invalid when: References a resource from a different package or tenant.

Platform gap fill

Platform linkage from manifest resource to file preservation, including PCI interaction-module files.

ITD-009 Package Resource And File Ingest, ITD-030 Portable Custom Interaction Persistence And Execution Boundary

Belongs to zero or one qti.package_resource.

Example: aafcefe4-9814-4d43-93be-556dc38dace0

package_path Unique with package_id
text
Required; no default

Normalized package-relative path for this file.

Required and unique inside package. Must not be absolute or contain traversal that escapes the package root.

Invalid when: Contains ../ escape, backslash ambiguity, an absolute scheme, duplicate normalized path, or a path not present in the uploaded package.

1EdTech pass-through

IMS package-relative file path.

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy

None.

Example: items/response-001.xml

media_type
text
Required; default 'application/octet-stream'

Detected or declared media type used for export, diagnostics, and content serving.

Required text. Use a valid media type string; default is application/octet-stream when unknown.

Invalid when: Null, unparseable as a media type, or trusted more than validation of the actual content.

Platform gap fill

Generated file metadata for platform serving and diagnostics.

ITD-009 Package Resource And File Ingest

None.

Example: application/xml

byte_length
integer
Required; no default

Original byte length of content_bytes.

Required integer. Must be zero or positive and equal to the stored bytea length.

Invalid when: Negative, null, or mismatched with content_bytes.

Platform gap fill

Generated package file evidence.

ITD-009 Package Resource And File Ingest

None.

Example: 18422

content_hash
text
Required; no default

Cryptographic hash of the original file bytes.

Required text. Store algorithm prefix with digest; must be reproducible from content_bytes.

Invalid when: Missing, digest does not match content_bytes, or algorithm is not recorded.

Platform gap fill

File-level audit and export integrity evidence.

ITD-010 Idempotency And Hashes

None.

Example: sha256:92fb3e7a6e6d1df0e5759bda79b9c5a0459dfb7ccf2e0b9eb81227759df8c371

content_bytes
bytea
Required; no default

Original bytes exactly as accepted from the package for this path, including QTI XML, media, and Portable Custom Interaction JavaScript module files.

Required byte array. Must be preserved for faithful package export, audit, media delivery, and PCI client rendering. PCI JavaScript bytes may be returned to a delivery client, but the platform must not execute them server-side.

Invalid when: Null, replaced by parsed text only, mutated after ingest, contains a file that failed package-closure validation, or loaded into the server runtime as executable PCI code.

1EdTech pass-through

Original package file bytes.

ITD-009 Package Resource And File Ingest, ITD-004 XML Authority And Canonical Hashes, ITD-030 Portable Custom Interaction Persistence And Execution Boundary

None.

Example: <binary XML or media bytes>

metadata
jsonb
Required; default '{}'::jsonb

Generated evidence about the file, such as manifest listing flags, validation role, and extracted diagnostics.

Required JSON object. Must not replace content_bytes.

Invalid when: Null, non-object JSON, auth tokens, direct learner PII, or raw file bytes duplicated as JSON.

Platform gap fill

Generated file-preservation evidence.

ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy

None.

Example: {"listedInManifest":true,"role":"primary-qti-xml"}

created_at
timestamptz
Required; default now()

Timestamp when the file row was inserted.

Required timestamp with time zone.

Invalid when: Null or used as a proxy for QTI content versioning.

Platform gap fill

Package-file audit metadata.

ITD-022 Operational DDL Discipline

None.

Example: 2026-05-20T13:00:01Z

Table

qti.artifact

Stable logical QTI document or package artifact across immutable versions.

Gap fill row with 1EdTech pass-through values Primary key artifact_id

Purpose

Represents one logical QTI thing: item, test, section, stimulus, outcome declaration, response processing, result, usage data, metadata, or manifest-only resource. QTI identifiers remain QTI-domain identifiers; artifact_id is the platform identity for version history, operation-specific artifact APIs, listArtifacts, QTI Results Reporting documents, and QTI Usage Data documents.

Lifecycle

Created during ingest or authoring. New edits create qti.artifact_version rows instead of replacing the artifact. latest_version_id is a convenience pointer to the newest version, the natural lookup path for getAuthoringJson, and the latestArtifactVersionId returned by listArtifacts.

Trace

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-004 XML Authority And Canonical Hashes, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-032 QTI Results Reporting And Caliper Boundary

The artifact row and kind are gap fills. qti_identifier, title, and language pass through when copied from QTI XML or package defaults. result and usage-data kinds are the stock QTI Results Reporting and Usage Data homes pinned by ITD-032. ITD-026 exposes a tenant-owned enumeration over this existing table; it adds no storage.

Relationships

  • Belongs to one platform.tenant.
  • May originate from qti.content_package and qti.package_resource.
  • Parent of qti.artifact_version.
  • Looked up by tenant_id plus artifact_id for getAuthoringJson.
  • Enumerated by listArtifacts as {artifactId, artifactKind, qtiIdentifier, title, latestArtifactVersionId, createdAt}.

Integrity rules

  • artifact_kind must satisfy artifact_kind_ck.
  • artifact_lookup_idx supports tenant/kind/QTI identifier lookup.
  • listArtifacts must filter by tenant_id, return 200 with an empty {items: [], nextCursor: null} page for a fresh tenant, and use a cursor created from stable ascending (created_at, artifact_id) order.
  • assessmentResult documents use artifact_kind=result; QTI Usage Data documents use artifact_kind=usage-data and keep item statistics inside the XML/document projection.

Invalid examples

  • Using qti_identifier as a database primary key.
  • Changing artifact_kind after versions exist.
  • Pointing latest_version_id to a version from another artifact.
  • A listArtifacts response omits latestArtifactVersionId for an artifact that has a latest_version_id.
  • Returning another tenant's artifact in a listArtifacts page.
  • Promoting usage-data item statistics into platform columns or emitting Caliper events from the QTI artifact row.

Example row

{
  "artifact_id": "58612cab-9c46-426c-8c20-0e9f19c807c5",
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "package_id": "7f2e4dd2-c147-49ea-af77-41a6fdd70980",
  "resource_id": "aafcefe4-9814-4d43-93be-556dc38dace0",
  "artifact_kind": "item",
  "qti_identifier": "RESPONSE-001",
  "title": "Linear equations checkpoint",
  "language": "en-US",
  "latest_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "created_at": "2026-05-20T13:00:02Z"
}

Common queries

select artifact_id as "artifactId", artifact_kind as "artifactKind", qti_identifier as "qtiIdentifier", title, latest_version_id as "latestArtifactVersionId", created_at as "createdAt" from qti.artifact where tenant_id = $1 order by created_at asc, artifact_id asc limit $2; -- raw path for listArtifacts
select artifact_id, latest_version_id from qti.artifact where tenant_id = $1 and (qti_identifier = $2 or title = $3) order by created_at asc, artifact_id asc; -- enumeration recovery after a lost ingest response
select artifact_id, latest_version_id from qti.artifact where tenant_id = $1 and artifact_kind = 'item' and qti_identifier = $2;
select latest_version_id from qti.artifact where tenant_id = $1 and artifact_id = $2; -- getAuthoringJson known-artifact lookup
select artifact_kind, count(*) from qti.artifact where package_id = $1 group by artifact_kind order by artifact_kind;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
artifact_id Primary key
uuid
Required; no default

Stable platform identity for one logical artifact across versions.

Valid UUID and unique primary key.

Invalid when: Not a UUID, reused, or derived from a mutable QTI identifier.

Platform gap fill

Platform logical artifact identity.

ITD-011 Artifact Versioning

None.

Example: 58612cab-9c46-426c-8c20-0e9f19c807c5

tenant_id Foreign key
uuid
Required; no default

Tenant that owns this artifact.

Must reference platform.tenant(tenant_id). It must match the owning package tenant when package_id is present.

Invalid when: Missing tenant or different from the owning package tenant.

Platform gap fill

Tenant ownership boundary inherited from Platform.

ITD-008 Tenant Boundary, ITD-011 Artifact Versioning, ITD-025 Platform Substrate Inheritance

Belongs to exactly one platform.tenant; old qti.tenant compatibility reads expose the same tenant_id only as a bridge.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

package_id Foreign key
uuid
Nullable; no default

Origin package for imported artifacts.

Nullable for authored loose artifacts. If present, must reference qti.content_package(package_id). Set null if package is deleted.

Invalid when: References a package owned by another tenant.

Platform gap fill

Package origin evidence.

ITD-009 Package Resource And File Ingest, ITD-011 Artifact Versioning

Belongs to zero or one qti.content_package.

Example: 7f2e4dd2-c147-49ea-af77-41a6fdd70980

resource_id Foreign key
uuid
Nullable; no default

Origin manifest resource for imported artifacts.

Nullable for generated or loose artifacts. If present, must reference qti.package_resource(resource_id). Set null if resource is deleted.

Invalid when: Resource comes from another package or tenant.

Platform gap fill

Manifest origin evidence.

ITD-009 Package Resource And File Ingest, ITD-011 Artifact Versioning

Belongs to zero or one qti.package_resource.

Example: aafcefe4-9814-4d43-93be-556dc38dace0

artifact_kind
text
Required; no default

Repository category derived from QTI root element or manifest resource type.

Must satisfy artifact_kind_ck.

Invalid when: Outside enum set, inconsistent with root_element on versions, changed for Alpha vocabulary, or used to create a QTI-owned Caliper event/result primitive.

Allowed values: Artifact kind

Platform gap fill

Gap-fill classification derived from QTI roots and package resources. ITD-032 pins result and usage-data as stock QTI document homes, not new platform result/statistics tables.

ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: item

qti_identifier
text
Nullable; no default

QTI identifier attribute copied from the root object when present. listArtifacts returns it as qtiIdentifier so a caller can recover an artifact by source QTI identity after a lost ingest response.

Nullable because not every artifact has a root identifier. Preserve source spelling; do not use as a globally unique database key. If present, the raw recovery query must still filter by tenant_id.

Invalid when: Invented when absent, rewritten to a UUID, or assumed unique outside artifact scope.

1EdTech pass-through

QTI identifier attribute.

ITD-004 XML Authority And Canonical Hashes, ITD-007 Provenance Labels, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: RESPONSE-001

title
text
Nullable; no default

QTI title or generated display label when present. listArtifacts returns it as title so a caller can recover and display an artifact without maintaining a client-side mirror.

Nullable. Preserve QTI title when present; generated labels must be distinguishable in metadata/spec trace. Title is a recovery aid, not a unique key.

Invalid when: Used as identity, translated without retaining source XML, or contains direct learner PII.

1EdTech pass-through

QTI title or package display metadata when copied from source.

ITD-004 XML Authority And Canonical Hashes, ITD-007 Provenance Labels, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: Linear equations checkpoint

language
text
Nullable; no default

xml:lang or package-default language associated with the artifact.

Nullable BCP 47 language tag when known.

Invalid when: Not a language tag, invented without source/default evidence, or used to filter tenant access.

1EdTech pass-through

xml:lang or package default language.

ITD-004 XML Authority And Canonical Hashes

None.

Example: en-US

latest_version_id Convenience pointer
uuid
Nullable; no default

Newest immutable artifact version for convenience reads, including the latest authoring_json projection returned by getAuthoringJson and the latestArtifactVersionId returned by listArtifacts.

Nullable until a version exists. Must point to a qti.artifact_version for this same artifact when populated. Must agree with the highest version_number before an authoring-json read returns an ETag. listArtifacts may expose null only for a just-created artifact with no persisted version yet.

Invalid when: Points to another artifact's version, lags the highest version_number without repair evidence, listArtifacts surfaces a stale latestArtifactVersionId, or is treated as authoritative history instead of the artifact_version table.

Platform gap fill

Platform version lookup convenience.

ITD-011 Artifact Versioning, ITD-018 API Boundary, ITD-020 Validation And Rejection Policy, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

Points to zero or one qti.artifact_version for the same artifact.

Example: efcf3561-3a66-4825-9588-e792ef20c312

created_at
timestamptz
Required; default now()

Logical artifact creation timestamp and the timestamp component of the stable listArtifacts ordering.

Required timestamp with time zone. listArtifacts orders by created_at then artifact_id and returns this value as createdAt.

Invalid when: Null or used as version_number.

Platform gap fill

Artifact audit metadata.

ITD-022 Operational DDL Discipline, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: 2026-05-20T13:00:02Z

Table

qti.artifact_version

Immutable XML, generated object graph, projections, and trace for one saved artifact edition.

Gap fill row with 1EdTech pass-through values Primary key artifact_version_id

Purpose

Binds original XML, canonical XML, generated object graph, public JSON projections, root/schema evidence, and spec trace to one immutable version. This table is the heart of faithful QTI round trips, stable historical delivery, operation-specific authoring-json reads, listArtifactVersions, test-structure delivery projection, PCI/template preservation, and result/usage-data interchange.

Lifecycle

Created on ingest or authoring save. Never updated in place except operational metadata that does not change content semantics. New edits append a later version_number; GET authoring-json reads the latest immutable version for a known artifact_id and supplies the current projection/version ETag for the next If-Match save. listArtifactVersions enumerates tenant-owned versions by joining through qti.artifact.

Trace

ITD-004 XML Authority And Canonical Hashes, ITD-006 JSON Projection Boundaries, ITD-011 Artifact Versioning, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

Versioning and JSON/object projection storage are gap fills. source_xml, canonical_xml, root_element, root_type, schema_file, and spec_trace preserve or derive from the 1EdTech source bundle. ITD-026 exposes tenant-owned enumeration over this existing table through its owning artifact. ITD-028, ITD-029, ITD-030, ITD-031, and ITD-032 use these same projections; they add no new table or field.

Relationships

  • Belongs to one qti.artifact.
  • Parent of qti.component, qti.variable_declaration, qti.processing_rule, qti.delivery_session, and qti.attempt references.
  • Enumerated by listArtifactVersions as {artifactVersionId, artifactId, versionNumber, xmlHash, rootElement, createdAt}, tenant-scoped through qti.artifact.tenant_id.

Integrity rules

  • Unique (artifact_id, version_number).
  • Unique (artifact_id, xml_hash).
  • source_xml must validate against bundled schema before persistence.
  • authoring_json returned by GET authoring-json must be the latest lossless projection for the known artifact_id, not a delivery_json reconstruction.
  • listArtifactVersions must join qti.artifact, filter qti.artifact.tenant_id, return 200 with an empty page for a fresh tenant, and use stable ascending (created_at, artifact_version_id) cursor order.
  • delivery_json must surface test-level constructs, qti-catalog-info, PCI markup/module references, qti-template body content, and qti-time-limits according to the scoped runtime rules; source_xml/canonical_xml remain the interchange authority.

Invalid examples

  • Mutating canonical_xml after attempts exist.
  • delivery_json drops an identifier needed for scoring.
  • schema_file not in the offline source bundle.
  • Serving delivery_json as authoring_json for an editor read, which would silently lose spec-defined authoring fields.
  • Selecting artifact_version rows by artifact_version_id without joining qti.artifact to prove tenant ownership.
  • A listArtifactVersions response includes a version owned by another tenant.
  • A delivery projection drops qti-selection, qti-catalog-info, qti-portable-custom-interaction, qti-template-processing, assessmentResult, or usageData content because runtime execution is deferred or handled elsewhere.

Example row

{
  "artifact_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "artifact_id": "58612cab-9c46-426c-8c20-0e9f19c807c5",
  "version_number": 1,
  "root_element": "qti-assessment-item",
  "root_type": "AssessmentItemDType",
  "schema_file": "imsqti_itemv3p0p1_v1p0.xsd",
  "xml_hash": "sha256:e8a86d190e6d8865c4562b8e8b2b1e299f8a8d37a58c0bb71b35ef28bb62ab31",
  "created_by": "system:package-ingest"
}

Common queries

select av.artifact_version_id as "artifactVersionId", av.artifact_id as "artifactId", av.version_number as "versionNumber", av.xml_hash as "xmlHash", av.root_element as "rootElement", av.created_at as "createdAt" from qti.artifact_version av join qti.artifact a on a.artifact_id = av.artifact_id where a.tenant_id = $1 order by av.created_at asc, av.artifact_version_id asc limit $2; -- raw path for listArtifactVersions
select artifact_version_id, version_number, xml_hash from qti.artifact_version where artifact_id = $1 order by version_number desc;
select delivery_json from qti.artifact_version where artifact_version_id = $1;
select av.artifact_version_id, av.version_number, av.xml_hash, av.authoring_json from qti.artifact a join qti.artifact_version av on av.artifact_id = a.artifact_id where a.tenant_id = $1 and a.artifact_id = $2 order by av.version_number desc limit 1; -- getAuthoringJson

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
artifact_version_id Primary key
uuid
Required; no default

Stable identifier for one immutable artifact edition.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform version identity.

ITD-011 Artifact Versioning

None.

Example: efcf3561-3a66-4825-9588-e792ef20c312

artifact_id Foreign key
uuid
Required; no default

Logical artifact this version belongs to; the join path that proves tenant ownership for listArtifactVersions and raw artifact-version reads.

Must reference qti.artifact(artifact_id). Cascades on artifact delete. Any tenant-scoped read must join qti.artifact and filter qti.artifact.tenant_id.

Invalid when: Missing artifact or cross-tenant mismatch through artifact.

Platform gap fill

Version belongs to logical artifact identity.

ITD-011 Artifact Versioning, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-008 Tenant Boundary

Belongs to exactly one qti.artifact.

Example: 58612cab-9c46-426c-8c20-0e9f19c807c5

version_number Unique with artifact_id
integer
Required; no default

Forward-only per-artifact version number returned by listArtifactVersions as versionNumber.

Required integer and unique with artifact_id. Should increase by one for each saved edition.

Invalid when: Zero or negative by convention, duplicated for an artifact, skipped without migration evidence, or reused after rollback.

Platform gap fill

Immutable version sequencing.

ITD-011 Artifact Versioning, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: 1

source_xml
xml
Required; no default

Original XML accepted after bundled XSD/Schematron validation.

Required PostgreSQL xml. Must validate before object creation and persistence.

Invalid when: Malformed XML, not valid against the bundled schema, contains direct learner runtime PII, or differs from the persisted object graph without trace.

1EdTech pass-through

Original QTI XML accepted from the 1EdTech source format.

ITD-004 XML Authority And Canonical Hashes, ITD-020 Validation And Rejection Policy

None.

Example: <qti-assessment-item identifier="RESPONSE-001" ...>

canonical_xml
text
Required; no default

Canonicalized XML used for equivalence checks and stable export.

Required text. Must round-trip from object graph to XML and hash to xml_hash.

Invalid when: Not reproducible from object_graph, hash mismatch, or changed after delivery sessions point to this version.

1EdTech pass-through

Canonical QTI XML derived from source XML under platform canonicalization rules.

ITD-004 XML Authority And Canonical Hashes

None.

Example: <qti-assessment-item identifier="RESPONSE-001" ...>

xml_hash Unique with artifact_id
text
Required; no default

Hash of canonical_xml used for idempotency and semantic preservation checks; returned by listArtifactVersions as xmlHash.

Required text and unique with artifact_id. Store algorithm prefix with digest.

Invalid when: Does not match canonical_xml, algorithm omitted, or duplicates a prior version for the same artifact.

Platform gap fill

Platform equivalence and idempotency evidence over QTI XML.

ITD-004 XML Authority And Canonical Hashes, ITD-010 Idempotency And Hashes, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: sha256:e8a86d190e6d8865c4562b8e8b2b1e299f8a8d37a58c0bb71b35ef28bb62ab31

root_element
text
Required; no default

Root XML element for this version; returned by listArtifactVersions as rootElement.

Required text generated from bundled XSD root element catalog.

Invalid when: Not present in the bundled root catalog or inconsistent with source_xml.

1EdTech pass-through

QTI root element from bundled schemas.

ITD-004 XML Authority And Canonical Hashes, ITD-002 Generated Object Model Hub, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: qti-assessment-item

root_type
text
Nullable; no default

Generated XSD type name for the root element.

Nullable for roots without generated type evidence; when present, must match bundled XSD generation.

Invalid when: Invented type, wrong schema namespace, or mismatch with root_element.

1EdTech pass-through

Generated XSD root type from bundled QTI schemas.

ITD-002 Generated Object Model Hub, ITD-004 XML Authority And Canonical Hashes

None.

Example: AssessmentItemDType

schema_file
text
Required; no default

Bundled schema file used as validation authority.

Required text. Must name a schema in the offline QTI source bundle.

Invalid when: Live network schema URL, missing local schema, or schema that does not define the root.

1EdTech pass-through

Bundled QTI XSD file.

ITD-001 Offline 1EdTech Source Bundle, ITD-020 Validation And Rejection Policy

None.

Example: imsqti_itemv3p0p1_v1p0.xsd

object_graph
jsonb
Required; no default

Canonical generated object-model graph serialized as JSONB for internal persistence.

Required JSON object. Must preserve element order, attributes, text, tail text, identifiers, namespaces, types, and source trace enough to rebuild canonical XML.

Invalid when: Null, non-object JSON, loses mixed-content tail text, loses namespace identity, or becomes a public delivery contract.

Platform gap fill

Generated internal object graph for persistence; QTI defines XML, not this JSONB envelope.

ITD-002 Generated Object Model Hub, ITD-005 Lossless Relational Projection

None.

Example: {"element":"qti-assessment-item","identifier":"RESPONSE-001"}

delivery_json
jsonb
Nullable; no default

Generated consumer-facing projection used by delivery applications and session snapshots.

Nullable before projection. May omit only declared authoring-only or diagnostic detail; must preserve identifiers needed for responses, feedback, scoring, accessibility matching, session replay, test-level navigation delegation, PCI client rendering, item-template presentation, and results/usage-data interchange where those documents are delivered.

Invalid when: Drops response identifiers, scoring dependencies, feedback links, accessibility references, test-navigation declarations, PCI markup/module references, template body references, or includes source traces not intended for delivery.

Platform gap fill

Generated delivery projection; QTI defines source XML, not this public JSON view. The projected QTI constructs remain stock pass-through values; runtime ownership is scoped by ITD-027 through ITD-032.

ITD-006 JSON Projection Boundaries, ITD-015 Delivery Session Snapshots, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: {"kind":"test","identifier":"fluency-180","timeLimits":{"maxTime":180,"allowLateSubmission":false},"sections":[...]}

authoring_json
jsonb
Nullable; no default

Generated authoring projection returned by GET /tenants/{tenantId}/qti/artifacts/{artifactId}/authoring-json for editors that must preserve all spec-defined fields.

Nullable before projection. Must round-trip to object_graph with no spec-defined field loss. When served by getAuthoringJson, it must come from the latest artifact_version for the known artifact_id and pair with an ETag derived from the immutable version/hash for the subsequent If-Match save.

Invalid when: Lossy, missing extension payloads, missing PCI/template/source trace needed for edits, built from delivery_json, stale relative to the selected latest version, or used for delivery without declared lossiness.

Platform gap fill

Generated authoring projection; QTI defines XML, not this editor JSON view. ITD-018 ships this operation-specific read because delivery_json may be declared-lossy.

ITD-006 JSON Projection Boundaries, ITD-011 Artifact Versioning, ITD-018 API Boundary, ITD-020 Validation And Rejection Policy, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning

None.

Example: {"kind":"item","identifier":"RESPONSE-001","sourceTrace":{...}}

spec_trace
jsonb
Required; default '{}'::jsonb

Generated traceability from classes, fields, and components to XSD/spec anchors.

Required JSON object. Must point back to the offline source bundle and generated model evidence.

Invalid when: Null, non-object JSON, live-only references, or links that cannot be reproduced from the bundle.

1EdTech pass-through

Generated traceability to bundled XSD/spec sources.

ITD-001 Offline 1EdTech Source Bundle, ITD-007 Provenance Labels

None.

Example: {"rootElement":"qti-assessment-item","schemaFile":"imsqti_itemv3p0p1_v1p0.xsd"}

supersedes_version_id Foreign key
uuid
Nullable; no default

Previous version replaced by this version, when the save was an edit or replacement.

Nullable. If present, must reference qti.artifact_version(artifact_version_id), normally for the same artifact.

Invalid when: Points forward in time, points to another artifact without explicit migration evidence, or creates a cycle.

Platform gap fill

Platform version history link.

ITD-011 Artifact Versioning

References zero or one earlier qti.artifact_version.

Example: a1f6b903-b26f-43ac-93ce-f6626ce3810d

created_at
timestamptz
Required; default now()

Timestamp when this immutable version was created and the timestamp component of the stable listArtifactVersions ordering.

Required timestamp with time zone. listArtifactVersions orders by created_at then artifact_version_id and returns this value as createdAt.

Invalid when: Null or mutated to reorder version history.

Platform gap fill

Version audit metadata.

ITD-022 Operational DDL Discipline, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

None.

Example: 2026-05-20T13:00:03Z

created_by
text
Nullable; no default

Principal or system actor that created the version.

Nullable text. Must be a safe principal label; do not store raw JWTs or secrets.

Invalid when: Raw access token, raw JWT subject that identifies a learner, email address, or other direct learner PII.

Platform gap fill

Platform audit metadata.

ITD-019 Security Boundary, ITD-024 Candidate And Learner Data Privacy

None.

Example: system:package-ingest

Table

qti.component

Lossless relational projection of generated QTI object-graph nodes.

Gap fill row with 1EdTech pass-through values Primary key component_id

Purpose

Stores ordered object-graph nodes so the repository can query, diff, validate, and rehydrate QTI content without making relational rows the source of truth. This is the pass-through home for test-level navigation constructs, catalog/PNP accessibility declarations, Portable Custom Interaction markup, item-template printed variables, and result/usage-data element trees that are not promoted into their own QTI tables.

Lifecycle

Created whenever an artifact_version object graph is persisted. Deleted when the owning artifact_version is deleted.

Trace

ITD-005 Lossless Relational Projection, ITD-004 XML Authority And Canonical Hashes, ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

The table, row IDs, parent links, ordinal, and path are gap fills. Element names, namespaces, type names, identifiers, attributes, text, tail text, and trace preserve generated QTI values. ITD-028/029/030/031/032 pin that their QTI constructs persist here without adding new storage.

Relationships

  • Belongs to one qti.artifact_version.
  • Self-references parent_component_id.
  • Parent of qti.variable_declaration and qti.processing_rule rows.
  • References package-file/module assets indirectly through preserved QTI attributes and package_resource/package_file rows; it does not execute those assets.

Integrity rules

  • Unique (artifact_version_id, component_path).
  • ordinal preserves sibling order.
  • component_identifier_idx supports identifier lookup when qti_identifier is present.
  • qti-selection, qti-ordering, qti-branch-rule, qti-pre-condition, adaptive/qti-adaptive-selection, qti-catalog-info, qti-portable-custom-interaction, qti-interaction-markup, qti-interaction-modules, qti-printed-variable, assessmentResult, and usageData nodes round-trip verbatim here unless promoted to variable_declaration or processing_rule by the existing projection rules.

Invalid examples

  • Two nodes with the same component_path in one version.
  • Dropping tail_value for mixed content.
  • Parent link creates a cycle.
  • Dropping qti-selection or qti-catalog-info because runtime evaluation is deferred.
  • Creating a custom PCI or results table instead of preserving the stock QTI nodes.

Example row

{
  "component_id": "c09956ea-d444-42b7-9e36-37f948a8c4f7",
  "artifact_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "parent_component_id": null,
  "ordinal": 0,
  "element_name": "qti-portable-custom-interaction",
  "qualified_name": "{http://www.imsglobal.org/xsd/imsqtiasi_v3p0}qti-portable-custom-interaction",
  "namespace_uri": "http://www.imsglobal.org/xsd/imsqtiasi_v3p0",
  "type_name": "PortableCustomInteractionDType",
  "qti_identifier": "graphing-pci-1",
  "component_path": "$.itemBody.qti-portable-custom-interaction[0]",
  "attributes": {
    "responseIdentifier": "RESPONSE",
    "customInteractionTypeIdentifier": "urn:tb:pci:graphing"
  },
  "source_trace": {
    "schemaFile": "imsqti_itemv3p0p1_v1p0.xsd"
  }
}

Common queries

select component_path, element_name, qti_identifier from qti.component where artifact_version_id = $1 order by component_path;
select * from qti.component where artifact_version_id = $1 and element_name in ('qti-selection','qti-ordering','qti-branch-rule','qti-pre-condition','qti-adaptive-selection','qti-catalog-info','qti-portable-custom-interaction','qti-printed-variable'); -- raw pass-through scope check

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
component_id Primary key
uuid
Required; no default

Stable identifier for one generated object node row.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform row identity.

ITD-005 Lossless Relational Projection

None.

Example: c09956ea-d444-42b7-9e36-37f948a8c4f7

artifact_version_id Foreign key
uuid
Required; no default

Artifact version containing this component.

Must reference qti.artifact_version(artifact_version_id). Cascades on version delete.

Invalid when: Missing version or cross-artifact projection.

Platform gap fill

Projection belongs to immutable version.

ITD-005 Lossless Relational Projection, ITD-011 Artifact Versioning

Belongs to exactly one qti.artifact_version.

Example: efcf3561-3a66-4825-9588-e792ef20c312

parent_component_id Self foreign key
uuid
Nullable; no default

Parent object node, preserving the XML/object hierarchy.

Nullable for root node. If present, references qti.component(component_id) and cascades on parent delete.

Invalid when: Parent is in a different artifact_version, creates a cycle, or is missing for non-root nodes.

Platform gap fill

Relational hierarchy projection.

ITD-005 Lossless Relational Projection

References zero or one parent qti.component.

Example: null for the root component; c09956ea-d444-42b7-9e36-37f948a8c4f7 for a child node

ordinal
integer
Required; no default

Sibling order under parent_component_id.

Required integer. Use zero-based or one-based consistently in the repository; order must be stable for XML canonicalization.

Invalid when: Null, negative by repository convention, duplicated among siblings without deterministic tie-break, or changed after hashing.

Platform gap fill

Relational ordering needed for round trip.

ITD-005 Lossless Relational Projection, ITD-004 XML Authority And Canonical Hashes

None.

Example: 0

element_name
text
Required; no default

XML element name generated from the bundled XSD index, including feature-scope constructs such as qti-selection, qti-ordering, qti-branch-rule, qti-pre-condition, qti-adaptive-selection, qti-catalog-info, qti-portable-custom-interaction, qti-template-processing children, qti-printed-variable, assessmentResult, and usageData.

Required text. Must match the generated object node and source XML; deferring runtime navigation, PNP selection, PCI execution, or Caliper event emission does not permit dropping or renaming the source element.

Invalid when: Unknown to the generated model, mismatched with qualified_name, rewritten to an Alpha name, or omitted because the platform delegates that construct's runtime behavior.

1EdTech pass-through

QTI/XML element name from bundled XSD index.

ITD-002 Generated Object Model Hub, ITD-005 Lossless Relational Projection, ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: qti-portable-custom-interaction

qualified_name
text
Nullable; no default

Clark-notation qualified XML name used to preserve namespace identity.

Nullable for nodes without namespace evidence; when present, must combine namespace_uri and element_name correctly.

Invalid when: Namespace does not match namespace_uri, prefix-only value loses URI, or generated from live schema outside the bundle.

1EdTech pass-through

Qualified XML name preserving namespace identity.

ITD-004 XML Authority And Canonical Hashes, ITD-005 Lossless Relational Projection

None.

Example: {http://www.imsglobal.org/xsd/imsqtiasi_v3p0}qti-assessment-item

namespace_uri
text
Nullable; no default

Namespace URI for this XML component, if any.

Nullable text. Must be the actual XML namespace URI, not an arbitrary prefix.

Invalid when: Prefix instead of URI, wrong QTI namespace, or discarded for namespaced elements.

1EdTech pass-through

Namespace URI from QTI/XML content.

ITD-004 XML Authority And Canonical Hashes

None.

Example: http://www.imsglobal.org/xsd/imsqtiasi_v3p0

type_name
text
Nullable; no default

Generated XSD type name for this object node.

Nullable. If present, must match generated type evidence from the bundled schema.

Invalid when: Invented type, type from the wrong namespace, or inconsistent with element_name.

1EdTech pass-through

Generated XSD type name.

ITD-002 Generated Object Model Hub, ITD-005 Lossless Relational Projection

None.

Example: AssessmentItemDType

qti_identifier
text
Nullable; no default

QTI identifier attribute on this component when present.

Nullable. Preserve source value; identifier scope is QTI-defined and not always global.

Invalid when: Invented, coerced to UUID, or assumed globally unique across artifacts.

1EdTech pass-through

QTI identifier attribute.

ITD-004 XML Authority And Canonical Hashes, ITD-007 Provenance Labels

None.

Example: RESPONSE

component_path Unique with artifact_version_id
text
Required; no default

Stable generated path from the root object to this component.

Required and unique within artifact_version_id. Must be reproducible from the generated object graph.

Invalid when: Not stable across rehydration, duplicated, or encodes tenant/private data.

Platform gap fill

Platform traceability and diff path.

ITD-005 Lossless Relational Projection

None.

Example: $.itemBody.choiceInteraction[0]

attributes
jsonb
Required; default '{}'::jsonb

Raw generated attribute projection for fields not promoted to typed query tables, including navigation, CAT engine refs, catalog refs, PCI module refs, template rendering refs, and result/usage-data attributes.

Required JSON object. Must preserve QTI/XML attributes, including extension attributes, without changing names. qti-adaptive-selection refs, qti-catalog-info refs, qti-interaction-modules refs, and assessmentResult/usageData identifiers stay QTI-shaped.

Invalid when: Null, non-object JSON, drops data-* extension attributes, drops qti-adaptive-selection/qti-catalog-info/PCI/template/result attributes, or stores API auth headers.

1EdTech pass-through

Raw QTI/XML attributes not promoted to query columns.

ITD-004 XML Authority And Canonical Hashes, ITD-005 Lossless Relational Projection, ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: {"responseIdentifier":"RESPONSE","module":"urn:tb:pci:graphing"}

text_value
text
Nullable; no default

Text node value for text-bearing QTI and embedded content nodes.

Nullable text. Preserve source text needed for canonical XML round trip.

Invalid when: Dropped for mixed content, normalized in a way that changes semantics, or used to store learner responses.

1EdTech pass-through

Text node value from QTI or permitted embedded content.

ITD-004 XML Authority And Canonical Hashes, ITD-005 Lossless Relational Projection

None.

Example: Solve for x.

tail_value
text
Nullable; no default

Tail text after this element, required for mixed-content XML round trips.

Nullable text. Must be preserved when XML mixed content uses tail text.

Invalid when: Dropped because it is inconvenient, moved into text_value incorrectly, or included in delivery_json without declared lossiness.

1EdTech pass-through

Mixed-content XML tail text needed for round trips.

ITD-004 XML Authority And Canonical Hashes, ITD-005 Lossless Relational Projection

None.

Example: after the interaction.

source_trace
jsonb
Required; default '{}'::jsonb

Generated trace to XSD and spec source for this component, including the ITD-pinned QTI feature construct that made this node important when applicable.

Required JSON object. Must be reproducible from the offline source bundle and generated model. For ITD-028 through ITD-032 constructs, include enough source evidence to prove the node was preserved rather than normalized away.

Invalid when: Null, non-object JSON, live-only link, not aligned with element_name/type_name, or missing evidence for a preserved navigation, PNP, PCI, template, result, or usage-data node.

1EdTech pass-through

Generated traceability to bundled XSD/spec sources.

ITD-001 Offline 1EdTech Source Bundle, ITD-007 Provenance Labels, ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: {"schemaFile":"imsqti_itemv3p0p1_v1p0.xsd","type":"PortableCustomInteractionDType"}

Table

qti.variable_declaration

Typed query projection for QTI response, outcome, template, and context variables.

Gap fill row with 1EdTech pass-through values Primary key variable_declaration_id

Purpose

Promotes QTI variable declarations into queryable rows so processing, delivery, validation, item-template realization, result projection, and reporting can find variables without scanning the full object graph. The object graph remains the reconstruction source.

Lifecycle

Created when an artifact_version object graph is projected. Deleted when the owning artifact_version or component is deleted.

Trace

ITD-013 Variable Declaration Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

The table and row identity are gap fills. Variable kind, identifier, cardinality, base type, default/correct values, mapping, and source trace preserve generated QTI values. ITD-031 pins template declarations as variable_kind=template, and ITD-032 pins result projection from stored response/template/outcome state.

Relationships

  • Belongs to one qti.artifact_version.
  • References the qti.component that declared the variable.

Integrity rules

  • Unique (artifact_version_id, variable_kind, identifier).
  • variable_kind must satisfy variable_kind_ck.
  • Cardinality and base_type must match QTI declaration semantics when present.
  • qti-template-declaration rows use variable_kind=template; assessmentResult responseVariable/outcomeVariable/templateVariable values are projected from attempt state, not duplicated here.

Invalid examples

  • Two response declarations with the same identifier in one version.
  • Cardinality value outside QTI cardinality vocabulary.
  • correct_response shape inconsistent with cardinality/base_type.
  • A template declaration is hidden in component JSON only and missing from variable_kind=template.
  • A result variable is copied into a separate QTI results column instead of projected from attempt state.

Example row

{
  "variable_declaration_id": "0c6f5271-9d9e-4df1-8d5d-2ad59dacde66",
  "artifact_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "component_id": "7293a5f9-55a9-42cb-b8e6-a2be7d166d35",
  "variable_kind": "response",
  "identifier": "RESPONSE",
  "cardinality": "single",
  "base_type": "identifier",
  "correct_response": {
    "values": [
      "choiceA"
    ]
  },
  "source_trace": {
    "element": "qti-response-declaration"
  }
}

Common queries

select identifier, cardinality, base_type from qti.variable_declaration where artifact_version_id = $1 and variable_kind = 'response';
select * from qti.variable_declaration where identifier = 'SCORE' and variable_kind = 'outcome';

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
variable_declaration_id Primary key
uuid
Required; no default

Stable row identifier for one promoted variable declaration.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform projection row identity.

ITD-013 Variable Declaration Projection

None.

Example: 0c6f5271-9d9e-4df1-8d5d-2ad59dacde66

artifact_version_id Foreign key
uuid
Required; no default

Artifact version that declares the variable.

Must reference qti.artifact_version(artifact_version_id). Cascades on version delete.

Invalid when: Missing version or mismatch with component_id artifact_version.

Platform gap fill

Variable projection belongs to immutable content version.

ITD-013 Variable Declaration Projection, ITD-011 Artifact Versioning

Belongs to exactly one qti.artifact_version.

Example: efcf3561-3a66-4825-9588-e792ef20c312

component_id Foreign key
uuid
Required; no default

Component node that declared this variable.

Must reference qti.component(component_id). Cascades on component delete.

Invalid when: Component is not in the same artifact_version or is not a variable declaration node.

Platform gap fill

Projection link back to object graph component.

ITD-005 Lossless Relational Projection, ITD-013 Variable Declaration Projection

Belongs to exactly one qti.component.

Example: 7293a5f9-55a9-42cb-b8e6-a2be7d166d35

variable_kind
text
Required; no default

QTI variable category.

Must satisfy variable_kind_ck.

Invalid when: Outside enum set, used to invent a platform-only variable category, or fails to mark qti-template-declaration as template.

Allowed values: Variable declaration kind

1EdTech pass-through

QTI variable declaration category. ITD-031 makes qti-template-declaration queryable as variable_kind=template for server-side realization.

ITD-013 Variable Declaration Projection, ITD-007 Provenance Labels, ITD-031 Item Template Declaration, Processing, And Cloning

None.

Example: response

identifier Unique with artifact_version_id and variable_kind
text
Required; no default

QTI variable identifier.

Required text. Unique with artifact_version_id and variable_kind.

Invalid when: Blank, duplicated within kind/version, rewritten to UUID, or mismatched with processing operands.

1EdTech pass-through

QTI variable identifier value.

ITD-013 Variable Declaration Projection, ITD-004 XML Authority And Canonical Hashes

None.

Example: RESPONSE

cardinality
text
Nullable; no default

QTI cardinality for the variable value container.

Nullable only when the source declaration allows absence. When present, use QTI cardinality values such as single, multiple, ordered, or record.

Invalid when: Outside QTI cardinality vocabulary, inconsistent with associated interaction, or inconsistent with JSON value shape.

1EdTech pass-through

QTI cardinality value.

ITD-013 Variable Declaration Projection, ITD-021 Runtime Execution Profile

None.

Example: single

base_type
text
Nullable; no default

QTI base-type for atomic values when the declaration has one.

Nullable for record variables or declarations where QTI permits no base-type. Values should be QTI base types such as boolean, directedPair, duration, file, float, identifier, integer, pair, point, string, or uri.

Invalid when: Outside QTI base-type vocabulary, present for record in a way QTI forbids, or inconsistent with correct_response/default_value.

1EdTech pass-through

QTI base-type value.

ITD-013 Variable Declaration Projection, ITD-021 Runtime Execution Profile

None.

Example: identifier

default_value
jsonb
Nullable; no default

Generated object value for qti-default-value.

Nullable. JSON shape must match cardinality and base_type.

Invalid when: Shape does not match cardinality/base_type, contains unvalidated extension payload, or is used as candidate response state.

1EdTech pass-through

Generated value for qti-default-value.

ITD-013 Variable Declaration Projection, ITD-004 XML Authority And Canonical Hashes

None.

Example: {"values":[0]}

correct_response
jsonb
Nullable; no default

Generated object value for qti-correct-response.

Nullable. JSON shape must match cardinality and base_type and preserve mapped identifiers exactly.

Invalid when: Correct response values are coerced, ordered values are stored as unordered, value type conflicts with base_type, or a templated correct response is realized by the client instead of the server.

1EdTech pass-through

Generated value for qti-correct-response. For templated items, server-side realization binds the effective correct response from template state before scoring.

ITD-013 Variable Declaration Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

None.

Example: {"values":["choiceA"]}

mapping
jsonb
Nullable; no default

Generated mapping, areaMapping, matchTable, or interpolationTable detail.

Nullable. Must preserve QTI mapping values, bounds, default scores, and interpolation details needed by processing.

Invalid when: Drops default mapping value, loses area coordinates, changes scoring numeric precision, or omits source trace.

1EdTech pass-through

Generated QTI mapping/detail payload.

ITD-013 Variable Declaration Projection, ITD-021 Runtime Execution Profile

None.

Example: {"defaultValue":0,"mapEntries":[{"mapKey":"choiceA","mappedValue":1}]}

source_trace
jsonb
Required; default '{}'::jsonb

Generated trace to XSD and spec section for the variable declaration.

Required JSON object and reproducible from the source bundle.

Invalid when: Null, non-object JSON, or not tied to the declaring component.

1EdTech pass-through

Generated trace to XSD and spec section.

ITD-001 Offline 1EdTech Source Bundle, ITD-007 Provenance Labels

None.

Example: {"element":"qti-response-declaration","schemaFile":"imsqti_itemv3p0p1_v1p0.xsd"}

Table

qti.processing_rule

Executable QTI processing and expression tree projection.

Gap fill row with 1EdTech pass-through values Primary key processing_rule_id

Purpose

Promotes response, outcome, template, and expression nodes into ordered rows for execution, coverage, diagnostics, and trace generation. Template-processing rows are executed server-side at delivery-session start to realize item clones; response and outcome rows are executed server-side for submitted attempts. The rule_name and operands remain generated QTI values; the row scope and sequence are platform execution aids.

Lifecycle

Created when an artifact_version object graph is projected. Deleted when the owning artifact_version or component is deleted.

Trace

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

The table, row IDs, parent links, scope, and sequence are gap fills. rule_name, operands, and source_trace preserve generated QTI processing data. ITD-031 pins qti-template-processing realization; ITD-032 pins assessmentResult projection from executed processing state.

Relationships

  • Belongs to one qti.artifact_version.
  • References the qti.component backing the rule.
  • Self-references parent_processing_rule_id for nested rule/expression trees.

Integrity rules

  • rule_scope must satisfy processing_rule_scope_ck.
  • processing_rule_execution_idx orders rules by artifact_version_id, rule_scope, and sequence_number.
  • qti-template-processing rows use rule_scope=template and run before response/outcome scoring at delivery-session start.
  • qti-template-constraint retry is finite and fail-closed under ITD-021; no unbounded loop is allowed.

Invalid examples

  • sequence_number does not match object-graph order.
  • rule_name not generated from a QTI processing element.
  • unregistered custom operator treated as successful execution.
  • Template processing is delegated to the client even though it can change the correct response.
  • Result projection is generated from raw XML without using the executed attempt state.

Example row

{
  "processing_rule_id": "74b1c279-e85f-407e-971e-16408ad792c0",
  "artifact_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "component_id": "913a57f5-98a5-4726-940b-d9610b721ccb",
  "parent_processing_rule_id": null,
  "rule_scope": "response",
  "rule_name": "qti-map-response",
  "sequence_number": 10,
  "operands": [
    {
      "variableIdentifier": "RESPONSE"
    }
  ],
  "source_trace": {
    "element": "qti-map-response"
  }
}

Common queries

select rule_scope, sequence_number, rule_name from qti.processing_rule where artifact_version_id = $1 order by rule_scope, sequence_number;
select * from qti.processing_rule where artifact_version_id = $1 and rule_scope = 'response';

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
processing_rule_id Primary key
uuid
Required; no default

Stable row identifier for one promoted processing rule or expression node.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform projection row identity.

ITD-014 Processing Rule Projection

None.

Example: 74b1c279-e85f-407e-971e-16408ad792c0

artifact_version_id Foreign key
uuid
Required; no default

Artifact version containing the processing rule.

Must reference qti.artifact_version(artifact_version_id). Cascades on version delete.

Invalid when: Missing version or mismatch with component_id artifact_version.

Platform gap fill

Processing projection belongs to immutable content version.

ITD-014 Processing Rule Projection, ITD-011 Artifact Versioning

Belongs to exactly one qti.artifact_version.

Example: efcf3561-3a66-4825-9588-e792ef20c312

component_id Foreign key
uuid
Required; no default

Component node backing this processing rule.

Must reference qti.component(component_id). Cascades on component delete.

Invalid when: Component is not in the same artifact_version or is not a processing/expression node.

Platform gap fill

Projection link back to generated component.

ITD-005 Lossless Relational Projection, ITD-014 Processing Rule Projection

Belongs to exactly one qti.component.

Example: 913a57f5-98a5-4726-940b-d9610b721ccb

parent_processing_rule_id Self foreign key
uuid
Nullable; no default

Parent processing rule for nested expression and rule trees.

Nullable for root rules. If present, references qti.processing_rule(processing_rule_id). Cascades on parent delete.

Invalid when: Parent is in another artifact_version, creates a cycle, or changes execution semantics.

Platform gap fill

Relational processing-tree hierarchy.

ITD-014 Processing Rule Projection

References zero or one parent qti.processing_rule.

Example: null for a root response-processing rule; 74b1c279-e85f-407e-971e-16408ad792c0 for a nested expression

rule_scope
text
Required; no default

Processing scope used for query and execution grouping.

Must satisfy processing_rule_scope_ck.

Invalid when: Outside enum set, used to claim QTI defines this SQL row scope, or stores qti-template-processing under response/outcome scope.

Allowed values: Processing rule scope

Platform gap fill

Repository classification for persisted processing rows. ITD-031 requires qti-template-processing to use template scope and be executed server-side at delivery-session start.

ITD-014 Processing Rule Projection, ITD-031 Item Template Declaration, Processing, And Cloning

None.

Example: response

rule_name
text
Required; no default

QTI processing rule or expression element/operator name, including qti-set-template-value, qti-template-constraint, and qti-template-default for item-template realization.

Required text generated from the object graph and bundled XSD model.

Invalid when: Not a QTI processing/expression element, renamed for Alpha, mismatched with component.element_name, or drops template-processing rules because realization is server-side.

1EdTech pass-through

QTI processing rule or expression element name.

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

None.

Example: qti-map-response

sequence_number
integer
Required; no default

Order within the parent processing scope.

Required integer. Must preserve QTI processing order and be stable across rehydration.

Invalid when: Null, order differs from object graph, or ties cause nondeterministic execution.

Platform gap fill

Execution ordering aid.

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile

None.

Example: 10

operands
jsonb
Required; default '[]'::jsonb

Generated operand references and literal values for execution.

Required JSON array. Must preserve variable references, literal values, and expression child references needed by the runtime.

Invalid when: Null, non-array JSON, points to undeclared variables, loses numeric precision, loses template-variable operands, or contains unredacted learner identity.

1EdTech pass-through

Generated QTI operand references and literal values, including template variable bindings used by qti-template-processing.

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

None.

Example: [{"variableIdentifier":"RESPONSE"}]

source_trace
jsonb
Required; default '{}'::jsonb

Generated trace to XSD and spec section for this processing rule.

Required JSON object and reproducible from the source bundle.

Invalid when: Null, non-object JSON, or not tied to the rule_name/component.

1EdTech pass-through

Generated trace to XSD and spec section.

ITD-001 Offline 1EdTech Source Bundle, ITD-007 Provenance Labels

None.

Example: {"element":"qti-map-response","schemaFile":"imsqti_responseprocessingv3p0_v1p0.xsd"}

Table

qti.delivery_session

Candidate delivery snapshot, including the server-authoritative timing window when the delivered QTI content declares qti-time-limits.

Platform gap fill Primary key delivery_session_id

Purpose

Freezes exactly what one pseudonymous candidate was shown by storing root artifact version, delivery JSON snapshot, lifecycle state, runtime session state, and the server clock window for timed delivery. This protects historical learner experience from later content edits, supplies the session half of the getCandidateRuntimeData response for a known candidate_ref, and gives the raw-DB path the same answer as the API for in-window versus late attempts.

Lifecycle

Created by startDeliverySession. Moves through created, active, suspended, submitted, review, closed, or voided. When the pinned content declares qti-time-limits with max-time, the server sets window_started_at at timed-delivery start, derives window_expires_at from effective_max_time_seconds, and uses those timestamps at submitAttempt. GET candidate runtime-data reads authorized sessions and attempts for one tenant-scoped candidate_ref; candidate-scoped deletion removes sessions and cascading attempts while leaving reusable content intact.

Trace

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy, ITD-019 Security Boundary, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing

QTI defines content, qti-time-limits, and processing; it does not define platform delivery session storage, learner privacy policy, or the server-authoritative timing-window columns.

Relationships

  • Belongs to one platform.tenant.
  • Pins one qti.artifact_version as root_artifact_version_id.
  • Parent of qti.attempt.
  • The timing-window columns are read by qti.attempt timing_status/effective_duration_seconds; no client-supplied elapsed time participates in the relationship.

Integrity rules

  • candidate_ref must be opaque tenant-scoped pseudonymous UUID string.
  • status must satisfy delivery_session_status_ck.
  • delivery_json_snapshot is required and immutable for historical stability.
  • For timed delivery with a declared max-time, window_started_at and effective_max_time_seconds are required and window_expires_at must equal window_started_at + effective_max_time_seconds. For untimed delivery, timing-window columns are null.

Invalid examples

  • candidate_ref contains a name, email, SIS ID, raw JWT subject, or access token.
  • delivery_json_snapshot does not match root_artifact_version_id projection at session start.
  • status outside enum set.
  • A timed session stores a client-supplied start time instead of the server-recorded window_started_at.
  • window_expires_at is present while window_started_at is null, or an untimed session has a non-null effective_max_time_seconds.

Example row

{
  "delivery_session_id": "e0b41369-3019-42a5-a419-d5da6e33904f",
  "tenant_id": "0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3",
  "candidate_ref": "9c41d14e-d011-4517-927e-b9bf0b7d5df4",
  "root_artifact_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "status": "active",
  "delivery_json_snapshot": {
    "kind": "test",
    "identifier": "fluency-180",
    "timeLimits": {
      "maxTime": 180,
      "minTime": null,
      "allowLateSubmission": false
    }
  },
  "session_state": {
    "navigation": "test",
    "currentItem": "math-facts-01"
  },
  "window_started_at": "2026-05-20T13:15:00Z",
  "window_expires_at": "2026-05-20T13:18:00Z",
  "effective_max_time_seconds": 180,
  "created_at": "2026-05-20T13:15:00Z",
  "updated_at": "2026-05-20T13:16:10Z"
}

Common queries

select delivery_session_id, status from qti.delivery_session where tenant_id = $1 and candidate_ref = $2 order by updated_at desc;
select ds.delivery_session_id, ds.status, a.attempt_id, a.outcome_state from qti.delivery_session ds left join qti.attempt a using (delivery_session_id) where ds.tenant_id = $1 and ds.candidate_ref = $2 order by ds.updated_at desc, a.attempt_number; -- getCandidateRuntimeData
select delivery_session_id, window_started_at, window_expires_at, effective_max_time_seconds from qti.delivery_session where tenant_id = $1 and delivery_session_id = $2; -- server clock window used by submitAttempt
delete from qti.delivery_session where tenant_id = $1 and candidate_ref = $2;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
delivery_session_id Primary key
uuid
Required; no default

Stable session identifier exposed by delivery APIs.

Valid UUID and unique primary key.

Invalid when: Not a UUID, reused, or guessable outside API authorization.

Platform gap fill

Platform delivery session identity.

ITD-015 Delivery Session Snapshots

None.

Example: e0b41369-3019-42a5-a419-d5da6e33904f

tenant_id Foreign key
uuid
Required; no default

Tenant boundary for the delivery session.

Must reference platform.tenant(tenant_id). The tenant must match the route tenant, JWT tenant claim, and root artifact tenant.

Invalid when: Does not match authenticated tenant claim or root artifact tenant.

Platform gap fill

Tenant-scoped learner runtime boundary inherited from Platform.

ITD-008 Tenant Boundary, ITD-015 Delivery Session Snapshots, ITD-025 Platform Substrate Inheritance

Belongs to exactly one platform.tenant; old qti.tenant compatibility reads expose the same tenant_id only as a bridge.

Example: 0d4ce2f4-1c42-4f3c-9f0d-03fb7f5271d3

candidate_ref
text
Required; no default

Opaque tenant-scoped pseudonymous UUID string for the candidate and the selector used by GET/DELETE /tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data.

Required text. Must be pseudonymous; direct names, emails, phone numbers, SIS IDs, raw JWT subjects, access tokens, and contact details are rejected. Must be indexed with tenant_id so getCandidateRuntimeData can read exactly one candidate's authorized runtime record without collection-wide browsing.

Invalid when: Contains direct learner or parent PII, raw JWT subject, auth token, SIS ID, or is not tenant-scoped.

Platform gap fill

Platform learner privacy boundary. QTI_CONTEXT candidateIdentifier uses this value only when runtime context needs it.

ITD-024 Candidate And Learner Data Privacy, ITD-019 Security Boundary

Indexed with tenant_id for candidate session history, candidate runtime-data read, and candidate runtime-data deletion.

Example: 9c41d14e-d011-4517-927e-b9bf0b7d5df4

root_artifact_version_id Foreign key
uuid
Required; no default

Immutable item, test, or section version delivered in this session.

Must reference qti.artifact_version(artifact_version_id).

Invalid when: Missing version, version not owned by tenant, or changed after session start.

Platform gap fill

Session pins immutable content version.

ITD-015 Delivery Session Snapshots, ITD-011 Artifact Versioning

Belongs to exactly one qti.artifact_version.

Example: efcf3561-3a66-4825-9588-e792ef20c312

status
text
Required; default 'created'

Session lifecycle state.

Must satisfy delivery_session_status_ck.

Invalid when: Outside enum set or inconsistent with attempts, submitted_at, or review workflow.

Allowed values: Delivery session status

Platform gap fill

Platform delivery lifecycle.

ITD-015 Delivery Session Snapshots

None.

Example: active

delivery_json_snapshot
jsonb
Required; no default

Snapshot of delivery_json at session start.

Required JSON object. Must remain stable for the session even if the artifact later receives a new version. When the pinned content declares qti-time-limits, the snapshot includes timeLimits.maxTime and timeLimits.minTime as QTI NonNegativeDouble seconds and timeLimits.allowLateSubmission as a boolean with the QTI default false.

Invalid when: Null, mismatched to root_artifact_version_id at start, mutated after attempts, contains authoring-only trace without declared lossiness, omits timeLimits for content that declared qti-time-limits, or treats client countdown state as authoritative timing.

Platform gap fill

Generated delivery projection snapshot for historical stability. The timeLimits values are pass-through QTI semantics; storing the snapshot is a platform gap fill.

ITD-006 JSON Projection Boundaries, ITD-015 Delivery Session Snapshots, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing

None.

Example: {"kind":"test","identifier":"fluency-180","timeLimits":{"maxTime":180,"minTime":null,"allowLateSubmission":false}}

session_state
jsonb
Required; default '{}'::jsonb

Runtime state not modeled as QTI variables, such as navigation, item sequencing, resume information, or review flags.

Required JSON object. Treat as learner-runtime data. Do not store direct PII, raw PNP records, tokens, IP addresses, or user agents.

Invalid when: Null, non-object JSON, auth headers, raw PNP records, direct learner identity, or state that contradicts delivery_json_snapshot.

Platform gap fill

Platform runtime state outside QTI variable declarations.

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

None.

Example: {"navigation":"item","currentItem":"RESPONSE-001"}

window_started_at
timestamptz
Nullable; no default

Server timestamp at which the timed delivery window started for this session.

Nullable for untimed sessions. Required for a session whose delivered scope declares qti-time-limits with max-time. Must be recorded by the server clock, stored in UTC, and be less than or equal to window_expires_at when that field is present.

Invalid when: Client supplied, null for a max-time session, after window_expires_at, before created_at without migration evidence, or compared as local wall time.

Platform gap fill

Server-authoritative timing-window persistence for stock QTI qti-time-limits.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-015 Delivery Session Snapshots

Used with qti.attempt.submitted_at to compute server-measured effective_duration_seconds and timing_status.

Example: 2026-05-20T13:15:00Z

window_expires_at
timestamptz
Nullable; no default

Derived server timestamp at which the effective max-time window closes.

Nullable for untimed sessions and for a scope with no max-time. For max-time delivery it must equal window_started_at plus effective_max_time_seconds, with any allowed PNP extended-time adjustment already applied.

Invalid when: Present without window_started_at, earlier than window_started_at, inconsistent with effective_max_time_seconds, or manually extended after the session starts without documented PNP/administrative evidence.

Platform gap fill

Derived timing-window persistence authorized by ITD-027; QTI supplies max-time seconds, not a platform timestamp.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-015 Delivery Session Snapshots

submitAttempt compares the server submission time against this timestamp to set qti.attempt.timing_status or return qti:time-limit-exceeded.

Example: 2026-05-20T13:18:00Z

effective_max_time_seconds
double precision
Nullable; no default

Effective maximum time window in seconds after applying any QTI Personal Needs & Preferences extended-time accommodation.

Nullable for untimed sessions and for timed scopes without max-time. When present, must be a non-negative finite seconds value from QTI NonNegativeDouble semantics, and window_expires_at must be derived from it.

Invalid when: Negative, NaN, copied from a client request, differs from the timeLimits/PNP calculation without evidence, or non-null on an untimed session.

Platform gap fill

QTI defines max-time as seconds; the platform persists the effective value actually enforced for this session.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-015 Delivery Session Snapshots

Derived from delivery_json_snapshot.timeLimits.maxTime plus authorized PNP extended-time policy; used by qti.attempt timing_status and the QTI duration built-in.

Example: 180

created_at
timestamptz
Required; default now()

Session creation timestamp.

Required timestamp with time zone.

Invalid when: Null or later than updated_at.

Platform gap fill

Runtime audit metadata.

ITD-022 Operational DDL Discipline

None.

Example: 2026-05-20T13:15:00Z

updated_at
timestamptz
Required; default now()

Last session state mutation timestamp.

Required timestamp with time zone. Must be updated when status or session_state changes.

Invalid when: Null, earlier than created_at, or stale after status/session_state update.

Platform gap fill

Runtime audit metadata.

ITD-022 Operational DDL Discipline

None.

Example: 2026-05-20T13:16:10Z

Table

qti.attempt

Candidate response, template, outcome, timing, and processing trace snapshot inside a delivery session.

Gap fill row with 1EdTech pass-through values Primary key attempt_id

Purpose

Stores the learner's QTI response, template, and outcome variable state, server-measured timing classification, effective duration, and deterministic processing trace needed to explain scoring and feedback for one item/test artifact version within a session. It is the learner-runtime record that turns QTI content into reportable outcomes and forms the attempt half of the getCandidateRuntimeData response; its response/template/outcome state is also the source for a generated QTI assessmentResult projection.

Lifecycle

Created when a candidate starts or submits an attempt. Moves through active, suspended, submitted, reviewed, or voided. For templated items, template_state is bound by server-side qti-template-processing at delivery-session start before scoring. For timed sessions, submitAttempt computes effective_duration_seconds from server timestamps, sets timing_status to untimed, in_window, late_accepted, or late_rejected, and only accepts late work when the declared QTI allow-late-submission permits it. Deleted through candidate-scoped runtime deletion by cascading from delivery_session.

Trace

ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile, ITD-024 Candidate And Learner Data Privacy, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

Response, template, and outcome state preserve QTI variable values. The QTI duration built-in, qti-time-limits, item templates, and assessmentResult vocabulary are stock QTI semantics; attempt identity, lifecycle, trace shape, timing_status, effective_duration_seconds, timestamps, redaction, retention, and deletion are platform gap fills.

Relationships

  • Belongs to one qti.delivery_session.
  • References the qti.artifact_version attempted.
  • Read through qti.delivery_session.tenant_id plus candidate_ref by getCandidateRuntimeData; never exposed as a collection-wide attempt list.
  • For timed sessions, timing_status and effective_duration_seconds are derived from the owning delivery_session timing window, never from client-supplied elapsed time.
  • The generated assessmentResult projection reads response_state, template_state, outcome_state, session/candidate context, and timing evidence from this row and its session.

Integrity rules

  • Unique (delivery_session_id, artifact_version_id, attempt_number).
  • status must satisfy attempt_status_ck.
  • timing_status must satisfy attempt_timing_status_ck.
  • effective_duration_seconds is server-measured and non-negative when present.
  • template_state must contain server-realized qti-template-declaration values for templated items.
  • processing_trace must be privacy-redacted.
  • QTI Usage Data item statistics and Caliper events are not stored here.

Invalid examples

  • processing_trace includes JWTs, IP addresses, user agents, raw PNP records, or direct learner identity.
  • attempt_number duplicates within one session/artifact version.
  • outcome_state cannot be reproduced by the trace and runtime profile.
  • timing_status is in_window even though submitted_at is after delivery_session.window_expires_at.
  • effective_duration_seconds comes from a client field instead of server timestamps.
  • template_state is generated by the client or regenerated on read.
  • Caliper event payloads or item statistics are copied into attempt JSON.

Example row

{
  "attempt_id": "69e74a21-1190-492f-9f64-7557754d6eef",
  "delivery_session_id": "e0b41369-3019-42a5-a419-d5da6e33904f",
  "artifact_version_id": "efcf3561-3a66-4825-9588-e792ef20c312",
  "attempt_number": 1,
  "status": "submitted",
  "response_state": {
    "RESPONSE": "choiceA"
  },
  "template_state": {},
  "outcome_state": {
    "SCORE": 1,
    "MASTERY": true,
    "completionStatus": "completed",
    "duration": 172.4
  },
  "processing_trace": [
    {
      "rule": "qti-outcome-processing",
      "variable": "MASTERY",
      "durationSource": "server"
    }
  ],
  "timing_status": "in_window",
  "effective_duration_seconds": 172.4,
  "started_at": "2026-05-20T13:16:20Z",
  "submitted_at": "2026-05-20T13:18:02Z"
}

Common queries

select attempt_number, status, timing_status, effective_duration_seconds, outcome_state from qti.attempt where delivery_session_id = $1 order by attempt_number;
select processing_trace from qti.attempt where attempt_id = $1;
select a.attempt_id, a.timing_status, a.effective_duration_seconds, ds.window_started_at, ds.window_expires_at from qti.attempt a join qti.delivery_session ds using (delivery_session_id) where ds.tenant_id = $1 and a.attempt_id = $2; -- raw timing audit equivalent to submitAttempt evidence
select a.attempt_id, a.response_state, a.outcome_state, a.processing_trace from qti.attempt a join qti.delivery_session ds using (delivery_session_id) where ds.tenant_id = $1 and ds.candidate_ref = $2; -- getCandidateRuntimeData

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
attempt_id Primary key
uuid
Required; no default

Stable identifier for one attempt record.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform attempt identity.

ITD-016 Attempt State And Processing Trace

None.

Example: 69e74a21-1190-492f-9f64-7557754d6eef

delivery_session_id Foreign key
uuid
Required; no default

Owning delivery session.

Must reference qti.delivery_session(delivery_session_id). Cascades on session delete.

Invalid when: Missing session or tenant mismatch through session.

Platform gap fill

Attempt belongs to learner runtime session.

ITD-015 Delivery Session Snapshots, ITD-016 Attempt State And Processing Trace

Belongs to exactly one qti.delivery_session.

Example: e0b41369-3019-42a5-a419-d5da6e33904f

artifact_version_id Foreign key
uuid
Required; no default

Immutable item/test artifact version attempted.

Must reference qti.artifact_version(artifact_version_id).

Invalid when: Does not belong to the delivery session snapshot or changes after processing.

Platform gap fill

Attempt pins content version for reproducible processing.

ITD-011 Artifact Versioning, ITD-016 Attempt State And Processing Trace

Belongs to exactly one qti.artifact_version.

Example: efcf3561-3a66-4825-9588-e792ef20c312

attempt_number Unique with delivery_session_id and artifact_version_id
integer
Required; no default

Attempt count within a session and artifact version.

Required integer and unique with delivery_session_id plus artifact_version_id. Should increase for repeated attempts.

Invalid when: Zero or negative by convention, duplicated, or reused after an adaptive attempt changes state.

Platform gap fill

Platform attempt sequencing.

ITD-016 Attempt State And Processing Trace

None.

Example: 1

status
text
Required; default 'active'

Attempt lifecycle state.

Must satisfy attempt_status_ck.

Invalid when: Outside enum set or inconsistent with suspended_at/submitted_at.

Allowed values: Attempt status

Platform gap fill

Platform attempt lifecycle.

ITD-016 Attempt State And Processing Trace

None.

Example: submitted

response_state
jsonb
Required; default '{}'::jsonb

Candidate response variables at the last processing point.

Required JSON object. Keys should be QTI response variable identifiers; values must match declaration cardinality/base_type. Treat as learner data.

Invalid when: Null, values inconsistent with qti.variable_declaration, unnecessary learner PII copied from outside the response, or logged without redaction.

1EdTech pass-through

QTI response variable state bound to learner runtime.

ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

None.

Example: {"RESPONSE":"choiceA"}

template_state
jsonb
Required; default '{}'::jsonb

Template variables used for item cloning and stability.

Required JSON object. Keys should be QTI template variable identifiers; values must match declarations.

Invalid when: Null, client-realized, regenerated on read instead of persisted, inconsistent with template processing trace, omitted for a templated item, or logged with learner identity.

1EdTech pass-through

QTI template variable state bound to session stability. ITD-031 requires the platform to realize qti-template-processing server-side before scoring.

ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile, ITD-024 Candidate And Learner Data Privacy, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: {"A":3,"B":5}

outcome_state
jsonb
Required; default '{}'::jsonb

Outcome variables after template, response, and outcome processing, including declared time-conditioned outcomes when the test uses duration in outcome processing.

Required JSON object. Keys should be QTI outcome variable identifiers; values must match declarations and processing results. For timed delivery, the QTI duration value available to outcome processing is the server-measured effective duration, not a client-supplied value.

Invalid when: Null, cannot be reproduced by processing rules under the runtime profile, includes unsupported outcomes without diagnostics, returns only raw/max when the QTI declared richer outcomes, uses client-supplied timing to compute a time-conditioned outcome, or diverges from the assessmentResult projection.

1EdTech pass-through

QTI outcome variable state. ITD-027 requires declared QTI outcome processing server-side for timed tests; ITD-031 requires scoring against server-realized template values; ITD-032 uses these variables in the generated assessmentResult projection.

ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile, ITD-024 Candidate And Learner Data Privacy, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: {"SCORE":1,"MASTERY":true,"duration":172.4,"completionStatus":"completed"}

processing_trace
jsonb
Required; default '[]'::jsonb

Deterministic trace of server-side template realization, response processing, outcome processing, assessmentResult projection evidence, and timed-enforcement operations.

Required JSON array. May include rule names, variable identifiers, before/after values, template-constraint retry/fail-closed diagnostics, timing-status decision evidence, and assessmentResult projection diagnostics. Must exclude JWTs, headers, access tokens, IP addresses, user agents, raw PNP records, raw package bytes, Caliper payloads, and direct learner identity fields.

Invalid when: Null, non-array JSON, nondeterministic, lacks failed-closed diagnostics for unsupported operators or template constraints, or contains auth/PII/Caliper data.

Platform gap fill

Implementation trace of QTI processing execution; QTI defines processing semantics, not this stored audit trail.

ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile, ITD-024 Candidate And Learner Data Privacy, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-032 QTI Results Reporting And Caliper Boundary

None.

Example: [{"rule":"qti-outcome-processing","variable":"MASTERY","durationSource":"server"}]

timing_status
text
Required; default 'untimed'

Server-measured classification of this attempt against the owning delivery session's QTI time-limit window.

Must satisfy attempt_timing_status_ck. For an untimed session use untimed. For a timed session, in_window requires submitted_at to be within window_expires_at; late_accepted requires allowLateSubmission=true; late_rejected is retained only as rejected evidence when the API returned qti:time-limit-exceeded.

Invalid when: Outside enum set, computed from a client-supplied elapsed value, in_window after the server window expired, late_accepted when allowLateSubmission=false, or untimed while the session has an enforceable max-time.

Allowed values: Attempt timing status

Platform gap fill

Platform enforcement state over stock QTI qti-time-limits.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-020 Validation And Rejection Policy

Derived from qti.delivery_session.window_started_at/window_expires_at and server submit time; exposed in runtime/scoring responses for timed delivery.

Example: in_window

effective_duration_seconds
double precision
Nullable; no default

Server-measured QTI duration value in seconds for this attempt.

Nullable only when duration is genuinely unknown. When present, must be finite, non-negative, measured from server timestamps, and at least one-second resolution with 0.1 seconds or smaller preferred by the QTI guidance. Suspended time must be excluded when the attempt was suspended.

Invalid when: Negative, NaN, rounded from a client-submitted timer, includes suspended time, absent for a submitted timed attempt, or contradicts started_at/submitted_at/session-window evidence.

Gap fill row with 1EdTech pass-through values

QTI defines duration as seconds; ITD-027 pins that the platform computes it from server timestamps and persists the effective value used by outcome processing.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

Fed into QTI duration built-in during outcome processing and compared with delivery_session.effective_max_time_seconds for timing_status.

Example: 172.4

started_at
timestamptz
Required; default now()

Attempt start timestamp.

Required timestamp with time zone.

Invalid when: Null or after submitted_at.

Platform gap fill

Runtime audit metadata.

ITD-022 Operational DDL Discipline

None.

Example: 2026-05-20T13:16:20Z

suspended_at
timestamptz
Nullable; no default

Attempt suspension timestamp, if the attempt was suspended.

Nullable timestamp with time zone. Should be set when status is suspended.

Invalid when: Set while status never suspended without lifecycle evidence, before started_at, or after submitted_at.

Platform gap fill

Runtime lifecycle audit metadata.

ITD-016 Attempt State And Processing Trace

None.

Example: 2026-05-20T13:17:00Z

submitted_at
timestamptz
Nullable; no default

Attempt submission timestamp, if the attempt was submitted.

Nullable timestamp with time zone. Should be set when status is submitted or reviewed.

Invalid when: Before started_at, set while status remains active without evidence, or absent for submitted/reviewed attempts.

Platform gap fill

Runtime lifecycle audit metadata.

ITD-016 Attempt State And Processing Trace

None.

Example: 2026-05-20T13:18:02Z

Table

qti.conformance_run

Release evidence for a QTI conformance/profile run.

Platform gap fill Primary key conformance_run_id

Purpose

Persists repeatable proof that a runner exercised the source bundle, examples, XML/object/relational/JSON round trips, processing coverage, and profile expectations. The run row summarizes the evidence; assertion rows contain details.

Lifecycle

Created when the conformance runner starts. Moves from running to passed, failed, or error. Retained as release evidence.

Trace

ITD-017 Conformance Evidence

QTI defines conformance expectations and examples, not a platform table for release-gate evidence.

Relationships

  • Parent of qti.conformance_assertion.

Integrity rules

  • status must satisfy conformance_run_status_ck.
  • bundle_hash must identify the offline source bundle used by the run.

Invalid examples

  • Run claims passed while child assertions failed.
  • bundle_hash omitted or points to live network state.
  • summary includes secrets or learner-runtime data.

Example row

{
  "conformance_run_id": "ed0065af-721c-4799-a48c-8ce0c5fdc7f2",
  "profile": "qti-3.0",
  "bundle_hash": "sha256:184e568a31e239c0b282e7e1926dd7e8756901826f4a91e9e5b1ad58369d9ef4",
  "runner_version": "qti-conformance-runner/2026-05-20",
  "started_at": "2026-05-20T14:00:00Z",
  "finished_at": "2026-05-20T14:04:30Z",
  "status": "passed",
  "summary": {
    "examples": 327,
    "assertions": 28292
  }
}

Common queries

select conformance_run_id, status, summary from qti.conformance_run where profile = 'qti-3.0' order by started_at desc limit 5;
select status, count(*) from qti.conformance_assertion where conformance_run_id = $1 group by status;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
conformance_run_id Primary key
uuid
Required; no default

Stable identifier for one conformance run.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform evidence row identity.

ITD-017 Conformance Evidence

None.

Example: ed0065af-721c-4799-a48c-8ce0c5fdc7f2

profile
text
Required; no default

Targeted QTI 3.0 conformance profile or optional feature set.

Required text. Must be a profile the runner understands.

Invalid when: Unsupported profile, Alpha-only label, or profile not represented by assertions.

Platform gap fill

Platform conformance profile label tied to the QTI bundle.

ITD-017 Conformance Evidence, ITD-001 Offline 1EdTech Source Bundle

None.

Example: qti-3.0

bundle_hash
text
Required; no default

Hash of the offline spec bundle used by the run.

Required text with algorithm prefix and digest.

Invalid when: Missing, live network URL, or not reproducible from the bundle used.

Platform gap fill

Release evidence ties to immutable local source bundle.

ITD-001 Offline 1EdTech Source Bundle, ITD-017 Conformance Evidence

None.

Example: sha256:184e568a31e239c0b282e7e1926dd7e8756901826f4a91e9e5b1ad58369d9ef4

runner_version
text
Required; no default

Version or identity of the conformance runner.

Required text. Must be specific enough to reproduce behavior.

Invalid when: Blank, vague, or points to unpinned code.

Platform gap fill

Release evidence metadata.

ITD-017 Conformance Evidence

None.

Example: qti-conformance-runner/2026-05-20

started_at
timestamptz
Required; default now()

Run start timestamp.

Required timestamp with time zone.

Invalid when: Null or after finished_at.

Platform gap fill

Conformance audit metadata.

ITD-022 Operational DDL Discipline

None.

Example: 2026-05-20T14:00:00Z

finished_at
timestamptz
Nullable; no default

Run finish timestamp, if complete.

Nullable timestamp with time zone. Should be set for passed, failed, or error.

Invalid when: Before started_at, absent for completed terminal status without explanation, or set while still running.

Platform gap fill

Conformance lifecycle audit metadata.

ITD-017 Conformance Evidence

None.

Example: 2026-05-20T14:04:30Z

status
text
Required; default 'running'

Run lifecycle status.

Must satisfy conformance_run_status_ck.

Invalid when: Outside enum set or inconsistent with child assertion statuses.

Allowed values: Conformance run status

Platform gap fill

Release evidence lifecycle.

ITD-017 Conformance Evidence

None.

Example: passed

summary
jsonb
Required; default '{}'::jsonb

Generated coverage and pass/fail summary.

Required JSON object. Should include counts, profile facts, and links/keys to assertion evidence; must not include secrets or learner-runtime data.

Invalid when: Null, non-object JSON, contradicts assertion rows, or includes raw package bytes/secrets.

Platform gap fill

Generated release-gate summary.

ITD-017 Conformance Evidence

None.

Example: {"examples":327,"assertions":28292,"failed":0}

Table

qti.conformance_assertion

Per-example and per-feature conformance evidence.

Gap fill row with 1EdTech pass-through values Primary key conformance_assertion_id

Purpose

Stores individual assertion results from conformance, round-trip, validation, processing, and coverage checks. This lets releases and future AI agents identify exactly which example, spec reference, or feature passed or failed.

Lifecycle

Created as child rows during a conformance run. Deleted when the parent run is deleted.

Trace

ITD-017 Conformance Evidence

Assertion row identity, generated status, and diagnostics are gap fills. artifact_ref and spec_ref can be pass-through references to QTI examples, schemas, or spec anchors.

Relationships

  • Belongs to one qti.conformance_run.

Integrity rules

  • Unique (conformance_run_id, assertion_key).
  • status must satisfy conformance_assertion_status_ck.
  • details must carry enough diagnostics for failed/error assertions.

Invalid examples

  • Duplicate assertion_key in one run.
  • failed assertion with empty details.
  • spec_ref points to a live-only reference that cannot be reproduced from the bundle.

Example row

{
  "conformance_assertion_id": "40bdfcf0-b97b-4a38-b308-a5d271a75943",
  "conformance_run_id": "ed0065af-721c-4799-a48c-8ce0c5fdc7f2",
  "assertion_key": "roundtrip:items/response-001.xml",
  "artifact_ref": "examples/qtiv3-examples/items/response-001.xml",
  "spec_ref": "imsqti_itemv3p0p1_v1p0.xsd#qti-assessment-item",
  "status": "passed",
  "details": {
    "canonicalHashMatched": true
  }
}

Common queries

select assertion_key, artifact_ref, spec_ref, details from qti.conformance_assertion where conformance_run_id = $1 and status in ('failed','error');
select status, count(*) from qti.conformance_assertion where conformance_run_id = $1 group by status;

Fields

Field Type Meaning Range, constraints, invalid values Provenance and ITD Relationship and example
conformance_assertion_id Primary key
uuid
Required; no default

Stable identifier for one assertion result.

Valid UUID and unique primary key.

Invalid when: Not a UUID or reused.

Platform gap fill

Platform assertion row identity.

ITD-017 Conformance Evidence

None.

Example: 40bdfcf0-b97b-4a38-b308-a5d271a75943

conformance_run_id Foreign key
uuid
Required; no default

Owning conformance run.

Must reference qti.conformance_run(conformance_run_id). Cascades on run delete.

Invalid when: Missing parent run or status contradicts parent run summary.

Platform gap fill

Assertion belongs to release evidence run.

ITD-017 Conformance Evidence

Belongs to exactly one qti.conformance_run.

Example: ed0065af-721c-4799-a48c-8ce0c5fdc7f2

assertion_key Unique with conformance_run_id
text
Required; no default

Stable key generated by the conformance runner for this assertion.

Required text and unique within the run. Must be deterministic for the same assertion.

Invalid when: Duplicated in a run, nondeterministic across reruns, or so vague it cannot locate the assertion.

Platform gap fill

Generated assertion identity.

ITD-017 Conformance Evidence

None.

Example: roundtrip:items/response-001.xml

artifact_ref
text
Nullable; no default

Example, fixture, or artifact path covered by this assertion.

Nullable when assertion is feature-level only. If present, should point to a bundled example, generated artifact, or artifact identifier.

Invalid when: Live-only URL, missing fixture, or reference to learner-runtime data.

1EdTech pass-through

QTI example or artifact reference covered by conformance evidence.

ITD-001 Offline 1EdTech Source Bundle, ITD-017 Conformance Evidence

None.

Example: examples/qtiv3-examples/items/response-001.xml

spec_ref
text
Nullable; no default

Spec section, schema component, or generated trace reference covered by this assertion.

Nullable when the assertion is implementation-only. If present, must be reproducible from the bundled source or generated traceability.

Invalid when: Points only to live network docs, wrong schema file, or cannot be followed by a future generator.

1EdTech pass-through

1EdTech spec, schema, or generated traceability reference.

ITD-001 Offline 1EdTech Source Bundle, ITD-007 Provenance Labels, ITD-017 Conformance Evidence

None.

Example: imsqti_itemv3p0p1_v1p0.xsd#qti-assessment-item

status
text
Required; no default

Assertion result status.

Must satisfy conformance_assertion_status_ck.

Invalid when: Outside enum set, contradicts details, or failed/error without diagnostics.

Allowed values: Conformance assertion status

Platform gap fill

Generated assertion result lifecycle.

ITD-017 Conformance Evidence

None.

Example: passed

details
jsonb
Required; default '{}'::jsonb

Assertion diagnostics, canonical hashes, processing outcomes, and failure details.

Required JSON object. Failed or error assertions must explain enough for reproduction. Must not include secrets, raw JWTs, direct learner PII, or unrelated package bytes.

Invalid when: Null, non-object JSON, missing diagnostics for failure, or contains auth/PII data.

Platform gap fill

Generated release-gate diagnostics; QTI defines conformance expectations, not this evidence payload.

ITD-017 Conformance Evidence, ITD-024 Candidate And Learner Data Privacy

None.

Example: {"canonicalHashMatched":true}

API and raw-DB convergence

Tenant-owned list endpoints, recovery, timed delivery, and QTI feature scope

ITD-026 ships three root list endpoints over existing tables. The raw-DB path below is the query shape an agent must use to reach the same answer the API returns; the API path adds the tenant-scoped JWT check, typed 400 Problems for bad cursors/limits, and the {items, nextCursor} envelope.

listArtifacts

GET /tenants/{tenantId}/qti/artifacts

{items: [...], nextCursor: string | null}

Row shape

  • artifactId <- qti.artifact.artifact_id
  • artifactKind <- qti.artifact.artifact_kind
  • qtiIdentifier <- qti.artifact.qti_identifier
  • title <- qti.artifact.title
  • latestArtifactVersionId <- qti.artifact.latest_version_id
  • createdAt <- qti.artifact.created_at

Paging and empty tenant

cursor is opaque and must be a prior nextCursor; limit defaults to 50 and may not exceed 200; order by created_at asc, artifact_id asc.

Return HTTP 200 with {items: [], nextCursor: null}; never return qti:not-found for a fresh tenant.

Raw-DB equivalent

select artifact_id, artifact_kind, qti_identifier, title, latest_version_id, created_at from qti.artifact where tenant_id = $tenant order by created_at asc, artifact_id asc limit $limit;

Invalid when: Rows are not filtered by tenant_id, pagination uses offset/page numbers, latest_version_id is omitted, or an invalid/foreign cursor silently returns an empty page instead of a 400 Problem.

ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-020 Validation And Rejection Policy

listArtifactVersions

GET /tenants/{tenantId}/qti/artifact-versions

{items: [...], nextCursor: string | null}

Row shape

  • artifactVersionId <- qti.artifact_version.artifact_version_id
  • artifactId <- qti.artifact_version.artifact_id
  • versionNumber <- qti.artifact_version.version_number
  • xmlHash <- qti.artifact_version.xml_hash
  • rootElement <- qti.artifact_version.root_element
  • createdAt <- qti.artifact_version.created_at

Paging and empty tenant

cursor is opaque and must be a prior nextCursor; limit defaults to 50 and may not exceed 200; order by artifact_version.created_at asc, artifact_version_id asc.

Return HTTP 200 with {items: [], nextCursor: null}; never return qti:not-found for a fresh tenant.

Raw-DB equivalent

select av.artifact_version_id, av.artifact_id, av.version_number, av.xml_hash, av.root_element, av.created_at from qti.artifact_version av join qti.artifact a on a.artifact_id = av.artifact_id where a.tenant_id = $tenant order by av.created_at asc, av.artifact_version_id asc limit $limit;

Invalid when: The query reads qti.artifact_version without joining qti.artifact for tenant ownership, returns another tenant's version, uses offset/page numbers, or accepts an invalid cursor.

ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-020 Validation And Rejection Policy

listPackages

GET /tenants/{tenantId}/qti/packages

{items: [...], nextCursor: string | null}

Row shape

  • packageId <- qti.content_package.package_id
  • manifestIdentifier <- qti.content_package.manifest_identifier
  • qtiProfile <- qti.content_package.qti_profile
  • importStatus <- qti.content_package.import_status
  • packageHash <- qti.content_package.package_hash
  • idempotencyKey <- qti.content_package.idempotency_key
  • importedAt <- qti.content_package.imported_at

Paging and empty tenant

cursor is opaque and must be a prior nextCursor; limit defaults to 50 and may not exceed 200; order by imported_at asc, package_id asc.

Return HTTP 200 with {items: [], nextCursor: null}; never return qti:not-found for a fresh tenant.

Raw-DB equivalent

select package_id, manifest_identifier, qti_profile, import_status, package_hash, idempotency_key, imported_at from qti.content_package where tenant_id = $tenant order by imported_at asc, package_id asc limit $limit;

Invalid when: Rows are not filtered by tenant_id, idempotency_key is hidden when present, import_status/package_hash are omitted, or an invalid/foreign cursor silently returns an empty page instead of a 400 Problem.

ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-010 Idempotency And Hashes, ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-020 Validation And Rejection Policy

Lost-response recovery pair

A lost package-ingest response is never a permanent artifact leak. Use same-key replay when the key was persisted; use enumeration when it was not.

Replay when the caller persisted Idempotency-Key

API path

POST /tenants/{tenantId}/qti/packages with the same Idempotency-Key and equivalent request body

The platform.idempotency_key ledger returns the original package-ingest response instead of creating a second package.

Raw-DB equivalent

join qti.content_package.platform_idempotency_key_id to platform.idempotency_key.idempotency_key_id under the same tenant_id, module=qti, surface=1edtech, method=POST, operation_id=ingestContentPackage.

Invalid when: QTI stores a second retry ledger, accepts a same-key request with a different request_hash as a replay, or loses the linkage between qti.content_package and platform.idempotency_key.

ITD-010 Idempotency And Hashes, ITD-025 Platform Substrate Inheritance, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery

Enumerate when the caller did not persist Idempotency-Key

API path

GET /tenants/{tenantId}/qti/packages and GET /tenants/{tenantId}/qti/artifacts

The caller locates the package by packageHash or the artifact by qtiIdentifier/title, reads latestArtifactVersionId, and continues with getAuthoringJson/getDeliveryJson/exportXml without any client-side mirror.

Raw-DB equivalent

filter qti.content_package by tenant_id and package_hash or qti.artifact by tenant_id plus qti_identifier/title; then use qti.artifact.latest_version_id as the artifactVersionId handle for existing reads.

Invalid when: The API makes recovery depend on a client-maintained artifact mirror, returns 404 for the three root lists, omits latestArtifactVersionId, or requires package_resource/package_file sub-collection browsing to recover a normal artifact.

ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-011 Artifact Versioning, ITD-018 API Boundary

Timed delivery and outcome-processing pair

ITD-027 keeps timing stock-QTI-first: QTI supplies qti-time-limits and duration; the platform stores the server-measured window and attempt evidence so API and raw-DB answers agree.

Delivery JSON timeLimits projection

API path

GET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json

When the artifact declares qti-time-limits, delivery JSON includes timeLimits.maxTime and timeLimits.minTime as QTI NonNegativeDouble seconds plus timeLimits.allowLateSubmission as a boolean with QTI default false. The client may display a countdown from these values, but the countdown is never authoritative.

Raw-DB equivalent

read qti.artifact_version.delivery_json for the tenant-owned artifact version; the timeLimits block is the projection of stock QTI qti-time-limits, not a separate platform table.

Invalid when: The projection omits timeLimits for QTI content that declared qti-time-limits, changes seconds into local wall-clock timestamps, treats allowLateSubmission as nullable/unknown instead of defaulting false, or requires a client countdown to decide scoring.

ITD-006 JSON Projection Boundaries, ITD-018 API Boundary, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing

submitAttempt server-clock enforcement

API path

POST /tenants/{tenantId}/qti/delivery-sessions/{deliverySessionId}/attempts

The server computes the QTI duration built-in from delivery_session.window_started_at and server submit time, executes declared outcome processing, persists attempt.timing_status/effective_duration_seconds, rejects late work with qti:time-limit-exceeded (HTTP 422) when allowLateSubmission=false, and accepts-but-flags late_accepted when allowLateSubmission=true.

Raw-DB equivalent

join qti.attempt to qti.delivery_session under tenant_id; use ds.window_started_at, ds.window_expires_at, ds.effective_max_time_seconds, a.submitted_at, a.timing_status, a.effective_duration_seconds, and a.outcome_state. Never use a client-submitted elapsed value.

Invalid when: A late attempt is scored as in_window, outcome_state contains only raw/max when the QTI declared richer outcomes, timing_status is absent, or effective_duration_seconds is not reproducible from server timestamps.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-020 Validation And Rejection Policy, ITD-021 Runtime Execution Profile

QTI 3.0 benchmark-feature scope ledger

The contracts below publish the SHIP/DEFER boundary for QTI feature areas that reuse existing tables. Persist + interchange is explicit; runtime ownership is explicit; no hidden table or client-side workaround is required to know where the data lives.

Test-level navigation construct scope

API path

GET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json

The projection preserves qti-selection, qti-ordering, qti-branch-rule, qti-pre-condition, adaptive=true, qti-adaptive-selection, and CAT engine/settings/usage-data/metadata refs. The QTI surface serves items in authored document order and does not evaluate selection, shuffle, branch/pre-condition, within-item adaptive iteration, or CAT item picking server-side.

Raw-DB equivalent

read qti.component rows for the tenant-owned artifact_version and inspect element_name/attributes/component_path; the raw path must preserve these nodes and must not infer that the platform executed their navigation semantics. Runtime navigation position belongs to the integrator delivery engine, not a QTI platform table.

Invalid when: The projection drops a navigation construct because runtime evaluation is deferred, a raw query treats component order after qti-selection/qti-ordering as a platform-selected runtime order, or a platform table stores CAT/branch session position.

ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-005 Lossless Relational Projection, ITD-006 JSON Projection Boundaries

Catalog / PNP accessibility scope

API path

GET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json and POST /tenants/{tenantId}/qti/delivery-sessions

delivery-json preserves qti-catalog-info and catalog references for renderer-side Access-For-All selection. The only server-activated PNP accommodation in this QTI surface is extended time, which changes delivery_session.effective_max_time_seconds under ITD-027; raw PNP profile records are never stored here.

Raw-DB equivalent

read qti.component rows to see qti-catalog-info and read qti.delivery_session.effective_max_time_seconds/window fields to see the server-applied extended-time effect. Do not look for a PNP profile table in qti.* and do not infer renderer-selected alternatives from server state.

Invalid when: qti-catalog-info is dropped, raw PNP records are persisted in qti.session_state or qti.attempt.processing_trace, or a raw query treats non-time PNP rendering choices as server-selected facts.

ITD-029 Catalog And PNP Accessibility Activation, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-024 Candidate And Learner Data Privacy

Portable Custom Interaction persistence / execution boundary

API path

GET delivery-json, package-file asset reads, and POST submitAttempt

PCI markup, qti-interaction-markup, qti-interaction-modules, and hosted JavaScript module files round-trip. The delivery client renders the PCI and runs the IMS PCI getResponse/getState lifecycle; the platform never executes vendor PCI JavaScript server-side and scores only the submitted response value through ordinary response/outcome processing.

Raw-DB equivalent

find PCI markup in qti.component, module refs in qti.component.attributes, and module bytes in qti.package_file through qti.package_resource. Treat the submitted PCI response as qti.attempt.response_state and the score as qti.attempt.outcome_state; do not search for or create a qti.pci_execution table.

Invalid when: Server code executes PCI JavaScript, PCI modules are normalized into executable server plugins, a PCI response bypasses qti.variable_declaration/processing_rule, or PCI storage uses a custom table instead of component/package_file rows.

ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-009 Package Resource And File Ingest, ITD-013 Variable Declaration Projection, ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile

Item-template realization scope

API path

POST /tenants/{tenantId}/qti/delivery-sessions and POST /tenants/{tenantId}/qti/delivery-sessions/{deliverySessionId}/attempts

qti-template-declaration, qti-template-processing, qti-template-constraint, qti-template-default, and qti-printed-variable round-trip. At delivery-session start, the platform runs template processing server-side, retries constraints up to the ITD-021 finite bound, stores realized values in qti.attempt.template_state, and scores against the realized correct response; printed-variable and MathML rendering stay client-side.

Raw-DB equivalent

read qti.variable_declaration where variable_kind='template', qti.processing_rule where rule_scope='template', qti.component for printed-variable/math template nodes, and qti.attempt.template_state for the realized per-attempt values. Do not regenerate template variables at read time.

Invalid when: Template realization is delegated to the client, template_state is missing for a templated item, qti-template-processing is stored under the wrong rule_scope, or a realized clone is stored as a new artifact_version without an explicit future ITD.

ITD-031 Item Template Declaration, Processing, And Cloning, ITD-013 Variable Declaration Projection, ITD-014 Processing Rule Projection, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

QTI Results Reporting and Usage Data scope

API path

GET /tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data

assessmentResult documents round-trip as artifact_kind=result. The generated assessmentResult projection is returned through the existing candidate runtime-data read, using stored response_state, template_state, outcome_state, and context/session evidence. QTI Usage Data round-trips as artifact_kind=usage-data; item statistics and IRT parameters stay in the usage-data document. Caliper event emission belongs to the Caliper module, not QTI.

Raw-DB equivalent

for live learner results, join qti.delivery_session and qti.attempt by tenant_id + candidate_ref and read response_state/template_state/outcome_state; for imported result documents read qti.artifact/artifact_version where artifact_kind='result'; for Usage Data read artifact_kind='usage-data'. Do not query QTI-owned Caliper/event tables because this surface owns none.

Invalid when: A dedicated results-export endpoint is assumed to exist, usage-data statistics are promoted to QTI columns, Caliper AssessmentEvent/GradeEvent rows are emitted or stored by QTI, or assessmentResult content diverges from the attempt state returned by getCandidateRuntimeData.

ITD-032 QTI Results Reporting And Caliper Boundary, ITD-012 Artifact Kind Allowed Values, ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

Global reference

Allowed Values

Every constrained value set includes behavioral meaning, provenance, and the architecture decision that authorizes it.

Allowed values

Package import status

qti.content_package.import_status content_package_import_status_ck Platform gap fill

Package lifecycle values are defined by the platform ingest workflow. QTI defines package content, not import job state. ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes.

ValueBehaviorInvalid whenProvenance and ITD
importing The package row has been created and validation or resource extraction is still in progress. Do not deliver artifacts from this package yet. Used outside qti.content_package.import_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes

imported Validation, resource extraction, artifact creation, and version projection succeeded. The package can be queried, delivered, and exported. Used outside qti.content_package.import_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes

rejected Validation, package-closure checks, XSD/Schematron validation, or privacy validation failed. Keep diagnostics in metadata; do not create deliverable sessions from this package. Used outside qti.content_package.import_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes

superseded A later package or version replaces this import for operational use while preserving this row for audit and reproducibility. Used outside qti.content_package.import_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes

Allowed values

IMS/QTI package resource type

qti.package_resource.resource_type IMS content-package resource type vocabulary 1EdTech pass-through

Resource type strings are copied from IMS/QTI content-package manifests and the bundled QTI ASI XML Binding package vocabulary. ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels.

ValueBehaviorInvalid whenProvenance and ITD
imsqti_test_xmlv3p0 A QTI assessment test XML resource. The primary href should point to a test XML document. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_section_xmlv3p0 A QTI assessment section XML resource. Use for sections managed independently from a test. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_item_xmlv3p0 A QTI assessment item XML resource. Use for a candidate-facing item with interactions and response processing. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_resprocessing_xmlv3p0 A QTI response-processing XML resource when response processing is represented as a separate package resource. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_outcomes_xmlv3p0 A QTI outcome-declaration XML resource, often used when outcomes are managed independently. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_stimulus_xmlv3p0 A QTI assessment stimulus XML resource that items can depend on for shared passage or stimulus content. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_fragment_xmlv3p0 A managed QTI fragment resource used by item, section, or test content. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsqti_rptemplate_xmlv3p0 A response-processing template XML resource, including standard or custom templates packaged with items. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

associatedcontent/learning-application-resource A learning-application asset referenced by QTI content. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

webcontent Generic web content asset, such as image, video, audio, HTML, or other supporting media. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

imsbasiclti_xmlv1p3 An LTI tool resource referenced by packaged content. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

controlfile A manifest control file or package control artifact. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

resourcemetadata/xml Metadata XML associated with a package resource. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

resourceextmetadata/xml External metadata XML associated with a package resource. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

qtiusagedata/xml A QTI usage-data XML resource carrying item or distractor statistics. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

pls Pronunciation lexicon resource used by speech or accessibility presentation. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

css2 CSS 2 stylesheet resource. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

css3 CSS 3 stylesheet resource. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

extension An extension resource. Preserve and export it, but do not treat it as a known QTI root without validation evidence. Used outside qti.package_resource.resource_type, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels

Allowed values

Artifact kind

qti.artifact.artifact_kind artifact_kind_ck Platform gap fill

Repository categories are derived from QTI root elements and package resources so APIs can route artifacts without renaming QTI concepts. ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary.

ValueBehaviorInvalid whenProvenance and ITD
item Logical artifact whose root is a QTI assessment item. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

test Logical artifact whose root is a QTI assessment test. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

section Logical artifact whose root is a QTI assessment section. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

stimulus Logical artifact whose root is a QTI assessment stimulus. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

outcome-declaration Logical artifact whose root is a standalone QTI outcome declaration. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

response-processing Logical artifact whose root is standalone QTI response processing or a response-processing template. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

result Logical artifact whose root is a QTI Results Reporting assessmentResult document. It round-trips as QTI XML and is also projectable from attempt response/template/outcome state through getCandidateRuntimeData; no standalone results-export endpoint is shipped yet. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

usage-data Logical artifact whose root is QTI Usage Data / item statistics. IRT a/b/c and item-statistic values stay inside the QTI usage-data document and are never promoted to platform columns. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

metadata Logical artifact for QTI or resource metadata XML. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

manifest-resource Manifest-only resource that must remain addressable even when it is not a QTI root document. Used outside qti.artifact.artifact_kind, misspelled, or used contrary to this behavior. Platform gap fill

ITD-011 Artifact Versioning, ITD-012 Artifact Kind Allowed Values, ITD-032 QTI Results Reporting And Caliper Boundary

Allowed values

Variable declaration kind

qti.variable_declaration.variable_kind variable_kind_ck 1EdTech pass-through

Values mirror QTI variable declaration categories: response, outcome, template, and context. ITD-013 Variable Declaration Projection, ITD-007 Provenance Labels, ITD-031 Item Template Declaration, Processing, And Cloning.

ValueBehaviorInvalid whenProvenance and ITD
response Candidate response variable declared by QTI and usually bound to an interaction. Used outside qti.variable_declaration.variable_kind, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-013 Variable Declaration Projection, ITD-007 Provenance Labels, ITD-031 Item Template Declaration, Processing, And Cloning

outcome Scoring, feedback, or reporting variable set by default values or processing rules. Used outside qti.variable_declaration.variable_kind, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-013 Variable Declaration Projection, ITD-007 Provenance Labels, ITD-031 Item Template Declaration, Processing, And Cloning

template Template variable from qti-template-declaration. The platform realizes these values server-side at delivery-session start, stores the realized values in qti.attempt.template_state, and scores against the realized correct response. Used outside qti.variable_declaration.variable_kind, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-013 Variable Declaration Projection, ITD-007 Provenance Labels, ITD-031 Item Template Declaration, Processing, And Cloning

context Contextual variable available to template or response processing, including candidate, test, or system context when declared. Used outside qti.variable_declaration.variable_kind, misspelled, or used contrary to this behavior. 1EdTech pass-through

ITD-013 Variable Declaration Projection, ITD-007 Provenance Labels, ITD-031 Item Template Declaration, Processing, And Cloning

Allowed values

Processing rule scope

qti.processing_rule.rule_scope processing_rule_scope_ck Platform gap fill

The row scope is a repository classification for query and execution order; QTI defines the processing elements and expressions themselves. ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning.

ValueBehaviorInvalid whenProvenance and ITD
response Rule belongs to response processing and computes outcome variables from candidate responses. Used outside qti.processing_rule.rule_scope, misspelled, or used contrary to this behavior. Platform gap fill

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

outcome Rule belongs to outcome processing at test or section level. Used outside qti.processing_rule.rule_scope, misspelled, or used contrary to this behavior. Platform gap fill

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

template Rule belongs to qti-template-processing and initializes template state before delivery. qti-set-template-value, qti-template-constraint, and qti-template-default run server-side at session start under the ITD-021 finite retry bound. Used outside qti.processing_rule.rule_scope, misspelled, or used contrary to this behavior. Platform gap fill

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

expression Row represents an expression subtree or operator nested inside response, outcome, or template processing. Used outside qti.processing_rule.rule_scope, misspelled, or used contrary to this behavior. Platform gap fill

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile, ITD-031 Item Template Declaration, Processing, And Cloning

Allowed values

Delivery session status

qti.delivery_session.status delivery_session_status_ck Platform gap fill

Delivery lifecycle states are platform persistence behavior. QTI defines item/test content and processing, not this session state machine. ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy.

ValueBehaviorInvalid whenProvenance and ITD
created Session exists and has a delivery JSON snapshot but has not yet become the active learner experience. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

active Candidate may interact with delivered content and create or update attempts. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

suspended Candidate work is paused and may be resumed with the same snapshot and session state. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

submitted Candidate has submitted the session; scoring and attempt records are complete enough for review. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

review Session is in review mode. Content and responses may be displayed, but interactions must not change response variables. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

closed Session is final for normal operations. Future edits to content do not affect it. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

voided Session is retained as an operational record but should not count toward reporting or outcomes. Used outside qti.delivery_session.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-015 Delivery Session Snapshots, ITD-024 Candidate And Learner Data Privacy

Allowed values

Attempt status

qti.attempt.status attempt_status_ck Platform gap fill

Attempt lifecycle states are platform persistence behavior around QTI response processing. ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy.

ValueBehaviorInvalid whenProvenance and ITD
active Candidate can still modify responses for this attempt. Used outside qti.attempt.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

suspended Candidate response state is saved for later continuation. Used outside qti.attempt.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

submitted Candidate submitted responses and processing has produced outcome state. Used outside qti.attempt.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

reviewed Attempt has been reviewed by an authorized person or workflow. Used outside qti.attempt.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

voided Attempt is retained for audit but excluded from reporting and outcomes. Used outside qti.attempt.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

Allowed values

Attempt timing status

qti.attempt.timing_status attempt_timing_status_ck Platform gap fill

Timing classification is platform persistence over stock QTI qti-time-limits and the QTI duration built-in. QTI defines the time-limit vocabulary; ITD-027 pins server-side enforcement and storage. ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile.

ValueBehaviorInvalid whenProvenance and ITD
untimed The pinned content declared no qti-time-limits for the delivered scope, so no max-time window was enforced. The attempt may still report a known duration, but timing does not affect acceptance. Used outside qti.attempt.timing_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

in_window The server received and measured the submitted attempt within effective_max_time_seconds for the session window. The QTI duration built-in available to outcome processing is server-measured. Used outside qti.attempt.timing_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

late_accepted The attempt arrived after the server-measured max-time window, but the QTI time limit allowed late submission; the attempt is accepted and the late flag remains visible in scoring/runtime reads. Used outside qti.attempt.timing_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

late_rejected The attempt arrived after the server-measured max-time window and allow-late-submission was false; the API returns qti:time-limit-exceeded (HTTP 422) and the row is retained only as rejected audit evidence if persisted. Used outside qti.attempt.timing_status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

Allowed values

Conformance run status

qti.conformance_run.status conformance_run_status_ck Platform gap fill

Release-evidence lifecycle values are platform gap fills. ITD-017 Conformance Evidence.

ValueBehaviorInvalid whenProvenance and ITD
running The conformance runner has started and assertions are not yet complete. Used outside qti.conformance_run.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

passed All required assertions for the targeted profile passed. Used outside qti.conformance_run.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

failed At least one required assertion failed. Used outside qti.conformance_run.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

error The runner could not complete because of tool, environment, or infrastructure failure. Used outside qti.conformance_run.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

Allowed values

Conformance assertion status

qti.conformance_assertion.status conformance_assertion_status_ck Platform gap fill

Per-assertion lifecycle values are generated evidence about implementation behavior, not QTI content. ITD-017 Conformance Evidence.

ValueBehaviorInvalid whenProvenance and ITD
passed This assertion met the expected result. Used outside qti.conformance_assertion.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

failed This assertion ran and found behavior that violates the target profile or platform contract. Used outside qti.conformance_assertion.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

skipped This assertion was intentionally not run, usually because it is out of profile or unavailable in the current runner. Used outside qti.conformance_assertion.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

error This assertion could not produce a valid pass/fail result because the runner or fixture failed. Used outside qti.conformance_assertion.status, misspelled, or used contrary to this behavior. Platform gap fill

ITD-017 Conformance Evidence

Allowed values

JSON projection lossiness

API projection metadata OpenAPI projection contract Platform gap fill

The platform names JSON projection lossiness because QTI defines XML, not public JSON projection envelopes. ITD-006 JSON Projection Boundaries, ITD-018 API Boundary.

ValueBehaviorInvalid whenProvenance and ITD
none The projection must preserve all spec-defined fields needed to reconstruct the generated object graph and canonical XML. Used outside API projection metadata, misspelled, or used contrary to this behavior. Platform gap fill

ITD-006 JSON Projection Boundaries, ITD-018 API Boundary

declared The projection may omit only explicitly documented authoring-only or diagnostic detail, such as source trace or mixed-content tail detail. Used outside API projection metadata, misspelled, or used contrary to this behavior. Platform gap fill

ITD-006 JSON Projection Boundaries, ITD-018 API Boundary

Allowed values

Platform tenant_status

platform.* tenant_status tenant_status Inherited platform table

Lifecycle state for platform.tenant rows. Modules must reject new customer writes unless the tenant is active. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform tenant_status, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
provisioning The tenant row exists, but required setup such as auth, domains, or integrations is not complete. Use when: Create this before the tenant can ingest content, launch learner activity, or receive production traffic. Invalid when: Used for a tenant that is already accepting module writes. Used outside platform.* tenant_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform tenant_status, ITD-025 Platform Substrate Inheritance

active Normal customer state. Tenant-scoped reads and writes may proceed when the JWT tenant claim and role checks pass. Use when: The tenant is fully configured and in good standing. Invalid when: Used while required onboarding, suspension, or archival conditions are unresolved. Used outside platform.* tenant_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform tenant_status, ITD-025 Platform Substrate Inheritance

suspended The tenant is known but temporarily blocked from ordinary customer writes. Use when: Billing, security, contract, or incident response requires stopping writes without deleting history. Invalid when: Used to hide a tenant that should be permanently archived or deleted through a documented retention flow. Used outside platform.* tenant_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform tenant_status, ITD-025 Platform Substrate Inheritance

archived The tenant is retained for history and audit, but new module writes are rejected except explicit maintenance or export flows. Use when: A customer relationship ended or a workspace was retired and records must remain queryable for retention. Invalid when: Used as a soft substitute for learner-runtime deletion, which belongs to module-specific learner data flows. Used outside platform.* tenant_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform tenant_status, ITD-025 Platform Substrate Inheritance

Allowed values

Platform module_key

platform.* module_key module_key Inherited platform table

Canonical module identifier stored in shared platform records. These values cover every documented shared-Supabase writer namespace allowed to create platform.idempotency_key and platform.audit_log rows. This is stable identity only; it does not mean a module surface is currently approved. A future writer becomes legal only when a later Platform data-dictionary and migration attempt adds its module_key value deliberately; raw free-form module strings are never accepted. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform module_key, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
platform Shared platform substrate, documentation, auth, idempotency, audit, and cross-module operations. Use when: The operation belongs to the platform surface itself, shared middleware, release tooling, or a cross-module administrative API. Invalid when: Used for a module-owned resource mutation that should remain attributed to its owning writer namespace. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

qti Question and Test Interoperability module namespace. The namespace remains valid while the QTI 1EdTech surface is under reconciliation. Use when: The operation touches QTI content, runtime, conformance, package import/export, or Alpha assessment facade records and the calling surface is itself allowed by the current release state. Invalid when: Used as a release-status signal, or used for roster, telemetry, standards, MAP, SIS, Alpha learning-surface, or platform-only administrative operations. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

oneroster OneRoster module namespace for roster, enrollment, class, school, user, course, and academic-session contracts. Use when: The operation touches OneRoster-owned roster data, import/export work, conformance evidence, or the OneRoster integration app. Invalid when: Used for QTI assessment resources, Caliper event telemetry, CASE standards data, NWEAMap rows, Ed-Fi records, Alpha facade rows, or platform-only tenant administration. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

caliper Caliper Analytics module namespace for event telemetry and metric-profile contracts. Use when: The operation touches Caliper event ingest, metric-profile validation, event-store reads, or the Caliper integration app. Invalid when: Used for QTI assessment content, OneRoster roster resources, CASE standards data, NWEAMap rows, Ed-Fi records, Alpha facade rows, or platform-only tenant administration. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

case CASE module namespace for CFDocument, CFItem, CFAssociation, CFPackage, and Alignment contracts. Use when: The operation touches CASE standards data, package import/export, graph reads, external-ID alignment, or the CASE integration app. Invalid when: Used for QTI assessment content, OneRoster roster resources, Caliper telemetry, NWEAMap rows, Ed-Fi records, Alpha facade rows, or platform-only tenant administration. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

nweamap NWEAMap sibling module namespace for MAP Growth export ingest, deduped test-of-record views, goal-strand rows, and NWEA account-scoped results. Use when: The operation imports NWEA CDF data, reconciles retakes, exposes NWEAMap 1EdTech rows, or writes audit/idempotency records for MAP-specific platform behavior. Invalid when: Used for Alpha Results rollups, generic assessment content, SIS administrative records, or non-MAP analytics. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

ed_fi Ed-Fi sibling module namespace for SIS-style administrative records such as attendance, guardians, program participation, transcripts, discipline, demographics, staff, and descriptors. Use when: The operation imports, validates, reads, or reconciles Ed-Fi-shaped school records and needs shared audit/idempotency attribution. Invalid when: Used for OneRoster identity truth, TimeBack learning facts, MAP results, or Alpha plain-language facade rows. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

people_and_orgs Alpha People & Orgs namespace for plain-language students, parents, guides, staff, schools, districts, levels, and effective-dated memberships over the shared roster base. Use when: The operation serves or writes the Alpha People & Orgs facade, source-shaped migration normalization, or school-language roster membership views. Invalid when: Used for raw OneRoster 1EdTech endpoints, SIS-only Ed-Fi records, learning results, or curriculum/content operations. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

curriculum Alpha Curriculum namespace for knowledge components, tracks, courses, course components, gates, remediation sequencing, policies, and learning-engine placement/routing state that is the same for every student. Use when: The operation authors, imports, routes, gates, remediates, or reconciles Curriculum-owned rows and needs shared audit/idempotency attribution. Invalid when: Used for student-specific Results mastery state, flat Content assets, Caliper Events, or OneRoster roster facts. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

content Alpha Content namespace for student-touchable assets: questions, stimuli, tests, test specs, test banks, articles, videos, audio, images, interactives, media assets, external links, scripts, and reusable banks. Use when: The operation creates, imports, catalogs, renders, or reconciles Content-owned rows and needs retry/audit attribution. Invalid when: Used for Curriculum sequencing, Results attempts/scores, Analytics rollups, or Events telemetry. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

events Alpha Events namespace for timestamped student interactions and Caliper-derived moments before they become settled results or rollups. Use when: The operation ingests, validates, reads, or reconciles learner interaction events and needs shared audit/idempotency attribution. Invalid when: Used for durable scores/mastery, derived Analytics measures, Content assets, or Curriculum structure. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

results Alpha Results namespace for durable student outcomes: test scores, gate passes, mastery state, working grade, report-card facts, XP awards, and Results-owned derived overlays. Use when: The operation writes, imports, reconciles, or reads settled Results facts and needs shared audit/idempotency attribution. Invalid when: Used for the original interaction event, raw Content, Curriculum policy, or Analytics window rollups. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

analytics Alpha Analytics namespace for typed derived facts and per-window rollups such as active/inactive/waste seconds, mastery deltas, XP totals, growth measures, and reporting views. Use when: The operation computes, materializes, refreshes, or reads Analytics-owned rollups and needs shared audit/idempotency attribution. Invalid when: Used for system-of-record Events, settled Results, source Content, or roster/Curriculum facts. Used outside platform.* module_key, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_key, ITD-025 Platform Substrate Inheritance

Allowed values

Platform module_release_status

platform.* module_release_status module_release_status Inherited platform table

Current release state exposed by the platform module registry and customer docs. It is derived from loop state, not from module_key values in platform.idempotency_key or platform.audit_log. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform module_release_status, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
approved The named module surface has passed the loop through its required release gate and may be advertised to cold integrators. Use when: Every required deliverable for that surface is currently approved in loop/<module>/state.json. Invalid when: Any required deliverable for that surface is doing, reviewing, changes_requested, rolled_back, or not_started. Used outside platform.* module_release_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_release_status, ITD-025 Platform Substrate Inheritance

under_reconciliation A previously published or linked surface is withdrawn while it is rebuilt against newer platform truth. Use when: The loop has rolled back that surface or restarted an upstream deliverable to reconcile a platform contract, but the module namespace remains valid for existing audit/idempotency rows. Invalid when: Used for a brand-new surface that was never published, or used to imply the public API is ready for new cold integrators. Used outside platform.* module_release_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_release_status, ITD-025 Platform Substrate Inheritance

in_progress The surface is being built and has not yet reached the customer-facing release gate. Use when: A current deliverable is doing, reviewing, or changes_requested and there is no previously approved public surface being withdrawn. Invalid when: Used after a rollback has invalidated previously advertised docs or API behavior. Used outside platform.* module_release_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_release_status, ITD-025 Platform Substrate Inheritance

rolled_back A surface deliverable has been explicitly invalidated and must not be treated as public release truth. Use when: loop/<module>/state.json marks the relevant deliverable or a required downstream deliverable as rolled_back. Invalid when: Used as a customer-facing substitute for under_reconciliation when the registry still lists the module surface. Used outside platform.* module_release_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_release_status, ITD-025 Platform Substrate Inheritance

not_started The module surface has no current approved or in-flight release surface. Use when: The surface entries are not_started in loop/<module>/state.json. Invalid when: Used for a surface with approved artifacts or an active current deliverable. Used outside platform.* module_release_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform module_release_status, ITD-025 Platform Substrate Inheritance

Allowed values

Platform surface_code

platform.* surface_code surface_code Inherited platform table

Which public or internal surface produced the shared record. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform surface_code, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
platform The platform substrate surface. Use when: The route, audit row, idempotency scope, or dictionary entry is cross-module rather than standard-specific. Invalid when: Used to mask whether a QTI expert or Alpha customer endpoint produced a module action. Used outside platform.* surface_code, misspelled, or used contrary to this behavior. Inherited platform table

Platform surface_code, ITD-025 Platform Substrate Inheritance

1edtech Expert standards surface that follows a 1EdTech specification exactly except documented gap fills. Use when: The operation is on an expert API or documentation surface for a 1EdTech standard. Invalid when: Used for Alpha facade calls that rename, restrict, cut, or extend standard language. Used outside platform.* surface_code, misspelled, or used contrary to this behavior. Inherited platform table

Platform surface_code, ITD-025 Platform Substrate Inheritance

alpha Plain-language customer and app-builder surface over the same persistence model. Use when: The operation comes from an Alpha API or documentation surface. Invalid when: Used for expert-only conformance mutation, standards package internals, or release tooling. Used outside platform.* surface_code, misspelled, or used contrary to this behavior. Inherited platform table

Platform surface_code, ITD-025 Platform Substrate Inheritance

Allowed values

Platform mutation_http_method

platform.* mutation_http_method mutation_http_method Inherited platform table

HTTP methods eligible for shared idempotency tracking. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform mutation_http_method, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
POST Create, import, upload, command, or async job operation where a network retry might duplicate work. Use when: The route creates work or resources and the customer site documents Idempotency-Key. Invalid when: Used for a read-only GET. Used outside platform.* mutation_http_method, misspelled, or used contrary to this behavior. Inherited platform table

Platform mutation_http_method, ITD-025 Platform Substrate Inheritance

PUT Full replacement mutation that may be retried by a client. Use when: The route is documented as retryable and uses If-Match or another validator if it can overwrite user work. Invalid when: Used without a request hash and concurrency rule. Used outside platform.* mutation_http_method, misspelled, or used contrary to this behavior. Inherited platform table

Platform mutation_http_method, ITD-025 Platform Substrate Inheritance

PATCH Partial update mutation that may be retried by a client. Use when: The route is documented as retryable and a duplicate patch must not apply twice. Invalid when: Used for non-repeatable patch semantics that the customer site has not made idempotent. Used outside platform.* mutation_http_method, misspelled, or used contrary to this behavior. Inherited platform table

Platform mutation_http_method, ITD-025 Platform Substrate Inheritance

DELETE Delete or redaction command where retrying should not produce a second distinct deletion event. Use when: The route documents retry behavior and audit requirements. Invalid when: Used for implicit retention cleanup that is not customer-visible or not keyed by Idempotency-Key. Used outside platform.* mutation_http_method, misspelled, or used contrary to this behavior. Inherited platform table

Platform mutation_http_method, ITD-025 Platform Substrate Inheritance

Allowed values

Platform idempotency_status

platform.* idempotency_status idempotency_status Inherited platform table

Replay lifecycle for platform.idempotency_key rows. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform idempotency_status, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
in_progress The first request claimed the key and the operation has not reached a final replayable outcome. Use when: The handler starts work and stores a lock before performing the mutation. Invalid when: Retained after locked_until has passed without takeover, completion, or failure handling. Used outside platform.* idempotency_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform idempotency_status, ITD-025 Platform Substrate Inheritance

completed The operation reached a successful replayable outcome. Same key plus same request hash returns the stored response. Use when: The mutation committed and response_status/response_body or resource pointers are safe to replay. Invalid when: Used when the committed resource is unknown or the stored response contains secrets or learner PII. Used outside platform.* idempotency_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform idempotency_status, ITD-025 Platform Substrate Inheritance

failed_permanent The original request reached a final caller-fixable error such as validation, authorization, conflict, or precondition failure. Use when: Replaying the same invalid request should return the same safe Problem response instead of re-running side effects. Invalid when: Used for transient 5xx failures that should be retried after the lock expires. Used outside platform.* idempotency_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform idempotency_status, ITD-025 Platform Substrate Inheritance

failed_transient The first attempt failed before a stable outcome could be recorded. Use when: A dependency or server failure prevents safe replay and the same request may take over after locked_until. Invalid when: Used after a database mutation might have committed without a response. Used outside platform.* idempotency_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform idempotency_status, ITD-025 Platform Substrate Inheritance

expired The key is retained only for audit or conflict explanation after its replay window has ended. Use when: expires_at has passed and the platform cleanup policy marks the row no longer replayable. Invalid when: Used to avoid returning a known conflict inside the documented replay window. Used outside platform.* idempotency_status, misspelled, or used contrary to this behavior. Inherited platform table

Platform idempotency_status, ITD-025 Platform Substrate Inheritance

Allowed values

Platform audit_action

platform.* audit_action audit_action Inherited platform table

Kind of customer-visible or high-risk action captured by platform.audit_log. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform audit_action, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
create A resource was created synchronously. Use when: The operation inserts a module or platform row that customers can later read. Invalid when: Used for import jobs that should be action=import. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

update An existing resource was changed. Use when: A mutable customer or administrative field changes. Invalid when: Used for append-only learner submission creation. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

delete A reusable resource or tenant-scoped object was deleted or archived. Use when: The operation removes or retires content, settings, or a non-runtime record. Invalid when: Used for learner-runtime deletion, which must be runtime_delete. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

import A package, roster, event batch, or standards bundle was accepted for ingest. Use when: The operation takes external source material and projects it into module tables. Invalid when: Used for manual object creation that has no source package or batch. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

export A customer or service-role actor generated an exportable data view. Use when: The operation produces a file, report, or export job with customer data. Invalid when: Used for ordinary API reads. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

read_privileged A read occurred through service-role, support, release, or other privileged authority. Use when: The read would not be available to an ordinary tenant-scoped caller. Invalid when: Used for normal customer GETs that are already covered by access logs. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

runtime_delete Learner runtime data was deleted or redacted under a student-data lifecycle rule. Use when: A student, parent, school, or retention process removes learner-specific state. Invalid when: Used for reusable content deletion. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

conformance_change A release or expert path changed conformance evidence. Use when: A conformance run starts, finishes, fails, or changes trust-relevant assertions. Invalid when: Used for read-only Alpha trust status views. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

trust_change Public trust status changed as a result of evidence, rollback, or release gating. Use when: A customer-visible trust summary moves between trusted, degraded, failed, or unknown states. Invalid when: Used for ordinary audit actions that do not change public trust. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

authz_denied An authenticated caller was denied by tenant, role, scope, service-role, or operation authorization. Use when: The request had an authenticated subject but failed authorization. Invalid when: Used for missing or invalid authentication; those are security logs, not tenant audit rows. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

maintenance A named platform or service-role maintenance operation touched durable state. Use when: Backfills, compatibility migrations, cleanup, or incident response make controlled changes. Invalid when: Used as a generic label when a more specific action exists. Used outside platform.* audit_action, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_action, ITD-025 Platform Substrate Inheritance

Allowed values

Platform audit_outcome

platform.* audit_outcome audit_outcome Inherited platform table

Final result of the audited action. This allowed-value set is inherited from the approved Platform 1EdTech data dictionary. Platform audit_outcome, ITD-025 Platform Substrate Inheritance.

ValueBehaviorInvalid whenProvenance and ITD
accepted The platform accepted work for asynchronous processing and returned a trackable resource. Use when: The HTTP status is usually 202 and later completion is represented by another row or resource state. Invalid when: Used after the work has already succeeded or failed. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

succeeded The action completed successfully. Use when: The HTTP status is 2xx and the intended state transition committed. Invalid when: Used when only validation passed but async work is still pending. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

failed_validation The action was rejected because caller-supplied data was malformed or semantically invalid. Use when: The HTTP status is usually 400 or 422-style validation mapped to 400 by platform docs. Invalid when: Used for authorization or conflict failures. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

failed_authorization The authenticated caller lacked tenant, role, scope, service-role, or operation authority. Use when: The HTTP status is 403. Invalid when: Used for missing Bearer token, which should not create a tenant audit row. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

failed_conflict The action conflicted with idempotency, uniqueness, state version, lifecycle, or resource state. Use when: The HTTP status is 409, 412, or 428. Invalid when: Used for not-found conditions. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

failed_not_found The resource was absent or not visible inside the authenticated tenant scope. Use when: The HTTP status is 404 and the action was high-risk enough to audit. Invalid when: Used to leak existence of resources outside the tenant. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

failed_server The action failed because of unexpected platform, dependency, runner, or storage behavior. Use when: The HTTP status is 5xx or equivalent background-job failure. Invalid when: Used for caller-fixable validation problems. Used outside platform.* audit_outcome, misspelled, or used contrary to this behavior. Inherited platform table

Platform audit_outcome, ITD-025 Platform Substrate Inheritance

Two first-class paths

Guardrails: rules the raw-DB path must apply to match the API.

An agent may answer a question by calling the QTI 1EdTech API or by querying these qti.* tables directly through this dictionary, and must get the same answer either way. The API applies the filters, version selection, projection choice, list-envelope rules, and join grains below on every read; a naive select * from <table> silently returns wrong-but-plausible rows unless it reproduces each rule. These guardrails are additive documentation only -- they do not change any field, table, or allowed value above; they document how the existing columns must be queried. The QTI surface ships exactly three root enumeration lists under ITD-026 plus operation-specific reads by known identifier under ITD-018; broader filter/sort/modifiedSince, runtime collection lists, sub-collection browsing, and eventing remain deferred.

1. Tenant scope -- always filter tenant_id

Rule the API enforces: Every read is scoped to the caller's tenant. The API never returns rows from another tenant; tenant_id comes from the authenticated JWT tenant claim and the path tenantId is checked against it, never trusted as authorization on its own. The same qti_identifier, package_hash, or candidate_ref can recur across tenants.

Why the API enforces it: tenant_id is the platform isolation boundary inherited from platform.tenant (ITD-008/ITD-025); it is not a QTI XML field. Two tenants share the same qti.* tables, and QTI identifiers and content hashes are only unique within a tenant, so an unscoped read mixes tenants and can resolve the wrong artifact.

Raw-DB path must do: Add `where tenant_id = $tenant` to every query against a tenant-owning table (qti.content_package, qti.artifact, qti.delivery_session). Tables without their own tenant_id (qti.artifact_version, qti.component, qti.variable_declaration, qti.processing_rule, qti.attempt) inherit tenant ownership through a parent: always reach them by joining `qti.artifact_version av join qti.artifact a on a.artifact_id = av.artifact_id where a.tenant_id = $tenant`, and reach attempts through `qti.attempt at join qti.delivery_session ds on ds.delivery_session_id = at.delivery_session_id where ds.tenant_id = $tenant`. Never select an artifact_version/component/attempt by id alone without proving tenant through its parent. qti.conformance_run / qti.conformance_assertion are platform-global evidence, not tenant data; do not treat them as tenant-scoped or expose them as tenant reads.

ITD-008 Tenant Boundary, ITD-025 Platform Substrate Inheritance, ITD-019 Security Boundary

2. Object-type / artifact_kind scope -- pick the right kind

Rule the API enforces: Each operation-specific read targets one logical artifact of one kind. qti.artifact is one shared table for every QTI root category; artifact_kind (item, test, section, stimulus, outcome-declaration, response-processing, result, usage-data, metadata, manifest-resource) is the column that distinguishes them. Manifest resources are also keyed by qti.package_resource.resource_type.

Why the API enforces it: A query that reads qti.artifact without pinning artifact_kind returns items mixed with tests, stimuli, response-processing, and manifest-only resources -- a set no single API read ever returns. Inferring kind from qti_identifier or title is unreliable; kind is the only authoritative discriminator.

Raw-DB path must do: When answering an item question add `and a.artifact_kind = 'item'`; for a test add `and a.artifact_kind = 'test'`; for a stimulus add `and a.artifact_kind = 'stimulus'`, etc. Do not union kinds unless the question genuinely spans them. When navigating package contents by manifest type, filter qti.package_resource.resource_type (e.g. imsqti_item_xmlv3p0, imsqti_test_xmlv3p0) within the package, not artifact rows alone.

ITD-012 Artifact Kind Allowed Values, ITD-002 Generated Object Model Hub

3. Version grain -- latest vs historical artifact_version

Rule the API enforces: qti.artifact_version is append-only and immutable; an artifact has many versions numbered by version_number. getAuthoringJson and getDeliveryJson answer about the CURRENT artifact, which the API resolves through qti.artifact.latest_version_id (equivalently the highest version_number). Historical reads (stable past delivery, audit) target a specific artifact_version_id.

Why the API enforces it: A raw `select * from qti.artifact_version where artifact_id = $a` returns every edition. Picking an arbitrary row -- or the lowest version -- answers a different question than the API's 'latest' read and can serve a superseded projection. latest_version_id must also agree with the highest version_number before it is authoritative.

Raw-DB path must do: For the current answer: `select av.* from qti.artifact a join qti.artifact_version av on av.artifact_version_id = a.latest_version_id where a.tenant_id = $tenant and a.artifact_id = $a`; if latest_version_id is null or you cannot trust it, fall back to `... join qti.artifact_version av on av.artifact_id = a.artifact_id ... order by av.version_number desc limit 1`. For a historical answer, select the exact artifact_version_id. Never assume one version per artifact.

ITD-011 Artifact Versioning, ITD-018 API Boundary, ITD-020 Validation And Rejection Policy

4. Projection grain -- authoring_json vs delivery_json vs object_graph vs source_xml

Rule the API enforces: One artifact_version carries four representations: source_xml/canonical_xml (interchange authority), object_graph (internal canonical hub, not a public contract), delivery_json (declared-lossy consumer projection), and authoring_json (lossless editor projection). Each operation-specific read returns exactly one: getAuthoringJson -> authoring_json, getDeliveryJson -> delivery_json, exportXml -> canonical/source XML, delivery sessions snapshot delivery_json.

Why the API enforces it: delivery_json may legitimately omit authoring-only and diagnostic fields, so serving it for an editor read silently loses spec-defined data. Serving object_graph as a public answer leaks an internal envelope that is not the published contract. Reading the wrong column produces a structurally valid but wrong-grain answer.

Raw-DB path must do: Read the column that matches the question: authoring/editing -> `authoring_json`; delivery/runtime rendering -> `delivery_json`; faithful XML round-trip/export -> `canonical_xml` (or `source_xml`); equivalence/diagnostics only -> `object_graph`. Never substitute delivery_json for authoring_json, and never expose object_graph as the consumer answer. A null projection means it was not generated yet -- do not fabricate it from another column.

ITD-006 JSON Projection Boundaries, ITD-004 XML Authority And Canonical Hashes, ITD-005 Lossless Relational Projection

5. Variable grain -- variable_kind, never infer from a null field

Rule the API enforces: qti.variable_declaration is one table for all four QTI variable categories, distinguished by variable_kind (response, outcome, template, context). identifiers are unique only within (artifact_version_id, variable_kind). correct_response, mapping, and default_value are kind- and authoring-dependent and are frequently null on legitimate rows.

Why the API enforces it: A query that reads variables without filtering variable_kind mixes response variables with outcomes, templates, and context -- which scoring and runtime never treat alike. Inferring 'this is the scored/correct variable' from a non-null correct_response or mapping (or treating a null as 'no such variable') gives a wrong-but-plausible answer; null means the optional authoring field was absent, not that the variable is unscored.

Raw-DB path must do: Filter the kind the question asks for, e.g. response variables: `... from qti.variable_declaration where artifact_version_id = $v and variable_kind = 'response'`. Join to the parent version through the tenant guardrail before reading. Read correct_response/mapping/default_value as present-or-absent authoring detail; never derive scored-ness, correctness, or existence from their nullness.

ITD-013 Variable Declaration Projection, ITD-002 Generated Object Model Hub

6. Processing grain -- rule_scope and execution order; never infer scoring from XML alone

Rule the API enforces: qti.processing_rule is one table for response, outcome, template, and expression rules, distinguished by rule_scope, and ordered within a scope by sequence_number (with parent_processing_rule_id forming the nested expression tree). The API's runtime answer (e.g. getCandidateRuntimeData processing_trace) comes from executing these rules in order, not from re-deriving scoring from the raw XML or object_graph.

Why the API enforces it: Reading processing rules without filtering rule_scope, or without ordering by sequence_number, executes the wrong logic in the wrong order and yields a score the API never produces. Flattening the parent/child tree loses nested-expression semantics. Inferring an outcome from the presence of a rule (rather than its ordered execution) is wrong-but-plausible.

Raw-DB path must do: For ordered response processing: `... from qti.processing_rule where artifact_version_id = $v and rule_scope = 'response' order by sequence_number` and walk children via parent_processing_rule_id; do the same per scope for outcome/template. Resolve operands against qti.variable_declaration in the same artifact_version. Never reconstruct scoring directly from XML/object_graph when the question is about processing outcome.

ITD-014 Processing Rule Projection, ITD-021 Runtime Execution Profile

7. Delivery-session / attempt grain -- candidate_ref, status, latest attempt

Rule the API enforces: Runtime answers (getCandidateRuntimeData) are keyed by (tenant_id, candidate_ref): the API selects that candidate's qti.delivery_session rows, then their qti.attempt rows. attempt_number is unique per (delivery_session, artifact_version); the 'current' attempt is the highest attempt_number for that pair. delivery_session.status and attempt.status carry lifecycle (created/active/suspended/submitted/review/closed/voided and active/suspended/submitted/reviewed/voided).

Why the API enforces it: Reading attempts by artifact_version alone returns every candidate's attempts. Treating any attempt row as 'the answer' ignores attempt_number ordering and returns a stale or superseded attempt. Counting voided sessions/attempts as live inflates results -- voided is the runtime retirement marker, parallel to a soft-delete, and must not be silently included in a 'current state' answer.

Raw-DB path must do: Resolve sessions: `... from qti.delivery_session where tenant_id = $tenant and candidate_ref = $candidate`. Resolve the current attempt per (session, artifact_version): `... order by attempt_number desc limit 1`, or `max(attempt_number)` grouped by (delivery_session_id, artifact_version_id). For a 'live/current' answer exclude retired states (`and ds.status <> 'voided'`, `and at.status <> 'voided'`); to audit retirements, select them explicitly. Never join attempts to a candidate without going through delivery_session under the same tenant.

ITD-015 Delivery Session Snapshots, ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy

8. Timed delivery grain -- server clock authority, never client timing

Rule the API enforces: For content that declares qti-time-limits, getDeliveryJson exposes timeLimits for display, startDeliverySession stores the server timing window, and submitAttempt computes timing_status plus effective_duration_seconds from server timestamps. The API rejects late submissions with qti:time-limit-exceeded when allowLateSubmission=false and accepts-but-flags late_accepted when allowLateSubmission=true. The QTI duration built-in available to outcome processing is server-measured.

Why the API enforces it: A client countdown is useful UI, but it is forgeable. If the raw path ignores timing_status/window_expires_at, or if a consumer computes duration from browser state, it can certify a late/tampered attempt as in-window and return a mastery outcome the API would not return. ITD-027 makes the server clock the shared source for both API and raw-DB answers.

Raw-DB path must do: For a timing answer, join `qti.attempt a` to `qti.delivery_session ds` under the tenant and read `ds.window_started_at`, `ds.window_expires_at`, `ds.effective_max_time_seconds`, `a.submitted_at`, `a.timing_status`, `a.effective_duration_seconds`, and `a.outcome_state`. For a current scoring answer, trust `a.timing_status` and `a.outcome_state` produced by server-side QTI outcome processing. Never recompute timing from a client elapsed field, browser countdown, local wall clock, or delivery_json_snapshot alone.

ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-015 Delivery Session Snapshots, ITD-016 Attempt State And Processing Trace, ITD-021 Runtime Execution Profile

9. Test-feature scope -- persisted constructs are not always server-executed

Rule the API enforces: qti-selection, qti-ordering, qti-branch-rule, qti-pre-condition, adaptive=true, qti-adaptive-selection, and qti-catalog-info are persisted in qti.component and surfaced in delivery_json. The QTI surface does not evaluate test-level navigation, CAT item picking, branch/pre-condition navigation, or non-time PNP selection server-side; it serves authored order and leaves those runtime choices to the integrator delivery engine/renderer. Extended time is the exception because it changes the server clock window.

Why the API enforces it: A raw query that sees a qti-selection or branch rule and then computes a selected/shuffled/branched order is answering a runtime question the API has explicitly deferred. Conversely, dropping the construct because runtime is deferred breaks interchange. The API and raw path must both preserve the construct while naming runtime ownership.

Raw-DB path must do: To audit preservation, read `qti.component where element_name in (...)` plus `artifact_version.delivery_json`. To audit server-executed timing, read delivery_session window fields and attempt timing_status. Do not create a platform-selected item-order answer from qti.component unless a future ITD ships navigation execution.

ITD-028 Test-Level Sequencing, Branching, And Adaptive Selection, ITD-029 Catalog And PNP Accessibility Activation, ITD-027 Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing, ITD-005 Lossless Relational Projection

10. PCI and template boundary -- client renders PCI; server realizes templates

Rule the API enforces: Portable Custom Interaction markup and modules round-trip through qti.component and qti.package_file, but the platform never executes vendor PCI JavaScript server-side. Item templates are different: qti-template-declaration and qti-template-processing round-trip through variable_declaration/processing_rule, and the platform realizes template variables server-side at delivery-session start because those values can change the correct response and score.

Why the API enforces it: Treating PCI and item templates as the same kind of client-owned feature creates a scoring vulnerability. PCI JavaScript is vendor UI code; the submitted response is then scored by ordinary server-side QTI processing. Template processing changes the correct response, so client-side realization would let a client forge scoring state.

Raw-DB path must do: For PCI, read qti.component/qti.package_file for markup/modules and qti.attempt.response_state/outcome_state for the submitted/scored result. For templates, read qti.variable_declaration.variable_kind='template', qti.processing_rule.rule_scope='template', and qti.attempt.template_state. Never execute PCI JS in raw/server workflows, and never regenerate template_state on read.

ITD-030 Portable Custom Interaction Persistence And Execution Boundary, ITD-031 Item Template Declaration, Processing, And Cloning, ITD-021 Runtime Execution Profile, ITD-016 Attempt State And Processing Trace

11. Results boundary -- QTI Results are attempt state; Caliper is another module

Rule the API enforces: A QTI assessmentResult document round-trips as artifact_kind=result, and getCandidateRuntimeData exposes the generated assessmentResult content through stored response_state, template_state, outcome_state, session context, and processing trace. QTI Usage Data round-trips as artifact_kind=usage-data and keeps item statistics / IRT values inside the QTI document. The QTI surface does not emit or own Caliper events.

Why the API enforces it: If a raw query looks for a QTI-owned gradebook, Caliper event, or item-statistics table, it invents storage the API does not publish. If it ignores attempt state and tries to regenerate results from current XML, it can disagree with the historical session the API returns.

Raw-DB path must do: For learner runtime results, join delivery_session -> attempt by tenant_id + candidate_ref and read response_state/template_state/outcome_state. For imported result/usage documents, read qti.artifact where artifact_kind in ('result','usage-data') and then the matching artifact_version XML/projections. Do not query or create QTI Caliper tables.

ITD-032 QTI Results Reporting And Caliper Boundary, ITD-016 Attempt State And Processing Trace, ITD-024 Candidate And Learner Data Privacy, ITD-012 Artifact Kind Allowed Values

12. Package status -- only imported packages are authoritative

Rule the API enforces: qti.content_package.import_status (importing, imported, rejected, superseded) governs which package the API treats as live. Ingest reads and artifact resolution only trust a package whose import_status = 'imported'; rejected and superseded rows are retained as evidence, and an in-flight 'importing' row is not yet authoritative.

Why the API enforces it: A raw join that picks up a rejected, superseded, or still-importing package resolves artifacts that the API would not serve, resurrecting content that was retired or never accepted. Package identity is also tenant-scoped on (tenant_id, package_hash) and (tenant_id, idempotency_key), so a hash/key lookup must include tenant_id.

Raw-DB path must do: When resolving a package or its artifacts for a current answer add `and import_status = 'imported'`. Look packages up by `where tenant_id = $tenant and package_hash = $h` (or idempotency_key), never by hash/key alone. To audit retired imports, select rejected/superseded explicitly; never silently include them in a 'current package' answer.

ITD-009 Package Resource And File Ingest, ITD-010 Idempotency And Hashes

13. Enumeration and query model -- three root lists only

Rule the API enforces: The API publishes exactly three tenant-owned root lists: listArtifacts over qti.artifact, listArtifactVersions over qti.artifact_version joined through qti.artifact, and listPackages over qti.content_package. Each list returns {items, nextCursor}, accepts only opaque cursor plus limit, orders by stable ascending creation timestamp plus primary key, and returns 200 with an empty page for a fresh tenant. Filter, sort, modifiedSince, delivery-session lists, attempt lists, candidate lists, result lists, and package-resource/file sub-collections remain deferred.

Why the API enforces it: ITD-026 fixes the lost-response and two-paths gap without turning QTI into a general query surface. A raw query that adds filters/sorts/modifiedSince, enumerates runtime learner data, or browses sub-collections answers a question the API still does not publish, while a raw query that refuses to enumerate the three roots contradicts the shipped API contract.

Raw-DB path must do: For root enumeration, use the exact list endpoint contracts in #list-endpoints: qti.artifact by tenant_id; qti.artifact_version by joining qti.artifact for tenant_id; qti.content_package by tenant_id. Use the documented stable order and cursor boundary. For other reads, drive the query from a known identifier the API also accepts (artifact_id, artifact_version_id, candidate_ref, package_hash), under the tenant guardrail. Do not implement ad-hoc filter/sort, created_at/updated_at modifiedSince feeds, runtime collection lists, or sub-collection browsing as API-equivalent answers.

ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-018 API Boundary, ITD-020 Validation And Rejection Policy

14. Candidate privacy -- candidate_ref is the only candidate key

Rule the API enforces: candidate_ref is an opaque, tenant-scoped, UUID-shaped pseudonym (enforced by delivery_session_candidate_ref_uuid_ck). The API identifies a learner only by candidate_ref; names, emails, phone numbers, SIS IDs, raw JWT subjects, and auth tokens are rejected before insert and never stored as candidate identity.

Why the API enforces it: Any raw query that tries to resolve a candidate by a human identifier, or that exposes created_by / session payloads as if they held learner PII, both fails to match the API (which only knows candidate_ref) and breaches the privacy boundary (ITD-024/ITD-019). created_by is a safe principal label, not a learner identity.

Raw-DB path must do: Join candidate runtime strictly on (tenant_id, candidate_ref); never attempt to map candidate_ref to a person inside this schema. Treat candidate_ref as the sole candidate join key, keep it tenant-scoped, and do not select or surface fields as learner PII -- the schema does not store it and the API does not return it.

ITD-024 Candidate And Learner Data Privacy, ITD-019 Security Boundary