executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
oneroster-csv-validatorValidates 1EdTech / IMS Global OneRoster v1.1 and v1.2 CSV roster sets: manifest integrity, bulk vs delta strictness, foreign key references, and encoding sanitization. Trigger phrases: oneroster csv validator, validate oneroster zip, clever roster error, oneroster sourcedid.
Independent GitHub snapshot · Automated classification · Original documentation governs
Perform rigorous pre-flight validation on OneRoster v1.1 and v1.2 CSV roster bundles prior to SIS/Clever/ClassLink ingestion: audit foreign key referential integrity, eliminate orphan enrollments, enforce bulk vs delta partitioning, and sanitize encoding traps.
Activate this skill when:
.zip archives or CSV directories) for OneRoster v1.1 or v1.2 compliance.manifest.csv, orgs.csv, users.csv, courses.csv, classes.csv, enrollments.csv, and academicSessions.csv.Do NOT use this skill when:
The Manifest Root Authority Law:
manifest.csv MUST reside strictly at the root level of the ZIP archive (never nested inside a subfolder).oneroster.version,1.1 or oneroster.version,1.2.manifest.csv, or listed in manifest.csv but missing from the zip, the package is rejected.The Strict Bulk vs Delta Partition:
dateLastModified or status columns. In 1.2, all status fields MUST be active.YYYY-MM-DDTHH:MM:SS.sssZ) in dateLastModified and a valid status (active or tobedeleted).The Foreign Key Referential Integrity Hierarchy:
schoolSourcedId in users.csv and classes.csv MUST resolve to an existing sourcedId in orgs.csv.courseSourcedId in classes.csv MUST resolve to courses.csv.userSourcedId and classSourcedId in enrollments.csv MUST resolve to active primary keys in users.csv and classes.csv.Strict Role Enum Constraint Axioms:
users.csv: role MUST strictly match the OneRoster enum:
administrator, proctor, student, teacher.aide, guardian, parent, staff.enrollments.csv: role is strictly constrained to administrator, proctor, student, teacher. Custom vendor roles (substitute, dean) must be mapped to valid specification enums.sourcedId values MUST be case-sensitively unique strings (RFC 4122 UUID format strongly recommended).RFC 4180 CSV & UTF-8 BOM Sanitization:
\xEF\xBB\xBF to CSV exports, corrupting the first column name (sourcedId $\neq$ sourcedId)."). Literal quotes inside fields must be escaped as "".| Anti-Pattern | Manifestation in CSV Packages | Mandatory Production Counter-Rule |
|---|---|---|
| Nested Archive Packaging | Zipping a parent folder (district_roster/manifest.csv). | Manifest and CSVs MUST reside at the archive root (/manifest.csv). |
| The UTF-8 BOM Trap | Excel export injecting \xEF\xBB\xBF into sourcedId. | Strip BOM headers during pre-flight sanitization pass. |
| Orphan Enrollment Links | enrollments.csv pointing to non-existent user IDs. | Enforce foreign key validation across all entity files before import. |
| Bulk / Delta Column Bleed | Including dateLastModified or tobedeleted in bulk. | Enforce schema separation: bulk files must not contain delta headers. |
| Circular Org Hierarchies | School A listed as parent of School B, and vice-versa. | Perform directed acyclic graph (DAG) cycle detection on orgs.csv. |
| Illegal Role Strings | Using non-standard strings like counselor or sub. | Map all roles to standard enums (administrator, teacher, student). |
| Malformed ISO 8601 Timestamps | Writing "09/22/2026 14:00" instead of ISO 8601. | Enforce strict RFC 3339 UTC format: YYYY-MM-DDTHH:MM:SS.sssZ. |
| Unescaped CSV Commas | Unquoted commas in user names (Doe, John) shifting columns. | Wrap text fields containing commas in standard RFC 4180 quotes. |
| Missing Mandatory Core Files | Exporting a bulk zip without academicSessions.csv. | Confirm all 7 mandatory core files exist in zip and manifest. |
| Hallucinated SourcedIDs | Inventing arbitrary IDs during reconciliation. | Preserve authoritative SIS primary keys without synthetic generation. |
import csv
import io
import re
import zipfile
from typing import Dict, List, Set
CORE_FILES = [
"manifest.csv", "orgs.csv", "users.csv", "courses.csv",
"classes.csv", "enrollments.csv", "academicSessions.csv"
]
VALID_ROLES_1_1 = {"administrator", "proctor", "student", "teacher"}
def validate_oneroster_archive(zip_path: str) -> dict:
errors = []
warnings = []
with zipfile.ZipFile(zip_path, 'r') as z:
names = z.namelist()
# 1. Manifest Root Check
if "manifest.csv" not in names:
return {"status": "FATAL", "errors": ["manifest.csv missing from zip root."]}
# 2. Extract and Parse Core Sets
data: Dict[str, List[dict]] = {}
keys: Dict[str, Set[str]] = {}
for filename in CORE_FILES:
if filename in names:
raw_bytes = z.read(filename)
# Strip UTF-8 BOM if present
if raw_bytes.startswith(b'\xef\xbb\xbf'):
warnings.append(f"{filename} contains UTF-8 BOM; stripped automatically.")
raw_bytes = raw_bytes[3:]
reader = csv.DictReader(io.StringIO(raw_bytes.decode('utf-8', errors='replace')))
rows = list(reader)
data[filename] = rows
if rows and "sourcedId" in rows[0]:
keys[filename] = {r["sourcedId"] for r in rows if "sourcedId" in r}
else:
errors.append(f"Mandatory core file missing: {filename}")
# 3. Foreign Key Checks
if "orgs.csv" in keys and "users.csv" in data:
for r in data["users.csv"]:
org_ref = r.get("orgSourcedIds") or r.get("schoolSourcedId")
if org_ref and org_ref not in keys["orgs.csv"]:
errors.append(f"users.csv: user {r.get('sourcedId')} references non-existent org {org_ref}")
if "users.csv" in keys and "classes.csv" in keys and "enrollments.csv" in data:
for r in data["enrollments.csv"]:
uid = r.get("userSourcedId")
cid = r.get("classSourcedId")
if uid not in keys["users.csv"]:
errors.append(f"enrollments.csv: orphan enrollment references missing user {uid}")
if cid not in keys["classes.csv"]:
errors.append(f"enrollments.csv: orphan enrollment references missing class {cid}")
role = r.get("role")
if role not in VALID_ROLES_1_1:
errors.append(f"enrollments.csv: illegal role '{role}' on user {uid}")
return {
"status": "FAILED" if errors else "PASSED",
"errors": errors,
"warnings": warnings,
"total_records_checked": sum(len(v) for v in data.values())
}
propertyName,value
oneroster.version,1.1
file.orgs,bulk
file.users,bulk
file.courses,bulk
file.classes,bulk
file.enrollments,bulk
file.academicSessions,bulk
sourcedId,status,dateLastModified,userSourcedId,classSourcedId,role,primary
enr_99201,active,2026-09-22T08:30:00.000Z,usr_0421,cls_bio101,student,true
enr_99202,tobedeleted,2026-09-22T08:30:00.000Z,usr_0884,cls_bio101,student,false
Before emitting validation results, CSV transforms, or roster scripts:
manifest.csv is located strictly at / without folder nesting.\xEF\xBB\xBF.userSourcedId, classSourcedId, orgSourcedIds, and courseSourcedId.orgs.csv to ensure no circular parent references."Zip your CSV folder up and upload it. If Clever says there is a missing manifest, just add a blank manifest.csv inside the folder. If some student IDs are missing in enrollments, just delete those rows or invent temporary user IDs." Problems: Nested zip breaks root discovery, blank manifest violates schema, inventing fake IDs corrupts student records, ignores UTF-8 BOM.
- Extract zip to verify
/manifest.csvexists at the root path; confirmoneroster.versionis declared as1.1.- Scan all CSV headers: detect and strip UTF-8 BOM (
\xEF\xBB\xBF) fromusers.csvto prevent header corruption.- Perform foreign key referential integrity pass:
- Flag 4 orphan rows in
enrollments.csvreferencing deleted student IDusr_9914.- Flag invalid role
"substitute_teacher"on row 14 ofenrollments.csv(must map to"teacher").- Verify bulk purity: confirm no
dateLastModifiedortobedeletedstatus flags are present in bulk mode.- Emit clean, line-numbered pre-flight audit report before committing to SIS ingestion.