Documentation
¶
Overview ¶
Package course — audit log implementation for academic integrity tracking.
Package course provides course and education management functionality for Prism.
This package implements course-based resource organization, template policy enforcement, and per-student budget tracking that enable instructors to manage classroom computing environments at scale.
Index ¶
- Variables
- type ArchiveResult
- type AuditAction
- type AuditEntry
- type AuditLog
- type BudgetExceededError
- type BulkEnrollRequest
- type CourseBudgetSummary
- type CourseFilter
- type CourseOverview
- type CreateCourseRequest
- type EnrollRequest
- type Manager
- func (m *Manager) AddApprovedTemplate(ctx context.Context, courseID, templateSlug string) error
- func (m *Manager) AppendCourseAudit(courseID string, entry AuditEntry) error
- func (m *Manager) BulkEnroll(ctx context.Context, courseID string, req *BulkEnrollRequest) (int, []error, error)
- func (m *Manager) CheckAndCloseExpiredCourses(ctx context.Context) ([]string, error)
- func (m *Manager) CheckStudentBudget(courseID, userID string, estimatedCost float64) error
- func (m *Manager) CloseCourse(ctx context.Context, courseID string) error
- func (m *Manager) CreateCourse(ctx context.Context, req *CreateCourseRequest) (*types.Course, error)
- func (m *Manager) DeleteCourse(ctx context.Context, courseID string) error
- func (m *Manager) DistributeBudget(ctx context.Context, courseID string, amountPerStudent float64) error
- func (m *Manager) EnrollMember(ctx context.Context, courseID string, req *EnrollRequest) (*types.ClassMember, error)
- func (m *Manager) GetBudgetSummary(ctx context.Context, courseID string) (*CourseBudgetSummary, error)
- func (m *Manager) GetCourse(ctx context.Context, courseID string) (*types.Course, error)
- func (m *Manager) GetCourseByCode(ctx context.Context, code, semester string) (*types.Course, error)
- func (m *Manager) GetCourseOverview(ctx context.Context, courseID string, ...) (*CourseOverview, error)
- func (m *Manager) GetMember(ctx context.Context, courseID, userID string) (*types.ClassMember, error)
- func (m *Manager) GetProvisioningContext(ctx context.Context, courseID, studentID, overrideTemplate string) (*ProvisioningContext, error)
- func (m *Manager) GetStudentDebugInfo(ctx context.Context, courseID, taID, studentID string) (*TADebugInfo, error)
- func (m *Manager) GetUsageReport(ctx context.Context, courseID string, ...) (*UsageReport, error)
- func (m *Manager) ImportRosterCSV(ctx context.Context, courseID string, rows []RosterRow, sendInvites bool) (int, []error, error)
- func (m *Manager) IsTemplateApproved(courseID, templateSlug string) bool
- func (m *Manager) ListArchiveEligibleCourses(ctx context.Context) ([]*types.Course, error)
- func (m *Manager) ListCourses(ctx context.Context, filter *CourseFilter) ([]*types.Course, error)
- func (m *Manager) ListMembers(ctx context.Context, courseID string, role *types.ClassRole) ([]types.ClassMember, error)
- func (m *Manager) MarkCourseArchived(ctx context.Context, courseID string) error
- func (m *Manager) QueryAuditLog(courseID, studentID string, since time.Time, limit int) ([]AuditEntry, error)
- func (m *Manager) RecordSpend(ctx context.Context, courseID, userID string, amount float64) error
- func (m *Manager) RemoveApprovedTemplate(ctx context.Context, courseID, templateSlug string) error
- func (m *Manager) ResetStudentInstance(ctx context.Context, courseID, taID string, req *TAResetRequest) error
- func (m *Manager) UnenrollMember(ctx context.Context, courseID, userID string) error
- func (m *Manager) UpdateCourse(ctx context.Context, courseID string, req *UpdateCourseRequest) (*types.Course, error)
- func (m *Manager) UpdateMember(ctx context.Context, courseID, userID string, role types.ClassRole, ...) error
- type ProvisioningContext
- type RosterFormat
- type RosterRow
- type StudentBudgetInfo
- type StudentStatus
- type StudentUsageRecord
- type TADebugInfo
- type TAResetRequest
- type UpdateCourseRequest
- type UsageReport
Constants ¶
This section is empty.
Variables ¶
var ( // ErrDuplicateCourse is returned when a course with the same code and semester already exists ErrDuplicateCourse = errors.New("course with this code and semester already exists") // ErrCourseNotFound is returned when the requested course does not exist ErrCourseNotFound = errors.New("course not found") // ErrMemberNotFound is returned when the requested member is not enrolled ErrMemberNotFound = errors.New("member not found") // ErrAlreadyEnrolled is returned when a user is already enrolled in the course ErrAlreadyEnrolled = errors.New("user is already enrolled in this course") // ErrNotAuthorized is returned when the caller lacks required permissions ErrNotAuthorized = errors.New("not authorized for this operation") // ErrCourseClosed is returned when an operation is attempted on a closed course ErrCourseClosed = errors.New("course is closed") )
Error constants
var ErrBudgetExceeded = errors.New("student budget exceeded")
ErrBudgetExceeded is the sentinel error for student budget enforcement.
Functions ¶
This section is empty.
Types ¶
type ArchiveResult ¶ added in v0.17.0
type ArchiveResult struct {
CourseID string `json:"course_id"`
InstancesStopped []string `json:"instances_stopped"`
SnapshotsCreated []string `json:"snapshots_created"`
Errors []string `json:"errors,omitempty"`
}
ArchiveResult is returned by the course archive operation.
type AuditAction ¶ added in v0.17.0
type AuditAction string
AuditAction categorises a course audit event.
const ( AuditActionEnroll AuditAction = "member.enroll" AuditActionUnenroll AuditAction = "member.unenroll" AuditActionBudgetSpend AuditAction = "budget.spend" AuditActionBudgetDistribute AuditAction = "budget.distribute" AuditActionTADebug AuditAction = "ta.debug" AuditActionTAReset AuditAction = "ta.reset" AuditActionInstanceLaunch AuditAction = "instance.launch" AuditActionInstanceStop AuditAction = "instance.stop" AuditActionCourseClose AuditAction = "course.close" AuditActionCourseArchive AuditAction = "course.archive" )
type AuditEntry ¶ added in v0.17.0
type AuditEntry struct {
Timestamp time.Time `json:"timestamp"`
CourseID string `json:"course_id"`
Actor string `json:"actor"` // who performed the action
Action AuditAction `json:"action"`
Target string `json:"target,omitempty"` // student ID, instance name, etc.
Detail map[string]interface{} `json:"detail,omitempty"`
}
AuditEntry is one line in the course audit JSONL log.
type AuditLog ¶ added in v0.17.0
type AuditLog struct {
// contains filtered or unexported fields
}
AuditLog manages the append-only JSONL audit log for a single course. The log lives at <stateDir>/<courseID>.jsonl.
func NewAuditLog ¶ added in v0.17.0
NewAuditLog returns an AuditLog for the given course. stateDir is the directory that holds all course audit files (e.g. ~/.prism/course-audits/).
func (*AuditLog) Append ¶ added in v0.17.0
func (l *AuditLog) Append(entry AuditEntry) error
Append writes one entry to the log. It opens the file, writes one JSON line, then closes it immediately — no buffering — so entries are never lost on crash. It is safe for concurrent use.
type BudgetExceededError ¶ added in v0.17.0
BudgetExceededError is returned when a launch would exceed the student's allocation.
func (*BudgetExceededError) Error ¶ added in v0.17.0
func (e *BudgetExceededError) Error() string
type BulkEnrollRequest ¶
type BulkEnrollRequest struct {
// Members is the list of members to enroll
Members []EnrollRequest `json:"members"`
// DefaultRole is applied to any member with no explicit role (default: student)
DefaultRole types.ClassRole `json:"default_role,omitempty"`
// SendInvites sends invitation emails to each enrolled member when true
SendInvites bool `json:"send_invites,omitempty"`
}
BulkEnrollRequest represents a request to enroll multiple members at once
type CourseBudgetSummary ¶
type CourseBudgetSummary struct {
// TotalBudget is the aggregate course cap in USD
TotalBudget float64 `json:"total_budget"`
// TotalSpent is the total spend across all students
TotalSpent float64 `json:"total_spent"`
// PerStudentDefault is the default allocation per student
PerStudentDefault float64 `json:"per_student_default"`
// Students lists per-student budget details
Students []StudentBudgetInfo `json:"students"`
}
CourseBudgetSummary summarizes budget usage across a course
type CourseFilter ¶
type CourseFilter struct {
// Owner filters to courses owned by this user ID
Owner string `json:"owner,omitempty"`
// Status filters by course lifecycle state
Status *types.CourseStatus `json:"status,omitempty"`
// Semester filters by semester identifier (exact match)
Semester string `json:"semester,omitempty"`
// Department filters by department
Department string `json:"department,omitempty"`
// Tags filters to courses matching all specified tags (AND logic)
Tags map[string]string `json:"tags,omitempty"`
}
CourseFilter defines criteria for filtering course lists
type CourseOverview ¶ added in v0.17.0
type CourseOverview struct {
CourseID string `json:"course_id"`
CourseCode string `json:"course_code"`
TotalStudents int `json:"total_students"`
ActiveInstances int `json:"active_instances"`
TotalBudgetSpent float64 `json:"total_budget_spent"`
Students []StudentStatus `json:"students"`
GeneratedAt time.Time `json:"generated_at"`
}
CourseOverview is the payload for GET /api/v1/courses/{id}/overview.
type CreateCourseRequest ¶
type CreateCourseRequest struct {
// Code is the institutional course code (required, e.g. "CS229")
Code string `json:"code"`
// Title is the full course title (required)
Title string `json:"title"`
// Department is the offering department (optional)
Department string `json:"department,omitempty"`
// Semester identifies the offering period (required, e.g. "Fall 2024")
Semester string `json:"semester"`
// SemesterStart is when the course begins (required)
SemesterStart time.Time `json:"semester_start"`
// SemesterEnd is when the course ends (required)
SemesterEnd time.Time `json:"semester_end"`
// GracePeriodDays is days after end before auto-cleanup (default 7)
GracePeriodDays int `json:"grace_period_days,omitempty"`
// Owner is the primary instructor's user ID (required)
Owner string `json:"owner"`
// ApprovedTemplates is the initial template whitelist (empty = all allowed)
ApprovedTemplates []string `json:"approved_templates,omitempty"`
// PerStudentBudget is the default per-student budget in USD (0 = unlimited)
PerStudentBudget float64 `json:"per_student_budget,omitempty"`
// TotalBudget is the aggregate course cap in USD (0 = unlimited)
TotalBudget float64 `json:"total_budget,omitempty"`
// Tags are optional metadata
Tags map[string]string `json:"tags,omitempty"`
// DefaultTemplate is the template slug auto-assigned to students on workspace provision.
DefaultTemplate string `json:"default_template,omitempty"`
// AutoProvisionOnEnroll creates a workspace for the student automatically upon
// enrollment when true (requires DefaultTemplate to be set).
AutoProvisionOnEnroll bool `json:"auto_provision_on_enroll,omitempty"`
}
CreateCourseRequest represents a request to create a new course
func (*CreateCourseRequest) Validate ¶
func (r *CreateCourseRequest) Validate() error
Validate validates the create course request
type EnrollRequest ¶
type EnrollRequest struct {
// UserID is the member's user identifier (required unless Email is provided)
UserID string `json:"user_id,omitempty"`
// Email is the member's email address (used as identifier and for invitations)
Email string `json:"email,omitempty"`
// DisplayName is the member's human-readable name
DisplayName string `json:"display_name,omitempty"`
// Role is the member's role in the course (default: student)
Role types.ClassRole `json:"role,omitempty"`
// BudgetLimit overrides the course default for this member (0 = use course default)
BudgetLimit float64 `json:"budget_limit,omitempty"`
}
EnrollRequest represents a request to enroll a single member
func (*EnrollRequest) Validate ¶
func (r *EnrollRequest) Validate() error
Validate validates the enroll request
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager handles course lifecycle, member enrollment, template policies, and budgets
func (*Manager) AddApprovedTemplate ¶
AddApprovedTemplate adds a template slug to the course whitelist
func (*Manager) AppendCourseAudit ¶ added in v0.17.0
func (m *Manager) AppendCourseAudit(courseID string, entry AuditEntry) error
AppendCourseAudit is the daemon-layer entry point for writing audit events that occur outside of manager methods (e.g. instance launch/stop events).
func (*Manager) BulkEnroll ¶
func (m *Manager) BulkEnroll(ctx context.Context, courseID string, req *BulkEnrollRequest) (int, []error, error)
BulkEnroll adds multiple members in one operation. Partial failures skip the bad row and continue; row errors are returned alongside the count.
func (*Manager) CheckAndCloseExpiredCourses ¶
CheckAndCloseExpiredCourses transitions courses past their end date + grace period to "closed"
func (*Manager) CheckStudentBudget ¶ added in v0.17.0
CheckStudentBudget returns a *BudgetExceededError if the student's accumulated spend plus estimatedCost would exceed their effective budget limit. Returns nil when the course has no budget configured, or when the member is not found (unknown users are not blocked — they will fail enrollment checks elsewhere).
func (*Manager) CloseCourse ¶
CloseCourse transitions a course to the "closed" state
func (*Manager) CreateCourse ¶
func (m *Manager) CreateCourse(ctx context.Context, req *CreateCourseRequest) (*types.Course, error)
CreateCourse creates a new course
func (*Manager) DeleteCourse ¶
DeleteCourse permanently removes a course record
func (*Manager) DistributeBudget ¶
func (m *Manager) DistributeBudget(ctx context.Context, courseID string, amountPerStudent float64) error
DistributeBudget sets per-student budget allocations for all currently enrolled students
func (*Manager) EnrollMember ¶
func (m *Manager) EnrollMember(ctx context.Context, courseID string, req *EnrollRequest) (*types.ClassMember, error)
EnrollMember adds a single member to the course
func (*Manager) GetBudgetSummary ¶
func (m *Manager) GetBudgetSummary(ctx context.Context, courseID string) (*CourseBudgetSummary, error)
GetBudgetSummary returns budget usage across the entire course
func (*Manager) GetCourseByCode ¶
func (m *Manager) GetCourseByCode(ctx context.Context, code, semester string) (*types.Course, error)
GetCourseByCode retrieves a course by its code and semester
func (*Manager) GetCourseOverview ¶ added in v0.17.0
func (m *Manager) GetCourseOverview(ctx context.Context, courseID string, instancesByStudent map[string][]types.Instance) (*CourseOverview, error)
GetCourseOverview returns a TA-facing summary of all students. instancesByStudent maps studentUserID → list of their instances (populated by the daemon handler from state, keyed by instance.ProjectID == courseID).
func (*Manager) GetMember ¶
func (m *Manager) GetMember(ctx context.Context, courseID, userID string) (*types.ClassMember, error)
GetMember returns a single member by user ID or email
func (*Manager) GetProvisioningContext ¶ added in v0.17.0
func (m *Manager) GetProvisioningContext(ctx context.Context, courseID, studentID, overrideTemplate string) (*ProvisioningContext, error)
GetProvisioningContext returns the template and budget context needed to launch a workspace for a student on behalf of an instructor/TA. overrideTemplate takes precedence over the course's DefaultTemplate. Returns ErrCourseNotFound, ErrMemberNotFound, or a descriptive error if no template is available.
func (*Manager) GetStudentDebugInfo ¶
func (m *Manager) GetStudentDebugInfo(ctx context.Context, courseID, taID, studentID string) (*TADebugInfo, error)
GetStudentDebugInfo returns read-only debug information for a student in a course. The caller (taID) must be a TA or instructor in the course.
func (*Manager) GetUsageReport ¶ added in v0.17.0
func (m *Manager) GetUsageReport(ctx context.Context, courseID string, instanceHoursByStudent map[string]float64, instanceCountByStudent map[string]int, ) (*UsageReport, error)
GetUsageReport aggregates semester-end usage for all students. instanceHoursByStudent and instanceCountByStudent are keyed by student UserID and are populated by the daemon handler from instance state history.
func (*Manager) ImportRosterCSV ¶
func (m *Manager) ImportRosterCSV(ctx context.Context, courseID string, rows []RosterRow, sendInvites bool) (int, []error, error)
ImportRosterCSV parses a CSV reader and bulk-enrolls students. CSV columns: email, display_name, budget (optional), role (optional) Header row is required. Malformed rows are skipped; errors returned per row.
func (*Manager) IsTemplateApproved ¶
IsTemplateApproved returns true if the template is allowed for this course. Returns true when the course has no whitelist (empty ApprovedTemplates = unrestricted).
func (*Manager) ListArchiveEligibleCourses ¶ added in v0.17.0
ListArchiveEligibleCourses returns courses in "closed" state whose SemesterEnd + GracePeriodDays has passed and that have not yet been archived.
func (*Manager) ListCourses ¶
ListCourses retrieves all courses matching the optional filter
func (*Manager) ListMembers ¶
func (m *Manager) ListMembers(ctx context.Context, courseID string, role *types.ClassRole) ([]types.ClassMember, error)
ListMembers returns enrolled members, optionally filtered by role
func (*Manager) MarkCourseArchived ¶ added in v0.17.0
MarkCourseArchived transitions a course from "closed" to "archived" and saves.
func (*Manager) QueryAuditLog ¶ added in v0.17.0
func (m *Manager) QueryAuditLog(courseID, studentID string, since time.Time, limit int) ([]AuditEntry, error)
QueryAuditLog returns audit entries for a course, filtered by optional studentID, since timestamp, and entry limit. Returns nil (no error) when no log file exists yet.
func (*Manager) RecordSpend ¶
RecordSpend increments the spend counter for a member
func (*Manager) RemoveApprovedTemplate ¶
RemoveApprovedTemplate removes a template slug from the course whitelist
func (*Manager) ResetStudentInstance ¶
func (m *Manager) ResetStudentInstance(ctx context.Context, courseID, taID string, req *TAResetRequest) error
ResetStudentInstance records a TA reset request in the course audit trail. The actual AWS snapshot + re-provision is handled by the daemon handler (requires awsManager).
func (*Manager) UnenrollMember ¶
UnenrollMember removes a member from the course
func (*Manager) UpdateCourse ¶
func (m *Manager) UpdateCourse(ctx context.Context, courseID string, req *UpdateCourseRequest) (*types.Course, error)
UpdateCourse applies a partial update to an existing course
type ProvisioningContext ¶ added in v0.17.0
type ProvisioningContext struct {
CourseID string
StudentID string
StudentEmail string
Template string
BudgetLimit float64
BudgetSpent float64
}
ProvisioningContext carries what the daemon needs to launch a workspace on behalf of a student.
type RosterFormat ¶ added in v0.17.0
type RosterFormat string
RosterFormat identifies the source LMS format of an uploaded CSV file.
const ( // RosterFormatPrism is the native Prism CSV format (email,display_name,role,budget). RosterFormatPrism RosterFormat = "prism" // RosterFormatCanvas is the Canvas LMS student roster export format. RosterFormatCanvas RosterFormat = "canvas" // RosterFormatBlackboard is the Blackboard Learn user list export format. RosterFormatBlackboard RosterFormat = "blackboard" )
type RosterRow ¶
type RosterRow struct {
// Email is the student's email address (required)
Email string
// DisplayName is the student's name
DisplayName string
// Budget is the per-student budget override in USD (0 = use course default)
Budget float64
// Role is the member role (optional; defaults to "student")
Role string
}
RosterRow maps a single row from a CSV roster file
func ParseBlackboardCSV ¶ added in v0.17.0
ParseBlackboardCSV parses a Blackboard Learn user list CSV export into RosterRows. Expected header columns: Last Name, First Name, Username (user ID), Email.
func ParseCanvasCSV ¶ added in v0.17.0
ParseCanvasCSV parses a Canvas LMS student roster CSV export into RosterRows. Expected header columns (case-insensitive): Student (display name), SIS Login ID (user ID), Email Address (email). Rows where the Student column equals "Points Possible" are skipped.
func ParseRosterCSV ¶
ParseRosterCSV parses CSV bytes into RosterRow slice. Expected columns (with header): email, display_name, budget, role
type StudentBudgetInfo ¶
type StudentBudgetInfo struct {
// UserID is the student's identifier
UserID string `json:"user_id"`
// DisplayName is the student's name
DisplayName string `json:"display_name,omitempty"`
// Email is the student's email
Email string `json:"email,omitempty"`
// BudgetLimit is the student's spending cap
BudgetLimit float64 `json:"budget_limit"`
// BudgetSpent is how much has been consumed
BudgetSpent float64 `json:"budget_spent"`
// Remaining is BudgetLimit - BudgetSpent (negative if over budget)
Remaining float64 `json:"remaining"`
}
StudentBudgetInfo holds per-student budget details
type StudentStatus ¶ added in v0.17.0
type StudentStatus struct {
UserID string `json:"user_id"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"`
Instances []types.Instance `json:"instances"`
BudgetSpent float64 `json:"budget_spent"`
BudgetLimit float64 `json:"budget_limit"`
BudgetStatus string `json:"budget_status"` // "ok" | "warning" (>80%) | "exceeded"
LastActive *time.Time `json:"last_active,omitempty"`
}
StudentStatus summarises a single student's current state for the TA dashboard.
type StudentUsageRecord ¶ added in v0.17.0
type StudentUsageRecord struct {
UserID string `json:"user_id"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"`
TotalSpent float64 `json:"total_spent"`
BudgetLimit float64 `json:"budget_limit"`
InstanceHours float64 `json:"instance_hours"`
InstanceCount int `json:"instance_count"`
}
StudentUsageRecord is one row in the semester usage report.
type TADebugInfo ¶
type TADebugInfo struct {
// CourseID is the course this debug info belongs to
CourseID string `json:"course_id"`
// StudentID is the user ID of the student
StudentID string `json:"student_id"`
// StudentEmail is the student's email
StudentEmail string `json:"student_email,omitempty"`
// Instances lists the student's current instances
Instances []types.Instance `json:"instances"`
// RecentEvents lists the last N instance state changes and errors
RecentEvents []string `json:"recent_events,omitempty"`
// BudgetSpent is how much of the student's budget has been consumed
BudgetSpent float64 `json:"budget_spent"`
// BudgetLimit is the student's spending cap (0 = unlimited)
BudgetLimit float64 `json:"budget_limit"`
}
TADebugInfo is the read-only view a TA gets of a student's environment
type TAResetRequest ¶
type TAResetRequest struct {
// StudentID is the user ID (or email) of the student whose instance to reset
StudentID string `json:"student_id"`
// Reason is a required explanation for the audit log
Reason string `json:"reason"`
// BackupRetentionDays is how long to keep the pre-reset snapshot (default 7)
BackupRetentionDays int `json:"backup_retention_days,omitempty"`
}
TAResetRequest represents a request to reset a student's instance
type UpdateCourseRequest ¶
type UpdateCourseRequest struct {
// Title updates the course title
Title *string `json:"title,omitempty"`
// Department updates the department
Department *string `json:"department,omitempty"`
// SemesterEnd extends or shortens the semester end date
SemesterEnd *time.Time `json:"semester_end,omitempty"`
// GracePeriodDays updates the cleanup grace period
GracePeriodDays *int `json:"grace_period_days,omitempty"`
// ApprovedTemplates replaces the template whitelist (nil = no change, empty slice = clear all)
ApprovedTemplates []string `json:"approved_templates,omitempty"`
// PerStudentBudget updates the default per-student budget
PerStudentBudget *float64 `json:"per_student_budget,omitempty"`
// TotalBudget updates the aggregate course budget cap
TotalBudget *float64 `json:"total_budget,omitempty"`
// Tags replaces the tag set
Tags map[string]string `json:"tags,omitempty"`
// Status manually sets the course status
Status *types.CourseStatus `json:"status,omitempty"`
// DefaultTemplate updates the default workspace template
DefaultTemplate *string `json:"default_template,omitempty"`
// AutoProvisionOnEnroll updates the auto-provision setting
AutoProvisionOnEnroll *bool `json:"auto_provision_on_enroll,omitempty"`
}
UpdateCourseRequest represents a request to update an existing course. All fields are optional pointers; nil means "do not change".
type UsageReport ¶ added in v0.17.0
type UsageReport struct {
CourseID string `json:"course_id"`
CourseCode string `json:"course_code"`
Semester string `json:"semester"`
TotalSpent float64 `json:"total_spent"`
TotalBudget float64 `json:"total_budget"`
Students []StudentUsageRecord `json:"students"`
GeneratedAt time.Time `json:"generated_at"`
}
UsageReport is the payload for GET /api/v1/courses/{id}/report.