P1 — server api_service providers and semantic/server/device Prolog config split (#158) #276

Closed
nsaspy wants to merge 1 commit from rage/158-api-service-providers into master
Owner

Closes #158

Summary

Implements the closed server execution-service boundary (epic #150, builds on #157):

  • Three-way Prolog config split: kb/config.pl keeps shared server-safe semantic settings; kb/device_providers.pl (new) holds Linux shell mappings, dictation and sounds and is consulted only by the desktop boot (main.pl, which now owns the user-config load hook); kb/server_providers.pl (new) is the versioned api_service registry (api_service_registry_version(1), flat api_service_provider/3 facts).
  • Server boot server_main.pl (new): capabilities + registry + selector + loader only — never device provider configuration. User semantic overrides ride config_loader:load_server_config/0, which fails the boot loudly (domain_error(zarathushtra_server_config_fact, _)) on server-inappropriate mappings (app mappings, dictation, sounds). Desktop user config.pl files keep working unchanged (device facts now route to kb_device_providers).
  • Versioned provider registry (zara/runtime/api_service.py): thread-safe register/unregister with race tests, live adapter resolution, version-gated against the KB, bounded provider timeouts; builtin adapters for search_server (URL-as-data via config_loader:search_url/2 — no shell, no browser), timer_server (bounded in-process timer service with injectable virtual clock), admin_restart (host restart hook, refuses when absent).
  • Plan execution behind RuntimeHost: PlanExecutionService builds the server PlanEnvironment (registry-owned reachability, no device advertisements, no aliases), selects via pure Prolog plan_for_frame, dispatches ready plans through PlanExecutor under per-provider timeouts; timeouts track the request id so retries replay instead of double-executing. PlanExecutor gains a dynamic adapter_resolver + public track(); #157 refusal contract unchanged.
  • RuntimeHost wiring: [api_service] config gates the service (default off); failed startup is non-fatal (typed event, host stays RUNNING — headless health invariant); restart rebuilds; shutdown clears. No new runtime command, no protocol surface — the ZMQ gateway never invokes services directly.
  • Desktop-hardware server providers (open_desktop, screen_server, pause_server) stay declared but unregistered: a headless server can never become the execution target for desktop commands.

Notable root-cause fix

Full-suite pytest segfaulted because two files declared module kb_config in one SWI process (server boot + wake-words test fixture) — SWI 10 raises 'No permission to redefine module' and the pyswip error path poisons the runtime. server_main.pl therefore does not consult kb/config.pl; overrides assert into an auto-created kb_config, and search_url/2 falls back to the default template when it was never consulted. Baseline verified on a throwaway origin/master worktree (1373 green) before the fix; 1420 green after. Details in rage/158-api-service-providers.org (F1–F3).

Tests

  • New plunit t/api_service.pl (15): boot isolation (device facts and semantic defaults absent), registry facts, server-scope accept/refuse matrix.
  • New pytest t/test_api_service.py (36): spec/registry/timer bounds, register/unregister races, build validation, adapters, plan execution, timeout→track→replay, unknown/disabled provider.
  • New pytest t/test_host_api_service.py (6): host wiring, disabled gate, degraded startup, headless open-app refusal + executor unknown_provider.
  • Config validation tests for malformed [api_service]; device-fact routing tests re-pointed in t/prolog_config.pl, t/sounds.pl, t/dictation_lifecycle.pl, t/process_safety.pl, scripts/test-replies.sh.
  • New gate scripts/test-api-service.sh registered as test-all phase 2c; wiki wiki/api-service.org + capability-plans scope note.

Gate

  • Full local gate: nix develop -c bash scripts/test-all.sh — 12/12 phases passed at HEAD 10f1258.
  • Focused TDD evidence (red/green per increment) in rage/158-api-service-providers.org.
Closes #158 ## Summary Implements the closed server execution-service boundary (epic #150, builds on #157): - **Three-way Prolog config split**: `kb/config.pl` keeps shared server-safe semantic settings; `kb/device_providers.pl` (new) holds Linux shell mappings, dictation and sounds and is consulted only by the desktop boot (`main.pl`, which now owns the user-config load hook); `kb/server_providers.pl` (new) is the versioned `api_service` registry (`api_service_registry_version(1)`, flat `api_service_provider/3` facts). - **Server boot** `server_main.pl` (new): capabilities + registry + selector + loader only — never device provider configuration. User semantic overrides ride `config_loader:load_server_config/0`, which **fails the boot loudly** (`domain_error(zarathushtra_server_config_fact, _)`) on server-inappropriate mappings (app mappings, dictation, sounds). Desktop user `config.pl` files keep working unchanged (device facts now route to `kb_device_providers`). - **Versioned provider registry** (`zara/runtime/api_service.py`): thread-safe register/unregister with race tests, live adapter resolution, version-gated against the KB, bounded provider timeouts; builtin adapters for `search_server` (URL-as-data via `config_loader:search_url/2` — no shell, no browser), `timer_server` (bounded in-process timer service with injectable virtual clock), `admin_restart` (host restart hook, refuses when absent). - **Plan execution behind RuntimeHost**: `PlanExecutionService` builds the server `PlanEnvironment` (registry-owned reachability, no device advertisements, no aliases), selects via pure Prolog `plan_for_frame`, dispatches ready plans through `PlanExecutor` under per-provider timeouts; timeouts track the request id so retries replay instead of double-executing. `PlanExecutor` gains a dynamic `adapter_resolver` + public `track()`; #157 refusal contract unchanged. - **RuntimeHost wiring**: `[api_service]` config gates the service (default off); failed startup is non-fatal (typed event, host stays RUNNING — headless health invariant); restart rebuilds; shutdown clears. No new runtime command, no protocol surface — the ZMQ gateway never invokes services directly. - Desktop-hardware server providers (`open_desktop`, `screen_server`, `pause_server`) stay declared but unregistered: a headless server can never become the execution target for desktop commands. ## Notable root-cause fix Full-suite pytest segfaulted because two files declared `module kb_config` in one SWI process (server boot + wake-words test fixture) — SWI 10 raises 'No permission to redefine module' and the pyswip error path poisons the runtime. `server_main.pl` therefore does not consult `kb/config.pl`; overrides assert into an auto-created `kb_config`, and `search_url/2` falls back to the default template when it was never consulted. Baseline verified on a throwaway `origin/master` worktree (1373 green) before the fix; 1420 green after. Details in `rage/158-api-service-providers.org` (F1–F3). ## Tests - New plunit `t/api_service.pl` (15): boot isolation (device facts and semantic defaults absent), registry facts, server-scope accept/refuse matrix. - New pytest `t/test_api_service.py` (36): spec/registry/timer bounds, register/unregister races, build validation, adapters, plan execution, timeout→track→replay, unknown/disabled provider. - New pytest `t/test_host_api_service.py` (6): host wiring, disabled gate, degraded startup, headless open-app refusal + executor unknown_provider. - Config validation tests for malformed `[api_service]`; device-fact routing tests re-pointed in `t/prolog_config.pl`, `t/sounds.pl`, `t/dictation_lifecycle.pl`, `t/process_safety.pl`, `scripts/test-replies.sh`. - New gate `scripts/test-api-service.sh` registered as test-all phase 2c; wiki `wiki/api-service.org` + capability-plans scope note. ## Gate - Full local gate: `nix develop -c bash scripts/test-all.sh` — 12/12 phases passed at HEAD `10f1258`. - Focused TDD evidence (red/green per increment) in `rage/158-api-service-providers.org`.
docs(rage): record 158 merge evidence
Some checks failed
CI / test (pull_request) Failing after 6s
CI / android skeleton gate (pull_request) Failing after 5s
CI / shared mic / Arch Linux (pull_request) Failing after 8m55s
CI / shared mic / Ubuntu 24.04 (pull_request) Failing after 12m12s
99d9fdf329
nsaspy closed this pull request 2026-09-04 23:09:16 +00:00
Some checks failed
CI / test (pull_request) Failing after 6s
CI / android skeleton gate (pull_request) Failing after 5s
CI / shared mic / Arch Linux (pull_request) Failing after 8m55s
CI / shared mic / Ubuntu 24.04 (pull_request) Failing after 12m12s

Pull request closed

Sign in to join this conversation.
No description provided.