Changelog
What shipped, when, and what it fixed. Current version: 1.0.0.
-
The money path, erasure, and two registers that stopped lying
The release a first paying customer can be sold. Every headline below was something the product either could not do or described incorrectly.
- A paying customer can change plan, modules and capacity from the billing page. Before this, selecting a plan answered 409 once a subscription existed, and a change made inside Stripe’s own portal moved money and granted nothing.
- Signup no longer grants an allowance the chosen plan does not sell.
- Purchase requests route to the requester’s manager, and above a threshold to finance as a second stage. The manager relationship existed and no code had read it.
- Employee Transitions is a joiner/leaver flow rather than the Handover table under a second price: an offboarding builds its checklist from what the person actually holds, and completing one returns the equipment and revokes the access.
- A workspace Owner can erase an individual, and an operator can delete a whole workspace. The privacy policy had promised this and nothing implemented it. /privacy §9 says precisely what erasure keeps.
- Who can reach a system, and who has held a piece of equipment, are now records that can be queried. “Who had this laptop in March” was previously unanswerable.
- Two authorization gaps closed: a revoked learner could still complete a compliance course, and the delivery centre refused people its own permission had admitted.
-
A public front door, real auth URLs, and a platform operator console
Every path served the same application shell, so a crawler executing it got “Preparing your workspace…” and the whole domain indexed as the sign-in form. There was no marketing content, no
robots.txt, no sitemap, no favicon and no<h1>in anything served.- Fourteen server-rendered pages at real URLs, plus a branded 404. Nothing on them quotes a number by hand: the pricing table, plan capacities and module cards are generated from the catalogue, and a test fails the build if a figure is typed in.
- Sign in, start trial and forgot password have their own addresses, and every deep link into the application is built by one function rather than assembled at each call site.
- The application shell moved to
/appand is markednoindex; unknown paths return 404 rather than 200. - A platform operator console, off unless enabled, with its own identity, mandatory two-factor authentication, and a structurally read-only surface. Every read it performs is recorded where the record outlives the workspace it describes.
- The backup and restore drill was written, run for the first time, and failed — on a source database that had never been stamped with a migration version. It refuses that source now, and the reference run restores and boots.
-
Database parity, delivery feedback and learning integrity
The whole test suite now runs against PostgreSQL as well as SQLite. The first run found a column overflow that had passed every previous test and would have aborted a transaction in production, plus two foreign keys the migrations had never created.
- Bounce and complaint feedback from the mail provider is ingested and enforced, so a hard-bounced address stops receiving mail platform-wide.
- Learning content is genuinely versioned. Acknowledgement evidence is insert-only and names the version acknowledged, so republishing a policy no longer rewrites the record of what people agreed to.
- Completion scores come from a server-graded quiz rather than from a number the browser submitted.
- Global search across every module, with escaped patterns, plus a command palette.
- Structured JSON logging with credential redaction.
-
Organization, audiences and scoped access
- Managed departments and teams with codes, cost centres, hierarchy and managers.
- Job title, department, team, manager and cost centre on every person.
- Dynamic audiences, so content is assigned to a department or a location rather than to a list that goes stale.
- Scoped permission grants, and CSV import and export for people.
-
Hardening
- Two-factor authentication, enforced on the session rather than on the sign-in form, so password reset and invitation acceptance cannot walk past it.
- Signed scanner callbacks, webhook replay defence and wider rate limiting.
- Direct-to-storage attachments with quarantine on a metadata mismatch.
-
Clean architecture and multi-location operations
- Explicit architectural layers, with dependency boundaries validated automatically.
- Managed locations as shared authorization and reporting infrastructure across every module.
-
Production foundation
Managed PostgreSQL, private object storage and a real transactional mail provider, replacing the single-file development assumptions.
-
Account lifecycle
Expiring single-use invitations and reset links stored only as digests, session control, notifications and reminders.