The pre-WR-01 lint stripped only LEADING `//` comment lines, so a
string literal like `let docs = "Use NSApp.run() to start"` or a
block comment `/* runModal is forbidden */` would false-positive
because the regex matched bare identifiers inside content the parser
wouldn't even reach.
Tightens both:
1. PATTERN now requires a call-site shape (`.method(` or `Type.method(`)
so bare identifiers don't trigger.
2. Pre-strip pass via sed: strip line comments, block comments
(whole-line delete), and string literals before grep.
Adds string-literal-modal-mentions.swift fixture covering:
- runModal inside a string literal
- runModal inside a block comment
- runModal in a trailing line comment
None of these should trigger; the lint now correctly accepts the
fixture (exit 0).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the build-time half of MCP-04: scripts/check-no-modal-presentation.sh
fails the Jarvis build on any runModal / beginModalSession / NSApp.run /
NSApplication.shared.run call outside the two-file allowlist
(packages/Shell/Sources/Shell/TCCAlertService.swift +
packages/MCP/Sources/MCP/ConfirmationPresenter.swift). Wired as a
preBuildScript on the Jarvis target alongside the other build-breaking
lints (bus-protocol-version, no-evaluateJavaScript, single-writer-HUD).
Self-tests:
- whole-repo scan: exit 0 (only the two allowlisted call sites match)
- scripts/test-fixtures/allowed-non-modal-call.swift: exit 0
- scripts/test-fixtures/forbidden-modal-call.swift: exit 1 with the
'forbidden modal-presentation call' error on stderr
Plan deviation: the plan's allowlist named 'App/TCC/TCCAlertService.swift'
but TCCAlertService actually lives at packages/Shell/Sources/Shell/. The
allowlist follows the file's real path.
- verify-entitlements.sh: refactor inline mcp-applescript helper grep into
check_helper_entitlements_xml() driven off per-helper required+forbidden
arrays. mcp-applescript MUST carry apple-events; mcp-time/mcp-clipboard
MUST NOT (and forbidden list also rejects allow-jit / speech-recognition-
assets / audio-input / allow-unsigned-executable-memory drift onto helpers)
- Unknown-helper case is a fatal error — 'no default-allow branch' so authoring
drift cannot smuggle in an unconfigured helper
- Empty array guard via ${#array[@]} prevents bash 'unbound variable' under
set -u when a helper has no required keys (Rule 1 fix discovered via Debug
xcodebuild)
- New --verify-helper-fixture <plist> <helper-name> mode for fault-injection
harness — feeds a fixture file in and runs check_helper_entitlements_xml
without needing a real signed bundle
- 3 new fixtures cover the three threat-model arms:
helper-applescript-good T-pos: mcp-applescript with apple-events
helper-applescript-missing T-05-03-02: mcp-applescript MISSING apple-events
helper-time-with-apple-events T-05-03-01: cross-helper drift (forbidden)
- test-verify-entitlements.sh: add run_helper_case() driver + 3 invocations,
total fixture corpus now 6 cases (3 P1-05 + 3 P5-03)
- AppleScriptRunner.swift / MCPAppleScriptMain.swift: reword TCC-isolation
comments so the literal subprocess CLI binary name does not appear in source
(plan's anti-pattern grep '! grep -q osascript' enforces absence)
- Debug xcodebuild end-to-end: post-codesign walker reports
'helper mcp-time / mcp-clipboard / mcp-applescript entitlements OK' x3
- Fault-injection harness: PASS=6 FAIL=0
Three build-breaking check scripts + two smoke test harnesses + two fixture dirs:
- scripts/check-bus-protocol-version.sh: greps Swift vs TS BUS_PROTOCOL_VERSION
constants, diffs the two fixture directories (packages/Bus/Tests/BusTests/
Fixtures/ vs webview/packages/bus/fixtures/). Fails build on any drift.
- scripts/check-no-evaluate-javascript.sh: scans App/ + packages/ for
`.evaluateJavaScript(` outside test dirs; HUD-04 architectural lint.
- scripts/check-bus-harness-parity.sh: byte-identical check between
canonical webview/bus-harness.html and bundle copy (Plan 02-03 provides
the bundle copy; this script enforces no-drift going forward).
- scripts/test-check-bus-protocol-version.sh + bus-mismatch/ fixture:
drives the protocol-version checker against a v2.0.0/v1.9.0 mismatch,
asserts exit 1 + both versions in error text.
- scripts/test-check-no-evaluate-javascript.sh + bus-evaluate-js/ fixture:
drives HUD-04 lint against bad.swift (evaluateJavaScript) + good.swift
(callAsyncJavaScript). Asserts bad.swift flagged, good.swift not flagged.
Fixture is copied to a temp dir to bypass the production */test-fixtures/*
path-skip rule.
[Rule 3 - Blocking] Normalized Swift-side fixtures to drop trailing newlines.
The 14 JSON files under packages/Bus/Tests/BusTests/Fixtures/ had stray
trailing \n while the TS mirror at webview/packages/bus/fixtures/ did not.
Plan 02-02's SUMMARY documented the no-trailing-newline convention (matches
Swift JSONEncoder default) but Plan 02-01's fixtures were authored via a
text editor that added them. Stripping \n lets check-bus-protocol-version.sh
pass without relaxing the byte-equality invariant. Swift CodableRoundTripTests
remain green (24/24) because JSONSerialization ignores trailing whitespace.
- 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.