Commit Graph
5 Commits
Author SHA1 Message Date
james.maes 0785d6e57d fix(01-WR-11): typed FeatureFlag enum prevents silent-typo feature disable
Phase 1 REVIEW.md WR-11: `FeatureFlags.isEnabled(_ key: String)` fell
through to `false` on any typo — `"orpheusTtsEnabled"` (wrong case),
`"orpheus_tts_enabled"` (wrong separator), etc. Phase 6 (Orpheus /
WhisperKit) wiring depends on these flags; a silently-disabled
feature caused by a typo is nearly impossible to debug.

packages/Config/Sources/Config/FeatureFlags.swift
- Add `public enum FeatureFlag: String, CaseIterable, Sendable` with
  cases `orpheusTTSEnabled`, `whisperKitSTTEnabled` (both known keys
  per REQUIREMENTS.md).
- Add `isEnabled(_ flag: FeatureFlag)` as the canonical typed path.
- Rename the string-keyed lookup to `isEnabledDynamic(_:)` so
  production callers get a compile error on typos — the dynamic
  string path is retained only for the dev overlay's "list all
  flags" affordance.

packages/Config/Tests/ConfigTests/PerTurnSnapshotTests.swift
- Update two existing tests to use the typed-enum API.
- New `test_featureFlagEnumRawValueMatchesDecoder` — regression guard
  that the enum cases' rawValue matches the JSON key the decoder
  sees, so a rename to `orpheusTtsEnabled` fails the build, not
  silently breaks feature detection.

No production callers of the old string-keyed `isEnabled` exist
outside tests — no migration needed elsewhere.
2026-04-23 16:29:54 -05:00
james.maes 569bc80451 fix(01-WR-04): disambiguate SchemaMigrator error payloads
Phase 1 REVIEW.md WR-04: `ConfigError.futureSchema(version:)` and
`ConfigError.unknownSchemaVersion(Int)` both carried a single Int
whose meaning depended on context — `futureSchema(2)` when migrating
`from: 2, to: 1` reports "2" but users read it as "your target is 2"
rather than "you have v2, this build only supports v1". For
multi-step migrations, `unknownSchemaVersion` also reported an
intermediate version, not the user's file version.

packages/Config/Sources/Config/ConfigError.swift
- `futureSchema(have:supports:)`: `have` is the user's version,
  `supports` is `SchemaMigrator.currentVersion`.
- `unknownSchemaVersion(have:supports:)`: `have` is the intermediate
  source version at the failing step, `supports` is the migration
  target. Callers can report "config is at v3, Jarvis supports up to
  v2" unambiguously.

packages/Config/Sources/Config/SchemaMigrator.swift
- Propagate `to:` through `migrateStep` so the thrown
  `unknownSchemaVersion` carries both numbers.

packages/Config/Tests/ConfigTests/SchemaMigratorTests.swift
- Update both negative-path assertions to the new enum shape.
2026-04-23 16:18:41 -05:00
james.maes e3024cdae3 fix(01-WR-03): DNS-rebind-safe Ollama host validation
Phase 1 REVIEW.md WR-03 (AGENT-05): `OllamaConfig.validateHost` only
string-matched the URL host against `{127.0.0.1, localhost, ::1}` — a
hijacked `/etc/hosts` or resolver that points `localhost` to a
non-loopback IP would pass validation, violating AGENT-05's "local
only" intent. IPv6 bracket handling (`[::1]`) was also inconsistent
across macOS versions.

packages/Config/Sources/Config/OllamaConfig.swift
- Normalize IPv6-bracketed hosts (`[::1]` → `::1`) before the string
  allowlist check.
- For IP literals (127.0.0.1, ::1), skip resolution — loopback by
  construction.
- For `localhost`, run `getaddrinfo` with AF_UNSPEC + AI_ADDRCONFIG
  and assert every resolved address is loopback:
    * IPv4: top octet == 0x7f (127.0.0.0/8)
    * IPv6: 15 zero bytes followed by 0x01 (::1)
- Fail closed on `getaddrinfo` errors or unknown address families.

Existing tests still pass (15/15) — no test changes needed because
`127.0.0.1` short-circuits the getaddrinfo path and the existing
config fixtures all use IP literals, not localhost.
2026-04-23 16:16:55 -05:00
james.maesandClaude Opus 4.7 1b2f067376 feat(01-02): build Config package (AGENT-05, SEC-05, SEC-08, OBS-05)
- Split config into LaunchSnapshot (frozen security keys: ollama,
  applescript, toolBlocklist, confirmationPolicy, logging) and
  PerTurnSnapshot (mutable: provider, tts, stt, featureFlags) per SEC-05
- OllamaConfig.init(from:) rejects non-localhost hosts at decode time
  with ConfigError.invalidOllamaHost (AGENT-05)
- AppleScriptPolicy has no skipAllowlist/bypass/regex field (SEC-08);
  reflection test ConfigSplitTests.test_noSkipAllowlistKey enforces
- FeatureFlags lives in PerTurnSnapshot only (OBS-05); reflection test
  LaunchSnapshotTests.test_featureFlagsNotInLaunchSnapshot enforces
- SchemaMigrator: currentVersion=1, v1->v1 pass-through, future
  versions throw ConfigError.futureSchema
- ConfigLoader.loadSnapshots: peek schemaVersion, migrate, decode both
  snapshots; wraps DecodingError into ConfigError.malformed
- ConfigStore actor holds launch + streams perTurn updates (file-
  watcher wiring is Plan 03 scope)
- Bundled default-config.json resource
- Tests: 15/15 pass covering all behaviors listed in the plan
- Deletes Plan 01 placeholders

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 13:19:41 -05:00
james.maes d8617ffe01 feat(01-01): add four SPM packages with Swift 6 strict concurrency
- packages/Keychain — zero SPM deps (Security.framework at link time only)
- packages/Logging — depends on apple/swift-log 1.5.3+; library product is
  named JarvisLogging to avoid collision with swift-log's own Logging module
- packages/Config — depends on Keychain + Logging (product JarvisLogging)
- packages/Shell — depends on Config + Logging; links AppKit,
  ServiceManagement, IOKit, Carbon for hotkey/launch-at-login surfaces

Every target (main + test) opts into .swiftLanguageMode(.v6) per D-04 so
data-race errors are compile-time failures, not warnings.

Placeholder sources (one per package) plus one trivial XCTest per package
per D-03 so each package builds standalone and has a passing test. Real
sources land in Plan 02 (Keychain/Config/Logging) and Plan 04 (Shell).

Bus/LLM/MCP/Voice/Memory packages NOT pre-created per D-01 — they arrive
in their owning phases (P2/P4/P5/P6/P7) to keep rebuild scope tight.
2026-04-22 12:21:07 -05:00