1EdTech QTI 3.0 customer specification

TimeBack QTI persistence surface

A standards-first website for 1EdTech reviewers, QTI implementers, and their coding agents. It explains what this surface promises, which database rows hold each promise, and which architecture decision owns every platform gap fill.

QTI persistence flow from package to attempt Package qti.content_package Resources qti.package_resource Artifacts qti.artifact Versions qti.artifact_version Sessions qti.delivery_session Attempts qti.attempt 1EdTech XML remains authoritative; SQL rows and JSON projections are traceable platform persistence views.
16 database tables all source-labeled
176 documented fields 43 pass-through / 86 platform gap fill / 43 inherited / 4 compatibility
19 allowed-value sets with behavior and ITD trace
87 XSD-reachable processing elements 87 runtime-supported
311 in-profile XML examples full schema and round-trip evidence
2 canonical upstream URLs architecture plus data dictionary
API quickstart

Copy-paste setup for the single QTI deployment

The same hierarchical canonical API root serves the public demo tenant and real tenants. Set QTI_BASE_URL before running the commands; the canonical implementation root reserved for this surface is https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api, and clients must not use deploy-hash URLs.

Single API root

QTI_BASE_URL and BASE_URL both point at the canonical QTI API root. The shell setup fails fast if the environment has not been supplied by the driver, operator, or local tester.

export QTI_BASE_URL="${QTI_BASE_URL:?set QTI_BASE_URL to the canonical QTI API root}"
export BASE_URL="$QTI_BASE_URL"
export BASE="$BASE_URL"
export RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"

Demo credentials

Cold readers mint a demo token with no prior credentials. Use tenantId=demo for copy-paste setup; the public demo tenant UUID 00000000-0000-4000-8000-000000000003 is also accepted. Use the returned tenantId, candidate reference, and demoPackageZipUrl in the protected calls that follow.

DEMO_TOKEN_RESPONSE=$(curl -fsS -X POST "$BASE_URL/dev/mint?tenantId=demo")
export TOKEN=$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.token')
export TENANT_ID=$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.tenantId')
export CANDIDATE_REF=$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.demoCandidateRef // "9c41d14e-d011-4517-927e-b9bf0b7d5df4"')
export DEMO_RUN_ID=$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.demoRunId')
export DEMO_PACKAGE_ZIP_URL=$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.demoPackageZipUrl')
printf '%s' "$DEMO_TOKEN_RESPONSE" | jq '.demoConformanceRequest // {}' > /tmp/qti-conformance-request.json

Real-tenant credentials

Reviewers and real tenants keep the same base URL. Real-tenant tokens are minted by operators or by the driver for review. Driver-minted reviewer tokens use the reviewer alias internally and are authorized for reviewer tenant 00000000-0000-4000-8000-000000000004.

export TOKEN="${QTI_REVIEWER_JWT:?operator must mint the scoped reviewer JWT}"
export TENANT_ID="${QTI_REVIEWER_TENANT_ID:-00000000-0000-4000-8000-000000000004}"
export CANDIDATE_REF="9c41d14e-d011-4517-927e-b9bf0b7d5df4"
export DEMO_PACKAGE_ZIP_URL="/fixtures/qti-package.zip?runId=$RUN_ID"
printf '{}' > /tmp/qti-conformance-request.json

One-sitting workflow: core delivery path

After either credential setup above, these commands exercise the reviewer workflow in order: mint a demo token when needed, download the per-run QTI package fixture, ingest it, use the ingest response handles, fetch delivery JSON, read lossless authoring JSON and its ETag, save authoring JSON with If-Match, export XML, start a session, submit an attempt, read one candidate's runtime data, delete that candidate's runtime data, and run conformance with {}.

The tenant-owned recovery endpoints listPackages, listArtifacts, and listArtifactVersions are part of the implementation contract and are documented below with full schemas. They are not required for this cold delivery smoke path because package ingest returns the package, artifact, and version handles used by the next calls.

# 1. Confirm the descriptor exposes the same operation order and fixture template.
curl -fsS "$BASE_URL" | jq '.quickstart.operationOrder, .fixtures.qtiPackageZipTemplate'

# 2. Download the mint-returned fixture URL, or the descriptor template for real-tenant reviewer runs.
curl -fsS "$BASE_URL$DEMO_PACKAGE_ZIP_URL" -o /tmp/qti-package.zip

# 3. ingestContentPackage: import the per-run ZIP package.
curl -fsS -X POST "$BASE_URL/tenants/$TENANT_ID/qti/packages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: pkg-upload-demo-$RUN_ID" \
  -H "Content-Type: application/zip" \
  -H "X-QTI-Profile: qti-3.0" \
  --data-binary @/tmp/qti-package.zip \
  -o /tmp/qti-ingest.json

export ARTIFACT_ID="$(jq -r '.artifacts[0].artifactId' /tmp/qti-ingest.json)"
export ARTIFACT_VERSION_ID="$(jq -r '.artifacts[0].artifactVersionId' /tmp/qti-ingest.json)"

# 4. getDeliveryJson: fetch the delivery projection for client rendering.
curl -fsS -D /tmp/qti-delivery.headers \
  "$BASE_URL/tenants/$TENANT_ID/qti/artifact-versions/$ARTIFACT_VERSION_ID/delivery-json" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-delivery.json

# 5. getAuthoringJson: read the lossless edit projection and capture its ETag.
curl -fsS -D /tmp/qti-authoring-read.headers \
  "$BASE_URL/tenants/$TENANT_ID/qti/artifacts/$ARTIFACT_ID/authoring-json" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-authoring-read.json

export ETAG="$(awk 'BEGIN{IGNORECASE=1} /^etag:/ {sub(/\r$/, "", $2); print $2}' /tmp/qti-authoring-read.headers)"

# 6. saveAuthoringJson: derive the save body from authoring JSON and send If-Match.
jq --arg title "$(jq -r '.qti.attributes.title // "QTI item"' /tmp/qti-authoring-read.json), saved through authoring JSON" '
  def strip_source:
    if type == "object" then del(.source) | with_entries(.value |= strip_source)
    elif type == "array" then map(strip_source)
    else . end;

  {
    documentId,
    sourceBundleVersion,
    lossiness: "none",
    qti: (.qti | strip_source)
  }
  | .qti.attributes = ((.qti.attributes // {}) + {title: $title})
' /tmp/qti-authoring-read.json > /tmp/qti-authoring-save.json

curl -fsS -X PUT "$BASE_URL/tenants/$TENANT_ID/qti/artifacts/$ARTIFACT_ID/authoring-json" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: $ETAG" \
  --data-binary @/tmp/qti-authoring-save.json \
  -o /tmp/qti-save.json

export SAVED_ARTIFACT_VERSION_ID="$(jq -r '.artifactVersionId' /tmp/qti-save.json)"

# 7. exportXml: read canonical XML for the saved immutable version.
curl -fsS -D /tmp/qti-xml.headers \
  "$BASE_URL/tenants/$TENANT_ID/qti/artifact-versions/$SAVED_ARTIFACT_VERSION_ID/xml" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/xml" \
  -o /tmp/qti-export.xml

# 8. startDeliverySession: create a runtime session against the saved version.
curl -fsS -X POST "$BASE_URL/tenants/$TENANT_ID/qti/delivery-sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"candidateRef\":\"$CANDIDATE_REF\",\"artifactVersionId\":\"$SAVED_ARTIFACT_VERSION_ID\"}" \
  -o /tmp/qti-session.json

export DELIVERY_SESSION_ID="$(jq -r '.deliverySessionId' /tmp/qti-session.json)"

# 9. submitAttempt: submit a response and inspect scoring proof.
curl -fsS -X POST "$BASE_URL/tenants/$TENANT_ID/qti/delivery-sessions/$DELIVERY_SESSION_ID/attempts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"artifactVersionId\":\"$SAVED_ARTIFACT_VERSION_ID\",\"responses\":{\"RESPONSE\":\"ChoiceA\"}}" \
  -o /tmp/qti-attempt.json

jq '.outcomeState.SCORE, (.processingTrace[0].event // .processingTrace[0].rule)' /tmp/qti-attempt.json

# 10. getCandidateRuntimeData: read the candidate-scoped runtime proof before deletion.
curl -fsS "$BASE_URL/tenants/$TENANT_ID/qti/candidates/$CANDIDATE_REF/runtime-data" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-runtime-data.json

jq '.candidateRef, .deliverySessionCount, .attemptCount' /tmp/qti-runtime-data.json

# 11. deleteCandidateRuntimeData: remove this candidate's runtime rows.
curl -i -X DELETE "$BASE_URL/tenants/$TENANT_ID/qti/candidates/$CANDIDATE_REF/runtime-data" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-delete.txt

# 12. runConformance: send the explicit empty body used by the configured conformance runner.
curl -fsS -X POST "$BASE_URL/qti/conformance-runs" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data '{}' \
  -o /tmp/qti-conformance.json

jq '.status, .summary.byStatus, .summary.bundleHash, .conformanceRunId, .runId' /tmp/qti-conformance.json

Node.js client example

This is the same workflow as the shell path, written as a Node 20 client with fetch. It uses a reviewer token when QTI_REVIEWER_JWT is present; otherwise it mints a public demo token and uses the mint-returned per-run fixture URL.

import { writeFile } from "node:fs/promises";

const baseUrl = process.env.QTI_BASE_URL;
if (!baseUrl) {
  throw new Error("Set QTI_BASE_URL to the canonical QTI API root before running this client.");
}
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
const reviewerToken = process.env.QTI_REVIEWER_JWT || "";
const reviewerTenantId = process.env.QTI_REVIEWER_TENANT_ID || "00000000-0000-4000-8000-000000000004";
const runId = new Date().toISOString().replace(/[-:.TZ]/g, "") + "-" + process.pid;

function apiUrl(path) {
  if (/^https?:\/\//.test(path)) return new URL(path);
  return new URL(normalizedBaseUrl + path);
}

async function request(path, init = {}, parse = "json") {
  const response = await fetch(apiUrl(path), init);
  const text = await response.text();
  if (!response.ok) {
    throw new Error(response.status + " " + response.statusText + " from " + path + ": " + text);
  }
  if (parse === "empty") return { response, body: null };
  if (parse === "text") return { response, body: text };
  return { response, body: text ? JSON.parse(text) : null };
}

function jsonHeaders(token, extra = {}) {
  return {
    Authorization: "Bearer " + token,
    "Content-Type": "application/json",
    ...extra
  };
}

function stripSource(value) {
  if (Array.isArray(value)) return value.map(stripSource);
  if (value && typeof value === "object") {
    return Object.fromEntries(
      Object.entries(value)
        .filter(([key]) => key !== "source")
        .map(([key, nested]) => [key, stripSource(nested)])
    );
  }
  return value;
}

let token = reviewerToken;
let tenantId = reviewerTenantId;
let candidateRef = "9c41d14e-d011-4517-927e-b9bf0b7d5df4";
let packagePath = "/fixtures/qti-package.zip?runId=" + encodeURIComponent(runId);

if (!token) {
  const demo = await request("/dev/mint?tenantId=demo", { method: "POST" });
  token = demo.body.token;
  tenantId = demo.body.tenantId;
  candidateRef = demo.body.demoCandidateRef || candidateRef;
  packagePath = demo.body.demoPackageZipUrl || packagePath;
}

const packageResponse = await fetch(apiUrl(packagePath));
if (!packageResponse.ok) {
  throw new Error("Failed to download fixture package: " + packageResponse.status);
}
const packageBytes = new Uint8Array(await packageResponse.arrayBuffer());

const ingest = await request("/tenants/" + tenantId + "/qti/packages", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + token,
    "Idempotency-Key": "pkg-upload-demo-" + runId,
    "X-QTI-Profile": "qti-3.0",
    "Content-Type": "application/zip"
  },
  body: packageBytes
});

const artifact = ingest.body.artifacts[0];
const delivery = await request(
  "/tenants/" + tenantId + "/qti/artifact-versions/" + artifact.artifactVersionId + "/delivery-json",
  { headers: { Authorization: "Bearer " + token } }
);

const authoringBody = {
  documentId: "",
  sourceBundleVersion: "",
  lossiness: "none",
  qti: {}
};
const authoringRead = await request(
  "/tenants/" + tenantId + "/qti/artifacts/" + artifact.artifactId + "/authoring-json",
  { headers: { Authorization: "Bearer " + token } }
);
authoringBody.documentId = authoringRead.body.documentId;
authoringBody.sourceBundleVersion = authoringRead.body.sourceBundleVersion;
authoringBody.qti = stripSource(authoringRead.body.qti);
authoringBody.qti.attributes = {
  ...(authoringBody.qti.attributes || {}),
  title: ((authoringBody.qti.attributes || {}).title || "QTI item") + ", saved through Node client"
};

const save = await request("/tenants/" + tenantId + "/qti/artifacts/" + artifact.artifactId + "/authoring-json", {
  method: "PUT",
  headers: jsonHeaders(token, { "If-Match": authoringRead.response.headers.get("etag") }),
  body: JSON.stringify(authoringBody)
});

const savedVersionId = save.body.artifactVersionId;
const xml = await request(
  "/tenants/" + tenantId + "/qti/artifact-versions/" + savedVersionId + "/xml",
  { headers: { Authorization: "Bearer " + token, Accept: "application/xml" } },
  "text"
);
await writeFile("/tmp/qti-node-export.xml", xml.body);

const session = await request("/tenants/" + tenantId + "/qti/delivery-sessions", {
  method: "POST",
  headers: jsonHeaders(token),
  body: JSON.stringify({ candidateRef, artifactVersionId: savedVersionId })
});

const attempt = await request(
  "/tenants/" + tenantId + "/qti/delivery-sessions/" + session.body.deliverySessionId + "/attempts",
  {
    method: "POST",
    headers: jsonHeaders(token),
    body: JSON.stringify({
      artifactVersionId: savedVersionId,
      responses: { RESPONSE: "ChoiceA" }
    })
  }
);

const runtimeData = await request(
  "/tenants/" + tenantId + "/qti/candidates/" + candidateRef + "/runtime-data",
  { headers: { Authorization: "Bearer " + token } }
);

await request(
  "/tenants/" + tenantId + "/qti/candidates/" + candidateRef + "/runtime-data",
  { method: "DELETE", headers: { Authorization: "Bearer " + token } },
  "empty"
);

const conformance = await request("/qti/conformance-runs", {
  method: "POST",
  headers: jsonHeaders(token),
  body: "{}"
});

console.log({
  packageId: ingest.body.packageId,
  savedVersionId,
  score: attempt.body.outcomeState.SCORE,
  runtimeSessions: runtimeData.body.deliverySessionCount,
  runtimeAttempts: runtimeData.body.attemptCount,
  traceEvent: attempt.body.processingTrace[0].event || attempt.body.processingTrace[0].rule,
  conformanceStatus: conformance.body.status,
  conformanceRunId: conformance.body.conformanceRunId
});
Authentication

Authentication and request controls

Read this section before implementing any endpoint. It defines the demo token mint helper plus the token, tenant, idempotency, optimistic-concurrency, profile, service-role, and HTTPS rules shared by protected routes.

ControlRequirementEndpointsTrace
Bearer JWT Every protected QTI operation requires Bearer authentication over HTTPS. The verifier checks issuer, expiry, signature, subject, tenant claim, and trusted key material before request handling. The only unauthenticated route is /dev/mint?tenantId=demo on the same canonical QTI API root named by QTI_BASE_URL. ingestContentPackage
listPackages
listArtifacts
listArtifactVersions
getDeliveryJson
getAuthoringJson
saveAuthoringJson
exportXml
startDeliverySession
submitAttempt
getCandidateRuntimeData
deleteCandidateRuntimeData
runConformance
Security Boundary, API Boundary
Demo token mint The QTI implementation exposes POST /dev/mint?tenantId=demo relative to QTI_BASE_URL without authentication so a cold reader can obtain a tenant-scoped demo JWT and per-run package fixture URLs. The copy-paste default is tenantId=demo; the public demo tenant UUID 00000000-0000-4000-8000-000000000003 is also accepted and maps to the same seeded tenant. The response returns the tenantId clients use in protected /tenants/{tenantId}/... calls. Real-tenant tokens are operator-minted out of band; reviewers use QTI_REVIEWER_JWT from .env.local. mintDemoToken platform.tenant.tenant_id, API Boundary, Security Boundary, Platform Substrate Inheritance
Tenant claim matching For /tenants/{tenantId}/... routes, the JWT tenant claim must equal the tenantId path value returned by token minting or assigned to the real tenant. Tenant identifiers are platform metadata, not QTI content. ingestContentPackage
listPackages
listArtifacts
listArtifactVersions
getDeliveryJson
getAuthoringJson
saveAuthoringJson
exportXml
startDeliverySession
submitAttempt
getCandidateRuntimeData
deleteCandidateRuntimeData
platform.tenant.tenant_id, Tenant Boundary, Security Boundary, Platform Substrate Inheritance
Idempotency-Key Package ingest requires Idempotency-Key so retrying the same upload returns the same package outcome instead of creating duplicate package rows. ingestContentPackage platform.idempotency_key.idempotency_key, qti.content_package.platform_idempotency_key_id, qti.content_package.idempotency_key, Idempotency And Hashes, Platform Substrate Inheritance
If-Match Read authoring JSON first, then send the returned ETag as If-Match when saving. Missing preconditions fail with 428; stale or conflicting version state fails with 409. getAuthoringJson
saveAuthoringJson
qti.artifact.latest_version_id, qti.artifact_version.version_number, qti.artifact_version.authoring_json, Artifact Versioning
Runtime-data read scope Candidate runtime-data read-back requires a tenant-scoped reviewer, service role, or equivalent runtime-data read scope. It is candidate-specific and does not create a generic candidate/session/attempt browsing API. getCandidateRuntimeData qti.delivery_session.candidate_ref, qti.attempt.delivery_session_id, Candidate And Learner Data Privacy, API Boundary
If-Match save Saving authoring JSON requires If-Match from the latest authoring-json read. Missing preconditions fail with 428; stale or conflicting version state fails with 409. saveAuthoringJson qti.artifact.latest_version_id, qti.artifact_version.version_number, Artifact Versioning
X-QTI-Profile Package ingest accepts X-QTI-Profile as the implementation conformance profile label. When omitted, it defaults to qti-3.0 and is stored with the package import evidence. ingestContentPackage qti.content_package.qti_profile, Validation And Rejection Policy
Service-role learner deletion Candidate runtime-data deletion requires either a tenant-authorized service role or equivalent tenant-scoped administrative authorization. Bulk access to learner runtime state is not available to ordinary delivery clients. deleteCandidateRuntimeData qti.delivery_session.candidate_ref, qti.attempt.delivery_session_id, Candidate And Learner Data Privacy, Operational DDL Discipline
HTTPS only Plain HTTP requests fail before application handling. Tokens, package bytes, learner responses, and processing traces are only accepted over HTTPS. ingestContentPackage
listPackages
listArtifacts
listArtifactVersions
getDeliveryJson
getAuthoringJson
saveAuthoringJson
exportXml
startDeliverySession
submitAttempt
getCandidateRuntimeData
deleteCandidateRuntimeData
runConformance
Security Boundary
curl "$BASE/tenants/$TENANT/qti/artifact-versions/$VERSION/delivery-json" \
  -H "Authorization: Bearer $TOKEN"
Errors

HTTP statuses and error body

All endpoint cards link back to this table. Error responses use the Problem shape below unless the successful endpoint explicitly returns XML or an empty 204 body.

CodeNameTriggering conditions on this surface
200 OK mintDemoToken returns a demo JWT; listPackages, listArtifacts, and listArtifactVersions return {items, nextCursor} pages, including empty pages for fresh tenants; getDeliveryJson returns a QtiDeliveryJsonEnvelope; getAuthoringJson returns a QtiAuthoringJsonEnvelope with ETag; saveAuthoringJson reuses an existing immutable ArtifactVersion when the authoring JSON canonicalizes to the current version; exportXml returns canonical XML; submitAttempt returns AttemptResult; getCandidateRuntimeData returns candidate-scoped session and attempt state.
201 Created saveAuthoringJson creates a new ArtifactVersion for changed authoring JSON; startDeliverySession returns DeliverySession.
202 Accepted ingestContentPackage accepts package validation/import work; runConformance accepts conformance-run work.
204 No Content deleteCandidateRuntimeData deleted the candidate runtime rows in scope and returns no body.
400 Bad Request Malformed XML, bundled-XSD or Schematron validation failure, package path traversal, missing package references, invalid list cursor or limit, privacy validation failure, invalid candidateRef format, authoring JSON that cannot round-trip, invalid response cardinality/base_type, unsupported runtime feature, or unregistered custom operator. See ingestContentPackage, listPackages, listArtifacts, listArtifactVersions, getCandidateRuntimeData, saveAuthoringJson, startDeliverySession, submitAttempt, and deleteCandidateRuntimeData.
401 Unauthorized Missing, malformed, expired, untrusted, or unsigned Bearer JWT. Applies to every protected operation in ingestContentPackage through runConformance; it does not apply to the demo-only mintDemoToken helper.
403 Forbidden Authenticated principal lacks access: tenant claim does not match tenantId, the route requires a runtime-data read/service role, the conformance runner is not authorized, or mintDemoToken was called for any tenantId other than the demo alias or public demo tenant UUID. See getDeliveryJson, getAuthoringJson, getCandidateRuntimeData, saveAuthoringJson, deleteCandidateRuntimeData, and runConformance.
404 Not Found Tenant-scoped resource is absent or belongs to another tenant: artifact version, artifact, delivery session, or candidate runtime scope. Repository root lists do not use 404 for fresh tenants; they return 200 with {items: [], nextCursor: null}. See getDeliveryJson, getAuthoringJson, saveAuthoringJson, exportXml, startDeliverySession, submitAttempt, getCandidateRuntimeData, and deleteCandidateRuntimeData.
409 Conflict Idempotency-Key reuse conflicts with the original ingest request, or If-Match/version state is stale during authoring save. See ingestContentPackage and saveAuthoringJson.
422 Unprocessable Content submitAttempt returns qti:time-limit-exceeded when the server-measured submission time is outside the QTI max-time window and allowLateSubmission is false. The client countdown is never authoritative.
428 Precondition Required The authoring save omitted required If-Match precondition. See saveAuthoringJson.
405 Method Not Allowed mintDemoToken accepts POST only and returns Allow: POST for other methods on the single deployment.
500/502/503/504 Server or gateway error Unexpected server, gateway, dependency, or release-blocking conformance failure. For XML export, a canonical XML validation failure is release-blocking evidence rather than silent output. See exportXml and runConformance.

Problem response schema

FieldTypeRequiredDescriptionTrace
typestringRequiredStable RFC 7807 problem type URI. QTI uses Platform problem URIs such as https://platform.timeback.com/problems/qti-validation-failed.Validation And Rejection Policy, Platform Substrate Inheritance
titlestringRequiredShort human-readable summary of the problem.Validation And Rejection Policy
statusintegerRequiredHTTP status code repeated inside the error body.Validation And Rejection Policy
detailstringOptionalSpecific detail safe for the caller. Must not include direct learner PII, JWTs, request headers, IP addresses, user agents, or raw package bytes.Candidate And Learner Data Privacy, Security Boundary
codestringRequiredStable machine-readable error code. Clients should branch on code rather than parsing title or detail.Validation And Rejection Policy, Platform Substrate Inheritance
requestIdstringRequiredRequest identifier shared with platform.audit_log for support and replay diagnostics.platform.audit_log.request_id, Security Boundary, Platform Substrate Inheritance
traceIdstringRequiredTrace identifier shared with platform.audit_log and operational telemetry.platform.audit_log.trace_id, Security Boundary, Platform Substrate Inheritance
fieldErrorsarray<object>OptionalField-level validation details for malformed input, package closure failures, invalid QTI values, or privacy validation failures. The public QTI contract uses this fieldErrors array rather than any legacy alternate validation array.Validation And Rejection Policy, Platform Substrate Inheritance
Compliance map

What a reviewer can verify from this page

This is the quick route for a 1EdTech expert checking fidelity, traceability, runtime behavior, privacy boundaries, and release evidence.

Reviewer questionAnswerArchitecture traceData trace
Can I prove this is still QTI 3.0 rather than a renamed product model? Yes. XML is the authority, generated object graph is the typed hub, source_trace/spec_trace point back to the bundled 1EdTech source, and Alpha vocabulary is deliberately absent. Offline 1EdTech Source Bundle, Generated Object Model Hub, XML Authority And Canonical Hashes, Provenance Labels qti.artifact_version.source_xml
qti.artifact_version.spec_trace
qti.component.source_trace
Can I see which data is 1EdTech pass-through and which data is a platform gap fill? Yes. The data dictionary labels 43 fields as 1EdTech pass-through, 86 fields as Platform gap fills, 43 fields as inherited Platform substrate, and 4 fields as QTI compatibility view fields, each with source basis and ITD links. Provenance Labels, Platform Substrate Inheritance qti.package_resource.resource_type
qti.artifact_version.delivery_json
qti.attempt.processing_trace
platform.audit_log.request_id
Can a package be exported faithfully after ingest? Yes. Original bytes are kept in package_file, XML is preserved as source_xml and canonical_xml, component rows rehydrate the generated object graph, and xml_hash checks equivalence. XML Authority And Canonical Hashes, Lossless Relational Projection, Package Resource And File Ingest qti.package_file.content_bytes
qti.artifact_version.canonical_xml
qti.component.component_path
Can later edits change historical learner results? No. Delivery sessions snapshot an immutable artifact version and delivery JSON. Attempts persist response, template, outcome, and trace snapshots against that session. Delivery Session Snapshots, Attempt State And Processing Trace qti.delivery_session.delivery_json_snapshot
qti.attempt.response_state
qti.attempt.outcome_state
Can an API client recover package and artifact handles after losing an ingest response? Yes. Reuse the saved Idempotency-Key when available, or call the tenant-owned package/artifact/version lists. The lists return packageHash, artifactId, latestArtifactVersionId, and artifactVersionId without requiring a client-maintained mirror. Tenant-Owned Enumeration And Lost-Response Recovery, Idempotency And Hashes, Artifact Versioning qti.content_package.package_hash
qti.content_package.idempotency_key
qti.artifact.latest_version_id
qti.artifact_version.artifact_version_id
Can direct learner PII leak into QTI XML or logs? The contract forbids it. candidate_ref is pseudonymous; runtime state and processing traces must be redacted; platform-generated QTI XML cannot include direct learner PII, auth tokens, raw PNP records, or session-specific runtime state. Candidate And Learner Data Privacy, Security Boundary qti.delivery_session.candidate_ref
qti.delivery_session.session_state
qti.attempt.processing_trace
Can I reproduce the claimed conformance surface? Yes. The conformance tables persist profile, bundle hash, runner version, assertion key, artifact/spec references, status, and diagnostics. The current evidence covers full schema validation, round trips, and processing coverage. Conformance Evidence qti.conformance_run.profile
qti.conformance_run.bundle_hash
qti.conformance_assertion.assertion_key
qti.conformance_assertion.status
End-to-end behavior

8 product workflows across 14 operations

These workflows are the customer-facing contract. The implementation deliverable should treat them as tests, not as descriptive copy.

01

Ingest a QTI package

Accept an IMS/QTI package, validate package closure and QTI XML against the bundled source bundle, then persist resources, files, artifacts, and immutable versions.

API
POST /tenants/{tenantId}/qti/packages
Data rows
qti.content_package, qti.package_resource, qti.package_file, qti.artifact, qti.artifact_version, qti.component, qti.variable_declaration, qti.processing_rule
Critical fields
qti.content_package.import_status, qti.content_package.package_hash, qti.package_resource.resource_type, qti.package_file.content_bytes, qti.artifact_version.source_xml, qti.artifact_version.canonical_xml, qti.artifact_version.xml_hash
Decision trace
Offline 1EdTech Source Bundle, XML Authority And Canonical Hashes, Package Resource And File Ingest, Idempotency And Hashes, Validation And Rejection Policy
curl -X POST "$BASE/tenants/$TENANT/qti/packages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: pkg-upload-2026-05-20-001" \
  -H "X-QTI-Profile: qti-3.0" \
  -H "Content-Type: application/zip" \
  --data-binary @qti-package.zip
02

Recover from a lost ingest response

If the client lost the package-ingest response, replay the same Idempotency-Key when it was durably saved; otherwise enumerate tenant-owned packages, artifacts, and artifact versions to recover canonical handles without keeping a client-side mirror.

API
GET /tenants/{tenantId}/qti/packages, GET /tenants/{tenantId}/qti/artifacts, GET /tenants/{tenantId}/qti/artifact-versions
Data rows
qti.content_package, qti.artifact, qti.artifact_version, platform.idempotency_key
Critical fields
qti.content_package.package_hash, qti.content_package.idempotency_key, qti.artifact.artifact_id, qti.artifact.qti_identifier, qti.artifact.latest_version_id, qti.artifact_version.artifact_version_id, platform.idempotency_key.idempotency_key
Decision trace
Tenant-Owned Enumeration And Lost-Response Recovery, Idempotency And Hashes, Artifact Versioning, API Boundary
curl "$BASE/tenants/$TENANT/qti/packages?limit=50" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-packages.json

curl "$BASE/tenants/$TENANT/qti/artifacts?limit=50" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-artifacts.json

jq -r '.items[] | select(.qtiIdentifier == "item-1") | .latestArtifactVersionId' /tmp/qti-artifacts.json
03

Inspect persisted QTI content

Use stable artifacts and immutable versions to inspect original XML, canonical XML, generated object graph, public JSON projections, variables, and processing rules without treating SQL rows as the 1EdTech source.

API
GET delivery JSON, GET XML export, or SQL/query APIs over artifact/version rows
Data rows
qti.artifact, qti.artifact_version, qti.component, qti.variable_declaration, qti.processing_rule
Critical fields
qti.artifact.artifact_kind, qti.artifact.qti_identifier, qti.artifact_version.object_graph, qti.artifact_version.authoring_json, qti.artifact_version.delivery_json, qti.component.ordinal, qti.variable_declaration.identifier, qti.processing_rule.rule_name
Decision trace
Generated Object Model Hub, Lossless Relational Projection, JSON Projection Boundaries, Artifact Versioning, Variable Declaration Projection, Processing Rule Projection
select artifact_kind, qti_identifier, latest_version_id
from qti.artifact
where tenant_id = $1 and qti_identifier = $2;
04

Deliver a frozen learner experience

Start a delivery session that snapshots one immutable artifact version and its delivery JSON so later content edits cannot change what the candidate saw.

API
POST /tenants/{tenantId}/qti/delivery-sessions
Data rows
qti.delivery_session, qti.artifact_version
Critical fields
qti.delivery_session.candidate_ref, qti.delivery_session.root_artifact_version_id, qti.delivery_session.status, qti.delivery_session.delivery_json_snapshot, qti.delivery_session.session_state
Decision trace
Delivery Session Snapshots, Candidate And Learner Data Privacy, Security Boundary
curl -X POST "$BASE/tenants/$TENANT/qti/delivery-sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"candidateRef":"9c41d14e-d011-4517-927e-b9bf0b7d5df4","artifactVersionId":"4f4c18f4-c2ec-4278-97e2-2b07a3070d91"}'
05

Submit responses and explain scoring

Run template, response, and outcome processing against the session snapshot; persist response, template, outcome, and trace state for reproducible scoring review.

API
POST /tenants/{tenantId}/qti/delivery-sessions/{deliverySessionId}/attempts
Data rows
qti.attempt, qti.delivery_session, qti.processing_rule, qti.variable_declaration
Critical fields
qti.attempt.response_state, qti.attempt.template_state, qti.attempt.outcome_state, qti.attempt.processing_trace, qti.attempt.status, qti.attempt.submitted_at
Decision trace
Attempt State And Processing Trace, Runtime Execution Profile, Candidate And Learner Data Privacy
curl -X POST "$BASE/tenants/$TENANT/qti/delivery-sessions/$SESSION/attempts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"artifactVersionId":"4f4c18f4-c2ec-4278-97e2-2b07a3070d91","responses":{"RESPONSE":"ChoiceA"}}'
06

Export QTI XML without platform taste

Export canonical XML from the generated object graph and validate it against the bundled schemas; QTI identifiers stay QTI identifiers and are not replaced by platform IDs.

API
GET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/xml
Data rows
qti.artifact, qti.artifact_version, qti.component
Critical fields
qti.artifact.qti_identifier, qti.artifact_version.source_xml, qti.artifact_version.canonical_xml, qti.artifact_version.xml_hash, qti.component.source_trace
Decision trace
XML Authority And Canonical Hashes, Lossless Relational Projection, Artifact Versioning, Validation And Rejection Policy
curl "$BASE/tenants/$TENANT/qti/artifact-versions/$VERSION/xml" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/xml"
07

Read and delete one candidate's runtime data

Read the delivery sessions and attempts for one tenant-scoped pseudonymous candidate reference, then delete that candidate's runtime rows while leaving reusable QTI content, package files, artifacts, and versions intact.

API
GET/DELETE /tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data
Data rows
qti.delivery_session, qti.attempt
Critical fields
qti.delivery_session.candidate_ref, qti.delivery_session.delivery_session_id, qti.attempt.delivery_session_id, qti.attempt.response_state, qti.attempt.processing_trace
Decision trace
Candidate And Learner Data Privacy, Tenant Boundary, API Boundary, Operational DDL Discipline
curl "$BASE/tenants/$TENANT/qti/candidates/9c41d14e-d011-4517-927e-b9bf0b7d5df4/runtime-data" \
  -H "Authorization: Bearer $TOKEN" \
  -o /tmp/qti-runtime-data.json

curl -X DELETE "$BASE/tenants/$TENANT/qti/candidates/9c41d14e-d011-4517-927e-b9bf0b7d5df4/runtime-data" \
  -H "Authorization: Bearer $TOKEN"
08

Persist conformance evidence

Run bundled examples, validation, round trips, processing assertions, and profile checks; persist the evidence so a reviewer can reproduce which profile and bundle were tested.

API
POST /qti/conformance-runs
Data rows
qti.conformance_run, qti.conformance_assertion
Critical fields
qti.conformance_run.profile, qti.conformance_run.bundle_hash, qti.conformance_run.status, qti.conformance_assertion.assertion_key, qti.conformance_assertion.spec_ref, qti.conformance_assertion.details
Decision trace
Conformance Evidence, Offline 1EdTech Source Bundle, Validation And Rejection Policy
curl -X POST "$BASE/qti/conformance-runs" \
  -H "Authorization: Bearer $TOKEN"
API reference

1EdTech QTI boundary contracts

All protected tenant-owned operations are tenant-scoped and authenticated; the demo token mint helper exists only so a cold reader can get a demo Bearer token. Repository list endpoints are root enumeration only: packages, artifacts, and artifact versions. Each endpoint carries inline request and response schema tables so a client can be implemented from this page without opening the OpenAPI YAML. These contracts preserve QTI names and platform gap-fill names; Alpha-style simplifications belong to the future Alpha surface, not here.

POST

/dev/mint?tenantId=demo

mintDemoToken

Mint a Bearer JWT for the public demo tenant on the same deployment used by real tenants.

Request

Unauthenticated demo helper. Query string tenantId accepts the copy-paste alias demo or the public demo tenant UUID 00000000-0000-4000-8000-000000000003; real-tenant tokens are minted out of band.

Success

200 application/json DemoToken with token, tenant, expiry, seeded demo identifiers, per-run package fixture URLs, and the conformance request body for the documented demo tenant in Postgres.

Rejects

400 when tenantId is missing, duplicated, or not the demo alias/public demo UUID; 403 when a different UUID is supplied; 405 for non-POST methods.

Request schema

FieldInTypeRequiredDescriptionTrace
tenantIdQuerystring enum: demo | 00000000-0000-4000-8000-000000000003RequiredPublic demo tenant selector. Use demo for cold copy-paste setup, or use the documented public demo tenant UUID. Protected tenant routes use the tenantId returned in the token response.Tenant Boundary, API Boundary
bodyBodyemptyRequired emptyThe token mint endpoint takes no JSON body.API Boundary

Response envelope schema

FieldTypeRequiredDescriptionTrace
tokenJWT stringRequiredHS256 Bearer token scoped to tenantId and a demo/service role. Send as Authorization: Bearer $TOKEN on protected routes.Security Boundary, platform.tenant.tenant_id
tokenTypeconst BearerRequiredToken type for the Authorization header.Security Boundary
tenantIdstringRequiredTenant claim embedded in the token and the exact value to place into /tenants/{tenantId}/... paths. The implementation may map the public demo alias to a stored tenant UUID, but clients must use this returned value.platform.tenant.tenant_id, qti.tenant.tenant_id
expiresAttimestampRequiredExpiry time for the short-lived demo token.Security Boundary
demoArtifactIduuidRequired in demoSeeded logical artifact identity paired with demoArtifactVersionId for the public demo tenant. Real tenants must use an artifactId returned by their own package ingest before calling saveAuthoringJson.qti.artifact.artifact_id, Artifact Versioning
demoArtifactVersionIduuidRequired in demoSeeded artifact version that lets a cold reader fetch delivery JSON, start a session, submit an attempt, and export XML even when package ingest is accepted asynchronously. Real tenants should use their own ingest response.qti.artifact_version.artifact_version_id
demoCandidateRefstring pseudonymous UUIDRequired in demoSeeded pseudonymous candidate reference for the smoke workflow and runtime-data deletion example. Real tenants must supply their own pseudonymous candidateRef.qti.delivery_session.candidate_ref, Candidate And Learner Data Privacy
demoRunIdstringRequired in demoPer-mint fixture run identifier used to generate package bytes that do not collide with earlier public-demo imports.qti.content_package.package_hash, Idempotency And Hashes
demoPackageZipUrlrelative URLRequired in demoReady-to-ingest per-run package zip on the same QTI_BASE_URL. Download it and POST it as application/zip to ingestContentPackage.qti.package_file.package_path, qti.package_file.media_type, Package Resource And File Ingest
demoPackageXmlUrlrelative URLRequired in demoPer-run loose XML fixture on the same QTI_BASE_URL; use the ZIP URL for the canonical cold workflow and this URL for minimal XML smoke checks.qti.package_file.package_path, qti.package_file.media_type, Package Resource And File Ingest
demoConformanceRequestempty objectRequired in demoRequest body to send to runConformance. Current value is {} because the server uses the configured offline QTI source bundle.qti.conformance_run.bundle_hash, qti.conformance_run.runner_version, Conformance Evidence
Related tables
platform.tenant, qti.tenant, qti.artifact_version, qti.delivery_session
Related fields
platform.tenant.tenant_id, qti.tenant.tenant_id, qti.artifact_version.artifact_version_id, qti.delivery_session.candidate_ref
HTTP statuses
200, 400, 403, 405, 500/502/503/504
POST

/tenants/{tenantId}/qti/packages

ingestContentPackage

Ingest an IMS/QTI content package or zip bundle.

Request

QTI XML item/test/section payload or binary application/zip body. Required Idempotency-Key header. Optional X-QTI-Profile header defaults to qti-3.0.

Success

202 application/json IngestAccepted with packageId, import status, and artifact versions when available.

Rejects

400 for malformed XML, bundled-XSD failure, package path traversal, missing package references, unsupported package closure, or privacy validation failure.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
Content-TypeHeaderapplication/xml or application/zipRequiredUse application/xml for the copy-paste single-item smoke payload below, or application/zip for an IMS/QTI package bundle.qti.package_file.media_type, Package Resource And File Ingest
Idempotency-KeyHeaderstringRequiredClient retry key. Reusing the same key for the same tenant and same package returns the same import outcome.platform.idempotency_key.idempotency_key, qti.content_package.platform_idempotency_key_id, qti.content_package.idempotency_key
X-QTI-ProfileHeaderstringOptional; default qti-3.0Implementation conformance profile label stored with the import record.qti.content_package.qti_profile
bodyBodyQTI XML bytes or binary zip bytesRequiredSingle QTI XML document for cold smoke tests, IMS Content Package zip, or loose QTI zip bundle. Original file bytes and normalized package hashes are persisted after validation.qti.package_file.content_bytes, qti.content_package.package_hash

Response envelope schema

FieldTypeRequiredDescriptionTrace
packageIduuidRequiredStable identifier for the accepted package ingest record.qti.content_package.package_id
statusenum(importing, imported, rejected, superseded)RequiredCurrent import lifecycle state for the accepted package.qti.content_package.import_status, import_status
artifactsarray<ArtifactVersion>OptionalArtifact versions created during ingest when projection is available in the response.qti.artifact_version
artifacts[].artifactIduuidRequired when artifacts[] is presentLogical artifact identity across versions.qti.artifact.artifact_id
artifacts[].artifactVersionIduuidRequired when artifacts[] is presentImmutable version identity for the generated artifact edition.qti.artifact_version.artifact_version_id
artifacts[].artifactKindenum(item, test, section, stimulus, outcome-declaration, response-processing, result, usage-data, metadata, manifest-resource)Required when artifacts[] is presentRepository category derived from QTI root element or manifest resource type.qti.artifact.artifact_kind, artifact_kind
artifacts[].rootElementstringRequired when artifacts[] is presentRoot XML element for this version.qti.artifact_version.root_element
artifacts[].schemaFilestringRequired when artifacts[] is presentBundled schema file used as validation authority.qti.artifact_version.schema_file
artifacts[].canonicalXmlHashstringRequired when artifacts[] is presentHash of canonical XML used for idempotency and preservation checks.qti.artifact_version.xml_hash
Related tables
qti.content_package, qti.package_resource, qti.package_file, qti.artifact, qti.artifact_version
Related fields
platform.idempotency_key.idempotency_key, qti.content_package.platform_idempotency_key_id, qti.content_package.idempotency_key, qti.content_package.package_hash, qti.content_package.import_status, qti.package_file.package_path, qti.artifact_version.source_xml, qti.artifact_version.canonical_xml
HTTP statuses
202, 400, 401, 403, 409, 500/502/503/504
GET

/tenants/{tenantId}/qti/packages

listPackages

Enumerate tenant-owned content-package imports for recovery and repository inspection.

Request

Tenant path scope plus optional cursor and limit query parameters. This list is the API-path twin of reading qti.content_package by tenant_id.

Success

200 application/json with {items, nextCursor}. A fresh tenant returns an empty page, never qti:not-found.

Rejects

400 for malformed cursor, foreign cursor, or limit outside 1..200; 401/403 for missing auth or tenant mismatch. Empty tenants still return 200 with an empty page.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
cursorQueryopaque stringOptionalPage cursor returned as nextCursor from the previous response. Cursors are opaque, tenant-scoped, and must not be constructed by clients.Tenant-Owned Enumeration And Lost-Response Recovery, Validation And Rejection Policy
limitQueryinteger 1..200Optional; default 50Maximum page size. Values above 200 or invalid numbers return a 400 Problem.Tenant-Owned Enumeration And Lost-Response Recovery, API Boundary

Response envelope schema

FieldTypeRequiredDescriptionTrace
itemsarray<PackageListItem>RequiredTenant-owned page of repository rows. A fresh tenant returns an empty array with HTTP 200 rather than 404.Tenant-Owned Enumeration And Lost-Response Recovery, Tenant Boundary
items[].packageIduuidRequiredStable package ingest record.qti.content_package.package_id
items[].manifestIdentifierstring|nullRequired nullableIMS manifest identifier when the import came from a package manifest.qti.content_package.manifest_identifier
items[].qtiProfilestringRequiredQTI profile label stored with the import, defaulting to qti-3.0.qti.content_package.qti_profile
items[].importStatusenum(importing, imported, rejected, superseded)RequiredCurrent import lifecycle state.qti.content_package.import_status, import_status
items[].packageHashsha256 hashRequiredNormalized package hash used to identify a package when a caller lost the ingest response.qti.content_package.package_hash, Idempotency And Hashes
items[].idempotencyKeystring|nullRequired nullableClient retry key when the original ingest request supplied one. It is visible so lost-response recovery can use the same surface path as the raw dictionary path.qti.content_package.idempotency_key, platform.idempotency_key.idempotency_key
items[].importedAttimestampRequiredStable ascending list order: importedAt, then packageId.qti.content_package.imported_at, Tenant-Owned Enumeration And Lost-Response Recovery
nextCursorstring|nullRequiredOpaque cursor for the next page, or null when the page is complete. Clients pass it back as cursor without parsing.Tenant-Owned Enumeration And Lost-Response Recovery

Filled examples

200 response body
{
  "items": [
    {
      "packageId": "c7653dc2-1086-4c4a-bd47-583f421c6f27",
      "manifestIdentifier": "manifest-demo-1",
      "qtiProfile": "qti-3.0",
      "importStatus": "imported",
      "packageHash": "sha256:4b3f1d9f2b3d52d2ed2e2a9133a106098f7c2d08b61781b63a9d64df9718f4d2",
      "idempotencyKey": "pkg-upload-demo-20260610",
      "importedAt": "2026-06-10T16:20:50.000Z"
    }
  ],
  "nextCursor": null
}
Related tables
qti.content_package, platform.idempotency_key
Related fields
qti.content_package.package_id, qti.content_package.manifest_identifier, qti.content_package.qti_profile, qti.content_package.import_status, qti.content_package.package_hash, qti.content_package.idempotency_key, qti.content_package.imported_at, platform.idempotency_key.idempotency_key
HTTP statuses
200, 400, 401, 403, 500/502/503/504
GET

/tenants/{tenantId}/qti/artifacts

listArtifacts

Enumerate tenant-owned logical QTI artifacts and their latest immutable version handles.

Request

Tenant path scope plus optional cursor and limit query parameters. Use this when a client needs to recover artifactId or latestArtifactVersionId after a lost ingest response.

Success

200 application/json with {items, nextCursor}. A fresh tenant returns an empty page, never qti:not-found.

Rejects

400 for malformed cursor, foreign cursor, or limit outside 1..200; 401/403 for missing auth or tenant mismatch. Empty tenants still return 200 with an empty page.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
cursorQueryopaque stringOptionalPage cursor returned as nextCursor from the previous response. Cursors are opaque, tenant-scoped, and must not be constructed by clients.Tenant-Owned Enumeration And Lost-Response Recovery, Validation And Rejection Policy
limitQueryinteger 1..200Optional; default 50Maximum page size. Values above 200 or invalid numbers return a 400 Problem.Tenant-Owned Enumeration And Lost-Response Recovery, API Boundary

Response envelope schema

FieldTypeRequiredDescriptionTrace
itemsarray<ArtifactListItem>RequiredTenant-owned page of repository rows. A fresh tenant returns an empty array with HTTP 200 rather than 404.Tenant-Owned Enumeration And Lost-Response Recovery, Tenant Boundary
items[].artifactIduuidRequiredLogical artifact identity across versions.qti.artifact.artifact_id
items[].artifactKindenum(item, test, section, stimulus, outcome-declaration, response-processing, result, usage-data, metadata, manifest-resource)RequiredRepository category derived from QTI root element or manifest resource type.qti.artifact.artifact_kind, artifact_kind
items[].qtiIdentifierstring|nullRequired nullableQTI identifier from the source artifact when present.qti.artifact.qti_identifier
items[].titlestring|nullRequired nullableQTI title or package-facing label when present. It is useful for human recovery but is not the primary key.qti.artifact.title
items[].latestArtifactVersionIduuidRequired when an artifact has an imported versionLatest immutable version handle to pass to getDeliveryJson, exportXml, startDeliverySession, or recovery workflows.qti.artifact.latest_version_id, qti.artifact_version.artifact_version_id, Artifact Versioning
items[].createdAttimestampRequiredStable ascending list order: createdAt, then artifactId.qti.artifact.created_at, Tenant-Owned Enumeration And Lost-Response Recovery
nextCursorstring|nullRequiredOpaque cursor for the next page, or null when the page is complete. Clients pass it back as cursor without parsing.Tenant-Owned Enumeration And Lost-Response Recovery

Filled examples

200 response body
{
  "items": [
    {
      "artifactId": "d21f5d0c-82f8-46b2-b42d-a23286f53740",
      "artifactKind": "item",
      "qtiIdentifier": "item-1",
      "title": "One point item",
      "latestArtifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
      "createdAt": "2026-06-10T16:20:50.000Z"
    }
  ],
  "nextCursor": null
}
Related tables
qti.artifact, qti.artifact_version
Related fields
qti.artifact.artifact_id, qti.artifact.artifact_kind, qti.artifact.qti_identifier, qti.artifact.title, qti.artifact.latest_version_id, qti.artifact.created_at, qti.artifact_version.artifact_version_id
HTTP statuses
200, 400, 401, 403, 500/502/503/504
GET

/tenants/{tenantId}/qti/artifact-versions

listArtifactVersions

Enumerate tenant-owned immutable artifact versions through their owning logical artifacts.

Request

Tenant path scope plus optional cursor and limit query parameters. The tenant boundary is enforced by joining qti.artifact_version through qti.artifact.

Success

200 application/json with {items, nextCursor}. A fresh tenant returns an empty page, never qti:not-found.

Rejects

400 for malformed cursor, foreign cursor, or limit outside 1..200; 401/403 for missing auth or tenant mismatch. Empty tenants still return 200 with an empty page.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
cursorQueryopaque stringOptionalPage cursor returned as nextCursor from the previous response. Cursors are opaque, tenant-scoped, and must not be constructed by clients.Tenant-Owned Enumeration And Lost-Response Recovery, Validation And Rejection Policy
limitQueryinteger 1..200Optional; default 50Maximum page size. Values above 200 or invalid numbers return a 400 Problem.Tenant-Owned Enumeration And Lost-Response Recovery, API Boundary

Response envelope schema

FieldTypeRequiredDescriptionTrace
itemsarray<ArtifactVersionListItem>RequiredTenant-owned page of repository rows. A fresh tenant returns an empty array with HTTP 200 rather than 404.Tenant-Owned Enumeration And Lost-Response Recovery, Tenant Boundary
items[].artifactVersionIduuidRequiredImmutable version identity for one QTI artifact edition.qti.artifact_version.artifact_version_id
items[].artifactIduuidRequiredOwning logical artifact; used to enforce tenant ownership.qti.artifact_version.artifact_id, qti.artifact.artifact_id
items[].versionNumberintegerRequiredMonotonic version number within the logical artifact.qti.artifact_version.version_number
items[].xmlHashsha256 hashRequiredCanonical XML hash for equivalence, idempotency, and round-trip checks.qti.artifact_version.xml_hash, XML Authority And Canonical Hashes
items[].rootElementstringRequiredRoot QTI XML element for this version.qti.artifact_version.root_element
items[].createdAttimestampRequiredStable ascending list order: artifact_version.created_at, then artifactVersionId.qti.artifact_version.created_at, Tenant-Owned Enumeration And Lost-Response Recovery
nextCursorstring|nullRequiredOpaque cursor for the next page, or null when the page is complete. Clients pass it back as cursor without parsing.Tenant-Owned Enumeration And Lost-Response Recovery

Filled examples

200 response body
{
  "items": [
    {
      "artifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
      "artifactId": "d21f5d0c-82f8-46b2-b42d-a23286f53740",
      "versionNumber": 1,
      "xmlHash": "sha256:927fc8cf2aa639ad38989b46c88fb7b28d7a857994cc2f954fcdf0658e6bdc49",
      "rootElement": "qti-assessment-item",
      "createdAt": "2026-06-10T16:20:50.000Z"
    }
  ],
  "nextCursor": null
}
Related tables
qti.artifact_version, qti.artifact
Related fields
qti.artifact_version.artifact_version_id, qti.artifact_version.artifact_id, qti.artifact_version.version_number, qti.artifact_version.xml_hash, qti.artifact_version.root_element, qti.artifact_version.created_at, qti.artifact.artifact_id, qti.artifact.tenant_id
HTTP statuses
200, 400, 401, 403, 500/502/503/504
GET

/tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json

getDeliveryJson

Fetch declared-lossiness delivery JSON generated from the canonical object model.

Request

Tenant path scope and artifactVersionId path parameter.

Success

200 application/json QtiDeliveryJsonEnvelope with ETag. lossiness is declared and omittedFields names the allowed omissions.

Rejects

404 if the artifact version is absent or belongs to another tenant; 401/403 if the token tenant claim does not match.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
artifactVersionIdPathuuidRequiredImmutable artifact version whose generated delivery JSON projection should be returned.qti.artifact_version.artifact_version_id

Response envelope schema

FieldTypeRequiredDescriptionTrace
ETagstring response headerRequiredValidator for the immutable projection returned by this version.qti.artifact_version.xml_hash, qti.artifact_version.artifact_version_id
artifactVersionIduuidOptionalImmutable artifact version identity, included when the projection generator emits it.qti.artifact_version.artifact_version_id
documentIdstringRequiredDocument identifier carried by the QTI projection; normally the QTI identifier when present.qti.artifact.qti_identifier
sourceBundleVersionstringRequiredIdentifier for the offline 1EdTech source bundle used to generate this projection.Offline 1EdTech Source Bundle
qtiQtiDeliveryNodeRequiredDelivery-safe generated QTI node tree. It preserves response, feedback, scoring, accessibility, and session identifiers.qti.artifact_version.delivery_json
qti.elementNameKnownElementNameRequiredQTI XML element name from the generated source bundle model.qti.component.element_name
qti.typeNameKnownTypeNameOptionalGenerated XSD type name when known for this node.qti.component.type_name
qti.attributesobject<string, string|number|boolean|null>RequiredAttribute projection for delivery clients.qti.component.attributes
qti.childrenarray<QtiDeliveryNode>RequiredChild QTI nodes in XML order.qti.component.ordinal, qti.component.component_path
qti.textstring|nullOptionalText value for text-bearing nodes when available in the delivery projection.qti.component.text_value
lossinessconst declaredRequiredSignals that only documented authoring-only or diagnostic fields may be omitted.qti.artifact_version.delivery_json, projection_lossiness
omittedFieldsarray<string>RequiredNames the fields intentionally left out of the delivery projection.qti.artifact_version.delivery_json, projection_lossiness
timeLimits.maxTimenumber secondsRequired when the QTI declares qti-time-limits max-timeQTI NonNegativeDouble max-time projected for client display. Server-side session timing remains authoritative.qti.artifact_version.delivery_json, qti.delivery_session.effective_max_time_seconds, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing
timeLimits.minTimenumber secondsOptional when the QTI declares qti-time-limits min-timeQTI NonNegativeDouble min-time projected for delivery clients.qti.artifact_version.delivery_json, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing
timeLimits.allowLateSubmissionbooleanRequired when timeLimits is present; default falseQTI late-submission allowance. false means a server-measured late attempt is rejected with qti:time-limit-exceeded.qti.artifact_version.delivery_json, qti.attempt.timing_status, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing

Filled examples

200 response body
{
  "artifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
  "documentId": "item-1",
  "sourceBundleVersion": "qti-3.0-platform3-2026-05-20",
  "qti": {
    "elementName": "qti-assessment-item",
    "typeName": "AssessmentItemType",
    "attributes": {
      "identifier": "item-1",
      "title": "One point item"
    },
    "children": [
      {
        "elementName": "qti-response-declaration",
        "attributes": {
          "identifier": "RESPONSE",
          "cardinality": "single",
          "base-type": "identifier"
        },
        "children": []
      },
      {
        "elementName": "qti-outcome-declaration",
        "attributes": {
          "identifier": "SCORE",
          "cardinality": "single",
          "base-type": "float"
        },
        "children": []
      }
    ]
  },
  "timeLimits": {
    "maxTime": 120,
    "minTime": 0,
    "allowLateSubmission": false
  },
  "lossiness": "declared",
  "omittedFields": [
    "sourceTrace",
    "authoringOnlyMetadata"
  ]
}
Related tables
qti.artifact_version, qti.delivery_session
Related fields
qti.artifact_version.delivery_json, qti.artifact_version.object_graph, qti.artifact_version.artifact_version_id, qti.delivery_session.effective_max_time_seconds
HTTP statuses
200, 401, 403, 404, 500/502/503/504
GET

/tenants/{tenantId}/qti/artifacts/{artifactId}/authoring-json

getAuthoringJson

Read the latest lossless authoring JSON projection for one logical artifact.

Request

Tenant path scope and artifactId path parameter. This is the read-before-write half of the authoring flow; it returns the ETag that must be sent as If-Match on saveAuthoringJson.

Success

200 application/json QtiAuthoringJsonEnvelope with ETag derived from the latest immutable artifact version.

Rejects

404 if the artifact is absent, has no latest version, or belongs to another tenant; 401/403 if the token tenant claim does not match.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
artifactIdPathuuidRequiredLogical artifact whose latest immutable version should be projected as lossless authoring JSON.qti.artifact.artifact_id, qti.artifact.latest_version_id

Response envelope schema

FieldTypeRequiredDescriptionTrace
ETagstring response headerRequiredCurrent artifact-version validator. Send this exact value as If-Match when calling saveAuthoringJson.qti.artifact.latest_version_id, qti.artifact_version.artifact_version_id, Artifact Versioning
artifactVersionIduuidOptionalLatest immutable artifact version that produced this authoring projection.qti.artifact.latest_version_id, qti.artifact_version.artifact_version_id
documentIdstringRequiredDocument identifier carried by the QTI authoring projection.qti.artifact.qti_identifier
sourceBundleVersionstringRequiredIdentifier for the offline 1EdTech source bundle used to generate this projection.Offline 1EdTech Source Bundle, qti.artifact_version.spec_trace
qtiQtiAuthoringNodeRequiredLossless generated QTI object graph node tree. Source trace may be present, and the body must round-trip to canonical XML.qti.artifact_version.authoring_json, qti.artifact_version.object_graph, qti.component.source_trace
qti.elementNameKnownElementNameRequiredRoot QTI XML element name from the generated source bundle model.qti.component.element_name
qti.typeNameKnownTypeNameOptionalGenerated XSD type name when known for this authoring node.qti.component.type_name
qti.attributesobject<string, string|number|boolean|null>RequiredLossless attribute projection for authoring clients.qti.component.attributes
qti.childrenarray<QtiAuthoringNode>RequiredChild QTI nodes in XML order, preserving authoring-relevant structure.qti.component.ordinal, qti.component.component_path
qti.sourceobjectOptionalGenerated source trace when present. Clients may omit it on save; the server infers bundled schema detail from the root element when safe.qti.component.source_trace, qti.artifact_version.spec_trace
lossinessconst noneRequiredAuthoring JSON is the lossless projection and is the safe base for edits that must round-trip to QTI XML.qti.artifact_version.authoring_json, projection_lossiness
editMetadataobjectOptionalEditor metadata that remains platform metadata; it must not replace QTI source trace or identifiers.qti.artifact_version.spec_trace

Filled examples

200 response body
{
  "artifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
  "documentId": "item-1",
  "sourceBundleVersion": "qti-3.0-platform3-2026-05-20",
  "qti": {
    "elementName": "qti-assessment-item",
    "typeName": "AssessmentItemType",
    "attributes": {
      "identifier": "item-1",
      "title": "One point item"
    },
    "source": {
      "schemaFile": "imsqti_itemv3p0p1_v1p0.xsd",
      "xpath": "/qti-assessment-item"
    },
    "children": []
  },
  "lossiness": "none",
  "editMetadata": {}
}
Related tables
qti.artifact, qti.artifact_version, qti.component
Related fields
qti.artifact.artifact_id, qti.artifact.latest_version_id, qti.artifact_version.artifact_version_id, qti.artifact_version.authoring_json, qti.artifact_version.object_graph, qti.component.source_trace
HTTP statuses
200, 401, 403, 404, 500/502/503/504
PUT

/tenants/{tenantId}/qti/artifacts/{artifactId}/authoring-json

saveAuthoringJson

Save a lossless authoring JSON edit and create or reuse an artifact version.

Request

QtiAuthoringJsonEnvelope body and required If-Match header for version safety.

Success

200 application/json ArtifactVersion when the authoring JSON canonicalizes to the current immutable version and that existing version is reused; 201 application/json ArtifactVersion when a changed authoring JSON creates a new immutable version.

Rejects

409 for stale If-Match or version conflict; 400 when authoring JSON cannot round-trip to the generated object graph and canonical XML.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
artifactIdPathuuidRequiredLogical artifact to edit. A changed save creates a new immutable artifact_version row; an unchanged canonical authoring body reuses the current immutable version.qti.artifact.artifact_id
If-MatchHeaderstringRequiredCurrent version validator. Missing If-Match is 428; stale values are 409.qti.artifact.latest_version_id, qti.artifact_version.version_number
artifactVersionIdBodyuuidOptionalVersion identity the authoring projection came from, when carried by the editor.qti.artifact_version.artifact_version_id
documentIdBodystringRequiredDocument identifier carried by the QTI authoring projection.qti.artifact.qti_identifier
sourceBundleVersionBodystringRequiredIdentifier for the offline 1EdTech source bundle used to generate this authoring projection.Offline 1EdTech Source Bundle
qtiBodyQtiAuthoringNodeRequiredLossless generated QTI object graph node tree. It must round-trip to canonical XML.qti.artifact_version.authoring_json, qti.artifact_version.object_graph
lossinessBodyconst noneRequiredAuthoring edits must preserve all spec-defined fields needed to reconstruct the object graph and XML.qti.artifact_version.authoring_json, projection_lossiness
editMetadataBodyobjectOptionalEditor metadata. It is platform metadata and must not replace QTI source trace.qti.artifact_version.spec_trace

Response envelope schema

FieldTypeRequiredDescriptionTrace
artifactIduuidRequiredLogical artifact identity across versions.qti.artifact.artifact_id
artifactVersionIduuidRequiredImmutable version identity created or reused by this save.qti.artifact_version.artifact_version_id
artifactKindenum(item, test, section, stimulus, outcome-declaration, response-processing, result, usage-data, metadata, manifest-resource)RequiredRepository category derived from QTI root element or manifest resource type.qti.artifact.artifact_kind, artifact_kind
rootElementstringRequiredRoot XML element for the new version.qti.artifact_version.root_element
schemaFilestringRequiredBundled schema file used as validation authority.qti.artifact_version.schema_file
canonicalXmlHashstringRequiredHash of canonical XML for the created or reused version.qti.artifact_version.xml_hash
Related tables
qti.artifact, qti.artifact_version, qti.component
Related fields
qti.artifact.latest_version_id, qti.artifact_version.version_number, qti.artifact_version.authoring_json, qti.artifact_version.xml_hash
HTTP statuses
200, 201, 400, 401, 403, 404, 409, 428, 500/502/503/504
GET

/tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/xml

exportXml

Export canonical XML from a persisted artifact version.

Request

Tenant path scope and artifactVersionId path parameter.

Success

200 application/xml canonical QTI XML.

Rejects

404 for missing or cross-tenant artifact version; 500/release-blocking evidence if canonical XML cannot validate against the bundled source bundle.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
artifactVersionIdPathuuidRequiredImmutable artifact version whose canonical XML should be exported.qti.artifact_version.artifact_version_id
AcceptHeaderapplication/xmlOptionalClients should request XML. The response body is canonical QTI XML, not JSON.qti.artifact_version.canonical_xml

Response envelope schema

FieldTypeRequiredDescriptionTrace
bodystring application/xmlRequiredCanonical XML generated from the persisted object graph and validated against the bundled source bundle.qti.artifact_version.canonical_xml, qti.artifact_version.object_graph
Content-Typeapplication/xml response headerRequiredSignals XML export; this endpoint does not return an API JSON envelope on success.XML Authority And Canonical Hashes
Related tables
qti.artifact_version, qti.component
Related fields
qti.artifact_version.canonical_xml, qti.artifact_version.xml_hash, qti.artifact_version.schema_file, qti.component.element_name
HTTP statuses
200, 401, 403, 404, 500/502/503/504
POST

/tenants/{tenantId}/qti/delivery-sessions

startDeliverySession

Start a delivery session using a snapshot of the current delivery JSON.

Request

JSON body with candidateRef and artifactVersionId. candidateRef must be an opaque tenant-scoped pseudonymous UUID string. rootArtifactVersionId is accepted only as a deprecated compatibility alias.

Success

201 application/json DeliverySession with deliverySessionId, rootArtifactVersionId, status, and deliveryJsonSnapshot.

Rejects

400 if candidateRef contains direct PII or if the root artifact version cannot produce delivery JSON; 401/403 for tenant mismatch.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
candidateRefBodystring pseudonymous UUIDRequiredOpaque tenant-scoped pseudonymous candidate reference. Direct learner PII is rejected.qti.delivery_session.candidate_ref
artifactVersionIdBodyuuidRequiredPublished request field for the immutable item, section, or test version to snapshot for this learner session.qti.delivery_session.root_artifact_version_id, qti.artifact_version.artifact_version_id

Response envelope schema

FieldTypeRequiredDescriptionTrace
deliverySessionIduuidRequiredStable session identifier exposed by delivery APIs.qti.delivery_session.delivery_session_id
rootArtifactVersionIduuidRequiredImmutable artifact version pinned by this session.qti.delivery_session.root_artifact_version_id
statusenum(created, active, suspended, submitted, review, closed, voided)RequiredSession lifecycle state after creation.qti.delivery_session.status, delivery_session_status
deliveryJsonSnapshotQtiDeliveryJsonEnvelope snapshotRequiredSnapshot of delivery JSON at session start. Later content edits cannot change what this session saw.qti.delivery_session.delivery_json_snapshot, qti.artifact_version.delivery_json
deliveryJsonSnapshot.documentIdstringRequiredQTI document identifier carried by the frozen delivery projection for this session.qti.artifact.qti_identifier, qti.delivery_session.delivery_json_snapshot
deliveryJsonSnapshot.sourceBundleVersionstringRequiredOffline 1EdTech QTI 3.0 bundle identifier used to generate the snapshot.Offline 1EdTech Source Bundle, qti.delivery_session.delivery_json_snapshot
deliveryJsonSnapshot.qti.elementNameKnownElementNameRequiredRoot QTI XML element name from the generated source bundle model.qti.component.element_name, qti.delivery_session.delivery_json_snapshot
deliveryJsonSnapshot.qti.attributes.identifierstringRequired when the QTI node has identifierQTI identifier attribute preserved inside the delivery-safe node tree.qti.component.attributes, qti.component.qti_identifier, qti.delivery_session.delivery_json_snapshot
deliveryJsonSnapshot.qti.children[]array<QtiDeliveryNode>RequiredChild nodes in XML order. Each child repeats elementName, optional typeName, attributes, text, and children.qti.component.ordinal, qti.component.component_path, qti.artifact_version.delivery_json
deliveryJsonSnapshot.lossinessconst declaredRequiredSignals that the snapshot is the delivery projection, not the lossless authoring object graph.qti.artifact_version.delivery_json, projection_lossiness
deliveryJsonSnapshot.omittedFields[]array<string>RequiredNames the authoring-only or diagnostic fields omitted from this delivery snapshot.qti.artifact_version.delivery_json, projection_lossiness
deliveryJsonSnapshot.timeLimits.maxTimenumber secondsRequired when the delivered QTI declares qti-time-limits max-timeQTI NonNegativeDouble max-time projected into the frozen delivery snapshot. Clients may display it, but the server clock remains authoritative.qti.delivery_session.delivery_json_snapshot, qti.delivery_session.effective_max_time_seconds, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing
deliveryJsonSnapshot.timeLimits.minTimenumber secondsOptional when the delivered QTI declares qti-time-limits min-timeQTI NonNegativeDouble min-time projected for delivery clients.qti.delivery_session.delivery_json_snapshot, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing
deliveryJsonSnapshot.timeLimits.allowLateSubmissionbooleanRequired when timeLimits is present; default falseQTI late-submission allowance. false means a server-measured late attempt fails with qti:time-limit-exceeded.qti.delivery_session.delivery_json_snapshot, qti.attempt.timing_status, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing

Filled examples

Request body
{
  "candidateRef": "9c41d14e-d011-4517-927e-b9bf0b7d5df4",
  "artifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91"
}
201 response body
{
  "deliverySessionId": "e0b41369-3019-42a5-a419-d5da6e33904f",
  "rootArtifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
  "status": "active",
  "deliveryJsonSnapshot": {
    "documentId": "item-1",
    "sourceBundleVersion": "qti-3.0-platform3-2026-05-20",
    "qti": {
      "elementName": "qti-assessment-item",
      "typeName": "AssessmentItemType",
      "attributes": {
        "identifier": "item-1",
        "title": "One point item"
      },
      "children": [
        {
          "elementName": "qti-response-declaration",
          "attributes": {
            "identifier": "RESPONSE",
            "cardinality": "single",
            "base-type": "identifier"
          },
          "children": []
        },
        {
          "elementName": "qti-outcome-declaration",
          "attributes": {
            "identifier": "SCORE",
            "cardinality": "single",
            "base-type": "float"
          },
          "children": []
        }
      ]
    },
    "lossiness": "declared",
    "omittedFields": [
      "sourceTrace",
      "authoringOnlyMetadata"
    ]
  }
}
Related tables
qti.delivery_session, qti.artifact_version
Related fields
qti.delivery_session.candidate_ref, qti.delivery_session.root_artifact_version_id, qti.delivery_session.status, qti.delivery_session.delivery_json_snapshot
HTTP statuses
201, 400, 401, 403, 404, 500/502/503/504
POST

/tenants/{tenantId}/qti/delivery-sessions/{deliverySessionId}/attempts

submitAttempt

Submit responses and execute QTI processing against the session snapshot.

Request

JSON body with artifactVersionId and responses object. Response keys must match QTI response variable identifiers and declaration shape.

Success

200 application/json AttemptResult with attemptId, responseState, outcomeState, and privacy-redacted processingTrace.

Rejects

400 for invalid response cardinality/base_type, unknown artifact version for the session, unsupported runtime feature, or unregistered custom operator; 422 qti:time-limit-exceeded when the server clock says the attempt is late and allowLateSubmission=false; unsupported processing fails closed with a trace event.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
deliverySessionIdPathuuidRequiredDelivery session receiving the attempt submission.qti.delivery_session.delivery_session_id, qti.attempt.delivery_session_id
artifactVersionIdBodyuuidRequiredImmutable artifact version attempted within the session.qti.attempt.artifact_version_id, qti.artifact_version.artifact_version_id
responsesBodyobject<string, QTI value>RequiredCandidate response values keyed by QTI response variable identifier. Cardinality and base type must match declarations.qti.attempt.response_state, qti.variable_declaration.identifier, qti.variable_declaration.cardinality, qti.variable_declaration.base_type
responses.<responseIdentifier>BodyQTI valueRequired per submitted variableDynamic key matching a qti-response-declaration identifier. single cardinality sends one scalar; multiple/ordered sends an array; record sends an object keyed by QTI field identifier.qti.attempt.response_state, qti.variable_declaration.identifier, qti.variable_declaration.cardinality, qti.variable_declaration.base_type
responses.RESPONSEBodystring identifierExample for single/identifierFor the copy-paste demo item, RESPONSE is a single identifier and ChoiceA is the submitted QTI value.qti.attempt.response_state, qti.variable_declaration.identifier, qti.variable_declaration.correct_response

Response envelope schema

FieldTypeRequiredDescriptionTrace
attemptIduuidRequiredStable identifier for the persisted attempt record.qti.attempt.attempt_id
responseStateobject<string, QTI value>RequiredCandidate response variables at the last processing point.qti.attempt.response_state
outcomeStateobject<string, QTI value>RequiredOutcome variables after template, response, and outcome processing.qti.attempt.outcome_state
processingTracearray<ProcessingTraceEvent>RequiredPrivacy-redacted deterministic trace of processing operations and diagnostics.qti.attempt.processing_trace, Candidate And Learner Data Privacy
responseState.<responseIdentifier>QTI valueRequired for every processed response variableNormalized candidate response value keyed by QTI response variable identifier. Shape follows the variable declaration cardinality and base_type.qti.attempt.response_state, qti.variable_declaration.identifier, qti.variable_declaration.cardinality, qti.variable_declaration.base_type
responseState.RESPONSEstring identifierExample when RESPONSE has single/identifier shapeConcrete demo response value after validation. Multiple or ordered variables return arrays; record variables return objects.qti.attempt.response_state, qti.variable_declaration.identifier, qti.variable_declaration.base_type
outcomeState.<outcomeIdentifier>QTI valueRequired for every outcome produced by processingOutcome variable keyed by QTI outcome-declaration identifier and shaped by its cardinality/base_type.qti.attempt.outcome_state, qti.variable_declaration.identifier, qti.variable_declaration.cardinality, qti.variable_declaration.base_type
outcomeState.SCOREnumber floatExample when SCORE has single/float shapeConcrete scored outcome for the demo item after match_correct response processing.qti.attempt.outcome_state, qti.variable_declaration.identifier, qti.variable_declaration.base_type
outcomeState.completionStatusstringOptional when emitted by the runtime profileRuntime completion state carried as learner-derived outcome state when the processing profile emits it.qti.attempt.outcome_state, Runtime Execution Profile
timingStatusenum(untimed, in_window, late_accepted, late_rejected)RequiredServer-measured attempt timing classification against the owning delivery-session window.qti.attempt.timing_status, attempt_timing_status, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing
effectiveDurationSecondsnumber seconds|nullRequired when duration is knownServer-measured QTI duration available to time-conditioned outcome processing. Clients must not submit or compute this value.qti.attempt.effective_duration_seconds, qti.delivery_session.window_started_at, Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing
processingTrace[].scopeenum(response, outcome, template, expression)RequiredProcessing phase that produced this trace event.qti.attempt.processing_trace, qti.processing_rule.rule_scope
processingTrace[].rulestringRequiredQTI processing rule or expression element/operator name, such as qti-map-response.qti.attempt.processing_trace, qti.processing_rule.rule_name
processingTrace[].variablestring|nullOptionalQTI variable identifier touched by the rule when applicable.qti.attempt.processing_trace, qti.variable_declaration.identifier
processingTrace[].beforeQTI value|nullOptionalRedacted pre-rule value when useful for diagnostics. Must not include JWTs, headers, IP addresses, user agents, raw PNP records, or direct learner identity.qti.attempt.processing_trace, Candidate And Learner Data Privacy
processingTrace[].afterQTI value|nullOptionalRedacted post-rule value when useful for diagnostics.qti.attempt.processing_trace, qti.attempt.outcome_state, Candidate And Learner Data Privacy
processingTrace[].statusenum(applied, skipped, failed_closed)RequiredDeterministic processing result. Unsupported rules fail closed with diagnostics instead of being silently ignored.qti.attempt.processing_trace, Runtime Execution Profile
processingTrace[].diagnosticstring|nullOptionalSafe diagnostic for failed-closed processing or validation context.qti.attempt.processing_trace, Runtime Execution Profile, Candidate And Learner Data Privacy

Filled examples

Request body
{
  "artifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
  "responses": {
    "RESPONSE": "ChoiceA"
  }
}
200 response body
{
  "attemptId": "69e74a21-1190-492f-9f64-7557754d6eef",
  "responseState": {
    "RESPONSE": "ChoiceA"
  },
  "outcomeState": {
    "SCORE": 1,
    "completionStatus": "completed"
  },
  "timingStatus": "in_window",
  "effectiveDurationSeconds": 18.4,
  "processingTrace": [
    {
      "scope": "responseProcessing",
      "rule": "qti-map-response",
      "variable": "RESPONSE",
      "before": null,
      "after": "ChoiceA",
      "status": "applied",
      "diagnostic": null
    },
    {
      "scope": "timedEnforcement",
      "rule": "qti-time-limits",
      "variable": "duration",
      "before": null,
      "after": 18.4,
      "status": "applied",
      "diagnostic": null
    },
    {
      "scope": "outcomeProcessing",
      "rule": "qti-set-outcome-value",
      "variable": "SCORE",
      "before": 0,
      "after": 1,
      "status": "applied",
      "diagnostic": null
    }
  ]
}
Related tables
qti.attempt, qti.delivery_session, qti.variable_declaration, qti.processing_rule
Related fields
qti.attempt.response_state, qti.attempt.template_state, qti.attempt.outcome_state, qti.attempt.processing_trace, qti.attempt.timing_status, qti.attempt.effective_duration_seconds, qti.processing_rule.rule_name
HTTP statuses
200, 400, 401, 403, 404, 422, 500/502/503/504
GET

/tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data

getCandidateRuntimeData

Read candidate-scoped delivery sessions and attempts for one pseudonymous candidate reference.

Request

Tenant path scope and candidateRef path parameter. Runtime-data readers need a reviewer, service, or runtime-data read authorization scope.

Success

200 application/json CandidateRuntimeData with candidateRef, delivery session count, attempt count, session snapshots, and attempt state.

Rejects

400 if candidateRef is not the pseudonymous UUID format; 403 if the token lacks reviewer/service/runtime-data read authorization; 404 if tenant-owned runtime state is absent or inaccessible.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
candidateRefPathstring pseudonymous UUIDRequiredOpaque tenant-scoped pseudonymous candidate reference whose runtime sessions and attempts should be read.qti.delivery_session.candidate_ref, Candidate And Learner Data Privacy

Response envelope schema

FieldTypeRequiredDescriptionTrace
tenantIduuidRequiredTenant boundary for the returned runtime data.platform.tenant.tenant_id, qti.delivery_session.tenant_id
candidateRefstring pseudonymous UUIDRequiredThe same candidate reference from the path. Direct learner identity is not returned.qti.delivery_session.candidate_ref, Candidate And Learner Data Privacy
deliverySessionCountintegerRequiredNumber of delivery sessions returned for this candidateRef and tenant.qti.delivery_session.delivery_session_id
attemptCountintegerRequiredNumber of attempts returned through sessions owned by this candidateRef and tenant.qti.attempt.attempt_id, qti.attempt.delivery_session_id
deliverySessions[]array<DeliverySessionRuntime>RequiredRuntime delivery-session rows for this one candidate, including frozen delivery JSON snapshots and session state.qti.delivery_session.delivery_session_id, qti.delivery_session.delivery_json_snapshot, qti.delivery_session.session_state
deliverySessions[].deliverySessionIduuidRequiredSession identifier for one returned candidate session.qti.delivery_session.delivery_session_id
deliverySessions[].rootArtifactVersionIduuidRequiredImmutable artifact version snapshot used by that session.qti.delivery_session.root_artifact_version_id, qti.artifact_version.artifact_version_id
deliverySessions[].statusenum(created, active, suspended, submitted, review, closed, voided)RequiredSession lifecycle state.qti.delivery_session.status, delivery_session_status
deliverySessions[].deliveryJsonSnapshotQtiDeliveryJsonEnvelope snapshotRequiredFrozen delivery JSON snapshot for the session. Later content edits do not change this value.qti.delivery_session.delivery_json_snapshot, qti.artifact_version.delivery_json
deliverySessions[].sessionStateobjectRequiredPrivacy-redacted runtime session state such as navigation, item sequencing, resume information, or review flags.qti.delivery_session.session_state, Candidate And Learner Data Privacy
attempts[]array<AttemptRuntime>RequiredAttempts joined through returned candidate sessions.qti.attempt.attempt_id, qti.attempt.delivery_session_id
attempts[].attemptIduuidRequiredStable attempt identifier.qti.attempt.attempt_id
attempts[].deliverySessionIduuidRequiredOwning delivery session for this attempt.qti.attempt.delivery_session_id, qti.delivery_session.delivery_session_id
attempts[].artifactVersionIduuidRequiredImmutable item/test artifact version attempted.qti.attempt.artifact_version_id, qti.artifact_version.artifact_version_id
attempts[].responseStateobject<string, QTI value>RequiredPrivacy-redacted candidate response variables at the last processing point.qti.attempt.response_state, Candidate And Learner Data Privacy
attempts[].outcomeStateobject<string, QTI value>RequiredOutcome variables after template, response, and outcome processing.qti.attempt.outcome_state, Runtime Execution Profile
attempts[].processingTracearray<ProcessingTraceEvent>RequiredPrivacy-redacted processing evidence. It must not include direct learner identity, JWTs, headers, IP addresses, user agents, or raw PNP records.qti.attempt.processing_trace, Candidate And Learner Data Privacy

Filled examples

200 response body
{
  "tenantId": "00000000-0000-4000-8000-000000000003",
  "candidateRef": "9c41d14e-d011-4517-927e-b9bf0b7d5df4",
  "deliverySessionCount": 1,
  "attemptCount": 1,
  "deliverySessions": [
    {
      "deliverySessionId": "e0b41369-3019-42a5-a419-d5da6e33904f",
      "rootArtifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
      "status": "created",
      "deliveryJsonSnapshot": {
        "documentId": "item-1",
        "lossiness": "declared"
      },
      "sessionState": {}
    }
  ],
  "attempts": [
    {
      "attemptId": "69e74a21-1190-492f-9f64-7557754d6eef",
      "deliverySessionId": "e0b41369-3019-42a5-a419-d5da6e33904f",
      "artifactVersionId": "4f4c18f4-c2ec-4278-97e2-2b07a3070d91",
      "attemptNumber": 1,
      "status": "submitted",
      "responseState": {
        "RESPONSE": "ChoiceA"
      },
      "outcomeState": {
        "SCORE": 1
      },
      "processingTrace": [
        {
          "event": "match-correct",
          "status": "applied"
        }
      ]
    }
  ]
}
Related tables
qti.delivery_session, qti.attempt
Related fields
qti.delivery_session.candidate_ref, qti.delivery_session.delivery_session_id, qti.delivery_session.delivery_json_snapshot, qti.delivery_session.session_state, qti.attempt.attempt_id, qti.attempt.response_state, qti.attempt.outcome_state, qti.attempt.processing_trace
HTTP statuses
200, 400, 401, 403, 404, 500/502/503/504
DELETE

/tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data

deleteCandidateRuntimeData

Delete one pseudonymous candidate's delivery sessions and cascading attempts.

Request

Tenant path scope and candidateRef path parameter. Service-role or tenant-authorized token required for bulk learner-runtime deletion.

Success

204 with no body after deleting learner-runtime rows. Reusable content package, artifact, version, resource, file, and conformance rows remain.

Rejects

400 if candidateRef is not the pseudonymous UUID format; 401/403 for tenant mismatch or insufficient role.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
tenantIdPathuuidRequiredTenant boundary for the operation. Cross-tenant reads, writes, exports, sessions, attempts, and deletions are rejected.platform.tenant.tenant_id, qti.tenant.tenant_id
candidateRefPathstring pseudonymous UUIDRequiredOpaque tenant-scoped pseudonymous candidate reference whose runtime data should be deleted.qti.delivery_session.candidate_ref

Response envelope schema

FieldTypeRequiredDescriptionTrace
bodyemptyRequired emptyNo response body. Successful deletion is represented by HTTP 204.204
deleted scopeside effectRequiredDeletes qti.delivery_session rows for tenantId + candidateRef and cascades qti.attempt rows; content tables remain.qti.delivery_session.candidate_ref, qti.delivery_session.delivery_session_id, qti.attempt.delivery_session_id
Related tables
qti.delivery_session, qti.attempt
Related fields
qti.delivery_session.candidate_ref, qti.delivery_session.delivery_session_id, qti.attempt.delivery_session_id
HTTP statuses
204, 400, 401, 403, 404, 500/502/503/504
POST

/qti/conformance-runs

runConformance

Run the bundled QTI example corpus through validation, round-trip, and processing assertions.

Request

Administrative request using the configured source bundle and runner version. Send an empty JSON object: {}.

Success

202 application/json with conformanceRunId, runId compatibility alias, status, and summary evidence.

Rejects

Error state when the runner, bundle, validation tool, or persistence adapter cannot produce a valid pass/fail result.

Request schema

FieldInTypeRequiredDescriptionTrace
AuthorizationHeaderBearer JWTRequiredSigned, trusted, unexpired token. Tenant-owned routes also require the token tenant claim to match tenantId.Security Boundary, Tenant Boundary
bodyBodyempty JSON objectRequiredSend {}. The server uses the configured offline source bundle, validation tools, persistence adapter, and runner version.qti.conformance_run.bundle_hash, qti.conformance_run.runner_version

Response envelope schema

FieldTypeRequiredDescriptionTrace
conformanceRunIduuidRequiredStable identifier for the accepted conformance run.qti.conformance_run.conformance_run_id
runIduuidRequiredCompatibility alias equal to conformanceRunId so generic run-polling clients do not receive a null run id.qti.conformance_run.conformance_run_id
statusenum(running, passed, failed, error)RequiredAggregate conformance result. Error is returned when the runner cannot complete a valid pass/fail result.qti.conformance_run.status, conformance_run_status
summary.byStatusrecord<enum(passed, failed, skipped, error), integer>RequiredCounts of persisted conformance assertions by lifecycle status.qti.conformance_assertion.status, conformance_assertion_status
summary.bundleHashsha256 hashRequiredHash of the offline QTI 3.0 source bundle used as validation authority for the run.qti.conformance_run.bundle_hash, Offline 1EdTech Source Bundle
summary.totalAssertionsintegerRequiredTotal profile, validation, round-trip, and processing assertions recorded for the run.qti.conformance_assertion.conformance_assertion_id, Conformance Evidence
Related tables
qti.conformance_run, qti.conformance_assertion
Related fields
qti.conformance_run.profile, qti.conformance_run.bundle_hash, qti.conformance_run.runner_version, qti.conformance_assertion.status, qti.conformance_assertion.spec_ref
HTTP statuses
202, 401, 403, 500/502/503/504
Dictionary convergence

API and raw-DB paths answer the same questions

These rows come from the approved data dictionary. They name the API result, the equivalent raw database rule, invalid states, and architecture trace so a reviewer can verify both first-class paths without leaving this page.

List Endpoints

API path
API result
Raw DB rule
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.
Trace
ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-020 Validation And Rejection Policy

API path
API result
Raw DB rule
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.
Trace
ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-020 Validation And Rejection Policy

API path
API result
Raw DB rule
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.
Trace
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

Replay when the caller persisted Idempotency-Key

API path
POST /tenants/{tenantId}/qti/packages with the same Idempotency-Key and equivalent request body
API result
The platform.idempotency_key ledger returns the original package-ingest response instead of creating a second package.
Raw DB rule
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.
Trace
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
API result
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 rule
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.
Trace
ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery, ITD-011 Artifact Versioning, ITD-018 API Boundary

Timed Delivery

Delivery JSON timeLimits projection

API path
GET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json
API result
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 rule
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.
Trace
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
API result
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 rule
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.
Trace
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

Benchmark Feature Scope

Test-level navigation construct scope

API path
GET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json
API result
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 rule
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.
Trace
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
API result
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 rule
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.
Trace
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
API result
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 rule
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.
Trace
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
API result
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 rule
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.
Trace
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
API result
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 rule
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.
Trace
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
Normative behavior

Behavior contracts

Each behavior below is backed by the approved architecture and by a data dictionary location. If implementation work cannot satisfy one of these rows, the loop should roll back to the earliest flawed upstream deliverable.

Source authority

The offline 1EdTech QTI 3.0 bundle is the standards input. Live web drift does not change accepted schemas, examples, response-processing templates, vocabularies, or generated trace until a new platform3 architecture/data-dictionary deliverable adopts it.

27 XSDs, 683 global elements, 28,292 embedded Schematron assertions, 6 bundled response-processing templates, and 327 XML examples.

Immutable versioning and idempotency

Repeated package bytes and repeated canonical XML resolve through tenant-scoped hashes and idempotency keys. Authoring edits create new immutable artifact_version rows instead of overwriting historical content; re-PUTing authoring JSON that canonicalizes to the current version reuses that immutable version and returns 200 instead of inserting a duplicate, while changed authoring JSON returns 201 for the new version.

platform.idempotency_key.idempotency_key, qti.content_package.platform_idempotency_key_id, qti.content_package.idempotency_key, qti.content_package.package_hash, qti.artifact_version.version_number, qti.artifact_version.xml_hash.

Tenant-owned enumeration and lost-response recovery

The API path can recover the same repository handles as the raw-DB path. listPackages, listArtifacts, and listArtifactVersions return tenant-owned pages with cursor + limit, include the handles needed to continue the workflow, and return an empty page for fresh tenants instead of 404.

listPackages, listArtifacts, listArtifactVersions, qti.content_package.package_hash, qti.artifact.latest_version_id, qti.artifact_version.artifact_version_id.

Declared JSON projection lossiness

Authoring JSON is lossless for editor round-trip. Delivery JSON may omit only declared authoring-only or diagnostic detail while preserving identifiers needed for responses, feedback, scoring, accessibility matching, time-limit display, and session snapshots.

qti.artifact_version.authoring_json, qti.artifact_version.delivery_json, qti.delivery_session.delivery_json_snapshot, projection_lossiness.

Timed delivery is server-authoritative

If QTI content declares qti-time-limits, delivery JSON exposes timeLimits for client display, but the server owns the window start, max-time calculation, timing classification, late rejection, and duration value used by outcome processing.

qti.delivery_session.window_started_at, qti.delivery_session.window_expires_at, qti.delivery_session.effective_max_time_seconds, qti.attempt.timing_status, qti.attempt.effective_duration_seconds, attempt_timing_status.

Runtime execution profile

The runtime executes item-level template, response, and outcome processing deterministically against one submitted attempt. Unsupported rules or expressions fail the processing result with a trace entry; they are not silently ignored.

87 XSD-reachable processing elements, 87 inline elements supported, 6 bundled templates supported, 75 processing assertions passed.

Architecture coverage

Every approved QTI ITD is visible from this page

This matrix is the customer-website bridge back to the approved architecture. Endpoint cards, workflow rows, data-model summaries, behavior contracts, and this coverage matrix together expose every active QTI 1EdTech ITD; the architecture URL remains the source for alternatives and tradeoffs.

DecisionTitleCustomer-visible coverageStatusDate
itd-001-source-bundle Offline 1EdTech Source Bundle source authority, offline validation input, QTI spec bundle active 2026-05-20
itd-002-generated-object-model Generated Object Model Hub generated model, object graph, schema-derived validators active 2026-05-20
itd-003-shared-supabase-postgresql Shared Supabase PostgreSQL shared database, PostgreSQL, Supabase platform3 active 2026-05-20
itd-004-xml-authority XML Authority And Canonical Hashes source_xml, canonical_xml, xml_hash, XML export active 2026-05-20
itd-005-relational-projection Lossless Relational Projection qti.component, object graph rows, rehydration active 2026-05-20
itd-006-json-projections JSON Projection Boundaries object_graph, delivery_json, authoring_json, lossiness active 2026-05-20
itd-007-provenance-labels Provenance Labels data dictionary provenance, 1EdTech pass-through, Gap fill active 2026-05-20
itd-008-tenant-boundary Tenant Boundary platform.tenant, tenant_id, tenant-scoped APIs, qti.tenant compatibility bridge active 2026-05-20
itd-009-package-ingest Package Resource And File Ingest qti.content_package, qti.package_resource, qti.package_file, package closure active 2026-05-20
itd-010-idempotency Idempotency And Hashes platform.idempotency_key, Idempotency-Key on package ingest, qti.content_package.platform_idempotency_key_id, package_hash, content_hash, artifact_version_hash_unique active 2026-05-20
itd-011-artifact-versioning Artifact Versioning qti.artifact, qti.artifact_version, immutable versions, If-Match authoring save active 2026-05-20
itd-012-artifact-kind Artifact Kind Allowed Values artifact_kind_ck, root classification active 2026-05-20
itd-013-variable-projection Variable Declaration Projection qti.variable_declaration, variable_kind_ck active 2026-05-20
itd-014-processing-projection Processing Rule Projection qti.processing_rule, processing_rule_scope_ck active 2026-05-20
itd-015-delivery-session Delivery Session Snapshots qti.delivery_session, delivery_session_status_ck, delivery_json_snapshot active 2026-05-20
itd-016-attempt-trace Attempt State And Processing Trace qti.attempt, attempt_status_ck, processing_trace active 2026-05-20
itd-017-conformance-evidence Conformance Evidence qti.conformance_run, qti.conformance_assertion, release evidence active 2026-05-20
itd-018-api-boundary API Boundary OpenAPI, HTTP API, endpoint behavior, demo fixture helpers, write granularity, read shape, query model, eventing model, list endpoints, route-scoped CORS active 2026-05-29
itd-019-security-boundary Security Boundary Bearer JWT, tenant claim, subject claim, role, scopes, authorization, platform.audit_log active 2026-05-26
itd-020-validation-policy Validation And Rejection Policy XSD validation, Schematron validation, package path validation, If-Match, typed RFC 7807 Problem envelope, stable problem type URI, code, requestId, traceId, fieldErrors, 409 stale version, 428 missing precondition active 2026-05-26
itd-021-runtime-profile Runtime Execution Profile response processing, custom operators, template retry bound active 2026-05-20
itd-022-operational-ddl Operational DDL Discipline forward-only migrations, comments, indexes active 2026-05-20
itd-023-hosted-docs Hosted Documentation Identity Vercel, hierarchical canonical URL, master rewrites, public documentation active 2026-05-26
itd-024-candidate-learner-data-privacy Candidate And Learner Data Privacy candidate_ref, response_state, template_state, outcome_state, processing_trace redaction, QTI_CONTEXT candidateIdentifier, learner-runtime retention, candidate-scoped deletion active 2026-05-20
itd-025-platform-substrate-inheritance Platform Substrate Inheritance platform.tenant, platform.idempotency_key, platform.audit_log, Platform Problem builder, route-scoped CORS, downstream cascade active 2026-05-28
itd-026-tenant-enumeration Tenant-Owned Enumeration And Lost-Response Recovery tenant-owned list endpoints (listArtifacts, listArtifactVersions, listPackages), cursor + limit paging envelope, lost-response recovery pair (Idempotency-Key replay + enumeration), don't-break-it-twice regression suite, platform3 issue #19 / triage decision 2026-06-10-054 active 2026-06-10
itd-027-timed-delivery Server-Authoritative Timed Delivery And Time-Conditioned Outcome Processing server-authoritative qti-time-limits enforcement (window_started_at, effective_max_time_seconds), delivery-json timeLimits projection (maxTime, minTime, allowLateSubmission), attempt timing_status (untimed, in_window, late_accepted, late_rejected) + effective_duration_seconds, qti:time-limit-exceeded typed Platform Problem (HTTP 422) for late submission when allow-late-submission=false, server-side time-conditioned outcome processing returning declared outcome variables (server-measured duration built-in), AcmeTest timed-delivery issue / triage decision 2026-06-11-006 active 2026-06-11
itd-028-test-navigation Test-Level Sequencing, Branching, And Adaptive Selection persist + interchange (SHIP) qti-selection (select, with-replacement), qti-ordering (shuffle), qti-branch-rule (target), qti-pre-condition, adaptive=true, qti-adaptive-selection verbatim via XML authority + qti.component projection + delivery JSON, qti-adaptive-selection engine refs (qti-adaptive-engine-ref, qti-adaptive-settings-ref, qti-usagedata-ref, qti-metadata-ref) round-trip and surface in delivery JSON, runtime navigation DEFER: items served in authored document order; selection/ordering/branch/pre-condition/within-item-adaptive/CAT delegated to the integrator's delivery engine (external IMS CAT 1.0 engine for adaptive-selection), runtime CAT/branching session position is engine-owned, never stored as platform Content; no new gap-fill table or field, re-open trigger: a committed consumer (Curriculum gate-failure target_ref at an adaptive/branching test, or test_bank unseen-form selection) needs the platform itself to run test-level navigation active 2026-06-11
itd-029-catalog-pnp Catalog And PNP Accessibility Activation persist + interchange (SHIP) qti-catalog-info / catalog (Access-For-All / APIP PNP) verbatim via XML authority + qti.component projection + delivery JSON, runtime PNP activation DEFER except extended-time: renderer selects the catalog alternative per the candidate PNP profile from delivery JSON, the only server-activated PNP accommodation is extended time (gates the authoritative clock), per ITD-027, raw PNP records are never stored (ITD-024); a delivery-time candidateIdentifier resolving PNP receives the pseudonymous candidate_ref; no new gap-fill table, re-open trigger: a committed consumer needs server-side Access-For-All resolution (and an approved decision to store/look-up PNP profiles, currently out of scope under ITD-024) active 2026-06-11
itd-030-portable-custom-interaction Portable Custom Interaction (PCI) Persistence And Execution Boundary persist + interchange (SHIP) qti-portable-custom-interaction / qti-custom-interaction markup, qti-interaction-markup, and qti-interaction-modules verbatim via XML authority (ITD-004) + qti.component projection (ITD-005) + delivery JSON (ITD-006), PCI hosted JavaScript module files persist as qti.package_file bytes indexed by qti.package_resource (ITD-009); the bound response variable is an ordinary qti.variable_declaration response declaration (ITD-013); no new gap-fill table or field, runtime PCI execution DEFER: the delivery client renders the PCI and runs the IMS PCI getResponse/getState lifecycle; the platform never executes vendor JS server-side (the same boundary ITD-021 draws), the platform scores the PCI-produced response value through ordinary server-side response/outcome processing (ITD-014/ITD-021) like any other response variable, re-open trigger: a committed consumer needs the platform itself to render a PCI and compute its response server-side (headless PCI scoring), requiring a sandboxed-PCI-execution security review active 2026-06-11
itd-031-item-templates Item Template Declaration, Processing, And Cloning persist + interchange (SHIP) qti-template-declaration into qti.variable_declaration variable_kind=template (ITD-013), qti-template-processing (qti-set-template-value / qti-template-constraint / qti-template-default) into qti.processing_rule scope=template (ITD-014), qti-printed-variable + math templates into qti.component (ITD-005), runtime realization SHIP server-side: the platform runs qti-template-processing at session start to bind template variables and the correct response, honoring qti-template-default and the ITD-021 100-attempt qti-template-constraint fail-closed bound, storing realized values in qti.attempt.template_state (ITD-016), realization is server-authoritative because template values feed the correct response and score (same tamper argument as ITD-027); a client that realized the variant could forge the correct response, presentation half DEFER to client: qti-printed-variable substitution and MathML rendering are read from delivery JSON / template_state by the renderer (consistent with ITD-030); no new gap-fill table or field, re-open trigger: a consumer needs a realized variant addressable independently of an attempt (e.g., a cached bank of clones for offline delivery) -> template-realization-cache ITD active 2026-06-11
itd-032-results-reporting QTI Results Reporting And The Caliper Module Boundary QTI Results Reporting persist + interchange (SHIP): an assessmentResult document round-trips as artifact_kind=result (ITD-012) via XML authority (ITD-004) + component projection (ITD-005); element vocabulary (assessmentResult, context, testResult, itemResult, responseVariable, outcomeVariable, templateVariable, candidateResponse, sessionIdentifier) is pass-through from imsqti_resultv3p0_v1p0.xsd, QTI Usage Data (qti-usagedata incl. item statistics / IRT a-b-c) persists as artifact_kind=usage-data pass-through, never promoted to platform columns, generated assessmentResult projection SHIP via the existing getCandidateRuntimeData learner-runtime read (a stored attempt's response/template/outcome state is the assessmentResult content); a dedicated standalone results-export endpoint DEFER, Caliper event emission DEFER (cross-module): owned by the Caliper 1EdTech module (Events domain); the QTI surface emits no Caliper events, stores no Caliper profile, owns no Caliper vocabulary, re-open trigger: ship a results-export endpoint when a committed consumer needs a standalone assessmentResult document without the learner-runtime read; re-open QTI-side Caliper emission only if a platform-level decision moves event ownership off the Caliper module active 2026-06-11
Data model

Objects, lifecycle, and provenance

The customer website summarizes the model; the approved data dictionary remains the field-level source of truth. Every row below links back to that dictionary and to the owning ITDs.

Inherited Platform substrate

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

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.
Primary key
tenant_id
Architecture
Module Schemas And Shared Platform Schema, Shared Tenant Model, Authentication, Authorization, And Tenant Scope, ITD-025 Platform Substrate Inheritance
Full dictionary
Open platform.tenant in the 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).
Fields
tenant_id tenant_key display_name status metadata created_at updated_at

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

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.
Primary key
idempotency_key_id
Architecture
Idempotency And Optimistic Concurrency, HTTP Envelope, Status, And Problem Errors, Cross-Module Audit Log, ITD-025 Platform Substrate Inheritance
Full dictionary
Open platform.idempotency_key in the 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.
Fields
idempotency_key_id tenant_id module surface method route_template operation_id idempotency_key request_hash status response_status response_body resource_type resource_id first_request_id locked_until expires_at created_at updated_at

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

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.
Primary key
audit_log_id
Architecture
Cross-Module Audit Log, Student Data Privacy And PII Handling, Observability, Metrics, And SLOs, ITD-025 Platform Substrate Inheritance
Full dictionary
Open platform.audit_log in the 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.
Fields
audit_log_id 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 occurred_at redacted_metadata

qti.tenant

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

QTI compatibility view

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.
Primary key
tenant_id
Architecture
ITD-008 Tenant Boundary, ITD-019 Security Boundary, ITD-025 Platform Substrate Inheritance
Full dictionary
Open qti.tenant in the data dictionary
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.
Fields
tenant_id tenant_key display_name created_at

QTI package ingest

qti.content_package

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

Gap fill row with 1EdTech pass-through values

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.
Primary key
package_id
Architecture
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
Full dictionary
Open qti.content_package in the data dictionary
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}.
Fields
package_id tenant_id source_uri idempotency_key platform_idempotency_key_id package_hash manifest_identifier qti_profile import_status metadata imported_at

qti.package_resource

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

Gap fill row with 1EdTech pass-through values

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.
Primary key
resource_id
Architecture
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
Full dictionary
Open qti.package_resource in the data dictionary
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.
Fields
resource_id package_id resource_identifier resource_type href dependencies metadata

qti.package_file

Original file bytes from an imported IMS/QTI package.

Gap fill row with 1EdTech pass-through values

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.
Primary key
package_file_id
Architecture
ITD-009 Package Resource And File Ingest, ITD-020 Validation And Rejection Policy, ITD-030 Portable Custom Interaction Persistence And Execution Boundary
Full dictionary
Open qti.package_file in the data dictionary
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.
Fields
package_file_id package_id resource_id package_path media_type byte_length content_hash content_bytes metadata created_at

QTI artifact persistence

qti.artifact

Stable logical QTI document or package artifact across immutable versions.

Gap fill row with 1EdTech pass-through values

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.
Primary key
artifact_id
Architecture
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
Full dictionary
Open qti.artifact in the data dictionary
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}.
Fields
artifact_id tenant_id package_id resource_id artifact_kind qti_identifier title language latest_version_id created_at

qti.artifact_version

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

Gap fill row with 1EdTech pass-through values

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.
Primary key
artifact_version_id
Architecture
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
Full dictionary
Open qti.artifact_version in the data dictionary
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.
Fields
artifact_version_id artifact_id version_number source_xml canonical_xml xml_hash root_element root_type schema_file object_graph delivery_json authoring_json spec_trace supersedes_version_id created_at created_by

qti.component

Lossless relational projection of generated QTI object-graph nodes.

Gap fill row with 1EdTech pass-through values

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.
Primary key
component_id
Architecture
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
Full dictionary
Open qti.component in the data dictionary
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.
Fields
component_id artifact_version_id parent_component_id ordinal element_name qualified_name namespace_uri type_name qti_identifier component_path attributes text_value tail_value source_trace

qti.variable_declaration

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

Gap fill row with 1EdTech pass-through values

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.
Primary key
variable_declaration_id
Architecture
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
Full dictionary
Open qti.variable_declaration in the data dictionary
Relationships
  • Belongs to one qti.artifact_version.
  • References the qti.component that declared the variable.
Fields
variable_declaration_id artifact_version_id component_id variable_kind identifier cardinality base_type default_value correct_response mapping source_trace

qti.processing_rule

Executable QTI processing and expression tree projection.

Gap fill row with 1EdTech pass-through values

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.
Primary key
processing_rule_id
Architecture
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
Full dictionary
Open qti.processing_rule in the data dictionary
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.
Fields
processing_rule_id artifact_version_id component_id parent_processing_rule_id rule_scope rule_name sequence_number operands source_trace

Learner runtime

qti.delivery_session

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

Platform gap fill

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.
Primary key
delivery_session_id
Architecture
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
Full dictionary
Open qti.delivery_session in the data dictionary
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.
Fields
delivery_session_id tenant_id candidate_ref root_artifact_version_id status delivery_json_snapshot session_state window_started_at window_expires_at effective_max_time_seconds created_at updated_at

qti.attempt

Candidate response, template, outcome, timing, and processing trace snapshot inside a delivery session.

Gap fill row with 1EdTech pass-through values

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.
Primary key
attempt_id
Architecture
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
Full dictionary
Open qti.attempt in the data dictionary
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.
Fields
attempt_id delivery_session_id artifact_version_id attempt_number status response_state template_state outcome_state processing_trace timing_status effective_duration_seconds started_at suspended_at submitted_at

Release evidence

qti.conformance_run

Release evidence for a QTI conformance/profile run.

Platform gap fill

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.
Primary key
conformance_run_id
Architecture
ITD-017 Conformance Evidence
Full dictionary
Open qti.conformance_run in the data dictionary
Relationships
  • Parent of qti.conformance_assertion.
Fields
conformance_run_id profile bundle_hash runner_version started_at finished_at status summary

qti.conformance_assertion

Per-example and per-feature conformance evidence.

Gap fill row with 1EdTech pass-through values

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.
Primary key
conformance_assertion_id
Architecture
ITD-017 Conformance Evidence
Full dictionary
Open qti.conformance_assertion in the data dictionary
Relationships
  • Belongs to one qti.conformance_run.
Fields
conformance_assertion_id conformance_run_id assertion_key artifact_ref spec_ref status details
Field index

Every documented field links to its dictionary entry and ITDs

This compact index is intentionally exhaustive: 176 fields, each with meaning, invalid conditions, source label, and trace.

FieldMeaningTypeInvalid whenSourceArchitecture trace
platform.tenant.tenant_id Stable database identifier for one customer/workspace boundary. This is the value module tables reference and tenant-scoped JWTs must match. uuid
Required, default gen_random_uuid()
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
platform.tenant.tenant_key Human-stable lookup key for routes, local tooling, logs, and examples. It is a convenience key, not an authorization secret. text
Required
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
platform.tenant.display_name Customer-facing label shown in admin tools and documentation examples. text
Required
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
platform.tenant.status Tenant lifecycle state that tells modules whether ordinary tenant-scoped writes may proceed. text
Required, default 'provisioning'
Outside tenant_status, null, or manually changed without an audit row explaining the administrative action. 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
platform.tenant.metadata Small redacted operational facts about the tenant that do not deserve first-class columns yet. jsonb
Required, default '{}'::jsonb
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
platform.tenant.created_at Timestamp when the tenant row was inserted. timestamptz
Required, default now()
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
platform.tenant.updated_at Timestamp when the tenant row was last changed. timestamptz
Required, default now()
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
platform.idempotency_key.idempotency_key_id Stable identifier for the retry ledger row. Audit rows refer to this value rather than repeating replay internals. uuid
Required, default gen_random_uuid()
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
platform.idempotency_key.tenant_id Tenant that owns the retry scope and the mutation being protected. uuid
Required
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
platform.idempotency_key.module Module namespace whose operation claimed the idempotency key. text
Required
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. 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
platform.idempotency_key.surface Surface whose API contract produced the retryable operation. text
Required
Null, outside surface_code, or used to hide whether an Alpha divergence changed operation behavior. 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
platform.idempotency_key.method HTTP method for the mutation protected by the key. text
Required
Null, GET, lowercase if the implementation normalizes to uppercase, or method does not match the documented endpoint. 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
platform.idempotency_key.route_template Stable route pattern from the customer website or OpenAPI operation, with variable path segments expressed as braces. text
Required
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
platform.idempotency_key.operation_id Stable operation identifier used by docs, OpenAPI, logs, audit, and idempotency middleware. text
Required
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
platform.idempotency_key.idempotency_key Opaque customer-supplied Idempotency-Key header value for one retryable operation. text
Required
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
platform.idempotency_key.request_hash Digest of the canonical replay identity for the first request. It lets middleware distinguish a safe retry from key reuse with different content. text
Required
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
platform.idempotency_key.status Replay lifecycle state of the idempotency row. text
Required, default 'in_progress'
Outside idempotency_status, null, or incompatible with response_status/locked_until, such as completed with no final 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
platform.idempotency_key.response_status HTTP status originally returned for a final replayable outcome. integer
Nullable
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
platform.idempotency_key.response_body Redacted JSON body safe to replay to the same tenant for completed or failed_permanent outcomes. jsonb
Nullable
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
platform.idempotency_key.resource_type Optional type of resource created, imported, deleted, or accepted by the operation. text
Nullable
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
platform.idempotency_key.resource_id Optional identifier of the primary resource associated with the replayable outcome. text
Nullable
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
platform.idempotency_key.first_request_id Request identifier of the first request that claimed this key. text
Required
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
platform.idempotency_key.locked_until Temporary lock deadline used while an in-progress operation is executing. timestamptz
Nullable
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
platform.idempotency_key.expires_at Timestamp after which the key is no longer promised to replay the original response. timestamptz
Required
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
platform.idempotency_key.created_at Timestamp when the first request claimed the idempotency key. timestamptz
Required, default now()
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
platform.idempotency_key.updated_at Timestamp when the row last changed status, replay body, lock, or expiry. timestamptz
Required, default now()
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
platform.audit_log.audit_log_id Stable identifier for one append-only audit event. uuid
Required, default gen_random_uuid()
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
platform.audit_log.tenant_id Tenant affected by the audited action. uuid
Required
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
platform.audit_log.module Module responsible for the operation being audited. text
Required
Null, outside module_key, set to platform for module resource changes, or used by support tooling as proof that the module surface is approved. 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
platform.audit_log.surface Surface through which the action was initiated or exposed. text
Required
Null, outside surface_code, or set to alpha for expert-only conformance mutation. 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
platform.audit_log.operation_id Stable operation identifier from the customer website, OpenAPI, or platform maintenance command. text
Required
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
platform.audit_log.actor_subject Pseudonymous actor identifier for the user, service, or release process that attempted the action. text
Required
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
platform.audit_log.actor_roles Roles or scopes that justified the action or explain why authorization failed. text[]
Required, default '{}'::text[]
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
platform.audit_log.resource_type Stable resource class affected by the action. text
Required
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
platform.audit_log.resource_id Identifier of the primary resource affected by the action, or a documented sentinel when authorization failed before resource resolution. text
Required
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
platform.audit_log.action Behavioral category of the audited action. text
Required
Null, outside audit_action, or too vague to distinguish import from create, delete from runtime_delete, or read from read_privileged. 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
platform.audit_log.outcome Final result category for the action. text
Required
Null, outside audit_outcome, inconsistent with http_status, or hides authorization failure as validation failure. 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
platform.audit_log.http_status HTTP status returned for the request or the HTTP-equivalent status assigned to a background/service operation. integer
Required
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
platform.audit_log.request_id Per-request identifier exposed in Problem responses and support logs. text
Required
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
platform.audit_log.trace_id Trace identifier that links logs, metrics, audit rows, and downstream spans for one request or job. text
Required
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
platform.audit_log.idempotency_key_id Optional link to the idempotency row that governed the audited operation. uuid
Nullable
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
platform.audit_log.occurred_at Timestamp when the audited action reached the recorded outcome. timestamptz
Required, default now()
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
platform.audit_log.redacted_metadata Small, safe, structured context that helps explain the audit event without storing raw sensitive data. jsonb
Required, default '{}'::jsonb
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
qti.tenant.tenant_id Stable platform.tenant identifier exposed through the legacy qti.tenant shape. uuid
Required
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
qti.tenant.tenant_key Human-stable platform.tenant lookup key exposed through the legacy QTI projection. text
Required
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
qti.tenant.display_name Customer-facing platform.tenant label exposed for older QTI tools. text
Required
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
qti.tenant.created_at Timestamp when the backing platform.tenant row was inserted. timestamptz
Required
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
qti.content_package.package_id Stable identifier assigned to one package ingest record. uuid
Required
Not a UUID or reused across package rows. Platform gap fill
Platform package identity.
ITD-009 Package Resource And File Ingest
qti.content_package.tenant_id Tenant that owns the package and all extracted resources. uuid
Required
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
qti.content_package.source_uri Original filename, URI, or content-addressable reference supplied by the ingest caller. text
Nullable
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
qti.content_package.idempotency_key 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. text
Nullable
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
qti.content_package.platform_idempotency_key_id 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. uuid
Nullable
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
qti.content_package.package_hash Cryptographic hash of the normalized package payload used to identify repeated imports. text
Required
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
qti.content_package.manifest_identifier IMS manifest identifier copied from imsmanifest when the package has one. text
Nullable
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
qti.content_package.qti_profile Conformance profile asserted for this import. text
Required, default 'qti-3.0'
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
qti.content_package.import_status Current lifecycle state of package ingest. text
Required, default 'imported'
Outside the enum set or inconsistent with resource/artifact projection state. Platform gap fill
Platform package import lifecycle.
ITD-009 Package Resource And File Ingest, ITD-026 Tenant-Owned Enumeration And Lost-Response Recovery
qti.content_package.metadata Generated package-level import evidence such as manifest facts, QTI metadata summaries, counts, validation diagnostics, and vocabulary projections. jsonb
Required, default '{}'::jsonb
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
qti.content_package.imported_at Timestamp when the package row was inserted and the timestamp component of the stable listPackages ordering. timestamptz
Required, default now()
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
qti.package_resource.resource_id Stable identifier for this manifest resource row. uuid
Required
Not a UUID or reused by another package_resource row. Platform gap fill
Platform row identity.
ITD-009 Package Resource And File Ingest
qti.package_resource.package_id Owning content package. uuid
Required
Missing package, cross-tenant package/resource mixture, or null. Platform gap fill
Package ownership boundary.
ITD-009 Package Resource And File Ingest
qti.package_resource.resource_identifier IMS manifest resource identifier copied from imsmanifest. text
Required
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
qti.package_resource.resource_type IMS/QTI resource type from the package manifest. text
Required
Blank, invented by Alpha naming, or used to bypass validation. 1EdTech pass-through
IMS/QTI package resource type value.
ITD-009 Package Resource And File Ingest, ITD-007 Provenance Labels
qti.package_resource.href Package-relative path to the resource's primary file. text
Nullable
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
qti.package_resource.dependencies Manifest dependency references and variant resource links generated from the package manifest. jsonb
Required, default '[]'::jsonb
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
qti.package_resource.metadata Manifest-derived metadata, file list, and resource facts generated at ingest. jsonb
Required, default '{}'::jsonb
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
qti.package_file.package_file_id Stable identifier for one original package file row. uuid
Required
Not a UUID or reused. Platform gap fill
Platform file row identity.
ITD-009 Package Resource And File Ingest
qti.package_file.package_id Owning content package. uuid
Required
References a missing package or mixes tenants. Platform gap fill
Package ownership boundary.
ITD-009 Package Resource And File Ingest
qti.package_file.resource_id Manifest resource that first listed this file, when applicable. uuid
Nullable
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
qti.package_file.package_path Normalized package-relative path for this file. text
Required
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
qti.package_file.media_type Detected or declared media type used for export, diagnostics, and content serving. text
Required, default 'application/octet-stream'
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
qti.package_file.byte_length Original byte length of content_bytes. integer
Required
Negative, null, or mismatched with content_bytes. Platform gap fill
Generated package file evidence.
ITD-009 Package Resource And File Ingest
qti.package_file.content_hash Cryptographic hash of the original file bytes. text
Required
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
qti.package_file.content_bytes Original bytes exactly as accepted from the package for this path, including QTI XML, media, and Portable Custom Interaction JavaScript module files. bytea
Required
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
qti.package_file.metadata Generated evidence about the file, such as manifest listing flags, validation role, and extracted diagnostics. jsonb
Required, default '{}'::jsonb
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
qti.package_file.created_at Timestamp when the file row was inserted. timestamptz
Required, default now()
Null or used as a proxy for QTI content versioning. Platform gap fill
Package-file audit metadata.
ITD-022 Operational DDL Discipline
qti.artifact.artifact_id Stable platform identity for one logical artifact across versions. uuid
Required
Not a UUID, reused, or derived from a mutable QTI identifier. Platform gap fill
Platform logical artifact identity.
ITD-011 Artifact Versioning
qti.artifact.tenant_id Tenant that owns this artifact. uuid
Required
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
qti.artifact.package_id Origin package for imported artifacts. uuid
Nullable
References a package owned by another tenant. Platform gap fill
Package origin evidence.
ITD-009 Package Resource And File Ingest, ITD-011 Artifact Versioning
qti.artifact.resource_id Origin manifest resource for imported artifacts. uuid
Nullable
Resource comes from another package or tenant. Platform gap fill
Manifest origin evidence.
ITD-009 Package Resource And File Ingest, ITD-011 Artifact Versioning
qti.artifact.artifact_kind Repository category derived from QTI root element or manifest resource type. text
Required
Outside enum set, inconsistent with root_element on versions, changed for Alpha vocabulary, or used to create a QTI-owned Caliper event/result primitive. 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
qti.artifact.qti_identifier 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. text
Nullable
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
qti.artifact.title 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. text
Nullable
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
qti.artifact.language xml:lang or package-default language associated with the artifact. text
Nullable
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
qti.artifact.latest_version_id Newest immutable artifact version for convenience reads, including the latest authoring_json projection returned by getAuthoringJson and the latestArtifactVersionId returned by listArtifacts. uuid
Nullable
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
qti.artifact.created_at Logical artifact creation timestamp and the timestamp component of the stable listArtifacts ordering. timestamptz
Required, default now()
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
qti.artifact_version.artifact_version_id Stable identifier for one immutable artifact edition. uuid
Required
Not a UUID or reused. Platform gap fill
Platform version identity.
ITD-011 Artifact Versioning
qti.artifact_version.artifact_id Logical artifact this version belongs to; the join path that proves tenant ownership for listArtifactVersions and raw artifact-version reads. uuid
Required
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
qti.artifact_version.version_number Forward-only per-artifact version number returned by listArtifactVersions as versionNumber. integer
Required
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
qti.artifact_version.source_xml Original XML accepted after bundled XSD/Schematron validation. xml
Required
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
qti.artifact_version.canonical_xml Canonicalized XML used for equivalence checks and stable export. text
Required
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
qti.artifact_version.xml_hash Hash of canonical_xml used for idempotency and semantic preservation checks; returned by listArtifactVersions as xmlHash. text
Required
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
qti.artifact_version.root_element Root XML element for this version; returned by listArtifactVersions as rootElement. text
Required
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
qti.artifact_version.root_type Generated XSD type name for the root element. text
Nullable
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
qti.artifact_version.schema_file Bundled schema file used as validation authority. text
Required
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
qti.artifact_version.object_graph Canonical generated object-model graph serialized as JSONB for internal persistence. jsonb
Required
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
qti.artifact_version.delivery_json Generated consumer-facing projection used by delivery applications and session snapshots. jsonb
Nullable
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
qti.artifact_version.authoring_json Generated authoring projection returned by GET /tenants/{tenantId}/qti/artifacts/{artifactId}/authoring-json for editors that must preserve all spec-defined fields. jsonb
Nullable
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
qti.artifact_version.spec_trace Generated traceability from classes, fields, and components to XSD/spec anchors. jsonb
Required, default '{}'::jsonb
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
qti.artifact_version.supersedes_version_id Previous version replaced by this version, when the save was an edit or replacement. uuid
Nullable
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
qti.artifact_version.created_at Timestamp when this immutable version was created and the timestamp component of the stable listArtifactVersions ordering. timestamptz
Required, default now()
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
qti.artifact_version.created_by Principal or system actor that created the version. text
Nullable
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
qti.component.component_id Stable identifier for one generated object node row. uuid
Required
Not a UUID or reused. Platform gap fill
Platform row identity.
ITD-005 Lossless Relational Projection
qti.component.artifact_version_id Artifact version containing this component. uuid
Required
Missing version or cross-artifact projection. Platform gap fill
Projection belongs to immutable version.
ITD-005 Lossless Relational Projection, ITD-011 Artifact Versioning
qti.component.parent_component_id Parent object node, preserving the XML/object hierarchy. uuid
Nullable
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
qti.component.ordinal Sibling order under parent_component_id. integer
Required
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
qti.component.element_name 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. text
Required
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
qti.component.qualified_name Clark-notation qualified XML name used to preserve namespace identity. text
Nullable
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
qti.component.namespace_uri Namespace URI for this XML component, if any. text
Nullable
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
qti.component.type_name Generated XSD type name for this object node. text
Nullable
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
qti.component.qti_identifier QTI identifier attribute on this component when present. text
Nullable
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
qti.component.component_path Stable generated path from the root object to this component. text
Required
Not stable across rehydration, duplicated, or encodes tenant/private data. Platform gap fill
Platform traceability and diff path.
ITD-005 Lossless Relational Projection
qti.component.attributes 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. jsonb
Required, default '{}'::jsonb
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
qti.component.text_value Text node value for text-bearing QTI and embedded content nodes. text
Nullable
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
qti.component.tail_value Tail text after this element, required for mixed-content XML round trips. text
Nullable
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
qti.component.source_trace Generated trace to XSD and spec source for this component, including the ITD-pinned QTI feature construct that made this node important when applicable. jsonb
Required, default '{}'::jsonb
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
qti.variable_declaration.variable_declaration_id Stable row identifier for one promoted variable declaration. uuid
Required
Not a UUID or reused. Platform gap fill
Platform projection row identity.
ITD-013 Variable Declaration Projection
qti.variable_declaration.artifact_version_id Artifact version that declares the variable. uuid
Required
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
qti.variable_declaration.component_id Component node that declared this variable. uuid
Required
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
qti.variable_declaration.variable_kind QTI variable category. text
Required
Outside enum set, used to invent a platform-only variable category, or fails to mark qti-template-declaration as template. 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
qti.variable_declaration.identifier QTI variable identifier. text
Required
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
qti.variable_declaration.cardinality QTI cardinality for the variable value container. text
Nullable
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
qti.variable_declaration.base_type QTI base-type for atomic values when the declaration has one. text
Nullable
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
qti.variable_declaration.default_value Generated object value for qti-default-value. jsonb
Nullable
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
qti.variable_declaration.correct_response Generated object value for qti-correct-response. jsonb
Nullable
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
qti.variable_declaration.mapping Generated mapping, areaMapping, matchTable, or interpolationTable detail. jsonb
Nullable
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
qti.variable_declaration.source_trace Generated trace to XSD and spec section for the variable declaration. jsonb
Required, default '{}'::jsonb
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
qti.processing_rule.processing_rule_id Stable row identifier for one promoted processing rule or expression node. uuid
Required
Not a UUID or reused. Platform gap fill
Platform projection row identity.
ITD-014 Processing Rule Projection
qti.processing_rule.artifact_version_id Artifact version containing the processing rule. uuid
Required
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
qti.processing_rule.component_id Component node backing this processing rule. uuid
Required
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
qti.processing_rule.parent_processing_rule_id Parent processing rule for nested expression and rule trees. uuid
Nullable
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
qti.processing_rule.rule_scope Processing scope used for query and execution grouping. text
Required
Outside enum set, used to claim QTI defines this SQL row scope, or stores qti-template-processing under response/outcome 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
qti.processing_rule.rule_name QTI processing rule or expression element/operator name, including qti-set-template-value, qti-template-constraint, and qti-template-default for item-template realization. text
Required
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
qti.processing_rule.sequence_number Order within the parent processing scope. integer
Required
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
qti.processing_rule.operands Generated operand references and literal values for execution. jsonb
Required, default '[]'::jsonb
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
qti.processing_rule.source_trace Generated trace to XSD and spec section for this processing rule. jsonb
Required, default '{}'::jsonb
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
qti.delivery_session.delivery_session_id Stable session identifier exposed by delivery APIs. uuid
Required
Not a UUID, reused, or guessable outside API authorization. Platform gap fill
Platform delivery session identity.
ITD-015 Delivery Session Snapshots
qti.delivery_session.tenant_id Tenant boundary for the delivery session. uuid
Required
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
qti.delivery_session.candidate_ref Opaque tenant-scoped pseudonymous UUID string for the candidate and the selector used by GET/DELETE /tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data. text
Required
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
qti.delivery_session.root_artifact_version_id Immutable item, test, or section version delivered in this session. uuid
Required
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
qti.delivery_session.status Session lifecycle state. text
Required, default 'created'
Outside enum set or inconsistent with attempts, submitted_at, or review workflow. Platform gap fill
Platform delivery lifecycle.
ITD-015 Delivery Session Snapshots
qti.delivery_session.delivery_json_snapshot Snapshot of delivery_json at session start. jsonb
Required
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
qti.delivery_session.session_state Runtime state not modeled as QTI variables, such as navigation, item sequencing, resume information, or review flags. jsonb
Required, default '{}'::jsonb
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
qti.delivery_session.window_started_at Server timestamp at which the timed delivery window started for this session. timestamptz
Nullable
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
qti.delivery_session.window_expires_at Derived server timestamp at which the effective max-time window closes. timestamptz
Nullable
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
qti.delivery_session.effective_max_time_seconds Effective maximum time window in seconds after applying any QTI Personal Needs & Preferences extended-time accommodation. double precision
Nullable
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
qti.delivery_session.created_at Session creation timestamp. timestamptz
Required, default now()
Null or later than updated_at. Platform gap fill
Runtime audit metadata.
ITD-022 Operational DDL Discipline
qti.delivery_session.updated_at Last session state mutation timestamp. timestamptz
Required, default now()
Null, earlier than created_at, or stale after status/session_state update. Platform gap fill
Runtime audit metadata.
ITD-022 Operational DDL Discipline
qti.attempt.attempt_id Stable identifier for one attempt record. uuid
Required
Not a UUID or reused. Platform gap fill
Platform attempt identity.
ITD-016 Attempt State And Processing Trace
qti.attempt.delivery_session_id Owning delivery session. uuid
Required
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
qti.attempt.artifact_version_id Immutable item/test artifact version attempted. uuid
Required
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
qti.attempt.attempt_number Attempt count within a session and artifact version. integer
Required
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
qti.attempt.status Attempt lifecycle state. text
Required, default 'active'
Outside enum set or inconsistent with suspended_at/submitted_at. Platform gap fill
Platform attempt lifecycle.
ITD-016 Attempt State And Processing Trace
qti.attempt.response_state Candidate response variables at the last processing point. jsonb
Required, default '{}'::jsonb
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
qti.attempt.template_state Template variables used for item cloning and stability. jsonb
Required, default '{}'::jsonb
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
qti.attempt.outcome_state Outcome variables after template, response, and outcome processing, including declared time-conditioned outcomes when the test uses duration in outcome processing. jsonb
Required, default '{}'::jsonb
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
qti.attempt.processing_trace Deterministic trace of server-side template realization, response processing, outcome processing, assessmentResult projection evidence, and timed-enforcement operations. jsonb
Required, default '[]'::jsonb
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
qti.attempt.timing_status Server-measured classification of this attempt against the owning delivery session's QTI time-limit window. text
Required, default 'untimed'
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. 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
qti.attempt.effective_duration_seconds Server-measured QTI duration value in seconds for this attempt. double precision
Nullable
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
qti.attempt.started_at Attempt start timestamp. timestamptz
Required, default now()
Null or after submitted_at. Platform gap fill
Runtime audit metadata.
ITD-022 Operational DDL Discipline
qti.attempt.suspended_at Attempt suspension timestamp, if the attempt was suspended. timestamptz
Nullable
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
qti.attempt.submitted_at Attempt submission timestamp, if the attempt was submitted. timestamptz
Nullable
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
qti.conformance_run.conformance_run_id Stable identifier for one conformance run. uuid
Required
Not a UUID or reused. Platform gap fill
Platform evidence row identity.
ITD-017 Conformance Evidence
qti.conformance_run.profile Targeted QTI 3.0 conformance profile or optional feature set. text
Required
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
qti.conformance_run.bundle_hash Hash of the offline spec bundle used by the run. text
Required
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
qti.conformance_run.runner_version Version or identity of the conformance runner. text
Required
Blank, vague, or points to unpinned code. Platform gap fill
Release evidence metadata.
ITD-017 Conformance Evidence
qti.conformance_run.started_at Run start timestamp. timestamptz
Required, default now()
Null or after finished_at. Platform gap fill
Conformance audit metadata.
ITD-022 Operational DDL Discipline
qti.conformance_run.finished_at Run finish timestamp, if complete. timestamptz
Nullable
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
qti.conformance_run.status Run lifecycle status. text
Required, default 'running'
Outside enum set or inconsistent with child assertion statuses. Platform gap fill
Release evidence lifecycle.
ITD-017 Conformance Evidence
qti.conformance_run.summary Generated coverage and pass/fail summary. jsonb
Required, default '{}'::jsonb
Null, non-object JSON, contradicts assertion rows, or includes raw package bytes/secrets. Platform gap fill
Generated release-gate summary.
ITD-017 Conformance Evidence
qti.conformance_assertion.conformance_assertion_id Stable identifier for one assertion result. uuid
Required
Not a UUID or reused. Platform gap fill
Platform assertion row identity.
ITD-017 Conformance Evidence
qti.conformance_assertion.conformance_run_id Owning conformance run. uuid
Required
Missing parent run or status contradicts parent run summary. Platform gap fill
Assertion belongs to release evidence run.
ITD-017 Conformance Evidence
qti.conformance_assertion.assertion_key Stable key generated by the conformance runner for this assertion. text
Required
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
qti.conformance_assertion.artifact_ref Example, fixture, or artifact path covered by this assertion. text
Nullable
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
qti.conformance_assertion.spec_ref Spec section, schema component, or generated trace reference covered by this assertion. text
Nullable
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
qti.conformance_assertion.status Assertion result status. text
Required
Outside enum set, contradicts details, or failed/error without diagnostics. Platform gap fill
Generated assertion result lifecycle.
ITD-017 Conformance Evidence
qti.conformance_assertion.details Assertion diagnostics, canonical hashes, processing outcomes, and failure details. jsonb
Required, default '{}'::jsonb
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
Allowed values

Allowed values with behavioral meaning

Each value is constrained by the dictionary and linked to the decision that explains whether it is a 1EdTech pass-through or a platform gap fill.

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.

ValueBehavior
importingThe package row has been created and validation or resource extraction is still in progress. Do not deliver artifacts from this package yet.
importedValidation, resource extraction, artifact creation, and version projection succeeded. The package can be queried, delivered, and exported.
rejectedValidation, package-closure checks, XSD/Schematron validation, or privacy validation failed. Keep diagnostics in metadata; do not create deliverable sessions from this package.
supersededA later package or version replaces this import for operational use while preserving this row for audit and reproducibility.

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.

ValueBehavior
imsqti_test_xmlv3p0A QTI assessment test XML resource. The primary href should point to a test XML document.
imsqti_section_xmlv3p0A QTI assessment section XML resource. Use for sections managed independently from a test.
imsqti_item_xmlv3p0A QTI assessment item XML resource. Use for a candidate-facing item with interactions and response processing.
imsqti_resprocessing_xmlv3p0A QTI response-processing XML resource when response processing is represented as a separate package resource.
imsqti_outcomes_xmlv3p0A QTI outcome-declaration XML resource, often used when outcomes are managed independently.
imsqti_stimulus_xmlv3p0A QTI assessment stimulus XML resource that items can depend on for shared passage or stimulus content.
imsqti_fragment_xmlv3p0A managed QTI fragment resource used by item, section, or test content.
imsqti_rptemplate_xmlv3p0A response-processing template XML resource, including standard or custom templates packaged with items.
associatedcontent/learning-application-resourceA learning-application asset referenced by QTI content.
webcontentGeneric web content asset, such as image, video, audio, HTML, or other supporting media.
imsbasiclti_xmlv1p3An LTI tool resource referenced by packaged content.
controlfileA manifest control file or package control artifact.
resourcemetadata/xmlMetadata XML associated with a package resource.
resourceextmetadata/xmlExternal metadata XML associated with a package resource.
qtiusagedata/xmlA QTI usage-data XML resource carrying item or distractor statistics.
plsPronunciation lexicon resource used by speech or accessibility presentation.
css2CSS 2 stylesheet resource.
css3CSS 3 stylesheet resource.
extensionAn extension resource. Preserve and export it, but do not treat it as a known QTI root without validation evidence.

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.

ValueBehavior
itemLogical artifact whose root is a QTI assessment item.
testLogical artifact whose root is a QTI assessment test.
sectionLogical artifact whose root is a QTI assessment section.
stimulusLogical artifact whose root is a QTI assessment stimulus.
outcome-declarationLogical artifact whose root is a standalone QTI outcome declaration.
response-processingLogical artifact whose root is standalone QTI response processing or a response-processing template.
resultLogical 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.
usage-dataLogical 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.
metadataLogical artifact for QTI or resource metadata XML.
manifest-resourceManifest-only resource that must remain addressable even when it is not a QTI root document.

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.

ValueBehavior
responseCandidate response variable declared by QTI and usually bound to an interaction.
outcomeScoring, feedback, or reporting variable set by default values or processing rules.
templateTemplate 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.
contextContextual variable available to template or response processing, including candidate, test, or system context when declared.

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.

ValueBehavior
responseRule belongs to response processing and computes outcome variables from candidate responses.
outcomeRule belongs to outcome processing at test or section level.
templateRule 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.
expressionRow represents an expression subtree or operator nested inside response, outcome, or template processing.

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.

ValueBehavior
createdSession exists and has a delivery JSON snapshot but has not yet become the active learner experience.
activeCandidate may interact with delivered content and create or update attempts.
suspendedCandidate work is paused and may be resumed with the same snapshot and session state.
submittedCandidate has submitted the session; scoring and attempt records are complete enough for review.
reviewSession is in review mode. Content and responses may be displayed, but interactions must not change response variables.
closedSession is final for normal operations. Future edits to content do not affect it.
voidedSession is retained as an operational record but should not count toward reporting or outcomes.

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.

ValueBehavior
activeCandidate can still modify responses for this attempt.
suspendedCandidate response state is saved for later continuation.
submittedCandidate submitted responses and processing has produced outcome state.
reviewedAttempt has been reviewed by an authorized person or workflow.
voidedAttempt is retained for audit but excluded from reporting and outcomes.

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.

ValueBehavior
untimedThe 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.
in_windowThe 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.
late_acceptedThe 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.
late_rejectedThe 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.

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.

ValueBehavior
runningThe conformance runner has started and assertions are not yet complete.
passedAll required assertions for the targeted profile passed.
failedAt least one required assertion failed.
errorThe runner could not complete because of tool, environment, or infrastructure failure.

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.

ValueBehavior
passedThis assertion met the expected result.
failedThis assertion ran and found behavior that violates the target profile or platform contract.
skippedThis assertion was intentionally not run, usually because it is out of profile or unavailable in the current runner.
errorThis assertion could not produce a valid pass/fail result because the runner or fixture failed.

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.

ValueBehavior
noneThe projection must preserve all spec-defined fields needed to reconstruct the generated object graph and canonical XML.
declaredThe projection may omit only explicitly documented authoring-only or diagnostic detail, such as source trace or mixed-content tail detail.

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.

ValueBehavior
provisioningThe 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.
activeNormal 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.
suspendedThe 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.
archivedThe 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.

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.

ValueBehavior
platformShared 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.
qtiQuestion 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.
onerosterOneRoster 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.
caliperCaliper 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.
caseCASE 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.
nweamapNWEAMap 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.
ed_fiEd-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.
people_and_orgsAlpha 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.
curriculumAlpha 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.
contentAlpha 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.
eventsAlpha 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.
resultsAlpha 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.
analyticsAlpha 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.

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.

ValueBehavior
approvedThe 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.
under_reconciliationA 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.
in_progressThe 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.
rolled_backA 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.
not_startedThe 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.

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.

ValueBehavior
platformThe 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.
1edtechExpert 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.
alphaPlain-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.

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.

ValueBehavior
POSTCreate, 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.
PUTFull 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.
PATCHPartial 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.
DELETEDelete 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.

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.

ValueBehavior
in_progressThe 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.
completedThe 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.
failed_permanentThe 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.
failed_transientThe 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.
expiredThe 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.

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.

ValueBehavior
createA 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.
updateAn existing resource was changed. Use when: A mutable customer or administrative field changes. Invalid when: Used for append-only learner submission creation.
deleteA 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.
importA 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.
exportA 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.
read_privilegedA 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.
runtime_deleteLearner 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.
conformance_changeA 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.
trust_changePublic 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.
authz_deniedAn 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.
maintenanceA 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.

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.

ValueBehavior
acceptedThe 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.
succeededThe 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.
failed_validationThe 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.
failed_authorizationThe 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.
failed_conflictThe 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.
failed_not_foundThe 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.
failed_serverThe 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.
Validation, runtime, privacy

Rules that protect QTI fidelity and learners

Runtime

  • Template, response, and outcome variables are initialized from generated QTI declarations.
  • All 87 XSD-reachable inline processing elements are covered by runtime assertions, and all 6 bundled templates are supported.
  • Unsupported future features fail closed with trace evidence rather than disappearing. qti.attempt.processing_trace.
  • Attempts remain stable because they reference immutable artifact versions and retain response, template, outcome, and trace snapshots.

Privacy

  • qti.delivery_session.candidate_ref must be an opaque tenant-scoped pseudonymous UUID string.
  • Direct names, emails, phone numbers, SIS IDs, raw JWT subjects, access tokens, raw PNP records, IP addresses, and user agents are forbidden in learner-runtime state and traces.
  • The deletion contract removes delivery sessions and cascading attempts for one candidate_ref while preserving reusable content.
  • When QTI_CONTEXT candidateIdentifier is needed at runtime, it receives candidate_ref, not direct learner identity.
Evidence

Conformance and release evidence

The website summarizes generated evidence and points to the persistence rows that make the evidence queryable after implementation.

EvidenceCurrent factsSourceTrace
Source bundle27 XSDs, 683 global elements, 842 complex types, 181 simple types, 2 VDEX vocabularies, 20 VDEX terms.vendor/qti-spec-bundle/MANIFEST.md plus approved architecture evidenceOffline 1EdTech Source Bundle
Schema and round-trip corpus327 XML files classified; 311 schema checks and 311 round-trip schema checks passed for the in-profile corpus.vendor/qti-spec-bundle/examples/qtiv3-examples plus approved conformance ITDConformance Evidence, Validation And Rejection Policy
Processing runtime87 XSD-reachable processing elements; 87 runtime-supported inline elements; 6 bundled templates supported.vendor/qti-spec-bundle/v3p0/rptemplates plus implementation conformance provenanceRuntime Execution Profile, Conformance Evidence
Release evidence tablesConformance runs and assertions persist profile, bundle hash, runner version, status, assertion keys, artifact/spec references, and diagnostics.qti.conformance_run and qti.conformance_assertionConformance Evidence
Implementation input

What the implementation must satisfy

The implementation deliverable is derived from this website, the approved architecture, and the approved data dictionary. The canonical API root for this surface is https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api; set QTI_BASE_URL to the deployed root before running the examples. Implementation work must satisfy this page instead of inventing missing behavior during coding.

Single canonical API

Serve the protected QTI API at the canonical root reserved for this surface (https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api) for every tenant. The same canonical deployment exposes POST /dev/mint?tenantId=demo and POST /dev/mint?tenantId=00000000-0000-4000-8000-000000000003 for the public demo tenant, returns per-run fixture URLs at /fixtures/qti-package.zip, and accepts operator-minted QTI_REVIEWER_JWT tokens for real tenants. Consumers must not use deploy-hash URLs.

Package ingest

Accept ZIP/package input, validate closure and XML, persist package/resource/file/artifact/version/component rows, and reject invalid import_status values.

Repository enumeration

Implement listPackages, listArtifacts, and listArtifactVersions with tenant-scoped auth, cursor + limit paging, {items, nextCursor}, empty-page 200 responses, and enough handles for lost-response recovery.

Projection and export

Generate lossless authoring JSON reads with ETag, declared-lossiness delivery JSON, canonical XML export, authoring saves that require If-Match, and reproducible XML hashes.

Tenant and auth

Require tenant-scoped reads/writes and Bearer JWT tenant-claim matching for tenant-owned routes.

Delivery and attempts

Snapshot delivery JSON, execute QTI processing against immutable versions, persist response/template/outcome/trace state.

Privacy read/delete

Enforce pseudonymous candidate_ref, redact trace/runtime state, read candidate-scoped runtime data for authorized callers, and implement candidate runtime-data deletion.

Conformance

Persist conformance run/assertion evidence and keep profile diagnostics separate from QTI schema restrictions.

Source trail

Inputs used to generate this website

This platform3 deliverable is derived from the approved upstream architecture, data dictionary, and QTI customer-site contract. The vendored QTI bundle supplies the offline 1EdTech source material, and the source trail names only platform3-approved inputs.

Approved platform3 upstreams

  • 1EdTech architecture canonical URL
  • 1EdTech data dictionary canonical URL
  • 1EdTech implementation API canonical URL
  • Current approved QTI API root for QTI_BASE_URL / BASE_URL: https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api
  • /fixtures/qti-package.zip?runId=$RUN_ID = ready-to-ingest per-run package fixture on the same canonical API root
  • QTI_REVIEWER_JWT = operator-minted real-tenant JWT from .env.local
  • QTI_REVIEWER_TENANT_ID = optional override for reviewer tenant path; default 00000000-0000-4000-8000-000000000004
  • /dev/mint?tenantId=demo = cold-copy public demo token mint on the same canonical API root
  • /dev/mint?tenantId=00000000-0000-4000-8000-000000000003 = equivalent public demo tenant UUID selector
  • loop/qti/artifacts/1edtech/architecture/site/architecture-traceability.json
  • loop/qti/artifacts/1edtech/data_dictionary/site/qti-data-dictionary.json

Benchmarks considered

  • Stripe API reference for top-level authentication/errors references, resource-oriented endpoint clarity, and inline request/response specificity.
  • Algolia docs for navigation, search, and information scent.
  • loop/context/benchmarks/index.json records Stripe as the customer_website benchmark; this workspace did not contain a local customer_website.html snapshot, so the live benchmark URL was fetched directly.
Reference files read
  • loop/qti/artifacts/1edtech/architecture/site/architecture-traceability.json
  • loop/qti/artifacts/1edtech/data_dictionary/site/qti-data-dictionary.json
  • loop/qti/artifacts/1edtech/implementation/summary.json
  • loop/qti/artifacts/1edtech/implementation/impl/README.md
  • loop/qti/artifacts/1edtech/implementation/impl/openapi/qti-boundary.openapi.yaml
  • loop/qti/artifacts/1edtech/implementation/impl/provenance.json
  • loop/qti/artifacts/1edtech/implementation/impl/src/http/qtiHttpServer.mjs
  • loop/qti/artifacts/1edtech/implementation/impl/tests/deployed.smoke.test.mjs
  • vendor/qti-spec-bundle/README.md
  • vendor/qti-spec-bundle/MANIFEST.md
  • vendor/qti-spec-bundle/v3p0/spec/qti_v3p0_oview.html
  • vendor/qti-spec-bundle/v3p0/spec/qti_v3p0_info.html
  • vendor/qti-spec-bundle/v3p0/spec/qti_v3p0_bind.html
  • vendor/qti-spec-bundle/v3p0/spec/qti_v3p0_impl.html
  • vendor/qti-spec-bundle/v3p0/spec/qti_v3p0_conf.html
  • vendor/qti-spec-bundle/v3p0/spec/qti_v3p0_vocab.html
  • vendor/qti-spec-bundle/v3p0/xsd/*.xsd
  • vendor/qti-spec-bundle/v3p0/rptemplates/*.xml
  • vendor/qti-spec-bundle/v3p0/vocab/*.xml
  • vendor/qti-spec-bundle/examples/COMMIT.txt
  • vendor/qti-spec-bundle/examples/qtiv3-examples/**/*.xml