Data model
The Advocate keeps a student's record in one document store, one collection per kind of thing, every document
owned by exactly one student, so export and deletion are a single query each. Three other stores sit beside it,
each with its own guarantee.
The student
erDiagram
students {
string id PK "stable id"
string phone "optional"
string chosenName
string pronouns "optional"
string zip
string county "optional"
number age "optional"
string goal "optional"
HelpDial dial "depth, channel, reading grade, language, quiet hours"
Household household "size, income, children"
Enrollment enrollment "college, credits, work hours"
object receives "benefits already received"
string_ helpAreas
string_ communities "chosen, never inferred"
string account "keyed hash of the sign-in subject"
date createdAt
}The record: schedule
erDiagram
students { string id PK string chosenName HelpDial dial }
deadlines {
string id PK
string studentId FK "owner"
string title
date_or_date_time due
string course "optional"
enum kind "assignment, exam, reading, college, aid, benefit, bill, appointment, other"
object source "where it was found, with the line"
boolean confirmed
number effortHours "optional"
number durationMin "optional"
boolean done "optional"
}
classes {
string course
weekday_ days
time start
time end
string building "optional"
string room "optional"
string campus "optional"
}
shifts {
date date
time start
time end
string employer "optional"
}
holds {
string id PK
string deadlineId FK "optional"
string title
date date
time start
time end
}
reminders {
string id PK
string about
date_or_date_time at
string text
}
meetings {
string id PK
string title
date date
time start
string_ attendees
string course "optional"
}
courses {
string course
enum aiPolicy
string aiPolicyLine "optional"
}
students ||--o{ deadlines : "has"
students ||--o{ classes : "attends"
students ||--o{ shifts : "works"
students ||--o{ holds : "holds time"
students ||--o{ reminders : "is reminded"
students ||--o{ meetings : "attends"
students ||--o{ courses : "takes"
deadlines ||--o{ holds : "gets time in"The record: conversation
erDiagram
students { string id PK string chosenName HelpDial dial }
drafts {
string id PK
string studentId FK
string to
string recipient
string body
string purpose
enum status "shown, sent, discarded"
}
messages {
string id PK
string studentId FK
string author "which agent"
string text
NextStep_ nextSteps
enum urgency "safety, time-sensitive, routine"
string why "optional"
date_time at
enum status "delivered, deferred, held"
string holdReason "optional"
}
last_steps {
NextStep_ steps
}
pulse_state {
record asked
number everyDays
boolean off "optional"
}
pulse_pending {
string pulse
}
dismissed {
string subject
date at
}
students ||--o{ drafts : "approves"
students ||--o{ messages : "receives"
students ||--o| pulse_state : "answers"
students ||--o{ dismissed : "dismisses"The record: help and money
erDiagram
students { string id PK string chosenName HelpDial dial }
playbook_runs {
string playbook
string studentId FK
string stepId
enum status "active, waiting, done, unresolved"
date startedAt
object_ history
date waitingSince "optional"
}
vault {
string name
date addedAt
}
money {
number balance
Bill_ bills
Paycheck pay "optional"
}
household_plan {
ChildCare childCare "optional"
number commuteMin "optional"
number sleepHours "optional"
enum commuteMode "bus, car, walk"
string homeStop "transit stop id the student picked"
}
aid {
date disbursement
}
students ||--o{ playbook_runs : "runs"
students ||--o{ vault : "keeps"
students ||--o| money : "tracks"
students ||--o| household_plan : "plans"
students ||--o| aid : "expects"The record: connections
erDiagram
students { string id PK string chosenName HelpDial dial }
sources {
enum source
date connectedAt
}
api_tokens {
string id PK "sha256 of the token"
string label
enum_ scopes "read, write, actions"
date createdAt
}
webhooks {
string id PK
url url
string_ events
string secret "signs each delivery"
}
students ||--o{ sources : "connects"
students ||--o{ api_tokens : "creates"
students ||--o{ webhooks : "registers"
| Collection |
Key |
What it holds |
students |
id |
Who the student is and how they want to be helped. Sealed facts are never stored here. |
deadlines |
id |
Every date that matters: assignments, exams, aid, bills, appointments. Found dates stay unconfirmed until the student checks them. |
classes |
student:course:index |
Weekly class meetings, from the schedule or a room-change email. |
shifts |
student:date:start |
Work shifts, typed by the student or sent through the API. |
holds |
id |
Time the student asked the Advocate to keep for a piece of work. |
reminders |
id |
Reminders the student asked for. |
drafts |
id |
Messages the Advocate wrote for the student to send. Nothing leaves without the student approving the exact text. |
messages |
id |
Everything the team said to the student, with why and what the tone gate decided. |
last_steps (transient) |
student |
The choices on the last message, so a reply of "1" by text picks the first. |
playbook_runs |
student:playbook |
Where the student is in each playbook, with every checkpoint. |
pulse_state |
student |
Quick questions: which were asked when, how often, or off. |
pulse_pending (transient) |
student |
A quick question waiting for a yes or no. |
sources |
student:source |
What the student connected: calendar, forwarding, class site, location. Each grant is also in the consent ledger. |
api_tokens |
sha256(token) |
Tokens the student created to connect other tools. Only a hash is stored. |
webhooks |
id |
Where the student asked to be notified. Https only; payloads carry ids, never message text. |
vault |
student:name |
Names of documents the student keeps; contents are sealed. |
money |
student |
Balance, bills and pay, for spotting a short month ahead of time. |
household_plan |
student |
Child care hours, commute and sleep, for the week solver. |
commute_plans (transient) |
student |
date |
transit_now (transient) |
student |
Live arrivals and alerts at the student's stop; used only while under ten minutes old. |
dispatches |
idempotency key |
Writes the student asked for (room bookings), each sent once with an idempotency key and confirmed by looking it up. |
campus_feeds (transient) |
campus |
What the operator last fetched from each campus service: dining, places, careers and transit stops. Shared, not about any student. |
leases (transient) |
job |
Which operator holds which job, with a heartbeat, so two never run the same job. |
meetings |
id |
Group meetings to prepare for. |
courses |
student:course |
Each course and its AI policy, read from the syllabus. |
dismissed |
student:subject |
Alerts the student said are fine. |
aid |
student |
When financial aid is expected, for book timing. |
The other stores
erDiagram
consent_ledger { int seq PK text at text student_id text kind text subject json detail text prev_hash text hash }
consent_ledger ||--|| consent_ledger : "prev_hash chains to"
sealed_record { text studentId PK text wrappedKey "data key, wrapped by the master key" json facts "each encrypted" json grants "fact to identities" }
evidence_event { int seq PK text at text kind text subject "keyed hash" json data }
| Store |
Guarantee |
| consent ledger |
Every grant, revoke, approval, export and deletion, hash-chained and append-only. |
| sealed facts |
Facts only granted identities can open; encrypted with a per-student key wrapped by the master key. |
| evidence |
What the factory and the product did, pseudonymous and append-only; never message content. |
How the stores relate
flowchart LR
app[(App store<br/>documents per student)] -- every grant, approval, export, deletion --> ledger[(Consent ledger<br/>append-only, hash-chained)]
app -. names only .-> vault[vault]
vault -. contents .-> sealed[(Sealed facts<br/>per-student keys)]
app -- what happened, pseudonymous --> evidence[(Evidence<br/>append-only)]
export([Export]) --> app & ledger
delete([Delete]) --> app & sealed
delete -- recorded --> ledger
Verified by