docs: add implementation plans for specs 0a and 0b

- 2026-07-24-shared-module.md: 8 tasks, extract the shared Go module
- 2026-07-24-instance-rename.md: 9 tasks, Org -> Instance including the
  database field, with migration 0004 and a rollback command

No automated tests per instruction; verification is by compiler, grep, and
manual end-to-end plus production-snapshot rehearsal. Spec testing sections
updated to match.
This commit is contained in:
2026-07-24 13:31:14 +01:00
parent b027ad3f7f
commit bdc26e4799
4 changed files with 2831 additions and 31 deletions
@@ -160,23 +160,38 @@ meaning and the marketing site is the first place a customer meets it.
## Testing
Extends the suite started in spec 0a.
**No automated tests.** Decision taken 2026-07-24, consistent with spec 0a.
1. **Migration test, empty database.** Runs clean, writes the marker.
2. **Migration test, seeded database.** Seed one document in every collection in
`scopedCollections` with a known `org_id`. Run. Assert every document has
`instance_id` with the same value, no document has `org_id`, and per-collection
counts are unchanged.
3. **Idempotency.** Run the migration twice. Second run is a no-op and does not
error.
4. **Interrupted run.** Rename half the collections, then run the full migration.
It completes the rest without error and passes verification.
5. **Guard.** With `instances` present and `orgs` absent, the migration
short-circuits and records the marker.
6. **Scoped-collection completeness.** A test that reads the collection list from
the live database and fails if any collection outside `scopedCollections`
contains an `org_id` field. This test protects the list from going stale.
7. **`shared/provision` tests from 0a** pass unchanged after the rename.
This is the change where that costs the most: it moves the tenant isolation key
across 17 collections, and a mistake orphans a customer's entire fleet rather
than breaking a build. The compensating controls are therefore not optional, and
the implementation plan makes each a mandatory step:
1. **Dry run against a restored copy** before the code is even committed —
migrate a `mongorestore`d duplicate of production and read the per-collection
rename counts.
2. **Idempotency by hand** — run the dry run twice; the second must complete
with no error and nothing left to rename.
3. **Interrupted-run recovery by hand** — rename `orgs` manually, then run the
migration; it must complete and leave every document carrying `instance_id`.
4. **Count comparison against a production snapshot** — record every
collection's document count before and after; any difference stops the
release.
5. **Per-tenant isolation comparison** — for three real tenants, count rows in
`servers`, `keys`, `workflows`, `monitors`, `secrets` and `audit_logs` by
`org_id` before and by `instance_id` after. Identical, or the release stops.
This is the check that proves tenant isolation survived.
6. **Stale-field sweep** — assert no collection anywhere still holds an
`org_id`.
7. **Rollback rehearsal** — migrate a third copy, run `rename-rollback`, confirm
the counts return to baseline and the pre-release binary boots against it.
Deploying without having done this is not permitted.
8. **Boot guard, both directions** — sitesvc must refuse an unmigrated database
and start normally against a migrated one.
`AssertNoScopedCollectionMissed` runs at every boot and is fatal. With no test
suite it is the standing protection against a future collection being added
without being added to `ScopedCollections`.
## Verification before merge
@@ -226,23 +226,27 @@ called out here so it is a decision rather than an accident.
## Testing
The repo has no Go test suite today. This refactor is where one starts, because
the moved code is exactly the code whose correctness is load-bearing.
**No automated tests.** Decision taken 2026-07-24: the repo has no Go test suite
and one is not being started here. Verification is by compiler, `grep`, and
running both services end to end.
`shared/provision` unit tests, against a real MongoDB (testcontainers or a
`MONGO_TEST_URI` env guard — skip when unset rather than fail):
That places the whole weight on three manual checks, which the implementation
plan makes mandatory steps rather than suggestions:
1. `Slugify` — table test covering the existing regex behaviour: casing,
punctuation runs collapsing to a single `-`, leading/trailing trim.
2. `BaseSlug` — rejects under `MinSlugLength`, truncates over `MaxSlugLength`,
rejects every entry in `ReservedSlugs`.
3. `CreateOrg` — a second org with a colliding name gets `-2`; a third gets `-3`.
4. `CreateOrg` under a concurrent duplicate insert returns the clean
"slug already taken" error rather than a raw Mongo error.
5. `CreateUser` — hash verifies with bcrypt at cost 12; duplicate email is
rejected by the unique index.
6. `RollbackOrg` — deletes an org with no users; refuses one that has users.
7. `EnsureCoreIndexes` — idempotent across two calls.
1. **bson tag diff**`diff` the `bson:"…"` tags of each moved struct against
the originals. A changed tag orphans production data silently, and this is
the only thing that catches it.
2. **Slug behaviour walkthrough** — a throwaway `main` printing `Slugify`,
`BaseSlug` and `NextSlug` output for a fixed input table, compared against
expected output recorded in the plan.
3. **End-to-end agreement** — sign up through sitesvc against a scratch
database, open the verification link, then log into the control plane with
those credentials. This is the check that proves the two services still agree
about the documents they share. If it passes, the refactor worked.
Plus `grep` assertions that exactly one definition of `Slugify` and
`ReservedSlugs` survives repo-wide, and that no struct under `sitesvc/` carries
a `bson:"org_id"` tag.
## Verification before merge