Repository navigation
Conversation
Add the user_identities table and reshape the four user association tables so each row can reference a v1 artifact, a v2 record, or both, with a per-user client_ref for idempotent imports. Simulation and report associations lose their cross-service foreign keys to v2 simulations and reports. One autogenerated v2 revision (fbb7488ebe8a) covers both parts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port the Simulation Entrypoint JWTDecoder (PyJWT PyJWKClient, RS256, iss/aud) into libs/user_auth.py with issuer normalization and client principal classification, plus an optional Auth0 Management API client for account deletion. Validate PUBLIC_API_AUTH_* and AUTH0_MANAGEMENT_* settings in the readiness gate, map authentication failures to the v2 error envelope with WWW-Authenticate, and expose that header via CORS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve (and on first use provision) the caller's v2 user from the bearer token through user_identities, with savepoint race recovery on UNIQUE(issuer, subject). Remove user_id from association requests and queries, scope every read and write to the owner (404 otherwise), and accept legacy v1 references checked through a batched, read-only v1 connector (422 when missing). Repointing re-resolves or clears the v2 reference, and rows owned by the Stage 10 mirror return 409. Add /v2/user-simulations, /v2/user-reports (with the v3 report fields), and /v2/me (GET, PATCH, DELETE, export, idempotent imports). The Stage 10 projection now records legacy_policy_id. require_user is a fail-closed NativeRouteDependencies field attached at router level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add user-simulations, user-reports, and me path segments to the migration registry; give ContractRequest a required-headers field; record authenticated variants of the user-policy and user-household contracts and new simulation, report, and account contracts; guard that user-owned routes always require Authorization; and regenerate the contract artifacts. Add disposable-Postgres tests for concurrent first sign-in, concurrent idempotent imports, MATCH SIMPLE keys, and account deletion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report the four v2 user-association row counts through SQLAlchemy in a read-only, rolled-back transaction, and stop when the Stage 12A revision is pending while user_simulation_associations or user_report_associations hold rows. The seed workflow runs it before the schema upgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…utes Pass PUBLIC_API_AUTH_* and the optional AUTH0_MANAGEMENT_* settings through the staging and production deploy jobs and the Cloud Run candidate script, validate them before deploying, and keep them out of the release test environment. Rewrite the live staging probe to sign in a dedicated staging Auth0 test user and exercise /v2/me, all four association resources, imports, export, and account deletion without direct database access. Both candidate smoke suites now assert that anonymous user-route requests return 401. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the Stage 12A migration guide (routes, provisioning, schema revision, settings, GitHub variables and secrets, Auth0 prerequisites, rollout, verification), cross-reference it from the Stage 10 and 11 guides, add Stage 12A test commands to the testing skill, record a changelog fragment, and add a doc-only, preview-first Cloud Armor proposal for signed-in user writes and imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt test Fold the new auth unsets into the existing release-test command line so the workflow keeps its three-line inline limit, and move the Stage 11 household reassignment integration test to the signed-in contract, repointing through a mapped legacy household. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
Draft
This branch was successfully deployed
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.
Fixes #3892
Summary
Stage 12A of the unified API migration, Public API side. It adds verified user identity and server-side user associations anchored to v1 artifacts, so app v3 can offer social sign-on and save a user's work to their account.
libs/user_auth.py), with a router-levelrequire_userthat fails closed: 401 without a token, 403 for machine clients, 503 when auth is unconfigured.user_identitiestable, which maps an Auth0(issuer, subject)tousers.id./v2/me:GET/PATCH,GET /v2/me/export(UK GDPR),DELETE /v2/me, and idempotentPOST /v2/me/importsfor the user's existing localStorage items (≤500 items, 1 MiB)./v2/user-policiesand/v2/user-householdsare locked down. The owner comes from the token,user_idis removed from bodies and queries, and another user's row returns 404./v2/user-simulationsand/v2/user-reports.legacy_*_id), each checked to exist in Cloud SQL through the v1 SQLAlchemy models. A missing one returns 422.fbb7488ebe8a(parent91af4fc8d8c1):user_identities;client_refwithUNIQUE(user_id, client_ref);user_simulation_associationsanduser_report_associationsare reshaped, dropping their FKs tosimulations/reports;policyengine_api.data.v2.user_association_migration_qualification. It is wired intoseed-v2-database.ymlbefore the upgrade and fails if the dormant tables hold rows.docs/migration/armor/proposed/. An operator applies them.docs/migration/stage-12a-user-login.md.PUBLIC_API_AUTH_REQUIRED, set to0or1. Deploy validation fails if it is unset.PUBLIC_API_AUTH_ISSUERandPUBLIC_API_AUTH_AUDIENCE, required whenPUBLIC_API_AUTH_REQUIRED=1.stagingenvironment. Without these the test fails and staging deploys stay blocked.STAGING_AUTH0_TEST_DOMAIN,STAGING_AUTH0_TEST_AUDIENCE, and optionallySTAGING_AUTH0_TEST_REALM.STAGING_AUTH0_TEST_CLIENT_ID,STAGING_AUTH0_TEST_CLIENT_SECRET,STAGING_AUTH0_TEST_USERNAME,STAGING_AUTH0_TEST_PASSWORD.AUTH0_MANAGEMENT_DOMAIN/AUTH0_MANAGEMENT_CLIENT_ID(variables) andAUTH0_MANAGEMENT_CLIENT_SECRET_RESOURCE(secret) letDELETE /v2/mealso delete the Auth0 user.user_simulation_associationsanduser_report_associationsare empty. The seed workflow also enforces this before upgrading.Behaviour changes
/v2/user-policiesand/v2/user-householdspreviously accepted a caller-supplieduser_idwith no ownership check. Nothing in production calls them; app-v2 never did.PATCH /v2/user-householdsno longer acceptshousehold_id. Repointing goes throughlegacy_household_id.legacy_user_policy_mappingsentry) return 409 on PATCH and DELETE.DELETE /v2/mereturns 502 if the data was deleted but the Auth0 deletion then failed. A retry is safe.MeItem.emailstays null unless an Auth0 Action addsemail/email_verifiedto access tokens.Testing
uv run pytest tests/unit tests/contract: 2512 passed.test_v2_user_identity_persistence.py: 8 concurrent first sign-ins create one user, imports are idempotent under concurrency, account deletion cascades, and qualification is read-only.alembic -c alembic-v2.ini checkis clean; a fresh upgrade, then downgrade -1 and re-upgrade, all succeed.ruff format --check,ruff checkandmypyare clean.openspec amendments (local-only specs, not in this PR)
v2-user-policy-associations:user_idis removed;legacy_policy_id, a nullablepolicy_idwith a CHECK,baseline,provenance,client_ref;legacy_policy_id.v2-user-household-associations: legacy references, owner scoping, andhousehold_idis no longer patchable.user_identitiesprovisioning;/v2/mewith export, import and deletion order;Authorizationon user-owned routes.Related
🤖 Generated with Claude Code