You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A production deployment should contain no provisioning code. Creating the first tenant,
minting the first admin credential and writing sample rows are setup, not serving —
a distinct mode, entered on an explicit signal, and closed by a state change that cannot
be re-entered from the web once the install is configured.
Today all three run inside the serving application, on every start, and one of them asks
whether a test suite is running.
What runs at boot today
init_db() is called on every production start — scripts/deploy/run_all_services.py:181,
and src/core/main.py:211. The comment immediately above the call reads:
ℹ️ Note: init_db() is safe - it only creates tables (IF NOT EXISTS) and default tenant (if no tenants exist)
That sentence is the assumption worth revisiting: creating a tenant is not a safe no-op,
it is provisioning.
What
Where
Gated by
tenant_id="default", subdomain="default"
src/core/database/database.py:45-72
nothing.CREATE_DEMO_TENANT picks the SHAPE (configured demo vs "My Sales Agent" awaiting setup); both branches create the row
mock adapter, currency limits for USD/EUR/GBP, and a principal holding the literal token ci-test-token
database.py:90-110
CREATE_DEMO_TENANT=true
sample products and inventory
database.py:142, :231
provisioning.create_sample_data (config.py:186)
the admin bootstrap login route
src/admin/app.py:350-351
settings.testing.adcp_auth_test_mode and not is_production
The last row is the one that makes the shape visible: production composition asks whether
a suite is running. The app under test is therefore not the app deployed, and a test that
wants the route has to set a test flag to conjure it. That is a first-run bootstrap
capability named, credentialled and gated as a test fixture.
Two integration tests (test_template_url_validation.py::test_all_template_url_for_calls_resolve
and ::test_form_actions_point_to_valid_endpoints) pass or fail depending on whether the
test flag is on, because they scan templates for url_for() with no awareness of the {% if test_mode %} guard around the bootstrap route. They are ledgered pending this
decision, and they are the symptom that surfaced it.
This is a solved problem; the pattern is worth copying rather than inventing
Every CMS and framework has settled on the same shape, and the shared property is not the
UI — it is that setup is closed by durable state, not by a flag:
WordPress: wp-admin/install.php runs the wizard, and refuses once the options table
carries a siteurl. The route stays on disk and becomes permanently inert.
Django: migrate and createsuperuser are explicit management commands; the serving
app has no route that can create a superuser.
Drupal: an install profile runs once, and settings.php gaining a hash closes it.
Rails: db:setup / db:seed are tasks, never request handlers.
So the design question is which signal opens setup and which state closes it — not whether
a CLI or a wizard. A wizard is friendlier than a CLI, as noted, and a wizard is perfectly
compatible with "no provisioning code in production" provided the gate is a database fact
(no configured tenant / no admin identity) rather than an environment variable, and
provided re-entry after that fact changes requires wiping the database.
Where this sits relative to the AuthZ work
Adjacent, not overlapping, and the seam between them is worth stating:
This issue asks where the first such identity comes from, and how the deployment gets
from empty database to configured seller without shipping the code that does it.
They meet at exactly one point: whatever mints the first admin credential produces an
identity that #2232's authenticators and #2256's one admin seat then handle like any other.
Setup should hand off to that vocabulary and own nothing beyond it.
Scope for a first pass
Decide the gate: which database fact means "not yet set up", and what closes it.
Move tenant creation, credential minting and sample data out of the serving app.
Delete adcp_auth_test_mode from composition, so the deployed app and the tested app
are the same app.
Graduate the two ledgered test_template_url_validation entries, whose verdict stops
depending on a flag.
The rule being proposed
A production deployment should contain no provisioning code. Creating the first tenant,
minting the first admin credential and writing sample rows are setup, not serving —
a distinct mode, entered on an explicit signal, and closed by a state change that cannot
be re-entered from the web once the install is configured.
Today all three run inside the serving application, on every start, and one of them asks
whether a test suite is running.
What runs at boot today
init_db()is called on every production start —scripts/deploy/run_all_services.py:181,and
src/core/main.py:211. The comment immediately above the call reads:That sentence is the assumption worth revisiting: creating a tenant is not a safe no-op,
it is provisioning.
tenant_id="default",subdomain="default"src/core/database/database.py:45-72CREATE_DEMO_TENANTpicks the SHAPE (configured demo vs "My Sales Agent" awaiting setup); both branches create the rowci-test-tokendatabase.py:90-110CREATE_DEMO_TENANT=truedatabase.py:142,:231provisioning.create_sample_data(config.py:186)src/admin/app.py:350-351settings.testing.adcp_auth_test_mode and not is_productionThe last row is the one that makes the shape visible: production composition asks whether
a suite is running. The app under test is therefore not the app deployed, and a test that
wants the route has to set a test flag to conjure it. That is a first-run bootstrap
capability named, credentialled and gated as a test fixture.
Consequences already filed
defaulttenant for a loopbackHost.That fallback is only reachable because
defaultalways exists; a deployment that wasnever provisioned with one would have nothing to fall into.
ADCP_TESTING. Same class one frame down:an allowance for the machine running the code, surviving into the path that serves buyers.
test_template_url_validation.py::test_all_template_url_for_calls_resolveand
::test_form_actions_point_to_valid_endpoints) pass or fail depending on whether thetest flag is on, because they scan templates for
url_for()with no awareness of the{% if test_mode %}guard around the bootstrap route. They are ledgered pending thisdecision, and they are the symptom that surfaced it.
This is a solved problem; the pattern is worth copying rather than inventing
Every CMS and framework has settled on the same shape, and the shared property is not the
UI — it is that setup is closed by durable state, not by a flag:
wp-admin/install.phpruns the wizard, and refuses once the options tablecarries a
siteurl. The route stays on disk and becomes permanently inert.migrateandcreatesuperuserare explicit management commands; the servingapp has no route that can create a superuser.
settings.phpgaining a hash closes it.db:setup/db:seedare tasks, never request handlers.So the design question is which signal opens setup and which state closes it — not whether
a CLI or a wizard. A wizard is friendlier than a CLI, as noted, and a wizard is perfectly
compatible with "no provisioning code in production" provided the gate is a database fact
(no configured tenant / no admin identity) rather than an environment variable, and
provided re-entry after that fact changes requires wiping the database.
Where this sits relative to the AuthZ work
Adjacent, not overlapping, and the seam between them is worth stating:
how an already-identified caller's requests are authorized. Both take the existence of
an admin identity as given.
from empty database to configured seller without shipping the code that does it.
They meet at exactly one point: whatever mints the first admin credential produces an
identity that #2232's authenticators and #2256's one admin seat then handle like any other.
Setup should hand off to that vocabulary and own nothing beyond it.
Scope for a first pass
adcp_auth_test_modefrom composition, so the deployed app and the tested appare the same app.
test_template_url_validationentries, whose verdict stopsdepending on a flag.
defaulttenant that makes it reachable.