Source-bound decision record
31 committed decisions · people_and_orgs/alpha Source generated 2026-06-11T00:00:00Z
#people_orgs_served_audiences
people orgs served audiences
Unspecified
Decision
Students, parents, teachers, and app-builder LLMs using the Alpha surface to answer who someone is and where they belong as of a date.
Alternatives and tradeoffs
- 1EdTech SIS developers
- data-warehouse-only roster integrators
- gradebook/result consumers
Rationale and consequences
The Alpha roster audience needs school-language People & Orgs vocabulary over the approved OneRoster and Ed-Fi data dictionaries.
#people_orgs_module_boundary
people orgs module boundary
Unspecified
Decision
Define People & Orgs as the Alpha roster module over the approved OneRoster 1EdTech data dictionary. The module aliases users, orgs, roles, enrollments, and academic_sessions, and it explicitly cuts Results, Events, Content, Curriculum, and gradebook-owned facts from this surface.
Alternatives and tradeoffs
- Expose the entire OneRoster surface as Alpha
- Create new Alpha roster tables unrelated to OneRoster
Rationale and consequences
The alias-only boundary is the smallest decision that lets a parent ask 'where does this student belong today?' while preserving exact upstream provenance. It avoids a new database and avoids exposing unrelated OneRoster collections as if they were roster concepts.
#people_orgs_person_vocabulary
people orgs person vocabulary
Unspecified
Decision
Rename OneRoster user to person. Students, parents, guardians, guides, and staff are expressed through typed memberships and role_kind values, not by copying the same human into separate tables. Password fields are not part of person.
Alternatives and tradeoffs
- Keep the name user
- Create separate student, parent, guide, and staff tables
Rationale and consequences
A single person row is easiest to explain and safest to maintain: the person is who they are; memberships say what they are in a place on a date.
#people_orgs_place_nwea_boundary
people orgs place nwea boundary
Unspecified
Decision
Rename OneRoster org to place. place_kind comes from orgs.type and remains authoritative. For MAP reporting, each Brand x Modality NWEA account is exactly one place with place_kind=district, required nwea_district_id, locked brand and modality tags, and schools linked through parent_place_id.
Alternatives and tradeoffs
- Put NWEA account id on every school
- Represent NWEA accounts as separate non-OneRoster objects
Rationale and consequences
The district-as-account rule is already pinned by the brainlift and needs no parallel hierarchy. One parent row, one external id, and locked tags make the Brand x Modality grain impossible to blur.
#people_orgs_point_in_time_memberships
people orgs point in time memberships
Unspecified
Decision
Rename OneRoster roles to place_membership and enrollments to class_membership. Both are point-in-time objects with begin_date, end_date, and derived is_time_locatable. begin_date null excludes the row from every point-in-time computation; end_date null means still active for dates on or after begin_date.
Alternatives and tradeoffs
- Expose current-only roster shortcuts
- Let consumers interpret null dates
Rationale and consequences
A single asOfDate contract removes the false choice between current and historical APIs. is_time_locatable gives consumers a field to filter instead of guessing from nulls.
#people_orgs_school_periods
people orgs school periods
Unspecified
Decision
Rename academicSession to school_period and expose period_kind, start_date, end_date, parent_period_id, and school_year as typed fields. Terms, semesters, grading periods, and school years are never parsed from titles.
Alternatives and tradeoffs
- Keep academicSession
- Represent only school years
Rationale and consequences
School period is the smallest plain term that covers the OneRoster types without inventing separate APIs for year, term, semester, and grading period.
#people_orgs_tag_registry
people orgs tag registry
Unspecified
Decision
Add a governed Alpha tag registry for low-cardinality roster reporting dimensions that are not relationships, constraints, or computed inputs. A relationship to another object is always a real column and foreign key. Brand and modality are locked tags seeded at setup for NWEA accounts. Segment is a closed governed tag whose allowed values are seeded at setup, and a place spanning more than one segment is tagged segment=mixed.
Alternatives and tradeoffs
- Use a free-form metadata bag
- Promote every reporting dimension to a column
Rationale and consequences
The first-match rule is simple enough for humans and LLMs: enforce, join, sort, compute, or relate means column; report-slice-only means governed tag.
#people_orgs_tenure_and_grade_context
people orgs tenure and grade context
Unspecified
Decision
Add alpha.person_ext with admission_date, exit_date, and canonical age_grade context, surfaced through alpha.person and alpha.student views. admission_date is the person-level lifecycle open date; exit_date is the exclusive person-level lifecycle close date and remains null when the person lifecycle is still open. Tenure bucket is computed from admission_date and asOfDate using alpha.policy.tenure_buckets. Grade aliases are folded to a canonical grade type at the Alpha write boundary. Membership begin_date/end_date rows and school_period start_date/end_date rows are separate interval facts and must not be used as substitutes for person lifecycle dates.
Alternatives and tradeoffs
- Let every report compute tenure
- Store tenure bucket directly on person
Rationale and consequences
Admission and exit dates are real person lifecycle facts; tenure is a policy-derived answer. Separating person lifecycle dates from membership and school-period intervals keeps history correct and lets policy change without rewriting consumers.
#people_orgs_app_credentials
people orgs app credentials
Unspecified
Decision
Cut users.password and user_profiles.password from Alpha. Add alpha.app_credential with student_person_id, app_id, secret_ref, created_at, and last_rotated_at. secret_ref points at a managed vault; Alpha APIs expose existence and last rotation only, never the secret value.
Alternatives and tradeoffs
- Expose OneRoster password fields
- Store app passwords as governed tags
Rationale and consequences
A vault reference is the smallest safe primitive because it lets apps know a credential exists without making the roster database a password store.
#people_orgs_sensitive_profile_privacy
people orgs sensitive profile privacy
Unspecified
Decision
Restrict demographics and user_profiles to explicitly authorized self, guardian, or administrative views. Ordinary person and place list endpoints do not include birth date, race/ethnicity flags, birth location, residence status, vendor profile usernames, or profile credentials.
Alternatives and tradeoffs
- Expose every OneRoster demographic field on person
- Cut demographics entirely from Alpha
Rationale and consequences
Restriction is simpler than pretending sensitive facts are ordinary roster columns: it keeps the data documented, authorized, and auditable without making every app see it.
#people_orgs_alias_map_contract
people orgs alias map contract
Unspecified
Decision
Publish alpha-alias-map.json from this architecture attempt. Each Alpha object and field either maps to exactly one OneRoster 1EdTech source view column, a derived view expression, or an Alpha extension table with its owning ITD. Downstream artifacts must be generated from this map, not from memory.
Alternatives and tradeoffs
- Describe aliases only in prose
- Let the data dictionary invent the map later
Rationale and consequences
A generated map is the smallest artifact that makes the two-surface claim provable: aliases are no-data-moved views, extension tables are labeled, and cuts are intentional.
#people_orgs_storage_model
people orgs storage model
Unspecified
Decision
Realize every renamed, cut, or restricted People & Orgs object as a SQL view over the approved OneRoster 1EdTech base. Only true Alpha extensions get tables, and those tables contain only new Alpha columns plus foreign-key references to the base row. The public Alpha object is a read view composed from the base alias view plus its extension table or tables; sourced-field writes route to the OneRoster 1EdTech surface, while extension writes update only the extension table after the base row exists.
Alternatives and tradeoffs
- Create owned Alpha tables for every public object
- Translate only in the API with no SQL views
Rationale and consequences
Views plus extension tables are the smallest shape that satisfies both first-class paths. A parent-readable Alpha query exists in SQL, but no OneRoster-sourced column is copied into Alpha-owned storage. The surface can be plain without forking the database.
#people_orgs_edfi_second_upstream
people orgs edfi second upstream
Unspecified
Decision
Set rules.upstream_module to oneroster+ed_fi. People & Orgs continues to use OneRoster for core roster identity, places, memberships, and school periods, and adds Ed-Fi 1EdTech canonical records only for administrative facts that genuinely say who someone is or where they belong: guardians, guardian relationships, staff assignments, and program participation. These Alpha objects are SQL views over edfi.canonical_record, with descriptor fields resolved through edfi.descriptor_code and roster references joined through platform3 OneRoster sourcedIds.
Alternatives and tradeoffs
- Keep OneRoster as the only upstream
- Create a separate Alpha administrative-record module for these facts
Rationale and consequences
The second-upstream rule is the smallest change that makes guardian, staff-assignment, and program-participation questions first-class without copying Ed-Fi data or broadening People & Orgs into attendance, transcript, discipline, or results.
#people_orgs_guardian_views
people orgs guardian views
Unspecified
Decision
Expose alpha.guardian_view from Ed-Fi Contact records and alpha.guardian_relationship_view from Ed-Fi StudentContactAssociation records. Where an Ed-Fi deployment uses the older Parent / StudentParentAssociation naming, the data dictionary labels that as the same approved Ed-Fi guardian-family source; the platform3 implementation follows the approved Ed-Fi v6.1 catalog names. Relationship descriptors, legal-guardian flags, lives-with flags, emergency-contact flags, contact priority, and contact restrictions come from Ed-Fi canonical records and descriptor_code resolution.
Alternatives and tradeoffs
- Use only OneRoster users.agent_sourced_ids
- Create Alpha guardian and relationship tables
Rationale and consequences
A pair of restricted views is simpler than either under-modeling guardians through OneRoster agents or duplicating family-contact records. The raw database and API can answer the same guardian question from the same view.
#people_orgs_staff_assignment_views
people orgs staff assignment views
Unspecified
Decision
Expose alpha.staff_assignment_view over Ed-Fi StaffEducationOrganizationAssignmentAssociation canonical records, optionally joined to Staff records for display fields and always joined to OneRoster users/orgs through platform3 staff_sourced_id and school_sourced_id boundaries. This view carries assignment-specific facts beyond OneRoster roles: staff classification, position title, begin/end dates, order of assignment, full-time equivalency, and current-organization experience.
Alternatives and tradeoffs
- Use only OneRoster roles for staff
- Expose the whole Ed-Fi Staff domain in Alpha
Rationale and consequences
The assignment view is the smallest plain object that answers where staff serve and in what capacity without turning People & Orgs into an HR system.
#people_orgs_program_participation_views
people orgs program participation views
Unspecified
Decision
Expose alpha.program_participation_view over Ed-Fi Program plus GeneralStudentProgramAssociation / StudentProgramAssociation-family canonical records, including specializations such as Special Education, Title I, English Learner, Section 504, and food-service program associations when present. The view says that a student belongs to a program at a place for a date range; it does not store outcomes, services effectiveness, mastery, eligibility calculations, or policy thresholds.
Alternatives and tradeoffs
- Model program participation as governed tags on person
- Move program participation to Results or Policy
Rationale and consequences
A view over Ed-Fi program associations is the simplest shape that keeps the relationship enforceable, dated, and raw-queryable while preventing program outcome logic from leaking into People & Orgs.
#people_orgs_schema_migration_concurrency_gate
people orgs schema migration concurrency gate
Unspecified
Decision
The People & Orgs Alpha schema migration must run inside one transaction and acquire pg_advisory_xact_lock(hashtextextended('platform3.people_and_orgs.alpha.schema.v1', 0)) before any DDL. DDL must stay idempotent with create ... if not exists and create or replace view so concurrent Vercel cold starts do not deadlock or race while adding OneRoster or Ed-Fi-backed views.
Alternatives and tradeoffs
- Run idempotent DDL without a lock
- Use a session-level advisory lock
Rationale and consequences
The transaction-scoped lock is the smallest operational guard that preserves stateless Vercel behavior, keeps DDL idempotent, and prevents the already-fixed cold-start deadlock from returning.
#people_orgs_reality_flag
people orgs reality flag
Unspecified
Decision
Add a typed closed reality enum with exactly three values - real, test, and synthetic - as a first-class Alpha extension field on both person (alpha.person_ext.reality) and place (alpha.place_ext.reality). reality is set at the Alpha write boundary from the authoritative source classification and is never inferred from names, emails, or campus labels. Ordinary list and report endpoints default to reality=real; including test or synthetic requires an explicit documented parameter. real means a production person or place; test means a QA or AI-agent test entity; synthetic means generated, seeded, anonymized, or fixture data (demo-tenant and development fixtures fold into synthetic, not a separate value).
Alternatives and tradeoffs
- Infer test or synthetic entities from names, emails, or campus labels
- Store reality only on person, not on place
- Use the governed tag registry for reality
- Add a four-value enum that keeps demo as its own reality value
Rationale and consequences
A single typed enum set at the write boundary and defaulted to real on read is smaller than every consumer maintaining its own exclusion logic and safer than inferring intent from display strings. Three values cover exactly the three origins the platform actually has - production, QA/agent test, and generated data (seeded, anonymized, demo-tenant, and development fixtures all fold into synthetic) - with no overlap; a fourth demo value would only restate non-production data that synthetic already covers and would diverge from the approved data dictionary and implementation. The field reuses the existing person_ext and place_ext extension tables, so it adds no new table.
#people_orgs_over_time_history
people orgs over time history
Unspecified
Decision
Add two Alpha-owned effective-dated history tables - alpha.alpha_level_history and alpha.age_grade_history - each a dated row carrying history_id, a person_id foreign key to the base person, the canonical value (alpha_level or age_grade), begin_date, end_date, and a derived is_time_locatable. Each is read with a required asOfDate that defaults to now, exposing alphaLevel(person, asOfDate) and ageGrade(person, asOfDate). The dated tables are the system of record for point-in-time reads; they extend, and never replace with a single mutable current column, the convenience current values. This applies the one point-in-time pattern (PAITD-004) uniformly to the two People & Orgs facts that change over time.
Alternatives and tradeoffs
- Keep a single mutable current alpha_level / age_grade column
- Reconstruct level and grade history from Events or an audit log
Rationale and consequences
The platform already promises point-in-time and already has the dated-row plus asOfDate plus is_time_locatable pattern (PAITD-004). Reusing it for Alpha level and age grade is smaller than inventing per-fact history logic and far smaller than every consumer reconstructing past values; the begin_date/end_date null rules are inherited unchanged, and each table holds only Alpha-owned columns plus the base-row foreign key.
#people_orgs_write_granularity
people orgs write granularity
Unspecified
Decision
SHIP per-resource Alpha commands and defer Alpha bulk CSV import/export. CSV bulk remains available through the OneRoster 1EdTech surface. For per-resource Alpha commands, sourced-field mutations route to the OneRoster base surface; extension-field mutations write only the owning Alpha extension table after the base row exists.
Alternatives and tradeoffs
Rationale and consequences
Per-resource Alpha commands remove a duplicate primitive while keeping 1EdTech bulk exchange where it already belongs. Routing sourced fields to the base surface preserves OneRoster as system of record.
#people_orgs_read_shape
people orgs read shape
Unspecified
Decision
SHIP list, detail, and narrow sub-collections: people, places, school_periods, memberships, sensitive profiles, app credential status, guardians, guardian relationships, staff assignments, program participation, a person's memberships, a student's guardians, a student's programs, a place's people, a place's staff assignments, a district's schools, and a school period's active memberships.
Alternatives and tradeoffs
- List-only
- List plus detail
Rationale and consequences
Narrow sub-collections match the natural questions: who is this person, where do they belong, who may act for this student, which staff serve here, and what programs is the student in on this date.
#people_orgs_query_model
people orgs query model
Unspecified
Decision
SHIP documented filters, stable sorting, opaque cursor paging, modifiedSince for source-change scans, and asOfDate for point-in-time membership reads. Ed-Fi-backed views inherit raw-DB guardrails: tenant scope, resource filter, is_deleted=false by default, canonical-only state, descriptor_code resolution, and OneRoster sourcedId joins. Unsupported parameters return typed 400 Problems.
Alternatives and tradeoffs
- No query model
- Filter and sort only
Rationale and consequences
The chosen model is the smallest one that prevents every app from reimplementing point-in-time, soft-delete, draft-state, descriptor, and sync behavior locally.
#people_orgs_concurrency_model
people orgs concurrency model
Unspecified
Decision
SHIP ETag on mutable resource reads and require If-Match for PUT, PATCH, and DELETE that can overwrite person, place, membership, tag, profile, or credential-reference state.
Alternatives and tradeoffs
- No concurrency control
- Bulk-only concurrency
Rationale and consequences
HTTP validators are simpler than a custom lock service and match approved platform behavior.
#people_orgs_idempotency_model
people orgs idempotency model
Unspecified
Decision
SHIP Idempotency-Key on retryable writes, stored in platform.idempotency_key with module=people_and_orgs and surface=alpha.
Alternatives and tradeoffs
- No idempotency
- Natural unique constraints only
Rationale and consequences
The platform ledger is already approved; reusing it is simpler than module-specific duplicate detection.
#people_orgs_auth_shape
people orgs auth shape
Unspecified
Decision
SHIP Bearer JWT with tenant_id or tenantId, role/scopes, and relationship-scoped People & Orgs claims: agentOf[], personIds[], placeIds[], schoolIds[], and classIds[] where visibility must be narrowed.
Alternatives and tradeoffs
- Role-only
- Role plus scopes only
Rationale and consequences
Relationship claims are necessary to avoid overbroad roster reads, and they refine the existing JWT primitive instead of creating a second auth system.
#people_orgs_eventing_model
people orgs eventing model
Unspecified
Decision
SHIP poll plus modifiedSince for roster synchronization; defer webhooks and push streams until repeated integrator demand proves polling blocks a committed job.
Alternatives and tradeoffs
Rationale and consequences
modifiedSince is the smallest freshness primitive that avoids full polling without adding webhook infrastructure.
#people_orgs_error_envelope
people orgs error envelope
Unspecified
Decision
SHIP typed RFC 7807 Problems with stable type URIs, code values prefixed people_orgs:, requestId, traceId, and fieldErrors when validation fails.
Alternatives and tradeoffs
- No envelope
- Plain RFC 7807
Rationale and consequences
Stable typed Problems reuse the platform contract and make invalid roster values easy to fix.
#people_orgs_tenant_routing
people orgs tenant routing
Unspecified
Decision
SHIP tenant routing by signed JWT tenant_id/tenantId for ordinary People & Orgs routes, matching the OneRoster flat-route contract. Demo token minting may accept tenantId=demo, but tenant headers are not accepted as source of truth.
Alternatives and tradeoffs
- Tenant in URL
- Tenant in header
Rationale and consequences
The Alpha surface is a school-language alias over OneRoster. Keeping flat module routes while enforcing the signed tenant claim avoids path drift and duplicate tenant precedence rules.
#people_orgs_conformance_evidence
people orgs conformance evidence
Unspecified
Decision
SHIP local evidence: alias-map coverage tests, point-in-time membership fixtures, NWEA district-boundary fixtures, Ed-Fi guardian/staff/program view fixtures, privacy/secret leak tests, migration-concurrency tests, platform auth/error/idempotency/concurrency tests, live demo smoke tests, surface QC, integration app, and skill-pack self-check. Defer official 1EdTech certification claims for Alpha.
Alternatives and tradeoffs
- No evidence
- Claim 1EdTech certification for Alpha
Rationale and consequences
The evidence matches what Alpha changed. It proves the surface is buildable and does not imply a standard certification that does not apply.
#people_orgs_privacy_retention
people orgs privacy retention
Unspecified
Decision
SHIP GDPR-style roster privacy: tenant-scoped reads, relationship-scoped sensitive views, redacted logs/Problems/examples, audit rows for privileged reads, logical deletion where OneRoster status applies, Ed-Fi is_deleted filtering for canonical records, draft exclusion from ordinary reads, and tombstone/erasure handling for student/family PII.
Alternatives and tradeoffs
- No privacy policy
- Only OneRoster status deletion
Rationale and consequences
Roster and family-contact data is direct student/family PII; explicit minimization and audit rules are simpler than deciding privacy at each endpoint.
#people_orgs_list_endpoints
people orgs list endpoints
Unspecified
Decision
SHIP one list endpoint per public Alpha collection: people, places, place_memberships, class_memberships, school_periods, person_sensitive_profiles, guardians, guardian_relationships, staff_assignments, program_participations, app_credentials, tag_definitions, and object_tags. Write-and-remember is rejected.
Alternatives and tradeoffs
- Write-and-remember
- Only people and places lists
Rationale and consequences
A public collection without a list endpoint is not buildable from docs alone. One list per collection keeps the skill pack thin.