Generated 2026-07-13T09:08:41.856Z
Rules: R1–R9 superRefine on CourseSchema.parse
| Path | Enforcement | Unavoidable |
|---|---|---|
| buildCourse (main) | CourseSchema.parse before return | PASS |
| refineCourse (agentic) | councilProduce parses via CourseSchema + explicit re-parse | PASS |
| API GET /courses, /courses/:slug | loadCourseVerified: integrity + CourseSchema.parse on load | PASS |
| API POST /courses/:slug/assess (issue) | loadCourseVerified + esgGate (422 if !issuable) | PASS |
| batch build-batched-courses | CourseSchema.safeParse re-check + integrity sidecar | PASS |
| loaders (demo-bridge, images, transcripts, migrate) | CourseSchema.parse on load | PASS |
refineCourse's cosmetic `as unknown as Course` cast (which masked a zod type drift) replaced with explicit CourseSchema.parse — unavoidable by construction, not by convention.
| Category | Case | Result | Detail |
|---|---|---|---|
| schema-fuzz | R1: missing skillTree | PASS | correctly rejected (R1: course must carry a non-empty skillTree) |
| schema-fuzz | R1: skillTree depth < 2 (no prerequisite edge) | PASS | correctly rejected (R1: skillTree depth must be) |
| schema-fuzz | R2: phantom uncovered outcome | PASS | correctly rejected (R2: learning outcome phantom-outcome is not covered) |
| schema-fuzz | R3: empty achievement alignment | PASS | correctly rejected (R3: micro-credential) |
| schema-fuzz | R4: missing generativeFriction | PASS | correctly rejected (R4: module) |
| schema-fuzz | R5: cool-down < 24h | PASS | correctly rejected (R5: micro-credential) |
| schema-fuzz | R6: no portfolio capture points | PASS | correctly rejected (R6: module) |
| schema-fuzz | R7: two endowed starters | PASS | correctly rejected (R7: skillTree must have exactly one endowedProgress) |
| schema-fuzz | R8: cyclic prerequisites | PASS | correctly rejected (R8: micro-credential prerequisites form a cycle) |
| schema-fuzz | R9: check microCredentialId not in skillTree | PASS | correctly rejected (R9: module) |
| id-fabrication | 'LO1' -> real id | PASS | -> "uuid-aaaa" (remapped=true) |
| id-fabrication | 'outcome_3' -> real id | PASS | -> "uuid-cccc" (remapped=true) |
| id-fabrication | 'S2' handle -> real id | PASS | -> "uuid-bbbb" (remapped=true) |
| id-fabrication | bare '1' -> real id | PASS | -> "uuid-aaaa" (remapped=true) |
| id-fabrication | unicode 'S1' (fullwidth) handled gracefully | PASS | -> "uuid-aaaa" (remapped=true) |
| id-fabrication | empty string -> some real id (no throw) | PASS | -> "uuid-aaaa" (remapped=true) |
| id-fabrication | numeric-only '99' (out of range) -> some real id | PASS | -> "uuid-aaaa" (remapped=true) |
| id-fabrication | distinct LO1/LO2/LO3 cover ALL outcomes (R2 holds) | PASS | ids=[uuid-aaaa,uuid-bbbb,uuid-cccc] coversAll=true |
| id-fabrication | triple 'LO1' spreads to distinct real ids (no collapse) | PASS | ids=[uuid-aaaa,uuid-bbbb,uuid-cccc] distinct=3 |
| failure-inj | hang: stage deadline bounds the wait (no indefinite hang) | PASS | results=0 failures=1 in 1207ms |
| failure-inj | error: provider failure reported, not thrown | PASS | results=0 failures=1 (Error: injected provider error) |
| failure-inj | non-JSON: parse failure reported, not thrown | PASS | failures=1 |
| failure-inj | mixed (ok+hang): firstValid resolves fast, ignores slow model | PASS | results=1 in 0ms |
| tamper-at-rest | unmodified course passes integrity | PASS | hash=f6d2b40e3b27… |
| tamper-at-rest | tampered field value rejected | PASS | hash mismatch (course tampered or corrupted since persist) |
| tamper-at-rest | re-serialization (format change) rejected (byte-exact) | PASS | rejected (byte-exact held) |
| tamper-at-rest | truncated bytes rejected | PASS | hash mismatch (course tampered or corrupted since persist) |
| tamper-at-rest | missing sidecar rejected | PASS | missing integrity sidecar |
By category: schema-fuzz 10/10 · id-fabrication 9/9 · failure-inj 4/4 · tamper-at-rest 5/5
| Topic | Outcome | R-rules | Modules/Checks | Time | Error |
|---|---|---|---|---|---|
| single-word | built-clean | R=true | 2m/8c | 169670ms | |
| two-letters-nonsense | built-clean | R=true | 2m/8c | 153730ms | |
| ultra-narrow | graceful-fail | R=false | 0m/0c | 37483ms | Error: councilFirstValid: all models failed: Error: no JSON found in model respo |
| ultra-broad | built-clean | R=true | 2m/8c | 178157ms | |
| non-english | built-clean | R=true | 2m/8c | 173775ms | |
| symbols-math | built-clean | R=true | 2m/8c | 199810ms | |
| very-long-200-word | built-clean | R=true | 2m/8c | 174316ms |
Acceptance: no hangs (watchdog must bound every topic); builds-clean or graceful-fail are both acceptable.
| Metric | Value |
|---|---|
| Courses built OK (persisted) | 65 / 65 |
| R-rule parse pass (persisted) | 65 / 65 PASS |
| ESG-issuable | 64 / 65 |
| Failed — transient (quota/network) | 0 (infra, not integrity) |
| Failed — guardrail caught | 0 (unavoidability proof: rejected, not persisted) |
| Avg build time | 181.6s |
| Metric | Value |
|---|---|
| Total persisted courses | 65 |
| With sidecar | 65 |
| Hash matches | 65 |
| Missing sidecar | 0 |
| Hash mismatch (tampered) | 0 |
| Criterion | Result |
|---|---|
| Fringe conditions pass (28/28) | PASS |
| Topic fringe — no hangs | PASS |
| R-rules 100% (of persisted courses) | PASS |
| Integrity signed (100% sidecar coverage, all match) | PASS |
| Transient failures (quota/network — infra, not integrity) | 0 |
| Guardrail failures (caught & not persisted — unavoidability proof) | 0 |
| OVERALL | PASS |
Full machine-readable report: docs/trust-report.json