Requirements traceability matrix
Every requirement tag on a Gherkin feature file becomes a row here, mapped to the stories tagged on that feature, its scenarios, the test modules that bind it, and the diagrams named after its stories. The Gaps section lists what the repository structure cannot vouch for.
| Requirement | Story | Feature (scenarios) | Test module(s) | Diagrams | Tier deferrals |
|---|---|---|---|---|---|
| DOC-A | UC-1, UC-2, UC-3, UC-5, UC-7, UC-8, UC-10, UC-11, US-52, US-53, US-54, US-55, US-56, US-57, US-58, US-59 | backfill_assessment_scoring.feature — The shipped feature is reviewed; Scoring and override are documented; Each shipped story is traced to its requirements; The reflection and quiz surfaces are realigned to the design; backfill_authentication_enrollment.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The sign-in and enroll surfaces are realigned to the design; backfill_challenge_builder.feature — The shipped feature is reviewed; The feature has a data-model and authoring documentation; Each shipped story is traced to its requirements; backfill_live_event_operations.feature — The shipped feature is reviewed; Override-with-audit and the live dashboard are documented; Each shipped story is traced to its requirements; The live dashboard surface is realigned to the design; backfill_passport_progress.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The passport and prize surfaces are realigned to the design; backfill_qr_checkin.feature — The shipped feature is reviewed; The core loop has a sequence diagram with exception paths; Each scenario is traced; The scan / check-in surface is realigned to the design; backfill_reporting_prize_export.feature — The shipped feature is reviewed; The reporting suite is documented; Each shipped story is traced to its requirements; backfill_wellness_guide.feature — The shipped safety feature is reviewed; Crisis routing is documented as code-driven; Each scenario is traced; The guide surface is realigned to the design |
test_backfill_assessment_scoring_bdd.py, test_backfill_authentication_enrollment_bdd.py, test_backfill_challenge_builder_bdd.py, test_backfill_live_event_operations_bdd.py, test_backfill_passport_progress_bdd.py, test_backfill_qr_checkin_bdd.py, test_backfill_reporting_prize_export_bdd.py, test_backfill_wellness_guide_bdd.py |
activity/pass63_eligibility_gate.puml, activity/pass64_view_passport_offline.puml, activity/pass65_checkin_exceptions.puml, activity/pass66_challenge_authoring.puml, activity/pass69_report_generation.puml, activity/pass70_manual_override_audit.puml, class/pass20_guide_seam.puml, class/pass56_data_model.puml, component/pass64_passport_read_path.puml, component/pass65_checkin_service.puml, component/pass69_reports_service.puml, sequence/pass20_guide_conversation.puml, sequence/pass56_ai_scoring.puml, sequence/pass56_qr_checkin.puml, sequence/pass56_sso_sign_in.puml, sequence/pass63_enrollment.puml, sequence/pass68_mcq_auto_scoring.puml, sequence/pass70_live_event_dashboard.puml, sequence/pass71_reskin_config.puml, state/pass56_challenge_lifecycle.puml, usecase/pass56_use_cases.puml |
— |
| DOC-B | UC-1, UC-2, UC-3, UC-5, UC-7, UC-8, UC-10, UC-11, US-52, US-53, US-54, US-55, US-56, US-57, US-58, US-59 | backfill_assessment_scoring.feature — The shipped feature is reviewed; Scoring and override are documented; Each shipped story is traced to its requirements; The reflection and quiz surfaces are realigned to the design; backfill_authentication_enrollment.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The sign-in and enroll surfaces are realigned to the design; backfill_challenge_builder.feature — The shipped feature is reviewed; The feature has a data-model and authoring documentation; Each shipped story is traced to its requirements; backfill_live_event_operations.feature — The shipped feature is reviewed; Override-with-audit and the live dashboard are documented; Each shipped story is traced to its requirements; The live dashboard surface is realigned to the design; backfill_passport_progress.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The passport and prize surfaces are realigned to the design; backfill_qr_checkin.feature — The shipped feature is reviewed; The core loop has a sequence diagram with exception paths; Each scenario is traced; The scan / check-in surface is realigned to the design; backfill_reporting_prize_export.feature — The shipped feature is reviewed; The reporting suite is documented; Each shipped story is traced to its requirements; backfill_wellness_guide.feature — The shipped safety feature is reviewed; Crisis routing is documented as code-driven; Each scenario is traced; The guide surface is realigned to the design |
test_backfill_assessment_scoring_bdd.py, test_backfill_authentication_enrollment_bdd.py, test_backfill_challenge_builder_bdd.py, test_backfill_live_event_operations_bdd.py, test_backfill_passport_progress_bdd.py, test_backfill_qr_checkin_bdd.py, test_backfill_reporting_prize_export_bdd.py, test_backfill_wellness_guide_bdd.py |
activity/pass63_eligibility_gate.puml, activity/pass64_view_passport_offline.puml, activity/pass65_checkin_exceptions.puml, activity/pass66_challenge_authoring.puml, activity/pass69_report_generation.puml, activity/pass70_manual_override_audit.puml, class/pass20_guide_seam.puml, class/pass56_data_model.puml, component/pass64_passport_read_path.puml, component/pass65_checkin_service.puml, component/pass69_reports_service.puml, sequence/pass20_guide_conversation.puml, sequence/pass56_ai_scoring.puml, sequence/pass56_qr_checkin.puml, sequence/pass56_sso_sign_in.puml, sequence/pass63_enrollment.puml, sequence/pass68_mcq_auto_scoring.puml, sequence/pass70_live_event_dashboard.puml, sequence/pass71_reskin_config.puml, state/pass56_challenge_lifecycle.puml, usecase/pass56_use_cases.puml |
— |
| DOC-C | UC-1, UC-2, UC-3, UC-5, UC-7, UC-8, UC-10, UC-11, US-52, US-53, US-54, US-55, US-56, US-57, US-58, US-59 | backfill_assessment_scoring.feature — The shipped feature is reviewed; Scoring and override are documented; Each shipped story is traced to its requirements; The reflection and quiz surfaces are realigned to the design; backfill_authentication_enrollment.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The sign-in and enroll surfaces are realigned to the design; backfill_challenge_builder.feature — The shipped feature is reviewed; The feature has a data-model and authoring documentation; Each shipped story is traced to its requirements; backfill_live_event_operations.feature — The shipped feature is reviewed; Override-with-audit and the live dashboard are documented; Each shipped story is traced to its requirements; The live dashboard surface is realigned to the design; backfill_passport_progress.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The passport and prize surfaces are realigned to the design; backfill_qr_checkin.feature — The shipped feature is reviewed; The core loop has a sequence diagram with exception paths; Each scenario is traced; The scan / check-in surface is realigned to the design; backfill_reporting_prize_export.feature — The shipped feature is reviewed; The reporting suite is documented; Each shipped story is traced to its requirements; backfill_wellness_guide.feature — The shipped safety feature is reviewed; Crisis routing is documented as code-driven; Each scenario is traced; The guide surface is realigned to the design |
test_backfill_assessment_scoring_bdd.py, test_backfill_authentication_enrollment_bdd.py, test_backfill_challenge_builder_bdd.py, test_backfill_live_event_operations_bdd.py, test_backfill_passport_progress_bdd.py, test_backfill_qr_checkin_bdd.py, test_backfill_reporting_prize_export_bdd.py, test_backfill_wellness_guide_bdd.py |
activity/pass63_eligibility_gate.puml, activity/pass64_view_passport_offline.puml, activity/pass65_checkin_exceptions.puml, activity/pass66_challenge_authoring.puml, activity/pass69_report_generation.puml, activity/pass70_manual_override_audit.puml, class/pass20_guide_seam.puml, class/pass56_data_model.puml, component/pass64_passport_read_path.puml, component/pass65_checkin_service.puml, component/pass69_reports_service.puml, sequence/pass20_guide_conversation.puml, sequence/pass56_ai_scoring.puml, sequence/pass56_qr_checkin.puml, sequence/pass56_sso_sign_in.puml, sequence/pass63_enrollment.puml, sequence/pass68_mcq_auto_scoring.puml, sequence/pass70_live_event_dashboard.puml, sequence/pass71_reskin_config.puml, state/pass56_challenge_lifecycle.puml, usecase/pass56_use_cases.puml |
— |
| DOC-D | PASS-383, PASS-384, PASS-472, PASS-479, UC-1, UC-2, UC-3, UC-7, UC-8, UC-11, US-52, US-53, US-54, US-56, US-57, US-59 | backfill_assessment_scoring.feature — The shipped feature is reviewed; Scoring and override are documented; Each shipped story is traced to its requirements; The reflection and quiz surfaces are realigned to the design; backfill_authentication_enrollment.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The sign-in and enroll surfaces are realigned to the design; backfill_live_event_operations.feature — The shipped feature is reviewed; Override-with-audit and the live dashboard are documented; Each shipped story is traced to its requirements; The live dashboard surface is realigned to the design; backfill_passport_progress.feature — The shipped feature is reviewed; The feature has 4+1 documentation; Each shipped story is traced to its requirements; The passport and prize surfaces are realigned to the design; backfill_qr_checkin.feature — The shipped feature is reviewed; The core loop has a sequence diagram with exception paths; Each scenario is traced; The scan / check-in surface is realigned to the design; backfill_wellness_guide.feature — The shipped safety feature is reviewed; Crisis routing is documented as code-driven; Each scenario is traced; The guide surface is realigned to the design; design_of_record.feature — The design of record is imported and documented; A divergence inventory exists; Design tokens feed the frontend theme; Realignment work is routed to feature stories; first_party_default_theme.feature — The default skin renders CSUB's identity on a cold start; The CSUB theme is a first-class skin; Every shipped theme block declares where its values came from; The retired theme is gone from the frontend; The retired theme is gone from the backend; The retired theme is not a servable theme on a freshly seeded database; An existing database is migrated, not orphaned; The migration is safe on a database that already holds an admin-authored CSUB theme; The migration does not crash when both the retired and CSUB rows already exist; A freshly seeded database resolves the demo challenge's theme; Current-state documentation describes the shipped theme; loading_state_fade_through.feature — Data arriving before the reveal delay elapses never paints the loading state; A slow fetch fades the loading state in; Motion tokens exist as tokens, not inline values; Reduced motion still suppresses the flicker; Reduced motion collapses the fade but not the reveal delay; With motion allowed the same clock positions are still mid-fade; Button feedback is unaffected; An applied theme is remembered; A reload resumes on the remembered skin; A first-ever load still falls back to the default; The server remains the authority; Storage failure degrades quietly; The loading state never displaces the content it covers; The sign-in card is unchanged by a remembered non-default skin; No screen's loading branch paints a full-bleed slab; week_sheet_checkin_cta.feature — The week detail sheet restores the "Check in at event" CTA; The restored CTA reintroduces no self-service completion path; The CTA's scan path still completes the week; The CTA keeps a reachable touch target on a mobile viewport; The missed-week CTA uses the design's distinct "Catch up" label and icon |
test_backfill_assessment_scoring_bdd.py, test_backfill_authentication_enrollment_bdd.py, test_backfill_live_event_operations_bdd.py, test_backfill_passport_progress_bdd.py, test_backfill_qr_checkin_bdd.py, test_backfill_wellness_guide_bdd.py, test_design_of_record_bdd.py, test_first_party_default_theme_bdd.py, test_loading_state_fade_through_bdd.py, test_week_sheet_checkin_cta_bdd.py, frontend/src/components/LoadingScreen/LoadingScreen.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx |
activity/pass384_theme_id_migration.puml, activity/pass63_eligibility_gate.puml, activity/pass64_view_passport_offline.puml, activity/pass65_checkin_exceptions.puml, activity/pass70_manual_override_audit.puml, class/pass20_guide_seam.puml, class/pass383_motion_tokens_and_theme_storage.puml, class/pass384_theme_provenance.puml, class/pass56_data_model.puml, component/pass383_frame_sampler_tier.puml, component/pass472_overlay_mount_gate.puml, component/pass479_clock_probe_seam.puml, component/pass64_passport_read_path.puml, component/pass65_checkin_service.puml, sequence/pass20_guide_conversation.puml, sequence/pass383_loading_fade_through.puml, sequence/pass383_theme_bootstrap_persistence.puml, sequence/pass472_deterministic_overlay_probe.puml, sequence/pass479_reduced_motion_clock_probe.puml, sequence/pass56_ai_scoring.puml, sequence/pass56_qr_checkin.puml, sequence/pass56_sso_sign_in.puml, sequence/pass63_enrollment.puml, sequence/pass68_mcq_auto_scoring.puml, sequence/pass70_live_event_dashboard.puml, usecase/pass56_use_cases.puml |
— |
| FR-A1 | — | sign_in_fixed_csub_identity.feature — Sign-in's hero and CTA use fixed CSUB tokens, never the semester theme's; The sheet and its captions use a fixed neutral surface, not the theme surface; Sign-in renders fixed institutional copy, not useThemeCopy(); The installed PWA identity and the sign-in hero agree |
test_sign_in_fixed_csub_identity_bdd.py |
— | — |
| FR-A2 | — | sign_in_fixed_csub_identity.feature — Sign-in's hero and CTA use fixed CSUB tokens, never the semester theme's; The sheet and its captions use a fixed neutral surface, not the theme surface; Sign-in renders fixed institutional copy, not useThemeCopy(); The installed PWA identity and the sign-in hero agree |
test_sign_in_fixed_csub_identity_bdd.py |
— | — |
| FR-A4 | UC-1 | role_based_access_control.feature — Student is denied access to the admin builder; Admin reaches admin surfaces; Direct API access is authorized by role |
test_role_based_access_control_bdd.py |
— | — |
| FR-B1 | — | admin_topbar_responsive.feature — The topbar wraps instead of overflowing at phone widths; Every topbar control keeps a 44x44px touch target; The topbar's flex children are allowed to shrink and wrap; The topbar fits the viewport and the page does not scroll horizontally; challenge_builder_date_field_overflow.feature — Every two-up date field row clears the default min-width floor; The date field rows and the modal that holds them both bound their grid track minimums; Window start, Window end, Start date, and End date all keep a 44px touch target; The Window start / Window end and Start date / End date rows do not overlap or overflow the modal on a real phone viewport |
test_admin_topbar_responsive_bdd.py, test_challenge_builder_date_field_overflow_bdd.py |
— | — |
| FR-B4 | PASS-383, PASS-384, PASS-472, PASS-479, UC-2, UC-5 | first_party_default_theme.feature — The default skin renders CSUB's identity on a cold start; The CSUB theme is a first-class skin; Every shipped theme block declares where its values came from; The retired theme is gone from the frontend; The retired theme is gone from the backend; The retired theme is not a servable theme on a freshly seeded database; An existing database is migrated, not orphaned; The migration is safe on a database that already holds an admin-authored CSUB theme; The migration does not crash when both the retired and CSUB rows already exist; A freshly seeded database resolves the demo challenge's theme; Current-state documentation describes the shipped theme; live_challenge_visibility.feature — The admin can see which published challenge students are seeing; Editing a challenge nobody is seeing says so; A campus resolves exactly one active challenge; loading_state_fade_through.feature — Data arriving before the reveal delay elapses never paints the loading state; A slow fetch fades the loading state in; Motion tokens exist as tokens, not inline values; Reduced motion still suppresses the flicker; Reduced motion collapses the fade but not the reveal delay; With motion allowed the same clock positions are still mid-fade; Button feedback is unaffected; An applied theme is remembered; A reload resumes on the remembered skin; A first-ever load still falls back to the default; The server remains the authority; Storage failure degrades quietly; The loading state never displaces the content it covers; The sign-in card is unchanged by a remembered non-default skin; No screen's loading branch paints a full-bleed slab; offline_themed_fonts.feature — historical, superseded by PASS-221 (self_hosted_fonts.feature) — The themed display font renders with no connection; A missing font never blocks the passport; self_hosted_fonts.feature — The passport asks no third party for its type; A theme's display face renders before it has ever been seen; The passport survives a launch with no connection; The CSP names no outside origin for fonts; theme_applies_app_wide.feature — A signed-in student can resolve the active theme with no passport of their own; A signed-in admin can resolve the active theme too; A signed-in but non-current student still resolves the campus's theme; No session at all is refused, not served a themeless default; A campus with no active challenge resolves to the app default; Switching the active challenge's theme is reflected on the very next request; Sign-in's precondition (no session) is refused by the theme endpoint too |
test_first_party_default_theme_bdd.py, test_live_challenge_visibility_bdd.py, test_loading_state_fade_through_bdd.py, test_self_hosted_fonts_bdd.py, test_theme_applies_app_wide_bdd.py, frontend/src/components/LoadingScreen/LoadingScreen.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx |
activity/pass384_theme_id_migration.puml, class/pass383_motion_tokens_and_theme_storage.puml, class/pass384_theme_provenance.puml, component/pass383_frame_sampler_tier.puml, component/pass472_overlay_mount_gate.puml, component/pass479_clock_probe_seam.puml, sequence/pass383_loading_fade_through.puml, sequence/pass383_theme_bootstrap_persistence.puml, sequence/pass472_deterministic_overlay_probe.puml, sequence/pass479_reduced_motion_clock_probe.puml |
— |
| FR-B5 | PASS-379, UC-9 | challenge_authoring_by_hand.feature — The challenge builder offers no document import; The import endpoint is gone; Hand-authoring a challenge is unaffected; Route resolution is undisturbed by the removal; No application code sends a document to a model |
test_challenge_authoring_by_hand_bdd.py, frontend/src/components/admin/ChallengeBuilder/ChallengeBuilder.test.tsx |
activity/pass66_challenge_authoring.puml, class/pass56_data_model.puml |
— |
| FR-B6 | UC-5 | duplicate_challenge.feature — Duplicate creates an editable draft; Editing the copy does not affect the original |
test_duplicate_challenge_bdd.py |
— | — |
| FR-B7 | PASS-390, UC-21 | staff_notification_queue.feature — A proposed notification is not sent until staff act; Staff send a proposed notification; Staff skip a proposed notification; Staff edit the copy before sending; Staff compose a broadcast to students with an incomplete week; A challenge with notifications disabled sends nothing; A student cannot reach the notification queue; Every send decision is attributable |
test_staff_notification_queue_bdd.py |
activity/pass390_broadcast_audience.puml, class/pass390_notification_queue_model.puml, component/pass390_queue_authoring_seam.puml, sequence/pass390_staff_decision.puml, state/pass390_notification_decision_lifecycle.puml |
— |
| FR-C1 | UC-2, UC-5 | installed_app_exits.feature — An un-enrolled student launches the installed app; The way out survives having no browser chrome; Nothing published is still not a dead end; A student already in the challenge is not asked to join again; live_challenge_visibility.feature — The admin can see which published challenge students are seeing; Editing a challenge nobody is seeing says so; A campus resolves exactly one active challenge |
test_installed_app_exits_bdd.py, test_live_challenge_visibility_bdd.py |
— | — |
| FR-C2 | UC-2 | missed_weeks_stay_open.feature — A week I missed stays open instead of locking; Missing a week does not lock the weeks after it; A week whose window has not opened is still locked; Catching up requires the same evidence as any check-in; A catch-up completion counts toward the prize; Catching up cannot be done from the passport alone; passport_scan_fab_signout_overlap.feature — The Scan FAB's fixed offset clears the sign-out bar's tallest possible band, computed for wrapped pills; The screen's own bottom padding clears the full fixed-chrome stack; The Scan FAB and Sign out both keep a 44x44px touch target; The Scan FAB and the sign-out bar do not overlap in a real browser |
test_missed_weeks_stay_open_bdd.py, test_passport_scan_fab_signout_overlap_bdd.py |
— | — |
| FR-C4 | PASS-9, PASS-385, PASS-388, PASS-442, UC-2, UC-21 | app_shell_scroll_container.feature — Overscrolling past the top of sign-in never reveals the theme surface; Overscrolling past the bottom of sign-in never reveals the theme surface; The seam does not return under a different semester theme; A retracting URL bar leaves no band under the sheet; The browser chrome matches the front door; Full-height routes still scroll their content to the end; An inner scroller does not chain its scroll to the document; No full-height stylesheet mixes dynamic and static viewport units against the same root; app_update_activation.feature — A returning student is not left on a stale shell; An update arriving mid-session is announced, not silently applied; Accepting the update actually applies it; The service worker prompts before activating a new version; installed_app_exits.feature — An un-enrolled student launches the installed app; The way out survives having no browser chrome; Nothing published is still not a dead end; A student already in the challenge is not asked to join again; offline_themed_fonts.feature — historical, superseded by PASS-221 (self_hosted_fonts.feature) — The themed display font renders with no connection; A missing font never blocks the passport; pwa_browser_coverage.feature — Sign-in survives an active service worker; The app is installable in a real browser; Offline viewing serves the last-synced passport; A missing icon fails the browser tier; self_hosted_fonts.feature — The passport asks no third party for its type; A theme's display face renders before it has ever been seen; The passport survives a launch with no connection; The CSP names no outside origin for fonts; service_worker_push.feature — A push event renders a notification; Tapping a notification focuses an already-open app; Tapping with the app closed opens the passport; A payload naming a week deep-links to that week; The push handler ships without disturbing the update flow |
test_app_shell_scroll_container_bdd.py, test_app_update_activation_bdd.py, test_installed_app_exits_bdd.py, test_pwa_browser_coverage_bdd.py, test_self_hosted_fonts_bdd.py, test_service_worker_push_bdd.py, frontend/src/a11y.test.tsx, frontend/src/components/Passport/Passport.test.tsx, frontend/src/components/UpdateBanner/UpdateBanner.test.tsx, frontend/src/pwa/pushSw.test.tsx, frontend/src/pwa/useAppUpdate.test.tsx |
activity/pass385_overscroll_paint_path.puml, activity/pass388_notification_click_routing.puml, activity/pass442_update_wait_discipline.puml, class/pass9_update_surface.puml, component/pass385_app_shell_scroll_model.puml, component/pass385_overscroll_gate_seam.puml, component/pass388_push_sw_build_seam.puml, sequence/pass388_push_notification_click.puml, sequence/pass9_update_activation.puml |
docs/tier-deferrals/PASS-385-url-bar-retraction.md |
| FR-C5 | UC-2 | missed_weeks_stay_open.feature — A week I missed stays open instead of locking; Missing a week does not lock the weeks after it; A week whose window has not opened is still locked; Catching up requires the same evidence as any check-in; A catch-up completion counts toward the prize; Catching up cannot be done from the passport alone |
test_missed_weeks_stay_open_bdd.py |
— | — |
| FR-C6 | UC-2 | missed_weeks_stay_open.feature — A week I missed stays open instead of locking; Missing a week does not lock the weeks after it; A week whose window has not opened is still locked; Catching up requires the same evidence as any check-in; A catch-up completion counts toward the prize; Catching up cannot be done from the passport alone |
test_missed_weeks_stay_open_bdd.py |
— | — |
| FR-C7 | PASS-387, PASS-389, UC-21 | notification_preferences.feature — Reminders are off until I opt in; The pre-prompt precedes the browser prompt; Accepting the pre-prompt registers this device; The client can fetch the key it needs to subscribe; The key is withheld while the feature is dark; The preference categories are the ones the worker actually emits; I can keep new-week reminders and drop expiring ones; Turning reminders off stops delivery while permission remains granted; A blocked browser permission is explained, not retried; iOS in a browser tab is told to install first; The reminders control is usable on a phone; push_subscription_storage.feature — A signed-in student registers a device; Re-registering the same device does not duplicate it; A student cannot delete another student's subscription; The feature ships dark; Deleting a student removes their device subscriptions; The new routes are classified by the authorization sweep |
test_notification_preferences_bdd.py, test_push_subscription_storage_bdd.py, frontend/src/components/Reminders/Reminders.test.tsx |
activity/pass389_toggle_decision.puml, class/pass387_push_subscription_model.puml, class/pass389_notification_preference_seam.puml, component/pass389_preference_client_seam.puml, sequence/pass387_push_subscribe.puml, sequence/pass389_preprompt_opt_in.puml, state/pass389_reminder_permission_states.puml |
— |
| FR-C8 | PASS-388, PASS-391, PASS-392, UC-21 | in_app_reminder_banner.feature — A student without push sees the reminder in the app; A skipped notification never appears in-app; Dismissal persists across reloads; Another student's challenge reminder is not shown to me; The banner does not crowd out the passport on a phone; notification_scheduling.feature — A new week proposes a reminder; An ending challenge proposes an expiry reminder; A draft challenge proposes nothing; A proposal is made once, not once per run; Dispatch reaches every opted-in device exactly once; An overlapping run cannot double-send; A gone device is garbage-collected; Students who never opted in receive nothing; The scheduled run is the command the cluster actually runs; The dispatch ships dark; service_worker_push.feature — A push event renders a notification; Tapping a notification focuses an already-open app; Tapping with the app closed opens the passport; A payload naming a week deep-links to that week; The push handler ships without disturbing the update flow |
test_in_app_reminder_banner_bdd.py, test_notification_scheduling_bdd.py, test_service_worker_push_bdd.py, frontend/src/a11y.test.tsx, frontend/src/components/Passport/Passport.test.tsx, frontend/src/components/ReminderBanner/ReminderBanner.test.tsx, frontend/src/pwa/pushSw.test.tsx |
activity/pass388_notification_click_routing.puml, activity/pass391_proposal_run.puml, activity/pass392_banner_visibility_decision.puml, class/pass391_notification_scheduling_model.puml, class/pass392_in_app_feed_model.puml, component/pass388_push_sw_build_seam.puml, component/pass391_notification_entrypoints.puml, component/pass392_fallback_channel_seam.puml, deployment/pass391_scheduler_topology.puml, sequence/pass388_push_notification_click.puml, sequence/pass391_dispatch_fanout.puml, sequence/pass392_in_app_banner_render.puml, state/pass391_notification_lifecycle.puml |
— |
| FR-D1 | UC-3 | qr_event_binding.feature — Scanning a week's code credits exactly that week; A code from a previous challenge is rejected; A stale poster does not credit a replacement task; A legacy code without event identity is rejected; scanned_qr_only_checkin.feature — The passport offers no self-service completion; Scanning the live event QR still completes the week; A completion request carrying no scanned token is refused; A student cannot self-credit a week they did not attend; Staff verification remains available; An admin override remains available and audited; Reporting can trust the automatic bucket |
test_qr_event_binding_bdd.py, test_scanned_qr_only_checkin_bdd.py |
— | — |
| FR-D2 | UC-3 | qr_event_binding.feature — Scanning a week's code credits exactly that week; A code from a previous challenge is rejected; A stale poster does not credit a replacement task; A legacy code without event identity is rejected |
test_qr_event_binding_bdd.py |
— | — |
| FR-D3 | UC-4 | staff_scan_verification.feature — Attendant verifies a student for the active task; Attendant cannot verify a duplicate completion |
test_staff_scan_verification_bdd.py |
— | — |
| FR-D4 | UC-3, UC-4, UC-11 | live_event_dashboard.feature — Generate the event QR; Live count updates as students check in; qr_event_binding.feature — Scanning a week's code credits exactly that week; A code from a previous challenge is rejected; A stale poster does not credit a replacement task; A legacy code without event identity is rejected; scanned_qr_only_checkin.feature — The passport offers no self-service completion; Scanning the live event QR still completes the week; A completion request carrying no scanned token is refused; A student cannot self-credit a week they did not attend; Staff verification remains available; An admin override remains available and audited; Reporting can trust the automatic bucket; staff_scan_verification.feature — Attendant verifies a student for the active task; Attendant cannot verify a duplicate completion |
test_live_event_dashboard_bdd.py, test_qr_event_binding_bdd.py, test_scanned_qr_only_checkin_bdd.py, test_staff_scan_verification_bdd.py |
— | — |
| FR-D5 | UC-3 | rotating_qr_token.feature — Fresh token is accepted; Stale token is rejected; Forged or tampered token is rejected |
test_rotating_qr_token_bdd.py |
— | — |
| FR-D6 | UC-11 | manual_override.feature — Admin manually marks a completion; Admin overrides an existing completion |
test_manual_override_bdd.py |
— | — |
| FR-D7 | UC-3, UC-10 | manual_checkin_provenance.feature — The manual bucket separates audited overrides from legacy self-reports; scanned_qr_only_checkin.feature — The passport offers no self-service completion; Scanning the live event QR still completes the week; A completion request carrying no scanned token is refused; A student cannot self-credit a week they did not attend; Staff verification remains available; An admin override remains available and audited; Reporting can trust the automatic bucket; week_sheet_checkin_cta.feature — The week detail sheet restores the "Check in at event" CTA; The restored CTA reintroduces no self-service completion path; The CTA's scan path still completes the week; The CTA keeps a reachable touch target on a mobile viewport; The missed-week CTA uses the design's distinct "Catch up" label and icon |
test_manual_checkin_provenance_bdd.py, test_scanned_qr_only_checkin_bdd.py, test_week_sheet_checkin_cta_bdd.py |
— | — |
| FR-E1 | UC-6 | post_checkin_tip.feature — Tip is shown after a check-in; Tip is personalized by progress; Model calls are server-side with no PHI |
test_post_checkin_tip_bdd.py |
— | — |
| FR-E2 | PASS-378, UC-7 | guide_conversation.feature — Guide answers a wellness question from grounded content; Guide is skinned to the active theme; Conversations are minimally logged with no PHI; guide_dormant_for_pilot.feature — The wellness guide chat is dormant for the pilot; Crisis resources stay available with the guide disabled; The guide answers again when the flag is on |
test_guide_conversation_bdd.py, test_guide_dormant_for_pilot_bdd.py, frontend/src/App.test.tsx, frontend/src/components/BottomNav/BottomNav.test.tsx, frontend/src/components/Passport/KnowledgeCheck.test.tsx, frontend/src/components/admin/ChallengeBuilder/ScoreOverridePanel.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx, frontend/src/types/assessment.test.ts |
activity/pass378_guide_flag_gate.puml, class/pass378_reflection_review_seam.puml, sequence/pass378_reflection_review.puml |
— |
| FR-E3 | PASS-378, UC-7 | guide_dormant_for_pilot.feature — The wellness guide chat is dormant for the pilot; Crisis resources stay available with the guide disabled; The guide answers again when the flag is on; guide_guardrails.feature — Out-of-scope medical request is declined; Crisis signal triggers immediate escalation; Responses stay grounded and refuse to invent |
test_guide_dormant_for_pilot_bdd.py, test_guide_guardrails_bdd.py, frontend/src/App.test.tsx, frontend/src/components/BottomNav/BottomNav.test.tsx, frontend/src/components/Passport/KnowledgeCheck.test.tsx, frontend/src/components/admin/ChallengeBuilder/ScoreOverridePanel.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx, frontend/src/types/assessment.test.ts |
activity/pass378_guide_flag_gate.puml, class/pass378_reflection_review_seam.puml, sequence/pass378_reflection_review.puml |
— |
| FR-E4 | UC-8 | mcq_scoring.feature — Correct answer is scored instantly; Incorrect answer is scored instantly with feedback |
test_mcq_scoring_bdd.py |
— | — |
| FR-E5 | UC-8 | scoredby_contract.feature — Every scorer value the API serves is one the contract admits; The frontend contract cannot carry a scorer it does not know |
test_scoredby_contract_bdd.py |
— | — |
| FR-E6 | PASS-378, PASS-393, UC-6, UC-7, UC-8, UC-21 | guide_conversation.feature — Guide answers a wellness question from grounded content; Guide is skinned to the active theme; Conversations are minimally logged with no PHI; no_student_text_to_a_model.feature — A student can still write a free-text reflection; Submitting a reflection sends nothing to a third-party model; Staff review reflections without scoring them; A reviewed reflection records who reviewed it and when; Reflections do not distort learning-outcome means; Production boots without an Anthropic key; Production still refuses an unsafe configuration; The compliance record matches the runtime; post_checkin_tip.feature — Tip is shown after a check-in; Tip is personalized by progress; Model calls are server-side with no PHI; push_notification_privacy.feature — The subprocessor is disclosed; The no-subprocessor answer accounts for the push path; Notification payloads carry no student-identifying content; The device identifier has a stated retention rule; Every new requirement is traceable |
test_guide_conversation_bdd.py, test_no_student_text_to_a_model_bdd.py, test_post_checkin_tip_bdd.py, test_push_notification_privacy_bdd.py, frontend/src/App.test.tsx, frontend/src/components/BottomNav/BottomNav.test.tsx, frontend/src/components/Passport/KnowledgeCheck.test.tsx, frontend/src/components/admin/ChallengeBuilder/ScoreOverridePanel.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx, frontend/src/types/assessment.test.ts |
activity/pass378_guide_flag_gate.puml, class/pass378_reflection_review_seam.puml, component/pass393_payload_minimization_seam.puml, sequence/pass378_reflection_review.puml, sequence/pass393_push_egress_trust_boundary.puml |
— |
| FR-F1 | PASS-394, PASS-470, PASS-471, UC-10 | aggregate_report_csv_export.feature — Each report can be downloaded as CSV; The CSV agrees with the dashboard; A suppressed cell exports blank, never zero; An unanswered outcome tag is distinguishable from a hidden one; An empty report still exports a header row; Attendance still exports its structural zero rows; Filenames identify the challenge; A student role cannot reach the exports; All four CSV download controls fit a phone screen; participation_report.feature — Report shows enrollment and per-week completion; Report reflects new check-ins; per_event_export.feature — Every event gets a row, including the ones nobody attended; Rows carry the event's format and venue; Per-event method counts reconcile with the event's completions; Totals reconcile with the attendance report; A high-interest low-turnout event is visible; Response latency is blank when it cannot be computed; A suppressed row hides its method columns too; A student role cannot reach the export; The per-event download control fits a phone screen; reporting_dashboard_auto_refresh.feature — Numbers update while staff simply watch the screen; The dashboard says how current its numbers are; A failed read ages the stamp instead of blanking the screen; A dashboard that has stopped updating says so; A dashboard nobody is looking at stops querying; Closing the dashboard stops the polling; The freshness stamp fits the title block on a phone |
test_aggregate_report_csv_export_bdd.py, test_participation_report_bdd.py, test_per_event_export_bdd.py, test_reporting_dashboard_auto_refresh_bdd.py, frontend/src/components/admin/Reports/Reports.autorefresh.test.tsx, frontend/src/components/admin/Reports/Reports.csvexport.test.tsx, frontend/src/components/admin/Reports/Reports.perevent.test.tsx |
activity/pass470_per_event_export.puml, activity/pass471_aggregate_csv_export.puml, component/pass394_gherkin_vitest_execution_binding.puml, pass27_small_cell_suppression.puml, pass69_reports_service.puml, sequence/pass394_reporting_autorefresh.puml, state/pass394_read_freshness.puml |
— |
| FR-F2 | PASS-470, PASS-471, UC-10 | aggregate_report_csv_export.feature — Each report can be downloaded as CSV; The CSV agrees with the dashboard; A suppressed cell exports blank, never zero; An unanswered outcome tag is distinguishable from a hidden one; An empty report still exports a header row; Attendance still exports its structural zero rows; Filenames identify the challenge; A student role cannot reach the exports; All four CSV download controls fit a phone screen; attendance_report.feature — Attendance is broken down by method; Auto share is highlighted; per_event_export.feature — Every event gets a row, including the ones nobody attended; Rows carry the event's format and venue; Per-event method counts reconcile with the event's completions; Totals reconcile with the attendance report; A high-interest low-turnout event is visible; Response latency is blank when it cannot be computed; A suppressed row hides its method columns too; A student role cannot reach the export; The per-event download control fits a phone screen |
test_aggregate_report_csv_export_bdd.py, test_attendance_report_bdd.py, test_per_event_export_bdd.py, frontend/src/components/admin/Reports/Reports.csvexport.test.tsx, frontend/src/components/admin/Reports/Reports.perevent.test.tsx |
activity/pass470_per_event_export.puml, activity/pass471_aggregate_csv_export.puml, pass27_small_cell_suppression.puml, pass69_reports_service.puml |
— |
| FR-F3 | PASS-470, PASS-471, UC-10 | aggregate_report_csv_export.feature — Each report can be downloaded as CSV; The CSV agrees with the dashboard; A suppressed cell exports blank, never zero; An unanswered outcome tag is distinguishable from a hidden one; An empty report still exports a header row; Attendance still exports its structural zero rows; Filenames identify the challenge; A student role cannot reach the exports; All four CSV download controls fit a phone screen; engagement_report.feature — Report shows content views and guide usage; per_event_export.feature — Every event gets a row, including the ones nobody attended; Rows carry the event's format and venue; Per-event method counts reconcile with the event's completions; Totals reconcile with the attendance report; A high-interest low-turnout event is visible; Response latency is blank when it cannot be computed; A suppressed row hides its method columns too; A student role cannot reach the export; The per-event download control fits a phone screen |
test_aggregate_report_csv_export_bdd.py, test_engagement_report_bdd.py, test_per_event_export_bdd.py, frontend/src/components/admin/Reports/Reports.csvexport.test.tsx, frontend/src/components/admin/Reports/Reports.perevent.test.tsx |
activity/pass470_per_event_export.puml, activity/pass471_aggregate_csv_export.puml, pass27_small_cell_suppression.puml, pass69_reports_service.puml |
— |
| FR-F4 | PASS-378, PASS-471, UC-8, UC-10 | aggregate_report_csv_export.feature — Each report can be downloaded as CSV; The CSV agrees with the dashboard; A suppressed cell exports blank, never zero; An unanswered outcome tag is distinguishable from a hidden one; An empty report still exports a header row; Attendance still exports its structural zero rows; Filenames identify the challenge; A student role cannot reach the exports; All four CSV download controls fit a phone screen; learning_outcome_report.feature — Scores aggregate by outcome tag; Human-overridden scores are included; no_student_text_to_a_model.feature — A student can still write a free-text reflection; Submitting a reflection sends nothing to a third-party model; Staff review reflections without scoring them; A reviewed reflection records who reviewed it and when; Reflections do not distort learning-outcome means; Production boots without an Anthropic key; Production still refuses an unsafe configuration; The compliance record matches the runtime |
test_aggregate_report_csv_export_bdd.py, test_learning_outcome_report_bdd.py, test_no_student_text_to_a_model_bdd.py, frontend/src/App.test.tsx, frontend/src/components/BottomNav/BottomNav.test.tsx, frontend/src/components/Passport/KnowledgeCheck.test.tsx, frontend/src/components/admin/ChallengeBuilder/ScoreOverridePanel.test.tsx, frontend/src/components/admin/Reports/Reports.csvexport.test.tsx, frontend/src/components/admin/Reports/Reports.perevent.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx, frontend/src/types/assessment.test.ts |
activity/pass378_guide_flag_gate.puml, activity/pass471_aggregate_csv_export.puml, class/pass378_reflection_review_seam.puml, pass69_reports_service.puml, sequence/pass378_reflection_review.puml |
— |
| FR-F5 | UC-10 | prize_export.feature — Export contains only prize-eligible students; Export reflects derived eligibility |
test_prize_export_bdd.py |
— | — |
| FR-F6 | PASS-470, UC-10 | per_event_export.feature — Every event gets a row, including the ones nobody attended; Rows carry the event's format and venue; Per-event method counts reconcile with the event's completions; Totals reconcile with the attendance report; A high-interest low-turnout event is visible; Response latency is blank when it cannot be computed; A suppressed row hides its method columns too; A student role cannot reach the export; The per-event download control fits a phone screen; reporting_privacy.feature — Reports never contain PHI; Small aggregates respect privacy |
test_per_event_export_bdd.py, test_reporting_privacy_bdd.py, frontend/src/components/admin/Reports/Reports.perevent.test.tsx |
activity/pass470_per_event_export.puml, pass27_small_cell_suppression.puml, pass69_reports_service.puml |
— |
| FR-F7 | PASS-469, PASS-470, PASS-471, UC-10 | aggregate_report_csv_export.feature — Each report can be downloaded as CSV; The CSV agrees with the dashboard; A suppressed cell exports blank, never zero; An unanswered outcome tag is distinguishable from a hidden one; An empty report still exports a header row; Attendance still exports its structural zero rows; Filenames identify the challenge; A student role cannot reach the exports; All four CSV download controls fit a phone screen; engagement_detail_export.feature — Enrolled students who did nothing appear with zeros; A partial completer is not excluded; Method columns reconcile with weeks completed; Reflections never contribute a score; Prize eligibility agrees with the prize export; Re-exporting the same challenge is byte-identical; The export carries no PHI and no name; Pulling the export is recorded; A student role cannot reach the export; A challenge from another campus is not exportable; Both export buttons fit a phone screen; per_event_export.feature — Every event gets a row, including the ones nobody attended; Rows carry the event's format and venue; Per-event method counts reconcile with the event's completions; Totals reconcile with the attendance report; A high-interest low-turnout event is visible; Response latency is blank when it cannot be computed; A suppressed row hides its method columns too; A student role cannot reach the export; The per-event download control fits a phone screen |
test_aggregate_report_csv_export_bdd.py, test_engagement_detail_export_bdd.py, test_per_event_export_bdd.py, frontend/src/components/admin/Reports/Reports.csvexport.test.tsx, frontend/src/components/admin/Reports/Reports.perevent.test.tsx |
activity/pass469_engagement_detail_export.puml, activity/pass470_per_event_export.puml, activity/pass471_aggregate_csv_export.puml, pass27_small_cell_suppression.puml, pass56_data_model.puml, pass69_reports_service.puml |
— |
| INF-A1 | — | architecture_diagrams.feature — All five 4+1 views are present as PlantUML sources; Sources follow the house convention and render cleanly; The scenarios view traces to the use cases |
test_architecture_diagrams_bdd.py |
— | — |
| INF-A2 | — | architecture_diagrams.feature — All five 4+1 views are present as PlantUML sources; Sources follow the house convention and render cleanly; The scenarios view traces to the use cases |
test_architecture_diagrams_bdd.py |
— | — |
| INF-B1 | — | docs_image_ci.feature — The docs image builds with Docker as the only toolchain; A regression inside the docs image fails the build loudly; The docker-only docs build has its own pipeline step; docs_render_fallback.feature — Render falls back to docker when java is absent but docker is present; Render prints clear guidance when neither java nor docker is available; Render falls back to docker when graphviz is absent on an otherwise-java host; Render prints clear guidance when graphviz is absent and docker is unavailable; docs_site_build.feature — One command renders diagrams and builds the site; Toolchain is pinned and consistent; Build artifacts are not committed; Local preview is available |
test_docs_image_ci_bdd.py, test_docs_render_fallback_bdd.py, test_docs_site_build_bdd.py |
— | — |
| INF-B2 | — | docs_nav_labels.feature — No nav label carries a story key; The gate flags a label that carries a story key; A diagram gaining a story key needs no nav edit; Provenance is recorded in the traceability matrix |
test_docs_nav_labels_bdd.py |
— | — |
| INF-B3 | PASS-485, PASS-487, PASS-489, PASS-494 | convention_module_roots_stay_argument_derived.feature — A second existence gate re-added to any probe is invisible from a tmp tree carrying only the CLI; A path frozen at import time off the real repo would have hidden that gap; Every artifact those probes need still resolves to the committed file in the real tree; A frozen Path added tomorrow is discovered; The enumeration is derived and non-empty; Every discovered helper is callable, none silently dropped; cross_runner_render_seam_binding.feature — The Vitest render-seam proofs run under the same PR gate as the pytest suite; A hollowed-out render-seam proof fails the gate instead of passing silently; No pytest binding gates a render-seam proof on a Vitest test-name string; feature_file_bindings.feature — Every executable feature file has a binding module; The gate flags an unbound feature file that claims no successor; An archived feature names a successor that is itself bound; The gate flags an archive marker that has gone stale; An archived feature keeps its row in the traceability matrix; Every feature name the gate counts as bound resolves to a real feature file; The gate ignores a binding whose path resolves outside the features directory; The gate's own test modules contain no raw binding-call literal; require_assertions_roots_fail_loud.feature — A tree whose meta config and fixtures are gone fails instead of skipping; An absent Vitest binary is the only condition that still skips; The real meta config and fixtures still execute, so the honored-feature scenarios really run; tier_deferral_enforcement.feature — Every tier-deferral names an alternate enforcement artifact present in the tree; Every tier-deferral's alternate enforcement tier is invoked by a CI job; The require-assertions probe's frontend fallback exists and runs in the GitHub Actions workflow; tier_deferral_registry_completeness.feature — Every requires_* marker conftest.py defines has a tier deferral or allowlist entry; Every marker make test-api deselects has a tier deferral or allowlist entry; Every raw pytest.mark.skip call site has a tier deferral or allowlist entry; An allowlist entry without a justification is rejected; vitest_require_assertions_honored.feature — An assertion-free test body fails under the installed Vitest; A body asserting only through a third-party matcher still fails; A body with a real Vitest assertion passes under the same config; vitest_spec_binding_fails_loud.feature — A probe whose spec file no longer exists fails instead of skipping; An absent Vitest binary is the only condition that still skips; The unchanged spec still executes, so the Vitest-tier scenarios really run |
feature_binding_conventions.py, test_convention_module_roots_stay_argument_derived_bdd.py, test_cross_runner_render_seam_binding_bdd.py, test_feature_file_bindings_bdd.py, test_require_assertions_roots_fail_loud_bdd.py, test_tier_deferral_enforcement_bdd.py, test_tier_deferral_registry_completeness_bdd.py, test_vitest_require_assertions_honored_bdd.py, test_vitest_spec_binding_fails_loud_bdd.py |
activity/pass485_vitest_tier_skip_or_fail.puml, activity/pass487_require_assertions_roots_skip_or_fail.puml, activity/pass489_second_gate_observability.puml, activity/pass494_new_frozen_path_is_discovered.puml, component/pass485_vitest_tier_convention.puml, component/pass487_require_assertions_roots_parameterization.puml, component/pass489_convention_module_path_surface.puml, component/pass494_frozen_path_discovery_sweep.puml |
— |
| INF-C1 | — | container_images.feature — Each service has a Dockerfile that builds; The docs image builds hermetically; Images are tagged by commit |
test_container_images_bdd.py |
— | — |
| INF-D1 | — | k8s_manifests.feature — The full set applies from the kustomize base; Every managed Deployment is listed in the kustomization; The demo carries no real student data; Reachability is healthchecked |
test_k8s_manifests_bdd.py |
— | — |
| INF-D2 | — | k8s_manifests.feature — The full set applies from the kustomize base; Every managed Deployment is listed in the kustomization; The demo carries no real student data; Reachability is healthchecked |
test_k8s_manifests_bdd.py |
— | — |
| INF-E1 | PASS-302, PASS-307, PASS-318, PASS-325 | publish_provenance.feature — Merge publishes docs and demo; The published site carries the merge provenance; Publication is verified at the edge; Rollout waits gate on managed Deployments only; pytest_marker_registration.feature — Every @pass-NNN tag in the feature files is a registered marker; Collecting the suite emits no unknown-mark warning for a pass_* tag; A newly added @pass-NNN tag needs no pyproject.toml edit; An in-process pytest session leaves the parent's marker-registration config intact; A session that configures but never unconfigures does not desync a later pair |
test_publish_provenance_bdd.py, test_pytest_marker_registration_bdd.py |
— | — |
| INF-E2 | PASS-302, PASS-307, PASS-318, PASS-325 | publish_provenance.feature — Merge publishes docs and demo; The published site carries the merge provenance; Publication is verified at the edge; Rollout waits gate on managed Deployments only; pytest_marker_registration.feature — Every @pass-NNN tag in the feature files is a registered marker; Collecting the suite emits no unknown-mark warning for a pass_* tag; A newly added @pass-NNN tag needs no pyproject.toml edit; An in-process pytest session leaves the parent's marker-registration config intact; A session that configures but never unconfigures does not desync a later pair |
test_publish_provenance_bdd.py, test_pytest_marker_registration_bdd.py |
— | — |
| INF-E3 | PASS-399, PASS-416, PASS-420, PASS-423, PASS-442, PASS-485, PASS-487, PASS-489, PASS-494 | bitbucket_pipelines.feature — Every PR runs the gate; The gate runs on shell runners; The gate is reproducible locally; Merge to the default branch builds, pushes, and deploys (gated); The deploy freeze switch fails safe; Secrets come from repository variables; A manual deploy/rollback path exists; browser_tier_pr_gate.feature — The PR gate runs the mobile layout tier; The gate unsets DOCKER_HOST before the docker-dependent layout tier runs; The layout tier selects exactly the mobile browser scenarios, and nothing heavier; A pipeline that only runs by hand does not count as automatic enforcement; The remaining heavy browser slice stays off the gate; The layout tier reports its skip reason instead of a bare "s"; The still-deferred nightly tiers name an owner and a policy for going red; ci_gate_visibility.feature — make test-api reports skip reasons like test-model and test-cluster; Every gated pytest target in the Makefile reports skip reasons; convention_module_roots_stay_argument_derived.feature — A second existence gate re-added to any probe is invisible from a tmp tree carrying only the CLI; A path frozen at import time off the real repo would have hidden that gap; Every artifact those probes need still resolves to the committed file in the real tree; A frozen Path added tomorrow is discovered; The enumeration is derived and non-empty; Every discovered helper is callable, none silently dropped; cross_runner_render_seam_binding.feature — The Vitest render-seam proofs run under the same PR gate as the pytest suite; A hollowed-out render-seam proof fails the gate instead of passing silently; No pytest binding gates a render-seam proof on a Vitest test-name string; docs_image_ci.feature — The docs image builds with Docker as the only toolchain; A regression inside the docs image fails the build loudly; The docker-only docs build has its own pipeline step; gate_duration_instrumentation.feature — Every command in the PR gate is timed, but the shell-mutating lines are not; The timing wrapper never changes whether a command passed; The wrapper records a command's real elapsed time; The report names every timed command, slowest first; Every dependency the PR gate installs has a declared cache; The declared Playwright cache is the directory Playwright really uses; The PR gate step actually emits the duration report, not just wraps commands; The wrapper never swallows a command's real exit code or output; The report still names a command that failed, and the code it failed with; require_assertions_roots_fail_loud.feature — A tree whose meta config and fixtures are gone fails instead of skipping; An absent Vitest binary is the only condition that still skips; The real meta config and fixtures still execute, so the honored-feature scenarios really run; tier_deferral_enforcement.feature — Every tier-deferral names an alternate enforcement artifact present in the tree; Every tier-deferral's alternate enforcement tier is invoked by a CI job; The require-assertions probe's frontend fallback exists and runs in the GitHub Actions workflow; tier_deferral_registry_completeness.feature — Every requires_* marker conftest.py defines has a tier deferral or allowlist entry; Every marker make test-api deselects has a tier deferral or allowlist entry; Every raw pytest.mark.skip call site has a tier deferral or allowlist entry; An allowlist entry without a justification is rejected; update_activation_wait_discipline.feature — A slow activation past the base deadline is waited out, not failed; A genuinely settled stale shell is reported without burning the ceiling; A never-settling activation gives up at the ceiling naming what it saw; A reload the page triggers under the poll counts as progress; A runner declared slower is given proportionally longer to activate; The scale the browser E2E pipeline step declares is what carries a slow activation past the base deadline; vitest_require_assertions_honored.feature — An assertion-free test body fails under the installed Vitest; A body asserting only through a third-party matcher still fails; A body with a real Vitest assertion passes under the same config; vitest_spec_binding_fails_loud.feature — A probe whose spec file no longer exists fails instead of skipping; An absent Vitest binary is the only condition that still skips; The unchanged spec still executes, so the Vitest-tier scenarios really run |
test_bitbucket_pipelines_bdd.py, test_browser_tier_pr_gate_bdd.py, test_ci_gate_visibility_bdd.py, test_convention_module_roots_stay_argument_derived_bdd.py, test_cross_runner_render_seam_binding_bdd.py, test_docs_image_ci_bdd.py, test_gate_duration_instrumentation_bdd.py, test_require_assertions_roots_fail_loud_bdd.py, test_tier_deferral_enforcement_bdd.py, test_tier_deferral_registry_completeness_bdd.py, test_update_activation_wait_discipline_bdd.py, test_vitest_require_assertions_honored_bdd.py, test_vitest_spec_binding_fails_loud_bdd.py |
activity/pass399_layout_tier_selection.puml, activity/pass416_gate_step_timing.puml, activity/pass420_gate_report_rendering.puml, activity/pass442_update_wait_discipline.puml, activity/pass485_vitest_tier_skip_or_fail.puml, activity/pass487_require_assertions_roots_skip_or_fail.puml, activity/pass489_second_gate_observability.puml, activity/pass494_new_frozen_path_is_discovered.puml, component/pass399_browser_tier_pr_gate.puml, component/pass416_gate_duration_instrumentation.puml, component/pass485_vitest_tier_convention.puml, component/pass487_require_assertions_roots_parameterization.puml, component/pass489_convention_module_path_surface.puml, component/pass494_frozen_path_discovery_sweep.puml |
— |
| INF-F1 | PASS-381, UC-19 | no_live_model_tier.feature — No test is gated on model credentials; The default suite selector names no model tier; No test constructs a vendor SDK exception; The remaining deferral tiers still work; A genuine failure in the remaining suite still fails loud |
test_no_live_model_tier_bdd.py |
— | — |
| INF-F2 | PASS-381, UC-19 | no_live_model_tier.feature — No test is gated on model credentials; The default suite selector names no model tier; No test constructs a vendor SDK exception; The remaining deferral tiers still work; A genuine failure in the remaining suite still fails loud |
test_no_live_model_tier_bdd.py |
— | — |
| NFR-1 | PASS-470, UC-10 | per_event_export.feature — Every event gets a row, including the ones nobody attended; Rows carry the event's format and venue; Per-event method counts reconcile with the event's completions; Totals reconcile with the attendance report; A high-interest low-turnout event is visible; Response latency is blank when it cannot be computed; A suppressed row hides its method columns too; A student role cannot reach the export; The per-event download control fits a phone screen; reporting_privacy.feature — Reports never contain PHI; Small aggregates respect privacy |
test_per_event_export_bdd.py, test_reporting_privacy_bdd.py, frontend/src/components/admin/Reports/Reports.perevent.test.tsx |
activity/pass470_per_event_export.puml, pass27_small_cell_suppression.puml, pass69_reports_service.puml |
— |
| NFR-2 | UC-12 | browser_deployment_path.feature — A student signs in through the campus IdP and reaches their passport; A deep route survives a hard refresh; A deep route opened cold is not bounced back to sign-in; deployment_path.feature — The SPA and API are served as one origin; An API miss is a JSON 404, never the app shell; An unconfigured deployment refuses to start; Data survives a redeploy |
test_browser_deployment_path_bdd.py, test_deployment_path_bdd.py |
— | — |
| NFR-5 | PASS-383, PASS-385, PASS-399, PASS-472, PASS-479 | admin_topbar_responsive.feature — The topbar wraps instead of overflowing at phone widths; Every topbar control keeps a 44x44px touch target; The topbar's flex children are allowed to shrink and wrap; The topbar fits the viewport and the page does not scroll horizontally; app_shell_scroll_container.feature — Overscrolling past the top of sign-in never reveals the theme surface; Overscrolling past the bottom of sign-in never reveals the theme surface; The seam does not return under a different semester theme; A retracting URL bar leaves no band under the sheet; The browser chrome matches the front door; Full-height routes still scroll their content to the end; An inner scroller does not chain its scroll to the document; No full-height stylesheet mixes dynamic and static viewport units against the same root; browser_tier_pr_gate.feature — The PR gate runs the mobile layout tier; The gate unsets DOCKER_HOST before the docker-dependent layout tier runs; The layout tier selects exactly the mobile browser scenarios, and nothing heavier; A pipeline that only runs by hand does not count as automatic enforcement; The remaining heavy browser slice stays off the gate; The layout tier reports its skip reason instead of a bare "s"; The still-deferred nightly tiers name an owner and a policy for going red; challenge_builder_date_field_overflow.feature — Every two-up date field row clears the default min-width floor; The date field rows and the modal that holds them both bound their grid track minimums; Window start, Window end, Start date, and End date all keep a 44px touch target; The Window start / Window end and Start date / End date rows do not overlap or overflow the modal on a real phone viewport; loading_state_fade_through.feature — Data arriving before the reveal delay elapses never paints the loading state; A slow fetch fades the loading state in; Motion tokens exist as tokens, not inline values; Reduced motion still suppresses the flicker; Reduced motion collapses the fade but not the reveal delay; With motion allowed the same clock positions are still mid-fade; Button feedback is unaffected; An applied theme is remembered; A reload resumes on the remembered skin; A first-ever load still falls back to the default; The server remains the authority; Storage failure degrades quietly; The loading state never displaces the content it covers; The sign-in card is unchanged by a remembered non-default skin; No screen's loading branch paints a full-bleed slab; passport_scan_fab_signout_overlap.feature — The Scan FAB's fixed offset clears the sign-out bar's tallest possible band, computed for wrapped pills; The screen's own bottom padding clears the full fixed-chrome stack; The Scan FAB and Sign out both keep a 44x44px touch target; The Scan FAB and the sign-out bar do not overlap in a real browser |
test_admin_topbar_responsive_bdd.py, test_app_shell_scroll_container_bdd.py, test_browser_tier_pr_gate_bdd.py, test_challenge_builder_date_field_overflow_bdd.py, test_loading_state_fade_through_bdd.py, test_passport_scan_fab_signout_overlap_bdd.py, frontend/src/components/LoadingScreen/LoadingScreen.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx |
activity/pass385_overscroll_paint_path.puml, activity/pass399_layout_tier_selection.puml, class/pass383_motion_tokens_and_theme_storage.puml, component/pass383_frame_sampler_tier.puml, component/pass385_app_shell_scroll_model.puml, component/pass385_overscroll_gate_seam.puml, component/pass399_browser_tier_pr_gate.puml, component/pass472_overlay_mount_gate.puml, component/pass479_clock_probe_seam.puml, sequence/pass383_loading_fade_through.puml, sequence/pass383_theme_bootstrap_persistence.puml, sequence/pass472_deterministic_overlay_probe.puml, sequence/pass479_reduced_motion_clock_probe.puml |
docs/tier-deferrals/PASS-385-url-bar-retraction.md |
| NFR-6 | PASS-383, PASS-384, PASS-472, PASS-479 | first_party_default_theme.feature — The default skin renders CSUB's identity on a cold start; The CSUB theme is a first-class skin; Every shipped theme block declares where its values came from; The retired theme is gone from the frontend; The retired theme is gone from the backend; The retired theme is not a servable theme on a freshly seeded database; An existing database is migrated, not orphaned; The migration is safe on a database that already holds an admin-authored CSUB theme; The migration does not crash when both the retired and CSUB rows already exist; A freshly seeded database resolves the demo challenge's theme; Current-state documentation describes the shipped theme; loading_state_fade_through.feature — Data arriving before the reveal delay elapses never paints the loading state; A slow fetch fades the loading state in; Motion tokens exist as tokens, not inline values; Reduced motion still suppresses the flicker; Reduced motion collapses the fade but not the reveal delay; With motion allowed the same clock positions are still mid-fade; Button feedback is unaffected; An applied theme is remembered; A reload resumes on the remembered skin; A first-ever load still falls back to the default; The server remains the authority; Storage failure degrades quietly; The loading state never displaces the content it covers; The sign-in card is unchanged by a remembered non-default skin; No screen's loading branch paints a full-bleed slab; theme_applies_app_wide.feature — A signed-in student can resolve the active theme with no passport of their own; A signed-in admin can resolve the active theme too; A signed-in but non-current student still resolves the campus's theme; No session at all is refused, not served a themeless default; A campus with no active challenge resolves to the app default; Switching the active challenge's theme is reflected on the very next request; Sign-in's precondition (no session) is refused by the theme endpoint too |
test_first_party_default_theme_bdd.py, test_loading_state_fade_through_bdd.py, test_theme_applies_app_wide_bdd.py, frontend/src/components/LoadingScreen/LoadingScreen.test.tsx, frontend/src/theme/ThemePersistence.crossrunner.test.tsx |
activity/pass384_theme_id_migration.puml, class/pass383_motion_tokens_and_theme_storage.puml, class/pass384_theme_provenance.puml, component/pass383_frame_sampler_tier.puml, component/pass472_overlay_mount_gate.puml, component/pass479_clock_probe_seam.puml, sequence/pass383_loading_fade_through.puml, sequence/pass383_theme_bootstrap_persistence.puml, sequence/pass472_deterministic_overlay_probe.puml, sequence/pass479_reduced_motion_clock_probe.puml |
— |
| NFR-8 | UC-7 | guide_guardrails.feature — Out-of-scope medical request is declined; Crisis signal triggers immediate escalation; Responses stay grounded and refuse to invent |
test_guide_guardrails_bdd.py |
— | — |
| PR-A2 | — | explicit_staff_role_mapping.feature — Mapped staff member receives the admin role; Unmapped affiliation values grant no privileges; Role map changes require no code change |
test_explicit_staff_role_mapping_bdd.py |
— | — |
| PR-B1 | UC-14 | deploy_time_migration.feature — Kubernetes migrates before the app serves traffic; A developer can migrate a fresh checkout; The documented Run path works on a fresh checkout; production_database.feature — App runs against PostgreSQL; Schema changes are applied by migration; Startup does not mutate schema; schema_migrations.feature — A new column reaches a database that already exists; A database behind the code does not fail silently |
test_deploy_time_migration_bdd.py, test_production_database_bdd.py, test_schema_migrations_bdd.py |
— | — |
| PR-B2 | — | clean_production_data.feature — Production startup seeds nothing; Demo seeding requires an explicit dev flag; Reports contain no demo artifacts |
test_clean_production_data_bdd.py |
— | — |
| PR-B3 | PASS-38, UC-14 | backups_verified_restore.feature — Backups run automatically; Restore drill is executed before launch; Recovery objectives are stated |
test_backups_verified_restore_bdd.py |
activity/pass38_restore_drill.puml, component/pass38_backup_tooling.puml, deployment/pass38_backup_restore_topology.puml |
— |
| PR-C1 | — | secrets_management.feature — Production refuses default secrets; Secrets come from the managed store; Exposed secrets are rotated and invalidated; Local secret files are ignored by git |
test_secrets_management_bdd.py |
— | — |
| PR-C2 | UC-2 | self_hosted_fonts.feature — The passport asks no third party for its type; A theme's display face renders before it has ever been seen; The passport survives a launch with no connection; The CSP names no outside origin for fonts; web_session_hardening.feature — Session cookie is Secure and HttpOnly; HTTP requests are redirected to HTTPS; API docs are not public; Security headers are present |
test_self_hosted_fonts_bdd.py, test_web_session_hardening_bdd.py |
— | — |
| PR-C3 | — | rate_limiting.feature — Check-in endpoint throttles a hammering client; Auth callback is rate limited; Legitimate event bursts are not blocked |
test_rate_limiting_bdd.py |
— | — |
| PR-D1 | — | staging_deployment.feature — Containers build from the repository; Staging deploys from committed definitions; Production and staging match; Rollback is a defined operation; staging_rollback_drill.feature — Rollback restores the previous release on staging; A rollback that does not revert is caught by the drill; The rollback drill runs against a live staging cluster when enabled |
test_staging_deployment_bdd.py, test_staging_rollback_drill_bdd.py |
— | — |
| PR-D2 | — | monitoring_health_checks.feature — Downtime triggers an alert; Error-rate spike is visible; Logs are usable without exposing students |
test_monitoring_health_checks_bdd.py |
— | — |
| PR-F1 | — | accessibility_conformance.feature — Automated accessibility gate runs in CI; Manual assistive-technology pass is performed; ACR is produced |
test_accessibility_conformance_bdd.py |
— | — |
| PR-F2 | PASS-393, UC-21 | push_notification_privacy.feature — The subprocessor is disclosed; The no-subprocessor answer accounts for the push path; Notification payloads carry no student-identifying content; The device identifier has a stated retention rule; Every new requirement is traceable; vendor_security_package.feature — HECVAT Lite is complete and current; Every citation resolves to the line it names; Every configuration fact carries a citation; Dependency audit gates CI; DAST baseline is triaged; Authorization sweep passes; Session-guarded sweep passes; Every row is classified as stating a live value or not; A row declared to state no live value cannot quietly start stating one |
test_push_notification_privacy_bdd.py, test_vendor_security_package_bdd.py |
component/pass393_payload_minimization_seam.puml, sequence/pass393_push_egress_trust_boundary.puml |
— |
| PR-F3 | — | event_scale_load_validation.feature — Event burst succeeds within targets; Reports remain responsive at scale; Load-test results are recorded |
test_event_scale_load_validation_bdd.py |
— | — |
Gaps
Derived, not asserted — each line is a missing tag, binding, or artifact:
- FR-A3 appears in docs but no feature file carries its tag
- FR-A5 appears in docs but no feature file carries its tag
- FR-B2 appears in docs but no feature file carries its tag
- FR-B3 appears in docs but no feature file carries its tag
- FR-C3 appears in docs but no feature file carries its tag
- NFR-4 appears in docs but no feature file carries its tag
- PR-A1 appears in docs but no feature file carries its tag
- PR-E1 appears in docs but no feature file carries its tag
- PR-E2 appears in docs but no feature file carries its tag
- PR-E3 appears in docs but no feature file carries its tag
- SEC-5 appears in docs but no feature file carries its tag
- offline_themed_fonts.feature is bound by no test module
- diagram sources whose pass
key matches no @pass-tagged feature, so no row claims them (add the @pass-NNN tag to the owning feature file, or a # diagrams:note):activity/pass8_installed_app_exits.puml,activity/pass27_small_cell_suppression.puml,activity/pass30_fail_closed_boot.puml,activity/pass31_boot_schema_guard.puml,activity/pass37_startup_seed_gating.puml,activity/pass40_secret_validation.puml,activity/pass52_a11y_conformance_process.puml,activity/pass53_vendor_security_package.puml,activity/pass54_load_validation.puml,activity/pass56_crisis_routing.puml,activity/pass61_deploy_gating.puml,activity/pass78_docs_image_gate.puml,activity/pass79_render_fallback.puml,activity/pass218_citation_anchor_check.puml,activity/pass248_boot_schema_check.puml,activity/pass270_catchup_gate.puml,activity/pass271_scan_only_checkin.puml,activity/pass280_manual_provenance_split.puml,activity/pass291_week_sheet_checkin_cta.puml,class/pass18_tip_seam.puml,class/pass171_scoredby_contract.puml,component/pass4_rbac_enforcement.puml,component/pass6_e2e_tier.puml,component/pass31_migration_toolchain.puml,component/pass52_a11y_toolchain.puml,component/pass53_security_toolchain.puml,component/pass56_codebase_organization.puml,component/pass56_system_components.puml,component/pass57_docs_toolchain.puml,component/pass58_container_images.puml,component/pass59_deploy_pipeline.puml,component/pass60_provenance_stamp.puml,component/pass61_bitbucket_pipeline.puml,component/pass71_design_of_record.puml,component/pass78_docs_image_ci.puml,component/pass221_font_asset_pipeline.puml,component/pass225_feature_archive.puml,component/pass248_migration_entrypoints.puml,component/pass322_week_cta_render_gate.puml,component/pass327_cross_runner_render_seam_binding.puml,component/pass339_require_assertions_meta_gate.puml,component/pass346_ci_gate_skip_visibility.puml,component/pass348_tier_deferral_enforcement.puml,component/pass352_tier_deferral_registry_completeness.puml,deployment/pass30_compose_stack.puml,deployment/pass36_production_database_topology.puml,deployment/pass45_staging_topology.puml,deployment/pass53_dast_scan_topology.puml,deployment/pass54_load_topology.puml,deployment/pass56_demo_topology.puml,deployment/pass59_k8s_topology.puml,deployment/pass61_ci_topology.puml,deployment/pass248_migration_topology.puml,sequence/pass4_role_denied_access.puml,sequence/pass6_signin_with_service_worker.puml,sequence/pass7_font_cache.puml,sequence/pass11_rotating_qr.puml,sequence/pass14_staff_scan_verification.puml,sequence/pass16_active_challenge_resolution.puml,sequence/pass18_post_checkin_tip.puml,sequence/pass23_override_labelling.puml,sequence/pass34_staff_role_mapping.puml,sequence/pass41_session_hardening.puml,sequence/pass42_rate_limiting.puml,sequence/pass46_monitoring_alerting.puml,sequence/pass60_publish_verification.puml,sequence/pass163_browser_signin_refresh.puml,sequence/pass221_self_hosted_fonts.puml,sequence/pass248_deploy_time_migration.puml,sequence/pass273_theme_bootstrap.puml,sequence/pass275_rollback_drill.puml,sequence/pass339_require_assertions_probe.puml