From 76dfe85fa93787b3845d0bd460aa18ff753ca2ca Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:24:48 +0800 Subject: [PATCH 01/10] fix: separate hosted and local MCP access Remove repository-driven hosted MCP registration and the unnecessary Railway CLI Cloud install. Add a shell-only GitHub fallback preflight and document the fresh hosted-app acceptance boundary. --- .codex/config.toml | 6 +- AGENTS.md | 22 +++-- docs/agents-guide.md | 2 +- .../cloud-connection-acceptance-2026-08-05.md | 75 ++++++++++++++++ docs/codebase-index.md | 26 +++--- docs/codex-cloud.md | 85 +++++++++++-------- docs/codex-review-protocol.md | 7 ++ docs/scripts-index.md | 40 ++++----- package.json | 1 + scripts/check-codex-cloud-setup.mjs | 53 ++++-------- scripts/check-github-shell-access.mjs | 67 +++++++++++++++ scripts/setup-codex-cloud.sh | 26 +----- tests/codex-cloud-setup.test.ts | 34 +++----- 13 files changed, 278 insertions(+), 166 deletions(-) create mode 100644 docs/audit/cloud-connection-acceptance-2026-08-05.md create mode 100644 scripts/check-github-shell-access.mjs diff --git a/.codex/config.toml b/.codex/config.toml index 90047c7796..cb5b32df4d 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -1,4 +1,4 @@ -# Secret-free, project-scoped MCP registrations for trusted Codex hosts. +# Secret-free MCP registrations for trusted Codex Desktop/CLI hosts. # OAuth credentials remain in each host credential store — never commit them here. # # checked-in policy: @@ -7,7 +7,7 @@ # explicit approval; write-capable Figma/Railway/Sentry tools use "writes" # - Paid API canaries (eval:rag, eval:retrieval:quality, eval:quality, verify:release, # test:live, check:supabase-project) still need explicit confirmation per AGENTS.md -# Hosted Cloud tools come from installed OAuth plugins/connectors; `.mcp.json` is a cross-client template, not runtime proof. +# Hosted ChatGPT/Codex tools come only from installed, authenticated apps; this file is not runtime proof. [mcp_servers.figma_cloud] url = "https://mcp.figma.com/mcp" @@ -21,7 +21,7 @@ url = "https://mcp.supabase.com/mcp?project_ref=sjrfecxgysukkwxsowpy&read_only=t enabled = false default_tools_approval_mode = "prompt" -[mcp_servers.railway_cloud] +[mcp_servers.railway] url = "https://mcp.railway.com" enabled = false default_tools_approval_mode = "writes" diff --git a/AGENTS.md b/AGENTS.md index 52f8393de5..1e46bb20c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -366,7 +366,7 @@ surface, read `docs/rag-behaviour/` (README → behaviour-map → refuted-approa - Production services `Database` (Next.js app tier, serves `https://psychiatry.tools`) and `worker` (ingestion) auto-deploy from `BigSimmo/Database` pushes to `main`; the `staging` environment runs the `app` service. - The older Railway project `clinical-kb` (`4361c04f-dd3c-4ee9-9e97-49e4e5707b70`) is superseded with zero active deployments; treat it as stale — never `railway link` to it or deploy there. - The similarly named Supabase project `Clinical KB Database` is the database/auth tier, not a Railway project; see "Supabase project safety" above. -- Railway CLI/MCP auth uses `RAILWAY_API_TOKEN` (personal account token; see `.env.example`). The project-scoped `RAILWAY_TOKEN` is for CI deploys only and cannot list or link projects. The project-scoped Railway MCP server is registered in `.mcp.json`. +- Railway CLI token auth uses `RAILWAY_API_TOKEN` (personal account token; see `.env.example`). The project-scoped `RAILWAY_TOKEN` is for CI deploys only and cannot list or link projects. Desktop/CLI MCP uses the secret-free `railway` entry in `.codex/config.toml` or `.mcp.json` plus `codex mcp login railway`; neither file activates a hosted ChatGPT/Codex app. - Railway deploys and mutations fall under the "API and provider confirmation boundary" below; verify target project/environment IDs before any mutation. @@ -876,11 +876,12 @@ Use `docs/codex-cloud.md` as the environment contract: Write-capable Figma, Railway, and Sentry tools still require explicit confirmation. Paid API canaries (`eval:rag`, `eval:retrieval:quality`, `eval:quality`, `verify:release`, `test:live`, `check:supabase-project`) still need explicit confirmation. Project - `.codex/config.toml` keeps MCP entries `enabled = false` so ordinary/offline hosts do not - initialize them. Connected setup writes enabled Railway and constrained Supabase entries to the - host `$CODEX_HOME/config.toml`; actual availability still requires the installed host - plugin/connector to complete OAuth and a fresh task to prove the callable inventory with - read-only identity calls. Root `.mcp.json` is a static cross-client template, not runtime proof. + `.codex/config.toml` keeps Desktop/CLI MCP entries `enabled = false`; use + `codex mcp login railway` locally after enabling the `railway` entry. Cloud setup never writes + Railway or Supabase MCP registrations to `$CODEX_HOME`. Hosted ChatGPT/Codex requires an + installed, workspace-authorized, OAuth-authenticated app, and a fresh task must prove the callable + inventory with read-only identity calls. Root `.mcp.json` is a static cross-client template, not + hosted runtime proof. - Cloud has no Windows task-start script. Report that exact fact, then perform equivalent read-only identity, branch, status, worktree, and Git-operation checks. Proceed only in a clean disposable checkout on a task-specific non-protected branch. @@ -915,9 +916,12 @@ Use `docs/codex-cloud.md` as the environment contract: `.github/workflows/authenticated-live-tests.yml` GitHub Actions workflow, its explicit dispatch confirmation, and the `Database / production` environment, never by exposing credentials to the Codex Cloud agent shell. -- Connected Cloud Railway access uses the hosted Railway MCP connector and browser OAuth; prove it - with the callable tool inventory and a read-only identity/project-list call. CLI token auth is a - separate operator capability: it requires both the pinned CLI and a dedicated +- Connected Cloud Railway access uses Railway's installed official app, or an Enterprise/Edu + workspace custom app for `https://mcp.railway.com`, plus browser OAuth; prove it with the callable + tool inventory and a read-only identity/project-list call. Repository setup and local MCP config + cannot activate it. + CLI token auth is a separate operator capability: it requires a separately installed Railway CLI + and a dedicated `RAILWAY_API_TOKEN`, and must never substitute `RAILWAY_TOKEN` or expose either token to an ordinary agent shell. GitHub connector access, GitHub CLI authentication, the credential-free `origin` URL, and shell Git authentication are separate capabilities. diff --git a/docs/agents-guide.md b/docs/agents-guide.md index ed5d43ba93..b955969d3f 100644 --- a/docs/agents-guide.md +++ b/docs/agents-guide.md @@ -44,7 +44,7 @@ below defers to it, so rules live in one place and cannot drift. | **Codex** (OpenAI) | Primary PR code-review + automatic resolve | AGENTS.md "Codex review" sections, `docs/codex-review-protocol.md`, `docs/codex-prompt-playbook.md`, `.github/workflows/codex-autofix-review-comments.yml` | | **Claude Code** | Interactive dev; scoped review subagents + workflow skills | `.claude/` (agents, skills, hooks), `.github/workflows/claude.yml` | | **Cursor** | Editor skills + project MCP (Supabase, Context7, …) | `.cursor/` (skills, `mcp.json`) | -| **Railway MCP** | Deploy/logs/env **names** (project-scoped) | Root `.mcp.json` (`@railway/cli` mcp); needs `RAILWAY_API_TOKEN` — not `RAILWAY_TOKEN` | +| **Railway MCP** | Desktop/CLI template; hosted app is separate | Root `.mcp.json` / `.codex/config.toml` use `https://mcp.railway.com` with OAuth; hosted ChatGPT/Codex requires a workspace-installed app | | **CodeRabbit** | Advisory PR review (never blocking) | `.coderabbit.yaml` (`commit_status: false`) | | **`.agents/`** | Home-grown single-word skill catalogue | `.agents/skills/catalog.json`; list with `npm run skills` | diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md new file mode 100644 index 0000000000..20bcd30ae3 --- /dev/null +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -0,0 +1,75 @@ +# Cloud connection acceptance — 2026-08-05 + +## Root cause and durable boundary + +Hosted ChatGPT/Codex does not register tools from repository `.mcp.json`, `.codex/config.toml`, +or container setup. Those files remain secret-free Desktop/CLI templates. Hosted tools require an +installed, workspace-authorized, OAuth-authenticated app and must be verified in a fresh task. +Cloud setup also omits Railway CLI: it is unnecessary for the hosted-app path and its binary +postinstall can fail when GitHub release downloads are unavailable. + +## Hosted Railway setup and observed controls + +- Railway's official hosted app was already installed and OAuth-connected in the available ChatGPT + Pro workspace. Its permission was tightened from **Allow low-risk actions** to **Allow read + actions**, so reads can run automatically and every change must ask. No static token, header, or + repository secret was supplied. +- The available workspace is personal Pro, not the Enterprise/Edu workspace assumed by the plan. + It offers the global read-versus-change control but not per-tool disable or dedicated-group RBAC. + If Enterprise/Edu governance is required, an admin must enable Developer Mode and either govern + the official app or create `Railway — Database` at `https://mcp.railway.com`, run Scan Tools, + restrict it to a dedicated group, and disable write tools individually. +- The installed app exposed these read actions: `Fetch-docs`, `Get-feature-flag`, `Get-logs`, + `Get-service-config`, `Get-service-metrics`, `Get-status`, `List-deployments`, `List-domains`, + `List-feature-flags`, `List-projects`, `List-services`, `List-variables`, `List-workspaces`, + `Search-docs`, and `Whoami`. `List-variables` reports names only. +- It exposed these change actions, all approval-gated by **Allow read actions**: `Accept-deploy`, + `Create-deployment`, `Create-project`, `Create-service`, `Delete-feature-flag`, `Generate-domain`, + `Railway-agent`, `Redeploy`, `Set-feature-flag`, `Set-variables`, and `Update-service`. None was + invoked. +- Railway OAuth metadata advertises `openid`, `profile`, `email`, `offline_access`, and + `workspace:member`. Confirm the scanned consent requests `offline_access`, then repeat a + read-only call from a new task after the one-hour access-token lifetime. If ChatGPT receives no + refresh token, record reauthentication as required; never introduce a static-token workaround. + +## Acceptance status + +| Surface | Required proof | Status | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Repository boundary | Setup never writes Railway/Supabase MCP servers; local templates stay secret-free | PASS: focused tests and static checker pass; Cloud setup also no longer installs Railway CLI | +| GitHub | Connector reads `BigSimmo/Database`; exact commit is published and verified | Read PASS via `mcp__codex_apps__github_get_repo`; draft publication pending | +| Railway | Exact tools callable in a fresh ChatGPT chat and fresh Codex Cloud task; read-only identity/project/service checks pass | ChatGPT PASS via `mcp__codex_apps__railway_whoami`, `railway_list_projects`, and `railway_list_services`; exact project and services `app`, `worker`, `Database` visible. Fresh Codex Cloud rerun pending after publication | +| Supabase | Existing app exposes only project-scoped read-only metadata without row queries | ChatGPT PASS via `mcp__codex_apps__supabase_list_projects`: `sjrfecxgysukkwxsowpy`, `Clinical KB Database`, `ACTIVE_HEALTHY`; no schema/table/row/log call | +| Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI PASS: exactly the five documented values and no `OPENAI_BASE_URL`; fresh raw-shell PASS pending | +| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | Pending elapsed-time validation; no token workaround added | + +The first fresh Codex Cloud acceptance attempt did not reach the agent: setup failed while +`@railway/cli@5.30.4` tried to download its binary from GitHub Releases and received +`ENETUNREACH`. The repository fix removes that unnecessary CLI installation. This is evidence of +the old setup defect, not evidence that Railway is callable in Codex Cloud; the corrected branch +must be published and tested in another fresh Cloud task. + +## Prior branch and blocked-result disposition + +- GitHub connector inspection found PR #1613 still open at current head + `68c1f17909802c1d0e7b8e999de2259de4072dc7`; its description still cites stale acceptance head + `8a4ad8ff53072bc796c81c0039006222bca6c068` and retains the old claim that repository setup enables + connected Railway/Supabase MCPs. This repair is isolated from that unrelated PR and does not + merge, close, or rewrite it. +- Connector commit search did not find the Cloud-only `fe31128` GitHub preflight commit. The bounded + behavior was recreated as `check:github-shell-access`: its normal mode intentionally checks only + the optional `gh` shell fallback, while `--self-test` is fully offline. `GH_AUTH_MISSING` therefore + says nothing about the hosted GitHub connector, which passed independently. +- The original blocked report's `OPENAI_BASE_URL` condition was removed in Codex environment + settings. `Database - connected` now contains exactly the five documented non-secret variables. + A new raw-shell probe is still required because only a new task can prove the launcher stopped + inheriting the removed value. + +Do not mark Railway accepted because it appears in workspace settings or repository files. Success +requires a callable tool in both fresh hosted contexts. If direct Railway exposure or safe project +isolation fails, stop before deployment and design a separate tool-only gateway with per-user OAuth +and PKCE/refresh, exact project allowlisting, read-only v1 tools, encrypted server-side tokens, +audit logs, redaction, rate limits, bounded log metadata, request timeouts, and explicit write +approvals. Its hosting target and Railway OAuth registration require separate approval before code. + +References: [OpenAI Developer Mode](https://help.openai.com/en/articles/12584461-developer-mode-and-full-mcp-connectors-in-chatgpt-beta), [Apps in ChatGPT](https://help.openai.com/en/articles/11487775-connectors-in-chatgpt/), [Railway MCP server](https://docs.railway.com/ai/mcp-server), and [Railway OAuth token lifecycle](https://docs.railway.com/integrations/oauth/login-and-tokens). diff --git a/docs/codebase-index.md b/docs/codebase-index.md index adc6096dd8..3331ee8093 100644 --- a/docs/codebase-index.md +++ b/docs/codebase-index.md @@ -34,19 +34,19 @@ Structured map for AI agents and onboarding. For live routes, see `docs/site-map Smaller top-level directories that are easy to miss: -| Path | Purpose | -| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `data/` | Committed clinical **snapshot exports** loaded at runtime by `src/lib/` (differentials, forms, medications, services, specifiers). Regenerate via the matching `scripts/import-*-export.ts` / `build-*-index.mjs`; do not hand-edit. Distinct from `src/data/`, which holds hand-authored static content. | -| `eslint-rules/` | Repo-specific lint rules enforced by `npm run lint` (button wiring, hardcoded hex, type/icon scale, z-index ladder) | -| `mockups/` | Notes for the design-scratch routes under `src/app/mockups/` (the routes themselves 404 in production) | -| `plugins/` | `plugins/clinical-kb/` Codex plugin manifest and workflow skill | -| `.agents/` | Single-word skill catalogue (`npm run skills`, validated by `npm run check:skills`) | -| `.claude/` | Claude Code agents, skills, hooks, settings — plus the `.claude/worktrees/` working copies | -| `.codex/` | Project-scoped Codex host config; tracked `config.toml` has disabled, secret-free Figma, Supabase, Railway, and Sentry MCP registrations. Figma/Railway/Sentry use `"writes"`; read-only Supabase uses `"auto"`. Runtime Cloud MCP stays `.mcp.json`; all `.codex/*` stay ignored; OAuth stays in the host credential store. | -| `.cursor/` | Cursor project rules and local-agent configuration | -| `.design-sync/` | Generated design-system package metadata, validation notes, and project-sync artifacts | -| `.githooks/` | Installed by `npm install`; `pre-push` runs `scripts/guard-push.mjs` (format, auto-merge race, drift staleness) | -| `.vscode/` | Shared VS Code workspace recommendations and settings | +| Path | Purpose | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data/` | Committed clinical **snapshot exports** loaded at runtime by `src/lib/` (differentials, forms, medications, services, specifiers). Regenerate via the matching `scripts/import-*-export.ts` / `build-*-index.mjs`; do not hand-edit. Distinct from `src/data/`, which holds hand-authored static content. | +| `eslint-rules/` | Repo-specific lint rules enforced by `npm run lint` (button wiring, hardcoded hex, type/icon scale, z-index ladder) | +| `mockups/` | Notes for the design-scratch routes under `src/app/mockups/` (the routes themselves 404 in production) | +| `plugins/` | `plugins/clinical-kb/` Codex plugin manifest and workflow skill | +| `.agents/` | Single-word skill catalogue (`npm run skills`, validated by `npm run check:skills`) | +| `.claude/` | Claude Code agents, skills, hooks, settings — plus the `.claude/worktrees/` working copies | +| `.codex/` | Trusted Desktop/CLI config; tracked `config.toml` has disabled, secret-free Figma, Supabase, Railway, and Sentry MCP templates. Hosted ChatGPT/Codex apps are installed and authenticated separately; OAuth stays in the host credential store. | +| `.cursor/` | Cursor project rules and local-agent configuration | +| `.design-sync/` | Generated design-system package metadata, validation notes, and project-sync artifacts | +| `.githooks/` | Installed by `npm install`; `pre-push` runs `scripts/guard-push.mjs` (format, auto-merge race, drift staleness) | +| `.vscode/` | Shared VS Code workspace recommendations and settings | **Do not commit:** `.next/`, `node_modules/`, `coverage/`, `.env*`, `sample-documents/`, logs. diff --git a/docs/codex-cloud.md b/docs/codex-cloud.md index 8722df8f0f..c8e9372cab 100644 --- a/docs/codex-cloud.md +++ b/docs/codex-cloud.md @@ -46,10 +46,10 @@ it makes every normal `node`, `npm`, and `npx` invocation load the generated san profile before starting Node. It is idempotent and uses `nvm which` rather than `command -v node`, so maintenance cannot accidentally wrap an earlier wrapper. -The setup command fails if the complete toolchain cannot be installed. It pins Railway CLI -`5.30.4` and Codex CLI `0.146.0`, both stable npm releases as reviewed on 2026-07-30. Railway's -[official CLI guide](https://docs.railway.com/cli) supports global npm installation on Node 16+ -(this repository uses Node 24). OpenAI's +The setup command fails if the required Cloud toolchain cannot be installed. It intentionally does +not install Railway CLI: hosted Railway access comes from the authenticated workspace app, and the +CLI postinstall downloads a separate binary that may be blocked in the Cloud setup network. It pins +Codex CLI `0.146.0`, reviewed on 2026-07-30. OpenAI's [official Codex CLI guide](https://learn.chatgpt.com/docs/codex/cli) supports Linux installation; the npm package is used here so maintenance can verify an exact version without running an unversioned installer. Set @@ -106,8 +106,8 @@ PLAYWRIGHT_OFFLINE_MODE=true Keep OpenAI disabled unless a later task explicitly authorizes it. Do not add provider keys, tokens, database URLs, service-role values, E2E credentials, or `ALLOW_PROVIDER_TESTS` to this environment. The generated agent profile removes the complete provider-variable inventory in -both access profiles. Connected access configures the repository profile for scoped OAuth MCP -servers and GitHub integration, but it does not expose raw credentials to the shell or guarantee +both access profiles. Connected access records authorization intent and keeps the GitHub boundary +explicit, but it does not register hosted MCP apps, expose raw credentials to the shell, or guarantee that every GitHub capability appears as a direct agent tool. For ordinary Cloud task publishing, use the native Cloud diff/PR controls and verify the resulting GitHub branch and PR link. A metadata-only `make_pr` response is not publication evidence. If a requested GitHub API is not @@ -234,7 +234,7 @@ Expected decisive lines include: The effective-environment check runs automatically when `CODEX_CLOUD=1`, including without `--runtime`, so a newly started agent shell cannot pass with stale modes. Its report prints only approved mode values and presence booleans. The runtime check additionally verifies Node/npm -policy and installed-lock parity, pinned Railway/Codex CLIs, Deno 2, Python 3 and worker imports, +policy and installed-lock parity, the pinned Codex CLI, Deno 2, Python 3 and worker imports, Tesseract, actual headless launch-and-close for Chromium/Firefox/WebKit, the Python requirements fingerprint plus `pip check` and medspaCy/spaCy versions, the expected base commit as an ancestor of HEAD, the `BigSimmo/Database` origin identity, offline credential absence when applicable, @@ -269,13 +269,15 @@ in the offline profile. Provider access is verified separately because a generic bootstrap must not make paid or production-like calls. For a connected environment, name each provider, use a read-only or minimal no-op endpoint, confirm the intended account/project by non-secret metadata, and -report cost or mutation risk before any write. The checked-in MCP configuration uses Railway's -hosted `https://mcp.railway.com` endpoint so fresh Cloud tasks authenticate through browser OAuth -instead of depending on machine-local CLI state. Authorize only workspace `bigsimmo's Projects` -and project `Database` (`5deaad0b-675a-4c13-978e-5ca2b5b877f9`), restart the MCP client after -consent, and reduce identity/status results to non-secret account, project, workspace, environment, -and service metadata. Railway's remote MCP does not accept project tokens; retain the pinned CLI -only for explicitly approved local/operator workflows. +report cost or mutation risk before any write. Prefer Railway's installed official app and complete +browser OAuth without static tokens or headers. If the official app is unavailable, an +Enterprise/Edu admin can enable Developer Mode and create a workspace-managed custom app named +`Railway — Database` at Railway's official `https://mcp.railway.com` endpoint, then run Scan Tools +and complete OAuth. Authorize only workspace `bigsimmo's Projects` and project `Database` +(`5deaad0b-675a-4c13-978e-5ca2b5b877f9`) where Railway offers that choice, restart the MCP client +after consent, and reduce identity/status results to non-secret account, project, workspace, +environment, and service metadata. Railway's remote MCP does not accept project tokens; install +Railway CLI separately only for explicitly approved local/operator workflows. The Supabase MCP entry is scoped to production project `sjrfecxgysukkwxsowpy`, forces `read_only=true`, and exposes only documentation/development metadata tools. The database and @@ -286,14 +288,13 @@ and storage mutations require a separately configured non-production project or broaden the production entry. OpenAI generation, Supabase live data, Railway changes, hosted CI reruns, ingestion, deployment, and release workflows remain separate explicit actions. -Project `.codex/config.toml` is the checked-in Codex MCP template. Its URL-only entries -remain `enabled = false` so offline tasks do not initialize providers. In the connected profile, -setup copies the audited Railway and constrained Supabase URLs into its managed -`$CODEX_HOME/config.toml` block with `enabled = true`; the first use completes browser OAuth. -Hosted ChatGPT still requires the matching installed plugin/connector. In either host, start a fresh -task after consent and verify the actual callable inventory. -The root `.mcp.json` is a cross-client template and static allowlist only. It does not prove hosted -Cloud availability unless a plugin manifest or host explicitly imports it. Context7 / library-docs +Project `.codex/config.toml` is the checked-in Codex Desktop/CLI MCP template. Its URL-only entries +remain `enabled = false`; a trusted local operator can enable `railway` and run +`codex mcp login railway`. Setup does not copy any MCP server into `$CODEX_HOME`. Hosted ChatGPT and +Codex Cloud require the separately installed/authenticated workspace app. Start a fresh task after +consent and verify the actual callable inventory. +The root `.mcp.json` is a cross-client Desktop/CLI template and static allowlist only. It does not +prove hosted Cloud availability. Context7 / library-docs MCP is Cursor-side (`.cursor/mcp.json` or a host-injected connector), not part of this Codex Cloud Railway + Supabase allowlist. @@ -322,15 +323,21 @@ copying credentials into the checkout. maintenance as `bash scripts/maintain-codex-cloud.sh && bash scripts/install-codex-cloud-command-shims.sh`. Do not add provider keys, database URLs, service-role credentials, test-user credentials, or - `ALLOW_PROVIDER_TESTS`. Connected setup writes only the audited Railway/Supabase endpoints - into the managed host MCP block; it never writes OAuth tokens. -2. **Grant the host integrations.** Authorize the Codex GitHub connector for + `ALLOW_PROVIDER_TESTS`. Connected setup writes only the managed shell-environment policy; it + never registers hosted MCP apps or writes OAuth tokens. +2. **Grant the host integrations.** Install Railway's official app and complete Railway OAuth. If it + is unavailable, an Enterprise/Edu workspace admin enables Developer Mode, creates + `Railway — Database` with `https://mcp.railway.com`, runs Scan Tools, and publishes access only + to a dedicated RBAC group containing the user. Authorize the Codex GitHub connector for `BigSimmo/Database` with repository write access. Complete Railway OAuth only for workspace `bigsimmo's Projects` and project `Database` (`5deaad0b-675a-4c13-978e-5ca2b5b877f9`). Complete Supabase OAuth only for the organization containing `Clinical KB Database`; retain project ref `sjrfecxgysukkwxsowpy`, `read_only=true`, and the docs/development-only feature allowlist. Do not broaden the production Supabase MCP to write access. Enable Figma or Sentry only for a task that names that - provider; their write-capable tools remain approval-gated. + provider; their write-capable tools remain approval-gated. Railway's OAuth metadata advertises + `offline_access`; verify the scanned consent includes it and prove refresh behavior with a second + read-only call after the one-hour access-token lifetime. If no refresh token is issued, require + reauthentication instead of adding a token workaround. 3. **Start a fresh task.** OAuth tools and environment values are fixed when the task starts. A setup rerun inside an already-running offline task can validate a generated connected profile, but it cannot inject host MCP tools or retroactively grant OAuth. Restart the MCP client or open @@ -345,7 +352,13 @@ copying credentials into the checkout. 5. **Prove each provider read-only.** Use the tools exposed by the fresh host session, not shell tokens. For GitHub, read repository metadata and confirm `BigSimmo/Database` plus the intended identity. For Railway, read workspace/project/service metadata and confirm the IDs above without - triggering a deployment. For Supabase, read project/schema metadata and confirm the pinned ref + triggering a deployment. Record the exact Railway inventory. Set the app to allow reads and ask + before changes. In an Enterprise/Edu workspace, also allow `whoami`, `list-projects`, + `list-services`, `list-feature-flags`, and `get-feature-flag`; disable `create-project`, + `set-feature-flag`, `delete-feature-flag`, `redeploy`, `accept-deploy`, and `railway-agent`, with + newly discovered actions disabled by default. Personal workspaces may expose only the global + read-versus-change approval control; that does not prove tool-level RBAC. For Supabase, read + project/schema metadata and confirm the pinned ref without querying clinical row contents. Report only non-secret identity and status metadata. OpenAI has no generic connected-profile credential: leave `RAG_PROVIDER_MODE=offline` until a separately approved paid canary or protected workflow supplies its own credential boundary. @@ -371,15 +384,15 @@ copying credentials into the checkout. Acceptance is complete only when evidence distinguishes these independent capabilities: -| Capability | Required evidence | Not sufficient | -| ----------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | -| Connected boundary | Cloud environment and runtime PASS lines with provider variables absent | Editing the generated profile in one running task | -| GitHub read/write | Connector repository read plus verified publication of the exact task commit | Repository discovery, `make_pr` metadata, or a local commit | -| Railway read | OAuth-backed metadata for the expected workspace and project | Installed Railway CLI alone | -| Supabase read | OAuth-backed metadata for the pinned read-only project | A configured MCP URL without completed OAuth | -| OpenAI/live application | Explicitly approved paid canary or protected authenticated workflow | Setting `RAG_PROVIDER_MODE=auto` without credentials | -| Local application | Project identity plus the reported health status from the `ensure` URL | Assuming a localhost port or treating demo 503 as production-ready | -| Capacity | Full intended checks complete without OOM, or a host-level capacity change is proven | Swap size by itself | +| Capability | Required evidence | Not sufficient | +| ----------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| Connected boundary | Cloud environment and runtime PASS lines with provider variables absent | Editing the generated profile in one running task | +| GitHub read/write | Connector repository read plus verified publication of the exact task commit | Repository discovery, `make_pr` metadata, or a local commit | +| Railway read | Exact callable hosted-app tools plus OAuth metadata for the expected workspace/project | Installed Railway CLI or repository MCP config alone | +| Supabase read | OAuth-backed metadata for the pinned read-only project | A configured MCP URL without completed OAuth | +| OpenAI/live application | Explicitly approved paid canary or protected authenticated workflow | Setting `RAG_PROVIDER_MODE=auto` without credentials | +| Local application | Project identity plus the reported health status from the `ensure` URL | Assuming a localhost port or treating demo 503 as production-ready | +| Capacity | Full intended checks complete without OOM, or a host-level capacity change is proven | Swap size by itself | If a capability still fails, record the exact sanitized failure and its owner: repository setup, Codex environment/OAuth, provider RBAC, GitHub installation, protected workflow, or host capacity. diff --git a/docs/codex-review-protocol.md b/docs/codex-review-protocol.md index a97741c654..d753902c8f 100644 --- a/docs/codex-review-protocol.md +++ b/docs/codex-review-protocol.md @@ -22,6 +22,13 @@ Use this protocol for every Codex review, audit, bug hunt, PR review, release-re - Include checks run, checks not run, and whether any check was skipped because it could touch an API/provider. - Keep summaries secondary to findings. +## GitHub access routing + +For an explicitly authorized hosted task, use the GitHub connector or native Cloud controls first. +Their permissions are independent of `gh` in the agent shell. Run `npm run check:github-shell-access` +only before an intentional GitHub CLI fallback; `GH_AUTH_MISSING` means shell authentication is +absent, not that the hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. + ## Mutation Rules - For a pure review request, do not edit files, stage, commit, push, post PR comments, rerun hosted CI, or call provider-backed services. diff --git a/docs/scripts-index.md b/docs/scripts-index.md index 2aa21a4d2e..83f0b6f4d3 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -1,6 +1,6 @@ # Scripts index -Curated map of `scripts/` (213 files) and the `package.json` script surface (222 entries), +Curated map of `scripts/` (214 files) and the `package.json` script surface (223 entries), grouped by purpose. This is orientation, not an exhaustive per-file listing — the authoritative command list is `package.json`, and `npm run docs:check-scripts` verifies every `npm run ` referenced in docs resolves to a real script. `npm run docs:update` refreshes the exact counts above. @@ -16,25 +16,25 @@ migration has shipped (see `docs/maturity-backlog-workorders.md` L1). ## Runner & guard infrastructure [infra] -| Script | Role | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | -| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | -| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | -| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | -| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | -| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized acceptance, and safe credential-free `origin` repair | -| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | -| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | -| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | -| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | -| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | -| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | -| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | -| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | -| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | -| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | -| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | +| Script | Role | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | +| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | +| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | +| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | +| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | +| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized acceptance, an intentional provider-backed GitHub shell preflight (`--self-test` is offline), and safe credential-free `origin` repair | +| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | +| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | +| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | +| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | +| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | +| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | +| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | +| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | +| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | +| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | +| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | ## Verification gates [live] diff --git a/package.json b/package.json index 3e50ba5e01..7d47ce7aa6 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "check:installed-lock-parity": "node scripts/check-installed-lock-parity.mjs", "check:upload-limit-parity": "node scripts/check-upload-limit-parity.mjs --self-test && node scripts/check-upload-limit-parity.mjs", "check:codex-cloud": "node scripts/check-codex-cloud-setup.mjs", + "check:github-shell-access": "node scripts/check-github-shell-access.mjs", "cloud:setup": "bash scripts/setup-codex-cloud.sh", "cloud:maintain": "bash scripts/maintain-codex-cloud.sh", "check:codex-autofix-workflow": "node scripts/check-codex-autofix-workflow.mjs", diff --git a/scripts/check-codex-cloud-setup.mjs b/scripts/check-codex-cloud-setup.mjs index 432f192e89..ee90526d6a 100644 --- a/scripts/check-codex-cloud-setup.mjs +++ b/scripts/check-codex-cloud-setup.mjs @@ -16,7 +16,6 @@ import { providerEnvironmentKeys } from "./test-environment.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); export const expectedCloudCliVersions = Object.freeze({ - railway: "5.30.4", codex: "0.146.0", }); @@ -34,7 +33,7 @@ export const expectedCodexProjectMcpServers = Object.freeze({ url: "https://mcp.figma.com/mcp", approvalMode: "writes", }), - railway_cloud: Object.freeze({ + railway: Object.freeze({ url: expectedMcpConfiguration.railwayUrl, approvalMode: "writes", }), @@ -151,8 +150,8 @@ function validateSupabaseMcpUrl(urlString, label, errors) { } /** - * Project `.codex/config.toml` must register the approved MCP surface as disabled - * URL-only templates so offline/ordinary Codex hosts do not initialize providers. + * Project `.codex/config.toml` must register the approved Desktop/CLI MCP surface + * as disabled URL-only templates. Hosted tools require separately installed apps. * @param {string} text * @returns {string[]} */ @@ -172,7 +171,7 @@ export function validateCodexProjectMcpConfiguration(text) { const expected = expectedCodexProjectMcpServers[name]; if (server.enabled !== false) { - errors.push(`${label} must set enabled = false (host/connected layers opt in).`); + errors.push(`${label} must set enabled = false (trusted Desktop/CLI operators opt in).`); } if (server.default_tools_approval_mode !== expected.approvalMode) { const reason = @@ -294,16 +293,6 @@ export function validateCodexCloudEnvironment(env = process.env) { return errors; } -/** @param {NodeJS.ProcessEnv | Record} [env] */ -export function railwayReadCapability(env = process.env, cliAvailable = false) { - return { - cliAvailable, - dedicatedCredentialPresent: Boolean(env.RAILWAY_API_TOKEN), - projectCredentialPresent: Boolean(env.RAILWAY_TOKEN), - cliTokenAuthReady: cliAvailable && Boolean(env.RAILWAY_API_TOKEN), - }; -} - export function parseMcpServerMetadata(text) { const parsed = JSON.parse(text); const servers = parsed?.mcpServers; @@ -347,17 +336,17 @@ export function validateMcpConfiguration(text) { const serverNames = Object.keys(servers).sort(); if (JSON.stringify(serverNames) !== JSON.stringify(["railway", "supabase"])) { - errors.push("Cloud MCP configuration must contain only Railway and Supabase."); + errors.push("Desktop/CLI .mcp.json must contain only Railway and Supabase."); } for (const name of ["railway", "supabase"]) { if (servers[name]?.env !== undefined || servers[name]?.headers !== undefined) { - errors.push(`${name} MCP must use hosted OAuth without embedded environment variables or headers.`); + errors.push(`${name} MCP must use OAuth without embedded environment variables or headers.`); } } const railway = servers.railway; if (railway?.type !== "http" || railway?.url !== expectedMcpConfiguration.railwayUrl.replace(/\/$/, "")) { - errors.push("Railway MCP must use the hosted OAuth endpoint."); + errors.push("Railway MCP must use Railway's official remote OAuth endpoint."); } const supabase = servers.supabase; @@ -410,7 +399,6 @@ function commandAvailable(command) { /** @param {NodeJS.ProcessEnv | Record} [env] */ export function sanitizedCloudCapabilityLines(env = process.env, options = {}) { const origin = options.origin ?? inspectOriginRemote(repoRoot); - const railway = railwayReadCapability(env, options.railwayCliAvailable ?? commandAvailable("railway")); const codexCliAvailable = options.codexCliAvailable ?? commandAvailable("codex"); const safeGitHelper = options.safeGitHelper ?? hasSafeGitHubCredentialHelper(repoRoot); const mcpServers = options.mcpServers ?? parseMcpServerMetadata(read(".mcp.json")); @@ -423,11 +411,7 @@ export function sanitizedCloudCapabilityLines(env = process.env, options = {}) { `PLAYWRIGHT_OFFLINE_MODE=${approvedModeValue(env.PLAYWRIGHT_OFFLINE_MODE, ["true", "false"])}`, ]; for (const name of providerCredentialVariables) lines.push(`${name}.present=${Boolean(env[name])}`); - lines.push(`railway.cli_available=${railway.cliAvailable}`); - lines.push(`railway.dedicated_credential_present=${railway.dedicatedCredentialPresent}`); - lines.push(`railway.project_credential_present=${railway.projectCredentialPresent}`); - lines.push(`railway.cli_token_auth_ready=${railway.cliTokenAuthReady}`); - lines.push("mcp.runtime_tool_inventory=host-provided-unverified-by-repository"); + lines.push("hosted_app.inventory=external-unverified-until-fresh-task"); lines.push(`codex.cli_available=${codexCliAvailable}`); lines.push(pythonWorkerVersionLine(env.CODEX_CLOUD_OCR_PYTHON)); lines.push(`git.origin_configured=${origin.configured}`); @@ -442,7 +426,7 @@ export function sanitizedCloudCapabilityLines(env = process.env, options = {}) { lines.push(`git.checkout_freshness=${checkout.freshness}`); for (const server of mcpServers) { lines.push( - `mcp.server=${server.name} type=${server.type} command=${server.command} endpoint=${server.endpoint} query_names=${server.queryNames.join(",") || "none"} environment_names=${server.environmentNames.join(",") || "none"}`, + `desktop_cli_mcp.template=${server.name} type=${server.type} command=${server.command} endpoint=${server.endpoint} query_names=${server.queryNames.join(",") || "none"} environment_names=${server.environmentNames.join(",") || "none"}`, ); } return lines; @@ -648,18 +632,9 @@ export function validateCodexCloudSetup() { [/CODEX_CLOUD_OCR_PYTHON/, "Cloud setup must expose the Python worker environment."], [/playwright install --with-deps chromium firefox webkit/, "Cloud setup must install every browser."], [/CODEX_CLOUD_ACCESS_PROFILE/, "Cloud setup must support explicit access profiles."], - [ - /mcp_servers\.railway_connected/, - "Connected Cloud setup must enable the hosted Railway MCP server in the managed host config.", - ], - [ - /mcp_servers\.supabase_connected/, - "Connected Cloud setup must enable the constrained Supabase MCP server in the managed host config.", - ], [/RAG_PROVIDER_MODE=offline/, "Cloud setup must default RAG to offline mode."], [/unset OPENAI_API_KEY/, "Cloud setup must remove raw provider variables from the agent shell."], [/\.bash_profile/, "Cloud setup must cover Bash login-profile precedence."], - [/@railway\/cli/, "Cloud setup must install the Railway CLI."], [/@openai\/codex/, "Cloud setup must install the Codex CLI."], [/ensure-codex-cloud-git-remote\.mjs/, "Cloud setup must restore a safe origin remote."], [/check:codex-cloud -- --runtime/, "Cloud setup must run runtime acceptance."], @@ -681,15 +656,18 @@ export function validateCodexCloudSetup() { ]) { requireMatch(errors, setup, pattern, message); } - if (!setup.includes(`railway_cli_version="${expectedCloudCliVersions.railway}"`)) { - errors.push("Cloud setup Railway CLI version must match the checked runtime contract."); - } if (!setup.includes(`codex_cli_version="${expectedCloudCliVersions.codex}"`)) { errors.push("Cloud setup Codex CLI version must match the checked runtime contract."); } for (const name of providerCredentialVariables) { if (!setup.includes(name)) errors.push(`Cloud setup must handle provider environment variable ${name}.`); } + if (/printf[^\n]*mcp_servers\./.test(setup)) { + errors.push("Cloud setup must not generate MCP registrations; hosted apps are external to the repository."); + } + if (setup.includes("@railway/cli") || setup.includes('setup_step="railway-cli"')) { + errors.push("Cloud setup must not install or invoke Railway CLI; hosted access comes from the authenticated app."); + } const providerScrubIndex = setup.indexOf("unset OPENAI_API_KEY"); const accessProfileBranchIndex = setup.indexOf('if [ "\\$CODEX_CLOUD_ACCESS_PROFILE" = "connected" ]'); if (providerScrubIndex < 0 || accessProfileBranchIndex < 0 || providerScrubIndex > accessProfileBranchIndex) { @@ -849,7 +827,6 @@ export async function validateCodexCloudRuntime(env = process.env) { for (const error of [ commandVersion("deno", ["--version"], /^deno 2\./m), commandVersion("tesseract", ["--version"], /^tesseract \d+\./m), - commandVersion("railway", ["--version"], exactVersionPattern(expectedCloudCliVersions.railway)), commandVersion("codex", ["--version"], exactVersionPattern(expectedCloudCliVersions.codex)), ]) { if (error) errors.push(error); diff --git a/scripts/check-github-shell-access.mjs b/scripts/check-github-shell-access.mjs new file mode 100644 index 0000000000..b7c8836c0a --- /dev/null +++ b/scripts/check-github-shell-access.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const repository = "BigSimmo/Database"; + +function shellGh(command, args) { + return spawnSync(command, args, { encoding: "utf8", shell: false }); +} + +/** + * Check only the optional GitHub CLI fallback available to the current shell. + * Hosted GitHub connector/native Cloud access is a separate capability. + */ +export function githubShellAccess(run = shellGh) { + if (run("gh", ["--version"]).status !== 0) { + return { ok: false, outcome: "GH_CLI_MISSING" }; + } + if (run("gh", ["auth", "status", "--hostname", "github.com"]).status !== 0) { + return { ok: false, outcome: "GH_AUTH_MISSING" }; + } + if (run("gh", ["repo", "view", repository, "--json", "nameWithOwner,viewerPermission"]).status !== 0) { + return { ok: false, outcome: "GH_REPO_ACCESS_MISSING" }; + } + if ( + run("gh", ["pr", "list", "--repo", repository, "--state", "open", "--limit", "1", "--json", "number"]).status !== 0 + ) { + return { ok: false, outcome: "GH_PR_LIST_ACCESS_MISSING" }; + } + return { ok: true, outcome: "GH_SHELL_ACCESS_READY" }; +} + +function fakeRun(statuses) { + let index = 0; + return () => ({ status: statuses[index++] ?? 0, stdout: "", stderr: "" }); +} + +function selfTest() { + const cases = [ + [[1], "GH_CLI_MISSING"], + [[0, 1], "GH_AUTH_MISSING"], + [[0, 0, 1], "GH_REPO_ACCESS_MISSING"], + [[0, 0, 0, 1], "GH_PR_LIST_ACCESS_MISSING"], + [[0, 0, 0, 0], "GH_SHELL_ACCESS_READY"], + ]; + for (const [statuses, expected] of cases) { + const actual = githubShellAccess(fakeRun(statuses)); + if (actual.outcome !== expected || actual.ok !== (expected === "GH_SHELL_ACCESS_READY")) { + throw new Error(`expected ${expected}, received ${actual.outcome}`); + } + } + console.log("GITHUB_SHELL_ACCESS_SELF_TEST=PASS"); +} + +function main() { + if (process.argv.includes("--self-test")) { + selfTest(); + return; + } + const result = githubShellAccess(); + console.log(`GITHUB_SHELL_ACCESS=${result.outcome}`); + console.log("GITHUB_HOSTED_CONNECTOR=SEPARATE_UNVERIFIED_CAPABILITY"); + if (!result.ok) process.exitCode = 1; +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) main(); diff --git a/scripts/setup-codex-cloud.sh b/scripts/setup-codex-cloud.sh index c34d81d709..660c60ed9f 100644 --- a/scripts/setup-codex-cloud.sh +++ b/scripts/setup-codex-cloud.sh @@ -35,7 +35,6 @@ cd "$repo_root" expected_node_major="$(tr -cd '0-9' < .node-version)" expected_npm_version="$(sed -n 's/.*"packageManager"[[:space:]]*:[[:space:]]*"npm@\([^"]*\)".*/\1/p' package.json | head -n 1)" -railway_cli_version="5.30.4" codex_cli_version="0.146.0" expected_cloud_python="3.12" [[ -n "$expected_node_major" ]] || fail "Could not read the Node major from .node-version." @@ -98,13 +97,6 @@ else rag_provider_mode="offline" fi -connected_supabase_mcp_url="" -if [[ "$access_profile" = "connected" ]]; then - connected_supabase_mcp_url="$(sed -n '/^\[mcp_servers\.supabase_cloud\]$/,/^\[mcp_servers\./ s/^url = "\(.*\)"$/\1/p' .codex/config.toml | head -n 1)" - [[ "$connected_supabase_mcp_url" = https://mcp.supabase.com/mcp\?* ]] || - fail "Could not resolve the audited Supabase MCP URL from .codex/config.toml." -fi - runtime_profile="$HOME/.clinical-kb-codex-cloud.sh" cat > "$runtime_profile" < "$codex_config_candidate" if [[ "${CODEX_CLOUD_SETUP_TEST_FAIL_ATOMIC_WRITE:-0}" = "1" ]]; then @@ -247,8 +229,6 @@ log "Installing locked Node dependencies." setup_step="node-dependencies" npm ci --include=dev -setup_step="railway-cli" -install_npm_cli "@railway/cli" "$railway_cli_version" "railway" setup_step="codex-cli" install_npm_cli "@openai/codex" "$codex_cli_version" "codex" diff --git a/tests/codex-cloud-setup.test.ts b/tests/codex-cloud-setup.test.ts index de77427370..01d59e4582 100644 --- a/tests/codex-cloud-setup.test.ts +++ b/tests/codex-cloud-setup.test.ts @@ -19,7 +19,6 @@ import { pythonWorkerImportError, pythonWorkerVersionLine, pythonWorkerImports, - railwayReadCapability, sanitizedCloudCapabilityLines, validateCodexCloudEnvironment, validateCodexProjectMcpConfiguration, @@ -224,7 +223,6 @@ describe("Codex Cloud environment contract", () => { repositoryMatch: true, credentialsEmbedded: false, }, - railwayCliAvailable: true, codexCliAvailable: true, safeGitHelper: true, checkout: { @@ -249,7 +247,10 @@ describe("Codex Cloud environment contract", () => { ); const report = lines.join("\n"); expect(report).toContain("OPENAI_API_KEY.present=true"); - expect(report).toContain("mcp.server=railway type=http command=none endpoint=https://mcp.railway.com/"); + expect(report).toContain("hosted_app.inventory=external-unverified-until-fresh-task"); + expect(report).toContain( + "desktop_cli_mcp.template=railway type=http command=none endpoint=https://mcp.railway.com/", + ); expect(report).toContain(`git.head=${"a".repeat(40)}`); expect(report).toContain("git.expected_base_ancestor=true"); expect(report).toContain("git.checkout_freshness=verified"); @@ -264,7 +265,6 @@ describe("Codex Cloud environment contract", () => { }, { origin: { configured: true, repositoryMatch: true, credentialsEmbedded: false }, - railwayCliAvailable: false, codexCliAvailable: false, safeGitHelper: false, checkout: { @@ -281,16 +281,6 @@ describe("Codex Cloud environment contract", () => { ).toContain("RAG_PROVIDER_MODE=invalid"); }); - it("requires the Railway CLI and dedicated account token without substituting a project token", () => { - expect(railwayReadCapability({ RAILWAY_API_TOKEN: "configured" }, true).cliTokenAuthReady).toBe(true); - expect(railwayReadCapability({ RAILWAY_TOKEN: "configured" }, true)).toMatchObject({ - dedicatedCredentialPresent: false, - projectCredentialPresent: true, - cliTokenAuthReady: false, - }); - expect(railwayReadCapability({ RAILWAY_API_TOKEN: "configured" }, false).cliTokenAuthReady).toBe(false); - }); - it("parses MCP transport metadata without query or environment values", () => { const secret = "never-print-mcp-value"; const metadata = parseMcpServerMetadata( @@ -335,10 +325,10 @@ describe("Codex Cloud environment contract", () => { "Supabase MCP must keep the production project read-only.", ); expect(validateMcpConfiguration(valid.replace('"supabase":', '"unexpected":{},"supabase":'))).toContain( - "Cloud MCP configuration must contain only Railway and Supabase.", + "Desktop/CLI .mcp.json must contain only Railway and Supabase.", ); expect(validateMcpConfiguration(valid.replace('"railway":{"type"', '"railway":{"headers":{},"type"'))).toContain( - "railway MCP must use hosted OAuth without embedded environment variables or headers.", + "railway MCP must use OAuth without embedded environment variables or headers.", ); expect(validateMcpConfiguration(valid.replace("&read_only=true", "&read_only=true&token=forbidden"))).toContain( "Supabase MCP must not include additional query parameters.", @@ -352,7 +342,7 @@ describe("Codex Cloud environment contract", () => { const tracked = readFileSync(new URL("../.codex/config.toml", import.meta.url), "utf8"); expect(validateCodexProjectMcpConfiguration(tracked)).toEqual([]); expect(validateCodexProjectMcpConfiguration(tracked.replaceAll("enabled = false", "enabled = true"))).toContain( - `.codex/config.toml figma_cloud must set enabled = false (host/connected layers opt in).`, + `.codex/config.toml figma_cloud must set enabled = false (trusted Desktop/CLI operators opt in).`, ); expect( validateCodexProjectMcpConfiguration( @@ -483,6 +473,8 @@ describe("Codex Cloud environment contract", () => { expect(setup).toContain("diagnose-codex-cloud.mjs"); expect(setup).toContain("trap diagnose_setup_failure ERR"); expect(setup).toContain('setup_step="python-worker-requirements"'); + expect(setup).not.toContain("@railway/cli"); + expect(setup).not.toContain('setup_step="railway-cli"'); expect(setup).toContain("--require-hashes -r worker/python/requirements-cloud.txt"); expect(setup).toContain('"$ocr_venv/bin/python" -m pip check'); expect(setup).toContain("CODEX_CLOUD_PROVISIONING=1 npm run check:codex-cloud -- --runtime"); @@ -547,11 +539,7 @@ describe("Codex Cloud environment contract", () => { expect(connected.status, connected.stderr || connected.stdout).toBe(0); const connectedProfile = readRuntimeProfile(connectedHome); const connectedConfig = readCodexConfig(connectedHome); - expect(connectedConfig).toContain("[mcp_servers.railway_connected]"); - expect(connectedConfig).toContain("[mcp_servers.supabase_connected]"); - expect(connectedConfig).toContain("features=docs%2Cdevelopment"); - expect(connectedConfig.match(/^enabled = true$/gm)).toHaveLength(2); - expect(connectedConfig).toContain('default_tools_approval_mode = "prompt"'); + expect(connectedConfig).not.toContain("[mcp_servers."); expect(connectedProfile).toContain('export CODEX_CLOUD_ACCESS_PROFILE="connected"'); expect(connectedProfile).toContain('export RAG_PROVIDER_MODE="offline"'); expect(connectedProfile).not.toContain("${RAG_PROVIDER_MODE:-auto}"); @@ -642,7 +630,7 @@ describe("Codex Cloud environment contract", () => { expect(incomplete.status).not.toBe(0); expect(incomplete.stderr).toContain("Incomplete managed shell policy block"); expect(readFileSync(incompletePath, "utf8")).toBe(incompleteConfig); - }); + }, 120_000); it("accepts a task-only HEAD only inside Codex Cloud", () => { const directory = temporaryGitRepository(); From 03367cd84fe0984f10163131da4e8d0f1dc7a692 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:47:29 +0800 Subject: [PATCH 02/10] docs: record fresh Cloud MCP acceptance blockers Document the exact connected-task evidence, persistent launcher variable, absent hosted tools, and design-only OAuth gateway fallback. --- .../cloud-connection-acceptance-2026-08-05.md | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index 20bcd30ae3..b510f7b56b 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -34,20 +34,35 @@ postinstall can fail when GitHub release downloads are unavailable. ## Acceptance status -| Surface | Required proof | Status | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Repository boundary | Setup never writes Railway/Supabase MCP servers; local templates stay secret-free | PASS: focused tests and static checker pass; Cloud setup also no longer installs Railway CLI | -| GitHub | Connector reads `BigSimmo/Database`; exact commit is published and verified | Read PASS via `mcp__codex_apps__github_get_repo`; draft publication pending | -| Railway | Exact tools callable in a fresh ChatGPT chat and fresh Codex Cloud task; read-only identity/project/service checks pass | ChatGPT PASS via `mcp__codex_apps__railway_whoami`, `railway_list_projects`, and `railway_list_services`; exact project and services `app`, `worker`, `Database` visible. Fresh Codex Cloud rerun pending after publication | -| Supabase | Existing app exposes only project-scoped read-only metadata without row queries | ChatGPT PASS via `mcp__codex_apps__supabase_list_projects`: `sjrfecxgysukkwxsowpy`, `Clinical KB Database`, `ACTIVE_HEALTHY`; no schema/table/row/log call | -| Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI PASS: exactly the five documented values and no `OPENAI_BASE_URL`; fresh raw-shell PASS pending | -| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | Pending elapsed-time validation; no token workaround added | +| Surface | Required proof | Status | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Repository boundary | Setup never writes Railway/Supabase MCP servers; local templates stay secret-free | PASS: focused tests and static checker pass; Cloud setup also no longer installs Railway CLI | +| GitHub | Connector reads `BigSimmo/Database`; exact commit is published and verified | ChatGPT PASS via `mcp__codex_apps__github_get_repo`; draft PR #1617 created through the GitHub connector at exact published head `76dfe85fa93787b3845d0bd460aa18ff753ca2ca`. Codex Cloud FAIL: no GitHub tool exposed | +| Railway | Exact tools callable in a fresh ChatGPT chat and fresh Codex Cloud task; read-only identity/project/service checks pass | ChatGPT PASS via `mcp__codex_apps__railway_whoami`, `railway_list_projects`, and `railway_list_services`; exact project and services `app`, `worker`, `Database` visible. Codex Cloud FAIL: no Railway tool exposed | +| Supabase | Existing app exposes only project-scoped read-only metadata without row queries | ChatGPT PASS via `mcp__codex_apps__supabase_list_projects`: `sjrfecxgysukkwxsowpy`, `Clinical KB Database`, `ACTIVE_HEALTHY`; no schema/table/row/log call. Codex Cloud FAIL: no Supabase tool exposed | +| Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI has exactly the five documented values and no `OPENAI_BASE_URL`, but fresh raw-shell FAIL still reports the inherited name. This is a launcher/workspace defect, not repository state | +| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | Pending elapsed-time validation; no token workaround added | The first fresh Codex Cloud acceptance attempt did not reach the agent: setup failed while `@railway/cli@5.30.4` tried to download its binary from GitHub Releases and received -`ENETUNREACH`. The repository fix removes that unnecessary CLI installation. This is evidence of -the old setup defect, not evidence that Railway is callable in Codex Cloud; the corrected branch -must be published and tested in another fresh Cloud task. +`ENETUNREACH`. The corrected fresh task at +`https://chatgpt.com/codex/cloud/tasks/task_e_6a7303bcdd988322bc979d5d2c0f946f` passed setup and ran at +exact head `76dfe85fa93787b3845d0bd460aa18ff753ca2ca`. It reported: + +- `[Codex Cloud Check] PASS: static and environment Cloud contracts match.` +- `[Runtime Check] PASS: Node runtime 24.19.0 matches required Node 24.x.` +- `[Runtime Check] PASS: npm runtime 11.17.0 matches required npm 11.x.` +- `[Codex Cloud Check] PASS: static, environment, and runtime Cloud contracts match.` +- `git.expected_base_ancestor=true` and `git.checkout_freshness=verified` for base + `9d4a28c16e189256d2e2b1fc6edfb351138837cc`. +- `[Codex Cloud Raw Env] FAIL: inherited provider variable names: OPENAI_BASE_URL` before profiles, + npm, Node, or shims. +- No callable Railway, GitHub, or Supabase tools: MCP resources/templates were empty and focused + tool discovery returned zero tools. No provider call was attempted. + +This proves the repository setup fix and also proves that repository code cannot close either +remaining hosted blocker. The environment UI and launcher disagree about `OPENAI_BASE_URL`, and +the Codex Cloud product did not project the installed ChatGPT apps into the task tool inventory. ## Prior branch and blocked-result disposition @@ -61,9 +76,35 @@ must be published and tested in another fresh Cloud task. the optional `gh` shell fallback, while `--self-test` is fully offline. `GH_AUTH_MISSING` therefore says nothing about the hosted GitHub connector, which passed independently. - The original blocked report's `OPENAI_BASE_URL` condition was removed in Codex environment - settings. `Database - connected` now contains exactly the five documented non-secret variables. - A new raw-shell probe is still required because only a new task can prove the launcher stopped - inheriting the removed value. + settings. `Database - connected` now contains exactly the five documented non-secret variables, + but a new task still inherited the variable name. Escalate this mismatch to Codex environment + support; repository scripts correctly fail closed and must not hide it. + +## Tool-only gateway fallback design + +Do not deploy this fallback unless OpenAI confirms that Codex Cloud can install the resulting app +and the user separately approves a hosting target and Railway OAuth registration. + +- **Hosting boundary:** a new isolated service outside Railway project `Database`, with its own + environment, network policy, secret store, persistence, monitoring, and public HTTPS callback. + Do not host it in either production service. The exact provider, region, URL, and data-retention + policy remain approval-required choices. +- **Identity:** one Railway authorization-code grant per ChatGPT user, using PKCE and refresh only + if Railway's registered-client requirements allow it. Store client credentials and refresh tokens + encrypted server-side; never return them to ChatGPT or place them in repository/Cloud variables. + Railway client registration, redirect URIs, supported scopes, and revocation behavior must be + confirmed from Railway before implementation. Do not invent endpoints or broader scopes. +- **Authorization:** hard-code the only allowed project ID as + `5deaad0b-675a-4c13-978e-5ca2b5b877f9`; reject every other project/workspace after resolving the + authenticated Railway identity. A workspace match alone is insufficient. +- **v1 tool surface:** expose only `whoami`, `list-projects`, `list-services`, + `list-feature-flags`, and `get-feature-flag`. Return bounded non-secret metadata. Do not expose + logs, variables, service configuration, deployments, agent tools, mutations, or generic HTTP/API + forwarding in v1. +- **Operational controls:** authenticated per-user sessions, encrypted token storage, append-only + audit events, correlation IDs, redaction, per-user/project rate limits, response-size limits, + connection and request timeouts, strict schemas, and denial-by-default for new tools. Any later + write tool requires a separate review, explicit per-call approval, and a non-production canary. Do not mark Railway accepted because it appears in workspace settings or repository files. Success requires a callable tool in both fresh hosted contexts. If direct Railway exposure or safe project From 7d485f88db391cc7e8e73c57ddbde61f532375fc Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:19:22 +0800 Subject: [PATCH 03/10] docs: define Personal Pro cloud access workarounds Document the split provider control plane and keep raw launcher leaks fail-closed. Add sanitized capability-route output and focused contract coverage. --- AGENTS.md | 13 ++-- .../cloud-connection-acceptance-2026-08-05.md | 16 +++++ docs/codex-cloud.md | 62 ++++++++++++------- docs/scripts-index.md | 38 ++++++------ scripts/check-codex-cloud-setup.mjs | 8 +++ tests/codex-cloud-setup.test.ts | 4 ++ 6 files changed, 96 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1e46bb20c4..68dbb988d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -916,10 +916,15 @@ Use `docs/codex-cloud.md` as the environment contract: `.github/workflows/authenticated-live-tests.yml` GitHub Actions workflow, its explicit dispatch confirmation, and the `Database / production` environment, never by exposing credentials to the Codex Cloud agent shell. -- Connected Cloud Railway access uses Railway's installed official app, or an Enterprise/Edu - workspace custom app for `https://mcp.railway.com`, plus browser OAuth; prove it with the callable - tool inventory and a read-only identity/project-list call. Repository setup and local MCP config - cannot activate it. +- The active hosted workspace is **Personal Pro**. Use Railway's installed official ChatGPT app + with browser OAuth and **Allow read actions**; Personal Pro does not provide the dedicated-group + RBAC or per-tool action disabling assumed by Enterprise/Edu instructions. Prove Railway with the + callable tool inventory and a read-only identity/project-list call. Repository setup and local + MCP config cannot activate it, and the Codex Cloud connector page currently offers no Railway + connector. Use the documented split control plane: Codex Cloud for code and its native GitHub + connector, ChatGPT web for Railway and project-scoped read-only Supabase. Every provider change + still requires explicit approval. Enterprise/Edu custom-app controls are a future governance + option, not the current workspace classification. CLI token auth is a separate operator capability: it requires a separately installed Railway CLI and a dedicated `RAILWAY_API_TOKEN`, and must never substitute `RAILWAY_TOKEN` or expose either token to an diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index b510f7b56b..8f15bd0fa8 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -19,6 +19,10 @@ postinstall can fail when GitHub release downloads are unavailable. If Enterprise/Edu governance is required, an admin must enable Developer Mode and either govern the official app or create `Railway — Database` at `https://mcp.railway.com`, run Scan Tools, restrict it to a dedicated group, and disable write tools individually. +- A current Codex settings review independently confirmed the **PRO** classification. Its connector + page shows GitHub connected to `BigSimmo` and offers GitHub, Slack, and Linear; it provides no + Railway or Supabase connector control. This is a product-surface limit, not a repository setup + failure. - The installed app exposed these read actions: `Fetch-docs`, `Get-feature-flag`, `Get-logs`, `Get-service-config`, `Get-service-metrics`, `Get-status`, `List-deployments`, `List-domains`, `List-feature-flags`, `List-projects`, `List-services`, `List-variables`, `List-workspaces`, @@ -43,6 +47,18 @@ postinstall can fail when GitHub release downloads are unavailable. | Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI has exactly the five documented values and no `OPENAI_BASE_URL`, but fresh raw-shell FAIL still reports the inherited name. This is a launcher/workspace defect, not repository state | | OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | Pending elapsed-time validation; no token workaround added | +## Personal Pro operating workarounds + +| Blocker | Safe workaround applied | Functional boundary | +| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| No Personal Pro group RBAC or per-tool disabling | Railway app policy is **Allow read actions**; every change remains approval-gated | Maximum available Pro control; not equivalent to Enterprise/Edu RBAC | +| Railway and Supabase absent from Codex Cloud connectors | Use a split control plane: Codex Cloud for repository/GitHub work; ChatGPT web for official Railway OAuth and project-scoped read-only Supabase | Providers remain usable without copying tokens; a single Codex Cloud task still cannot call them | +| Raw `OPENAI_BASE_URL` injected although absent in environment UI | Keep the name-only raw probe fail-closed, then use the generated profile, Codex shell policy, and `node`/`npm`/`npx` shims that remove provider variables before ordinary work | Normal repository commands are sanitized and functional; the raw parent-process defect remains visible for OpenAI support | +| Railway refresh behavior not yet proven after one hour | Use normal OAuth reauthentication if a later read returns an authorization error; never add a static or shared token | Safe continuity workaround until refresh durability is observed | + +This operating mode favors maximum safe functionality on Personal Pro. It does not relabel the +Codex Cloud provider-tool acceptance failure as success. + The first fresh Codex Cloud acceptance attempt did not reach the agent: setup failed while `@railway/cli@5.30.4` tried to download its binary from GitHub Releases and received `ENETUNREACH`. The corrected fresh task at diff --git a/docs/codex-cloud.md b/docs/codex-cloud.md index c8e9372cab..007c3cec31 100644 --- a/docs/codex-cloud.md +++ b/docs/codex-cloud.md @@ -255,8 +255,11 @@ bash --noprofile --norc scripts/check-codex-cloud-raw-env.sh The probe checks the complete provider-variable inventory and prints names only. A failure is a launcher/environment defect; remove the variable in host environment settings and start another -fresh task. Passing only after sourcing the profile or using a command shim does not close the -raw-environment boundary. +fresh task. If Personal Pro still injects `OPENAI_BASE_URL` after the environment UI no longer +contains it, preserve the failing name-only output for OpenAI support and continue provider-free +work only through the generated profile and command shims, followed by a passing +`npm run check:codex-cloud`. This keeps normal repository commands functional without treating the +sanitized child shell as proof that the raw-parent boundary passed. `npm run check:production-readiness` remains useful in the offline profile for local safeguards. Missing Supabase/OpenAI agent-phase credentials are reported as a provider capability gap and do @@ -267,17 +270,16 @@ in the offline profile. ## Provider acceptance Provider access is verified separately because a generic bootstrap must not make paid or -production-like calls. For a connected environment, name each provider, use a read-only or -minimal no-op endpoint, confirm the intended account/project by non-secret metadata, and -report cost or mutation risk before any write. Prefer Railway's installed official app and complete -browser OAuth without static tokens or headers. If the official app is unavailable, an -Enterprise/Edu admin can enable Developer Mode and create a workspace-managed custom app named -`Railway — Database` at Railway's official `https://mcp.railway.com` endpoint, then run Scan Tools -and complete OAuth. Authorize only workspace `bigsimmo's Projects` and project `Database` -(`5deaad0b-675a-4c13-978e-5ca2b5b877f9`) where Railway offers that choice, restart the MCP client -after consent, and reduce identity/status results to non-secret account, project, workspace, -environment, and service metadata. Railway's remote MCP does not accept project tokens; install -Railway CLI separately only for explicitly approved local/operator workflows. +production-like calls. The active hosted workspace is **Personal Pro**. It does not have the +dedicated-group RBAC or per-tool action disabling assumed by Enterprise/Edu instructions. Use +Railway's installed official ChatGPT app, complete browser OAuth without static tokens or headers, +set the global app policy to **Allow read actions**, and leave changes approval-gated. Authorize +only workspace `bigsimmo's Projects` and project `Database` +(`5deaad0b-675a-4c13-978e-5ca2b5b877f9`) where Railway offers that choice. Reduce read results to +non-secret account, project, workspace, environment, and service metadata. Railway's remote MCP +does not accept project tokens; install Railway CLI separately only for explicitly approved +local/operator workflows. Enterprise/Edu custom-app controls are an optional future governance +upgrade, not the current operating target. The Supabase MCP entry is scoped to production project `sjrfecxgysukkwxsowpy`, forces `read_only=true`, and exposes only documentation/development metadata tools. The database and @@ -298,6 +300,23 @@ prove hosted Cloud availability. Context7 / library-docs MCP is Cursor-side (`.cursor/mcp.json` or a host-injected connector), not part of this Codex Cloud Railway + Supabase allowlist. +### Personal Pro split control plane + +Personal Pro currently exposes GitHub, Slack, and Linear on the Codex connector settings page; it +does not expose Railway or Supabase there. Use the smallest functional split instead of copying +credentials into Cloud: + +- **Codex Cloud:** repository work, offline checks, and GitHub reads/publication through the native + GitHub connector or Cloud PR controls. +- **ChatGPT web:** Railway through the official OAuth app and Supabase through the pinned + project-scoped read-only app. Keep Railway on **Allow read actions** and ask before every change. +- **Desktop/CLI:** opt-in local MCP from `.codex/config.toml`, followed by + `codex mcp login railway`; this is a local operator fallback, never hosted proof. + +The repository checker prints these routes as sanitized `provider_route.*` lines. They describe +where a capability is allowed, not proof that a host installed or authenticated it. A fresh task +must still establish the callable inventory. + Production Supabase stays project-scoped and `read_only=true`, with `default_tools_approval_mode = "prompt"` so every production metadata/read call requires confirmation. Do not use unrestricted SQL or query clinical rows. Railway, Figma, and Sentry @@ -325,10 +344,10 @@ copying credentials into the checkout. Do not add provider keys, database URLs, service-role credentials, test-user credentials, or `ALLOW_PROVIDER_TESTS`. Connected setup writes only the managed shell-environment policy; it never registers hosted MCP apps or writes OAuth tokens. -2. **Grant the host integrations.** Install Railway's official app and complete Railway OAuth. If it - is unavailable, an Enterprise/Edu workspace admin enables Developer Mode, creates - `Railway — Database` with `https://mcp.railway.com`, runs Scan Tools, and publishes access only - to a dedicated RBAC group containing the user. Authorize the Codex GitHub connector for +2. **Grant the host integrations.** In the Personal Pro workspace, install Railway's official + ChatGPT app, complete Railway OAuth, select **Allow read actions**, and keep all changes subject + to explicit approval. Personal Pro has no dedicated-group RBAC or per-tool disabling, so do not + claim those controls. Authorize the Codex GitHub connector for `BigSimmo/Database` with repository write access. Complete Railway OAuth only for workspace `bigsimmo's Projects` and project `Database` (`5deaad0b-675a-4c13-978e-5ca2b5b877f9`). Complete Supabase OAuth only for the organization containing `Clinical KB Database`; retain project ref @@ -353,11 +372,10 @@ copying credentials into the checkout. tokens. For GitHub, read repository metadata and confirm `BigSimmo/Database` plus the intended identity. For Railway, read workspace/project/service metadata and confirm the IDs above without triggering a deployment. Record the exact Railway inventory. Set the app to allow reads and ask - before changes. In an Enterprise/Edu workspace, also allow `whoami`, `list-projects`, - `list-services`, `list-feature-flags`, and `get-feature-flag`; disable `create-project`, - `set-feature-flag`, `delete-feature-flag`, `redeploy`, `accept-deploy`, and `railway-agent`, with - newly discovered actions disabled by default. Personal workspaces may expose only the global - read-versus-change approval control; that does not prove tool-level RBAC. For Supabase, read + before changes. The Personal Pro global read-versus-change control does not provide tool-level + RBAC. If the account later moves to Enterprise/Edu, an admin may additionally allow only + `whoami`, `list-projects`, `list-services`, `list-feature-flags`, and `get-feature-flag`, while + disabling write/agent tools and newly discovered actions by default. For Supabase, read project/schema metadata and confirm the pinned ref without querying clinical row contents. Report only non-secret identity and status metadata. OpenAI has no generic connected-profile credential: leave `RAG_PROVIDER_MODE=offline` until a diff --git a/docs/scripts-index.md b/docs/scripts-index.md index 83f0b6f4d3..afe20c0115 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -16,25 +16,25 @@ migration has shipped (see `docs/maturity-backlog-workorders.md` L1). ## Runner & guard infrastructure [infra] -| Script | Role | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | -| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | -| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | -| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | -| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | -| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized acceptance, an intentional provider-backed GitHub shell preflight (`--self-test` is offline), and safe credential-free `origin` repair | -| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | -| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | -| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | -| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | -| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | -| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | -| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | -| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | -| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | -| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | -| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | +| Script | Role | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | +| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | +| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | +| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | +| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | +| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized Personal Pro capability/route acceptance, an intentional provider-backed GitHub shell preflight (`--self-test` is offline), and safe credential-free `origin` repair | +| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | +| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | +| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | +| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | +| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | +| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | +| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | +| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | +| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | +| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | +| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | ## Verification gates [live] diff --git a/scripts/check-codex-cloud-setup.mjs b/scripts/check-codex-cloud-setup.mjs index ee90526d6a..12d67db238 100644 --- a/scripts/check-codex-cloud-setup.mjs +++ b/scripts/check-codex-cloud-setup.mjs @@ -19,6 +19,8 @@ export const expectedCloudCliVersions = Object.freeze({ codex: "0.146.0", }); +export const expectedHostedWorkspaceClass = "personal-pro"; + export const expectedMcpConfiguration = Object.freeze({ // Canonical form matches `.mcp.json` (no trailing slash). railwayUrl: "https://mcp.railway.com", @@ -404,6 +406,7 @@ export function sanitizedCloudCapabilityLines(env = process.env, options = {}) { const mcpServers = options.mcpServers ?? parseMcpServerMetadata(read(".mcp.json")); const checkout = options.checkout ?? gitCheckoutFreshness(repoRoot, env); const lines = [ + `hosted_workspace.class=${expectedHostedWorkspaceClass}`, `CODEX_CLOUD=${approvedModeValue(env.CODEX_CLOUD, ["1"])}`, `CODEX_CLOUD_ACCESS_PROFILE=${approvedModeValue(env.CODEX_CLOUD_ACCESS_PROFILE ?? "offline", ["offline", "connected"])}`, `RAG_PROVIDER_MODE=${approvedModeValue(env.RAG_PROVIDER_MODE, ["offline"])}`, @@ -412,6 +415,9 @@ export function sanitizedCloudCapabilityLines(env = process.env, options = {}) { ]; for (const name of providerCredentialVariables) lines.push(`${name}.present=${Boolean(env[name])}`); lines.push("hosted_app.inventory=external-unverified-until-fresh-task"); + lines.push("provider_route.github=codex-native-connector"); + lines.push("provider_route.railway=chatgpt-official-app"); + lines.push("provider_route.supabase=chatgpt-project-scoped-read-only-app"); lines.push(`codex.cli_available=${codexCliAvailable}`); lines.push(pythonWorkerVersionLine(env.CODEX_CLOUD_OCR_PYTHON)); lines.push(`git.origin_configured=${origin.configured}`); @@ -762,6 +768,8 @@ export function validateCodexCloudSetup() { requireMatch(errors, guide, /CODEX_CLOUD_ACCESS_PROFILE=connected/, "The guide must document connected access."); requireMatch(errors, guide, /CODEX_CLOUD_GITHUB_PAT/, "The guide must document the narrowly scoped PAT exception."); requireMatch(errors, guide, /GitHub connector/, "The guide must document GitHub connector access."); + requireMatch(errors, guide, /Personal Pro/, "The guide must identify the active Personal Pro workspace."); + requireMatch(errors, guide, /split control plane/, "The guide must document the Personal Pro provider workaround."); try { parseMcpServerMetadata(mcp); } catch (error) { diff --git a/tests/codex-cloud-setup.test.ts b/tests/codex-cloud-setup.test.ts index 01d59e4582..6ec950e9b1 100644 --- a/tests/codex-cloud-setup.test.ts +++ b/tests/codex-cloud-setup.test.ts @@ -246,8 +246,12 @@ describe("Codex Cloud environment contract", () => { }, ); const report = lines.join("\n"); + expect(report).toContain("hosted_workspace.class=personal-pro"); expect(report).toContain("OPENAI_API_KEY.present=true"); expect(report).toContain("hosted_app.inventory=external-unverified-until-fresh-task"); + expect(report).toContain("provider_route.github=codex-native-connector"); + expect(report).toContain("provider_route.railway=chatgpt-official-app"); + expect(report).toContain("provider_route.supabase=chatgpt-project-scoped-read-only-app"); expect(report).toContain( "desktop_cli_mcp.template=railway type=http command=none endpoint=https://mcp.railway.com/", ); From 4be20b17c293bbdd5afe0cc81df1d7ab9167bac5 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:27:53 +0800 Subject: [PATCH 04/10] docs: record fresh Cloud acceptance evidence --- .../cloud-connection-acceptance-2026-08-05.md | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index 8f15bd0fa8..2ffa7eb872 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -32,20 +32,21 @@ postinstall can fail when GitHub release downloads are unavailable. `Railway-agent`, `Redeploy`, `Set-feature-flag`, `Set-variables`, and `Update-service`. None was invoked. - Railway OAuth metadata advertises `openid`, `profile`, `email`, `offline_access`, and - `workspace:member`. Confirm the scanned consent requests `offline_access`, then repeat a - read-only call from a new task after the one-hour access-token lifetime. If ChatGPT receives no - refresh token, record reauthentication as required; never introduce a static-token workaround. + `workspace:member`. An elapsed-time follow-up from a new ChatGPT Work task displayed **Your + Railway connection has expired** before `whoami` could run and offered the normal **Reconnect** + flow. Treat reauthentication as required for this Personal Pro app path; never introduce a + static-token workaround. ## Acceptance status -| Surface | Required proof | Status | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Repository boundary | Setup never writes Railway/Supabase MCP servers; local templates stay secret-free | PASS: focused tests and static checker pass; Cloud setup also no longer installs Railway CLI | -| GitHub | Connector reads `BigSimmo/Database`; exact commit is published and verified | ChatGPT PASS via `mcp__codex_apps__github_get_repo`; draft PR #1617 created through the GitHub connector at exact published head `76dfe85fa93787b3845d0bd460aa18ff753ca2ca`. Codex Cloud FAIL: no GitHub tool exposed | -| Railway | Exact tools callable in a fresh ChatGPT chat and fresh Codex Cloud task; read-only identity/project/service checks pass | ChatGPT PASS via `mcp__codex_apps__railway_whoami`, `railway_list_projects`, and `railway_list_services`; exact project and services `app`, `worker`, `Database` visible. Codex Cloud FAIL: no Railway tool exposed | -| Supabase | Existing app exposes only project-scoped read-only metadata without row queries | ChatGPT PASS via `mcp__codex_apps__supabase_list_projects`: `sjrfecxgysukkwxsowpy`, `Clinical KB Database`, `ACTIVE_HEALTHY`; no schema/table/row/log call. Codex Cloud FAIL: no Supabase tool exposed | -| Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI has exactly the five documented values and no `OPENAI_BASE_URL`, but fresh raw-shell FAIL still reports the inherited name. This is a launcher/workspace defect, not repository state | -| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | Pending elapsed-time validation; no token workaround added | +| Surface | Required proof | Status | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Repository boundary | Setup never writes Railway/Supabase MCP servers; local templates stay secret-free | PASS: focused tests and static checker pass; Cloud setup also no longer installs Railway CLI | +| GitHub | Connector reads `BigSimmo/Database`; exact commit is published and verified | ChatGPT PASS via `mcp__codex_apps__github_get_repo`; draft PR #1617 was published through the GitHub connector. Fresh Codex Cloud FAIL: no GitHub tool exposed at tested head `7d485f88db391cc7e8e73c57ddbde61f532375fc` | +| Railway | Exact tools callable in a fresh ChatGPT chat and fresh Codex Cloud task; read-only identity/project/service checks pass | ChatGPT PASS via `railway_whoami`, `railway_get_status`, `railway_list_projects`, and `railway_list_services`; project `Database` and services `Database` and `worker` were visible with `SUCCESS` status. Fresh Codex Cloud FAIL: no Railway tool exposed | +| Supabase | Existing app exposes only project-scoped read-only metadata without row queries | ChatGPT PASS via `mcp__codex_apps__supabase_list_projects`: `sjrfecxgysukkwxsowpy`, `Clinical KB Database`, `ACTIVE_HEALTHY`; no schema/table/row/log call. Codex Cloud FAIL: no Supabase tool exposed | +| Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI has exactly the five documented values and no `OPENAI_BASE_URL`, but fresh raw-shell FAIL still reports the inherited name. This is a launcher/workspace defect, not repository state | +| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | REAUTH REQUIRED: the elapsed-time task reported that the Railway connection had expired and opened Railway's normal login/OAuth flow; no token workaround was added | ## Personal Pro operating workarounds @@ -54,7 +55,7 @@ postinstall can fail when GitHub release downloads are unavailable. | No Personal Pro group RBAC or per-tool disabling | Railway app policy is **Allow read actions**; every change remains approval-gated | Maximum available Pro control; not equivalent to Enterprise/Edu RBAC | | Railway and Supabase absent from Codex Cloud connectors | Use a split control plane: Codex Cloud for repository/GitHub work; ChatGPT web for official Railway OAuth and project-scoped read-only Supabase | Providers remain usable without copying tokens; a single Codex Cloud task still cannot call them | | Raw `OPENAI_BASE_URL` injected although absent in environment UI | Keep the name-only raw probe fail-closed, then use the generated profile, Codex shell policy, and `node`/`npm`/`npx` shims that remove provider variables before ordinary work | Normal repository commands are sanitized and functional; the raw parent-process defect remains visible for OpenAI support | -| Railway refresh behavior not yet proven after one hour | Use normal OAuth reauthentication if a later read returns an authorization error; never add a static or shared token | Safe continuity workaround until refresh durability is observed | +| Railway OAuth expires instead of refreshing in this app path | Use the app's normal **Reconnect** flow when prompted; never add a static or shared token | Safe continuity workaround; Railway login/consent remains user-controlled | This operating mode favors maximum safe functionality on Personal Pro. It does not relabel the Codex Cloud provider-tool acceptance failure as success. @@ -76,6 +77,20 @@ exact head `76dfe85fa93787b3845d0bd460aa18ff753ca2ca`. It reported: - No callable Railway, GitHub, or Supabase tools: MCP resources/templates were empty and focused tool discovery returned zero tools. No provider call was attempted. +A second, independently provisioned environment-attached task at +`https://chatgpt.com/codex/cloud/tasks/task_e_6a7313d1364483228b7642cb942e674c` repeated acceptance at +exact head `7d485f88db391cc7e8e73c57ddbde61f532375fc`. It passed locked installation, static and runtime +Cloud contracts, Node `24.19.0`, npm `11.17.0`, installed-lock parity, and expected-base ancestry and +freshness for `9d4a28c16e189256d2e2b1fc6edfb351138837cc`. The raw probe again failed only on the inherited +name `OPENAI_BASE_URL`, and the task again exposed no callable Railway, GitHub, or Supabase tools. + +A separate fresh ChatGPT Work task did expose the hosted apps and completed only safe metadata +reads: GitHub authenticated as `BigSimmo`; Supabase project `Clinical KB Database` +(`sjrfecxgysukkwxsowpy`) was `ACTIVE_HEALTHY`; Railway authenticated as `bigsimmo`, project +`Database` was visible, and services `Database` and `worker` were `SUCCESS`. No logs, variables, +SQL, row contents, or provider writes were requested. This validates the Personal Pro split-control +plane workaround, not single-task Codex Cloud provider acceptance. + This proves the repository setup fix and also proves that repository code cannot close either remaining hosted blocker. The environment UI and launcher disagree about `OPENAI_BASE_URL`, and the Codex Cloud product did not project the installed ChatGPT apps into the task tool inventory. From 7af5920e5fd17b8bf93be60be92d7804f5ee64c7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 11:53:28 +0000 Subject: [PATCH 05/10] fix(cloud): harden GitHub shell preflight and Cloud setup contracts Require an explicit opt-in before live gh API calls, label Personal Pro as a documented assumption, reject any non-comment MCP generation in Cloud setup, and resolve the entry-point path the same way as other scripts. Co-authored-by: BigSimmo --- .codex/config.toml | 2 + .../cloud-connection-acceptance-2026-08-05.md | 5 ++- docs/codex-review-protocol.md | 8 ++-- docs/scripts-index.md | 38 +++++++++---------- scripts/check-codex-cloud-setup.mjs | 11 +++++- scripts/check-github-shell-access.mjs | 34 ++++++++++++++++- tests/codex-cloud-setup.test.ts | 2 +- 7 files changed, 72 insertions(+), 28 deletions(-) diff --git a/.codex/config.toml b/.codex/config.toml index cb5b32df4d..5ea5c115f4 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -21,6 +21,8 @@ url = "https://mcp.supabase.com/mcp?project_ref=sjrfecxgysukkwxsowpy&read_only=t enabled = false default_tools_approval_mode = "prompt" +# Named `railway` (not `railway_cloud`) so `codex mcp login railway` matches the +# entry name. Other providers keep the `_cloud` suffix; this asymmetry is intentional. [mcp_servers.railway] url = "https://mcp.railway.com" enabled = false diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index 2ffa7eb872..f215da299c 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -104,8 +104,9 @@ the Codex Cloud product did not project the installed ChatGPT apps into the task merge, close, or rewrite it. - Connector commit search did not find the Cloud-only `fe31128` GitHub preflight commit. The bounded behavior was recreated as `check:github-shell-access`: its normal mode intentionally checks only - the optional `gh` shell fallback, while `--self-test` is fully offline. `GH_AUTH_MISSING` therefore - says nothing about the hosted GitHub connector, which passed independently. + the optional `gh` shell fallback and fails closed unless `--allow-provider` or + `ALLOW_GITHUB_SHELL_ACCESS=true` is set, while `--self-test` is fully offline. `GH_AUTH_MISSING` + therefore says nothing about the hosted GitHub connector, which passed independently. - The original blocked report's `OPENAI_BASE_URL` condition was removed in Codex environment settings. `Database - connected` now contains exactly the five documented non-secret variables, but a new task still inherited the variable name. Escalate this mismatch to Codex environment diff --git a/docs/codex-review-protocol.md b/docs/codex-review-protocol.md index d753902c8f..b1b3537efd 100644 --- a/docs/codex-review-protocol.md +++ b/docs/codex-review-protocol.md @@ -25,9 +25,11 @@ Use this protocol for every Codex review, audit, bug hunt, PR review, release-re ## GitHub access routing For an explicitly authorized hosted task, use the GitHub connector or native Cloud controls first. -Their permissions are independent of `gh` in the agent shell. Run `npm run check:github-shell-access` -only before an intentional GitHub CLI fallback; `GH_AUTH_MISSING` means shell authentication is -absent, not that the hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. +Their permissions are independent of `gh` in the agent shell. Run +`npm run check:github-shell-access -- --allow-provider` (or `ALLOW_GITHUB_SHELL_ACCESS=true`) only +before an intentional GitHub CLI fallback; the script fails closed without that opt-in, and +`--self-test` is offline. `GH_AUTH_MISSING` means shell authentication is absent, not that the +hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. ## Mutation Rules diff --git a/docs/scripts-index.md b/docs/scripts-index.md index afe20c0115..94e4e322cd 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -16,25 +16,25 @@ migration has shipped (see `docs/maturity-backlog-workorders.md` L1). ## Runner & guard infrastructure [infra] -| Script | Role | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | -| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | -| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | -| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | -| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | -| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized Personal Pro capability/route acceptance, an intentional provider-backed GitHub shell preflight (`--self-test` is offline), and safe credential-free `origin` repair | -| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | -| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | -| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | -| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | -| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | -| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | -| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | -| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | -| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | -| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | -| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | +| Script | Role | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | +| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | +| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | +| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | +| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | +| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized Personal Pro capability/route acceptance, an intentional provider-backed GitHub shell preflight (fails closed unless `--allow-provider` or `ALLOW_GITHUB_SHELL_ACCESS=true`; `--self-test` is offline), and safe credential-free `origin` repair | +| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | +| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | +| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | +| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | +| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | +| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | +| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | +| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | +| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | +| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | +| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | ## Verification gates [live] diff --git a/scripts/check-codex-cloud-setup.mjs b/scripts/check-codex-cloud-setup.mjs index 12d67db238..903bb73a19 100644 --- a/scripts/check-codex-cloud-setup.mjs +++ b/scripts/check-codex-cloud-setup.mjs @@ -406,7 +406,7 @@ export function sanitizedCloudCapabilityLines(env = process.env, options = {}) { const mcpServers = options.mcpServers ?? parseMcpServerMetadata(read(".mcp.json")); const checkout = options.checkout ?? gitCheckoutFreshness(repoRoot, env); const lines = [ - `hosted_workspace.class=${expectedHostedWorkspaceClass}`, + `hosted_workspace.class_documented=${expectedHostedWorkspaceClass}`, `CODEX_CLOUD=${approvedModeValue(env.CODEX_CLOUD, ["1"])}`, `CODEX_CLOUD_ACCESS_PROFILE=${approvedModeValue(env.CODEX_CLOUD_ACCESS_PROFILE ?? "offline", ["offline", "connected"])}`, `RAG_PROVIDER_MODE=${approvedModeValue(env.RAG_PROVIDER_MODE, ["offline"])}`, @@ -668,7 +668,14 @@ export function validateCodexCloudSetup() { for (const name of providerCredentialVariables) { if (!setup.includes(name)) errors.push(`Cloud setup must handle provider environment variable ${name}.`); } - if (/printf[^\n]*mcp_servers\./.test(setup)) { + // Reject any executable (non-comment) reference that would write MCP tables. + // Comments may mention mcp_servers as preserved host settings; printf/echo/cat/tee + // generations must not. + const setupWithoutFullLineComments = setup + .split("\n") + .filter((line) => !/^\s*#/.test(line)) + .join("\n"); + if (setupWithoutFullLineComments.includes("mcp_servers.")) { errors.push("Cloud setup must not generate MCP registrations; hosted apps are external to the repository."); } if (setup.includes("@railway/cli") || setup.includes('setup_step="railway-cli"')) { diff --git a/scripts/check-github-shell-access.mjs b/scripts/check-github-shell-access.mjs index b7c8836c0a..7e7151f2af 100644 --- a/scripts/check-github-shell-access.mjs +++ b/scripts/check-github-shell-access.mjs @@ -1,9 +1,12 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; +import path from "node:path"; import { fileURLToPath } from "node:url"; const repository = "BigSimmo/Database"; +const allowProviderFlag = "--allow-provider"; +const allowProviderEnv = "ALLOW_GITHUB_SHELL_ACCESS"; function shellGh(command, args) { return spawnSync(command, args, { encoding: "utf8", shell: false }); @@ -12,6 +15,10 @@ function shellGh(command, args) { /** * Check only the optional GitHub CLI fallback available to the current shell. * Hosted GitHub connector/native Cloud access is a separate capability. + * + * Live `gh` API calls require an explicit opt-in (`--allow-provider` or + * `ALLOW_GITHUB_SHELL_ACCESS=true`) so the provider confirmation boundary is + * self-enforcing rather than documentation-only. `--self-test` stays offline. */ export function githubShellAccess(run = shellGh) { if (run("gh", ["--version"]).status !== 0) { @@ -31,6 +38,10 @@ export function githubShellAccess(run = shellGh) { return { ok: true, outcome: "GH_SHELL_ACCESS_READY" }; } +export function providerAccessAuthorized(argv = process.argv, env = process.env) { + return argv.includes(allowProviderFlag) || env[allowProviderEnv] === "true"; +} + function fakeRun(statuses) { let index = 0; return () => ({ status: statuses[index++] ?? 0, stdout: "", stderr: "" }); @@ -50,6 +61,15 @@ function selfTest() { throw new Error(`expected ${expected}, received ${actual.outcome}`); } } + if (providerAccessAuthorized(["node", "script.mjs"], {})) { + throw new Error("provider access must fail closed without an opt-in"); + } + if (!providerAccessAuthorized(["node", "script.mjs", allowProviderFlag], {})) { + throw new Error("provider access must accept --allow-provider"); + } + if (!providerAccessAuthorized(["node", "script.mjs"], { [allowProviderEnv]: "true" })) { + throw new Error(`provider access must accept ${allowProviderEnv}=true`); + } console.log("GITHUB_SHELL_ACCESS_SELF_TEST=PASS"); } @@ -58,10 +78,22 @@ function main() { selfTest(); return; } + if (!providerAccessAuthorized()) { + console.error( + [ + "Refusing live GitHub API calls without confirmation.", + `Re-run with ${allowProviderFlag} or ${allowProviderEnv}=true only after explicit provider approval.`, + "Offline proof: npm run check:github-shell-access -- --self-test", + ].join("\n"), + ); + process.exitCode = 1; + return; + } const result = githubShellAccess(); console.log(`GITHUB_SHELL_ACCESS=${result.outcome}`); console.log("GITHUB_HOSTED_CONNECTOR=SEPARATE_UNVERIFIED_CAPABILITY"); if (!result.ok) process.exitCode = 1; } -if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) main(); +const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; +if (invokedPath === fileURLToPath(import.meta.url)) main(); diff --git a/tests/codex-cloud-setup.test.ts b/tests/codex-cloud-setup.test.ts index 6ec950e9b1..32886698fc 100644 --- a/tests/codex-cloud-setup.test.ts +++ b/tests/codex-cloud-setup.test.ts @@ -246,7 +246,7 @@ describe("Codex Cloud environment contract", () => { }, ); const report = lines.join("\n"); - expect(report).toContain("hosted_workspace.class=personal-pro"); + expect(report).toContain("hosted_workspace.class_documented=personal-pro"); expect(report).toContain("OPENAI_API_KEY.present=true"); expect(report).toContain("hosted_app.inventory=external-unverified-until-fresh-task"); expect(report).toContain("provider_route.github=codex-native-connector"); From bf7684a77ef5fa5d53a1e7891cb7f446d4741e4a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 12:15:18 +0000 Subject: [PATCH 06/10] fix(cloud): close remaining hosted-boundary review findings Name-scope the raw-env OPENAI_BASE_URL allowance in the probe, make check:github-shell-access offline by default with a :live opt-in, align Railway MCP docs/indexing, and harden the Cloud setup/static contracts. Co-authored-by: BigSimmo --- AGENTS.md | 23 ++++++++------- docs/README.md | 1 + docs/agents-guide.md | 14 +++++----- docs/codex-cloud.md | 21 +++++++++----- docs/codex-review-protocol.md | 9 +++--- docs/scripts-index.md | 40 +++++++++++++-------------- package.json | 3 +- scripts/check-codex-cloud-raw-env.sh | 40 +++++++++++++++++++++++++-- scripts/check-codex-cloud-setup.mjs | 35 +++++++++++++++++++---- scripts/check-github-shell-access.mjs | 20 ++++++++++---- tests/codex-cloud-setup.test.ts | 31 +++++++++++++++++++++ 11 files changed, 175 insertions(+), 62 deletions(-) mode change 100644 => 100755 scripts/check-codex-cloud-raw-env.sh diff --git a/AGENTS.md b/AGENTS.md index 68dbb988d1..6604600c25 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -894,16 +894,19 @@ Use `docs/codex-cloud.md` as the environment contract: separately without printing secret values. - In a fresh Cloud task, run `bash scripts/check-codex-cloud-raw-env.sh` before sourcing a profile or entering a login shell. It must report only provider variable names and presence, - never values. Then run `npm run check:codex-cloud` directly; it must report the - static-and-environment PASS line. Run `npm run check:codex-cloud -- --runtime` with - `CODEX_CLOUD_EXPECTED_BASE_SHA` set to the intended merge/base commit when the checkout has - only a task HEAD. Setup and maintenance may report freshness as unverified so provisioning - remains repairable, but explicit acceptance must not pass an arbitrary HEAD. The command shims - load the generated profile for normal `node`, `npm`, and `npx` work. Also run - `npm run check:runtime` and `npm run check:installed-lock-parity` before trusting a new or - reset environment. A skipped browser install is not full browser readiness. Output is limited - to approved mode values, presence booleans, full Git commit identities, and MCP - server/command/environment-variable names; never print credential values. + never values. Treat exit `1` / `FAIL`+`STOP` as a hard stop for any unexpected inherited name. + Only exit `2` / `FAIL-KNOWN` for `OPENAI_BASE_URL` alone may use the restricted + profile-and-shim continue path; do not generalize that allowance. Then run + `npm run check:codex-cloud` directly; it must report the static-and-environment PASS line. Run + `npm run check:codex-cloud -- --runtime` with `CODEX_CLOUD_EXPECTED_BASE_SHA` set to the + intended merge/base commit when the checkout has only a task HEAD. Setup and maintenance may + report freshness as unverified so provisioning remains repairable, but explicit acceptance must + not pass an arbitrary HEAD. The command shims load the generated profile for normal `node`, + `npm`, and `npx` work. Also run `npm run check:runtime` and + `npm run check:installed-lock-parity` before trusting a new or reset environment. A skipped + browser install is not full browser readiness. Output is limited to approved mode values, + presence booleans, full Git commit identities, and MCP server/command/environment-variable + names; never print credential values. - Do not add OpenAI, Supabase, Railway, GitHub, database, or user credentials as ordinary Cloud environment variables. Codex Cloud secrets are setup-only and unavailable to the agent phase unless the platform explicitly exposes a secret to the named task phase; do not diff --git a/docs/README.md b/docs/README.md index f0fd281ec3..63bb45c5d4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -125,6 +125,7 @@ as it was on that date; supersede with a new dated document rather than editing. - [audit/audit-handover-2026-07-14.md](audit/audit-handover-2026-07-14.md) — multi-skill repository audit findings inventory - [audit/audit-remediation-plan-2026-07-14.md](audit/audit-remediation-plan-2026-07-14.md) — sequenced remediation plan for the 2026-07-14 audit, with the 2026-07-17 reconciliation - [audit/design-audit-2026-07-17.md](audit/design-audit-2026-07-17.md) — repository-wide design, accessibility, and interaction audit +- [audit/cloud-connection-acceptance-2026-08-05.md](audit/cloud-connection-acceptance-2026-08-05.md) — hosted versus local MCP boundary acceptance, Personal Pro split control plane, and remaining Cloud launcher blockers - [current-clinical-work-brief.md](current-clinical-work-brief.md) — ledger #063 product/privacy/persistence brief (decision only, no implementation) - [factsheets-reading-model-brief.md](factsheets-reading-model-brief.md) — ledger #041 reading-model decision (no second Factsheets mode) - [source-governance-refresh-worklist-2026-07-22.md](source-governance-refresh-worklist-2026-07-22.md) — ledger #022 worklist and BMJ attestation policy status diff --git a/docs/agents-guide.md b/docs/agents-guide.md index b955969d3f..a862286cf9 100644 --- a/docs/agents-guide.md +++ b/docs/agents-guide.md @@ -58,13 +58,13 @@ servers** per session (tool-schema token bloat degrades agents). Use registered MCPs before opening dashboards when the task is read-only inspection. Writes, secret rotations, and hosted mutations stay confirmation-gated per `AGENTS.md`. -| Server | Config | Use for | Do not | -| --------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| **Supabase** (read-only) | `.cursor/mcp.json` — pinned `project_ref=sjrfecxgysukkwxsowpy`, `read_only=true` | `search_docs`, advisors, read SQL, schema inspection | Print secret values; raw-edit retrieval RPCs via `execute_sql`; Auth DB connection-cap (`#011`) — **dashboard only** | -| **Railway** | Root `.mcp.json` | Deploy status, service logs, env **names**/presence | Confuse `RAILWAY_API_TOKEN` (personal) with CI `RAILWAY_TOKEN`; mutate without approval | -| **Context7** | `.cursor/mcp.json` → `https://mcp.context7.com/mcp` (+ Cursor `context7-plugin`) | Versioned docs for **Tailwind 4, Zod 4, Playwright, Vitest, React 19, `@supabase/supabase-js`** (peers; not exhaustive). Optional higher limits: set `CONTEXT7_API_KEY` (see below) | Next.js 16 — always use `node_modules/next/dist/docs/` (AGENTS.md). Do not invent App Router APIs from training data; never commit the API key | -| **Chrome DevTools** | `.cursor/mcp.json` → `npx -y chrome-devtools-mcp@1.6.0` | CLS/LCP/console/network while implementing redesigns (`#147`, `#162`–`#164`, Therapy Compass) | Don't leave it always-on with Browse + Playwright MCP (token bloat). Use for perf/debug passes | -| **GitHub Checks / Actions** | Operator approval pending | PR check visibility when `gh pr checks` returns empty totals | Bot `update-branch`; broaden scopes beyond Checks/Actions read | +| Server | Config | Use for | Do not | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| **Supabase** (read-only) | `.cursor/mcp.json` — pinned `project_ref=sjrfecxgysukkwxsowpy`, `read_only=true` | `search_docs`, advisors, read SQL, schema inspection | Print secret values; raw-edit retrieval RPCs via `execute_sql`; Auth DB connection-cap (`#011`) — **dashboard only** | +| **Railway** | Desktop/CLI: root `.mcp.json` / `.codex/config.toml` (`railway` + OAuth). Hosted ChatGPT/Codex: workspace-installed app only — repository MCP files are not a read path there | Deploy status, service logs, env **names**/presence (Desktop/CLI MCP or hosted app tools) | Treat root `.mcp.json` as hosted proof; confuse `RAILWAY_API_TOKEN` (personal) with CI `RAILWAY_TOKEN`; mutate without approval | +| **Context7** | `.cursor/mcp.json` → `https://mcp.context7.com/mcp` (+ Cursor `context7-plugin`) | Versioned docs for **Tailwind 4, Zod 4, Playwright, Vitest, React 19, `@supabase/supabase-js`** (peers; not exhaustive). Optional higher limits: set `CONTEXT7_API_KEY` (see below) | Next.js 16 — always use `node_modules/next/dist/docs/` (AGENTS.md). Do not invent App Router APIs from training data; never commit the API key | +| **Chrome DevTools** | `.cursor/mcp.json` → `npx -y chrome-devtools-mcp@1.6.0` | CLS/LCP/console/network while implementing redesigns (`#147`, `#162`–`#164`, Therapy Compass) | Don't leave it always-on with Browse + Playwright MCP (token bloat). Use for perf/debug passes | +| **GitHub Checks / Actions** | Operator approval pending | PR check visibility when `gh pr checks` returns empty totals | Bot `update-branch`; broaden scopes beyond Checks/Actions read | ### Context7 API key (optional) diff --git a/docs/codex-cloud.md b/docs/codex-cloud.md index 007c3cec31..3aeaab2767 100644 --- a/docs/codex-cloud.md +++ b/docs/codex-cloud.md @@ -253,13 +253,20 @@ sourcing any profile or invoking node/npm in a fresh task, run: bash --noprofile --norc scripts/check-codex-cloud-raw-env.sh ``` -The probe checks the complete provider-variable inventory and prints names only. A failure is a -launcher/environment defect; remove the variable in host environment settings and start another -fresh task. If Personal Pro still injects `OPENAI_BASE_URL` after the environment UI no longer -contains it, preserve the failing name-only output for OpenAI support and continue provider-free -work only through the generated profile and command shims, followed by a passing -`npm run check:codex-cloud`. This keeps normal repository commands functional without treating the -sanitized child shell as proof that the raw-parent boundary passed. +The probe checks the complete provider-variable inventory and prints names only. Outcomes are +name-scoped in the probe itself: + +- exit `0` / `PASS` — raw boundary clean +- exit `1` / `FAIL` + `STOP` — any unexpected provider name (for example + `SUPABASE_SERVICE_ROLE_KEY`); start another fresh task and do not continue +- exit `2` / `FAIL-KNOWN` + `CONTINUE-RESTRICTED` — only the documented Personal Pro launcher + defect name `OPENAI_BASE_URL` + +When the probe reports `FAIL-KNOWN` for `OPENAI_BASE_URL` alone, preserve that name-only output +for OpenAI support and continue provider-free work only through the generated profile and command +shims, followed by a passing `npm run check:codex-cloud`. Do not generalize that restricted path +to any other inherited name. A sanitized child shell is never proof that the raw-parent boundary +passed. `npm run check:production-readiness` remains useful in the offline profile for local safeguards. Missing Supabase/OpenAI agent-phase credentials are reported as a provider capability gap and do diff --git a/docs/codex-review-protocol.md b/docs/codex-review-protocol.md index b1b3537efd..6fec9ac543 100644 --- a/docs/codex-review-protocol.md +++ b/docs/codex-review-protocol.md @@ -26,10 +26,11 @@ Use this protocol for every Codex review, audit, bug hunt, PR review, release-re For an explicitly authorized hosted task, use the GitHub connector or native Cloud controls first. Their permissions are independent of `gh` in the agent shell. Run -`npm run check:github-shell-access -- --allow-provider` (or `ALLOW_GITHUB_SHELL_ACCESS=true`) only -before an intentional GitHub CLI fallback; the script fails closed without that opt-in, and -`--self-test` is offline. `GH_AUTH_MISSING` means shell authentication is absent, not that the -hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. +`npm run check:github-shell-access:live` (or `ALLOW_GITHUB_SHELL_ACCESS=true` / +`--allow-provider`) only before an intentional GitHub CLI fallback. The plain +`npm run check:github-shell-access` entry is offline `--self-test` so script sweeps cannot +accidentally make provider calls. `GH_AUTH_MISSING` means shell authentication is absent, not that +the hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. ## Mutation Rules diff --git a/docs/scripts-index.md b/docs/scripts-index.md index 94e4e322cd..be686be6d5 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -1,6 +1,6 @@ # Scripts index -Curated map of `scripts/` (214 files) and the `package.json` script surface (223 entries), +Curated map of `scripts/` (214 files) and the `package.json` script surface (224 entries), grouped by purpose. This is orientation, not an exhaustive per-file listing — the authoritative command list is `package.json`, and `npm run docs:check-scripts` verifies every `npm run ` referenced in docs resolves to a real script. `npm run docs:update` refreshes the exact counts above. @@ -16,25 +16,25 @@ migration has shipped (see `docs/maturity-backlog-workorders.md` L1). ## Runner & guard infrastructure [infra] -| Script | Role | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | -| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | -| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | -| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | -| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | -| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized Personal Pro capability/route acceptance, an intentional provider-backed GitHub shell preflight (fails closed unless `--allow-provider` or `ALLOW_GITHUB_SHELL_ACCESS=true`; `--self-test` is offline), and safe credential-free `origin` repair | -| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | -| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | -| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | -| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | -| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | -| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | -| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | -| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | -| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | -| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | -| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | +| Script | Role | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | +| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | +| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | +| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | +| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | +| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe (`FAIL-KNOWN`/`exit 2` only for documented `OPENAI_BASE_URL`; hard `STOP` otherwise), an operator-only non-Cloud PAT branch-deletion helper, sanitized Personal Pro capability/route acceptance, GitHub shell preflight (`check:github-shell-access` = offline self-test; `check:github-shell-access:live` = provider-backed opt-in), and safe credential-free `origin` repair | +| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | +| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | +| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | +| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | +| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | +| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | +| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | +| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | +| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | +| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | +| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | ## Verification gates [live] diff --git a/package.json b/package.json index 7d47ce7aa6..fdb300c4d0 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ "check:installed-lock-parity": "node scripts/check-installed-lock-parity.mjs", "check:upload-limit-parity": "node scripts/check-upload-limit-parity.mjs --self-test && node scripts/check-upload-limit-parity.mjs", "check:codex-cloud": "node scripts/check-codex-cloud-setup.mjs", - "check:github-shell-access": "node scripts/check-github-shell-access.mjs", + "check:github-shell-access": "node scripts/check-github-shell-access.mjs --self-test", + "check:github-shell-access:live": "node scripts/check-github-shell-access.mjs --allow-provider", "cloud:setup": "bash scripts/setup-codex-cloud.sh", "cloud:maintain": "bash scripts/maintain-codex-cloud.sh", "check:codex-autofix-workflow": "node scripts/check-codex-autofix-workflow.mjs", diff --git a/scripts/check-codex-cloud-raw-env.sh b/scripts/check-codex-cloud-raw-env.sh old mode 100644 new mode 100755 index cd37bbe435..23c018a23e --- a/scripts/check-codex-cloud-raw-env.sh +++ b/scripts/check-codex-cloud-raw-env.sh @@ -4,6 +4,13 @@ set -Eeuo pipefail # Run this directly with a pristine shell before sourcing profiles or invoking # node/npm shims. It reports names and presence only, never values. +# +# Exit codes: +# 0 — no provider variables inherited (raw boundary passed) +# 1 — unexpected provider variable names present; stop and start a fresh task +# 2 — only the documented Personal Pro launcher defect (`OPENAI_BASE_URL`) is +# present. The raw boundary still failed; continue only through the +# generated profile and command shims, never by treating this as a pass. provider_variables=( OPENAI_API_KEY OPENAI_ORG_ID OPENAI_PROJECT_ID OPENAI_BASE_URL NEXT_PUBLIC_SUPABASE_URL NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY NEXT_PUBLIC_SUPABASE_ANON_KEY @@ -17,6 +24,10 @@ provider_variables=( E2E_AUTH_ENABLED E2E_USER_EMAIL E2E_USER_PASSWORD ALLOW_PROVIDER_TESTS ) +# Name-scoped allowance: only this inherited name may use the restricted continue +# path. Any other provider name remains a hard stop. +known_launcher_defect_variables=(OPENAI_BASE_URL) + present=() for name in "${provider_variables[@]}"; do if [[ -n "${!name:-}" ]]; then @@ -24,9 +35,34 @@ for name in "${provider_variables[@]}"; do fi done -if (( ${#present[@]} > 0 )); then +if (( ${#present[@]} == 0 )); then + printf '[Codex Cloud Raw Env] PASS: no provider variables are inherited by the raw task shell.\n' + exit 0 +fi + +known=() +unexpected=() +for name in "${present[@]}"; do + is_known=0 + for known_name in "${known_launcher_defect_variables[@]}"; do + if [[ "$name" == "$known_name" ]]; then + is_known=1 + break + fi + done + if (( is_known )); then + known+=("$name") + else + unexpected+=("$name") + fi +done + +if (( ${#unexpected[@]} > 0 )); then printf '[Codex Cloud Raw Env] FAIL: inherited provider variable names: %s\n' "${present[*]}" >&2 + printf '[Codex Cloud Raw Env] STOP: unexpected credential-bearing names require a fresh task; do not continue.\n' >&2 exit 1 fi -printf '[Codex Cloud Raw Env] PASS: no provider variables are inherited by the raw task shell.\n' +printf '[Codex Cloud Raw Env] FAIL-KNOWN: inherited documented launcher defect names: %s\n' "${known[*]}" >&2 +printf '[Codex Cloud Raw Env] CONTINUE-RESTRICTED: use only the generated profile and command shims; raw boundary remains failed.\n' >&2 +exit 2 diff --git a/scripts/check-codex-cloud-setup.mjs b/scripts/check-codex-cloud-setup.mjs index 903bb73a19..275b7c319b 100644 --- a/scripts/check-codex-cloud-setup.mjs +++ b/scripts/check-codex-cloud-setup.mjs @@ -669,13 +669,17 @@ export function validateCodexCloudSetup() { if (!setup.includes(name)) errors.push(`Cloud setup must handle provider environment variable ${name}.`); } // Reject any executable (non-comment) reference that would write MCP tables. - // Comments may mention mcp_servers as preserved host settings; printf/echo/cat/tee - // generations must not. - const setupWithoutFullLineComments = setup + // Strip full-line comments and whitespace-prefixed trailing comments so an + // inline note cannot false-trip the guard. This remains a text-level contract; + // the generated-config behavioural test is the stronger proof. + const setupWithoutComments = setup .split("\n") - .filter((line) => !/^\s*#/.test(line)) + .map((line) => { + if (/^\s*#/.test(line)) return ""; + return line.replace(/\s+#.*$/, ""); + }) .join("\n"); - if (setupWithoutFullLineComments.includes("mcp_servers.")) { + if (/\bmcp_servers\b/.test(setupWithoutComments)) { errors.push("Cloud setup must not generate MCP registrations; hosted apps are external to the repository."); } if (setup.includes("@railway/cli") || setup.includes('setup_step="railway-cli"')) { @@ -734,6 +738,24 @@ export function validateCodexCloudSetup() { "PAT deletion helper must reject the Codex Cloud agent phase and direct operators to native publication.", ); requireMatch(errors, rawEnvironmentProbe, /never values/, "Raw Cloud environment probe must report names only."); + requireMatch( + errors, + rawEnvironmentProbe, + /known_launcher_defect_variables=\(OPENAI_BASE_URL\)/, + "Raw Cloud environment probe must name-scope the OPENAI_BASE_URL launcher-defect allowance.", + ); + requireMatch( + errors, + rawEnvironmentProbe, + /FAIL-KNOWN: inherited documented launcher defect names/, + "Raw Cloud environment probe must emit FAIL-KNOWN for the documented launcher defect.", + ); + requireMatch( + errors, + rawEnvironmentProbe, + /STOP: unexpected credential-bearing names require a fresh task/, + "Raw Cloud environment probe must hard-stop on unexpected provider names.", + ); for (const name of providerCredentialVariables) { if (!rawEnvironmentProbe.includes(name)) { errors.push(`Raw Cloud environment probe must cover provider environment variable ${name}.`); @@ -839,6 +861,9 @@ function repositoryCommand(command, args) { export async function validateCodexCloudRuntime(env = process.env) { const errors = validateCodexCloudEnvironment(env); + // Hosted Railway access is the OAuth ChatGPT/Codex app, not CLI token auth. + // Do not reintroduce `railway --version` or RAILWAY_API_TOKEN-vs-RAILWAY_TOKEN + // substitution checks here unless a future workflow restores CLI token auth. for (const error of [ commandVersion("deno", ["--version"], /^deno 2\./m), commandVersion("tesseract", ["--version"], /^tesseract \d+\./m), diff --git a/scripts/check-github-shell-access.mjs b/scripts/check-github-shell-access.mjs index 7e7151f2af..91663eea13 100644 --- a/scripts/check-github-shell-access.mjs +++ b/scripts/check-github-shell-access.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node +import { realpathSync } from "node:fs"; import { spawnSync } from "node:child_process"; -import path from "node:path"; import { fileURLToPath } from "node:url"; const repository = "BigSimmo/Database"; @@ -18,7 +18,9 @@ function shellGh(command, args) { * * Live `gh` API calls require an explicit opt-in (`--allow-provider` or * `ALLOW_GITHUB_SHELL_ACCESS=true`) so the provider confirmation boundary is - * self-enforcing rather than documentation-only. `--self-test` stays offline. + * self-enforcing rather than documentation-only. The plain npm script runs + * `--self-test` offline; use `npm run check:github-shell-access:live` for the + * provider-backed path. */ export function githubShellAccess(run = shellGh) { if (run("gh", ["--version"]).status !== 0) { @@ -82,8 +84,8 @@ function main() { console.error( [ "Refusing live GitHub API calls without confirmation.", - `Re-run with ${allowProviderFlag} or ${allowProviderEnv}=true only after explicit provider approval.`, - "Offline proof: npm run check:github-shell-access -- --self-test", + `Re-run with npm run check:github-shell-access:live, or pass ${allowProviderFlag} / ${allowProviderEnv}=true, only after explicit provider approval.`, + "Offline proof: npm run check:github-shell-access", ].join("\n"), ); process.exitCode = 1; @@ -95,5 +97,11 @@ function main() { if (!result.ok) process.exitCode = 1; } -const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; -if (invokedPath === fileURLToPath(import.meta.url)) main(); +const invokedDirectly = (() => { + try { + return Boolean(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); + } catch { + return false; + } +})(); +if (invokedDirectly) main(); diff --git a/tests/codex-cloud-setup.test.ts b/tests/codex-cloud-setup.test.ts index 32886698fc..a273162012 100644 --- a/tests/codex-cloud-setup.test.ts +++ b/tests/codex-cloud-setup.test.ts @@ -437,9 +437,40 @@ describe("Codex Cloud environment contract", () => { }); expect(result.status).toBe(1); expect(result.stderr).toContain("OPENAI_API_KEY"); + expect(result.stderr).toContain("STOP:"); expect(result.stderr).not.toContain(secret); }); + it("name-scopes the documented OPENAI_BASE_URL launcher defect separately from unexpected leaks", () => { + const known = spawnSync(bashCommand, ["scripts/check-codex-cloud-raw-env.sh"], { + cwd: repoRoot, + encoding: "utf8", + env: { PATH: process.env.PATH, NODE_ENV: "test", OPENAI_BASE_URL: "https://example.invalid" }, + }); + expect(known.status).toBe(2); + expect(known.stderr).toContain("FAIL-KNOWN:"); + expect(known.stderr).toContain("OPENAI_BASE_URL"); + expect(known.stderr).toContain("CONTINUE-RESTRICTED:"); + expect(known.stderr).not.toContain("https://example.invalid"); + + const mixed = spawnSync(bashCommand, ["scripts/check-codex-cloud-raw-env.sh"], { + cwd: repoRoot, + encoding: "utf8", + env: { + PATH: process.env.PATH, + NODE_ENV: "test", + OPENAI_BASE_URL: "https://example.invalid", + SUPABASE_SERVICE_ROLE_KEY: "never-print-service-role", + }, + }); + expect(mixed.status).toBe(1); + expect(mixed.stderr).toContain("FAIL:"); + expect(mixed.stderr).toContain("STOP:"); + expect(mixed.stderr).toContain("SUPABASE_SERVICE_ROLE_KEY"); + expect(mixed.stderr).not.toContain("never-print-service-role"); + expect(mixed.stderr).not.toContain("CONTINUE-RESTRICTED:"); + }); + it("keeps setup and maintenance repairs guarded for repeat execution", () => { const setup = readFileSync(new URL("../scripts/setup-codex-cloud.sh", import.meta.url), "utf8"); const maintenance = readFileSync(new URL("../scripts/maintain-codex-cloud.sh", import.meta.url), "utf8"); From 5a85503ead06bd69ead7949ac59a2d1edc1445c6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 12:28:27 +0000 Subject: [PATCH 07/10] Address PR review feedback (#1617) - Honor GitHub shell-access opt-in over --self-test so live confirmation is never a silent stub - Clarify live vs offline GitHub preflight docs and acceptance notes - Warn that OPENAI_BASE_URL CONTINUE-RESTRICTED still forbids raw-parent/shim-bypass OpenAI calls - Tighten MCP text-guard residual notes and connected-setup assertions Co-authored-by: BigSimmo --- AGENTS.md | 4 ++- .../cloud-connection-acceptance-2026-08-05.md | 9 +++--- docs/codex-cloud.md | 6 ++-- docs/codex-review-protocol.md | 11 +++++--- scripts/check-codex-cloud-raw-env.sh | 1 + scripts/check-codex-cloud-setup.mjs | 12 ++++++-- scripts/check-github-shell-access.mjs | 28 +++++++++++++++---- tests/codex-cloud-setup.test.ts | 4 +++ 8 files changed, 56 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6604600c25..d242cda538 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -896,7 +896,9 @@ Use `docs/codex-cloud.md` as the environment contract: profile or entering a login shell. It must report only provider variable names and presence, never values. Treat exit `1` / `FAIL`+`STOP` as a hard stop for any unexpected inherited name. Only exit `2` / `FAIL-KNOWN` for `OPENAI_BASE_URL` alone may use the restricted - profile-and-shim continue path; do not generalize that allowance. Then run + profile-and-shim continue path; do not generalize that allowance. That name can redirect + OpenAI-bound traffic, so never invoke OpenAI clients from the raw parent or any binary that + bypasses the profile/`node`/`npm`/`npx` scrub. Then run `npm run check:codex-cloud` directly; it must report the static-and-environment PASS line. Run `npm run check:codex-cloud -- --runtime` with `CODEX_CLOUD_EXPECTED_BASE_SHA` set to the intended merge/base commit when the checkout has only a task HEAD. Setup and maintenance may diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index f215da299c..d2488bc0f6 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -103,10 +103,11 @@ the Codex Cloud product did not project the installed ChatGPT apps into the task connected Railway/Supabase MCPs. This repair is isolated from that unrelated PR and does not merge, close, or rewrite it. - Connector commit search did not find the Cloud-only `fe31128` GitHub preflight commit. The bounded - behavior was recreated as `check:github-shell-access`: its normal mode intentionally checks only - the optional `gh` shell fallback and fails closed unless `--allow-provider` or - `ALLOW_GITHUB_SHELL_ACCESS=true` is set, while `--self-test` is fully offline. `GH_AUTH_MISSING` - therefore says nothing about the hosted GitHub connector, which passed independently. + behavior was recreated as `check:github-shell-access`: the plain npm entry is offline + `--self-test`; the live `gh` shell fallback is `check:github-shell-access:live` (or a direct + script invocation with `--allow-provider` / `ALLOW_GITHUB_SHELL_ACCESS=true`, which overrides + `--self-test`). `GH_AUTH_MISSING` therefore says nothing about the hosted GitHub connector, + which passed independently. - The original blocked report's `OPENAI_BASE_URL` condition was removed in Codex environment settings. `Database - connected` now contains exactly the five documented non-secret variables, but a new task still inherited the variable name. Escalate this mismatch to Codex environment diff --git a/docs/codex-cloud.md b/docs/codex-cloud.md index 3aeaab2767..1b2996e123 100644 --- a/docs/codex-cloud.md +++ b/docs/codex-cloud.md @@ -265,8 +265,10 @@ name-scoped in the probe itself: When the probe reports `FAIL-KNOWN` for `OPENAI_BASE_URL` alone, preserve that name-only output for OpenAI support and continue provider-free work only through the generated profile and command shims, followed by a passing `npm run check:codex-cloud`. Do not generalize that restricted path -to any other inherited name. A sanitized child shell is never proof that the raw-parent boundary -passed. +to any other inherited name. An inherited `OPENAI_BASE_URL` can redirect OpenAI-bound traffic, so +never call OpenAI clients from the raw parent process or from binaries that bypass the +profile/`node`/`npm`/`npx` shim scrub. A sanitized child shell is never proof that the +raw-parent boundary passed. `npm run check:production-readiness` remains useful in the offline profile for local safeguards. Missing Supabase/OpenAI agent-phase credentials are reported as a provider capability gap and do diff --git a/docs/codex-review-protocol.md b/docs/codex-review-protocol.md index 6fec9ac543..2d71bbd8e1 100644 --- a/docs/codex-review-protocol.md +++ b/docs/codex-review-protocol.md @@ -26,11 +26,14 @@ Use this protocol for every Codex review, audit, bug hunt, PR review, release-re For an explicitly authorized hosted task, use the GitHub connector or native Cloud controls first. Their permissions are independent of `gh` in the agent shell. Run -`npm run check:github-shell-access:live` (or `ALLOW_GITHUB_SHELL_ACCESS=true` / -`--allow-provider`) only before an intentional GitHub CLI fallback. The plain +`npm run check:github-shell-access:live` (which passes `--allow-provider`; +`ALLOW_GITHUB_SHELL_ACCESS=true` or `--allow-provider` on a direct +`node scripts/check-github-shell-access.mjs` invocation authorize the same live path) +only before an intentional GitHub CLI fallback. The plain `npm run check:github-shell-access` entry is offline `--self-test` so script sweeps cannot -accidentally make provider calls. `GH_AUTH_MISSING` means shell authentication is absent, not that -the hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. +accidentally make provider calls; an explicit opt-in overrides that stub when present. +`GH_AUTH_MISSING` means shell authentication is absent, not that the hosted connector is +disconnected. Never add a PAT to an ordinary Cloud task. ## Mutation Rules diff --git a/scripts/check-codex-cloud-raw-env.sh b/scripts/check-codex-cloud-raw-env.sh index 23c018a23e..ee83d157ba 100755 --- a/scripts/check-codex-cloud-raw-env.sh +++ b/scripts/check-codex-cloud-raw-env.sh @@ -65,4 +65,5 @@ fi printf '[Codex Cloud Raw Env] FAIL-KNOWN: inherited documented launcher defect names: %s\n' "${known[*]}" >&2 printf '[Codex Cloud Raw Env] CONTINUE-RESTRICTED: use only the generated profile and command shims; raw boundary remains failed.\n' >&2 +printf '[Codex Cloud Raw Env] CONTINUE-RESTRICTED: OPENAI_BASE_URL can redirect OpenAI-bound traffic; never invoke OpenAI clients from the raw parent or any binary that bypasses the profile/shim scrub.\n' >&2 exit 2 diff --git a/scripts/check-codex-cloud-setup.mjs b/scripts/check-codex-cloud-setup.mjs index 275b7c319b..932a9f6ebc 100644 --- a/scripts/check-codex-cloud-setup.mjs +++ b/scripts/check-codex-cloud-setup.mjs @@ -670,8 +670,10 @@ export function validateCodexCloudSetup() { } // Reject any executable (non-comment) reference that would write MCP tables. // Strip full-line comments and whitespace-prefixed trailing comments so an - // inline note cannot false-trip the guard. This remains a text-level contract; - // the generated-config behavioural test is the stronger proof. + // inline note cannot false-trip the guard. Text-level only: a `#` inside a + // quoted shell string is also stripped, and split/concatenated table names + // would not match. The generated-config behavioural test (fresh temp $HOME + // asserting no `[mcp_servers.` after connected setup) is the stronger proof. const setupWithoutComments = setup .split("\n") .map((line) => { @@ -750,6 +752,12 @@ export function validateCodexCloudSetup() { /FAIL-KNOWN: inherited documented launcher defect names/, "Raw Cloud environment probe must emit FAIL-KNOWN for the documented launcher defect.", ); + requireMatch( + errors, + rawEnvironmentProbe, + /CONTINUE-RESTRICTED: OPENAI_BASE_URL can redirect OpenAI-bound traffic/, + "Raw Cloud environment probe must warn that OPENAI_BASE_URL can redirect provider traffic.", + ); requireMatch( errors, rawEnvironmentProbe, diff --git a/scripts/check-github-shell-access.mjs b/scripts/check-github-shell-access.mjs index 91663eea13..2bda1f37b3 100644 --- a/scripts/check-github-shell-access.mjs +++ b/scripts/check-github-shell-access.mjs @@ -18,9 +18,11 @@ function shellGh(command, args) { * * Live `gh` API calls require an explicit opt-in (`--allow-provider` or * `ALLOW_GITHUB_SHELL_ACCESS=true`) so the provider confirmation boundary is - * self-enforcing rather than documentation-only. The plain npm script runs - * `--self-test` offline; use `npm run check:github-shell-access:live` for the - * provider-backed path. + * self-enforcing rather than documentation-only. The plain npm script passes + * `--self-test`, which stays offline unless an opt-in is also present; use + * `npm run check:github-shell-access:live` for the dedicated provider-backed + * entry. Opt-in always wins over `--self-test` so env/flag confirmation cannot + * be silently swallowed by the offline stub. */ export function githubShellAccess(run = shellGh) { if (run("gh", ["--version"]).status !== 0) { @@ -72,11 +74,25 @@ function selfTest() { if (!providerAccessAuthorized(["node", "script.mjs"], { [allowProviderEnv]: "true" })) { throw new Error(`provider access must accept ${allowProviderEnv}=true`); } + // Opt-in must beat a bundled --self-test so confirmation is never a no-op. + if (shouldRunSelfTest(["node", "script.mjs", "--self-test", allowProviderFlag], {})) { + throw new Error("--allow-provider must skip the offline --self-test stub"); + } + if (shouldRunSelfTest(["node", "script.mjs", "--self-test"], { [allowProviderEnv]: "true" })) { + throw new Error(`${allowProviderEnv}=true must skip the offline --self-test stub`); + } + if (!shouldRunSelfTest(["node", "script.mjs", "--self-test"], {})) { + throw new Error("plain --self-test without opt-in must stay offline"); + } console.log("GITHUB_SHELL_ACCESS_SELF_TEST=PASS"); } +function shouldRunSelfTest(argv = process.argv, env = process.env) { + return argv.includes("--self-test") && !providerAccessAuthorized(argv, env); +} + function main() { - if (process.argv.includes("--self-test")) { + if (shouldRunSelfTest()) { selfTest(); return; } @@ -84,8 +100,8 @@ function main() { console.error( [ "Refusing live GitHub API calls without confirmation.", - `Re-run with npm run check:github-shell-access:live, or pass ${allowProviderFlag} / ${allowProviderEnv}=true, only after explicit provider approval.`, - "Offline proof: npm run check:github-shell-access", + `Use npm run check:github-shell-access:live, or invoke node scripts/check-github-shell-access.mjs with ${allowProviderFlag} / ${allowProviderEnv}=true, only after explicit provider approval.`, + "The plain npm run check:github-shell-access entry stays offline unless that same opt-in is present (opt-in overrides --self-test).", ].join("\n"), ); process.exitCode = 1; diff --git a/tests/codex-cloud-setup.test.ts b/tests/codex-cloud-setup.test.ts index a273162012..2b82e0f9dd 100644 --- a/tests/codex-cloud-setup.test.ts +++ b/tests/codex-cloud-setup.test.ts @@ -451,6 +451,8 @@ describe("Codex Cloud environment contract", () => { expect(known.stderr).toContain("FAIL-KNOWN:"); expect(known.stderr).toContain("OPENAI_BASE_URL"); expect(known.stderr).toContain("CONTINUE-RESTRICTED:"); + expect(known.stderr).toContain("redirect OpenAI-bound traffic"); + expect(known.stderr).toContain("bypasses the profile/shim scrub"); expect(known.stderr).not.toContain("https://example.invalid"); const mixed = spawnSync(bashCommand, ["scripts/check-codex-cloud-raw-env.sh"], { @@ -574,7 +576,9 @@ describe("Codex Cloud environment contract", () => { expect(connected.status, connected.stderr || connected.stdout).toBe(0); const connectedProfile = readRuntimeProfile(connectedHome); const connectedConfig = readCodexConfig(connectedHome); + // Fresh temp $HOME starts without Codex config; setup must not invent MCP tables. expect(connectedConfig).not.toContain("[mcp_servers."); + expect(connectedConfig).not.toContain("mcp_servers"); expect(connectedProfile).toContain('export CODEX_CLOUD_ACCESS_PROFILE="connected"'); expect(connectedProfile).toContain('export RAG_PROVIDER_MODE="offline"'); expect(connectedProfile).not.toContain("${RAG_PROVIDER_MODE:-auto}"); From 8079e50cb8abff540abbfd07378b4f14817d7a90 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 12:40:01 +0000 Subject: [PATCH 08/10] fix(cloud): close hosted-boundary review residuals for #1617 Keep GitHub shell --self-test offline regardless of ambient opt-in, document local-only Railway MCP enable ($CODEX_HOME / never-committed), and pin managed MCP cleanup plus exit-2 / CLI operator caveats. Co-authored-by: BigSimmo --- .codex/config.toml | 2 ++ AGENTS.md | 14 ++++---- .../cloud-connection-acceptance-2026-08-05.md | 10 +++--- docs/codex-cloud.md | 36 +++++++++++-------- docs/codex-review-protocol.md | 10 +++--- scripts/check-codex-cloud-setup.mjs | 4 ++- scripts/check-github-shell-access.mjs | 30 ++++++++-------- tests/codex-cloud-setup.test.ts | 33 ++++++++++++++--- 8 files changed, 89 insertions(+), 50 deletions(-) diff --git a/.codex/config.toml b/.codex/config.toml index 5ea5c115f4..a1959415b2 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -3,6 +3,8 @@ # # checked-in policy: # - enabled = false so ordinary/offline Codex sessions do not initialize providers +# (check:codex-cloud fails if any tracked entry is enabled; opt in via +# $CODEX_HOME/config.toml or a never-committed local edit) # - production Supabase uses "prompt" so every metadata/read operation requires # explicit approval; write-capable Figma/Railway/Sentry tools use "writes" # - Paid API canaries (eval:rag, eval:retrieval:quality, eval:quality, verify:release, diff --git a/AGENTS.md b/AGENTS.md index d242cda538..a05beab188 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -366,7 +366,7 @@ surface, read `docs/rag-behaviour/` (README → behaviour-map → refuted-approa - Production services `Database` (Next.js app tier, serves `https://psychiatry.tools`) and `worker` (ingestion) auto-deploy from `BigSimmo/Database` pushes to `main`; the `staging` environment runs the `app` service. - The older Railway project `clinical-kb` (`4361c04f-dd3c-4ee9-9e97-49e4e5707b70`) is superseded with zero active deployments; treat it as stale — never `railway link` to it or deploy there. - The similarly named Supabase project `Clinical KB Database` is the database/auth tier, not a Railway project; see "Supabase project safety" above. -- Railway CLI token auth uses `RAILWAY_API_TOKEN` (personal account token; see `.env.example`). The project-scoped `RAILWAY_TOKEN` is for CI deploys only and cannot list or link projects. Desktop/CLI MCP uses the secret-free `railway` entry in `.codex/config.toml` or `.mcp.json` plus `codex mcp login railway`; neither file activates a hosted ChatGPT/Codex app. +- Railway CLI token auth uses `RAILWAY_API_TOKEN` (personal account token; see `.env.example`). The project-scoped `RAILWAY_TOKEN` is for CI deploys only and cannot list or link projects; Cloud runtime acceptance no longer installs or probes the CLI, so that substitution rule is documentation-enforced until an operator workflow reintroduces CLI checks. Desktop/CLI MCP uses the secret-free `railway` entry (enable in `$CODEX_HOME/config.toml` or via a never-committed local edit — never commit `enabled = true`) plus `codex mcp login railway`; neither repository MCP file activates a hosted ChatGPT/Codex app. - Railway deploys and mutations fall under the "API and provider confirmation boundary" below; verify target project/environment IDs before any mutation. @@ -876,8 +876,9 @@ Use `docs/codex-cloud.md` as the environment contract: Write-capable Figma, Railway, and Sentry tools still require explicit confirmation. Paid API canaries (`eval:rag`, `eval:retrieval:quality`, `eval:quality`, `verify:release`, `test:live`, `check:supabase-project`) still need explicit confirmation. Project - `.codex/config.toml` keeps Desktop/CLI MCP entries `enabled = false`; use - `codex mcp login railway` locally after enabling the `railway` entry. Cloud setup never writes + `.codex/config.toml` keeps Desktop/CLI MCP entries `enabled = false` in git (`check:codex-cloud` + fails if any tracked entry is enabled). Opt in locally via `$CODEX_HOME/config.toml` (preferred) + or a never-committed project-file edit, then `codex mcp login railway`. Cloud setup never writes Railway or Supabase MCP registrations to `$CODEX_HOME`. Hosted ChatGPT/Codex requires an installed, workspace-authorized, OAuth-authenticated app, and a fresh task must prove the callable inventory with read-only identity calls. Root `.mcp.json` is a static cross-client template, not @@ -896,9 +897,10 @@ Use `docs/codex-cloud.md` as the environment contract: profile or entering a login shell. It must report only provider variable names and presence, never values. Treat exit `1` / `FAIL`+`STOP` as a hard stop for any unexpected inherited name. Only exit `2` / `FAIL-KNOWN` for `OPENAI_BASE_URL` alone may use the restricted - profile-and-shim continue path; do not generalize that allowance. That name can redirect - OpenAI-bound traffic, so never invoke OpenAI clients from the raw parent or any binary that - bypasses the profile/`node`/`npm`/`npx` scrub. Then run + profile-and-shim continue path; do not generalize that allowance. Exit `2` is still a failed raw + boundary — future automation must not treat non-1 as success or as a blind retry. That name can + redirect OpenAI-bound traffic, so never invoke OpenAI clients from the raw parent or any binary + that bypasses the profile/`node`/`npm`/`npx` scrub. Then run `npm run check:codex-cloud` directly; it must report the static-and-environment PASS line. Run `npm run check:codex-cloud -- --runtime` with `CODEX_CLOUD_EXPECTED_BASE_SHA` set to the intended merge/base commit when the checkout has only a task HEAD. Setup and maintenance may diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index d2488bc0f6..420bb1e8b6 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -103,11 +103,11 @@ the Codex Cloud product did not project the installed ChatGPT apps into the task connected Railway/Supabase MCPs. This repair is isolated from that unrelated PR and does not merge, close, or rewrite it. - Connector commit search did not find the Cloud-only `fe31128` GitHub preflight commit. The bounded - behavior was recreated as `check:github-shell-access`: the plain npm entry is offline - `--self-test`; the live `gh` shell fallback is `check:github-shell-access:live` (or a direct - script invocation with `--allow-provider` / `ALLOW_GITHUB_SHELL_ACCESS=true`, which overrides - `--self-test`). `GH_AUTH_MISSING` therefore says nothing about the hosted GitHub connector, - which passed independently. + behavior was recreated as `check:github-shell-access`: the plain npm entry is always offline + `--self-test` (ambient opt-in cannot override it); the live `gh` shell fallback is + `check:github-shell-access:live` (or a direct script invocation with `--allow-provider` / + `ALLOW_GITHUB_SHELL_ACCESS=true` and without `--self-test`). `GH_AUTH_MISSING` therefore says + nothing about the hosted GitHub connector, which passed independently. - The original blocked report's `OPENAI_BASE_URL` condition was removed in Codex environment settings. `Database - connected` now contains exactly the five documented non-secret variables, but a new task still inherited the variable name. Escalate this mismatch to Codex environment diff --git a/docs/codex-cloud.md b/docs/codex-cloud.md index 1b2996e123..aae49924a0 100644 --- a/docs/codex-cloud.md +++ b/docs/codex-cloud.md @@ -262,13 +262,15 @@ name-scoped in the probe itself: - exit `2` / `FAIL-KNOWN` + `CONTINUE-RESTRICTED` — only the documented Personal Pro launcher defect name `OPENAI_BASE_URL` -When the probe reports `FAIL-KNOWN` for `OPENAI_BASE_URL` alone, preserve that name-only output -for OpenAI support and continue provider-free work only through the generated profile and command -shims, followed by a passing `npm run check:codex-cloud`. Do not generalize that restricted path -to any other inherited name. An inherited `OPENAI_BASE_URL` can redirect OpenAI-bound traffic, so -never call OpenAI clients from the raw parent process or from binaries that bypass the -profile/`node`/`npm`/`npx` shim scrub. A sanitized child shell is never proof that the -raw-parent boundary passed. +Exit `2` is still a failed raw boundary: humans/agents may continue only under this contract. +Do not treat “non-1” or “retry on any failure” as success in future automation — wire the three +states explicitly. When the probe reports `FAIL-KNOWN` for `OPENAI_BASE_URL` alone, preserve that +name-only output for OpenAI support and continue provider-free work only through the generated +profile and command shims, followed by a passing `npm run check:codex-cloud`. Do not generalize +that restricted path to any other inherited name. An inherited `OPENAI_BASE_URL` can redirect +OpenAI-bound traffic, so never call OpenAI clients from the raw parent process or from binaries +that bypass the profile/`node`/`npm`/`npx` shim scrub. A sanitized child shell is never proof +that the raw-parent boundary passed. `npm run check:production-readiness` remains useful in the offline profile for local safeguards. Missing Supabase/OpenAI agent-phase credentials are reported as a provider capability gap and do @@ -287,8 +289,11 @@ only workspace `bigsimmo's Projects` and project `Database` (`5deaad0b-675a-4c13-978e-5ca2b5b877f9`) where Railway offers that choice. Reduce read results to non-secret account, project, workspace, environment, and service metadata. Railway's remote MCP does not accept project tokens; install Railway CLI separately only for explicitly approved -local/operator workflows. Enterprise/Edu custom-app controls are an optional future governance -upgrade, not the current operating target. +local/operator workflows (for example `npm run check:env-parity -- --railway`). That CLI path is +not available in ordinary Cloud tasks and is not part of Cloud runtime acceptance. Prefer +`RAILWAY_API_TOKEN` for personal CLI auth; never substitute the project-scoped CI +`RAILWAY_TOKEN`. Enterprise/Edu custom-app controls are an optional future governance upgrade, +not the current operating target. The Supabase MCP entry is scoped to production project `sjrfecxgysukkwxsowpy`, forces `read_only=true`, and exposes only documentation/development metadata tools. The database and @@ -300,10 +305,12 @@ broaden the production entry. OpenAI generation, Supabase live data, Railway cha reruns, ingestion, deployment, and release workflows remain separate explicit actions. Project `.codex/config.toml` is the checked-in Codex Desktop/CLI MCP template. Its URL-only entries -remain `enabled = false`; a trusted local operator can enable `railway` and run -`codex mcp login railway`. Setup does not copy any MCP server into `$CODEX_HOME`. Hosted ChatGPT and -Codex Cloud require the separately installed/authenticated workspace app. Start a fresh task after -consent and verify the actual callable inventory. +must stay `enabled = false` in git — `npm run check:codex-cloud` hard-fails on any tracked +`enabled = true`. A trusted local operator opts in outside the committed tree: prefer enabling +`railway` in `$CODEX_HOME/config.toml`, or make a never-committed local edit to the project file for +the session, then run `codex mcp login railway`. Setup does not copy any MCP server into +`$CODEX_HOME`. Hosted ChatGPT and Codex Cloud require the separately installed/authenticated +workspace app. Start a fresh task after consent and verify the actual callable inventory. The root `.mcp.json` is a cross-client Desktop/CLI template and static allowlist only. It does not prove hosted Cloud availability. Context7 / library-docs MCP is Cursor-side (`.cursor/mcp.json` or a host-injected connector), not part of this Codex Cloud @@ -319,7 +326,8 @@ credentials into Cloud: GitHub connector or Cloud PR controls. - **ChatGPT web:** Railway through the official OAuth app and Supabase through the pinned project-scoped read-only app. Keep Railway on **Allow read actions** and ask before every change. -- **Desktop/CLI:** opt-in local MCP from `.codex/config.toml`, followed by +- **Desktop/CLI:** opt-in local MCP via `$CODEX_HOME/config.toml` (preferred) or a + never-committed local enable of the project `.codex/config.toml` `railway` entry, followed by `codex mcp login railway`; this is a local operator fallback, never hosted proof. The repository checker prints these routes as sanitized `provider_route.*` lines. They describe diff --git a/docs/codex-review-protocol.md b/docs/codex-review-protocol.md index 2d71bbd8e1..f3a656e3cb 100644 --- a/docs/codex-review-protocol.md +++ b/docs/codex-review-protocol.md @@ -26,12 +26,12 @@ Use this protocol for every Codex review, audit, bug hunt, PR review, release-re For an explicitly authorized hosted task, use the GitHub connector or native Cloud controls first. Their permissions are independent of `gh` in the agent shell. Run -`npm run check:github-shell-access:live` (which passes `--allow-provider`; +`npm run check:github-shell-access:live` (which passes `--allow-provider` and does not pass +`--self-test`) only before an intentional GitHub CLI fallback. `ALLOW_GITHUB_SHELL_ACCESS=true` or `--allow-provider` on a direct -`node scripts/check-github-shell-access.mjs` invocation authorize the same live path) -only before an intentional GitHub CLI fallback. The plain -`npm run check:github-shell-access` entry is offline `--self-test` so script sweeps cannot -accidentally make provider calls; an explicit opt-in overrides that stub when present. +`node scripts/check-github-shell-access.mjs` invocation (without `--self-test`) authorize the +same live path. The plain `npm run check:github-shell-access` entry is always offline +`--self-test`; ambient opt-in cannot convert it into provider traffic. `GH_AUTH_MISSING` means shell authentication is absent, not that the hosted connector is disconnected. Never add a PAT to an ordinary Cloud task. diff --git a/scripts/check-codex-cloud-setup.mjs b/scripts/check-codex-cloud-setup.mjs index 932a9f6ebc..3bc92d9262 100644 --- a/scripts/check-codex-cloud-setup.mjs +++ b/scripts/check-codex-cloud-setup.mjs @@ -173,7 +173,9 @@ export function validateCodexProjectMcpConfiguration(text) { const expected = expectedCodexProjectMcpServers[name]; if (server.enabled !== false) { - errors.push(`${label} must set enabled = false (trusted Desktop/CLI operators opt in).`); + errors.push( + `${label} must set enabled = false (opt in via $CODEX_HOME/config.toml or a never-committed local edit; do not commit enabled = true).`, + ); } if (server.default_tools_approval_mode !== expected.approvalMode) { const reason = diff --git a/scripts/check-github-shell-access.mjs b/scripts/check-github-shell-access.mjs index 2bda1f37b3..19747f485f 100644 --- a/scripts/check-github-shell-access.mjs +++ b/scripts/check-github-shell-access.mjs @@ -19,10 +19,10 @@ function shellGh(command, args) { * Live `gh` API calls require an explicit opt-in (`--allow-provider` or * `ALLOW_GITHUB_SHELL_ACCESS=true`) so the provider confirmation boundary is * self-enforcing rather than documentation-only. The plain npm script passes - * `--self-test`, which stays offline unless an opt-in is also present; use - * `npm run check:github-shell-access:live` for the dedicated provider-backed - * entry. Opt-in always wins over `--self-test` so env/flag confirmation cannot - * be silently swallowed by the offline stub. + * `--self-test` and always stays offline: an ambient opt-in must not convert a + * documentation-safe sweep into provider traffic. Use + * `npm run check:github-shell-access:live` (no `--self-test`) for the + * dedicated provider-backed entry. */ export function githubShellAccess(run = shellGh) { if (run("gh", ["--version"]).status !== 0) { @@ -74,21 +74,21 @@ function selfTest() { if (!providerAccessAuthorized(["node", "script.mjs"], { [allowProviderEnv]: "true" })) { throw new Error(`provider access must accept ${allowProviderEnv}=true`); } - // Opt-in must beat a bundled --self-test so confirmation is never a no-op. - if (shouldRunSelfTest(["node", "script.mjs", "--self-test", allowProviderFlag], {})) { - throw new Error("--allow-provider must skip the offline --self-test stub"); + // --self-test always stays offline, even when an ambient opt-in is exported. + if (!shouldRunSelfTest(["node", "script.mjs", "--self-test", allowProviderFlag])) { + throw new Error("--self-test must stay offline when --allow-provider is also present"); } - if (shouldRunSelfTest(["node", "script.mjs", "--self-test"], { [allowProviderEnv]: "true" })) { - throw new Error(`${allowProviderEnv}=true must skip the offline --self-test stub`); + if (!shouldRunSelfTest(["node", "script.mjs", "--self-test"])) { + throw new Error("plain --self-test must stay offline"); } - if (!shouldRunSelfTest(["node", "script.mjs", "--self-test"], {})) { - throw new Error("plain --self-test without opt-in must stay offline"); + if (shouldRunSelfTest(["node", "script.mjs", allowProviderFlag])) { + throw new Error("live entry without --self-test must not run the offline stub"); } console.log("GITHUB_SHELL_ACCESS_SELF_TEST=PASS"); } -function shouldRunSelfTest(argv = process.argv, env = process.env) { - return argv.includes("--self-test") && !providerAccessAuthorized(argv, env); +function shouldRunSelfTest(argv = process.argv) { + return argv.includes("--self-test"); } function main() { @@ -100,8 +100,8 @@ function main() { console.error( [ "Refusing live GitHub API calls without confirmation.", - `Use npm run check:github-shell-access:live, or invoke node scripts/check-github-shell-access.mjs with ${allowProviderFlag} / ${allowProviderEnv}=true, only after explicit provider approval.`, - "The plain npm run check:github-shell-access entry stays offline unless that same opt-in is present (opt-in overrides --self-test).", + `Use npm run check:github-shell-access:live, or invoke node scripts/check-github-shell-access.mjs with ${allowProviderFlag} / ${allowProviderEnv}=true (and without --self-test), only after explicit provider approval.`, + "The plain npm run check:github-shell-access entry always stays offline (--self-test); ambient opt-in cannot override it.", ].join("\n"), ); process.exitCode = 1; diff --git a/tests/codex-cloud-setup.test.ts b/tests/codex-cloud-setup.test.ts index 2b82e0f9dd..c0f14cc546 100644 --- a/tests/codex-cloud-setup.test.ts +++ b/tests/codex-cloud-setup.test.ts @@ -346,7 +346,7 @@ describe("Codex Cloud environment contract", () => { const tracked = readFileSync(new URL("../.codex/config.toml", import.meta.url), "utf8"); expect(validateCodexProjectMcpConfiguration(tracked)).toEqual([]); expect(validateCodexProjectMcpConfiguration(tracked.replaceAll("enabled = false", "enabled = true"))).toContain( - `.codex/config.toml figma_cloud must set enabled = false (trusted Desktop/CLI operators opt in).`, + `.codex/config.toml figma_cloud must set enabled = false (opt in via $CODEX_HOME/config.toml or a never-committed local edit; do not commit enabled = true).`, ); expect( validateCodexProjectMcpConfiguration( @@ -569,6 +569,29 @@ describe("Codex Cloud environment contract", () => { it("pins connected retrieval mode and rejects unsafe shell-policy configs", () => { const connectedHome = temporaryDirectory("codex-cloud-connected-"); + mkdirSync(path.join(connectedHome, ".codex"), { recursive: true }); + // Seed the previous hosted registration shape between managed markers so re-run + // cleanup is proven (fresh empty $HOME alone would not exercise that path). + writeFileSync( + path.join(connectedHome, ".codex/config.toml"), + [ + "[mcp_servers.keep_outside]", + 'command = "echo"', + "", + "# BEGIN clinical-kb-codex-cloud shell policy (managed by setup-codex-cloud.sh)", + "[mcp_servers.railway_connected]", + 'url = "https://mcp.railway.com"', + "enabled = true", + "[mcp_servers.supabase_connected]", + 'url = "https://mcp.supabase.com/mcp"', + "enabled = true", + "[shell_environment_policy]", + 'inherit = "all"', + "exclude = []", + "# END clinical-kb-codex-cloud shell policy (managed by setup-codex-cloud.sh)", + "", + ].join("\n"), + ); const connected = runSetupPolicyOnly(connectedHome, { CODEX_CLOUD_ACCESS_PROFILE: "connected", RAG_PROVIDER_MODE: "offline", @@ -576,9 +599,11 @@ describe("Codex Cloud environment contract", () => { expect(connected.status, connected.stderr || connected.stdout).toBe(0); const connectedProfile = readRuntimeProfile(connectedHome); const connectedConfig = readCodexConfig(connectedHome); - // Fresh temp $HOME starts without Codex config; setup must not invent MCP tables. - expect(connectedConfig).not.toContain("[mcp_servers."); - expect(connectedConfig).not.toContain("mcp_servers"); + expect(connectedConfig).toContain("[mcp_servers.keep_outside]"); + expect(connectedConfig).not.toContain("[mcp_servers.railway_connected]"); + expect(connectedConfig).not.toContain("[mcp_servers.supabase_connected]"); + expect(connectedConfig).not.toContain("mcp_servers.railway"); + expect(connectedConfig).not.toContain("mcp_servers.supabase"); expect(connectedProfile).toContain('export CODEX_CLOUD_ACCESS_PROFILE="connected"'); expect(connectedProfile).toContain('export RAG_PROVIDER_MODE="offline"'); expect(connectedProfile).not.toContain("${RAG_PROVIDER_MODE:-auto}"); From 43a78367caac4d35a4311a638f728bf06ccc26f4 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:57:50 +0800 Subject: [PATCH 09/10] docs: record Railway OAuth recovery --- .../cloud-connection-acceptance-2026-08-05.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/audit/cloud-connection-acceptance-2026-08-05.md b/docs/audit/cloud-connection-acceptance-2026-08-05.md index 2ffa7eb872..13c21347f0 100644 --- a/docs/audit/cloud-connection-acceptance-2026-08-05.md +++ b/docs/audit/cloud-connection-acceptance-2026-08-05.md @@ -34,8 +34,10 @@ postinstall can fail when GitHub release downloads are unavailable. - Railway OAuth metadata advertises `openid`, `profile`, `email`, `offline_access`, and `workspace:member`. An elapsed-time follow-up from a new ChatGPT Work task displayed **Your Railway connection has expired** before `whoami` could run and offered the normal **Reconnect** - flow. Treat reauthentication as required for this Personal Pro app path; never introduce a - static-token workaround. + flow. The connection error identified the cause as `oauth_refresh_token_missing`. Reauthentication + through the normal Railway GitHub login completed successfully, after which `whoami`, + `list-projects`, and `get-status` passed immediately. Treat periodic reauthentication as required + for this Personal Pro app path; never introduce a static-token workaround. ## Acceptance status @@ -46,7 +48,7 @@ postinstall can fail when GitHub release downloads are unavailable. | Railway | Exact tools callable in a fresh ChatGPT chat and fresh Codex Cloud task; read-only identity/project/service checks pass | ChatGPT PASS via `railway_whoami`, `railway_get_status`, `railway_list_projects`, and `railway_list_services`; project `Database` and services `Database` and `worker` were visible with `SUCCESS` status. Fresh Codex Cloud FAIL: no Railway tool exposed | | Supabase | Existing app exposes only project-scoped read-only metadata without row queries | ChatGPT PASS via `mcp__codex_apps__supabase_list_projects`: `sjrfecxgysukkwxsowpy`, `Clinical KB Database`, `ACTIVE_HEALTHY`; no schema/table/row/log call. Codex Cloud FAIL: no Supabase tool exposed | | Raw Cloud shell | Only five documented non-secret values; `OPENAI_BASE_URL` absent before profiles/shims | Environment UI has exactly the five documented values and no `OPENAI_BASE_URL`, but fresh raw-shell FAIL still reports the inherited name. This is a launcher/workspace defect, not repository state | -| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | REAUTH REQUIRED: the elapsed-time task reported that the Railway connection had expired and opened Railway's normal login/OAuth flow; no token workaround was added | +| OAuth durability | Second read-only Railway call succeeds after one hour, or reauthentication is documented | REAUTH VERIFIED: the elapsed-time task reported `oauth_refresh_token_missing`; normal Railway GitHub OAuth restored access and immediate read-only validation passed. Periodic reconnect remains required; no token workaround was added | ## Personal Pro operating workarounds @@ -55,7 +57,7 @@ postinstall can fail when GitHub release downloads are unavailable. | No Personal Pro group RBAC or per-tool disabling | Railway app policy is **Allow read actions**; every change remains approval-gated | Maximum available Pro control; not equivalent to Enterprise/Edu RBAC | | Railway and Supabase absent from Codex Cloud connectors | Use a split control plane: Codex Cloud for repository/GitHub work; ChatGPT web for official Railway OAuth and project-scoped read-only Supabase | Providers remain usable without copying tokens; a single Codex Cloud task still cannot call them | | Raw `OPENAI_BASE_URL` injected although absent in environment UI | Keep the name-only raw probe fail-closed, then use the generated profile, Codex shell policy, and `node`/`npm`/`npx` shims that remove provider variables before ordinary work | Normal repository commands are sanitized and functional; the raw parent-process defect remains visible for OpenAI support | -| Railway OAuth expires instead of refreshing in this app path | Use the app's normal **Reconnect** flow when prompted; never add a static or shared token | Safe continuity workaround; Railway login/consent remains user-controlled | +| Railway OAuth expires instead of refreshing in this app path | Use the app's normal **Reconnect** flow when prompted; the existing GitHub sign-in restored access without static or shared tokens | Safe continuity workaround; Railway login/consent remains user-controlled | This operating mode favors maximum safe functionality on Personal Pro. It does not relabel the Codex Cloud provider-tool acceptance failure as success. @@ -91,6 +93,12 @@ reads: GitHub authenticated as `BigSimmo`; Supabase project `Clinical KB Databas SQL, row contents, or provider writes were requested. This validates the Personal Pro split-control plane workaround, not single-task Codex Cloud provider acceptance. +After the Railway token expired, a direct connector call returned `UNAUTHORIZED` with reason +`oauth_refresh_token_missing`. The normal **Reconnect** flow completed through the existing GitHub +sign-in, ChatGPT reported **Railway is now connected**, and new read-only calls again authenticated +as `bigsimmo`. Project `Database` remained visible and both production services remained `SUCCESS`. +GitHub repository access and project-scoped Supabase health also passed again in the same task. + This proves the repository setup fix and also proves that repository code cannot close either remaining hosted blocker. The environment UI and launcher disagree about `OPENAI_BASE_URL`, and the Codex Cloud product did not project the installed ChatGPT apps into the task tool inventory. From 0acace4a6fc5c43c0443211f9f1d55640669f4fd Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:17:50 +0800 Subject: [PATCH 10/10] docs: preserve Cloud checker index after main sync --- docs/scripts-index.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/scripts-index.md b/docs/scripts-index.md index 304cd7ce22..8f636f269d 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -16,25 +16,25 @@ migration has shipped (see `docs/maturity-backlog-workorders.md` L1). ## Runner & guard infrastructure [infra] -| Script | Role | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | -| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | -| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | -| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | -| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | -| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe, an operator-only non-Cloud PAT branch-deletion helper, sanitized acceptance, and safe credential-free `origin` repair | -| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | -| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | -| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | -| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | -| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | -| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | -| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | -| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | -| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | -| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | -| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | +| Script | Role | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `run-heavy.mjs` | Acquires shared/exclusive cross-worktree leases (`test-run-lock.mjs`) so focused checks can overlap safely | +| `run-tsx.mjs`, `run-vitest.mjs`, `run-playwright.mjs`, `run-eval-safe.mjs` | Typed/test/e2e/eval entrypoint wrappers | +| `dev-free-port.mjs`, `ensure-local-server.mjs` | Project-stable localhost port selection + background server ensure | +| `design-sync.mjs`, `capture-mockup-screenshots.mjs` | Local design-sync CSS prep (`node scripts/design-sync.mjs`) + redesign "current" PNG pack after ensure (`node scripts/capture-mockup-screenshots.mjs`) | +| `check-node-engine.cjs`, `install-git-hooks.mjs`, `guard-push.mjs`, `guard-next-build.mjs` | Install/preflight guards | +| `setup-codex-cloud.sh`, `maintain-codex-cloud.sh`, `install-codex-cloud-command-shims.sh`, `check-codex-cloud-raw-env.sh`, `delete-codex-cloud-branch-with-pat.sh`, `check-codex-cloud-setup.mjs`, `check-github-shell-access.mjs`, `ensure-codex-cloud-git-remote.mjs` | Reproducible Codex Cloud toolchain/profile setup, profile-loading Node command shims, a pre-profile name-only credential probe (`FAIL-KNOWN`/`exit 2` only for documented `OPENAI_BASE_URL`; hard `STOP` otherwise), an operator-only non-Cloud PAT branch-deletion helper, sanitized Personal Pro capability/route acceptance, GitHub shell preflight (`check:github-shell-access` = offline self-test; `check:github-shell-access:live` = provider-backed opt-in), and safe credential-free `origin` repair | +| `ci-change-scope.mjs`, `ci-triage.mjs`, `pr-policy.mjs`, `pr-mergeability.mjs` | CI change classification + PR policy + conflict signal (self-tested via `check:ci-scope`/`check:ci-triage`/`check:pr-policy`/`check:pr-mergeability`) | +| `check-outstanding-issues.mjs`, `check-pr-mergeability-workflow.mjs` | Outstanding-issues ID/marker/no-driver guard + PR mergeability workflow contract | +| `outstanding-issues.mjs` | Writer for `docs/outstanding-issues.md` (`issues:add` / `issues:done` / `issues:update`) — allocates the id, picks the right table, escapes `\|`, and re-runs the guard on its own output. Never hand-edit that file, as with `ledger:append` | +| `check-installed-lock-parity.mjs`, `phone-chrome-plan.mjs`, `verify-phone-chrome.mjs`, `playwright-browser-preflight.mjs` | Lock-trust preflight, change-scoped phone contracts, and Playwright browser-binary preflight before build | +| `final-merge-audit.mjs` | Fail-closed local merge-tree audit; explicit provider mode adds PR/check/thread/tree/deployment proof | +| `child-process-result.mjs`, `cli-utils.ts`, `productivity-core.mjs` | Shared helpers | +| `test-focused.mjs`, `test-run-selection.mjs`, `test-cache-path.mjs`, `test-environment.mjs` | Backs `npm run test:focused` — change-scoped selection, cache pathing, env setup; fails closed for deleted files and test infrastructure | +| `primary-checkout-lease.mjs`, `test-run-lock.mjs`, `clean-worktree.mjs` | Cross-worktree lease arbitration for the primary checkout, plus worktree cleanup | +| `resolve-tsx-cli.mjs`, `register-server-only.mjs`, `enable-server-only-stub.mjs` | tsx CLI resolution and `server-only` import shims | +| `check-format-changed.mjs`, `check-base-freshness.mjs`, `check-local-presence.mjs` | Push-time helpers behind `guard-push.mjs`: changed-file formatting, stale-base and local-presence checks | +| `yaml-contract.mjs`, `sensitive-text.mjs`, `design-system-contract-utils.mjs` | Shared parsing/redaction/contract helpers used by the gates | ## Verification gates [live]