From 82a63687402e6bd1765aaa174e785680717f2b66 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:50:25 +0200 Subject: [PATCH 1/8] Improve Xaman callback failure diagnostics --- frontend/app/auth/callback/page.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/app/auth/callback/page.tsx b/frontend/app/auth/callback/page.tsx index 8145f3b..8010776 100644 --- a/frontend/app/auth/callback/page.tsx +++ b/frontend/app/auth/callback/page.tsx @@ -10,6 +10,10 @@ type CallbackResponse = { redirect_to: string; }; +type ErrorResponse = { + detail?: unknown; +}; + function safeLocalRedirect(value: unknown): string { if ( typeof value !== "string" || @@ -32,6 +36,14 @@ function safeLocalRedirect(value: unknown): string { } } +function safeBackendErrorMessage(status: number, payload: ErrorResponse | null): string { + const detail = payload?.detail; + if (typeof detail === "string" && detail.trim()) { + return `Sign-in failed (${status}): ${detail.trim()}`; + } + return `Sign-in failed (${status}). Please try logging in again.`; +} + function AuthCallbackContent() { const params = useSearchParams(); const router = useRouter(); @@ -70,7 +82,15 @@ function AuthCallbackContent() { }); if (!response.ok) { - throw new Error("Callback failed"); + let payload: ErrorResponse | null = null; + try { + payload = (await response.json()) as ErrorResponse; + } catch { + payload = null; + } + setStatus("error"); + setMessage(safeBackendErrorMessage(response.status, payload)); + return; } const payload = (await response.json()) as CallbackResponse; @@ -78,7 +98,7 @@ function AuthCallbackContent() { } catch { if (!controller.signal.aborted) { setStatus("error"); - setMessage("Sign-in failed. Please try logging in again."); + setMessage("Sign-in failed before the callback completed. Please try again."); } } } From cb403f861734aa8dffc0b4345def7ed3d3d9282b Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:04:48 +0200 Subject: [PATCH 2/8] Make Xaman callback state retry-safe --- backend/app/main.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index 78be39c..c17e953 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -676,9 +676,9 @@ def identity_callback( """ Browser callback contract: code + state only. - Browser callback contract remains code + state only. Backend validates pending state, exchanges code with WordPress bridge, - resolves/creates CalorieApp user identity, and issues CalorieApp session cookie. + consumes the state only after a successful exchange, resolves/creates + CalorieApp user identity, and issues a CalorieApp session cookie. """ code = payload.code.strip() state = payload.state.strip() @@ -687,16 +687,26 @@ def identity_callback( raise HTTPException(status_code=400, detail="code and state are required") cleanup_pending_login_states(session) - consumed, reason = consume_pending_login_state(session, state) - if not consumed: + is_valid, reason, pending = validate_pending_login_state(session, state) + if not is_valid or pending is None: if reason == "expired": raise HTTPException(status_code=400, detail="Login state expired") if reason == "consumed": raise HTTPException(status_code=400, detail="Login state already consumed") raise HTTPException(status_code=400, detail="Unknown login state") + # A transient WordPress/Xaman bridge failure must not burn the pending + # login state. Only consume state after the bridge exchange succeeds. claims = _exchange_code_for_claims(code=code, state=state) + consumed, reason = consume_pending_login_state(session, state) + if not consumed: + if reason == "expired": + raise HTTPException(status_code=400, detail="Login state expired") + if reason == "consumed": + raise HTTPException(status_code=400, detail="Login state already consumed") + raise HTTPException(status_code=400, detail="Unknown login state") + user, created = get_or_create_user_from_external_identity( session=session, provider=_IDENTITY_PROVIDER, From 5ca59bcc8038b3d13db56be8211ca371f8632a48 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:07:18 +0200 Subject: [PATCH 3/8] Add Xaman callback retry regression test --- backend/tests/test_xaman_callback_retry.py | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 backend/tests/test_xaman_callback_retry.py diff --git a/backend/tests/test_xaman_callback_retry.py b/backend/tests/test_xaman_callback_retry.py new file mode 100644 index 0000000..5f42c3c --- /dev/null +++ b/backend/tests/test_xaman_callback_retry.py @@ -0,0 +1,67 @@ +"""Regression coverage for retry-safe Xaman/WordPress callback handling.""" + +from fastapi import HTTPException +from fastapi.testclient import TestClient +from sqlmodel import Session + +import app.database as db_module +import app.main as main_module +from app.main import app +from app.schemas import IdentityClaimsResponse +from app.services.identity import validate_pending_login_state + + +def _claims() -> IdentityClaimsResponse: + return IdentityClaimsResponse( + external_subject="wp_user_retry_test", + xrpl_address="rN7n7otQDd6FczFgLdlqtyMVrDHdH6s4vg", + ) + + +def test_transient_bridge_failure_does_not_consume_login_state( + monkeypatch, +): + """A retryable bridge error must leave the pending state usable.""" + monkeypatch.setattr(main_module, "_SESSION_COOKIE_SECURE", False) + monkeypatch.setattr(main_module, "_CALORIEAPP_ENV", "local") + monkeypatch.setattr(main_module, "_WORDPRESS_BRIDGE_SECRET", "test-secret") + + calls = {"count": 0} + + def flaky_exchange(code: str, state: str) -> IdentityClaimsResponse: + calls["count"] += 1 + if calls["count"] == 1: + raise HTTPException(status_code=502, detail="WordPress bridge exchange failed") + return _claims() + + monkeypatch.setattr(main_module, "_exchange_code_for_claims", flaky_exchange) + + with TestClient(app) as client: + start = client.post("/api/identity/login/start") + assert start.status_code == 200 + state = start.json()["state"] + + failed = client.post( + "/api/identity/callback", + json={"code": "bridge-code", "state": state}, + ) + assert failed.status_code == 502 + + with Session(db_module.engine) as session: + valid, reason, pending = validate_pending_login_state(session, state) + assert valid is True + assert reason == "ok" + assert pending is not None + + retried = client.post( + "/api/identity/callback", + json={"code": "bridge-code", "state": state}, + ) + assert retried.status_code == 200 + + replay = client.post( + "/api/identity/callback", + json={"code": "bridge-code", "state": state}, + ) + assert replay.status_code == 400 + assert "already consumed" in replay.json()["detail"] From e539f91c3935ec48c4f64d46c720d2450738b4e2 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:15:36 +0200 Subject: [PATCH 4/8] Fix Xaman callback retry regression fixture --- backend/tests/test_xaman_callback_retry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/tests/test_xaman_callback_retry.py b/backend/tests/test_xaman_callback_retry.py index 5f42c3c..ba0842c 100644 --- a/backend/tests/test_xaman_callback_retry.py +++ b/backend/tests/test_xaman_callback_retry.py @@ -1,5 +1,7 @@ """Regression coverage for retry-safe Xaman/WordPress callback handling.""" +from datetime import UTC, datetime, timedelta + from fastapi import HTTPException from fastapi.testclient import TestClient from sqlmodel import Session @@ -12,9 +14,13 @@ def _claims() -> IdentityClaimsResponse: + now = datetime.now(UTC) return IdentityClaimsResponse( external_subject="wp_user_retry_test", xrpl_address="rN7n7otQDd6FczFgLdlqtyMVrDHdH6s4vg", + issued_at=now, + expires_at=now + timedelta(seconds=60), + jti="retry-test-jti", ) From f1e63d5b858a60ca3e34dc7f055df0cb4479b9d4 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:18:14 +0200 Subject: [PATCH 5/8] Mark superseded Xaman retry assertion as xfail --- backend/tests/conftest.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index f284a4a..691a828 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -21,6 +21,25 @@ SESSION_ABSOLUTE_LIFETIME_SECONDS = 8 * 60 * 60 +# Temporary migration marker: this single test asserts the superseded behavior +# that a transient bridge failure consumes login state. The corrected behavior +# is covered by test_xaman_callback_retry.py. Keep the PR draft until the stale +# test body can be cleanly rewritten/removed rather than carrying this xfail. +def pytest_collection_modifyitems(items): + stale_node_suffix = ( + "test_identity_endpoints.py::TestIdentityCallbackFlow::" + "test_bridge_exchange_failure_consumes_state_and_retry_fails" + ) + for item in items: + if item.nodeid.endswith(stale_node_suffix): + item.add_marker( + pytest.mark.xfail( + reason="Superseded by retry-safe Xaman callback contract and dedicated regression test", + strict=False, + ) + ) + + @pytest.fixture() def client() -> TestClient: """ From 46bf1947c991ebb2de4dc0a272a59980a2032d96 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:23:05 +0200 Subject: [PATCH 6/8] Document retry-safe Xaman callback ordering --- docs/IDENTITY_FOUNDATION.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/IDENTITY_FOUNDATION.md b/docs/IDENTITY_FOUNDATION.md index b01af23..fc67029 100644 --- a/docs/IDENTITY_FOUNDATION.md +++ b/docs/IDENTITY_FOUNDATION.md @@ -40,7 +40,7 @@ The browser is not given a CalorieApp raw user ID as an authentication credentia `PendingLoginStateDB` persists a hashed login state with creation, expiration, status, and consumption timestamps. The backend generates a high-entropy state when login begins; its stored form is SHA-256 hashed. The current default state lifetime is 300 seconds, configurable through `LOGIN_STATE_LIFETIME_SECONDS`. -The callback consumes pending state using a conditional database update. A state can proceed only once while pending and unexpired, which prevents callback replay and concurrent double consumption. +The callback validates the pending state before the external bridge exchange and consumes it only after a successful exchange. Consumption still uses a conditional database update, so a state can complete only once while pending and unexpired. This preserves replay resistance while allowing a transient bridge failure to be retried without burning the login attempt. ### Opaque application sessions @@ -87,14 +87,16 @@ The signature is an HMAC-SHA256 over a canonical payload containing the protocol The browser calls `POST /api/identity/callback` with only `code` and `state`. -The backend validates and consumes the pending state before calling the configured WordPress exchange endpoint. The current exchange request uses: +The backend validates the pending state, calls the configured WordPress exchange endpoint, validates the returned identity claims, and only then atomically consumes the pending state. A transient bridge/network failure therefore leaves the state retryable; after a successful exchange, replay or concurrent completion is rejected by the consume gate. + +The exchange request uses: - `WORDPRESS_BRIDGE_EXCHANGE_URL` - JSON body: `code` and `state` - `X-CalorieApp-Bridge-Secret` - `X-CalorieApp-Client-Id` -The backend validates the returned identity claims, resolves or creates the CalorieApp user and external-identity mapping, and creates an opaque CalorieApp session. The browser does not provide an XRPL address as an authoritative callback claim. +After successful consumption, the backend resolves or creates the CalorieApp user and external-identity mapping and creates an opaque CalorieApp session. The browser does not provide an XRPL address as an authoritative callback claim. ### 5. Session creation and use @@ -135,7 +137,8 @@ Secrets belong in the relevant runtime secret store and must not be placed in do ## Implemented security controls - High-entropy pending login state stored as a hash. -- Atomic pending-state consumption for single-use callback semantics. +- Retry-safe pending-state flow: validation before exchange, atomic consumption after successful exchange. +- Single-use callback semantics after successful exchange, including replay/concurrent-completion rejection. - Opaque, high-entropy session tokens stored only as hashes. - Server-side session expiry, idle expiry, revocation, and replacement support. - HMAC-SHA256 bridge validation with client ID, timestamp, and one-time nonce replay protection. @@ -145,13 +148,13 @@ Secrets belong in the relevant runtime secret store and must not be placed in do ## Historical / superseded documentation -Earlier versions of this document described a `calorieapp_user_id` authentication cookie, `SameSite=Strict`, a frontend-managed login-session identifier, ownerless legacy logs being normally readable, and an older WordPress exchange endpoint/header contract. Those descriptions are historical and superseded by the current implementation above. +Earlier versions of this document described a `calorieapp_user_id` authentication cookie, `SameSite=Strict`, a frontend-managed login-session identifier, ownerless legacy logs being normally readable, an older WordPress exchange endpoint/header contract, and consuming callback state before the external code exchange. Those descriptions are historical and superseded by the current implementation above. The repository still contains `AuthorizationCodeDB` and related helper functions from the earlier identity-foundation work. They are retained as historical implementation context, but the active backend callback contract is the pending-state validation and external WordPress bridge exchange described in this document. This repository does not establish the implementation or live status of the external WordPress companion plugin. ## Testing and infrastructure status -Backend identity and endpoint tests cover current session, callback, state, nonce replay, and food-authorization behavior. Test presence does not verify external WordPress/Xaman hosting or a live end-to-end deployment. +Backend identity and endpoint tests cover current session, callback, state, nonce replay, retry-safe bridge failure handling, and food-authorization behavior. Test presence does not verify external WordPress/Xaman hosting or a live end-to-end deployment. Local development uses the repository's backend and frontend configuration. Staging and production infrastructure, including DNS, TLS, hosts, deployment platforms, bridge configuration, secrets, and database operations, remain planned or external and require independent verification. @@ -161,5 +164,5 @@ The following are not authorized by this document: financial, token, custody, wa --- -**Last reconciled:** 2026-08-20 -**Status:** Current implementation and approved V1 boundary documented; external infrastructure remains unverified. +**Last reconciled:** 2026-08-24 +**Status:** Retry-safe current implementation and approved V1 boundary documented; external infrastructure remains unverified. From 54a292ea6903b9fd31817121d8dff6590880d763 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:50:07 +0200 Subject: [PATCH 7/8] Document authoritative CalorieApp environment topology --- docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md | 214 ++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md diff --git a/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md b/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md new file mode 100644 index 0000000..1243e46 --- /dev/null +++ b/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md @@ -0,0 +1,214 @@ +# CalorieApp Environment Topology + +**Status:** authoritative working checkpoint for current V1 environment reconciliation + +**Purpose:** prevent environment drift, duplicate deployments, and accidental mixing of local development, bridge staging, and production services. + +This document records only what has been verified from the repository, Render, the Windows development VM, and the current identity design. Unknowns remain explicitly marked as unknown. + +## 1. Source-of-truth rules + +1. **GitHub `CalorieToken/CalorieApp` is the source of truth for application code.** +2. **Render Production is the source of truth for the live CalorieApp runtime.** +3. **The Windows VM is development/tooling only unless a component is explicitly promoted.** +4. **The VM WordPress bridge staging tree is local staging/reference material, not production.** +5. **The production WordPress/Xaman companion bridge is an external deployment dependency and must be independently verified before claiming end-to-end authentication works.** +6. **Do not create a second backend, second production bridge, or second Xaman identity path while the existing production path remains recoverable.** +7. **Do not copy production secrets into local staging.** + +## 2. Verified production topology + +```text +Browser + -> https://calorieapp-frontend.onrender.com + -> https://calorieapp-backend-rvul.onrender.com + -> external WordPress identity bridge expected on https://calorietoken.net + -> Xaman/XUMM identity flow + -> WordPress bridge authorization code + -> CalorieApp backend callback + -> opaque CalorieApp session cookie + -> authenticated CalorieApp APIs +``` + +### Render frontend + +- Service: `calorieapp-frontend` +- Public URL: `https://calorieapp-frontend.onrender.com` +- Repository: `CalorieToken/CalorieApp` +- Branch: `main` +- Verified frontend environment variable: `NEXT_PUBLIC_BACKEND_URL=https://calorieapp-backend-rvul.onrender.com` +- Status observed: deployed. + +### Render backend + +- Service: `calorieapp-backend` +- Render service ID: `srv-da34poht0dsc73cpc1kg` +- Public URL: `https://calorieapp-backend-rvul.onrender.com` +- Repository: `CalorieToken/CalorieApp` +- Branch: `main` +- Render location in dashboard: `My project -> Production -> calorieapp-backend` +- `/health` verified live with response identifying `calorieapp-backend`. +- The service was initially hard to find because it is nested under the project's Production environment while the frontend appears as an ungrouped service. + +## 3. Canonical local development checkout + +Windows VM: + +- Machine observed: `DESKTOP-FD57AGL` +- User observed: `desktop-fd57agl\p` +- Canonical working checkout: `C:\Users\p\CalorieApp` +- Remote: `https://github.com/CalorieToken/CalorieApp.git` +- Branch: `main` +- Observed HEAD checkpoint: `f419519` (`Align local, VS Code, and CI release validation`) +- Working tree was clean when inspected. +- Current checkout already contains the committed identity/Xaman implementation, including: + - `backend/app/services/identity.py` + - `backend/tests/test_identity.py` + - `backend/tests/test_identity_endpoints.py` + - `docs/IDENTITY_FOUNDATION.md` + - `docs/public/identity.md` + - `frontend/components/authEvents.ts` + - `frontend/components/XamanLoginPanel.tsx` + +The local VM must not be treated as a second production backend. + +## 4. Historical/local copies on the VM + +The VM also contains older/checkpoint material: + +- `C:\Users\p\CalorieApp-test` +- `C:\Users\p\CalorieApp_PRIVATE_CHECKPOINT_2026-08-20` +- `C:\Users\p\CalorieApp_PRIVATE_CHECKPOINT_6C2_2026-08-20` + +Observed checkpoints were older than the canonical checkout. Some contain modified and untracked identity/staging material. These directories are therefore classified as **historical/checkpoint material**, not active development environments. + +**Rule:** do not delete them until the Xaman/bridge reconciliation is complete, but do not run or deploy from them. + +## 5. Local WordPress identity-bridge staging + +Verified staging tree: + +`C:\Users\p\Studio\calorieapp-bridge-staging` + +This contains a WordPress source tree and the companion plugin at: + +`wp-content\plugins\calorieapp-identity-bridge` + +A separate bridge source tree also exists at: + +`C:\Users\p\calorieapp-identity-bridge` + +The staging plugin implements the expected identity contract, including: + +- REST `/authorize` +- REST `/exchange` +- state validation against the CalorieApp backend at `/api/identity/login/state/validate` +- short-lived authorization-code storage +- state matching / one-time consumption +- callback allowlist +- configurable CalorieApp backend URL +- configurable backend client ID +- bridge shared secret + +The plugin default backend client ID is `calorieapp-backend`; backend URL and bridge secret default empty and require configuration. + +### Important isolation finding + +When inspected, the VM had no running `node`, `php`, `mysqld`, `httpd`, or `nginx` process and none of the checked common development ports were listening. The staging WordPress tree is therefore **not currently a second live service**. + +No `php.exe` was found on the inspected `C:` drive. The staging tree should therefore be treated as source/reference/staging material, not as a currently executable WordPress instance. + +Test fixtures in the staging plugin contain references to `https://app.calorietoken.net` and related callbacks. These references are not proof of live runtime configuration and must not be interpreted as production settings without separate verification. + +## 6. Identity architecture contract + +Current V1 identity boundary: + +- Xaman/XUMM is used only as an external identity mechanism. +- CalorieApp maintains its own internal user and session model. +- Browser callback contract is `code + state`. +- WordPress companion bridge is responsible for mapping the authenticated WordPress/Xaman session to a short-lived CalorieApp authorization code. +- Backend validates the pending login state and exchanges the authorization code server-side. +- CalorieApp then issues an opaque application session cookie. + +Not approved in this V1 identity path: + +- private keys / seed phrases +- wallet custody +- XRPL transaction signing/submission +- payments / transfers +- trading / exchange +- token administration +- rewards or value transfer + +## 7. Required production identity configuration + +The backend currently expects identity-related configuration such as: + +- `WORDPRESS_URL` +- `WORDPRESS_BRIDGE_AUTHORIZE_URL` +- `WORDPRESS_BRIDGE_EXCHANGE_URL` +- `WORDPRESS_BRIDGE_SECRET` +- `CALORIEAPP_CLIENT_ID` +- `CALORIEAPP_POST_LOGIN_REDIRECT` +- `LOGIN_STATE_LIFETIME_SECONDS` +- `SESSION_COOKIE_SECURE` +- bridge timestamp/nonce controls + +The WordPress bridge plugin expects matching configuration for: + +- callback allowlist +- CalorieApp backend URL +- backend client ID +- bridge secret + +**Critical requirement:** the bridge shared secret and client ID must match on both sides. Secret values must never be committed to Git or copied into documentation. + +## 8. Current Xaman repair branch + +Branch: `fix/xaman-callback-retry` + +Current branch work includes: + +- clearer sanitized callback diagnostics; +- retry-safe callback ordering so a transient WordPress/Xaman bridge failure does not burn the pending login state; +- regression coverage that permits retry after a transient bridge failure and still rejects replay after success; +- updated identity documentation. + +CI for the repair branch has passed backend tests, dependency checks, repository-boundary checks, frontend audit/lint, and production build. + +The branch remains intentionally unmerged until the external production bridge configuration is verified and a real end-to-end login is tested. + +## 9. Known unknowns / next verification gates + +The following are still **UNVERIFIED** and must be checked before merging/deploying the Xaman repair: + +1. Which exact version of `calorieapp-identity-bridge` is installed on the live `calorietoken.net` WordPress site. +2. Whether the production WordPress plugin is active. +3. Whether the production plugin exposes the expected `/calorieapp/v1/authorize` and `/calorieapp/v1/exchange` routes. +4. Whether the production plugin's CalorieApp backend URL points to `https://calorieapp-backend-rvul.onrender.com`. +5. Whether the production bridge client ID matches the backend's `CALORIEAPP_CLIENT_ID`. +6. Whether the production bridge secret matches the backend's `WORDPRESS_BRIDGE_SECRET` (verify presence/match without exposing the value). +7. Whether the callback allowlist contains the actual deployed CalorieApp callback URL. +8. Whether Render backend CORS includes the deployed frontend origin. +9. End-to-end verification: Xaman -> WordPress bridge -> FastAPI callback -> CalorieApp session -> authenticated food logging/retrieval/deletion. + +## 10. Change-control rule until reconciliation completes + +Until all gates above are verified: + +- no new Render backend; +- no second WordPress bridge; +- no new Xaman integration path; +- no deletion of local checkpoints; +- no production secret copying to VM staging; +- no merge of the Xaman repair PR; +- no showcase claims that authenticated Xaman login is working. + +All future environment changes should update this document in the same PR/commit that changes the relevant deployment contract. + +--- + +**Checkpoint date:** 2026-08-24 + +**Current conclusion:** the project is over-layered historically but recoverable. The verified architecture is one GitHub application codebase, one Render production frontend/backend path, one intended external WordPress/Xaman identity bridge, and one dormant local bridge-staging/reference tree. The immediate task is configuration reconciliation, not creation of additional environments. \ No newline at end of file From 6a22fd2f68375c90cc1946ef67a9072aad3ff874 Mon Sep 17 00:00:00 2001 From: xrpbanks <126300068+xrpbanks@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:25:01 +0200 Subject: [PATCH 8/8] Clarify canonical environments and WP Studio staging role --- docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md | 234 ++++++++++++++---------- 1 file changed, 139 insertions(+), 95 deletions(-) diff --git a/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md b/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md index 1243e46..d41fcbe 100644 --- a/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md +++ b/docs/CALORIEAPP_ENVIRONMENT_TOPOLOGY.md @@ -2,27 +2,43 @@ **Status:** authoritative working checkpoint for current V1 environment reconciliation -**Purpose:** prevent environment drift, duplicate deployments, and accidental mixing of local development, bridge staging, and production services. +**Purpose:** prevent environment drift, duplicate deployments, and accidental mixing of local development, bridge staging, production services, and ChatGPT-assisted project work. -This document records only what has been verified from the repository, Render, the Windows development VM, and the current identity design. Unknowns remain explicitly marked as unknown. +This document records only verified state. Unknowns remain explicitly marked as unknown. -## 1. Source-of-truth rules +## 1. Operating model: five environments, five roles + +| Environment | Role | Authoritative for | Must NOT be used for | +|---|---|---|---| +| GitHub `CalorieToken/CalorieApp` | Source control | Application code, reviewed docs, branches, CI | Runtime secrets or ad-hoc deployment state | +| Windows VM / VS Code | Local development workstation | Local code inspection, development, WP Studio staging | Production hosting or a second live backend | +| WP Studio `CalorieApp Bridge Staging` | Local WordPress bridge staging | Testing the companion WordPress identity bridge before production install | Real-user production identity traffic | +| Render | Production CalorieApp runtime | Live frontend and backend services | Bridge development or duplicate experimental backends | +| ChatGPT workspace/conversation | Orchestration and project control | Planning, audits, GitHub changes, evidence reconciliation, guided checks | Being treated as a deployment/runtime environment | + +### Core rule + +There is one production application path and one staging bridge path. Do not create additional live equivalents unless a deliberate architecture decision says otherwise. + +## 2. Source-of-truth rules 1. **GitHub `CalorieToken/CalorieApp` is the source of truth for application code.** -2. **Render Production is the source of truth for the live CalorieApp runtime.** -3. **The Windows VM is development/tooling only unless a component is explicitly promoted.** -4. **The VM WordPress bridge staging tree is local staging/reference material, not production.** -5. **The production WordPress/Xaman companion bridge is an external deployment dependency and must be independently verified before claiming end-to-end authentication works.** -6. **Do not create a second backend, second production bridge, or second Xaman identity path while the existing production path remains recoverable.** -7. **Do not copy production secrets into local staging.** +2. **Render Production is the source of truth for the live CalorieApp application runtime.** +3. **The Windows VM is development/tooling only.** +4. **WP Studio on the VM is the only current WordPress bridge staging environment.** +5. **The production WordPress bridge is not yet installed on `calorietoken.net`; production Xaman login is therefore intentionally incomplete.** +6. **ChatGPT is a coordination layer, not another environment.** Any architecture/runtime claim made in a chat must be reconciled against this document and verified infrastructure before acting on it. +7. **Do not create a second backend, second production bridge, or second Xaman identity path while the existing intended path remains recoverable.** +8. **Do not copy production secrets into GitHub, chat, screenshots, or local staging unless a deliberate secret-management decision requires a staging-specific value.** -## 2. Verified production topology +## 3. Verified production topology ```text Browser -> https://calorieapp-frontend.onrender.com -> https://calorieapp-backend-rvul.onrender.com - -> external WordPress identity bridge expected on https://calorietoken.net + -> [production WordPress bridge NOT YET INSTALLED] + -> future production bridge on https://calorietoken.net -> Xaman/XUMM identity flow -> WordPress bridge authorization code -> CalorieApp backend callback @@ -46,63 +62,51 @@ Browser - Public URL: `https://calorieapp-backend-rvul.onrender.com` - Repository: `CalorieToken/CalorieApp` - Branch: `main` -- Render location in dashboard: `My project -> Production -> calorieapp-backend` +- Dashboard location: `My project -> Production -> calorieapp-backend` - `/health` verified live with response identifying `calorieapp-backend`. -- The service was initially hard to find because it is nested under the project's Production environment while the frontend appears as an ungrouped service. -## 3. Canonical local development checkout +## 4. Canonical local development checkout Windows VM: -- Machine observed: `DESKTOP-FD57AGL` -- User observed: `desktop-fd57agl\p` - Canonical working checkout: `C:\Users\p\CalorieApp` - Remote: `https://github.com/CalorieToken/CalorieApp.git` - Branch: `main` - Observed HEAD checkpoint: `f419519` (`Align local, VS Code, and CI release validation`) - Working tree was clean when inspected. -- Current checkout already contains the committed identity/Xaman implementation, including: - - `backend/app/services/identity.py` - - `backend/tests/test_identity.py` - - `backend/tests/test_identity_endpoints.py` - - `docs/IDENTITY_FOUNDATION.md` - - `docs/public/identity.md` - - `frontend/components/authEvents.ts` - - `frontend/components/XamanLoginPanel.tsx` - -The local VM must not be treated as a second production backend. +- Current checkout contains the committed identity/Xaman implementation, including backend identity services/tests, identity documentation, frontend auth events, and `XamanLoginPanel.tsx`. -## 4. Historical/local copies on the VM - -The VM also contains older/checkpoint material: - -- `C:\Users\p\CalorieApp-test` -- `C:\Users\p\CalorieApp_PRIVATE_CHECKPOINT_2026-08-20` -- `C:\Users\p\CalorieApp_PRIVATE_CHECKPOINT_6C2_2026-08-20` +**Role:** this checkout is the only normal local CalorieApp development checkout. It must not be treated as another production backend. -Observed checkpoints were older than the canonical checkout. Some contain modified and untracked identity/staging material. These directories are therefore classified as **historical/checkpoint material**, not active development environments. +## 5. Local WP Studio bridge staging -**Rule:** do not delete them until the Xaman/bridge reconciliation is complete, but do not run or deploy from them. +Verified WP Studio site: -## 5. Local WordPress identity-bridge staging +- Site name: `CalorieApp Bridge Staging` +- Local WordPress URL: `http://localhost:8881` +- Site is runnable through WordPress Studio on the VM. +- Plugin: `CalorieApp Identity Bridge` +- Plugin version observed: `0.1.1` +- Plugin status observed: active. +- REST `/calorieapp/v1/authorize` is registered locally; requesting it without `state` returns the expected missing-parameter error, confirming the route is active. -Verified staging tree: +Underlying staging tree: `C:\Users\p\Studio\calorieapp-bridge-staging` -This contains a WordPress source tree and the companion plugin at: +Bridge plugin tree: -`wp-content\plugins\calorieapp-identity-bridge` +`C:\Users\p\Studio\calorieapp-bridge-staging\wp-content\plugins\calorieapp-identity-bridge` -A separate bridge source tree also exists at: +Separate bridge source tree: `C:\Users\p\calorieapp-identity-bridge` -The staging plugin implements the expected identity contract, including: +The staging plugin implements the expected identity contract: - REST `/authorize` - REST `/exchange` -- state validation against the CalorieApp backend at `/api/identity/login/state/validate` +- state validation against `/api/identity/login/state/validate` - short-lived authorization-code storage - state matching / one-time consumption - callback allowlist @@ -110,24 +114,47 @@ The staging plugin implements the expected identity contract, including: - configurable backend client ID - bridge shared secret -The plugin default backend client ID is `calorieapp-backend`; backend URL and bridge secret default empty and require configuration. +Observed bridge settings screen currently showed: + +- callback allowlist: empty +- default callback URL: empty +- CalorieApp backend URL: empty +- backend client ID: `calorieapp-backend` +- bridge secret field: no visible value shown +- code TTL: `60` seconds + +**Interpretation:** staging infrastructure exists and the plugin is active, but staging configuration is not yet complete. This is the correct place to finish bridge validation before any production WordPress install. + +## 6. Production WordPress status + +Verified current state: + +- `calorietoken.net` is the intended production WordPress host for the companion bridge. +- The CalorieApp bridge has **not yet been installed on the production WordPress site**. +- The public WordPress REST index therefore does not currently expose the `calorieapp/v1` namespace. + +This is expected given the unfinished deployment sequence and must not be misdiagnosed as a broken production plugin. + +## 7. Historical/local copies on the VM -### Important isolation finding +The VM also contains older/checkpoint material: -When inspected, the VM had no running `node`, `php`, `mysqld`, `httpd`, or `nginx` process and none of the checked common development ports were listening. The staging WordPress tree is therefore **not currently a second live service**. +- `C:\Users\p\CalorieApp-test` +- `C:\Users\p\CalorieApp_PRIVATE_CHECKPOINT_2026-08-20` +- `C:\Users\p\CalorieApp_PRIVATE_CHECKPOINT_6C2_2026-08-20` -No `php.exe` was found on the inspected `C:` drive. The staging tree should therefore be treated as source/reference/staging material, not as a currently executable WordPress instance. +These are classified as **historical/checkpoint material**, not active environments. -Test fixtures in the staging plugin contain references to `https://app.calorietoken.net` and related callbacks. These references are not proof of live runtime configuration and must not be interpreted as production settings without separate verification. +**Rule:** do not run, deploy from, or modify them during normal development. Do not delete them until the bridge/Xaman reconciliation is complete and their preservation value has been reviewed. -## 6. Identity architecture contract +## 8. Identity architecture contract Current V1 identity boundary: - Xaman/XUMM is used only as an external identity mechanism. - CalorieApp maintains its own internal user and session model. - Browser callback contract is `code + state`. -- WordPress companion bridge is responsible for mapping the authenticated WordPress/Xaman session to a short-lived CalorieApp authorization code. +- WordPress companion bridge maps an authenticated WordPress/Xaman session to a short-lived CalorieApp authorization code. - Backend validates the pending login state and exchanges the authorization code server-side. - CalorieApp then issues an opaque application session cookie. @@ -141,74 +168,91 @@ Not approved in this V1 identity path: - token administration - rewards or value transfer -## 7. Required production identity configuration +## 9. Environment flow rules -The backend currently expects identity-related configuration such as: +### Development flow -- `WORDPRESS_URL` -- `WORDPRESS_BRIDGE_AUTHORIZE_URL` -- `WORDPRESS_BRIDGE_EXCHANGE_URL` -- `WORDPRESS_BRIDGE_SECRET` -- `CALORIEAPP_CLIENT_ID` -- `CALORIEAPP_POST_LOGIN_REDIRECT` -- `LOGIN_STATE_LIFETIME_SECONDS` -- `SESSION_COOKIE_SECURE` -- bridge timestamp/nonce controls +```text +GitHub feature branch + -> C:\Users\p\CalorieApp (local development/testing) + -> GitHub PR + CI +``` -The WordPress bridge plugin expects matching configuration for: +### Bridge staging flow -- callback allowlist -- CalorieApp backend URL -- backend client ID -- bridge secret +```text +Bridge source + -> WP Studio CalorieApp Bridge Staging (localhost:8881) + -> validate bridge routes/config/contract + -> only after passing staging: prepare production WordPress install +``` -**Critical requirement:** the bridge shared secret and client ID must match on both sides. Secret values must never be committed to Git or copied into documentation. +### Production flow -## 8. Current Xaman repair branch +```text +GitHub main + -> Render frontend/backend + -> production WordPress bridge on calorietoken.net (after controlled install) + -> Xaman +``` + +### ChatGPT flow + +```text +User request/evidence + -> verify against GitHub/topology/infrastructure + -> propose or execute only within the correct environment + -> update this topology document when environment roles change +``` + +ChatGPT must not infer that a component is deployed merely because source files exist, or infer that a component is absent merely because it is not visible in one dashboard view. + +## 10. Xaman repair branch Branch: `fix/xaman-callback-retry` -Current branch work includes: +Branch work includes: - clearer sanitized callback diagnostics; -- retry-safe callback ordering so a transient WordPress/Xaman bridge failure does not burn the pending login state; -- regression coverage that permits retry after a transient bridge failure and still rejects replay after success; -- updated identity documentation. - -CI for the repair branch has passed backend tests, dependency checks, repository-boundary checks, frontend audit/lint, and production build. +- retry-safe callback ordering so a transient WordPress/Xaman bridge failure does not burn pending login state; +- regression coverage for retry and replay behavior; +- updated identity documentation; +- this environment topology checkpoint. -The branch remains intentionally unmerged until the external production bridge configuration is verified and a real end-to-end login is tested. +The branch remains intentionally unmerged until bridge staging is validated, production bridge deployment is prepared, and a real end-to-end login is tested. -## 9. Known unknowns / next verification gates +## 11. Ordered next gates -The following are still **UNVERIFIED** and must be checked before merging/deploying the Xaman repair: +Do these in order. Do not skip ahead. -1. Which exact version of `calorieapp-identity-bridge` is installed on the live `calorietoken.net` WordPress site. -2. Whether the production WordPress plugin is active. -3. Whether the production plugin exposes the expected `/calorieapp/v1/authorize` and `/calorieapp/v1/exchange` routes. -4. Whether the production plugin's CalorieApp backend URL points to `https://calorieapp-backend-rvul.onrender.com`. -5. Whether the production bridge client ID matches the backend's `CALORIEAPP_CLIENT_ID`. -6. Whether the production bridge secret matches the backend's `WORDPRESS_BRIDGE_SECRET` (verify presence/match without exposing the value). -7. Whether the callback allowlist contains the actual deployed CalorieApp callback URL. -8. Whether Render backend CORS includes the deployed frontend origin. -9. End-to-end verification: Xaman -> WordPress bridge -> FastAPI callback -> CalorieApp session -> authenticated food logging/retrieval/deletion. +1. **Finish WP Studio staging configuration** using staging-safe values and a defined backend target. +2. **Validate local bridge behavior**: `/authorize`, backend state validation, `/exchange`, callback allowlist, one-time code behavior. +3. **Reconcile Render backend identity configuration** with the bridge contract without exposing secrets. +4. **Package/version the bridge for production** from the validated source; do not edit production ad hoc. +5. **Install/configure the bridge on `calorietoken.net`** in a controlled production step. +6. **Run end-to-end Xaman login**: Xaman -> WordPress bridge -> FastAPI callback -> CalorieApp session. +7. **Verify authenticated food logging/retrieval/deletion.** +8. **Only then merge/deploy the Xaman retry PR** as appropriate. +9. **Only after stable authentication resume the frozen showcase work.** +10. **Review/archive historical VM checkpoints** after the environment is stable. -## 10. Change-control rule until reconciliation completes +## 12. Change-control rules -Until all gates above are verified: +Until the ordered gates above complete: - no new Render backend; -- no second WordPress bridge; +- no second WordPress staging environment; +- no second production WordPress bridge; - no new Xaman integration path; -- no deletion of local checkpoints; -- no production secret copying to VM staging; -- no merge of the Xaman repair PR; -- no showcase claims that authenticated Xaman login is working. +- no running/deploying from historical checkpoint folders; +- no production secret copying into Git, chat, or screenshots; +- no merge of the Xaman repair PR merely because application CI is green; +- no showcase claim that authenticated Xaman login is working. -All future environment changes should update this document in the same PR/commit that changes the relevant deployment contract. +Any future environment-role change must update this document in the same branch/PR that changes the deployment contract. --- -**Checkpoint date:** 2026-08-24 +**Checkpoint date:** 2026-08-25 -**Current conclusion:** the project is over-layered historically but recoverable. The verified architecture is one GitHub application codebase, one Render production frontend/backend path, one intended external WordPress/Xaman identity bridge, and one dormant local bridge-staging/reference tree. The immediate task is configuration reconciliation, not creation of additional environments. \ No newline at end of file +**Current conclusion:** the project has accumulated multiple copies and tools, but the intended architecture is now clear: GitHub controls code, the VM is development, WP Studio is the single bridge staging environment, Render is the single production application runtime, production WordPress will host one bridge after staging passes, and ChatGPT coordinates work rather than acting as another runtime. \ No newline at end of file