Skip to article
DrawSplat Wiki
Read in your language
Privacy and district review
Source

DrawSplat™ Compliance — Operations Guide

Current documentation: DrawSplat 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 on window.complianceConfig. Listen for the compliance-config-ready event.
  • The Apps Script backend keeps a parallel snapshot under the COMPLIANCE_CONFIG Script 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:

  1. Save the file.
  2. 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.
  3. Open Project Settings > Script Properties and confirm ADMIN_PASSCODE is set (you already need this for any moderator action).
  4. Optionally set COMPLIANCE_ADMIN_EMAIL — new parent requests trigger a notification email to this address.
  5. 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_verification and writes a PARENT_REQUEST_CREATED event to Activity Records.
  • If COMPLIANCE_ADMIN_EMAIL is 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 is unknown_minor.
  • ageLocked defaults to true and only the admin can change ageBand. Every change requires a reason and emits an AGE_BAND_CHANGED audit 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:

  1. In the internal Compliance Console → Student Age Band Lock, click Issue Parent Code next to a student.
  2. 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.
  3. The teacher shares the code with the parent through the school's existing parent-communications channel (email, paper, etc.).
  4. The parent enters the code in the Verification code field on /parents/index.html when submitting a request.
  5. On submission, if the code matches and is unexpired, the ticket starts in verified status (skipping pending_verification). The code is then cleared so it cannot be reused.
  6. The PARENT_CODE_ISSUED event is logged when issued; the request itself logs verified: true on 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:

  1. Prompts the admin for a reason (logged in Activity Records).
  2. Calls exportStudentData on the Apps Script backend. The server gathers:
    • Every board in the Boards sheet whose className matches and whose title contains the student's name, including JSON and PNG.
    • Every turn-in in TurnIns keyed by (student, class).
    • The student's Users row, minus any hashed credentials.
  3. Builds a ZIP with boards/, turnins/, manifest.json (machine-readable summary), and a README.txt (human-readable).
  4. Returns it as base64; the browser saves it to your Downloads folder.
  5. Logs DATA_EXPORT with 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:

  1. Confirms the action (twice — once for the warning, once for a reason that goes into Activity Records).
  2. Trashes Drive files for every matching turn-in and (if a class is supplied) every matching board.
  3. Removes the user row and all matching turn-in / board rows.
  4. Emits a DATA_DELETED audit 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 an Archive Drive subfolder.
  • retention.boards.deleteAfterDays — boards untouched longer than this are trashed and removed from the Boards sheet.
  • retention.audit.keepDays — rows in the Audit sheet 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:

  1. Client — whiteboard (assets/js/app.js). When board.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) call submitImageForApproval(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.
  2. Server — uploadImage_. Decodes the base64, validates mime (allowedMimeTypes / blockedMimeTypes) and size (maxBytes), writes the bytes to the DrawSplatTM Saves Drive folder, and appends a row to the ImageQueue sheet with columns id, boardId, roomId, uploadedBy, uploaderRole, fileName, driveId, mimeType, byteSize, status, submittedAt, decidedBy, decidedAt, decisionNote. Student uploads return status: 'pending'. Teacher uploads auto-approve and return the bytes inline. Logs IMAGE_UPLOAD (and notifyComplianceAdmin_ for pending rows).
  3. Client placeholder. The whiteboard inserts an image object with src set to a PENDING_IMAGE_PLACEHOLDER SVG ("Pending teacher approval") and pendingImageId set to the returned id. A polling timer (every 8 seconds) calls ?action=imageQueueResolve&id=... for every object that still carries a pendingImageId.
  4. 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.
  5. Decision — setImageStatus_. Admin-gated. On reject, the Drive file is trashed. The row's status, decidedBy, decidedAt, decisionNote columns are updated and an IMAGE_APPROVED or IMAGE_REJECTED audit event is written.
  6. Client resolution. The next poll sees status: 'approved' (with the inline data URL src) or status: 'rejected', swaps the placeholder for either the real image or REJECTED_IMAGE_PLACEHOLDER, removes pendingImageId, re-renders, and persists.

Knobs in compliance.config.json → safety.images:

  • enabled — master switch; when false, the uploadImage endpoint refuses uploads.
  • teacherApprovalRequired — when false, student uploads auto-approve like teacher uploads (the queue still records them as approved rows 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 weekendAllowed is false and the current day is Saturday or Sunday, saves are rejected.
  • Daily limit. Once secondsToday reaches dailySeconds, 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 localStorage so a reload resumes the counter.
  • Posts a heartbeat to ?action=timeHeartbeat every 30 seconds with the delta; the server records it in a TimeUsage sheet 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) gates saveBoard_ / saveRoom_ for students (teachers bypass). Hits write a TIME_LIMIT_HIT audit event before throwing.
  • ?action=timeStatus is an open endpoint returning the active configuration and the student’s secondsToday / 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.