Commit Graph
4 Commits
Author SHA1 Message Date
james.maes 21644001b5 fix(codesign): Debug must sign WITHOUT runtime flag — AMFI rejects HR + adhoc
Debug builds were silently coming out (adhoc,runtime) despite project.yml
setting ENABLE_HARDENED_RUNTIME: NO and OTHER_CODE_SIGN_FLAGS: "" on the
Debug config. Two compounding bugs:

  1. project.yml's Debug-config OTHER_CODE_SIGN_FLAGS="" override didn't
     consistently propagate to the codesign.sh build-phase env (the
     parent Run Script env vs xcodegen-generated PBX project setting
     scope diverges).
  2. codesign.sh's ${OTHER_CODE_SIGN_FLAGS:-default} treats empty same as
     unset, re-introducing --options=runtime even if the override DID
     reach the script.

The combined effect: Hardened Runtime stayed ON, AMFI's strict
non-platform-Team-IDs-must-match rule fired against the adhoc-signed
Jarvis.debug.dylib, dyld aborted at __abort_with_payload (svc 0x209)
before any user code ran. App couldn't launch from Xcode (⌘R) or open.

Fix: explicit ${CONFIGURATION:-Release} == Debug branch in codesign.sh
that signs Debug with --timestamp=none and no runtime flag at all.
Result: Debug bundle is flags=0x2(adhoc) only — AMFI accepts it, app
launches, writes 'Jarvis launching' to system.log.

Also tried SWIFT_ENABLE_DEBUG_DYLIB: NO on Debug — the setting didn't
take in Xcode 26 (dylib still produced) but the codesign fix made that
moot. Kept the project.yml line as documentation; harmless if Xcode
ignores it.

Next blocker (Phase 1 self-check): app reaches main and logs
'Jarvis launching' but then hard-blocks on JarvisEntitlementsVerified
missing from Info.plist. That marker is supposed to be written by
verify-entitlements.sh --post-codesign. Investigate that script's
post-codesign Info.plist write path next session.
2026-04-26 07:18:21 -05:00
james.maes 0517926f61 fix(01): split entitlements per-config + address AMFI rejection on ad-hoc Debug
The debugger agent diagnosed the JarvisAppTests xctest launch failure
(Runningboard error 5 / Launchd job spawn failed). Root cause: AMFI
rejected the ad-hoc signed Debug bundle because it carried the managed
`com.apple.developer.speech-recognition-assets` entitlement. The kernel
logged `AMFI: Code has restricted entitlements, but the validation of
its code signature failed` → `load code signature error 4` → spawn
failure → Runningboard error 5.

**Changes:**

- Split `App/Jarvis.entitlements` → `Jarvis.Debug.entitlements` (no
  `speech-recognition-assets`; adds `get-task-allow` for XCTest attach)
  + `Jarvis.Release.entitlements` (full managed set, unchanged).
- `project.yml`: per-config `CODE_SIGN_ENTITLEMENTS`; Debug gets
  `ENABLE_HARDENED_RUNTIME=NO` + `OTHER_CODE_SIGN_FLAGS=""` +
  `ONLY_ACTIVE_ARCH=YES`; `DEVELOPMENT_TEAM` moved from base → Release
  only (avoids Team ID leaking into the Xcode 26 debug dylib).
- `scripts/codesign.sh`: resolves entitlements via $CODE_SIGN_ENTITLEMENTS
  env, falls back to config-specific file, then to the legacy path.
  Also signs `Contents/MacOS/*.debug.dylib` now that Xcode 26 emits it.
- `scripts/verify-entitlements.sh`: per-config `MAIN_REQUIRED` +
  `MAIN_FORBIDDEN` lists — Debug forbids `speech-recognition-assets`
  (wrong for ad-hoc) and requires `get-task-allow`; Release enforces
  the shipping posture with all 3 managed keys.

**Residual known issue** (documented in `.planning/debug/xctest-launch-runningboard-error-5.md`
and `01-HUMAN-UAT.md` §4): Xcode 26 still emits `Jarvis.debug.dylib`
with a code signature whose Team ID context diverges from our ad-hoc
outer sign, producing a dyld "different Team IDs" rejection when xctest
tries to load the bundle. Multiple workarounds attempted
(SWIFT_ENABLE_DEBUG_DYLIB=NO, resigning the dylib, stripping it
post-build). `xcodebuild test -scheme Jarvis -only-testing:JarvisAppTests`
fails bootstrap; SPM package tests (56, all passing) and `xcodebuild
build` both succeed. Actual code quality is unaffected — this is a
harness/tooling boundary issue.

Verified: 56 SPM tests pass; `xcodebuild build -configuration Debug`
BUILD SUCCEEDED; `scripts/test-verify-entitlements.sh` self-test
PASS=3 FAIL=0.
2026-04-23 17:06:10 -05:00
james.maes e9a0bb14d2 feat(01-05): add JarvisEntitlementProbeTests target + EntitlementProbe scheme
- App/Tests/JarvisEntitlementProbeTests/JarvisEntitlementProbeTests.swift:
  one-shot XCTest gated on #if JARVIS_ENTITLEMENT_PROBE. Calls
  SpeechTranscriber(locale:preset:) + AssetInventory.status(forModules:)
  and accepts any of three failure surfaces as confirmation that the
  speech-recognition-assets entitlement is load-bearing:
    (a) NSError SFSpeechErrorDomain code 1 (assetUnavailable)
    (b) NSError SFSpeechErrorDomain code 10 (macOS 26 'unallocated locales')
    (c) AssetInventory.Status description containing 'unavailable' /
        'unsupported'
- App/Tests/JarvisEntitlementProbeTests/Info.plist: standard test bundle.
- project.yml: new JarvisEntitlementProbeTests target (OTHER_SWIFT_FLAGS =
  -DJARVIS_ENTITLEMENT_PROBE) + EntitlementProbe scheme. Default Jarvis
  scheme's test action lists only JarvisAppTests — the probe target is
  opt-in via EntitlementProbe scheme only.
- Jarvis.xcodeproj/xcshareddata/xcschemes/EntitlementProbe.xcscheme: new
  shared scheme referencing JarvisEntitlementProbeTests as its sole
  testable.

Deviations from plan's copy-verbatim XCTest shape:
- Added @available(macOS 26.0, *) to the test class: macOS 26 SpeechModule
  / SpeechTranscriber / AssetInventory are not available pre-Tahoe.
  Deployment target stays at 13.0 for the rest of the project.
- preset: .progressiveLiveTranscription -> .progressiveTranscription
  (actual Apple SDK name on macOS 26.4).
- AssetInventory.status(forModules:) is async non-throwing on macOS 26.4;
  wrapped in a throwing adapter so the historical do/catch shape from
  RESEARCH Q4 (NSError SFSpeechErrorDomain codes 1/10) stays intact.

Also: [Rule 3 - Blocking] extend codesign.sh to handle nested .xctest
plugins under Contents/PlugIns/. When build-for-testing is invoked, the
xctest lives inside Jarvis.app/Contents/PlugIns/ and must be signed
before the main app, or codesign on the parent bundle fails with
'bundle format unrecognized'. Empty xctest shells that materialize
before the test target has been compiled are removed (Xcode recreates
+ populates them in a later iteration).

Task 3 acceptance: xcodebuild -list shows EntitlementProbe scheme +
JarvisEntitlementProbeTests target; EntitlementProbe build-for-testing
succeeds with the full codesign + entitlement verification chain.
2026-04-22 14:42:30 -05:00
james.maes 52f2bdee0d feat(01-05): add build-time codesign + entitlement verification scripts
- scripts/codesign.sh: deepest-first walker (find -depth) signing nested
  Contents/Helpers/**/*.app with per-helper .entitlements, then main app
  last. Never uses --deep (TN2206/Pitfall #3 guard).
- scripts/verify-entitlements.sh: two-mode script per RESEARCH Open Q #8.
  --pre-codesign validates source + writes JarvisEntitlementsVerified=YES
  to unsigned Info.plist so the flag becomes part of the signed content.
  --post-codesign greps signed entitlements (via codesign -d --entitlements -)
  for MAIN_REQUIRED/MAIN_FORBIDDEN + per-helper rules; never writes. Strips
  XML comments before grep (self-invalidating-grep defense).
- scripts/verify-codesign-settings.sh: pbxproj linter for CodeSignOnCopy = YES,
  --deep, and missing --options=runtime/--timestamp.
- scripts/test-verify-entitlements.sh: fault-injection self-test harness
  that feeds good/broken/forbidden fixtures to --verify-fixture mode and
  asserts exit codes.
- scripts/test-fixtures/{good,broken,forbidden}-entitlements.plist: fixtures
  exercising the happy path, missing allow-jit, and forbidden apple-events.

Self-test: PASS=3 FAIL=0. Linter passes against current pbxproj.

MCP-06, SEC-02, SEC-03, SEC-08 scaffolded.
2026-04-22 14:21:33 -05:00