Skip to content

Xcode Build/Test/Run Configs — Design Spec (#TBD)

Milestone: xcode-build-configs Manual: docs/contributor_manual/guide/10-setup-and-day-to-day-development.md

Design-led (Testing Constitution). Status: APPROVED — 2026-09-09 (invariants verified from the project this session). A guardrail pins them so config drift is a red test, not a debugging session. Tags: [OK] built · [MISSING] not built.

Why this spec exists

The UI-test harness bug (ui-test-harness.md) was, at root, a config that had drifted from its own comments: a source comment asserted “Dev Local is sandboxed” while the actual build setting says the opposite — and that stale belief sent the test socket into the real app container. Config truth currently lives only in prose comments that nothing checks. This spec turns the build/test/run matrix into executable invariants.

Intent (the design)

There is ONE authoritative statement of what each build config is for and what security/arch/ deployment shape it carries, and a guardrail (scripts/check_xcode_config_invariants.py) that asserts it against fichero.xcodeproj/project.pbxproj, the *.entitlements files, the shared .xcschemes, and the Tests/plans/*.xctestplans. A drifted comment or a flipped setting fails the gate.

The matrix (verified 2026-09-09)

Scheme Build config Sandbox Entitlements Purpose
Dev Local Debug OFF (ENABLE_APP_SANDBOX = NO) Fichero.entitlements day-to-day dev + the UI-test host; unsandboxed so tests reach temp-dir fixtures
Dev Embedded Dev Embedded ON Fichero.entitlements embedded-engine dev parity
Release Embedded / App Store Release ON FicheroRelease / FicheroAppStore DMG + MAS

Cross-cutting invariants (all configs): - arm64-only — EXCLUDED_ARCHS = x86_64 on the project configs (Golden Gate, Apple silicon). - Deployment floor macOS 26 — support 26 + 27, require 26. - FicheroTests carries SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor (else ~575 SIGTRAPs — see [[test-target-needs-mainactor-default-isolation]]). - The embedded test plan is attached to the Release Embedded scheme; dead plans stay deleted.

Behaviors

  • config.dev-local-unsandboxed [OK] — Fichero (Dev Local) → Debug → ENABLE_APP_SANDBOX=NO. Pinned: test_check_xcode_config_invariants.py::test_real_project_is_clean, ::test_debug_sandbox_flip_is_caught.
  • config.release-sandboxed [OK] — Release + App Store + Dev Embedded → sandbox ON. Pinned: test_check_xcode_config_invariants.py::test_real_project_is_clean, ::test_debug_sandbox_flip_is_caught.
  • config.arm64-only [OK] — every project config excludes x86_64. Pinned: test_check_xcode_config_invariants.py::test_missing_excluded_archs_is_caught.
  • config.deployment-floor-26 [OK] — MACOSX_DEPLOYMENT_TARGET floor is 26. Pinned: test_check_xcode_config_invariants.py::test_wrong_deployment_floor_is_caught.
  • config.tests-mainactor-isolation [OK] — FicheroTests configs set the MainActor default. Pinned: test_check_xcode_config_invariants.py::test_missing_mainactor_isolation_is_caught, ::test_mainactor_dropped_from_ficherotests_only_is_caught.
  • config.embedded-testplan-attached — [PARTIAL] (#4807) the embedded plan is referenced by its scheme, but re-checked 2026-09-18: check_xcode_config_invariants.py never actually asserts this — neither the guardrail’s source nor its test file mentions “testplan” or “embedded” plan attachment at all. The other five invariants in this spec really are checked and tested; this one just isn’t yet.
  • config.no-stale-sandbox-comments [MISSING] (#4778) — no source comment asserts a config’s sandbox state (the drift that caused the harness bug): the guardrail asserts the settings themselves, and the stale comments are deleted. Comment-checking is added only if the drift recurs (ruled 2026-10-04, design lead).

Test matrix

Leg This surface? Pins File
Config guardrail (py) y every invariant above, parsed from project files scripts/check_xcode_config_invariants.py
Gate wiring y the guardrail runs in verify_all.sh scripts/verify_all.sh

Hard-gate: all of config.*. These are cheap (file parse, no build) and catch the exact drift class that stranded the harness.

Rulings

  • Ruled 2026-10-04 (design lead, applying the spec’s own lean): assert the settings and delete the stale sandbox comments; add comment-checking only if the drift recurs (config.no-stale-sandbox-comments).

Open questions

None outstanding. - config.no-stale-sandbox-comments: ~~worth the parsing complexity?~~ Answered 2026-10-04: no; assert settings, delete stale comments (see Rulings).

Triaged from the backlog (2026-10-04)

  • config.shipping-configuration-is-tested — [GAP] (#4226) a check exercises the configuration users get (DMG/App Store: UDS, sandbox, bookmarks), and the Swift gate and scripts/check_*.py guards are run together so neither can go red unseen.
  • build.swiftlint-phase-reruns-on-change — [GAP] (#5096) the SwiftLint build phase declares file inputs that change when a source changes, so a fixed lint error stops being reported.
  • build.ios-target-compiles-without-macos-only-types — [BROKEN] (#5322) the iOS target compiles: EmbeddedBackendService.backendProcess (Process) and two other macOS-only pieces are guarded #if os(macOS); fix 4d4d768ec is not yet on integration.