feat: zero-Azure local mode, single-instance guard, one-script provisioning - #56
Merged
Conversation
…equired
`docker compose up --build` now runs the complete application (the production
image: SPA + API, one origin) against a local SQL Server 2025 seeded with the
worked-example schema (database/seed.sql), at http://localhost:8000. The only
prerequisite is Docker — no Azure subscription, no Entra sign-in, no config.
The backend gains DB_AUTH_MODE:
entra (default) the production posture, unchanged — Entra tokens end to
end, managed identity for reflection, per-user OBO tokens
for data.
local one SQL login (DB_USERNAME/DB_PASSWORD) for reflection AND
data — development/demo only. The per-user-grants model
deliberately does not apply; /me reports the SQL login so
writes are attributable. Refused outright on App Service
(WEBSITE_SITE_NAME guard in config.py) so a misconfigured
deployment can never silently fall back to a shared login.
Supporting pieces:
- db-init one-shot service creates + seeds the database once; re-ups leave
your data alone ("docker compose down -v" resets the demo dataset).
- .gitattributes pins *.sh/*.sql to LF so a Windows clone (autocrlf) can't
break the init script inside the Linux container.
- Vite proxy target is now AUTOCRUD_PROXY_TARGET (default unchanged :8001),
so the local-mode dev loop points straight at the API — two processes, no
auth proxy.
- .env.example documents both modes; README gains a "Try it now" quickstart
and a two-path Running locally section; CLAUDE.md records the commands.
Verified end to end against the running stack: reflection (13 tables), CRUD
round-trips, rowversion If-Match (fresh token updates, stale token 409s),
audit triggers stamping the SQL login, /me identity, seed-once on re-up,
clean down -v reset. Unit tests cover the config gate (credentials required,
App Service refusal), the tokenless local data path, entra-mode 401s
unchanged, and the /me behaviours.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ning Single-instance guard: the app keeps process-local state that is only correct on one instance — the reflected schema snapshot (a second instance would keep serving a stale schema after POST /admin/refresh), the per-token engine cache, the refresh throttle, and the health-probe cache. worker_count = 1 on the service plan makes that constraint enforced rather than remembered: Terraform now also reverts any manual portal scale-out on the next apply. infra/provision.ps1 collapses the post-apply runbook (terraform init/apply, az acr build, app restart, database seed + permission grants, adding yourself to the app-users group, writing the local .env) into one idempotent command: pwsh infra/provision.ps1 [-Environment dev|prod] [-ReseedDatabase] [-SkipTerraform] The demo seed is skipped when the database already has data (seed.sql is destructive by design), permissions.sql gets the environment's group name substituted automatically, and preflight checks catch the usual traps (not signed in, missing tfvars, legacy ODBC sqlcmd without Entra auth). The infra README now leads with the script and keeps the step-by-step as reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes the biggest friction in this project: getting it running. Three deliverables:
1. Zero-Azure local mode
That's the complete application — the production image (SPA + API, one origin) against a local SQL Server 2025 seeded from
database/seed.sql(the worked-example schema: computed columns, temporal history, composite keys, rowversion, cross-schema FKs). Docker is the only prerequisite; no Azure subscription, no Entra sign-in, no configuration.How it works:
DB_AUTH_MODEon the backend:entra(default — production posture, completely unchanged) orlocal(one SQL login for reflection and data). Local mode deliberately does not exercise the per-user-grants model;/mereportssa (local)so writes are attributable in the UI. Refused outright on App Service (WEBSITE_SITE_NAMEguard in config.py) so a misconfigured deployment can never silently fall back to a shared SQL login.db-initservice creates + seeds the database once; re-ups leave your data alone (docker compose down -vresets)..gitattributespins*.sh/*.sqlto LF so a Windows clone can't break the init script inside the Linux container.docker compose up db db-init, run the backend natively with the local-mode env (.env.examplehas the block), andAUTOCRUD_PROXY_TARGET=http://localhost:8000 npm run dev— two processes, no auth proxy. The vite proxy target is now configurable (default unchanged).2. Single-instance guard in Terraform
worker_count = 1on the App Service plan, with the reasoning in-line: the schema snapshot, per-token engine cache, refresh throttle, and health cache are process-local, so a scaled-out deployment would serve a stale schema after/admin/refresh. Terraform now also reverts any manual portal scale-out on the next apply — the constraint is enforced, not remembered.3. One-script hosted provisioning
pwsh infra/provision.ps1 [-Environment dev|prod]wraps the entire post-tfvars runbook: terraform init/apply →az acr build→ app restart → seed (skipped when data exists;-ReseedDatabaseto reset) →permissions.sqlwith the environment's group name substituted automatically → add yourself to the app-users group → write the local.env→ print the app URL. Preflight catches the usual traps (not signed in, missing tfvars, legacy ODBC sqlcmd without Entra auth). The infra README now leads with the script; the step-by-step remains as reference.Testing
/mebehaviours). ruff, pyright, tsc, oxlint, terraform fmt all clean.sa (local)identity badge, zero console errors. LF line endings verified to survive a Windows clone.down -vreset.Server=localhost,14330), confirming the documented two-process workflow.AUTOCRUD_PORT,AUTOCRUD_SQL_PORT) exercised throughout (this machine's :8000 was occupied — which is exactly what they're for).Not covered:
infra/provision.ps1has not been run against a real subscription in this session (it reorchestrates the commands the infra README already documents, and its SQL/az steps mirror what was previously copy-pasted); suggest exercising it on the next dev re-provision.🤖 Generated with Claude Code