A detour from plastron
The main line of work here is plastron — a polyglot reactive substrate (spreadsheet kernel, desktop shell, hosted at plastron.ca). This HRIS prototype is a detour: a self-contained experiment in CRDT SQLite (cr-sqlite) for offline-first data that must merge cleanly after a day of disconnected edits.
Plastron is hosted at plastron.ca with server-backed persistence today; the zero-server, offline-capable single-file build is still the goal (no OPFS in offline plastron yet). Before bolting CRDT SQLite into the full kernel, we wanted a smaller, adversarial test case: Protected B personnel rows, encrypted portable dumps, multi-operator convergence, and no network egress. Lessons from this build (changeset export, merge ritual, audit tables, CSP-safe inlined WASM) are intended to feed back into plastron as a future cr-sqlite integration, not to replace the spreadsheet model.
Think of offline HRIS as a field exercise for a capability plastron will need; the Juárez skin is just a narrative wrapper for the demo.
The problem
A community police office still has to plan staffing: which positions exist, who holds them, where the gaps are, what payroll pressure looks like. Enterprise HRIS products assume always-on connectivity, identity federation, and a vendor security boundary. A neighbourhood command post, a borrowed laptop, or a planning cell working from USB sticks does not get that luxury.
The design question was blunt: can several planners maintain one consistent personnel picture without a server, without the public internet, and without ever writing plaintext rows to disk?
The idea
Ship one file — a built index.html — that embeds React, SQLite,
and the cr-sqlite CRDT extension as inlined WebAssembly. Operators double-click it.
The live database exists only in memory. The only artifacts that touch disk are
encrypted blobs: a morning truth.hrisdump and evening
lastname.hrischanges deltas.
Content Security Policy sets connect-src 'none', so even if script execution were
compromised, the page has no fetch, WebSocket, or beacon path off the machine.
That is a deliberate trade: perfect for an air-gapped ritual, hostile to SaaS analytics.
The demo skin says Juárez PD — Zona Centro, Anapra, fictional badge numbers, invented salaries — because the original spreadsheet model came from a Canadian police HR layout and we wanted a clearly non-production narrative. The mechanics are jurisdiction-agnostic; only labels and seed data change.
The daily ritual
Think of it as git for staffing data, except the merge commit is encrypted and the coordinator may have no admin rights beyond a browser.
Morning — one truth
- The coordinator distributes
truth-YYYYMMDD.hrisdumpand the day's org passphrase (out of band — never bundled with the file). - Each planner opens the HTML file locally, decrypts the truth, and passes the user gate (pick identity from a pre-provisioned operator list).
- The app records a
session_openevent and stamps amorning_db_versionwatermark.
Day — offline work
- CRUD on officers, positions, assignments; dashboard refreshes deficits and headcount live.
- Every mutation appends to
change_event(who / what / when) and updates row-levelupdated_bycolumns. - cr-sqlite records replica-level changes in
crsql_changesfor merge — separate from human attribution. - Nothing is autosaved to disk. Closing the tab without exporting loses work — by design.
Evening — converge
- Each planner exports my changes → encrypted
.hrischanges(not a full database dump). - The coordinator opens the same HTML file, loads the morning truth, and uses Merge operator changes on the Data & Security screen to apply every delta.
- cr-sqlite merges in an order-independent way; conflicting edits converge under CRDT rules instead of last-writer-wins file replacement.
- Export the merged truth for tomorrow. Optional Node script
merge-hris.mjsexists for automation; it is not required to operate.
CRDT solves data convergence. It does not automatically solve accountability — that is whychange_eventandsession_logare first-class tables that sync with the same merge machinery.
Protected B and CSE cryptography
Personnel files (names, assignments, compensation) are not open data. In Government of Canada terms they typically land at Protected B: unauthorized disclosure could cause serious injury outside the national interest — see TBS Standard on Security Categorization (Appendix J) and the 2023 direction on personal information in the aggregate. A community police office handling officer PII faces the same injury model even when the formal framework is municipal privacy law rather than Treasury Board policy.
What the app satisfies technically
For encryption algorithms, CSE publishes ITSP.40.111 (Cryptographic algorithms for UNCLASSIFIED, Protected A, and Protected B information). The dump format uses the strongest practical stack from that document for file-at-rest:
- AES-256-GCM — bulk encryption with authenticated integrity (ITSP.40.111 §3.1, §4.2)
- PBKDF2-HMAC-SHA-256 at 250,000 iterations — passphrase-to-key derivation (ITSP.40.111 §10.9; GC password guidance minimum 10,000)
- Per-file random salt and IV — 16-byte salt, 12-byte GCM nonce
- Non-extractable Web Crypto keys — raw key material never surfaces in JavaScript
Storage and transit expectations for sensitive electronic media are articulated in Directive on Security Management, Appendix B §B.2.3.4–§B.2.3.6 (protect data at rest, in transit, and on portable media commensurate with sensitivity; encrypt on public or risky networks). This app addresses at-rest for portable dumps; coordinators still need a procedure for USB handoff or approved encrypted channels.
What the app enforces in software
- No plaintext
.sqliteor.csvexport paths - Memory-only database while unlocked; lock wipes passphrase and DB
- Operator identity gate + append-only
change_eventandsession_log - Encrypted changeset export for multi-planner sync without competing full dumps
What still requires organizational "soft" requirements
Protected B is not a checkbox you get from AES alone. Before operational use with real officers, a jurisdiction would still need documented controls the browser cannot implement:
- Privacy Impact Assessment — Directive on Privacy Impact Assessment (federal reference); municipal equivalent under local FOIP/IP legislation
- Threat & risk assessment and authorization to operate — Policy on Government Security lifecycle; accept residual risks (lost USB, shoulder surfing, stale truth files)
- Need-to-know roster — who appears in
app_user; security screening verified outside the app - Passphrase ceremony — org/day key distributed out of band; rotation on compromise; never stored beside dumps (including cloud-sync folders)
- Media policy — preferred USB handoff vs whether OneDrive/Google Drive may hold ciphertext
- Breach playbook — Privacy Breach Management Toolkit; PIN 2022-01 for cyber incidents involving personal information
- Coordinator SOP — morning distribution, evening collection, merge audit review, handling missed exports and stale watermarks
- Operator training — export changes before lock; do not treat the user picker as MFA
We split these explicitly in the project docs: in-app build requirements vs governance procedure requirements (the latter intentionally deferred until after the prototype stabilizes).
Why a browser tab at all?
A community office may not be allowed to install server software or database engines on managed
PCs. A static HTML file with inlined WASM is weird, but it has properties enterprise stacks rarely
offer together: offline-first, auditable merge semantics, ciphertext-only persistence, and zero
network egress enforced by CSP. The GitHub Pages host
(demo) is convenience for
evaluation; the intended deployment is download-and-run locally on a controlled machine —
file:// with no backend.
Status
The repository rheophile10/offline-Protected-B-HRIS includes Playwright user stories, an automated CRDT convergence test, and the screen-recorded walkthroughs below (also on the standalone how-to guide). It is an exploration of how far you can push personnel HRIS into a single encrypted, mergeable, offline artifact — not a production authorization, and not a fork of the product roadmap.
Next for plastron: absorb the CRDT SQLite patterns here into the substrate so cels, sheets, and shared state can survive offline edits and reconcile the same way. Next for this detour (if it continues): governance documentation for a real pilot, optional PIN-per-operator, jurisdiction-specific legal review.
How to use it
Each clip below is a Playwright test from
tests/hris.spec.ts
screen-recorded against the real built app with 800 ms slow-motion between
actions so each step is easy to follow — the same videos shipped in the repo's
how-to guide.
User stories are documented in
USER-STORIES.md;
setup and build notes live in the
README.
index.html,
double-click it, and everything below works offline — no server, no install, no admin rights.
Export your changes before locking or closing; unsaved work is lost by design.
Start a session US-1
Open the app, load today's data behind a passphrase, pick your operator identity.
- Open the file — locked gate first.
- Keep Demo data, enter a passphrase, Start session.
- Pick an operator; dashboard shows 27 officers and staffing gaps.
Add an officer US-2
Add a new hire; roster and dashboard update with audit attribution.
- Officers → + Add officer, badge + name, Save.
- Headcount rises; change is attributed and audited.
Assign an officer to a position US-3
Keep staffing levels and vacancy deficits current.
- Assignments → + Assign officer, pick officer + position, Assign.
- Staffing views recompute automatically.
Query data & export encrypted CSV US-4
Run ad-hoc SQL; share figures without plaintext ever touching disk.
- SQL Console → write a query → Run.
- Export produces
.csv.enconly — decrypt later from Data & Security.
Export changes & merge (no Node) US-5
The coordinator merges everyone's offline edits inside the same HTML file.
- Operator: Export my changes → encrypted
.hrischanges. - Coordinator: open morning truth → Merge operator changes → select deltas.
- Export merged truth for tomorrow. CRDT converges divergent edits.
Recruit & hire an applicant US-7
Run a hiring pipeline and convert a successful applicant into an officer.
- Recruitment kanban — add applicants, move through stages to Offer.
- Hire → creates officer record + active assignment; headcount updates.
Training & compliance US-8
See what's expiring and renew certifications.
- Compliance — Expired / Expiring / Valid counts and firearms-current ratio.
- Filter by KPI; Renew or + Record certification.
Leave & absence US-9
Track requests, approvals, and who's off today.
- Leave — on-leave today, pending, upcoming counts.
- + Request leave; Approve or Deny pending rows.
Workforce planning US-10
Project vacancies ahead of retirements.
- Planning — projected deficit at +1/+2/+3/+5-year horizons.
- Officers nearing pension milestone listed for hire-ahead decisions.
Audit trail US-11
See who changed what — attribution that survives the merge.
- Audit — change log (operator, entity, action) + session events.
- Logs are CRRs; they travel with each changeset into the merge.
Officer file US-12
Emergency contacts, performance reviews, and conduct in one place.
- Officer File → pick an officer.
- View contacts, reviews, conduct; add a review or update a disposition.
Equipment register US-13
Track who holds each firearm, vehicle, radio, or camera.
- Equipment — in-service / issued / available counts.
- Issue or Return assets; mark Maintenance or Retired.
Lock the session US-6
Wipe decrypted data from memory when you step away.
- Lock session — returns to gate, clears DB + key + identity from memory.
- Export changes first; unsaved work is lost by design.
Full standalone guide: rheophile10.github.io/offline-Protected-B-HRIS/how-to.html
References
- CSE ITSP.40.111 — approved algorithms (AES, GCM, PBKDF2)
- CSE ITSAP.40.016 — encryption for sensitive data at rest and in transit
- Policy on Government Security
- Directive on Security Management (Appendix B — IT security controls)
- Standard on Security Categorization (Protected A / B definitions)
- Direction on security categorization of personal information in the aggregate (2023)
- Guideline on Password Security (PBKDF2, offline attack countermeasures)
- cr-sqlite — convergent replicated SQLite
- offline-Protected-B-HRIS — source & build
- plastron — main project; offline + CRDT SQLite planned
- plastron.ca — hosted build (server-backed today; zero-server goal)