v0.2.0
Pre-releaseA stranger can install this now, and can see what it does without reading Thai.
Phase 1 of the plan in the backlog, plus the PDPA minimum a
pilot needs.
Added
-
First-run setup for a clean install (CW-022).
npm run db:initcreates an
organisation, the eight system roles and one administrator — and nothing else.
Until now the only way to get an account wasdb:seed, which the README
itself labels demo data, so installing Cwork for real people meant loading a
fictional company with published credentials and then cleaning up after it.npm run db:init -- --webprints a single-use token instead and setup
finishes in a browser at/setup. Minting a token requires shell access to
the server, which is what keeps a deployment that is up but not yet set up
from being claimed by whoever reaches it first. There is deliberately no
endpoint that issues one, and no "if no organisation exists, let anyone
through" check anywhere.- Both paths refuse once an organisation exists, including a token that was
valid moments earlier, and two concurrent attempts are serialised by a
Postgres advisory lock. - The first administrator holds
role:manage, so it enrols a second factor at
its first sign-in. Nothing in setup prints a TOTP secret.
-
GET /config, a public endpoint reporting the feature flags a client
needs before it can draw its shell. One flag today,assistantEnabled. -
npm run db:demo, a second half to the demo seed that gives the fictional
company a history: last month's payroll run, leave that was requested and
approved, expense claims, a hiring pipeline mid-flight and an open review
cycle.db:seedchains the two. It drives the application's own services
rather than inserting rows, so the payslips come from the real Thai tax code
and the run had to be prepared and approved by two different people. -
A recorded walkthrough in both READMEs, and the script that produces it
(docs/demo/record.mjs). It drives the real console against the seeded
company, second factor included, so the demo cannot drift from the product.
English captions are burned in, because the interface is Thai until CW-016
lands and thirty seconds of a language you do not read tells you nothing. -
The PDPA minimum a pilot needs (CW-026). Personal data
lists every class of personal data the schema holds and where, the retention
the software actually enforces and the far longer list it does not, and a
step-by-step erasure procedure that was run against a real database before it
was written down. A draft employee notice in
Thai goes with it.- Two facts that procedure had to establish rather than assume: an employee
who has ever clocked in cannot be deleted — the append-only trigger on
attendance_punchesblocks the cascade — and the audit trail cannot be
erased at all without destroying the property it exists for. Both are now
written down, with what to do instead.
- Two facts that procedure had to establish rather than assume: an employee
Changed
- The assistant is hidden when it is switched off (CW-027).
ASSISTANT_ENABLED=false
is the default and every role carriesassistant:use, so the standard install
put an assistant entry in front of everybody that could only fail — which
reads as a broken product rather than a disabled option. The console now drops
the sidebar entry and the app drops the bottom-navigation tab. A bookmark from
before it was switched off still resolves, to the screen that explains why it
is off. The boot-time rule is unchanged. db:seednow says it is demo data when it runs, and refuses to install itself
beside an organisation it did not create (SEED_FORCE=1overrides).- The demo seed no longer leaves most of the console empty. Payroll,
expenses, recruitment and performance all showed "nothing here yet" after
the documented five-minute setup, which did not match the screenshots in the
README and told an evaluator nothing about whether any of it works.
Fixed
- An account could not use an access token issued in the same wall-clock second
as the account row was created:User.sessionsValidFromis stored with
millisecond precision and is compared against a JWTiatin whole seconds, so
the token read as older than the account and every request came back "session
has been invalidated". Unreachable before — no account could be created and
signed into that quickly — and reachable the moment setup existed.