Effective date: October 29, 2026 · Operator: AR Dynamics Inc
1. Who we are
AR Dynamics Inc, a Hawaii corporation, 200 N Vineyard Blvd, Ste A325 #5128, Honolulu, HI 96817. Privacy contact and opt-out email: hello@ardynamicsinc.com. SOHK ("School of Hard Knocks") is a study app operated by AR Dynamics Inc.
2. What the software collects and why (data inventory)
The table below describes what the software stores as of the effective date.
| Data | Where it lives | Why | Student control |
|---|---|---|---|
| Email address, password (stored only as an argon2id hash) | users | account and sign-in | export; delete account |
| Login sessions (stored only as a SHA-256 of the token; one HttpOnly cookie wo_session) | auth_sessions | stay signed in | sign out; delete account |
| Study sessions and every message you and the instructor exchange; images you attach | study_sessions, messages, private upload folder | the tutoring conversation | export; delete account |
| Course intake: exam, track, textbook, current topic, target exam date | course_profiles | tailor questions | edit; delete account |
| Graded-answer tallies, mastery estimates, review schedule, mistakes you made (with a short quote of your own wrong answer), streaks | topic_stats, learner_* | adaptive practice | export; delete account |
| Roast style profile, reactions, and your consent record for Scorched Earth / profanity | roast_*, consent (append-only while the account exists; cascades on account delete) | personalise tone and prove consent | revoke consent; export; delete account |
| Your own courses: the notes/syllabus you paste or upload (as cards and passages), exam dates | user_courses, user_course_topics, user_cards | build your private course | delete a course; export; delete account |
| Flashcard reports and course requests | card_reports, course_requests | quality control; deciding what to research | export; delete account |
| Daily usage counts | usage_daily | quota | delete account |
| Security events: IP address, request path and user agent of blocked or suspicious requests | shield_events, shield_bans | defend the service | no student control. Events are deleted after 90 days; ban rows are deleted 30 days after the ban expires. See section 8 |
| Audit trail: pseudonymous account id + action name (e.g. "account.delete"), no email, no content | audit_log (append-only, no link to the account) | security and accountability | not erased with the account by design. Deleted after 24 months. |
| Payment data | Stripe, only if payments are switched on. They are off unless PAYMENTS_ENABLED is exactly true. When on, SOHK stores a Stripe customer id, plan, and current period end. Card numbers stay at Stripe. Account deletion calls Stripe customers.del when a customer id is stored, and aborts the local delete if that call fails | billing | billing portal (when payments are on); delete account |
| Age affirmation. Not a date of birth. age_confirmed_at is an earlier checkbox time and is not rewritten. age_affirmed_min is the minimum affirmed (18) and age_affirmed_at is when. Null min means not affirmed at 18, including a 13-era timestamp | users.age_confirmed_at, users.age_affirmed_min, users.age_affirmed_at | signup gate and re-confirmation at the next sign-in. MIN_ACCOUNT_AGE is 18 (src/lib/auth/age.ts). The checkbox says "I confirm I am 18 or older." | deleted with the account. Included in the JSON export |
| Password recovery codes (HMAC-SHA256 with a server pepper; plaintext shown once) | recovery_codes | reset a password without an email service | generate in settings (POST /api/me/recovery-codes); use one at /reset (POST /api/auth/reset); delete account. Hashes are not in the export |
Recovery codes, as built. There is no email reset. Settings shows eight one-time codes once (sohk- plus four groups of four symbols, src/lib/auth/recovery.ts). The database stores hmac-sha256: plus the HMAC-SHA256 of the normalized code, keyed with RECOVERY_CODE_PEPPER (at least 32 characters). It does not store the plaintext. Migration 0024_recovery_code_hash replaced an earlier unsalted SHA-256 format and deleted outstanding codes. Generating a new set deletes the previous set. A successful reset replaces the password, deletes login sessions, and deletes the whole set (consume_recovery_code). Rows cascade-delete with the account (migration 0023_age_recovery).
Not collected today: advertising identifiers, third-party analytics, location, contacts, date of birth. We do not sell personal data and do not use it to train models.
3. Where AI processing happens
Today the instructor runs on a model on the owner's own machine (Ollama); messages are not sent to a third-party AI
provider. If a hosted model is adopted, this section, section 2 and the Terms must change before launch.
Custom-course cards are made by deterministic code (no AI model). Shared practice drafts in flashcard_drafts are reference rows with no user id.
4. Safety messages
Messages that suggest self-harm are answered with fixed crisis resources before any model runs; the message is saved in the student's own transcript and an audit event records only that it happened. No human monitors chats. How this works is described at /safety.
5. Your rights
- Access / portability: Settings > Your data > "Download my data" (JSON of the tables listed in the export code, plus email, created time, plan, the age-affirmation fields, which Terms version you accepted and when, your auto-renewal consent records and your billing records). The download does not include the password hash, login tokens, or recovery-code hashes.
- Deletion: Settings > Your data > "Delete my account" (password and the word DELETE required). It erases the account and
cascaded rows, removes uploaded images from disk, and deletes a Stripe customer when one is stored. Recovery codes and the age timestamp go with the account. The audit log does not. Backups:
scripts/backup/backup.mjscan dump the database; a restore test isscripts/backup/restore-test.mjs. There is no scheduled off-site backup yet. Dumps in the backup directory are deleted after 30 days byscripts/ops/purge-backups.mjs. - Correction, objection, restriction, withdrawal of consent, and any other privacy request: email hello@ardynamicsinc.com from the address on your account. One process for everyone: we handle every request the same way, wherever you live. We confirm receipt, verify that the request comes from the account holder, act on it, and reply within 45 days, telling you what we did or why we could not. You may also complain to your local data-protection or consumer-protection authority.
- Arbitration opt-out: the Terms let a user opt out of arbitration within 30 days of first acceptance by emailing hello@ardynamicsinc.com. That is a contract opt-out, separate from privacy requests.
6. Children and students
The Service is not for anyone under 18. The same rule is in the Terms and the Addendum. MIN_ACCOUNT_AGE is 18. Signup asks for the month and year you were born, without saying what age is required. The answer is checked and not stored. If it is under 18, signup is refused and the question cannot be retried from that browser. Otherwise signup stores age_affirmed_min = 18 and age_affirmed_at (and age_confirmed_at when it was empty).
Accounts with a null age_affirmed_min — including people who only affirmed the old 13-year-old checkbox (age_confirmed_at from migration 0023_age_recovery) and accounts with no timestamp — must answer the same birth month and year question at the next sign-in before the app opens. Migration 0025_age_eighteen adds the columns and does not rewrite or delete existing rows.
If we learn a user is under 18, we delete the account. On the confirmation screen they can delete it by entering the password and the word DELETE. That is the same deletion as Settings. The product does not keep the birth month and year you enter.
Mature content. Scorched Earth and full profanity stay off unless the account has affirmed 18 or older and completed the five-box consent gate. The age box on that gate says "I confirm I am 18 or older." The server strips those modes when age_affirmed_min is missing or below 18, even if an older consent row exists.
Record of a minor's deletion. When an account is deleted because the person is under 18, all of its data is deleted, and we keep one minimal row in minor_account_deletions: the account's pseudonymous id (the same one the audit log keeps), the date, and a fixed reason code. It holds no email, name, birth date, or content, and exists only to show the deletion happened. [DECISION NEEDED: how long these rows are kept.]
7. Security
Passwords hashed with argon2id; recovery codes stored only as an HMAC with a server pepper; row-level security on student data; encrypted transport in production; rate limiting, a web application firewall and honeypots; append-only audit and consent logs. No system is perfectly secure.
Data breaches. If a security breach exposes your personal information, we will notify affected users and, where required, regulators, as required by Hawaii law (HRS chapter 487N) and any other law that applies.
8. Retention
Each period below is enforced by the software. The purge jobs are scripts/ops/retention-purge.mjs (database function public.retention_purge(), migration 0026_legal_consents_retention) and scripts/ops/purge-backups.mjs. The operator runs both daily.
- Account data, study content, courses, cards, roast profile, and consent rows: kept until you delete the account. That deletion is implemented. Consent rows are append-only while the account exists and cascade-delete with it, except the auto-renewal consent records below.
- Recovery-code hashes: kept until you generate a new set, a reset succeeds (the whole set is deleted), or you delete the account. Implemented.
- Age affirmation (
age_confirmed_at,age_affirmed_min,age_affirmed_at): kept until you delete the account. A new 18 affirmation does not erase an olderage_confirmed_at. Included in the JSON export. - Security events (
shield_events): append-only for the app. Deleted after 90 days. - Ban rows (
shield_bans): kept so a returning address can escalate. The running process loads bans updated in the last 30 days (STRIKE_MEMORY_MSinsrc/lib/shield/state.ts). A ban row is deleted 30 days after the ban expires. - Audit log: append-only, no account foreign key, not erased on account deletion. Deleted after 24 months.
- Operator dumps: no scheduled off-site backup. A dump made with
scripts/backup/backup.mjsis deleted after 30 days. - Auto-renewal consent records (
billing_consents): time, Terms version, disclosure version and checkout id for each checkout. Included in the export. When you delete the account they are detached from it (keyed only by the pseudonymous id) and deleted 3 years after the account is deleted, so we can show you agreed to the renewal terms. - Billing records (
billing_records): for each paid invoice, the Stripe invoice id, amount, tax, currency and payment date. Included in the export. Detached from the account on deletion and deleted 7 years after the payment, for tax and accounting. - Stripe: card data and full invoices stay at Stripe under Stripe's retention. Locally: customer id, plan, and period end, deleted with the account after the Stripe customer delete succeeds.
- Age check: if you answer the age question with a birth date under 18, a cookie (
sohk_age_check, 1 year) and, for an existing account, the time of that answer (age_check_failed_at) stop the question from being retried. We do not store the birth date you entered.
9. Do Not Track
We do not track you across other websites and do not use third-party analytics or advertising trackers, so we treat every visitor the same whether or not the browser sends a Do Not Track or Global Privacy Control signal. We do not sell or share personal data for advertising.
10. Where data is stored
The app and database run on servers controlled by AR Dynamics Inc in the United States.
11. Changes to this policy
We post every change on this page with a new effective date. For a material change we also update the Terms version, so every account is asked to review and accept at its next sign-in before the app opens. We log privacy requests received at hello@ardynamicsinc.com with the date and outcome.
12. Contact
Contact for privacy requests, including a request to opt out of arbitration under the Terms: hello@ardynamicsinc.com. Postal: AR Dynamics Inc, 200 N Vineyard Blvd, Ste A325 #5128, Honolulu, HI 96817-3938.