DrawSplat™ Compliance — Operations Guide
Current documentation: SplatWorks Wiki — setup, installation, licensing, pricing, OAuth, and district privacy review.
Operator-facing technical notes for the Apps Script Compliance Console. Its Drive/Sheets controls do not automatically manage district MariaDB/MySQL private boards or classroom records. Use the current console guide and district review reference for workflow and scope. Roadmaps describe historical development, not the authoritative current setup.
Configuration source of truth
A single JSON file holds the defaults: compliance.config.json at the repo root.
- The static page loader (
assets/js/app.js) fetches it on boot and exposes the parsed object onwindow.complianceConfig. Listen for thecompliance-config-readyevent. - The Apps Script backend keeps a parallel snapshot under the
COMPLIANCE_CONFIGScript Property. The Day 3.2 Privacy Settings panel writes to it; the District Privacy Packet generator (Day 3.6) reads from it. - If the Apps Script property is missing, the generator falls back to a placeholder note.
When you change defaults in compliance.config.json, commit and push so future deployments pick the new defaults up. Existing deployments keep using their Script-Property snapshot until an admin re-saves it.
Apps Script setup additions
After updating apps-script/Code.gs, in the Apps Script editor:
- Save the file.
- Run setup() once. It creates two new sheet tabs in addition to the existing ones:
Audit— the Activity Records log.ParentRequests— tickets from the Family Access Tools form.
- Open Project Settings > Script Properties and confirm
ADMIN_PASSCODEis set (you already need this for any moderator action). - Optionally set
COMPLIANCE_ADMIN_EMAIL— new parent requests trigger a notification email to this address. - Deploy > Manage deployments > Edit > New version > Deploy to publish the new endpoints.
What endpoints are now available
The whiteboard Apps Script Web App handles all of these in addition to the existing board / room / template / turn-in actions.
| Action | Method | Auth | Purpose |
|---|---|---|---|
auditList |
GET | passcode |
Recent Activity Records (filter by actionFilter, actor, since, limit) |
parentRequestList |
GET | passcode |
All Family Access tickets, newest first |
privacyPacket |
GET | passcode |
Returns base64-encoded ZIP (district privacy packet) |
parentRequest |
POST | none | Submit a new family request from the parent-facing form |
parentRequestDecide |
POST | passcode |
Approve / deny / complete a parent request |
The passcode value is the ADMIN_PASSCODE script property. The Teacher Admin browser prompts for it the first time and caches it in localStorage only.
Family Access Tools (parent-facing)
- Public-facing page:
/parents/index.html. - Form posts to the configured Apps Script Web App URL (the same one Teacher Admin uses).
- A successful submission creates a ticket with status
pending_verificationand writes aPARENT_REQUEST_CREATEDevent to Activity Records. - If
COMPLIANCE_ADMIN_EMAILis set, the admin gets an email. The teacher issues a verification code through the school’s existing parent-communications channel; see the current Console guide.
If the parent visits the public site and no Web App URL is configured in their browser, the form gives them a clear "email the school directly" message instead of failing silently. This means parents using drawsplat.org itself won't reach a backend — the form is for districts running their own deployment with their own Apps Script.
Internal Compliance Console
Open admin/compliance.html directly after unlocking the admin gate. The current Console includes Safety Review, Family Access Tools, Student Age Band Lock, Use Limits, Retention, Privacy Settings, Activity Records, and District Privacy Packet. Among its operator tools:
- Activity Records — opens to the last 200 events. Filter by action, download CSV.
- District Privacy Packet — one-click ZIP download containing:
compliance-config.json(the snapshot)activity-records.csv(last 90 days)parent-requests.csv(all tickets)README.txt(what's inside + companion URLs)
Use the Console guide for each section’s setup, access, and verification steps.
Activity Records schema
The Audit sheet has these columns:
| Column | Purpose |
|---|---|
id |
UUID per event |
timestamp |
ISO 8601 UTC |
actor |
Email or display name of the actor (best-effort) |
actorRole |
student, teacher, admin, parent, system |
action |
One of the audit.actions values in the config |
entityType |
board, image, parent_request, privacy_packet, etc. |
entityId |
Local UUID of the affected item |
before |
JSON string of prior state (optional) |
after |
JSON string of new state (optional) |
userAgent |
Caller's User-Agent if known (optional) |
Use logEvent_(action, payload) from anywhere in the Apps Script when you add a new compliance-relevant action. The helper never throws, so audit failures cannot break the calling endpoint.
Operator playbook
| Task | Where |
|---|---|
| Review a flagged parent request | Internal Compliance Console → Family Access Tools (Day 2.4 next) |
| Pull a CSV of last week's events | Activity Records section → filter since=YYYY-MM-DD → Download CSV |
| Produce a packet for a district reviewer | District Privacy Packet section → Download District Privacy Packet |
| Invalidate all stored admin passcodes in the browser | Have the operator clear localStorage for drawsplat.complianceAdminPasscode |
| Rotate the admin passcode | Apps Script > Project Settings > Script Properties > edit ADMIN_PASSCODE. Re-deploy. |
Student Age Band Lock (Days 2.1 / 2.2)
A new Users sheet tracks one row per (student, class) pair. Columns: id, studentName, className, email, ageBand, ageSource, ageLocked, ageChangedBy, ageChangedAt, ageChangeReason, parentCodeHash, parentCodeExpiresAt, lastSeen, createdAt, notes.
- Rows auto-populate when a student submits a turn-in (
upsertUserFromTurnIn_). - Allowed bands:
under_13,13_to_17,18_plus,unknown_minor. Default for new rows isunknown_minor. ageLockeddefaults totrueand only the admin can changeageBand. Every change requires a reason and emits anAGE_BAND_CHANGEDaudit event.- The internal Compliance Console → Student Age Band Lock section lists students, shows current band, and offers a dropdown to change it (prompts for a reason on submit).
Teacher-Issued Parent Verification Code (Day 2.5)
Each student row can carry a single-use 8-character verification code. Workflow:
- In the internal Compliance Console → Student Age Band Lock, click Issue Parent Code next to a student.
- The browser shows the cleartext code in a one-time prompt. The server stores only a SHA-256 hash (with
PASSWORD_SALT) and an expiry 14 days out. - The teacher shares the code with the parent through the school's existing parent-communications channel (email, paper, etc.).
- The parent enters the code in the Verification code field on
/parents/index.htmlwhen submitting a request. - On submission, if the code matches and is unexpired, the ticket starts in
verifiedstatus (skippingpending_verification). The code is then cleared so it cannot be reused. - The
PARENT_CODE_ISSUEDevent is logged when issued; the request itself logsverified: trueon successful match.
If a code is lost or compromised, click Issue Parent Code again — the new code replaces the old one.
Data Export (Day 2.6)
Clicking Export next to a student in the Age Band Lock section:
- Prompts the admin for a reason (logged in Activity Records).
- Calls
exportStudentDataon the Apps Script backend. The server gathers:- Every board in the
Boardssheet whoseclassNamematches and whosetitlecontains the student's name, including JSON and PNG. - Every turn-in in
TurnInskeyed by (student, class). - The student's
Usersrow, minus any hashed credentials.
- Every board in the
- Builds a ZIP with
boards/,turnins/,manifest.json(machine-readable summary), and aREADME.txt(human-readable). - Returns it as base64; the browser saves it to your Downloads folder.
- Logs
DATA_EXPORTwith counts and the supplied reason.
The export is admin-initiated. For verified parent requests, the typical workflow is: parent submits a request with their verification code → request shows up in Family Access Tools as verified → admin clicks Export on that student → emails the ZIP to the parent through the school's normal channel.
Data Deletion (Day 2.7)
Clicking Delete Data next to a student in the Age Band Lock section:
- Confirms the action (twice — once for the warning, once for a reason that goes into Activity Records).
- Trashes Drive files for every matching turn-in and (if a class is supplied) every matching board.
- Removes the user row and all matching turn-in / board rows.
- Emits a
DATA_DELETEDaudit event with counts of items removed and the supplied reason.
This action is permanent from the DrawSplat side. Drive files go to the trash and stay recoverable from Drive for the standard 30-day window.
Retention Policy & Cleanup (Days 3.7 / 3.8)
The Compliance Console → Retention Policy & Cleanup section drives three thresholds:
retention.boards.archiveAfterDays— boards untouched longer than this move to anArchiveDrive subfolder.retention.boards.deleteAfterDays— boards untouched longer than this are trashed and removed from theBoardssheet.retention.audit.keepDays— rows in theAuditsheet older than this are pruned.
The settings are stored in the COMPLIANCE_CONFIG Script Property. Save Settings writes them; Load Current re-reads from the server.
Manual run. Click Run Cleanup Now to execute the pass immediately. Returns counts and writes a RETENTION_ACTION audit event.
Daily trigger. Click Install Daily Trigger to schedule dailyRetentionCleanup() via Apps Script time-driven triggers (runs at 02:00 server time). Click Remove Trigger to stop the schedule. Manual runs still work without the trigger.
The status line below the buttons shows the last run's summary and whether the trigger is currently installed.
District-Wide Safety Defaults (Day 3.9)
The single COMPLIANCE_CONFIG Script Property is the authoritative server-side configuration. Every save through saveBoard_ / saveRoom_ reads safetyConfig_() which consults this property; the retention cleanup reads it on each run. So once an admin changes the config through the Console, every subsequent teacher's save (and every nightly cleanup pass) honors the new settings — there is no per-teacher override layer to keep in sync.
Reset to Defaults rewrites the property with the built-in defaults baked into Code.gs (DS_DEFAULT_COMPLIANCE). Useful if a previous edit landed in a bad state.
Compliance Console Panels (Days 3.1–3.5)
The internal Compliance Console is split into seven collapsible sections. Each section reads from and writes to the same COMPLIANCE_CONFIG Script Property via getCompliance / setCompliance. Saved changes propagate to every subsequent saveBoard_ / saveRoom_ call automatically.
| Section | What it controls |
|---|---|
| Safety Review | Text filter on/off, blockOnMatch, link allowlist on/off, blockUnapproved. Plus the existing board/room freeze controls. |
| Image Approval Queue | Teacher-approval-required toggle, max upload size, plus the pending / approved / rejected review queue (thumbnail + Approve / Reject). Student uploads land here when teacher approval is required; approved images appear on the student board within ~8 seconds. See "Image Approval Queue (Days 1.3 / 1.4)" below. |
| Family Access Tools | Parent portal enabled, request form enabled, verification method (teacher_code, district_roster, admin_approval). Plus the existing request queue. |
| Student Age Band Lock | Per-student age band table (see Day 2.1 / 2.2 section above). |
| Use Limits | Daily seconds, session seconds, allowed hours, weekend toggle, enabled flag. Enforcement code lands in Days 2.8–2.9; the config is pre-stageable now. |
| Retention Policy & Cleanup | Archive / delete / audit-keep thresholds + manual run + daily trigger install (see retention section above). |
| Privacy Settings | Read-only declarations from the privacy section of the config: storage location, encryption, third-party services, "AI training" / advertising / data-sold flags, and what the District Privacy Packet includes. |
| Activity Records | Filter by action, actor, and date range. Download as CSV or JSON. |
| District Privacy Packet | One-click ZIP download (see Day 3.6 section above). |
For advanced edits (anything not exposed as a checkbox or input), use Open Raw Config Editor in the Privacy Settings panel. The dialog shows the entire merged config as JSON; saving runs through the same setCompliance filter, so only the known top-level sections persist.
Image Approval Queue (Days 1.3 / 1.4)
When safety.images.teacherApprovalRequired is true (the default), every image that a student uploads to a board is held in a pending queue until a teacher approves it. The flow:
- Client — whiteboard (
assets/js/app.js). Whenboard.mode === 'student'and a Google Apps Script URL is configured, the four upload paths (main image button, paste-as-image, sticky-note attached image, custom sticker) callsubmitImageForApproval(dataUrl, fileName)instead of embedding the data URL directly. The function POSTs{action: 'uploadImage', dataUrl, fileName, boardId, roomId, uploadedBy, uploaderRole}to the script. Teacher mode and missing-script-URL deployments bypass the queue. - Server —
uploadImage_. Decodes the base64, validates mime (allowedMimeTypes/blockedMimeTypes) and size (maxBytes), writes the bytes to theDrawSplatTM SavesDrive folder, and appends a row to theImageQueuesheet with columnsid, boardId, roomId, uploadedBy, uploaderRole, fileName, driveId, mimeType, byteSize, status, submittedAt, decidedBy, decidedAt, decisionNote. Student uploads returnstatus: 'pending'. Teacher uploads auto-approve and return the bytes inline. LogsIMAGE_UPLOAD(andnotifyComplianceAdmin_for pending rows). - Client placeholder. The whiteboard inserts an image object with
srcset to aPENDING_IMAGE_PLACEHOLDERSVG ("Pending teacher approval") andpendingImageIdset to the returned id. A polling timer (every 8 seconds) calls?action=imageQueueResolve&id=...for every object that still carries apendingImageId. - Teacher review — Compliance Console → Image Approval Queue. The admin page lists pending rows with a thumbnail (loaded via
?action=imageQueueThumb&passcode=...&id=...), uploader, board / room, mime, size, and Approve / Reject buttons. Rejection prompts for an optional decision note. - Decision —
setImageStatus_. Admin-gated. On reject, the Drive file is trashed. The row'sstatus,decidedBy,decidedAt,decisionNotecolumns are updated and anIMAGE_APPROVEDorIMAGE_REJECTEDaudit event is written. - Client resolution. The next poll sees
status: 'approved'(with the inline data URLsrc) orstatus: 'rejected', swaps the placeholder for either the real image orREJECTED_IMAGE_PLACEHOLDER, removespendingImageId, re-renders, and persists.
Knobs in compliance.config.json → safety.images:
enabled— master switch; when false, theuploadImageendpoint refuses uploads.teacherApprovalRequired— when false, student uploads auto-approve like teacher uploads (the queue still records them asapprovedrows for audit).allowedMimeTypes— allowlist (default:image/png,image/jpeg,image/webp).blockedMimeTypes— explicit blocklist that wins over allowlist (default:image/svg+xml).maxBytes— per-file size cap (default 5 MiB).
Time Limits (Days 2.8 / 2.9)
When compliance.config.json (or the COMPLIANCE_CONFIG Script Property) has timeLimits.enabled = true, the system enforces three checks on student writes:
- Allowed hours. If the current local hour falls outside
allowedHoursStart–allowedHoursEnd, saves are rejected with a clear message. - Weekend access. If
weekendAllowedis false and the current day is Saturday or Sunday, saves are rejected. - Daily limit. Once
secondsTodayreachesdailySeconds, saves are rejected and the browser locks the workspace.
Client (browser) — assets/js/timelimits.js:
- Tracks active interaction time (counts only when the user has typed / moved the mouse / clicked within the last 60 seconds).
- Persists per-day total in
localStorageso a reload resumes the counter. - Posts a heartbeat to
?action=timeHeartbeatevery 30 seconds with the delta; the server records it in aTimeUsagesheet keyed by(student, class, date). - Displays a remaining-time chip at the bottom-right of the whiteboard.
- Locks the workspace (full-screen overlay, disables all inputs) when the limit is reached or the server responds with
allowed: false.
Server (Apps Script):
checkTimeLimitsAllowed_(studentName, className)gatessaveBoard_/saveRoom_for students (teachers bypass). Hits write aTIME_LIMIT_HITaudit event before throwing.?action=timeStatusis an open endpoint returning the active configuration and the student’ssecondsToday/remaining. Used by the client on load.?action=timeHeartbeat(POST) accepts a delta (capped at 90 seconds per beat to prevent inflated counters) and returns the updated counter.
Operators configure the limits in Internal Compliance Console → Use Limits. Disable by toggling enabled off — the chip disappears and saves are no longer gated.
What is intentionally not built yet
This file documents what ships in the current commits. The roadmap lists everything else, with day-sized work items and acceptance criteria. Do not assume any feature works just because it is mentioned in the roadmap — check the Status column in COMPLIANCE-ROADMAP.md for ticked boxes.