Skip to content

fix(ops): isolate beta Temporal namespace and sync dev fixes - #37

Merged
JOY (JOY) merged 8 commits into
mainfrom
dev
Sep 15, 2026
Merged

JOY (JOY) merged 8 commits into
mainfrom
dev

Conversation

@JOY

@JOY JOY (JOY) commented Sep 14, 2026

Copy link
Copy Markdown

Summary

  • Temporal Namespace Isolation: Configures TEMPORAL_NAMESPACE: 'beta' in scripts/docker-compose.beta.yaml and provisions dedicated beta namespace on Temporal cluster to prevent Beta worker from colliding with Production task queues (No Post incident U4).
  • Audit Documentation: Updates docs/audit-2026-09-08.html with root cause analysis and resolution status for U3 (beta DB) and U4 (temporal task queue isolation).
  • Prior Fixes in dev: Includes DNS ipv4first fix for Temporal gRPC client, CI test bootstrap integration, fail-closed mobile app scheme security check, and deployed MCP surface probe.

Changes

  • scripts/docker-compose.beta.yaml: Added TEMPORAL_NAMESPACE: 'beta' under environment for crove-post-beta.
  • docs/audit-2026-09-08.html: Documented resolution of U3 (Beta Supabase connectivity) and U4 (Temporal task queue isolation between Prod and Beta).

Verification

  • Verified on crove-server: crove-post-beta recreated and active in namespace beta.
  • Verified crove-post (Production) exclusively polls default namespace.
  • Disk capacity resized to 500GB with 415GB available.

Note

Medium Risk
Changes Beta/Prod workflow isolation and OAuth mobile redirect behavior (authorization codes); misconfiguration could break Beta posting or mobile connect flows, while CI/probe changes affect release signal and external monitoring only.

Overview
Beta Temporal isolation sets TEMPORAL_NAMESPACE: 'beta' on crove-post-beta (compose + validate-beta-compose.mjs) so Beta workers no longer share Production’s default namespace/task queue—addressing the “No Post” cross-environment workflow incident.

Mobile OAuth no longer falls back to postiz://: unset MOBILE_APP_SCHEME makes GET /oauth-mobile-callback return 501, .env.example defaults to empty, and the frontend uses shared getMobileAppScheme / context so mobile integration OAuth only sends redirectUrl when a deployment-specific scheme is configured.

CI & ops: the Build workflow adds Postgres/Redis on the ports the bootstrap suites require, prisma db push, and Jest --runInBand; a scheduled MCP Surface Probe workflow runs scripts/probe-mcp.mjs against prod/beta (optional MCP_PROBE_TOKEN). Backend startup sets DNS ipv4first for Temporal gRPC; ESLint flat config imports eslint-config-next directly instead of FlatCompat.

Docs/changelog/audit HTML are updated for these fixes and the new probe.

Reviewed by Cursor Bugbot for commit a18f55e. Configure here.

The backend's Temporal client was resolving the temporal hostname to IPv6 ::1 (loopback) instead of the Docker network IPv4 address 172.18.0.x. This caused ALL workflow starts to silently fail (D6 pattern: ECONNREFUSED swallowed by catch block).
The Build workflow has been red on every push to dev and main since at least
2026-09-10 (PRs #33-#36). The failing step is "Test bootstrap / OAuth consent
(jest)": those suites guard against running on a shared database and throw
'Use isolated local test services' unless DATABASE_URL points at
127.0.0.1:15491 (or :15432) and REDIS_URL at 127.0.0.1:16391 (or :16379).
CI provided neither, so beforeAll threw, prisma stayed undefined, and afterAll
then failed with "Cannot read properties of undefined (reading
'oAuthAuthorization')" — surfacing as 42 failed / 11 passed of 53.

Add Postgres 17 and Redis 7.2 service containers on exactly those ports, push
the Prisma schema into the throwaway database before the run, and add
--runInBand because the suites share one database and create/delete rows by
known ids.

Verified locally against the same images, ports, env and commands:

    Test Suites: 5 passed, 5 total
    Tests:       53 passed, 53 total

Jest exits cleanly, so no --forceExit is needed.

This does not make CI a trustworthy gate on its own. The ESLint workflow is
still red (eslint 8.57 cannot consume the flat eslint.config.mjs, and fixing
that needs eslint 9 plus @typescript-eslint 8), and it is
"Build & Publish Crove Containers" — not this workflow — that produces the
deployable image. Those are separate follow-ups.
GET /oauth-mobile-callback holds a live OAuth authorization code and
redirected it to `process.env.MOBILE_APP_SCHEME || 'postiz://auth/callback'`.
Deployments that never set the variable therefore handed this instance's
authorization codes to whatever app on the user's device registered the
upstream `postiz://` scheme. Crove ships no mobile app and leaves the variable
empty, so that fallback was the live behaviour, not a theoretical one.

Return 501 instead of redirecting when the scheme is unset.

The frontend had the same defect from the other side: it hardcoded
`postiz://integrations` as the mobile deep link and never read
MOBILE_APP_SCHEME, so the two halves could not agree even when the variable
was configured. Add getMobileAppScheme, which derives just the scheme prefix
from the same variable the backend consumes, thread it through the variable
context and the three layouts, and omit redirectUrl entirely when no scheme is
configured.

Also drop the upstream scheme from the .env.example default, since the
documented example value was the exact string that caused the leak.

Eight contract tests in the branding guard cover the derivation, including
that an unset, absent, whitespace-only or separator-less value yields '' so
callers fail closed rather than falling back.
D3 — verifying the MCP server actually answers with a real credential — could
not be done from a dev machine: DNS to the deployed domains is unavailable
there, and booting the full backend locally needs Temporal plus its own
Postgres and Elasticsearch.

GitHub Actions runners have public internet access, so the probe runs from
there instead, for production and beta. It asserts:

- RFC 9728 discovery is served and names the correct protected resource
- an unauthenticated initialize is refused with a real WWW-Authenticate header
  carrying resource_metadata — not the swallowed, empty 401 shape that made
  DOSClaw misread the 2026-09-08 database outage as an OAuth failure
- an unknown bearer token is rejected as invalid_token (fail closed)
- /mcp/:id with a bogus key answers the distinct 400 'Invalid API Key'
- when the MCP_PROBE_TOKEN secret is set: initialize with a pos_ token, a
  serverInfo.name that is branded and never 'Postiz MCP', tools/list, and
  public API is-connected — the exact route DOSClaw hit in that incident

All requests are read-only (discovery, initialize, tools/list), so it is safe
on a 30-minute schedule. It is deliberately not a push/PR gate: a transient
production blip must not fail unrelated work.

Set the MCP_PROBE_TOKEN repository secret to upgrade from "the surface fails
closed" to "a real credential completes the agent path".
@cursor

cursor Bot commented Sep 14, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_5cc0e47d-a875-44e4-b18e-18fb6db44b66)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances security and environment isolation by ensuring that mobile OAuth callbacks fail closed when the MOBILE_APP_SCHEME is unconfigured, preventing unauthorized code redirection. It introduces a utility to dynamically extract the scheme prefix to avoid hardcoding, configures a dedicated Temporal namespace for the beta environment, sets the default DNS resolution order to IPv4 first, and adds an MCP probing script for deployment validation. The review feedback correctly identifies that the regular expression used to parse the mobile app scheme is overly restrictive under RFC 3986, as it assumes the presence of ://, and provides a robust suggestion to support schemes without double slashes.

env.MOBILE_APP_SCHEME || env.NEXT_PUBLIC_MOBILE_APP_SCHEME || ''
).trim();
// RFC 3986 scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ":"
const match = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/.exec(raw);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current regular expression is a bit too restrictive as it requires the scheme to be followed by ://. According to RFC 3986, a scheme is simply scheme:, and the // part is optional (indicating an authority component).

This implementation will fail for valid schemes like myapp:callback.

To make this function more robust and compliant with the RFC, I suggest updating the regex to handle both cases (with and without //). This will improve flexibility for self-hosters configuring their mobile app schemes. You may also want to add a test case in scripts/branding-guard.ts for a scheme without // to ensure correctness.

For example:

const match = /^([a-zA-Z][a-zA-Z0-9+.-]+:(?:\/\/)?)/.exec(raw);

This updated regex will correctly extract:

  • postiz:// from postiz://auth/callback
  • postiz: from postiz:auth/callback
Suggested change
const match = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/.exec(raw);
const match = /^([a-zA-Z][a-zA-Z0-9+.-]+:(?:\/\/)?)/.exec(raw);

…amespace

- Replace FlatCompat extends in root eslint.config.mjs with direct flat
  imports of eslint-config-next v16 (eslintrc validator crashes on flat
  plugin objects, which broke both ESLint Analysis jobs)
- Update validate-beta-compose environment contract to require exactly
  {MASTRA_DISABLE_STORAGE_INIT: 'true', TEMPORAL_NAMESPACE: 'beta'} so
  the namespace isolation change passes the deployment gate
@cursor

cursor Bot commented Sep 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8c10d4a4-f07f-49c6-aa60-7e223f27f2be)

@JOY
JOY (JOY) merged commit 3a27172 into main Sep 15, 2026
19 checks passed
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.

1 participant