|
| 1 | +# Phase 3 handoff: migrate remaining registrations to `@BuiltInCodeMode` |
| 2 | + |
| 3 | +Self-contained work spec for converting the remaining hand-written capability |
| 4 | +registrations to the macro-authored tool idiom. Context: Phases 1–2 of |
| 5 | +`PLAN-registration-macros.md` are done; EventKit is the finished reference. |
| 6 | +This is mechanical work — the invariants below matter more than speed. |
| 7 | + |
| 8 | +## The one invariant |
| 9 | + |
| 10 | +**The advertised capability surface must not change except where this spec |
| 11 | +says it may.** `Tests/CodeModeTests/CapabilityMetadataGoldenTests.swift` pins |
| 12 | +every registration's full metadata (jsNames, title, summary, tags, example, |
| 13 | +permissions, argument lists/types/hints, constraints, resultSummary) against |
| 14 | +`Tests/CodeModeTests/capability-metadata-golden.json`. |
| 15 | + |
| 16 | +Workflow per domain: |
| 17 | +1. Convert the domain (recipe below). Run `swift test`. The golden test fails. |
| 18 | +2. Regenerate: `CODEMODE_REGENERATE_GOLDEN=1 swift test --filter capabilityMetadata` |
| 19 | +3. `git diff Tests/CodeModeTests/capability-metadata-golden.json` — **this diff |
| 20 | + is the review artifact.** Every hunk must be one of the allowed diffs below; |
| 21 | + anything else is a bug in your conversion. Fix the conversion, not the spec. |
| 22 | +4. Full `swift test` green → one commit for the domain, golden diff included, |
| 23 | + and the commit message lists which allowed-diff categories appear. |
| 24 | + |
| 25 | +Allowed golden diffs: |
| 26 | +- `argumentHints` gaining entries for arguments that previously had no hint |
| 27 | + (the tool idiom requires a hint per argument — write one consistent with the |
| 28 | + bridge's actual behavior, and call it out in the commit message). |
| 29 | +- `allowedStringValues` gaining alias spellings **only when the bridge already |
| 30 | + accepts them** (cite the bridge line in the commit message). |
| 31 | +- Nothing else. Not a reworded title, not a reordered jsNames array, not a |
| 32 | + type change, not a constraint that moved keys. |
| 33 | + |
| 34 | +## Reference implementation (read these first) |
| 35 | + |
| 36 | +- `Sources/CodeMode/Bridges/EventKitCodeModeTools.swift` — 9 macro-authored |
| 37 | + tools + 5 `CodeModeStringEnum`s. The pattern to replicate. |
| 38 | +- `Sources/CodeMode/Bridges/CapabilityRegistrations+EventKit.swift` — what a |
| 39 | + registration file looks like after conversion (a thin list). |
| 40 | +- `Sources/CodeMode/Bridges/BuiltInCodeModeTool.swift`, |
| 41 | + `Sources/CodeMode/Bridges/BuiltInCodeModeMacros.swift`, |
| 42 | + `Sources/CodeMode/API/CodeModeStringEnum.swift` — the infrastructure. |
| 43 | +- Commits `6f5e7e8` (Phase 1) and `c45fb10` (Phase 2) show the full shape of a |
| 44 | + domain conversion including bridge rewiring and tests. |
| 45 | + |
| 46 | +## Recipe per registration |
| 47 | + |
| 48 | +1. Create `Sources/CodeMode/Bridges/<Domain>CodeModeTools.swift`. One |
| 49 | + `@BuiltInCodeMode` struct per `CapabilityRegistration` in the old file. |
| 50 | +2. Copy **verbatim**: title, summary, tags, example, requiredPermissions, |
| 51 | + resultSummary. Do not improve the prose. |
| 52 | +3. `path` = the registration's first jsName; `aliases:` = the rest, in order. |
| 53 | +4. `Arguments` struct: one `@ToolParam("<exact existing hint>")` property per |
| 54 | + declared argument, in the old required-then-optional order. Required args |
| 55 | + are non-optional Swift types; optional args are optionals. |
| 56 | +5. **Property types must reproduce the old effective type.** If the old |
| 57 | + descriptor declared `argumentTypes`, match it. If it didn't, the effective |
| 58 | + type came from `CapabilityDescriptor.inferArgumentTypes` |
| 59 | + (`CapabilityRegistry.swift`) — look each name up in that table: |
| 60 | + `.string`→`String`, `.number`→`Int` or `Double` (pick what the bridge |
| 61 | + reads), `.bool`→`Bool`, `.array`→`[String]`/`[JSONValue]` (match bridge), |
| 62 | + `.object`→`[String: JSONValue]`, and **names absent from the table were |
| 63 | + `.any` — declare those as `JSONValue`**, never a tighter type. The golden |
| 64 | + test catches mistakes here; trust it. |
| 65 | +6. Constrained string arguments — any argument with a row in |
| 66 | + `CapabilityArgumentConstraints.defaults(for:)` (`CapabilityRegistry.swift`): |
| 67 | + - Define a `CodeModeStringEnum` whose **raw values are exactly the current |
| 68 | + advertised list** (case names = raw values). Add `codeModeAliases` only |
| 69 | + for spellings the bridge demonstrably accepts. |
| 70 | + - Use it as the property type; the macro derives the constraint from it. |
| 71 | + - Rewire the bridge's own parsing of that value to |
| 72 | + `EnumType.codeModeValue(matching:)` (see `EventKitBridge.eventSpan`, |
| 73 | + `SystemUIBridge.validateCalendarPickerArguments` for the pattern), so the |
| 74 | + enum is the single source of truth. |
| 75 | + - Delete the row from the `defaults(for:)` table in the same commit. |
| 76 | + - Exception: **dotted-path constraints** (`networkFetch`'s |
| 77 | + `options.responseEncoding`) stay in the central table — the tool argument |
| 78 | + model is flat. Leave them and note it. |
| 79 | +7. End every `Arguments` struct with `var raw: [String: JSONValue]` and call |
| 80 | + the same bridge method the old handler called, passing `arguments.raw` and |
| 81 | + the same `context`. Do not change bridge method signatures beyond the |
| 82 | + constrained-value parsing rewiring in step 6. |
| 83 | +8. Replace the old file's body with the thin builder-extension list (keep the |
| 84 | + function name the builder calls, e.g. `systemUIRegistrations()` — see |
| 85 | + `DefaultCapabilityLoader.loadAll()` for the roster). |
| 86 | + |
| 87 | +## Domain order and notes |
| 88 | + |
| 89 | +Work sequentially, one commit per domain, full suite green each time: |
| 90 | + |
| 91 | +1. **SystemUI** (`CapabilityRegistrations+SystemUI.swift`, 12 registrations) — |
| 92 | + heaviest constraint user: `photosUIPick`/`contactsUIPick`/`cameraUICapture`/ |
| 93 | + `cameraUIScanData` rows in the defaults table, and `SystemUIBridge` has |
| 94 | + matching `lowercased()` validations to rewire (mediaType, cameraDevice, |
| 95 | + flashMode, videoQuality, scan mode, preferredStyle…). Only enum-ify values |
| 96 | + that have a defaults-table row today; leave other `lowercased()` checks |
| 97 | + alone. |
| 98 | +2. **Core** (`+Core.swift`, 11) — includes `networkFetch` (dotted-path |
| 99 | + constraint stays in the table) and the filesystem/keychain area. Keychain is |
| 100 | + already converted; don't touch `SimpleBuiltInCodeModeProviders.swift`. |
| 101 | +3. **PeoplePhotosDocuments** (`+PeoplePhotosDocuments.swift`, 13) — |
| 102 | + `photosRead` mediaType row; `PhotosBridge` lowercases mediaType, rewire it. |
| 103 | +4. **SystemServices** (`+SystemServices.swift`, 19) — health/home/alarm/ |
| 104 | + notifications. Do **not** add `.healthKit` to any `requiredPermissions` |
| 105 | + (see `noBuiltInRegistrationGatesOnHealthKitPermission` test). Preserve the |
| 106 | + existing permission declarations exactly. |
| 107 | +5. **CloudPushSpeech** (`+CloudPushSpeech.swift`, 15) — the four CloudKit |
| 108 | + capabilities share the `database` row; one enum, four tools. |
| 109 | +6. **IntentsModelsActivityMaps** (`+IntentsModelsActivityMaps.swift`, 18) — |
| 110 | + `activityEnd` dismissalPolicy and `mapsRouteEstimate`/`mapsOpen` |
| 111 | + transportType rows. transportType is consumed in |
| 112 | + `SystemAppleServiceClients.swift` (`SystemMapsMapping`) — rewire there. |
| 113 | +7. **Commerce** (`+Commerce.swift`, 18) — mostly pass-through to host-supplied |
| 114 | + clients (music/passKit/storeKit). Convert metadata + `musicPlaybackControl` |
| 115 | + action enum (replicate the existing list; host clients stay authoritative |
| 116 | + for semantics). Do not invent constraints for values the table doesn't |
| 117 | + constrain today. |
| 118 | + |
| 119 | +## Hard rules |
| 120 | + |
| 121 | +- Do not touch `Sources/CodeMode/Runtime/RuntimeJavaScript.swift` (the JS |
| 122 | + function table is a separate Phase-3 item, not this task). |
| 123 | +- Do not reword any advertised string. Copy-paste, don't retype. |
| 124 | +- Do not change permission ownership (some capabilities deliberately declare |
| 125 | + `requiredPermissions: []` and check in the bridge — e.g. calendarWrite). |
| 126 | +- Do not migrate `LocationWeather` or `EventKit` (done) and do not modify |
| 127 | + `Tools/CodeModeEval` or CI. |
| 128 | +- If a registration doesn't fit the recipe (unexpected handler shape, shared |
| 129 | + state, anything surprising), **stop and leave that registration on the old |
| 130 | + idiom in its file** with a `// PHASE3-SKIP: <reason>` comment rather than |
| 131 | + improvising. Mixed files are fine; wrong conversions are not. |
| 132 | +- When all domains are done: delete any now-empty rows from `defaults(for:)`, |
| 133 | + and update `TODO.md`'s structural-improvements section + the status block in |
| 134 | + `PLAN-registration-macros.md`. |
| 135 | + |
| 136 | +## Definition of done (per domain) |
| 137 | + |
| 138 | +- `swift test` fully green (230+ tests). |
| 139 | +- Golden diff contains only allowed categories, enumerated in the commit |
| 140 | + message. |
| 141 | +- The old registration file is a thin list; its metadata lives on tools. |
| 142 | +- Constraint rows for the domain are deleted from the central table (except |
| 143 | + dotted paths) and the owning bridge parses through the shared enum. |
0 commit comments