Branch: fixes-and-improvements (pushed on top of main).
Scratch tracking for the follow-up work surfaced by the repo evaluation; kept on
the branch so the remaining items travel with it.
Everything from that evaluation is represented below, with honest status.
- done and pushed
- [~] done but unverified (no Swift toolchain / JavaScriptCore in this Linux env)
- [/] partially done — see sub-items
- not started
Root cause: synchronous bridge model settles the whole promise graph inside one
evaluateScript call, so the old wall-clock poll loop never ran while JS was
executing. The full "make the runtime resource-bounded" milestone is bigger than
just the watchdog:
- Preemptive watchdog via
JSContextGroupSetExecutionTimeLimit(through theCCodeModeJSCshim) → realtimeoutMs,while(true){}terminates.- macOS verification found the Linux-authored version completely broken:
on current OS releases (observed macOS 26.5) JSC does not re-arm the time
limit when the callback returns false — the callback fires exactly once at
50ms and the watchdog is dead for the rest of the execution, so nothing was
ever terminated (first full
swift testhung indefinitely with six runaway JS threads). Root-caused with a minimal C reproduction; fixed by having the callback re-install the time limit itself before returning false (codeModeWatchdogShouldTerminateinExecutionWatchdog.swift).
- macOS verification found the Linux-authored version completely broken:
on current OS releases (observed macOS 26.5) JSC does not re-arm the time
limit when the callback returns false — the callback fires exactly once at
50ms and the watchdog is dead for the rest of the execution, so nothing was
ever terminated (first full
- Real cancellation —
cancel()interrupts in-flight JS. - Tests for the above (
ExecutionWatchdogTests.swift) — all 8 pass on macOS after the re-arm fix (loop, promise-chain loop, runaway getter during serialization, cancellation, catch-proof termination, context recovery). - JS heap / memory cap — nothing bounds
JSContext/JSContextGroupheap;new Array(1e9)can still exhaust host memory. NOT addressed. - Bound on concurrent executions —
executionQueueis.concurrentwith spin-waiting workers (BridgeRuntime.swift:25-29), so N long-running scripts occupy N threads. No concurrency cap. NOT addressed. -
runOnExecutionQueuestill has no task-cancellation handler wired into the dispatched block (best-effort only). NOT addressed.
-
NetworkAccessPolicyonCodeModeConfiguration: by default requests are limited to public hosts — private / loopback / link-local / local-network addresses (including alternate numeric and IPv6-embedded spellings) andlocalhost/.local/.internal/trailing-dot names are declined. - Redirect targets re-checked against the policy before being followed.
- Response-size cap (default 10 MB), enforced by Content-Length pre-check + streaming cancel.
- Allow/deny host lists;
.permissiveopt-out for hosts that want the old unrestricted behavior. - Request destinations (allowed + declined) now recorded in the audit log, not just the execution transcript.
- Tests (
NetworkAccessPolicyTests.swift) — pass on macOS (2026-07-11).
- README no longer tells users to depend on
from: "0.1.0"against a tagless repo; documents branch-based install for now. - Tag
0.1.0(the actual fix) once CI is green, then restore the versioned SPM install snippet. NOT done.
- Watchdog compile blocker (found in self-review):
JSContextGroupSetExecutionTimeLimit/...Clear...are private-header JSC symbols; addedCCodeModeJSCC shim so it builds against the public SDK.- Owner decision still needed: private-but-exported symbol → App Store review risk. Keep+document (current) vs gate vs accept.
- Spurious timeout (self-review): poll loop discarded an already-settled
result at/just past deadline. Fixed via shared
waitForSettlement. - Serialization hang (self-review): a runaway getter/
toJSONcould occupy the thread because the watchdog was uninstalled before decode. Fixed via rearm. - HealthKit always denied through the default broker. Investigated on
macOS (2026-07-11): the claimed mechanism does not exist in current code — no
built-in registration declares
.healthKitinrequiredPermissions, so the registry's requested→notDetermined→denied path never fires for health.HealthBridgetreats.notDeterminedas passable and performs real per-typerequestAuthorizationitself. Added a registry test pinning the invariant (no registration may gate on.healthKit, since the default broker can never report.grantedfor it). - Calendar-span validation drift. Fixed: constraint validation is now
case-insensitive (matching the
lowercased()idiom used by effectively every bridge), and thecalendarDeletespan list includes the alias spellingsEventKitBridgeaccepts. Also removed the now-redundant lowercasevideoQualityduplicates. Tests added. -
DispatchQueue.main.syncinrequestLocationPermission. Fixed:main.async+ the existing 10s delegate wait. (The deadlock was background execution thread →main.syncwhile the host blocks main waiting on the JS result; theThread.isMainThreadbranch already covered the direct case.) - Duplicated eval model types will drift.
LLM.swiftis excluded from the build; ~250 lines of report/suite types are defined twice (there and in compiledLLMEvalModels.swift) with nothing keeping them in sync. NOT addressed.
Biggest maintenance risk was ~115 capabilities / ~2,540 lines of hand-written
registrations with four parallel sources of truth. Phases 1–3 of
PLAN-registration-macros.md landed 2026-07-11/12 (owner approved swift-syntax
in the core graph): 114 of 115 capabilities are now macro-authored
@BuiltInCodeMode tools; only networkFetch remains on the flat init
(PHASE3-SKIP — its nested dotted-path arguments can't be expressed by the flat
tool model).
- Move argument types/constraints to per-capability, co-located declarations
—
BuiltInCodeModeToolprotocol +CodeModeStringEnum(constrained string args declared once: advertised values, decode, and bridge parsing all come from the enum). All seven domains migrated; the centralCapabilityArgumentConstraints.defaults(for:)table now holds only networkFetch'soptions.responseEncodingdotted-path row.- fail loudly instead of degrading unknown args to
.any(inferArgumentTypes) — still pending; only networkFetch depends on it now, so the table can be deleted once that capability is handled.
- fail loudly instead of degrading unknown args to
- Add a test asserting registration metadata matches bridge reality —
CapabilityMetadataGoldenTestspins the full advertised surface of all 115 capabilities against a committed JSON baseline;constrainedArgumentMetadataIsCoherentForAllRegistrations+ the span test pin enum↔descriptor↔bridge agreement. (Golden-testingresultSummaryagainst bridge JSON encoders still open — the golden pins the string, not the encoder.) - Standardize on one registration idiom —
BuiltInCodeModeTool/@BuiltInCodeModeis now the sole idiom for built-ins; the flat descriptor init survives only for the single networkFetch skip. The raw-CodeModeRegistrationandbuiltInCapability:glue idioms are gone. - Fifth metadata surface found during migration: the hand-written JS
function table in
RuntimeJavaScript.swift(e.g.completeReminderinjectsoperation: 'complete', isCompleted: true). Untouched by Phase 3; candidate for generation from registrations as a follow-up. - Unify permission ownership —
calendarReadchecks in both registry and bridge;calendarWritechecks only in the bridge. - Decide the fate of
Tools/CodeModeAuthoring— resolved 2026-07-11: owner approved swift-syntax in the core graph; the package is folded into the root package as theCodeModeAuthoringproduct and itsCodeModeMacrosplugin now also backs the internal@BuiltInCodeModemacro (Phases 1+2 ofPLAN-registration-macros.mdlanded; EventKit is macro-authored). Remaining: per-domain migration (Phase 3) and enum-constraint support for the host-facing@CodeMode.
- [/] Core policy layer has zero dedicated tests.
-
PathPolicy(PathPolicyTests.swift): empty/whitespace, scoped roots, appGroup configured/unconfigured, absolute in/out,..escapes vs internal.., nonexistent nested paths, symlink escape vs symlink between roots. -
ArtifactStore+AuditLogger(CorePolicySupportTests.swift). -
SystemPermissionBroker— not unit-testable as written: every path terminates in a real OS framework call (CLLocationManager, EKEventStore, CNContactStore…), so tests would prompt/flake. Needs seams (injectable status providers) first; fold into the metadata/permission refactor.
-
- [/] CI never compiles iOS/visionOS code.
- Added a
platform-buildmatrix job (xcodebuild buildfor iOS + visionOS) so the UIKit presenters and theCCodeModeJSCshim compile against those SDKs on every PR/push. - Added SwiftPM build caching and
xcodebuild -versiontoolchain logging to the deterministic job. - macOS
swift testjob already existed and links the new C shim + watchdog on macOS — this is what verifies the private-symbol link. - Still missing: lint/format config + check, explicit Xcode/SDK pinning (currently uses runner default), artifact upload of eval reports.
- Note: the iOS/visionOS build verifies compilation; the private JSC symbol's
dynamic-link resolution is exercised by the macOS
swift testlink. Full iOS link verification would need a test bundle / host app.
- Added a
- Eval coverage gaps.
health.*,vision.*,photos.read/export,reminders.writehave zero scenarios among the 49, though the LLM prompt advertises photos/health/home/alarms. Add catalog + execution/validation scenarios for each. - Regression gate is inert in automation.
compareis never invoked in CI; thecatalogbaseline EVALS.md references doesn't exist. Generate/commit it and wirecompareinto a (gated) CI job. - No cost/token tracking in LLM eval reports — "budget" is a request count only; capture input/output tokens (+ derived cost) per run so model comparisons can weigh accuracy against price.
- Async bridge model. Resolve JS promises from Swift callbacks instead of
synchronous native returns, so long native ops (30s network, 15s permission
prompts) don't block inside
evaluateScript. Prerequisite for fully real mid-script cancellation; pairs with the watchdog as the "production-grade runtime" milestone. - Structured audit pipeline. Today's events are capability + free-text "success" with a pull-based drain. Move to a push-based sink with argument digests, outcomes, path/destination targets, and a per-execution correlation ID. (Partial down payment already made: fetch destinations now recorded.)
-
Examples/host app. Minimal SwiftUI demo wiringCodeModeAgentTools- a real
UIKitSystemUIPresenter+ an LLM loop — concrete integration story and a manual test bed for the UIKit presenters automation can't reach.
- a real
- DocC + doc comments. Zero
///comments inSources/today. Start with the public surface:CodeModeAgentTools,CodeModeConfiguration,SystemUIPresenter,CodeModeToolError,CapabilityID. - API polish for integrators. Group the 19-parameter
CodeModeConfigurationinit's service clients; clarify/mergeallowedCapabilitiesvsallowedCapabilityKeys; emit the validCapabilityIDlist as an enum in theexecuteJavaScriptJSON schema so agents get machine-checkable capability names.
- Apply the network destination policy to
apple.web.present/apple.auth.webAuthenticate(these open a visible browser view; the policy currently coversnetwork.fetch). - Data-driven address-range list in
NetworkAccessPolicy(ranges are hardcoded today). - Reconcile network audit entries with the runtime's generic success/failure audit (two entries per fetch — kept intentionally for destination detail).
- Migrate bespoke
NSLockstate (ExecutionWatchdog,FetchTaskHandler) toLockedBox/SynchronizedBoxwhere compound atomicity isn't needed. Cosmetic.
- [/] Verify on macOS — done 2026-07-11 (macOS 26.5, Swift 6.3.2 / Xcode 26.5):
-
CCodeModeJSCshim links and the private symbols resolve at load time on macOS. (iOS/visionOS remain compile-verified only, via CI.) -
swift testpasses — 206 tests, 0 failures, ~5s. Note: the first verification run hung indefinitely and exposed the watchdog re-arm bug (see CRITICAL ISSUES §1); after the fix the suite is green. - Watchdog terminates
while(true){}on an iOS device/simulator — verified on macOS only; JSC ships per-OS, so worth one manual check on a simulator before tagging.
-
- Quick wins: tag
0.1.0, fix calendar-span drift, add core-policy-layer tests, add lint + iOS build job to CI. - Runtime hardening milestone: (watchdog ✔) + JS heap cap + concurrency bound → "resource- and network-bounded runtime."
- Metadata-consolidation refactor + macro decision, protected by new drift tests.
- Decision on the private-but-exported JSC symbol (App Store risk)?
- After CI is green: tag
0.1.0and restore the versioned install snippet?