From c6a100405d8030cb2b498a304f5d4d5bfa3e5f6b Mon Sep 17 00:00:00 2001 From: Cody Schaefer Date: Mon, 24 Aug 2026 11:44:16 -0500 Subject: [PATCH] T10.8 - document that a pre-D13 local SQLite database rejects new sign-ins Found while diagnosing a login failure that turned out to be an account lockout. The local wpsuite.db has no alembic_version table - it was built by create_all() before D13 - so users.password_hash is still NOT NULL with no default while the current model has no such column. Verified on a COPY of the database rather than reasoned about: provisioning a new account raises IntegrityError: NOT NULL constraint failed: users.password_hash The shape of it is what makes it worth documenting. Accounts already in the file keep working, so the developer can sign in and nothing looks wrong; it breaks only when a NEW person signs in, and it surfaces as HTTP 500, which reads as a server fault rather than a schema one. Nothing anywhere told a developer their existing database needed migrating. The note gives the non-destructive fix - stamp a1b8c6d4e2f9 then upgrade head, which runs only the drop and keeps the data - and says why a plain `alembic upgrade head` would fail on such a file. Earlier in the session I suggested deleting the database; stamping is strictly better, since deleting discards test data for no benefit. Production is unaffected: Postgres, migrations applied at container start. Also updated the nested-group done-when to reflect what is now actually known. The transitive matching rule WAS exercised against the live directory today for a direct member of a 2,003-member group and returned a match, so the rule and the filter are right on this estate. A genuinely nested case remains untested for want of such an account, and the box is marked partial rather than done. Co-Authored-By: Claude Opus 5 (1M context) --- docs/waves/wave-10.md | 2 +- server/README.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/waves/wave-10.md b/docs/waves/wave-10.md index f1e72af..168dfe7 100644 --- a/docs/waves/wave-10.md +++ b/docs/waves/wave-10.md @@ -64,7 +64,7 @@ across resolved addresses, because round-robin will hand out a rebooting DC's ad - [x] an empty username returns failure without calling `bind()` - [x] `Tls` is constructed with `validate=ssl.CERT_REQUIRED` and an explicit `ca_certs_file` - [x] no code path sets `CERT_NONE`, and none falls back to the system trust store — asserted by an AST walk in `ldap_auth_check`, not a grep: the docstring names it to explain the ban -- [ ] `member_of` returns true for an account in a **nested** child of the required group +- [~] `member_of` returns true for an account in a **nested** child of the required group — **partially closed Aug 24.** The transitive matching rule was exercised against the live directory for a DIRECT member (`CN=Prime Employees`, 2,003 members) and returned a match, so the rule and the filter are correct on this estate. A genuinely NESTED case (member of a group that is a member of the required group) still has no test, because no such account was to hand. `member_of` now also warns loudly if the transitive query returns nothing where a direct check succeeds, which is how a broken nested lookup would announce itself rather than refusing real members silently. - [x] a bind against `192.168.3.37` (raw IP) fails hostname validation rather than silently passing — `selftest()` to the raw IP returns `untrusted`; to `prime.local`, `0 (ok)` - [ ] `docker compose exec api openssl s_client -connect prime.local:636 -CAfile $LDAP_CA_FILE` reports `Verify return code: 0 (ok)` - [x] the module imports cleanly with no LDAP env set (unconfigured is a first-class state, as with `MICRON_DB_URL`) diff --git a/server/README.md b/server/README.md index b6e4ff2..51a478b 100644 --- a/server/README.md +++ b/server/README.md @@ -138,6 +138,37 @@ previously did not. ## Local dev +> ### A SQLite database created before D13 will reject new sign-ins +> +> `Base.metadata.create_all()` creates missing tables; it never alters existing ones. +> So a `wpsuite.db` built before D13 still has `users.password_hash` declared +> `NOT NULL` with no default, while the current model has no such column — and an +> INSERT that omits it is rejected: +> +> ``` +> IntegrityError: NOT NULL constraint failed: users.password_hash +> ``` +> +> Accounts already in the file keep working, so **you** can sign in and nothing looks +> wrong. It breaks the moment a *new* person signs in, because provisioning them is an +> INSERT — and it surfaces as an HTTP **500**, not a 401, so it reads as a server fault +> rather than anything to do with the schema. +> +> Such a database also has no `alembic_version` table, so `alembic upgrade head` would +> try to replay the baseline against tables that already exist. Stamp it first: +> +> ```bash +> python -m alembic -c server/alembic.ini stamp a1b8c6d4e2f9 # the revision before the drop +> python -m alembic -c server/alembic.ini upgrade head # runs only the drop +> ``` +> +> That keeps whatever is in the file. Deleting the database also works and +> `create_all()` rebuilds a correct schema, but it throws away your test data. +> +> Production is unaffected: it runs Postgres and the container applies migrations at +> start, so the column is dropped properly there. + + ```bash cd server python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate