AlphaTest · Administration · Integrator API · Architecture

Assign once. Deliver in QTI. Keep every learning fact upstream.

This architecture lets the AcmeTest maintainer turn any promised test kind—mastery gate, formative, or adaptive diagnostic—and a OneRoster target into policy-gated native QTI delivery sessions, observe authoritative runtime state, rotate or restart attempts safely, and close outcomes into Platform3 Results without copying roster profiles, responses, scores, posteriors, or mastery into AlphaTest.

Architecture baseline · 15 July 2026 · close-operation correction certified 24 July 2026 · API namespace /v1 · status: active

Architecture certification

This is a thin orchestration API, not a second SIS, assessment runtime, or gradebook. Every public operation has an owner, an authoritative read path, a retry contract, and a failure mode that exposes the responsible dependency.

Who

The engineer maintaining AcmeTest, an Edulastic-shaped thin app already using Platform3 QTI and OneRoster.

Job

Wire assign → deliver → results in one afternoon, using Platform3-native IDs and never computing a score or authoring an item.

Status quo beaten

Ad hoc assignment glue and in-repo forms become one retry-safe administration resource with observable upstream-backed state.

Acceptance proof

A cold client can create, launch, poll, advance, retake, and close every promised test kind; every delivery reference dereferences in QTI and every durable learning fact resolves in Results.

Certification judgment: the complete adaptive candidate-level contract is now pinned SHIP by ITD-032ITD-036: exact start inputs, OneRoster→Results binding supplied and validated per candidate, mastery-engine ownership of every QTI offer, opaque continuation/ETag handling, resumable fan-out, retakes, four-state close composition, typed failures, and tenant probes. This is an architecture decision, not a false release claim. The supplied source-of-truth list approves the mastery-engine architecture, data dictionary, and customer website, but supplies no approved callable implementation URL. Therefore ITD-035 fail-closes production activation until immutable owner-backed wire receipts exist; it forbids replacing adaptive success with a permanent 422/424 stub.

Disposition of the blocking downstream findings

Finding supplied to this architecture rebuildWire evidence available nowPinned dispositionWhat changes this decision
Assignable-bank contract mismatch: Bank reported a fixed-form operation ready/assignable while authoritative Content still described its bank as draft and not a mastery gate.Fresh 2026-07-20 same-tenant evidence: Bank operation bop_M8VQZDF9H15CQWD2FQNJ6BVD34 returned bank 0ba123e1-5653-49af-938c-d1004a9f7f92 with five forms; Content read-back returned status=draft, is_mastery_gate=false, membership_rule=null, and spec_id=null. Administration correctly rejected it with 422 test-not-assignable before roster or QTI effects.ROLL BACK BANK ARCHITECTURE. Administration keeps its fail-closed input predicate and now pins the exact cross-module handshake in ITD-037. Bank must not emit ready/assignable until Content read-back satisfies that predicate and an Administration create replay crosses the boundary.A same-tenant Bank receipt must show published status, mastery-gate flag, fixed-form membership rule, matching spec ID, exact ordered members, and a successful Administration create using those native IDs.
Full-job upstream gap: scoring and Results write-back are required, but the prior v1 architecture had no close operation.Accepted triage decision 2026-07-17-004 records a live QTI scored attempt (outcomeState.SCORE) and a live Results create/exact-read seam that enforces administration_id, QTI attempt/session/test/artifact IDs, Content test-spec ID, test kind, and subject.SHIP. POST /v1/administrations/{id}/close is a mandatory idempotent operation. It reads authoritative QTI outcomes, writes Results, verifies exact read-back, and advances the administration to scored. See ITD-030.The P6 regression must replay create → delivery ref → submitted QTI attempt → close → exact Results lookup → scored.
Runtime-policy upstream gap: QTI does not enforce future windows, duration, or accommodations.QTI session creation accepts only candidateRef and artifactVersionId; no pre-start policy-update operation is published.SHIP launch-window authorization / DEFER duration and accommodation enforcement. AlphaTest withholds launch references outside opensAt/closesAt. timeLimitSeconds remains visibly nominal, and every non-default accommodation is rejected before persistence or QTI effects. AlphaTest never claims it can terminate a player that already holds a session. See ITD-018 and ITD-031.Duration or accommodation enforcement may ship only after authenticated QTI create/update probes prove the corresponding native semantics.
Adaptive success contract missing: the previous architecture reserved adaptive_diagnostic but always returned 422.The approved mastery-engine design pins POST /v1/adaptive-runs, inspect, advance, native QTI offers, opaque JWE continuation, strong ETags, and Results KC acknowledgement. No callable implementation is supplied as approved input.SHIP the complete contract; gate release evidence. ITD-032034 make create/provision/launch/advance/retake/close implementable. ITD-035 blocks production advertisement until the real upstream passes the trace.An implementation cell must use an approved immutable mastery-engine URL and receipts or route an upstream gap; it may not ship a permanent failure stub.

Decision authority is explicit: architecture pins the customer contract; immutable deployment receipts decide release readiness. Absence of an approved callable mastery-engine input is recorded honestly and cannot erase the promised adaptive branch or be disguised as successful integration.

System boundary and ownership

ConcernSystem of recordAlphaTest may retainAlphaTest must not retain
Test/form/blueprintPlatform3 Content + QTIOpaque test, form, bank, and blueprint references used by policyItems, packages, answer keys, test definitions
RosterOneRosterTarget selector and opaque sourcedId references needed for overridesNames, emails, demographics, copied class/enrollment rows
DeliveryQTI runtimeImmutable binding only: candidate sourcedId, QTI candidate/session reference, selected form reference, creation receiptSession or attempt bodies, responses, runtime state
OutcomesResults; aggregates in AnalyticsNo durable outcome body; reconciliation cursor and last success/failure metadataScores, mastery state, gap maps, rollups
EventsCaliperIdempotency receipt/correlation referencePrivate shadow event stream
Administration glueAlphaTestTenant, upstream references, window, timing, retake policy, accommodations references, candidate-to-session binding, lifecycle metadataIdentity profiles or any mutable fact already owned above
Hard invariant: an AlphaTest response may compose upstream data, but persistence may not turn that composition into a shadow roster, attempt store, session store, result store, or mastery ledger.

End-to-end flow

AcmeTest            AlphaTest admin        OneRoster     mastery_engine       QTI          Results
   | POST /administrations |                  |                 |              |              |
   |----------------------->| verify test ---->|              |              |                 |
   |                        | resolve target ->|              |              |                 |
   |                        | fixed: create QTI session -------------------->|                 |
   |                        | adaptive: start each candidate ->| create native offer --------->|
   | 202 + administration   |                  |              |              |                 |
   |<-----------------------|                  |              |              |                 |
   | POST /.../candidates/{candidateId}/launch                 |              |                 |
   |----------------------->| enforce launch window; return QTI ref -------->|                 |
   | GET /administrations/{id}?include=candidates             |              |                 |
   |----------------------->| read session/runtime state ------------------->|                 |
   | 200 composed status + launch eligibility                  |              |                 |
   |<-----------------------|                  |              |              |                 |
   | launch QTI player with native deliverySessionRef ---------------------->|                 |
   | POST /.../advance      | adaptive only: token + If-Match ->| read QTI; next offer -------->|
   |                        | read each bound candidate runtime ------------>|                 |
   | authoritative state   |                  |              |              |                 |
   |<-----------------------|                  |              |              |                 |
   | POST /close            | read outcomeState.SCORE ---------------------->|                 |
   |                        | write typed result_record --------------------------------------->|
   |                        | exact ?qtiAttemptId= read-back --------------------------------->|
   | 202 closing; GET → scored |                  |              |              |                 |

Creation is asynchronous for a reason

Resolving a class and provisioning N candidate attempts crosses multiple upstreams. POST therefore returns 202 Accepted once validation, the tenant-scoped orchestration record, candidate bindings, and idempotency receipt are durable. The administration progresses from provisioning to scheduled, or provisioning_with_errors while successful candidates remain usable. POST /{id}/provision resumes only failed candidates with their original child keys.

Public HTTP contract

All routes are JSON under /v1. IDs are opaque. Times are RFC 3339 UTC instants. Platform3-native references remain first-class fields.

Method and pathPurposeSuccessRequired scope / concurrency
POST /v1/administrationsCreate and asynchronously provision mastery_gate, formative, or adaptive_diagnostic for one OneRoster target. Adaptive requests must include the complete configuration and exact candidate→Results binding set.202, body + Location; pre-effect 422 adaptive-capability-required for an incomplete adaptive requestadministrations:write; Idempotency-Key required
POST /v1/administrations/{id}/provisionResume only failed candidate provisioning. Original deterministic child keys prevent duplicate QTI sessions or adaptive runs.202; synchronous dependency failure is typed 424 mastery-engine-unavailable or the owning dependency equivalentadministrations:write; Idempotency-Key required
GET /v1/administrationsList tenant administrations by status, test kind, target, or modification cursor.200 cursor pageadministrations:read
GET /v1/administrations/{id}Read orchestration state; optionally compose candidate/session state.200 + ETagadministrations:read
PATCH /v1/administrations/{id}Change AlphaTest-owned scheduling metadata or retake policy before any candidate leaves not_started. It never mutates QTI session policy.200; 409 administration-state-conflict after a startadministrations:write; If-Match required
GET /v1/administrations/{id}/candidatesCompose OneRoster identity refs with authoritative QTI state; page immutable bindings by createdAt,id.200 cursor pageadministrations:read_sensitive
POST /v1/administrations/{id}/candidates/{candidateSourcedId}/launchAuthorize launch and reveal the current native QTI deliverySessionRef. For adaptive attempts it is the current offeredItem.deliverySessionId created by mastery_engine, never by Administration.200; 403 launch-not-open, 410 launch-window-ended, or 409 candidate-not-provisionedadministrations:deliver; Idempotency-Key required on first launch
POST /v1/administrations/{id}/candidates/{candidateSourcedId}/advanceAdaptive only: after QTI scores the current offer, call mastery_engine :advance with the bound opaque token and strong ETag, then atomically replace token, ETag, expiry, and offered session.200 current candidate projection; 424 mastery-engine-unavailable leaves the old binding retryable; 409 adaptive-continuation-expired requires retakeadministrations:deliver; Idempotency-Key required
POST /v1/administrations/{id}/retakesWhile open and within policy, create attemptNumber+1. Fixed mastery gates rotate forms; adaptive diagnostics start a fresh mastery run with the original immutable pins and a new child key/continuation.202; 409 administration-state-conflict or 409 retake-policy-exhaustedadministrations:write; Idempotency-Key required
POST /v1/administrations/{id}/closeStop new launches/retakes and verify every learning fact at its owner. Fixed forms read QTI outcomeState.SCORE, write Results, and exact-read it back; adaptive runs must already be mastery-engine completed with reportable output and acknowledged Results KC components, which close verifies by inspect/reference only.202 with lifecycle closing; 409 close-not-ready before all launched attempts are complete; polling reaches scored only after every reference verifiesadministrations:close; Idempotency-Key required

Creation shape: class target

{
  "test": { "qtiTestId": "qti_test_…", "contentTestSpecId": "test_spec_…", "testKind": "mastery_gate" },
  "target": { "type": "class", "oneRosterSourcedId": "class_…" },
  "window": { "opensAt": "2026-08-20T13:00:00Z", "closesAt": "2026-08-22T21:00:00Z" },
  "timing": { "timeLimitSeconds": 5400 },
  "retakes": { "maxAttempts": 3, "parallelFormRotation": "different_form_required" },
  "accommodations": {
    "default": { "timeMultiplier": 1 },
    "candidateOverrides": []
  }
}

Creation shape: explicit candidate-list target

{
  "test": { "qtiTestId": "qti_test_…", "contentTestSpecId": "test_spec_…", "testKind": "formative" },
  "target": {
    "type": "candidate_list",
    "oneRosterCandidateSourcedIds": ["user_01…", "user_02…"]
  },
  "window": { "opensAt": "2026-08-20T13:00:00Z", "closesAt": "2026-08-22T21:00:00Z" },
  "timing": { "timeLimitSeconds": 1800 },
  "retakes": { "maxAttempts": 1, "parallelFormRotation": "not_required" },
  "accommodations": { "default": { "timeMultiplier": 1 }, "candidateOverrides": [] }
}

Creation shape: adaptive diagnostic

{
  "test": { "contentTestSpecId": "test_spec_…", "testKind": "adaptive_diagnostic" },
  "target": { "type": "class", "oneRosterSourcedId": "class_…" },
  "adaptiveConfig": {
    "selectionPolicyId": "policy_…",
    "scaleCalibrationId": "calibration_…",
    "testBankId": "test_bank_…",
    "bankOperationId": "bank_operation_…",
    "runMode": "production_learner",
    "resultsStudentBindings": [
      { "candidateSourcedId": "user_01…", "resultsStudentId": "student_01…" },
      { "candidateSourcedId": "user_02…", "resultsStudentId": "student_02…" }
    ]
  },
  "window": { "opensAt": "2026-08-20T13:00:00Z", "closesAt": "2026-08-22T21:00:00Z" },
  "retakes": { "maxAttempts": 2, "parallelFormRotation": "not_applicable" }
}

Administration supplies mastery_engine's exact seven production fields per candidate: the five immutable request pins above, testSpecId from test.contentTestSpecId, runMode, and that candidate's caller-supplied resultsStudentId. It resolves the target live, requires the binding set to match the resolved candidates exactly, and validates each Results reference in the same tenant before persisting anything. It never infers one identity from the other.

Response invariants

Close request and receipt

POST /v1/administrations/adm_123/close
Authorization: Bearer <platform-jwt>
Idempotency-Key: close-adm-123

{}

202 Accepted
{
  "id": "adm_123",
  "status": "closing",
  "close": { "submittedAttempts": 24, "verifiedResults": 0, "failed": 0 },
  "requestId": "req_..."
}

Close first rejects with 409 close-not-ready when any launched candidate remains in_progress or has no submitted attempt. During closing, deterministic child keys derived from tenant, administration, and QTI attempt make every Results create retry-safe. A retry resumes incomplete candidates; it never creates a second result record. A candidate is verified only when GET /alpha/results/v1/result-records?qtiAttemptId=... resolves exactly one record carrying the same administration_id and complete QTI/Content reference set. QTI or Results failures leave close_failed reconciliation metadata and are retried with the same key; the administration cannot read scored prematurely.

Runtime-policy boundary: AlphaTest does not reveal a deliverySessionRef before opensAt or after closesAt. It accepts only an absent accommodations object or the semantic no-op {default:{timeMultiplier:1},candidateOverrides:[]}; every non-default request returns 422 qti-accommodation-capability-required before persistence or QTI effects. timeLimitSeconds is nominal scheduling/audit metadata. Because QTI receives none of these fields, AlphaTest does not claim duration enforcement or the ability to terminate a third-party player that already retained a session reference. Those QTI-native capabilities remain DEFERRED.

Lifecycle and state authority

Administration lifecycle

SHIP provisioning → scheduled → open → closing → scored as AlphaTest orchestration state. provisioning_with_errors preserves successful candidates while failed candidates are resumed with original child keys; window_ended and close_failed remain explicit.

Candidate state

not_started, in_progress, submitted, scored are composed on read. Fixed forms derive them from QTI; adaptive runs map QTI launch plus mastery-engine in_progress/finalizing/completed and Results acknowledgement under ITD-036. They are never mutable AlphaTest facts.

Aggregate state

Counts are composed from QTI at read/reconcile time. AlphaTest may cache only a short-lived response cache; it is not durable evidence.

Close completion

For fixed forms, scored requires QTI SCORE plus exact Results read-back. For adaptive runs, it requires mastery-engine completed, reportable scale output, and acknowledged Results KC components. Administration retains only owner references and never a score or gap body.

Retake selection

The retake endpoint is valid only while the administration is open, the prior attempt is submitted, scored, completed, or has typed adaptive-continuation-expired, and maxAttempts is not exhausted; all other combinations return 409 administration-state-conflict or 409 retake-policy-exhausted. Fixed mastery gates exclude used forms and choose the least-used eligible parallel form with a stable hash tie-breaker. Adaptive diagnostics never reuse a continuation: attempt N+1 calls mastery_engine start with the original immutable configuration, the same validated Results binding, and a fresh deterministic child key.

Security, tenancy, and privacy

Upstream reality contract

Production base is https://platform3-andymontgomery-9773s-projects.vercel.app. Concrete adapter paths must be verified against each upstream’s published contract before implementation; they are not guessed here. Deployment uses configured UPSTREAM_* URLs and fails capability checks rather than substituting local fixtures.

Observed wire boundary · updated by accepted triage 2026-07-17-004: QTI session creation still accepts only candidateRef and artifactVersionId, so AlphaTest enforces launch policy without claiming QTI-native timing or accommodation controls. The scored attempt is real on the wire at outcomeState.SCORE. Results POST /alpha/results/v1/result-records is also live and enforces the full QTI/Content/administration reference set; exact GET ...?qtiAttemptId= read-back resolves the settled record. Deployment binds these calls to the provisioned PLATFORM3_TENANT/PLATFORM3_JWT service credential. Local substitutes and the obsolete workload-token-exchange gate are forbidden.
PortRequired wire capabilityFailure surfacedEvidence later required
ContentDereference the native test_spec and test_bank in the JWT tenant. For a fixed mastery gate, require authoritative read-back of status=published, is_mastery_gate=true, membership_rule=same_blueprint_fixed_forms, spec_id equal to the requested Content test-spec ID, and the exact ordered member set whose immutable QTI versions all dereference. Bank operation state is evidence correlation, never authority for these facts.424 content-unavailable or pre-effect 422 test-not-assignableContent detail + membership + QTI immutable-version trace, followed by same-tenant Administration create; see ITD-037
OneRosterRead class/candidate target and active enrollments by sourcedId.424 roster-unavailable or 422 target-emptyTrace proving live roster resolution
QTI runtimeSHIP: create delivery sessions, read runtime data/attempt history, and read each submitted attempt's outcomeState.SCORE. GAP: no QTI-native window, duration, accommodation, or pre-start policy-update contract.424 qti-unavailable; administration launch policy remains enforceable without asserting QTI-native controlsCreate/read/outcome traces, including attempt 89bfffe6-… precedent from accepted triage
ResultsSHIP: create one result_record per submitted QTI attempt with administration_id, qti_attempt_id, qti_session_id, qti_test_id, qti_artifact_version_id, content_test_spec_id, test_kind, and subject; verify exact read-back by QTI attempt ID.424 results-unavailable before acceptance, or retryable close_failed after asynchronous work beginsResult create + exact lookup trace; verified record 3912e8b0-f98d-47c6-82f6-828cea4128c7 is the accepted precedent
Reports/CaliperCOMPOSE, DO NOT REBUILD: Results owns the durable outcome; Reports owns mastery/report composition and its required Caliper emission. Administration records only opaque receipts and never constructs mastery components or a private event outbox.A Results write can complete close; report/Caliper failures are visible through their owning module and never cause local learning-fact storageSeparate Reports conformance evidence proves its result-components and Caliper contract
mastery_engineSHIP CONTRACT / RELEASE EVIDENCE NOT SUPPLIED: call start, inspect, and advance exactly as approved; mastery_engine owns every QTI offer and Results KC acknowledgement. Administration supplies validated immutable pins and Results identity, then stores only opaque token/ETag/offer references.Pre-effect request defects are 422 adaptive-capability-required; attempted transport/5xx/invalid owner responses are 424 mastery-engine-unavailable and preserve retryable per-candidate stateApproved callable immutable URL plus native-QTI, Results-KC, same-tenant, bidirectional cross-tenant, and transient-failure receipts required by ITD-035
Capability gate: adaptive is a required SHIP contract, not an optional route. The architecture cell may be approved from owner-published design; implementation and later cells may claim success only with an approved callable mastery_engine and replayable D5 receipts. If that upstream is unavailable, the implementation must route the owner gap and remain unreleased—not substitute a local engine, guess an alias, or publish a permanent 422/424 response as the product.

Adaptive candidate orchestration release contract

This table composes the approved mastery-engine contract with Administration's now-pinned cross-module rules. Every row is part of the SHIP API contract. The evidence column is the separate production-activation gate.

Required contract factApproved mastery-engine designRequired owner and composition ruleRelease evidence
Configuration identifiersStart requires selectionPolicyId, scaleCalibrationId, testSpecId, testBankId, bankOperationId, runMode, and production resultsStudentId.Caller supplies all except testSpecId, which is test.contentTestSpecId; Administration selects one validated resultsStudentId per resolved candidate. No pool/KC content is copied.Exact seven-field start body and immutable policy/calibration/bank dereference trace.
Candidate and Results identitymastery_engine requires a same-tenant Results student reference but does not infer it from OneRoster.Caller supplies an exact candidate→Results binding set. Administration resolves candidates in OneRoster, validates each Results ID in the same tenant, rejects missing/extra/duplicate/cross-tenant bindings with pre-effect 422, and stores only the two opaque references.Own-tenant success; missing, extra, duplicate, tenant-A→B and tenant-B→A failures.
Initial QTI session ownershipPOST /v1/adaptive-runs returns the first offered native QTI session, ETag, and opaque continuation.mastery_engine alone creates first and later QTI sessions. Administration launch reveals the returned session reference only; it never creates a generic adaptive session.Start trace to real QTI create plus successful dereference in the existing player.
Continuation and correlationContinuation is an encrypted opaque JWE renewed on advance; advance requires strong If-Match.Candidate-attempt binding keys tenant+administration+candidate+attempt to ciphertext token, ETag, expiry, and current offer. Administration never decrypts or returns the token; successful advance atomically replaces all four.Restart-safe inspect/advance, stale ETag 412, token redaction, expiry, and storage-allowlist proof.
Retry and failure semanticsMastery-engine pins idempotent replay, changed-input 409, ETags, and typed RFC 9457 problems.Child key is deterministic over tenant+administration+candidate+attempt+canonical start hash. Partial success is preserved; provision resumes failed candidates only. 422 is pre-effect; 424 means attempted dependency failure; expired continuation requires a fresh retake.Lost-response replay, partial fan-out resume, changed-payload conflict, transient 424 recovery, and zero duplicate adaptive/QTI run.
Tenant and service authenticationPINNED CREDENTIAL / OPEN IMPLEMENTATION PROOF. Tenant-from-JWT and scopes are pinned; Platform3 calls use the provisioned server-side PLATFORM3_TENANT/PLATFORM3_JWT credential. No workload-token exchange is required.Keep the verified Administration JWT tenantId authoritative, bind the server-side service credential to that tenant, and forbid caller-supplied upstream tokens.Authenticated same-tenant, anonymous, wrong-scope, bidirectional cross-tenant, expiry/refresh, least-privilege, and token-redaction probes.

Important Technical Decisions

Every consequential API axis is pinned SHIP or DEFERRED, including deliberate non-decisions. Each anchor is stable; active decisions are mirrored in commitments.json, while superseded ITDs remain here as audit history.

Standard axisPinDecision
Write granularitySHIP per-resource including close; DEFER bulkITD-002
Read shapeSHIP list + detail + candidatesITD-003
Query modelSHIP filters + cursor + modifiedSinceITD-004
ConcurrencySHIP If-Match on owned policyITD-005
IdempotencySHIP on exposed writes, including closeITD-006
Auth shapeSHIP role maximum + optional narrowing scopesITD-007
EventingSHIP polling; DEFER webhooksITD-008
Error envelopeSHIP typed RFC 9457ITD-009
Tenant routingSHIP verified JWT claimITD-010
Conformance evidenceSHIP live contract + security + close wire probesITD-011
Privacy / retentionSHIP bounded glue retentionITD-012
List endpointsSHIP canonical collectionsITD-013

ITD-001 · Product boundary: orchestration references only

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: AlphaTest stores only tenant-scoped administration policy, upstream identifiers, idempotency receipts, and reconciliation metadata; OneRoster, QTI, Results, Caliper, Content, and Analytics remain authoritative for their records.

Alternatives rejected: copy roster/session/result snapshots locally (faster reads, but creates conflicting facts); store an event-sourced shadow assessment (powerful, but rebuilds Platform3).

Why: It is the smallest boundary that can coordinate the job and makes ownership violations mechanically detectable.

ITD-002 · Write granularity: one administration per request

Active · SHIP/DEFERRED2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: SHIP per-administration create/update/provision-resume/close and per-candidate launch/advance/retake writes; DEFER bulk administration writes until one integrator must atomically schedule at least 100 distinct classes.

Alternatives rejected: Bulk-only writes; Both per-resource and bulk writes now; Defer close after its QTI outcome and Results seams became live.

Why: One resource maps to one school event, and the current wire contract supports every promised test-kind lifecycle without a duplicate bulk primitive.

ITD-003 · Read shape: list, detail, and candidates subcollection

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: ship tenant list and detail resources plus a separately authorized candidates subcollection; allow include=candidates only when the administration has at most 100 bindings, otherwise return 422 candidates-page-required and direct callers to the subcollection.

Alternatives rejected: list-only (cannot debug provisioning); always-embedded candidates (leaks sensitive session refs and scales poorly).

Why: AcmeTest gets one obvious navigation model without over-fetching learner-linked data.

ITD-004 · Query model: collection-specific opaque cursors

Active · SHIP/DEFERRED2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: the administrations collection supports exact status, testKind, qtiTestId, target sourcedId, modifiedSince, opaque cursor, and limit 1–100; modifiedSince reads order by modifiedAt,id ascending and browse reads by modifiedAt,id descending. The candidates subcollection supports state, opaque cursor, and limit 1–100 over immutable bindings ordered by createdAt,id ascending; each cursor advances over examined bindings even when a live QTI state filter removes rows. DEFER candidate modifiedSince until QTI exposes a monotonic runtime cursor.

Alternatives rejected: one ambiguous cursor contract for both collections (hides different authorities); arbitrary query language (larger security surface); offset paging (unstable under writes); fabricate candidate modified timestamps from reconciliation time (duplicates QTI state).

Why: Stable binding traversal gives complete enumeration without pretending QTI exposes a change feed.

ITD-005 · Concurrency: strong ETags on mutable glue

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: detail responses carry a strong ETag; PATCH requires If-Match and returns 412 on stale policy. QTI-derived candidate state is never updated through AlphaTest.

Alternatives rejected: last-write-wins (silently loses window/accommodation edits); ETags on composed runtime reads (pretends multiple upstream reads are atomic).

Why: Concurrency applies exactly where AlphaTest owns state.

ITD-006 · Idempotency: mandatory on exposed effectful POSTs

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: create, provision-resume, first launch, adaptive advance, retake, and close require Idempotency-Key (1–255 chars), scoped by tenant+method+path, retained 24 hours; identical replay returns the original accepted operation with Idempotent-Replayed: true, while a changed payload returns 409 idempotency-key-reused. Adaptive provisioning derives a stable mastery-engine child key from tenantId+administrationId+candidateSourcedId+attemptNumber+canonical start hash; close derives one stable Results child key per tenant+administration+QTI attempt.

Alternatives rejected: Optional keys; Dedupe from request body; One child key for the whole class; Random child keys on each resume.

Why: This follows the proven Stripe retry model and lets fixed and adaptive fan-out resume without duplicate QTI sessions, adaptive runs, or Results facts.

ITD-007 · Auth: verified Platform claims with narrowing scopes

Active · SHIP2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: require a verified Platform HS256 bearer JWT with sub, role, tenantId, iat, exp; map role to a published maximum permission set, allow an optional scope claim only to narrow that set, and apply optional school/class constraints. Standard five-claim reviewer tokens therefore authenticate without an undocumented scope requirement; candidate/session reads still require a role permission that includes sensitive administration reads.

Alternatives rejected: mandatory scope absent from the Platform token contract (rejects conforming callers); role-only without resource constraints (too coarse); API keys (lose principal claims); AlphaTest accounts (duplicate Platform auth).

Why: The contract accepts its declared Platform token shape while preserving least privilege.

ITD-008 · Eventing: polling + modifiedSince; no public webhooks in v1

Active · SHIP/DEFERRED2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: SHIP client polling of AlphaTest list/detail with modifiedSince; for candidate composition, read QTI runtime data once per persisted binding because QTI has no administration cursor. DEFER public webhooks until two external consumers require sub-30-second push updates and can supply signed-receiver operational ownership.

Alternatives rejected: pretend QTI has an upstream modified cursor (false contract); webhooks now (new delivery product); polling without an AlphaTest cursor (wasteful).

Why: AcmeTest gets deterministic polling without inventing an upstream API.

ITD-009 · Errors: typed RFC 9457 problem details

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: every non-2xx response is application/problem+json with stable HTTPS type, title, status, detail, instance, code, requestId, retryable, and field errors; dependency failures use 424 and name the dependency.

Alternatives rejected: bespoke JSON (weak tooling); pass-through upstream errors (unstable and may leak data).

Why: One machine-readable envelope makes failures actionable without coupling clients to upstream bodies.

ITD-010 · Tenant routing: JWT claim authoritative

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: route and scope all data by verified tenantId; optional X-Timeback-Tenant must match exactly and never selects a tenant.

Alternatives rejected: tenant in URL (caller-controlled and repetitive); header authoritative (spoofable if middleware regresses).

Why: Cross-tenant access requires defeating signature verification, not changing a path or header.

ITD-011 · Conformance evidence: contract suite + live upstream traces

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: publish an OpenAPI-driven contract suite, anonymous/cross-tenant probes, and live network/log evidence for every declared Platform3 capability; QTI references must dereference in the real runtime. Fixed-form proof replays create → launch → submitted QTI attempt → outcomeState.SCORE → Results create → exact ?qtiAttemptId= read-back → scored. Adaptive proof replays same-tenant create → per-candidate mastery start → native QTI offer → inspect/advance → Results KC acknowledgement, plus tenant-A-to-B and tenant-B-to-A denials and transient mastery-engine failure/resume. No adaptive implementation may release without an approved callable mastery_engine URL and immutable receipts for that exact trace.

Alternatives rejected: unit tests only; screenshots; manual certification; typed dependency failures as success; a health check without the business trace.

Why: The proof detects a locally convincing fake, enforces D5, and locks both customer journeys under P6.

ITD-012 · Privacy and retention: no public delete, bounded glue retention

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: no v1 DELETE endpoint; retain audit glue seven years by default, purge idempotency bodies after 24 hours and redacted operational logs after 30 days; upstream deletion/anonymization is reflected on composition.

Alternatives rejected: cascade delete (could destroy audit links or imply upstream deletion); forever retention (unnecessary exposure).

Why: Administration policy is audit evidence, while learner facts remain deletable by their owning systems.

ITD-013 · List endpoints: one collection, no write-and-remember

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: provide a canonical administrations collection and candidates subcollection; clients never need to persist returned IDs merely to rediscover resources.

Alternatives rejected: create/detail only (fragile write-and-remember); separate lists by test kind (three products).

Why: Resource discovery is a platform obligation and one list keeps the model coherent.

ITD-014 · Creation: asynchronous administration resource, no operations API

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: POST persists the administration then returns 202 + Location; that resource itself exposes provisioning progress and errors.

Alternatives rejected: synchronous fan-out (timeouts and ambiguous partial success); separate operation resources (extra IDs and polling concepts).

Why: It supports large classes while keeping a one-resource mental model.

ITD-015 · Candidate resolution: immutable candidate-attempt bindings

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: resolve the OneRoster target during provisioning and persist one immutable candidate-attempt binding. Fixed-form bindings contain tenantId, administrationId, candidateSourcedId, QTI candidate/session ref, selected form ref, and receipt. Adaptive bindings add only attemptNumber, caller-supplied same-tenant resultsStudentId, the opaque mastery_engine continuation JWE, its strong ETag and expiry, and current offered QTI deliverySessionId; Administration never decrypts the token and replaces token+ETag atomically after advance. Identity is reread from OneRoster, adaptive state from mastery_engine, and session/attempt state from QTI; no owner body, response, posterior, score, or mastery classification is stored.

Alternatives rejected: Persist a roster or mastery snapshot; Resolve only at launch; Make AcmeTest hold class fan-out continuations; Copy continuation claims into columns.

Why: These opaque references are the minimum restart-safe administration glue; all learner and assessment facts remain authoritative upstream.

ITD-016 · Runtime state: read-through QTI, never local transitions

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: derive not_started/in_progress/submitted/scored from QTI runtime data on read or reconciliation; local lifecycle describes orchestration only.

Alternatives rejected: event-mirrored candidate states (event loss creates lies); client-reported state (untrusted).

Why: One authoritative state machine eliminates reconciliation ambiguity.

ITD-017 · Retakes: state-guarded deterministic form rotation

Active · SHIP2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: accept a retake only while the administration lifecycle is open and the candidate's authoritative QTI state is submitted or scored; otherwise return 409 administration-state-conflict. For mastery gates, exclude prior forms using QTI attempt history, select the least-used eligible parallel form, stable-hash ties, and return 409 parallel-forms-exhausted when policy or inventory is exhausted.

Alternatives rejected: create a retake before submission (parallel active attempts); random choice (non-replayable); local attempt history (duplicates QTI); silent form reuse (violates gate integrity).

Why: The guard prevents overlapping attempts, and deterministic selection guarantees a different form when promised.

ITD-018 · Accommodations: reject every non-default policy until QTI exposes it

Active · SHIP REJECTION / DEFERRED ENFORCEMENT2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: DEFER non-default accommodations and any claim that QTI applies them. V1 accepts only an absent accommodations object or the semantic no-op {default:{timeMultiplier:1},candidateOverrides:[]}; every non-default request returns 422 qti-accommodation-capability-required. Reopen only when authenticated wire probes prove QTI delivery-session creation accepts a documented accommodation payload and a pre-start update operation mutates it.

Alternatives rejected: store unenforced overrides as if applied (misleading); copy learner preference profiles (privacy/ownership violation); emulate accommodations in AlphaTest or the browser (creates a second delivery runtime).

Why: A rejected unsupported policy is safer than certifying an accommodation the delivery runtime ignores.

ITD-019 · Timing: original non-enforcing policy

Superseded2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: ITD-031

Chosen: SHIP opensAt/closesAt as RFC 3339 UTC scheduling and audit metadata and timeLimitSeconds as nominal policy, but DEFER any claim that QTI prevents early/late starts, terminates delivery, or enforces duration. PATCH may change this AlphaTest-owned metadata only before every bound candidate remains not_started and never claims a QTI mutation. Reopen runtime enforcement only when authenticated probes prove QTI session creation accepts notBefore, notAfter, and effectiveDurationSeconds and a documented pre-start update operation mutates those values.

Alternatives rejected: claim enforcement from an active session reference (false); browser-only timers (bypassable); proxy the QTI player through AlphaTest (rebuilds delivery); omit scheduling metadata (removes needed event planning and audit context).

Why: The contract preserved useful scheduling policy while making the then-known runtime limitation explicit. ITD-031 later narrowed the claim and added administration-boundary launch authorization.

ITD-020 · Close and write-back: original wire-gated deferral

Superseded2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: ITD-030

Chosen: DEFER POST /close, QTI finalization, and Results/Caliper write-back. Reopen only when live authenticated probes prove both (1) a QTI administration finalize/score command with idempotent correlation and (2) a reports-owned idempotent attempt-correlation write contract returning a dereferenceable Results reference and Caliper receipt. Until then the lifecycle ends at window_ended, which makes no scoring or write-back claim.

Alternatives rejected: always return 424 (an unusable advertised endpoint); treat window expiry as scored close (false evidence); direct administration Results writes (ownership conflict); local scoring (rebuilds QTI).

Why: The deferral was correct on 15 July; accepted triage decision 2026-07-17-004 supplied the missing live QTI outcome and Results write/read seams, so ITD-030 supersedes it.

ITD-021 · Test kinds: original deferred adaptive branch

Superseded2026-07-16Author: alphatest-loopSupersedes: noneSuperseded by: ITD-032

Chosen: testKind recognized adaptive_diagnostic but returned 422 before persistence.

Alternatives rejected: separate APIs; infer kind; generic QTI session.

Why: Preserved as history only. It contradicted the promised adaptive product and is replaced by the complete contract in ITD-032.

ITD-022 · Upstream bindings: original adaptive execution deferral

Superseded2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: ITD-035

Chosen: the old decision deferred the Administration adaptive binding because no callable mastery-engine implementation was supplied.

Alternatives rejected: local substitute; guessed alias; fictional credential exchange.

Why: ITD-035 preserves the evidence gate without deleting the customer contract.

ITD-023 · Versioning: path-major v1 and additive evolution

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: major version in path; additive optional fields and enum values require tolerant readers, while removals/semantic breaks require a new major and published overlap window.

Alternatives rejected: date/header versions (harder cold-start and caches); unversioned API (unsafe evolution).

Why: The version is visible in every curl and easy for an integration LLM to preserve.

ITD-024 · Rate and dependency pressure: bounded fan-out with observable partial progress

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: provision and reconcile with bounded concurrency, exponential backoff plus jitter for retryable upstream failures, honor Retry-After, and expose counts/errors on the administration without claiming completion.

Alternatives rejected: unbounded Promise fan-out (dependency outage amplifier); all-or-nothing rollback (cannot uncreate unknown upstream effects safely).

Why: Idempotent resumability is safer than distributed rollback across systems AlphaTest does not own.

ITD-025 · Observability: correlation without learner payloads

Active2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: propagate a request/correlation ID to each upstream, log tenant-hashed administration and upstream reference hashes, dependency latency/status, and reconciliation counts; never log JWTs, response bodies, names, raw sourcedIds, answers, or scores.

Alternatives rejected: full bodies for debugging (privacy/secrets risk); no correlation (integration failures unauditable).

Why: Wire reality and incident diagnosis remain provable without creating another learner-data store.

ITD-026 · Target model: class or explicit OneRoster candidate list

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: SHIP a required discriminated target: either {type:"class", oneRosterSourcedId} or {type:"candidate_list", oneRosterCandidateSourcedIds} with 1–1000 unique IDs; provisioning tenant-authorizes and resolves every ID live in OneRoster and rejects the whole request on any missing, inactive, duplicate, or unauthorized candidate. Fixed-form provisioning creates one QTI session per candidate; adaptive provisioning calls mastery_engine once per candidate and accepts only its native QTI offer. Both retain only the immutable binding defined by ITD-015.

Alternatives rejected: Class-only targeting; A locally named or saved candidate-list resource; Partial acceptance; Names or emails as identifiers.

Why: AcmeTest can assign either promised target while OneRoster remains identity authority and AlphaTest retains only the minimum event-membership correlation it owns.

ITD-027 · Product hosting: one public origin, preserved documentation routes

Active · SHIP2026-07-15Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: SHIP one unauthenticated documentation origin whose root, /architecture, and /reference routes remain present in every later deliverable bundle, while authenticated API routes live under /api; a deployment must fail smoke rather than replace a previously published documentation route or require Vercel SSO.

Alternatives rejected: one disposable alias per cell (clobbers approved contracts); SSO-protected customer docs (not a public integration contract); disconnected module URLs (violates one-product navigation).

Why: Stable public anchors are part of the contract, and later code deployment must not erase the specification it implements.

ITD-028 · Close release: original conjunctive gate

Superseded2026-07-16Author: alphatest-loopSupersedes: noneSuperseded by: ITD-030

Chosen: DEFER the close route itself until one immutable release candidate proves all close gates together: an authenticated QTI finalize/score command with deterministic idempotent correlation; Reports Gates B, C, and D for Results components, retry-safe Caliper emission, exact attempt-to-result composition, and bidirectional two-tenant isolation; and a forced partial-failure replay that produces no duplicate result, component, event, or close receipt. While any gate is closed, POST /v1/administrations/{id}/close is absent and no generic 404, 424, or 503 is documented as a shipped close behavior.

Alternatives rejected: publish a permanently failing close stub (structural theater); release after a QTI-only probe (no durable outcomes); release after a Reports-only probe (no authoritative finalization); accept one-tenant success (does not prove D2); implement direct Results/Caliper writes in administration (violates ownership).

Why: The original gate prevented a cosmetic close. Accepted triage decision 2026-07-17-004 proved the narrower authoritative seams the administration job needs and replaces this over-broad gate with ITD-030.

ITD-029 · Original deferred adaptive orchestration

Superseded2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: ITD-032, ITD-033, ITD-034, ITD-035

Chosen: the old decision deferred adaptive create and retake and returned adaptive-administration-contract-unavailable before effects.

Alternatives rejected: local fake; generic QTI session; stored mastery state.

Why: Preserved as audit history; the downstream rollback proved that a release gate cannot replace the promised product contract.

ITD-030 · Close: QTI score read and exact Results write-back SHIP

Active · SHIP2026-07-17Author: alphatest-triage+2026-07-17-004Supersedes: ITD-020, ITD-028Superseded by: none

Chosen: SHIP POST /v1/administrations/{id}/close for fixed-form administrations. Using only the server-side provisioned PLATFORM3_TENANT/PLATFORM3_JWT credential, close stops new launch and retake authorization; requires every launched candidate's latest QTI attempt to be submitted or scored; reads the authoritative QTI outcomeState.SCORE; sends that outcome to POST /alpha/results/v1/result-records with the complete typed reference set administration_id, qti_attempt_id, qti_session_id, qti_test_id, qti_artifact_version_id, content_test_spec_id, test_kind, and subject; verifies exactly one settled record through GET /alpha/results/v1/result-records?qtiAttemptId={qtiAttemptId} carrying the same administration_id and reference set; and advances the administration to scored only after every candidate write verifies. Close is asynchronous, requires Idempotency-Key, derives a stable child key per QTI attempt, and resumes partial failure without duplicating Results facts.

Alternatives rejected: retain the old QTI-finalize and Reports-gate deferral (stale after current wire proof and blocks the module's defining job); compute scores locally (rebuilds QTI); copy outcome bodies locally (rebuilds Results); mark scored before exact read-back (can acknowledge a lost or mis-correlated write); require Caliper/report materialization to complete administration close (crosses ownership and makes close depend on unrelated report release gates).

Why: Accepted triage decision 2026-07-17-004 records both required seams live on the wire and makes the proof metric falsifiable: create → candidate delivery ref → submitted QTI attempt → close → exact Results lookup carrying this administration id → administration reads scored.

ITD-031 · Runtime policy boundary: SHIP launch-window authorization; DEFER duration enforcement

Active · SHIP/DEFERRED2026-07-17Author: alphatest-triage+2026-07-17-004Supersedes: ITD-019Superseded by: none

Chosen: SHIP opensAt/closesAt enforcement at AlphaTest's launch-authorization boundary by withholding the bound deliverySessionRef before or after the window; keep timeLimitSeconds explicitly nominal and DEFER any claim that QTI terminates delivery or enforces duration. Reopen duration enforcement only when authenticated probes prove QTI session create/update accepts and applies not-before, not-after, and effective-duration policy.

Alternatives rejected: reveal sessions before the window (makes AlphaTest's own policy meaningless); claim QTI enforcement from metadata it never receives (false); browser-only timers (bypassable); proxy or replace the QTI player (rebuilds delivery).

Why: AlphaTest gates the access it controls while making the limits after session disclosure impossible to miss; ITD-018 separately rejects all non-default accommodations.

ITD-032 · Test kinds: complete adaptive create contract SHIP

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: ITD-021, ITD-029Superseded by: none

Chosen: SHIP mastery_gate, formative, and adaptive_diagnostic through the same administration resource selected by required testKind. Adaptive create requires adaptiveConfig with selectionPolicyId, scaleCalibrationId, testBankId, bankOperationId, runMode=production_learner, and an exact resultsStudentBindings entry for every resolved OneRoster candidate; testSpecId is test.contentTestSpecId. A missing, duplicate, extra, or cross-tenant binding returns 422 adaptive-capability-required before persistence or upstream effects. A valid request returns 202 and provisions one mastery_engine run per candidate asynchronously.

Alternatives rejected: Separate adaptive API; Infer kind from test ID; Keep adaptive as a permanently failing reserved enum; Create a generic QTI session instead of a mastery run.

Why: One request field selects the promised product kind while an explicit configuration block makes every mastery start input checkable before fan-out.

ITD-033 · Adaptive identity, offer ownership, and continuation binding

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: ITD-029Superseded by: none

Chosen: For each adaptive candidate attempt, Administration calls mastery_engine POST /v1/adaptive-runs with the six request-level pins plus that candidate's validated resultsStudentId, using a deterministic child Idempotency-Key. mastery_engine alone creates the first and subsequent native QTI sessions. Administration binds tenantId+administrationId+candidateSourcedId+attemptNumber to the returned opaque continuationToken, strong ETag, expiry, and offeredItem.deliverySessionId; launch returns only the current native QTI reference, and advance calls mastery_engine :advance with the stored token and If-Match then atomically replaces token+ETag+offer. It stores no decrypted token claim, response, posterior, score, or mastery state.

Alternatives rejected: Let Administration create a generic first QTI session; Make AcmeTest coordinate every class continuation; Persist decrypted continuation claims or mastery state; Infer Results identity from OneRoster; Reuse a continuation for a retake.

Why: The binding is restart-safe orchestration glue while mastery_engine, QTI, OneRoster, and Results retain their declared ownership.

ITD-034 · Adaptive fan-out, retries, failures, and retakes

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: ITD-029Superseded by: none

Chosen: Adaptive provisioning is per-candidate and resumable, never distributed all-or-nothing. Validation failure returns 422 adaptive-capability-required before persistence. After 202 acceptance, a mastery_engine transport, 5xx, or invalid dependency response marks only that candidate attempt provisioning_failed with retryable dependency metadata and surfaces 424 mastery-engine-unavailable on synchronous provision/advance calls; the administration remains provisioning_with_errors and POST /v1/administrations/{id}/provision retries only failed candidates with the original deterministic child keys. Expired continuation returns 409 adaptive-continuation-expired and requires a policy-permitted retake, which starts attemptNumber+1 with a fresh child key and continuation. Cross-tenant resources and continuations return the same 404 resource-not-found in both directions, before effects.

Alternatives rejected: Rollback successful candidate runs; Retry every candidate; Hide partial failure behind parent success; Reuse an expired continuation; Expose tenant mismatch as 403.

Why: Per-candidate effects can be safely resumed without duplicating runs or revealing tenant existence, and retakes are new diagnostics rather than corrupted continuations.

ITD-035 · Adaptive release: contract SHIP, production evidence mandatory

Active · SHIP CONTRACT / RELEASE GATED2026-07-17Author: alphatest-loopSupersedes: ITD-022, ITD-029Superseded by: none

Chosen: bind Content, OneRoster, QTI, Results, Reports, Caliper, and mastery_engine only through explicit UPSTREAM_* base URLs backed by approved callable contracts and deployment probes; authenticate Platform3 calls with the server-selected credential profile whose tenant equals the verified JWT tenantId. The adaptive API contract is SHIP, but its implementation release is fail-closed: no deployment may advertise adaptive success until an approved callable mastery_engine immutable URL proves authenticated start/inspect/advance, native QTI session creation, Results KC acknowledgement, same-tenant success, bidirectional cross-tenant denial, and transient-unavailable behavior. Repository-local code, fixtures, health responses, guessed aliases, or typed permanent-failure stubs are not evidence.

Alternatives rejected: Treat approved design as runtime evidence; Require a nonexistent workload-token exchange; Use repository-local code or fixtures as an upstream; Release a permanent 422/424 adaptive stub; Generic proxy.

Why: Architecture completeness and production evidence are separate gates: the contract is implementable now, while D5 prevents a false release before the real owner is callable.

ITD-036 · Adaptive public state and close composition

Active · SHIP2026-07-17Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: Adaptive candidates preserve the public four-state contract by composition: not_started before the first offered QTI session is launched; in_progress after launch while mastery_engine reports in_progress; submitted while mastery_engine reports finalizing with no offered item; and scored only when inspect reports completed with reportable scale output and acknowledged Results KC components. POST /v1/administrations/{id}/close rejects 409 close-not-ready while any launched adaptive attempt is not scored, then stops new launches/retakes, inspect-verifies every completed run and Results reference, and marks the parent scored without copying the scale score, KC map, continuation contents, or Results body.

Alternatives rejected: Add a fifth public adaptive state; Treat finalizing as scored; Copy the mastery report into Administration; Close incomplete runs.

Why: AcmeTest keeps one state vocabulary across test kinds while mastery_engine and Results remain the only authorities for adaptive completion and learning facts.

ITD-037 · Fixed-gate assignability: authoritative Content handshake

Active · SHIP2026-07-20Author: alphatest-loopSupersedes: noneSuperseded by: none

Chosen: Before persisting an administration or touching OneRoster or QTI, fixed mastery-gate create MUST read the requested Content test_spec and test_bank in the verified JWT tenant and accept only when the bank is status=published, is_mastery_gate=true, membership_rule=same_blueprint_fixed_forms, spec_id exactly equals the requested contentTestSpecId, and its exact ordered member set is non-empty and every immutable QTI artifact version dereferences. A Bank operation's ready or assignable flag is correlation evidence only and cannot override Content. Any mismatch returns 422 test-not-assignable before effects. Bank may declare the output ready and assignable only after authoritative Content read-back proves this same predicate and a same-tenant Administration create contract probe accepts the returned native IDs.

Alternatives rejected: Trust Bank workflow state without Content read-back; relax draft or null metadata in Administration; infer spec or mastery-gate status from titles; patch Content from Administration; accept unordered or partially dereferenceable members.

Why: One producer-consumer predicate prevents a locally green Bank operation from producing an unusable assignment while Content and QTI remain the owners of assignability facts.

Requirement and decision traceability

PromisePinned byLater proof
Create by form/blueprint, class or explicit candidate list, launch window, nominal time limit, retakes, and accommodation policyITD-014, 015, 018 DEFERRED, 026, 031 SHIP/DEFERRED; 019 preserved as superseded historyTarget, retake, before/in/after-window, and non-default-accommodation rejection tests now; authenticated QTI probes before duration or accommodation enforcement is claimed
Issue QTI delivery-session referencesITD-015, 033 SHIP, 035 release gateDereference every fixed-form session and every mastery-engine-created adaptive offer in Platform3 QTI
Accept only an actually assignable fixed mastery gateITD-037 SHIPBank Content read-back receipt plus same-tenant Administration create using the exact returned bank/spec/member IDs; mismatches remain pre-effect 422
Track four candidate states from QTIITD-016Runtime-data trace matches API state
Close and verify owner-written outcomes without local learning factsITD-001, ITD-030 fixed-form SHIP, ITD-036 adaptive SHIP; 020/028 supersededFixed P6: QTI SCORE → Results create/exact read. Adaptive P6: completed/reportable inspect → acknowledged Results KC refs → close; neither path stores outcomes locally.
Secure datastore and tenant isolationITD-007, 010, 012Anonymous rejection and tenant A/B probes
All test kinds through one selectorITD-032 SHIP; 021/029 supersededAdaptive create/provision/launch/advance/retake/close contract suite plus immutable D5 owner-backed trace
Every standard API decision axis pinnedITD-002ITD-013Architecture axis lint
Stable public contract survives later deploysITD-027Unauthenticated root, /architecture, /reference smoke on every bundle

External references studied