Skip to content

Add tenant provisioning and provisioning hooks - #37

Merged
suchait007 merged 1 commit into
mainfrom
feature/11-tenant-provisioning
Sep 9, 2026
Merged

suchait007 merged 1 commit into
mainfrom
feature/11-tenant-provisioning

Conversation

@suchait007

Copy link
Copy Markdown
Contributor

Closes #11. Closes #6.

provisioning.onboard(account.slug());   // from your signup path — a call, not a deploy

The order is the design

registry row, PROVISIONING   the tenant exists; nothing serves it yet
migrate                      its schema or database, when the strategy needs one
hooks                        seed data and everything application-specific
registry row, ACTIVE         now it is served

A tenant marked ACTIVE before its hooks have run is briefly live and broken.

Why hooks run with the tenant bound

This is the bug the feature exists to remove. Onboarding was a three-step recipe every adopter wrote by hand, and step three is the one they got wrong — seed data written with no tenant bound fails the policy under row-level security and has no connection at all under database-per-tenant. So onboarding half-succeeded.

A hook now writes seed data with ordinary repository calls and no tenant parameter.

PROVISIONING, and why it exists

A half-created tenant that is absent looks like one nobody asked for. One left ACTIVE looks ready and is not. PROVISIONING is neither: not served, skipped by forEachTenant, and the exception names the hook that failed.

Onboarding is idempotent, so retrying is just calling it again — which means hooks must tolerate running twice, documented rather than assumed.

Scope, stated honestly

onboard provisions this service. It cannot reach the other nineteen in an estate, and an API pretending otherwise would lie. Under row-level security they have nothing to do anyway — the tables are shared. The docs describe the control-plane pattern and the three coordination options rather than inventing one.

TenantMigrationRunner now exposes migratesPerTenant(), so onboarding skips Flyway entirely under a shared store rather than starting it to discover there is nothing to apply.

Verification

mutation result
mark ACTIVE up front 3 tests failed
run hooks without binding the tenant 2 failed
carry on after a hook fails 3 failed
drop idempotency 1 failed
ignore hook ordering 1 failed

Reverts confirmed in compiled bytecode.

Library 161 green. order-service 39 green, including three where the seeding hook writes through the real repository — an insert the policy rejects unless provisioning bound the tenant. Removing that binding fails the consumer suite, which I verified rather than assumed.

Docs: docs/onboarding.md.

Note: adds PROVISIONING to TenantStatus, which #34 also touches. Small overlap, trivial to resolve, and they compose correctly — #34 refuses non-ACTIVE tenants, which is exactly the right treatment for one still provisioning.

Closes #11. Closes #6.

Onboarding was a three-step recipe every adopter wrote by hand, and step three is the one
they got wrong: seed data written with no tenant bound fails the policy under row-level
security and has no connection at all under database-per-tenant, so onboarding
half-succeeded. `TenantProvisioning.onboard` does the sequence, and runs each hook with the
new tenant bound so an implementation writes seed data with ordinary repository calls.

The order is the design. A tenant exists as PROVISIONING before anything is done for it,
gets its storage, gets its hooks, and only then becomes ACTIVE — a tenant marked ACTIVE
before its hooks have run is briefly live and broken.

PROVISIONING is a new status, and it is what makes a failure unambiguous. A half-created
tenant that is simply absent looks like one nobody asked for; one left ACTIVE looks ready
and is not. This is neither: it is not served, forEachTenant skips it, and the exception
names the hook that failed.

Onboarding is idempotent — an ACTIVE tenant is returned untouched, so a retried webhook or
a redelivered message is safe, and a tenant left PROVISIONING is resumed. That means hooks
must tolerate running twice, which is documented rather than assumed.

It provisions THIS service. It cannot reach the other nineteen in an estate, and an API
pretending otherwise would lie — under row-level security they have nothing to do anyway,
since the tables are shared. TenantMigrationRunner now exposes migratesPerTenant() so
onboarding skips Flyway entirely under a shared store rather than starting it to find
nothing to apply.

Mutation-tested: marking ACTIVE up front, running hooks unbound, continuing past a hook
failure, dropping idempotency, and ignoring hook order each turned tests red, with the
revert confirmed in the compiled bytecode.

Library 161 tests green. order-service 39 green, including three where the seeding hook
writes through the real repository — an insert the policy rejects unless provisioning bound
the tenant, so removing that binding fails the consumer suite, which was verified.

Signed-off-by: suchait007 <159273969+suchait007@users.noreply.github.com>
@suchait007
suchait007 merged commit a212938 into main Sep 9, 2026
5 checks passed
@suchait007
suchait007 deleted the feature/11-tenant-provisioning branch September 9, 2026 04:19
suchait007 added a commit that referenced this pull request Sep 9, 2026
Both were squash-merged, so main carries their content as new commits while this branch
still had the originals — which is why a stacked branch conflicts even when the content
agrees.

All four conflicts were additive and both sides are kept: two optional dependencies in the
pom, two autoconfiguration registrations, two documentation rows. The exception is
order-service's pom, where both branches had added spring-boot-starter-actuator; that is
now one dependency with a comment covering both reasons.

Repaired a broken dependency block on the way: resolving the pom by concatenating the two
sides split a <dependency> element and produced invalid XML, which the build caught.

Library 183 tests green, order-service 45 green.

Signed-off-by: suchait007 <159273969+suchait007@users.noreply.github.com>
@suchait007 suchait007 mentioned this pull request Sep 9, 2026
9 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New-tenant bootstrap Provisioning hooks

1 participant