Platform3 · Canonical architecture

Results Alpha Architecture

This page is rendered directly from the committed architecture decisions. Each stable section anchor is part of the public traceability contract.

Source-bound decision record

40 committed decisions · results/alpha Source generated 2026-06-12T16:00:00Z

#overview

results served audiences

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

students, parents, teachers, guides, administrators, and app-builder LLMs reading or writing durable statements about how one specific student is doing

Alternatives and tradeoffs

  • 1EdTech-only gradebook integrators
  • NWEA CDF import operators only
  • raw event-stream analysts

Rationale and consequences

Results Alpha is the school-language record plus student knowledge graph, derived from OneRoster, NWEAMap, and Ed-Fi Student Academic Record upstreams and extended for Alpha outcome reporting.

#ritd-000-alpha-storage-model

results alpha storage model

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Renamed, cut, and restricted Results objects are SQL views over the approved OneRoster, NWEAMap, and Ed-Fi 1EdTech base storage. Only true Alpha extensions get storage, and extension tables contain only new Alpha columns plus foreign-key references to base rows. The public result, MAP, and transcript objects are views that project base rows and join or UNION extension rows; writes to renamed/core facts route to the 1EdTech base surface.

Alternatives and tradeoffs

  • Create Alpha-owned copies of OneRoster, NWEAMap, and Ed-Fi result tables
  • Publish a standards-only read surface with no Alpha outcome extensions
  • Use metadata bags for Alpha-only fields

Rationale and consequences

Views are the smallest way to rename, cut, and restrict without moving data. Extension tables isolate genuinely new Alpha facts and make the no-copied-1EdTech-field gate machine-checkable.

#ritd-001-results-boundary-alias-map

results results boundary alias map

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Results is the Alpha module for durable, settled statements about one student: outcome records, gradebook/report-card statements, gate passes, MAP scores, academic records/transcripts, working grade, named grade movement, XP awards, content effectiveness, student track state, four-state KC state, and next-task selection. Grades mastered is the canonical grade-movement answer; generic advancement is retained only as its documented deprecated mastery-pure alias. Publish the alias map as both page content and results-alpha-alias-map.json, including the storage class for each name: base view, public union/read view, or extension table.

Alternatives and tradeoffs

  • Expose OneRoster and NWEA names directly
  • Create separate MAP, gradebook, and mastery modules
  • Use plain object names without a generated alias artifact

Rationale and consequences

The alias map is the smallest way to be plain-spoken without moving data or hiding provenance. Adding storage class makes the new no-copy rule enforceable before implementation.

#ritd-002-gradebook-result-boundary

results gradebook result boundary

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Rename OneRoster lineItems to gradebook_item and OneRoster results to result_record for section gradebook work through SQL views. Keep score-scale joins as explicit score_scale view bindings and reject persisted scored results that cannot resolve to exactly one same-tenant effective scale.

Alternatives and tradeoffs

  • Collapse lineItem and result into one Alpha grade row
  • Expose only OneRoster names
  • Resolve score scales only when reports are generated

Rationale and consequences

A plain view alias over the existing OneRoster objects removes jargon without adding storage. Keeping score scales explicit inherits OneRoster's approved score-scale enforcement and prevents ambiguous grade math.

#ritd-003-canonical-result-shape

results canonical result shape

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.result_record as a public SQL view. Its base branch projects OneRoster results; its extension branch UNIONs Alpha-owned non-section outcome rows from alpha.result_record_extension. Every producer writes through a named adapter or scorer into the same canonical shape: student FK, result_kind, subject, canonical_term_id or occurred_on, content FK when applicable, test_family FK when applicable, score evidence, QTI EAP5/EAP6 canonical_response plus isCorrect when item evidence exists, source_adapter_id, producer_result_id, and source_evidence.

Alternatives and tradeoffs

  • Only support OneRoster results
  • Let each app store its own outcome shape
  • Attach non-section outcomes as metadata on OneRoster results

Rationale and consequences

One canonical public view removes client-side half-mapping while preserving source evidence. The extension table is necessary because OneRoster gradebook rows do not cover self-paced or external outcome records, but the table still stores no copied OneRoster columns.

#ritd-037-test-result-component-hierarchy

results test result component hierarchy

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-22

Decision

Expose alpha.test_result_component as the Results-owned hierarchy for a stored assessment result. Rows are keyed to result_record and linked by parent_test_result_component_id, with component_role values test, section, part, and question_response. Question-response rows carry response_identifier, response_value, score, score_percent, max_score, is_correct, dok, kc_refs, feedback, rubric_score, source_adapter_id, producer_result_id, and source_evidence. result_record.canonical_response remains audit/source evidence; it is not the public hierarchy consumers parse.

Alternatives and tradeoffs

  • Leave item evidence inside result_record.canonical_response JSON
  • Route consumers to QTI attempt/runtime state
  • Join the result to class, course, org, or event structures
  • Store the hierarchy on Content/QTI

Rationale and consequences

A small extension table gives students and agents a plain hierarchy while preserving result_record as the canonical outcome row. It removes JSON parsing and QTI replay from clients, keeps Content as the blank-instrument owner, and lets imports materialize the same readable hierarchy from source-shaped producer data.

#ritd-038-ai-grading-audit

results ai grading audit

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-22

Decision

Expose alpha.ai_grading_audit as the Results-owned audit log for AI-produced assessment feedback. Rows are keyed to result_record and may link to a test_result_component for question/question-part feedback. Each row carries grading_stage values overall, question, or question_part; grading_status values proposed, accepted, superseded, or overridden; AI provider/model/version; grader_ref; prompt_ref; prompt_hash; input_evidence_refs; output_feedback; output_json; score, score_percent, rubric_score, confidence; revision_number; supersedes_ai_grading_audit_id; human_override_by; human_override_reason; graded_at; and modified_at. The demo/customer proof must include at least one non-null human override row with a supersession pointer so clients can validate the override lifecycle through the public Results endpoint. result_record.canonical_response remains source evidence, not the public AI grading audit.

Alternatives and tradeoffs

  • Use compact payload only
  • Rely on operations telemetry
  • Move it to the blank-instrument owner
  • Use a metadata bag

Rationale and consequences

A typed audit extension makes AI feedback inspectable, revision-safe, and student/result scoped. It gives customers a plain answer to who/what graded the test and what changed, while keeping model policy and blank instrument ownership outside Results.

#ritd-004-student-knowledge-graph

results student knowledge graph

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Create alpha.student_kc_state as an Alpha extension table for the per-student overlay on Curriculum KC nodes. It stores foreign-key references to the student and KC plus four named Alpha state fields on the same row: initial_mastery, current_retention, durable_mastery, and fluency_state. It also carries next_due_at, evidence_count, last_evidence_at, policy_version, retention_model_ref, effective_at, and source result links. AP staged acquisition exposes E1-E4 through fluency_state (not_practiced, building_accuracy, building_speed, fluent) under the active policy_config row. The retention model is Policy-owned, FIRe-style by default; consumers see next_due_at, retention_model_ref, policy_version, and the four named states, never model internals. It is derived and time-decaying, but Results is the system of record for the materialized state.

Alternatives and tradeoffs

  • Put mastery fields on the student record
  • Keep only raw result evidence
  • Publish one blended mastery_state field
  • Publish nightly mastery snapshots outside the surface

Rationale and consequences

The shared curriculum graph and the student's mastery overlay are different graphs. One row per student and KC with four named states is the smallest model that keeps acquisition, retention, durable mastery, and fluency distinct without creating four parallel tables or asking clients to blend/decay evidence.

#ritd-032-student-track-state

results student track state

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Create alpha.student_track_state as an Alpha extension table with one row per student and track. It stores student_id, track_id, current_track_level_id, current_segment_id, placement_evidence, status, switched_to_track_id, begin_date, end_date, is_time_locatable, effective_at, and source evidence refs. Switching tracks is a status change on the old row plus a new state row; the track, levels, segments, courses, and components remain Curriculum objects and are never copied into Results.

Alternatives and tradeoffs

  • Store current track fields on the student record
  • Copy the track and course tree into each student state
  • Infer track position from latest activity events
  • Allow many active rows per student and subject

Rationale and consequences

One state row per student and track is the smallest object that records the student's position while preserving Curriculum as the home for track definitions. Status transitions preserve switch history without copying the old track.

#ritd-036-course-grade-progress

results course grade progress

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-16

Decision

Create alpha.student_course_progress and alpha.student_grade_level_progress as Results-owned student-facing read models. Course progress answers xp_remaining, total_xp, percent_complete, progress_source_kind, evidence_kind, policy_ref, and source refs for one student/course/asOfDate. Grade-level progress answers grade_level_xp_remaining, main_sequence_total_xp, percent_complete, elapsed_school_year_percent, pacing_status, active_path_state, policy_ref, and source refs for one student/subject/grade/asOfDate. Analytics completion and XP rollups are evidence, Curriculum supplies main-course membership and expected-XP denominators, Events preserves app-reported progress, and People & Orgs supplies enrollment/calendar context. Consumers call Results and do not rebuild progress from those modules.

Alternatives and tradeoffs

  • Make Analytics the consumer progress surface
  • Make Curriculum expose per-student progress
  • Let the GOALS app compute progress locally
  • Use MAP/RIT/R90 placement band

Rationale and consequences

Progress is a student-facing state answer over multiple evidence sources. Results is already the system of record for one student's settled outcomes and state, so a Results read model gives every client one contract while keeping source ownership intact.

#ritd-034-next-task-selection

results next task selection

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Results owns next-task selection as one surface call returning a typed task ref. The call reads student_kc_state, student_track_state, gate_pass, Curriculum frontier/course_component_member and placement-candidate inputs, and Policy. It returns task_type, task_ref, reason, due_at, source_state_refs, policy_version, and null_reason when no task is available. Valid task types are new_lesson, review, practice, gate_retry, remediation, external_course_task, placement, test_out, and no_task. The Policy payload ranks placement and test_out before ordinary gate retry/review/practice/frontier lesson decisions, includes AP interleaving constraints, review:new ratio targets by ability band, and stage retry reason values including gate_retry_stage_e1, gate_retry_stage_e2, gate_retry_stage_e3, gate_retry_stage_e4_next_day, placement_required, placement_candidate_ready, and test_out_ready. POST /alpha/results/v1/imports accepts sourceShape=results_next_task_decision so production migrations can seed alpha.next_task decisions from PowerPath state while Results remains the one Jump In decision source. Apps render the task; they never pick it or rank tasks.

Alternatives and tradeoffs

  • Let each app choose from raw mastery, gate, and curriculum rows
  • Make Curriculum the scheduler
  • Make Events the scheduler
  • Expose only ranked choices without a typed reason

Rationale and consequences

The next task is a decision over one student's Results state plus the shared Curriculum map. One typed call removes client schedulers while keeping Curriculum and Policy as referenced inputs.

#ritd-033-xp-ledger

results xp ledger

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Create alpha.xp_ledger as an immutable Alpha extension ledger for awarded XP. A ledger row is computed by Results per settled attempt from Content expected_xp or a documented raw-import expected_xp_ref basis, accuracy, effort signals, repeat-attempt discount, passive-activity verification, and gaming/cheating penalties. Every accepted public-native result row exposes a typed XP disposition. Any of the five public result kinds with outcome_status=fully_graded, numeric score_percent, content_id, source_adapter_id, producer_result_id, and Content/Curriculum expected_xp atomically writes exactly one verified_attempt award, calculated as round(expected_xp * clamp(score_percent, 0, 100) / 100) and deduplicated by tenant + adapter + producer result. Adapter registration is audit/discovery, not an XP allowlist, and enrollment/track state is not required; unmet evidence is an accepted typed non-award. The row stores student_id, result_record_id, content_id, expected_xp_ref, awarded_xp, award_reason, calculation_policy_ref, source_evidence_refs, created_at, and reversal_of_xp_award_id when a correction is needed. Raw TimeBack imports distinguish expected_xp_ref.source values content_expected_xp, source_expected_xp, source_xp, and adapter_default; adapter_default is allowed only under alpha.policy.results.raw_timeback_import_expected_xp_default. Mastery gates do not mutate XP directly; events do not become canonical XP awards; engines do not write XP; apps display XP and ledger history.

Alternatives and tradeoffs

  • Activity-log points
  • Engine-owned rewards
  • Mutable student balance
  • Client-side reward math

Rationale and consequences

XP is reward history, so it needs ledger semantics: append-only awards, evidence, policy provenance, and explicit reversals. Computing it once in Results from settled attempts keeps Content expected_xp, documented raw-import expected_xp evidence/defaults, and Events effort signals as inputs without letting any of those sources own the award.

#ritd-005-policy-config

results policy config

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Store Results policy as named alpha.policy.* configuration read by the surface: mastery_cutoff, retention_model, next_task, stuck_attempt_threshold, gate_retake_cooldown, proxy_subject_rules, xp_award_formula, repeat_attempt_discount, effort_signal_weights, growth_x_target, school_year_boundaries, tenure_buckets, RIT calculator versions, 99_level_thresholds, reward_rules, and mastery_decay models. AP pedagogy is a versioned policy pack: mastery_cutoff carries the grade-level mastery gate plus E1-E4 staged-acquisition gates at >=80%, DOK1-2 core >=90%, DOK3 transfer >=70%, next-calendar-day failed mastery-test retry timing, and KC graph consistency over alpha.student_kc_state; retention_model exposes spacing_fraction keys, per-subject overrides, and latency_adjustment while hiding FIRe internals; next_task exposes interleaving constraints and review_new_ratio_targets.

Alternatives and tradeoffs

  • Hardcode policy values in reports
  • Store policy values as tags
  • Read policy values from deployment environment variables

Rationale and consequences

These values are policy, not data facts. A single config registry prevents magic numbers and keeps skill packs thin.

#ritd-006-map-identity-cohort

results map identity cohort

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.map_score as a public view over nweamap.assessment_result_projection joined to Alpha extension sidecars. The view shows TimeBack student identity and cohort dimensions through People & Orgs joins, cuts unused NWEA pass-through fields from the public shape, and keeps raw NWEA identity only in source_evidence and authorized all-sittings audit reads.

Alternatives and tradeoffs

  • Pass through every NWEA field
  • Replace NWEA identity everywhere
  • Use student email as the only public MAP identity

Rationale and consequences

Identity swap by view is the smallest Alpha change that makes MAP reports buildable by students, teachers, and their LLMs while retaining NWEA provenance and avoiding copied NWEA fields.

#ritd-007-map-test-of-record

results map test of record

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

alpha.map_score defaults to a view over the NWEAMap test_of_record projection. alpha.map_sitting is an authorized audit view over all NWEAMap sittings, including retakes and soft-deleted rows, only when the caller asks for audit scope.

Alternatives and tradeoffs

  • Expose all sittings and require client dedupe
  • Use GrowthMeasureYN as official
  • Expose separate current-score and retake-history tables

Rationale and consequences

The upstream NWEAMap surface already owns dedupe through sittingScope. Results should consume that decision through views, not create a second dedupe rule, copied table, or client choice.

#ritd-008-map-goal-strands

results map goal strands

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.map_goal_strand as a SQL view that unpivots NWEA Goal1 through Goal8 fields from nweamap.raw_assessment_results and joins the test-of-record projection. It returns one row per student, subject, term, goal_strand, and norms_set with strand_rit, strand_standard_error, strand_range, strand_adjective when available, overall_rit, and view-derived goal_gap = strand_rit - overall_rit.

Alternatives and tradeoffs

  • Leave NWEA goal columns wide
  • Expose only overall MAP score
  • Expose named goal columns plus a helper endpoint

Rationale and consequences

GAP-6 says the clean shape plus goal_gap is the platform-owned gap fill. A view gives Alpha the report-ready form without storing copied GoalN fields.

#ritd-009-growth-x-target

results growth x target

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Store Growth X as Alpha extension storage keyed to the NWEAMap projection row, subject, term, growth_window, and norms_set. Store growth_x_target from alpha.policy.growth_x_target, default 2.0, plus on_track_for_growth_x_target and projected_gap fields. Public MAP growth reads are views joining NWEAMap source growth fields to the Alpha extension.

Alternatives and tradeoffs

  • Compute Growth X in dashboards
  • Store only target flags
  • Store Growth X as metadata on the MAP score

Rationale and consequences

Growth X is Alpha's core outcome signal and NWEA does not publish it. First-class extension storage keeps the promise visible without copying NWEA observed/projected source fields into Alpha tables.

#ritd-010-norms-discipline

results norms discipline

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Every MAP observation, calculator output, Growth X row, effective-grade row, and longitudinal point carries norms_set as a typed public field. Growth windows are canonical enum rows, not NWEA column-name switches. Public reads accept normsSet=2020|2025 and growthWindow as typed parameters; extension tables store only Alpha calculator norms/config facts, while NWEA NormsReferenceData remains a base-source view field.

Alternatives and tradeoffs

  • Use NWEA NormsReferenceData pass-through only
  • Ship client norms tables
  • Infer norms and growth windows from term names

Rationale and consequences

Norms and growth windows are computed inputs. Making them typed fields and parameters removes term parsing, column switches, and silent mixed-norm corruption.

#ritd-011-rit-effective-grade

results rit effective grade

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.map_effective_grade as a public view over NWEAMap RIT observations joined to alpha.map_effective_grade_extension. The extension stores rit50_grade, rit90_grade, effective_grade, alpha_minimum_rit, alpha_predicted_rit, norms_set, calculator_version, and provenance flags distinguishing NWEA-published points from Alpha interpolation/extrapolation.

Alternatives and tradeoffs

  • Expose raw RIT only
  • Bundle calculator code in the skill pack
  • Expose calculator inputs and require a separate calculator tool

Rationale and consequences

RIT50 is grounded in NWEA-published data where available; RIT90 and predicted/minimum RIT are Alpha extensions. Publishing both with provenance is simpler than hiding extrapolation or outsourcing it to clients, and a sidecar table avoids copying NWEA RIT source fields.

#ritd-012-longitudinal-series

results longitudinal series

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.map_longitudinal_point as ordered per-student, per-subject time series rows from a view over NWEAMap term observations plus Alpha growth extension fields. The public row includes canonical_term_id, term_order, rit, observed_growth, typical_growth, Growth X, norms_set, and timeback_era flag.

Alternatives and tradeoffs

  • Return only current term score
  • Let reports sort terms and compute deltas
  • Expose raw term history without derived growth fields

Rationale and consequences

Longitudinal growth is a report primitive, not just a list of tests. A Results-owned view makes acceptance reports reproducible by surface calls only without storing copied NWEA term history.

#ritd-013-ninety-nine-levels

results ninety nine levels

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.map_99_level per student, subject, term, and norms_set as a view over NWEAMap percentile/RIT observations joined to alpha.map_99_level_extension. The extension stores threshold_99_rit, levels_above_99, calculator_version, and source threshold provenance; percentile and RIT remain base view fields.

Alternatives and tradeoffs

  • Use NWEA percentile only
  • Let advanced-student dashboards compute their own measure
  • Cap all advanced reports at percentile 99

Rationale and consequences

99-levels is an Alpha outcome signal, like Growth X. Storing the derived ceiling metric once avoids winter_map and student-progress-animation style local calculators while leaving NWEA source values in the base.

#ritd-014-working-grade-advancement

results working grade advancement

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-07-17

Decision

Expose alpha.working_grade and alpha.advancement as Alpha extension/read models keyed to source result, MAP, mastery, policy, and student FKs. The default grades-mastered answer is gradesMasteredSytd = max(0, currentWorkingGrade - syStartWorkingGrade). Placement/reporting context is separately named mapBaselineGradeDelta_notGradesMastered = max(0, currentWorkingGrade - max(mapBaselinePlacementGrade, syStartWorkingGrade)) and must never be called grades mastered. The governed wire fields are grades_mastered_sytd and map_baseline_grade_delta_not_grades_mastered. advancement_value and /advancement remain deprecated aliases to gradesMasteredSytd only and cannot vary by write path. Inputs, both formula versions, source evidence, as-of date, and null reasons are stored in Results as Alpha-derived facts and source references, not copied base rows.

Alternatives and tradeoffs

  • Compute advancement in each dashboard
  • Use MAP RIT growth only
  • Use report-card grade changes as advancement

Rationale and consequences

TimeBack 2026.07.17.11 defines mastery-pure year-to-date advancement separately from MAP-baseline placement delta. Results already owns working grade, MAP baseline placement grade, and school-year-start state, so it is the right home for both named, versioned formulas; storing references and derived values avoids a parallel base-result store and prevents consumers from guessing which formula a generic advancement write path used.

#ritd-015-report-card-gates

results report card gates

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

Expose alpha.report_card_entry and alpha.gate_pass as effective-dated Alpha extension tables. They link back to result_record, student_kc_state, gradebook_item, or MAP evidence as appropriate and carry new durable statement fields: status, begin_date, end_date, is_time_locatable, and policy_version.

Alternatives and tradeoffs

  • Use raw result rows only
  • Put gate status on tests or students
  • Expose gate state as a current-only cache

Rationale and consequences

Report-card entries and gate passes are not moments and not blank tests. They are durable Alpha statements about one student, so they belong in Results, and FKs preserve the base sources without copying their columns.

#ritd-016-content-effectiveness-derived-read

results content effectiveness derived read

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Results exposes alpha.content_effectiveness as a read/materialized Alpha extension keyed to Content item FK and recomputed by the Results surface after each scored result that references the content the student used. The read carries validated_by_count, failed_by_count, effectiveness_rate, last_recomputed_at, policy_version, and source result evidence refs. It is displayed on the Content item and consumed by Content/Curriculum tools, but the counters are derived from Results evidence and are never incremented by an app.

Alternatives and tradeoffs

  • Let apps increment validated/failed counters
  • Make Content recompute from raw events
  • Store effectiveness fields directly on Content base rows
  • Expose only result_content_evidence and require clients to aggregate

Rationale and consequences

The derived read mirrors student_kc_state: Results owns the outcome evidence and recompute rule, while Content remains the thing being evaluated. One read prevents app counters and avoids copying Content source fields into Results.

#ritd-017-report-ready-views-skill-pack-gate

results report ready views skill pack gate

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-08

Decision

Ship report-ready Results views for MAP Quadrants, retakes, growth-over-time, norms isolation, growth breakdown, student progress animation inputs, report cards, four-state KC state, student track state, course and grade-level progress, next task, XP ledger summaries, content effectiveness, academic-record/transcript answers, and closed-loop reconciliation keys. These are read models over base OneRoster/NWEAMap/Ed-Fi views plus Alpha extension tables. The skill pack may call these views and relay results; it may not calculate them.

Alternatives and tradeoffs

  • Let the skill pack include recipes plus local logic
  • Only expose primitive rows
  • Publish sample report SQL inside the skill pack

Rationale and consequences

The platform's differentiator is finished, trusted answers. Report-ready views are negative complexity because they remove repeated client logic and give one answer key for agents without creating copied base tables.

#ritd-031-edfi-academic-record-transcript

results edfi academic record transcript

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-05

Decision

Add Ed-Fi 1EdTech as the third Results Alpha upstream with rules.upstream_module = oneroster+nweamap+ed_fi. Surface only the Ed-Fi Student Academic Record domain that passes the Results belongs-here test: StudentAcademicRecord, CourseTranscript, Grade, and GradebookEntry-derived final course grade/credit facts. Publish alpha.academic_record_view, alpha.course_transcript_view, alpha.course_grade_view, and alpha.transcript_result_view as SQL views over edfi.canonical_record. The views filter by tenant and resource_name/record kind, exclude drafts and ordinary soft-deleted rows, extract typed payload_json fields, resolve descriptors through edfi.descriptor_code, and join roster references through platform3 sourcedIds. No Alpha table may store transcript fields whose source is Ed-Fi.

Alternatives and tradeoffs

  • Leave all Ed-Fi academic records only on the Ed-Fi 1EdTech surface
  • Copy transcript rows into Alpha-owned academic_record tables
  • Expose every Ed-Fi domain through Results
  • Let clients read edfi.canonical_record payload_json directly

Rationale and consequences

The Ed-Fi Student Academic Record domain is already a settled durable statement about one student, so it belongs in Results; the rest of Ed-Fi does not. Views are the smallest Alpha change because they keep Ed-Fi as system of record while giving students, teachers, and agents the transcript answer without local descriptor, draft, soft-delete, or payload parsing logic.

#ritd-018-axis-write-granularity

results axis write granularity

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP both bulk adapter ingest and per-result correction/create routes, with write routing pinned by storage class. Producer imports for NWEA/base facts route to the NWEAMap or OneRoster 1EdTech base surfaces; Ed-Fi transcript/academic-record writes route to the Ed-Fi 1EdTech base surface; platform-native scorers create individual Alpha extension result rows; manual corrections of base rows use the base surface, while corrections of Alpha-owned extensions use Results PATCH with If-Match. student_kc_state, student_track_state, xp_ledger, content_effectiveness, and next_task are written or recomputed only by surface-owned scorers/state transitions, not by apps.

Alternatives and tradeoffs

  • Bulk-only
  • Per-resource only
  • One generic upsert endpoint for every write job

Rationale and consequences

The duplicate-primitive test does not reject this because bulk ingest and per-result correction serve different jobs. Explicit write routing keeps the API useful without letting Alpha own copied base storage.

#ritd-019-axis-read-shape

results axis read shape

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP list + detail + sub-collections over public Results views. Results exposes collection lists, detail reads, and student-scoped sub-collections for result records, MAP scores, four-state KC state, student track state, course progress, grade-level progress, next task, XP ledger entries, content effectiveness, academic records/transcripts, report cards, gate passes, growth series, and named grade movement. Grades-mastered is the canonical grade-movement collection; advancement is a deprecated route alias to the mastery-pure answer only.

Alternatives and tradeoffs

  • List-only
  • List + detail only
  • Only report-ready views, no raw details

Rationale and consequences

Sub-collections are not duplicate primitives here; they are the plain way to ask 'what does this student know and score?'

#ritd-020-axis-query-model

results axis query model

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP filter + sort + paging + cursor + modifiedSince over typed view columns and extension columns, with typed Results parameters: asOfDate, subject, canonicalTermId, schoolYear, courseId, gradeLevel, transcriptRecordId, growthWindow, normsSet, resultKind, sourceKind, kcId, initialMastery, currentRetention, durableMastery, fluencyState, trackId, trackStatus, contentId, taskType, xpAwardReason, and cohort joins.

Alternatives and tradeoffs

  • No query model
  • Filter + sort only
  • Free-form query expression language

Rationale and consequences

Typed query parameters remove term parsing and client-side filtering. modifiedSince ships because it is negative complexity for sync clients.

#ritd-021-axis-concurrency-model

results axis concurrency model

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP If-Match per mutable resource. Detail reads return ETag. PATCH/DELETE/correction routes require If-Match. Derived report views are read-only and do not need ETags for mutation.

Alternatives and tradeoffs

  • No concurrency
  • Bulk-only concurrency
  • Last-write-wins with audit trail

Rationale and consequences

The negative-complexity test ships this: it removes an invisible failure mode that would otherwise surface as wrong student scores.

#ritd-022-axis-idempotency-model

results axis idempotency model

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP Idempotency-Key on every create, import, adapter ingest, correction, and recompute request that can change durable Results state. Store replay state in platform.idempotency_key with tenant, module, surface, route, operation id, key, and request hash. The idempotency record points to the base-surface write or Alpha extension write that actually owns the mutation.

Alternatives and tradeoffs

  • No idempotency
  • Producer-result id only
  • Per-route bespoke replay ledgers

Rationale and consequences

This is not a duplicate primitive because it reuses the platform idempotency ledger and covers all retryable writes.

#ritd-023-axis-auth-shape

results axis auth shape

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP platform HS256 Bearer JWTs with role/roles, scopes, tenant, and relationship-scoped claims: studentIds, guardianOf, guideOf, schoolSourcedIds, classSourcedIds, and permitted result scopes.

Alternatives and tradeoffs

  • Role-only
  • Separate Results auth system
  • Route-specific API keys for Results

Rationale and consequences

The platform JWT primitive already exists; relationship claims refine it instead of adding a new auth model.

#ritd-024-axis-eventing-model

results axis eventing model

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP polling with modifiedSince. DEFER webhooks and event streams until three distinct integrator decisions in 90 days show polling prevents a Results app from completing its documented job, or a platform-level webhook substrate ships for multiple Alpha modules.

Alternatives and tradeoffs

  • Full polling only
  • Webhooks now
  • Daily export files only

Rationale and consequences

modifiedSince removes the main sync pain. Webhooks would duplicate platform-level eventing before Results has evidence that polling blocks the served job.

#ritd-025-axis-error-envelope

results axis error envelope

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP typed RFC 7807 Problems with stable type URIs, requestId, traceId, fieldErrors, code, and Results branch codes for validation, source_adapter, norms, growth_window, policy_config, student_kc_state, student_track_state, next_task, xp_ledger, content_effectiveness, precondition, idempotency, auth, and not_found failures.

Alternatives and tradeoffs

  • Plain RFC 7807 only
  • Ad-hoc errors per route
  • Field-only validation JSON

Rationale and consequences

A stable error envelope is negative complexity for clients and keeps policy/math failures explicit.

#ritd-026-axis-tenant-routing

results axis tenant routing

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP tenant routing by platform JWT tenant_id/tenantId matched to every base row, view branch, and Alpha extension row. Do not add a /tenants/{id} route prefix. NWEA account, school, brand, modality, and cohort are data filters inside the tenant.

Alternatives and tradeoffs

  • Tenant in URL
  • Tenant in header
  • Tenant in subdomain

Rationale and consequences

The platform tenant model is already approved. Results should inherit it and use NWEA account as data partition, not auth identity.

#ritd-027-axis-conformance-evidence

results axis conformance evidence

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP executable evidence: alias-map storage coverage, upstream trace coverage, no-copied-base-column checks, policy-config fixtures, MAP test-of-record fixtures, goal-strand unpivot fixtures, norms 2020/2025 fixtures, Growth X fixtures, effective-grade fixtures, 99-level fixtures, Ed-Fi transcript descriptor/canonical/soft-delete fixtures, four-state student_kc_state fixtures, student_track_state switch fixtures, next-task fixtures, XP ledger fixtures, content-effectiveness recompute fixtures, report-ready view fixtures, old/new reconciliation fixtures, per-domain convergence probes, and skill-pack leak checks.

Alternatives and tradeoffs

  • No conformance evidence
  • Manual screenshots only
  • Reviewer-only manual checklist

Rationale and consequences

The acceptance test is binary. Executable fixtures are the only reliable way to prove the surface, not the skill pack, owns the work.

#ritd-028-axis-privacy-retention

results axis privacy retention

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP tenant-scoped privacy, relationship-scoped reads, audit for privileged reads/writes, redaction from logs/Problems/evidence, correction history, soft-delete/tombstone behavior for source rows, and policy-driven retention hooks. DEFER public hard-erasure API details until platform privacy policy names the workflow.

Alternatives and tradeoffs

  • No explicit retention
  • Hard delete everything on request
  • Use platform-wide privacy defaults with no Results-specific rules

Rationale and consequences

Results records are both sensitive and durable. The simplest safe policy is redacted evidence plus explicit correction/retention semantics, not silent deletion.

#ritd-029-axis-list-endpoints

results axis list endpoints

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP one documented list endpoint per public Results collection, backed by the public view or extension table for that collection: result-records, gradebook-items, academic-records, course-transcripts, course-grades, transcript-results, report-card-entries, gate-passes, student-kc-state, student-track-state, next-task, xp-ledger, content-effectiveness, map-scores, map-sittings, map-goal-strands, map-growth-windows, map-effective-grades, map-longitudinal-points, map-99-levels, map-percentile-translations, map-r90-norm-points, working-grades, grades-mastered with advancement as a deprecated mastery-pure route alias, result-content-evidence, source-adapters, and report-ready views including reports/group-growth-x.

Alternatives and tradeoffs

  • Write-and-remember only
  • Only student sub-collections
  • Single search endpoint for all discovery

Rationale and consequences

List endpoints are not optional for an AI-readable platform. They are how a cold agent learns what exists.

#ritd-030-axis-report-oracle

results axis report oracle

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-04

Decision

SHIP platform-owned report examples and answer-key endpoints for Results report families. Worked examples return expected answers from the live surface for demo fixtures under both 2020 and 2025 norms and for four-state KC state, student track state, next task, XP ledger, and content effectiveness; the skill pack checks itself against those answers.

Alternatives and tradeoffs

  • No answer keys
  • Golden cells inside the skill pack
  • Reviewer-maintained local expected-answer fixtures

Rationale and consequences

This ships because the skill-pack acceptance test is persona-blocking without it. The platform, not the pack, must own expected answers.

#ritd-035-raw-ingest-adapter

results raw ingest adapter

AcceptedOwner: platform3-loop / Results surfaceDate: 2026-06-12

Decision

Results accepts source-shaped production rows (raw TimeBack assessment results and raw NWEA MAP CDF rows) through named import adapters and normalizes every platform-owned quantity server-side. A failed import returns a typed RFC 7807 Problem and the matching HTTP status: results:validation_failed at HTTP 400 for malformed or invalid source rows, results:adapter_rejected at HTTP 422 when a named adapter cannot map a row; it never answers HTTP 200 over a failed import. A successful import materializes Results rows that the ordinary list and detail reads return. The closed-loop reconciliation read accepts a typed single-student studentId filter and returns one report-ready reconciliation row.

Alternatives and tradeoffs

  • Make each caller pre-shape and pre-calculate rows before posting
  • Always answer 200 and queue rows for asynchronous handling
  • Ship one bespoke importer per producer with its own response codes

Rationale and consequences

One adapter per producer plus a succeed-with-rows or fail-with-a-reason contract is the smallest shape that makes an import either readable or branchably rejected. It reuses the RITD-003 canonical shape, the RITD-022 idempotency ledger, and the RITD-025 Problem envelope instead of adding a new ingest error model.