Home›Complia›Test run
Complia · Test run · run 2026-11-07

Complia Test run, as shipped in the download

440 checks passed, 0 failed, when the suite that ships in the zip was run on 2026-11-07 with php tests/run.php. Run it yourself after you download.

== 1. Schema install (SQLite in-memory) ==
  PASS  schemaExists() after install
  PASS  table users exists
  PASS  table settings exists
  PASS  table complaints exists
  PASS  table complaint_log exists
  PASS  install() is idempotent (re-run does not throw)

== 2. Settings ==
  PASS  org_name read through App::set
  PASS  ack_window_days read
  PASS  timezone read
  PASS  App::today() uses business timezone

== 3. s.164A(3) acknowledgment-deadline math ==
  PASS  default 29-day window (day-1-inclusive safe)
  PASS  strict statutory reading satisfied: received Mon 2026-08-03 -> due 2026-09-01, never 09-02
  PASS  explicit 30-day window still available
  PASS  month rollover
  PASS  year rollover
  PASS  leap year Feb
  PASS  non-leap Feb
  PASS  custom window 14 days
  PASS  custom window 60 days
  PASS  window clamped: 0 -> 1 day
  PASS  window clamped: 9999 -> 365

== 4. daysRemaining (signed) ==
  PASS  10 days left
  PASS  due today = 0
  PASS  overdue by 5
  PASS  across month
  PASS  across year

== 5. Status workflow transitions ==
  PASS  received -> acknowledged
  PASS  received -> investigating
  PASS  received -> resolved
  PASS  received -> rejected
  PASS  acknowledged -> investigating
  PASS  acknowledged -> resolved
  PASS  investigating -> resolved
  PASS  investigating -> rejected
  PASS  NO acknowledged -> received (backwards)
  PASS  NO investigating -> acknowledged (backwards)
  PASS  NO resolved -> anything (terminal)
  PASS  NO rejected -> anything (terminal)
  PASS  NO received -> received (no-op)
  PASS  NO transition to bogus status

== 6. Derived display status (ack_overdue precedence) ==
  PASS  received before due -> received
  PASS  received past due -> ack_overdue
  PASS  received due today -> received (not overdue)
  PASS  acknowledged past due -> acknowledged
  PASS  investigating past due -> investigating
  PASS  resolved past due -> resolved
  PASS  rejected past due -> rejected
  PASS  received + ack timestamp -> received (duty met)
  PASS  bogus stored status -> received

== 7. Public intake -> token -> lookup flow ==
  PASS  create returns id
  PASS  token is 40 hex chars
  PASS  token hex only
  PASS  byToken finds complaint
  PASS  email lowercased + trimmed
  PASS  status starts received
  PASS  ack_due_at = today + 30 (business tz)
  PASS  reference stored
  PASS  submitted log entry written
  PASS  byToken miss -> null
  PASS  byToken rejects malformed token
  PASS  byToken rejects empty token
  PASS  byToken rejects SQLi-ish token

== 8. Intake validation ==
  PASS  valid input accepted
  PASS  empty name rejected
  PASS  overlong name rejected
  PASS  bad email rejected
  PASS  empty subject rejected
  PASS  empty details rejected

== 9. Anti-abuse: per-IP rate limit ==
  PASS  10 submissions/hour from one IP -> limited
  PASS  different IP not limited
  PASS  empty IP never limited (proxied installs)

== 10. clip/clamp overflow safety (MySQL strict-mode defense) ==
  PASS  clip long string to 190
  PASS  clip short string untouched
  PASS  clip multibyte-safe
  PASS  clamp_int huge -> 2e9
  PASS  clamp_int -huge -> -2e9
  PASS  clamp_int non-numeric -> 0
  PASS  clamp_int custom bounds
  PASS  oversized subject clipped to 255
  PASS  oversized reference clipped to 100
  PASS  oversized ip clipped to 45
  PASS  100KB details stored intact (big text column)

== 11. Investigation log (append-only) ==
  PASS  two notes appended
  PASS  order preserved (append-only)
  PASS  no UPDATE/DELETE of complaint_log anywhere in controllers

== 12. Status change semantics (ack timestamp + outcome) ==
  PASS  acknowledged_at recorded
  PASS  isAcked true after acknowledgment
  PASS  derived status acknowledged even past due
  PASS  outcome stored
  PASS  outcome_date stored
  PASS  terminal: no transitions from resolved

== 13. Auth (real users table) ==
  PASS  correct password accepted
  PASS  wrong password rejected
  PASS  unknown user rejected
  PASS  email case-insensitive
  PASS  inactive user rejected

== 14. Audit CSV export (formula guard, numeric-aware) ==
  PASS  =SUM formula neutralized
  PASS  @ formula neutralized
  PASS  +cmd neutralized
  PASS  -text neutralized
  PASS  leading TAB neutralized
  PASS  negative number NOT corrupted
  PASS  negative decimal NOT corrupted
  PASS  positive numeric "+1" kept
  PASS  plain text untouched
  PASS  comma cell RFC-4180 quoted
  PASS  quote cell escaped
  PASS  formula + comma: guarded AND quoted
  PASS  CSV header
  PASS  CSV row count = complaints + history + header
  PASS  CRLF line endings
  PASS  complaint rows tagged
  PASS  history rows present
  PASS  complaint row cell count
  PASS  formula name neutralized in real export
  PASS  formula subject neutralized in real export

== 15. Security hardening regressions (source-level) ==
  PASS  csrf_check rejects empty stored token (hash_equals empty-token bypass)
  PASS  installer rejects empty install token (_it bypass)
  PASS  demo session cookie is None + Secure + Partitioned (CHIPS)
  PASS  buyer session cookie is HttpOnly + Lax + Secure-on-HTTPS
  PASS  portable UTC timestamps (no SQLite datetime() in app SQL)
  PASS  clip + clamp_int helpers exist
  PASS  settings save clips org fields + clamps ack window
  PASS  complaint create clips every VARCHAR-bound field
  PASS  details/notes are MEDIUMTEXT on MySQL (64KB TEXT cap avoided)
  PASS  public POST handlers csrf_check (intake + track)
  PASS  honeypot field checked in intake handler
  PASS  per-IP rate limit enforced in intake handler
  PASS  status route enforces transition matrix
  PASS  closing a complaint requires an outcome (in the shared rule)
  PASS  v3: exactly ONE status-advance rule, used by UI + API + MCP
  PASS  token lookups are exact parameterized matches
  PASS  CSV export requires the export permission (not merely login)
  PASS  public status page 404s on miss
  PASS  all output escaped: views use e() on complaint fields
  PASS  .htaccess blocks config/data/src/bin/tests/controllers

== PWA ==
  PASS  manifest.json is valid JSON
  PASS  manifest has required fields
  PASS  manifest URLs are RELATIVE (subpath-install safe)
  PASS  manifest icons exist on disk (192 + 512 + apple-touch)
  PASS  sw.js never caches non-GET
  PASS  sw.js never caches page navigations (auth HTML can not enter cache storage)
  PASS  sw.js cache name carries the version (old caches purged on activate)
  PASS  offline fallback page ships
  PASS  every app layout links the manifest + registers the SW

-- Own It 2.0 --
  PASS  v2/v3: tables
  PASS  v2/v3: totp_cols
  PASS  v2/v3: smtp_cols
  PASS  v2/v3: mint
  PASS  v2/v3: revoke
  PASS  v2/v3: totp
  PASS  v2/v3: recovery
  PASS  v2/v3: delivery
  PASS  v2/v3: no_phantom_events
  PASS  v2/v3: rate
  PASS  v2/v3: ack_due
  PASS  v2/v3: days_remaining_signed
  PASS  v2/v3: derive_status
  PASS  v2/v3: overdue_not_a_column
  PASS  v2/v3: transitions
  PASS  v2/v3: create_validates
  PASS  v2/v3: create_sets_clock
  PASS  v2/v3: complaint_serializer
  PASS  v2/v3: serializer_nocompute
  PASS  v2/v3: csv_dry
  PASS  v2/v3: csv_backlog_clock
  PASS  v2/v3: backup_tables
  PASS  v2/v3: v3_tables
  PASS  v2/v3: audit_redacts
  PASS  v2/v3: gdpr_map_columns_exist
  PASS  v2/v3: letter_fields_are_data_only
  PASS  v2: routes registered (api, 2fa, security, backup, import, healthz)
  PASS  v2: migrate() runs on every boot (1.x had none — existing installs need it)
  PASS  v2: 2FA intercepts login + login is rate-limited
  PASS  v2: notifications + webhooks fire on the BROWSER path too, never fatally
  PASS  v2/v3: the product reuses its tested core (no re-derived guards)
  PASS  v2: the API never computes its own due date or overdue flag
  PASS  v2/v3: closing needs an outcome (shared rule), and ?status filters the DERIVED status
  PASS  v2: UK-reframed marketing/public copy left untouched
  PASS  v2: dark palette + no hardcoded white backgrounds outside @media print
  PASS  v2: theme.js + qr.js ship and layout loads theme.js + a toggle
  PASS  v2: shared modules are copy-identical to the shared core (never forked per app)
  PASS  v3: API.md ships + sw cache is v4.1.3 (App::VERSION)
  PASS  v3: no call to a function this product does not define
  PASS  C2: no sensitive handler is login-only — all are role-gated
  PASS  C2: both backup doors call the ONE shared redaction list
  PASS  C2: Api::requireKey refuses a deactivated user (u.active)
  PASS  C2: every App:: method the controllers call actually exists
  PASS  C2: Api::base() does not double an already-absolute base
  PASS  C2: the subject-access export takes the same standing as anonymize
  PASS  nav: every sidebar link is highlighted by the page it opens
  PASS  nav: no page highlights a sidebar item that does not exist
  PASS  ip: a direct visitor's forged headers are ignored
  PASS  ip: behind a trusted proxy the forwarded visitor is used
  PASS  ip: CF-Connecting-IP wins over the X-Forwarded-For chain
  PASS  ip: the chain is read right-to-left, past trusted hops
  PASS  ip: a Cloudflare edge is not a trusted proxy by default
  PASS  ip: CIDR maths (v4 boundaries + v6, no cross-family match)

== UI kit: acknowledging from the register ==
  PASS  register ack: the transition is accepted
  PASS  register ack: the stored status moved
  PASS  register ack: the statutory clock stopped (acknowledged_at is set)
  PASS  register ack: the derived status is no longer overdue-able
  PASS  register ack: it appended to the append-only log, it did not overwrite it
  PASS  register ack: an unacknowledged complaint past its deadline reads as overdue first
  PASS  register ack: a late acknowledgment is still recorded (lateness is evidence, not a lock)
  PASS  register ack: a resolved complaint refuses the transition, in the engine's own words
  PASS  register ack: the refused complaint was not touched
  PASS  register ack: acknowledging can never CLOSE a complaint (closing needs a written outcome)
  PASS  register ack: POST /complaints/{id}/ack is registered
  PASS  register ack: the endpoint requires complaint.advance and checks CSRF
  PASS  register ack: it delegates to the ONE shared transition rule — no second copy of the workflow
  PASS  register ack: an unknown complaint 404s and a refused transition answers 409
  PASS  register ack: it can only ever ask for "acknowledged"
  PASS  kit: layout loads kit css+js, groups the nav with icons + g-keys, carries the theme toggle and a who-line
  PASS  kit: the overdue count rides the sidebar on every page
  PASS  kit: no theme.js; app css is an app layer with a dark accent; the pre-kit file is gone
  PASS  kit: the printed register still forces a light palette (this print-out is the evidence pack)
  PASS  kit: App::asset() versions by mtime and the service worker revalidates code
  PASS  kit: nothing is fetched from another host
  PASS  dashboard: the first-run checklist is computed from the database, not the session
  PASS  dashboard: KPI sparkline, needs-your-action and the stage strip are wired
  PASS  hero: the register carries a clock in every row, tabs with counts, row links and the inline acknowledge
  PASS  record: the complaint page has the kit rail, the big clock and the append-only log
  PASS  public: the complaint form and the status page are on the kit public frame
  PASS  copy: no other product's vocabulary survives in the views

== 21. v3.1 — the complainant's receipt (P1) ==
  PASS  receipt: the subject names the organization and nothing else
  PASS  receipt: the body carries received date, the response deadline, their own reference and the tracking link
  PASS  receipt: it says why it is so short, so the silence reads as design rather than a bug
  PASS  receipt: sending is due when the operator AND the complainant both said yes
  PASS  receipt REFUSES to echo the complaint back: no subject line, no details, in body OR subject
  PASS  receipt carries no token beyond the one already in the complainant's own link, and no secret
  PASS  receipt refuses: operator switch off means nothing sends, even for a complainant who asked
  PASS  receipt refuses: complainant did not tick the box — the operator switch cannot override that
  PASS  receipt refuses: an unusable email address is never mailed
  PASS  receipt refuses: an install with no organization name still produces a sane subject
  PASS  receipt idempotency: a complaint already receipted is never due again
  PASS  receipt idempotency: the row is STAMPED before the mail is sent, and the UPDATE re-checks the stamp
  PASS  receipt: a mail failure can never lose the complaint — the send is wrapped and swallowed
  PASS  receipt: one emitter — the automatic path and the resend button both call _cp_send_receipt
  PASS  receipt permission: resending needs complaint.note + CSRF; a viewer cannot
  PASS  opt-in ticked is stored as 1
  PASS  opt-in untouched is stored as 0 — silence is never consent
  PASS  the public form only offers the box when the operator has switched receipts on

== 22. v3.1 — the complainant's evidence (P3) ==
  PASS  evidence: ONE storage implementation serves both doors — staff and complainant
  PASS  evidence: the file's own BYTES decide its type, in the one place that stores it
  PASS  evidence: stored under a RANDOM name, never the name the uploader chose
  PASS  evidence: capped at 10 MB and refused at zero bytes
  PASS  evidence: the public form is multipart, or the browser would send filenames and no files
  PASS  evidence: PHP's parallel-array upload shape is normalized, so evidence[] actually stores
  PASS  evidence: the record page finally RENDERS attachments — the 3.0 route had no screen at all
  PASS  evidence refuses: public uploads are OFF until the operator opens that door
  PASS  evidence refuses: at most 3 files from the public form, whatever the browser posts
  PASS  evidence refuses: a rejected file NEVER fails the submission — the complaint still lands
  PASS  evidence refuses: an attachment is never public — serving needs a login and a permission
  PASS  evidence permission: a viewer can neither attach nor delete
  PASS  evidence permission: delete is owner-gated — your own uploads, or admin
  PASS  evidence permission: only an ADMIN may remove what the complainant supplied
  PASS  evidence: deleting removes the ROW AND THE FILE — a delete that leaves the bytes is the defect
  PASS  evidence: deleting a file that is already gone still removes the row (a second click is safe)

== 23. v3.1 — anonymizing now reaches the evidence ==
  PASS  erasure: the map now names the identifying columns on attachments
  PASS  erasure: the FILES are unlinked, and stored_file is blanked so nothing points at them
  PASS  erasure: the files go BEFORE the column pass — a half-done erasure must not leave the bytes
  PASS  erasure: the count is reported to the operator and recorded in the log and the audit trail
  PASS  erasure: still refuses while the complaint is open — that rule is untouched
  PASS  the tracking link in the receipt actually opens the case — GET /track?t= looks it up
  PASS  a wrong or absent tracking code on that link is a 404, never a listing
  PASS  a refused file is REPORTED to the complainant, never silently dropped

== 24. v3.1 — truth on the surfaces ==
  PASS  README changelog carries 3.1.1 and both features
  PASS  README no longer claims Complia never emails the complainant
  PASS  every empty-state icon a view asks for is one the kit actually draws
  PASS  no .alert stacks multiple element children straight into the kit's flex row
  PASS  scope: api_keys gains a scope column defaulting to full — an upgrade never loosens
  PASS  scope: requireKey reads the key scope and refuses a write on a read-only key
  PASS  scope: mintKey persists the chosen scope, and anything but read is full access
  PASS  scope: /mcp hands the key scope to the core, which refuses write tools per tool
  PASS  scope: the key form lets an administrator mint a read-only key
  PASS  license: every font ships the SIL OFL text for its own family beside it
  PASS  license: LICENSE.txt is present and carries the IF WE STOP (CONTINUITY) clause

== F2. Uploads: the pages promise what the server accepts, and an over-size form never loses a complaint ==
  PASS  ini sizes: 2M, 512K, 1G and "unlimited"
  PASS  upload limits: per file never above 10 MB and never above what PHP takes
  PASS  upload limits: labels read as the page shows them
  PASS  an over-size form (PHP dropped every field) is recognized, not mistaken for an expired session
  PASS  ...and an ordinary form is not
  PASS  a file PHP refused as too large is kept (and so refused out loud); an empty slot is not
  PASS  ...and the refusal names the size limit instead of "choose a file first"
  PASS  the public form states the server's real limit and checks sizes before sending
  PASS  the public submit handles an over-size form BEFORE the CSRF check (which would 419 and lose the complaint)
  PASS  the Docker image accepts 10 MB files and a three-file form
  PASS  receipt: a send the mail server refuses is reported as not sent
  PASS  receipt: ...the register does not say "emailed", it says NOT sent
  PASS  receipt: ...and the claim is released, so it can be resent once mail works
  PASS  settings: saving the Organization form keeps the SMTP host, the recipient and every switch
  PASS  settings: the Email form still saves its own fields, unticked boxes included
  PASS  reminder steps: parsed descending, de-duplicated, nonsense dropped
  PASS  reminder step: 10 days left reaches none
  PASS  reminder step: 5 days left is the 7-day step
  PASS  reminder step: 2 days left is the TIGHTEST step reached (3), not every step
  PASS  reminder step: the day after the deadline is the overdue step (0)
  PASS  reminder step: a week overdue sends nothing more
  PASS  owners: only active members and admins can own a complaint
  PASS  reminders: off (the default), nothing is sent
  PASS  reminders: on, the complaint 3 days out gets the 3-day step, to its owner and the notification address
  PASS  reminders: the complaint due yesterday gets one overdue reminder
  PASS  reminders: long-overdue and acknowledged complaints get nothing
  PASS  reminders: with no mail server the step is recorded as no-smtp, never as sent
  PASS  reminders: the attempt is written to the complaint's own log, failure included
  PASS  reminders: a second run (cron after a page load) sends nothing — one latch
  PASS  reminders: undelivered steps are listed for the dashboard
  PASS  reminders: a cron route runs them, guarded by the backup token
  PASS  owner: a route names the owner, gated like a note and CSRF-checked
  PASS  dashboard: the reminder steps ride the page load
  PASS  the demo checklist promises a register, not a "defensible" one
  PASS  backup guard: the harness made one signed-in account per role (viewer,member,admin)
  PASS  backup guard: GET /backup.json — 403 for every non-admin role, open to admin
  PASS  backup guard: GET /backup.sqlite — 403 for every non-admin role, open to admin
  PASS  backup guard: GET /restore — 403 for every non-admin role, open to admin
  PASS  backup guard: POST /restore/dry-run — 403 for every non-admin role, open to admin
  PASS  backup guard: POST /restore/commit — 403 for every non-admin role, open to admin
  PASS  backup guard: POST /restore/token — 403 for every non-admin role, open to admin
  PASS  backup guard: GET /backup/scheduled without its token refuses every signed-in role, admin included
  PASS  backup guard: GET /reminders/run without its token refuses every signed-in role, admin included
  PASS  webhooks: "Add" reaches its route and saves the endpoint (was a 404)
  PASS  webhooks: "Pause" reaches its route and flips the endpoint
  PASS  webhooks: "Delete" reaches its route and removes the endpoint
  PASS  demo card: the public pages say what the page is, not "signed in as the admin"
  PASS  demo card: the admin pages keep "signed in as the admin"
  PASS  demo card: below 600px it starts as the small "Own it" pill; a tap opens it and the choice holds for the session
  PASS  demo card: boot() hooks it only behind the .demo-mode marker, and a page with no </body> passes through untouched
  PASS  backup redaction: a canary sits in every credential column of the schema (api_keys.token_hash, backup_state.s3_secret, feed_tokens.token_hash, invites.token_hash, settings.smtp_pass, settings.privara_api_key, settings.backup_token_hash, settings.oidc_client_secret, updater_state.license_api_key, users.password_hash, users.totp_secret, users.totp_recovery, webhooks.secret)
  PASS  backup redaction: each arrives as [REDACTED] and no canary is anywhere in the JSON backup

== 3.2.0: leadership report, procedure, related records ==
  PASS  report: received / on time / late / not yet overdue / not yet in time, in the period
  PASS  report: on-time rate counts only decided ones (2 of 4 = 50%); medians of 2, 31, 1 days and 20, 4 days
  PASS  report: outcomes, open now (any period) and overdue now, open by owner
  PASS  report: month by month
  PASS  report: an empty period has no rate and no medians, never a zero that reads as a result
  PASS  report page: renders the numbers for the chosen period
  PASS  report CSV: a row per month and a total
  PASS  report: a viewer can read it (a read-only seat is the board's)
  PASS  report period: a bad custom range falls back to the last 12 months
  PASS  procedure: a PDF is stored; a file that is not a document is refused whatever its name
  PASS  procedure: the version in effect today is current, not a future one
  PASS  procedure: review state overdue / soon / ok / none
  PASS  procedure: the public link serves the current public version, and the complaint form links to it
  PASS  procedure: unlinked, nothing is public and the form shows no link
  PASS  procedure: an overdue review shows on the statutory clock page
  PASS  procedure: a viewer cannot upload (403)
  PASS  related: a DSAR link is added and logged; a javascript: address is refused
  PASS  Privara: a reference is read through the detail route (type, status, deadline)
  PASS  Privara: an older Privara (reference 404 on the detail route) is found in the list
  PASS  Privara: a refused key and an unknown reference are said plainly, never shown as a status
  PASS  Privara: "Check in Privara" on the complaint stores what was read and when
  PASS  related: the complaint page shows the link, the Privara answer and the check button
  PASS  related: a viewer cannot add, check or remove (403)
  PASS  Privara settings: an empty key field keeps the saved key
  PASS  Privara settings: "Forget the saved key" clears it
  PASS  related: removing a link is logged
  PASS  backup: the Privara API key is a credential column, so the JSON backup redacts it by pattern
  PASS  the procedure checks leave no file behind in data/uploads
  PASS  api roles: POST /api/complaints refuses every role without the permission (403) and admits the rest
  PASS  api roles: POST /api/complaints/{id}/status refuses every role without the permission (403) and admits the rest

== customer voice: the team's chat ==
  PASS  chat payload: Slack, Teams and Google Chat read "text"; Discord reads "content"
  PASS  chat: a complaint from the public form posts one line with its number and acknowledgment date, and nothing the complainant wrote
  PASS  chat: nothing when the new-complaint notice is switched off
  PASS  chat: an acknowledgment reminder posts its line too, by number and date only
  PASS  Settings: "Send a test line" reports what the chat answered, and is audited
  PASS  ...and a chat that refuses is reported as refused (HTTP 500)
  PASS  the shareable JSON backup leaves the chat address out
  PASS  import dry run: an impossible date (2026-02-30) or a date in the wrong format is counted as skipped, not ready, and nothing is written (ready 1, skipped 2)

————————————————————————————————————————————————————

== 4.0: the Update button is wired ==
  PASS  updates: the release public key is pinned
  PASS  updates: Database::migrate() creates the updater table (in code, not in a comment)
  PASS  updates: the updater table holds exactly one state row
  PASS  updates: index.php clears stale compiled code before anything else loads
  PASS  updates: index.php finishes or rolls back an update before any controller loads, with this app's canary
  PASS  updates: route get /updates is updates_page()
  PASS  updates: route post /updates/key is updates_key_save()
  PASS  updates: route post /updates/check is updates_check()
  PASS  updates: route post /updates/apply is updates_apply()
  PASS  updates: updates_page() opens with the restore page's admin gate
  PASS  updates: updates_key_save() opens with the restore page's admin gate
  PASS  updates: updates_check() opens with the restore page's admin gate
  PASS  updates: updates_apply() opens with the restore page's admin gate
  PASS  updates: every POST checks the CSRF token
  PASS  updates: the page is told why updates are off here (Updater::whyOff, F5 A1 pass)
  PASS  updates: the menu links to the page
  PASS  updates: bin/update.php refuses the web and finishes in a fresh process
  PASS  updates: where updates are off (Cloud, the demo, a missing extension) the page says why and offers no button
  PASS  updates: with a key and a newer signed release the page offers "Back up and update"

== 4.0 A2: sample data, backups, health ==
  PASS  A2: the owner's-install checks ran to the end
  PASS  A2: an owner's own install (no Cloud config, no demo marker)
  PASS  A2: a fresh install's sample tables are all empty, so the installer's sample data can go in
  PASS  A2: switched off: an install without "Start with sample data" gets none
  PASS  A2: first request: the marker is used once and the sample business is there
  PASS  A2: every one of the owner's settings is theirs, not the sample's
  PASS  A2: the sample logins cannot sign in: no password matches, and switched off where the product can
  PASS  A2: the owner's own login is untouched
  PASS  A2: the load is in the audit log
  PASS  A2: a second marker on a Complia with records seeds nothing and changes nothing
  PASS  A2: real use: one of the owner's own rows among the sample
  PASS  A2: remove: the owner's own row stays, and a sample row it points at is kept
  PASS  A2: remove: the report says what went, and what was kept because the owner's records use it
  PASS  A2: remove: the rest of the sample is gone
  PASS  A2: remove: every kept sample row keeps all its own parts
  PASS  A2: remove: no row is left pointing at a row that was removed
  PASS  A2: remove: the sample logins are gone but the ones kept rows use, the owner stays, and nothing counts as sample any more
  PASS  A2: remove: the sample's files are gone from disk (1 written; one a row that stays names stays with it)
  PASS  A2: remove: the owner's own uploaded file is untouched
  PASS  A2: "Load demo data" on a Complia with records refuses and deletes nothing
  PASS  A2: backup: written, sent to the bucket (a signed PUT), and read back
  PASS  A2: backup: the bucket secret is not in the backup it protects
  PASS  A2: backup: the owner's "keep" count is honored (2 kept)
  PASS  A2: switched off: with automatic backups off, a backup is never due however old the last one
  PASS  A2: the backup lines for Backup & restore: last backup fine, offsite fine, read-back fine
  PASS  A2: an offsite endpoint must be https for an owner (plain http only in tests)
  PASS  A2: all 25 owner's-install checks reported
  PASS  A2: Database::migrate() creates the sample-data and backup tables (in code, not in a comment)
  PASS  A2: index.php loads the three cores and the wiring
  PASS  A2: the first request seeds the installer's sample, and an owner's install backs itself up when due
  PASS  A2: route post /sample-data/remove is sample_data_remove()
  PASS  A2: route post /restore/backups is backups_save()
  PASS  A2: route post /restore/backup-now is backup_now()
  PASS  A2: route get /health is health_page()
  PASS  A2: sample_data_remove() opens with the restore page's admin gate
  PASS  A2: backups_save() opens with the restore page's admin gate
  PASS  A2: backup_now() opens with the restore page's admin gate
  PASS  A2: health_page() opens with the restore page's admin gate
  PASS  A2: every POST checks the CSRF token
  PASS  A2: off an owner's install (Cloud, the demo) the backup settings and "Back up now" refuse in words
  PASS  A2: the installer offers sample data, ticked, and only leaves the first-run marker (with every table's row count)
  PASS  A2: the first run is strictly once (a marker that cannot be removed seeds nothing), and treats the installer's rows as its defaults only while nothing has changed
  PASS  A2: the scheduled-backup URL runs the same backup (offsite copy and read-back included)
  PASS  A2: "Load demo data" outside the public demo goes through SampleData (only into an empty Complia, removable)
  PASS  A2: the menu links to Health
  PASS  A2: the sample-data banner shows only to an admin, only while sample data is there, with its Remove button, and stays put (nested: the UI kit toasts a bare .main > .alert away)
  PASS  A2: Backup & restore shows the automatic backups, "Back up now" and the bucket settings (none on Cloud)
  SKIP  A2: the Health page render (this suite loads no view helpers in its own process; the owner's-install check and update_e2e.sh run the real app)
  PASS  erase planting · sqlite: the subject is created and a marker is planted in all 10 personal columns
  PASS  erase planting · sqlite: the app's own erase runs through its route and reports no failure
  PASS  erase planting · sqlite: no planted marker survives anywhere (23 tables dumped), outside the 3 column(s) kept by ruling (possible legal records)
  PASS  erase planting · sqlite: the file attached to the complaint is deleted from disk
  PASS  erase planting · sqlite: the erase page (/complaints/1) says what an erase keeps: "Kept: the complaint's subject, details and outcome and the h…"
  PASS  erase planting · sqlite: MANUAL.md says the same: "Kept: the complaint's subject, details and outcome and the h…"
ALL 440 TESTS PASSED (0 failed)

← Back to Complia · Manual · Quickstart · API