Skip to main content

kcode_k1_daemon_lib/
lib.rs

1#![doc = include_str!("../Documentation.md")]
2
3use axum::body::{Body, to_bytes};
4use axum::extract::Request;
5use axum::http::header::{CACHE_CONTROL, CONTENT_LENGTH, CONTENT_TYPE, HOST};
6use axum::http::{HeaderValue, StatusCode};
7use axum::middleware::{self, Next};
8use axum::response::Response;
9use axum::routing::get;
10use axum::{Json, Router};
11use kcode_gemini_3_1_pro::Gemini31Pro;
12use kcode_k1_access::K1Access;
13use kcode_k1_access_full_audio::K1AccessFullAudio;
14use kcode_k1_access_persons::K1AccessPersons;
15use kcode_k1_access_profiles::K1AccessProfiles;
16use kcode_k1_accounting::Accounting;
17use kcode_k1_accounts::K1Accounts;
18use kcode_k1_audio_classification::AudioClassification;
19use kcode_k1_codex_adapter::{
20    Adapter as CodexAdapter, Config as CodexConfig, Error as CodexAdapterError,
21};
22use kcode_k1_daemon_files::DaemonFiles;
23use kcode_k1_full_audio::K1FullAudio;
24use kcode_k1_groups::{K1Groups, ModelId};
25use kcode_k1_http::{Config as HttpConfig, K1Http};
26use kcode_k1_http_accounts::K1HttpAccounts;
27use kcode_k1_http_people::K1HttpPeople;
28use kcode_k1_http_replay::{ReplayConfig, ReplayWindow};
29use kcode_k1_invites::K1Invites;
30use kcode_k1_objects::K1Objects;
31use kcode_k1_peering::K1Peering;
32use kcode_k1_persons::K1Persons;
33use kcode_k1_txn_ordering::K1TxnOrdering;
34use kcode_k1_users::K1Users;
35use kcode_k1_vault::{ExposeSecret, K1Vault, SecretString};
36use kcode_speaker_v3_analysis::Analyzer;
37use serde::Serialize;
38use serde_json::Value;
39use std::ffi::OsString;
40use std::fmt;
41use std::io::Write as _;
42use std::path::{Path, PathBuf};
43use std::process::ExitCode;
44use std::sync::Arc;
45use std::time::{Duration, Instant};
46use tokio::net::TcpListener;
47use tokio::signal::unix::{Signal, SignalKind, signal};
48
49const LISTEN_ADDRESS: &str = "127.0.0.1:4450";
50const PUBLIC_ORIGIN: &str = "http://localhost:4450";
51const INVITE_LINK_URL: &str = "http://localhost:4321/lib/kcode-k1-ui/*/account.html";
52const AUTHORITY: &str = "localhost:4450";
53const STARTUP_BOUND: Duration = Duration::from_millis(100);
54const GEMINI_OPERATION_TIMEOUT: Duration = Duration::from_secs(30 * 60);
55const GEMINI_API_KEY: &str = "gemini-api-key";
56const CODEX_EXECUTABLE_ENV: &str = "K1_CODEX_EXECUTABLE";
57const CODEX_MODEL: &str = "gpt-5.6-terra";
58const GEMINI_MODEL_BYTES: [u8; 32] = *b"gemini-3.1-pro-preview..........";
59const TERRA_MODEL_BYTES: [u8; 32] = *b"gpt-5.6-terra...................";
60const API_OPERATION: &str = "serve API request";
61
62#[derive(Clone, Serialize)]
63struct PublicConfig {
64    protocol: &'static str,
65    server_id: String,
66    public_origin: &'static str,
67}
68
69#[derive(Serialize)]
70struct Ready {
71    event: &'static str,
72    public_origin: &'static str,
73    unused_invites: usize,
74}
75
76struct Prepared {
77    app: Router,
78    listener: TcpListener,
79    signals: Signals,
80    unused_invites: usize,
81    vault: Arc<K1Vault>,
82}
83
84struct Signals {
85    interrupt: Signal,
86    terminate: Signal,
87}
88
89enum StartupError {
90    Generic,
91    CodexAdapter(CodexAdapterError),
92}
93
94impl From<()> for StartupError {
95    fn from((): ()) -> Self {
96        Self::Generic
97    }
98}
99
100impl fmt::Display for StartupError {
101    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Self::Generic => formatter.write_str("kcode-k1-daemon: startup failed"),
104            Self::CodexAdapter(error) => {
105                write!(formatter, "kcode-k1-daemon: startup failed: {error}")
106            }
107        }
108    }
109}
110
111pub fn run(k1_root: PathBuf) -> ExitCode {
112    let runtime = match tokio::runtime::Builder::new_multi_thread()
113        .enable_all()
114        .build()
115    {
116        Ok(runtime) => runtime,
117        Err(_) => {
118            eprintln!("kcode-k1-daemon: startup failed");
119            return ExitCode::from(1);
120        }
121    };
122    let passphrase = match rpassword::prompt_password("Unlock K1 vault: ") {
123        Ok(passphrase) => match protect_passphrase(passphrase) {
124            Ok(passphrase) => passphrase,
125            Err(()) => {
126                eprintln!("kcode-k1-daemon: startup failed");
127                return ExitCode::from(1);
128            }
129        },
130        Err(_) => {
131            eprintln!("kcode-k1-daemon: startup failed");
132            return ExitCode::from(1);
133        }
134    };
135    runtime.block_on(run_async(k1_root, passphrase))
136}
137
138fn protect_passphrase(passphrase: String) -> Result<SecretString, ()> {
139    (!passphrase.is_empty())
140        .then(|| SecretString::from(passphrase))
141        .ok_or(())
142}
143
144async fn run_async(k1_root: PathBuf, passphrase: SecretString) -> ExitCode {
145    let started = Instant::now();
146    let prepared = match startup(k1_root, passphrase).await {
147        Ok(prepared) => prepared,
148        Err(error) => {
149            warn_if_slow(started.elapsed(), "error");
150            eprintln!("{error}");
151            return ExitCode::from(1);
152        }
153    };
154    let elapsed = started.elapsed();
155    if write_readiness(prepared.unused_invites).is_err() {
156        warn_if_slow(elapsed, "error");
157        eprintln!("kcode-k1-daemon: startup failed");
158        return ExitCode::from(1);
159    }
160    warn_if_slow(elapsed, "ready");
161    let Prepared {
162        app,
163        listener,
164        signals,
165        vault,
166        ..
167    } = prepared;
168    let result = axum::serve(listener, app)
169        .with_graceful_shutdown(signals.wait())
170        .await;
171    drop(vault);
172    match result {
173        Ok(()) => ExitCode::SUCCESS,
174        Err(_) => {
175            eprintln!("kcode-k1-daemon: listener failed");
176            ExitCode::from(1)
177        }
178    }
179}
180
181async fn startup(k1_root: PathBuf, passphrase: SecretString) -> Result<Prepared, StartupError> {
182    let state_root = state_root(&k1_root);
183    let files = DaemonFiles::open(&state_root).map_err(|_| ())?;
184    let ordering = Arc::new(K1TxnOrdering::open(&state_root.join("ordering")).map_err(|_| ())?);
185    let peering = Arc::new(
186        K1Peering::open(&state_root.join("peering"), Arc::clone(&ordering)).map_err(|_| ())?,
187    );
188    let vault = open_vault(
189        &state_root,
190        passphrase,
191        Arc::clone(&ordering),
192        Arc::clone(&peering),
193    )?;
194    let persons = Arc::new(
195        K1Persons::open(
196            &state_root.join("persons"),
197            Arc::clone(&ordering),
198            Arc::clone(&peering),
199        )
200        .map_err(|_| ())?,
201    );
202    let invites = Arc::new(
203        K1Invites::open(
204            &state_root.join("invites"),
205            Arc::clone(&ordering),
206            Arc::clone(&peering),
207        )
208        .map_err(|_| ())?,
209    );
210    let accounts = Arc::new(K1Accounts::open(Arc::clone(&invites)).map_err(|_| ())?);
211    let users = Arc::new(K1Users::new(Arc::clone(&accounts), Arc::clone(&persons)));
212    let groups = Arc::new(
213        K1Groups::open(
214            &state_root.join("groups"),
215            Arc::clone(&ordering),
216            Arc::clone(&peering),
217        )
218        .map_err(|_| ())?,
219    );
220    let profiles = Arc::new(
221        K1AccessProfiles::open(
222            &state_root.join("access-profiles"),
223            Arc::clone(&ordering),
224            Arc::clone(&peering),
225        )
226        .map_err(|_| ())?,
227    );
228    let gemini_key = vault.secret(GEMINI_API_KEY).map_err(|_| ())?.ok_or(())?;
229    let gemini = Gemini31Pro::new(
230        gemini_key.expose_secret().to_owned(),
231        Accounting::new(),
232        GEMINI_OPERATION_TIMEOUT,
233    )
234    .map_err(|_| ())?;
235    let codex_adapter = CodexAdapter::open(codex_config(
236        std::env::current_dir().map_err(|_| ())?,
237        std::env::var_os(CODEX_EXECUTABLE_ENV),
238    ))
239    .await
240    .map_err(StartupError::CodexAdapter)?;
241    let analyzer = Analyzer::from_codex_adapter(gemini, codex_adapter);
242    let objects =
243        Arc::new(K1Objects::open(Arc::clone(&ordering), Arc::clone(&peering)).map_err(|_| ())?);
244    let classification = Arc::new(
245        AudioClassification::open(
246            &state_root.join("audio-classification"),
247            Arc::clone(&ordering),
248            Arc::clone(&peering),
249            Arc::clone(&objects),
250            analyzer,
251        )
252        .map_err(|_| ())?,
253    );
254    let full_audio = Arc::new(
255        K1FullAudio::open(
256            resolve_ffmpeg()?,
257            Arc::clone(&objects),
258            Arc::clone(&classification),
259        )
260        .map_err(|_| ())?,
261    );
262    let access = Arc::new(
263        K1Access::open(
264            &state_root.join("access"),
265            Arc::clone(&ordering),
266            Arc::clone(&peering),
267            Arc::clone(&groups),
268        )
269        .map_err(|_| ())?,
270    );
271    let access_persons = Arc::new(
272        K1AccessPersons::open(
273            Arc::clone(&access),
274            Arc::clone(&profiles),
275            Arc::clone(&persons),
276        )
277        .map_err(|_| ())?,
278    );
279    let audio = Arc::new(
280        K1AccessFullAudio::open_for_models(
281            Arc::clone(&access),
282            Arc::clone(&profiles),
283            full_audio,
284            classification,
285            Arc::clone(&groups),
286            audio_models().to_vec(),
287        )
288        .map_err(|_| ())?,
289    );
290    let replay = ReplayWindow::open(ReplayConfig {
291        epoch_file: files.replay_epoch_path().to_owned(),
292        max_nonces_per_epoch: usize::MAX,
293    })
294    .await
295    .map_err(|_| ())?;
296    let unused_invites = kcode_k1_daemon_invite_stock::reconcile(
297        &invites,
298        files.invite_links_path(),
299        INVITE_LINK_URL,
300    )
301    .map_err(|_| ())?;
302    if unused_invites < 100 {
303        return Err(().into());
304    }
305    let adapter = K1HttpAccounts::new(
306        Arc::clone(&accounts),
307        Arc::clone(&invites),
308        Arc::clone(&users),
309    );
310    let people = K1HttpPeople::new(accounts, users, groups, profiles);
311    let http = K1Http::new(
312        HttpConfig {
313            server_id: files.server_id().to_owned(),
314            public_origin: PUBLIC_ORIGIN.to_owned(),
315            max_body_bytes: usize::MAX,
316        },
317        replay,
318        adapter.identity_provider(),
319    )
320    .map_err(|_| ())?;
321    let person_routes =
322        kcode_k1_http_persons::authenticated_routes(access_persons, access, audio_models()[0])
323            .map_err(|_| ())?;
324    let authenticated = adapter
325        .authenticated_routes()
326        .merge(people.authenticated_routes())
327        .merge(kcode_k1_http_audio::authenticated_routes(audio))
328        .merge(person_routes)
329        .fallback(api_not_found);
330    let api = http
331        .router(
332            adapter.registration_endpoint(),
333            kcode_k1_terms::endpoint(),
334            authenticated,
335        )
336        .layer(middleware::from_fn(contextualize_api_error));
337    let config = PublicConfig {
338        protocol: "K1-HTTP-1",
339        server_id: files.server_id().to_owned(),
340        public_origin: PUBLIC_ORIGIN,
341    };
342    let config_route = get(move || {
343        let config = config.clone();
344        async move { ([(CACHE_CONTROL, "no-store")], Json(config)) }
345    });
346    let app = Router::new()
347        .route("/config.json", config_route)
348        .merge(api)
349        .layer(middleware::from_fn(require_authority));
350    Ok(Prepared {
351        app,
352        listener: TcpListener::bind(LISTEN_ADDRESS).await.map_err(|_| ())?,
353        signals: Signals::install()?,
354        unused_invites,
355        vault,
356    })
357}
358
359fn codex_config(working_directory: PathBuf, executable_override: Option<OsString>) -> CodexConfig {
360    CodexConfig {
361        executable: codex_executable(executable_override),
362        working_directory: working_directory.to_string_lossy().into_owned(),
363        model: CODEX_MODEL.to_owned(),
364        reasoning_effort: Some("medium".to_owned()),
365        base_instructions: String::new(),
366        tools: kcode_speaker_v3_terra_analysis::codex_adapter_tools(),
367    }
368}
369
370fn codex_executable(executable_override: Option<OsString>) -> PathBuf {
371    executable_override.map_or_else(|| PathBuf::from("codex-safe"), PathBuf::from)
372}
373
374fn audio_models() -> [ModelId; 2] {
375    [
376        ModelId::from_bytes(GEMINI_MODEL_BYTES),
377        ModelId::from_bytes(TERRA_MODEL_BYTES),
378    ]
379}
380
381fn resolve_ffmpeg() -> Result<PathBuf, ()> {
382    let path = std::env::var_os("PATH").ok_or(())?;
383    resolve_executable("ffmpeg", std::env::split_paths(&path))
384}
385
386fn resolve_executable(name: &str, paths: impl IntoIterator<Item = PathBuf>) -> Result<PathBuf, ()> {
387    paths
388        .into_iter()
389        .find_map(|directory| {
390            let candidate = directory.join(name);
391            executable(&candidate)
392                .then(|| std::fs::canonicalize(candidate).ok())
393                .flatten()
394                .filter(|path| path.is_absolute())
395        })
396        .ok_or(())
397}
398
399#[cfg(unix)]
400fn executable(path: &Path) -> bool {
401    use std::os::unix::fs::PermissionsExt as _;
402    std::fs::metadata(path)
403        .is_ok_and(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
404}
405
406#[cfg(not(unix))]
407fn executable(path: &Path) -> bool {
408    std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file())
409}
410
411fn open_vault(
412    state_root: &Path,
413    passphrase: SecretString,
414    ordering: Arc<K1TxnOrdering>,
415    peering: Arc<K1Peering>,
416) -> Result<Arc<K1Vault>, ()> {
417    K1Vault::open(&state_root.join("vault"), passphrase, ordering, peering)
418        .map(Arc::new)
419        .map_err(|_| ())
420}
421
422fn state_root(k1_root: &Path) -> PathBuf {
423    k1_root.join("state")
424}
425
426async fn api_not_found() -> Response {
427    json_error(
428        StatusCode::NOT_FOUND,
429        "not_found",
430        "authenticated API route not found",
431    )
432}
433
434async fn contextualize_api_error(request: Request, next: Next) -> Response {
435    let response = next.run(request).await;
436    if !(response.status().is_client_error() || response.status().is_server_error()) {
437        return response;
438    }
439    let (mut parts, body) = response.into_parts();
440    let bytes = match to_bytes(body, usize::MAX).await {
441        Ok(bytes) => bytes,
442        Err(_) => return Response::from_parts(parts, Body::empty()),
443    };
444    let Some(contextualized) = contextualize_error_body(&bytes) else {
445        return Response::from_parts(parts, Body::from(bytes));
446    };
447    parts.headers.remove(CONTENT_LENGTH);
448    Response::from_parts(parts, Body::from(contextualized))
449}
450
451fn contextualize_error_body(bytes: &[u8]) -> Option<Vec<u8>> {
452    let mut payload: Value = serde_json::from_slice(bytes).ok()?;
453    let object = payload.as_object_mut()?;
454    let code = object.get("error")?.as_str()?.to_owned();
455    let source = object
456        .get("message")
457        .and_then(Value::as_str)
458        .map(str::to_owned)
459        .unwrap_or_else(|| format!("error code {code}"));
460    object.insert(
461        "message".to_owned(),
462        Value::String(format!("{API_OPERATION}: {source}")),
463    );
464    Some(payload.to_string().into_bytes())
465}
466
467async fn require_authority(request: Request, next: Next) -> Response {
468    let mut values = request.headers().get_all(HOST).iter();
469    if values
470        .next()
471        .is_some_and(|value| value.as_bytes() == AUTHORITY.as_bytes())
472        && values.next().is_none()
473    {
474        next.run(request).await
475    } else {
476        json_error(
477            StatusCode::MISDIRECTED_REQUEST,
478            "invalid_request_authority",
479            "validate request authority: request authority is invalid",
480        )
481    }
482}
483
484fn json_error(status: StatusCode, code: &'static str, message: &'static str) -> Response {
485    let mut response = Response::new(Body::from(
486        serde_json::json!({"error": code, "message": message}).to_string(),
487    ));
488    *response.status_mut() = status;
489    response
490        .headers_mut()
491        .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
492    response
493        .headers_mut()
494        .insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
495    response
496}
497
498fn write_readiness(unused_invites: usize) -> Result<(), ()> {
499    let stdout = std::io::stdout();
500    let mut output = stdout.lock();
501    serde_json::to_writer(
502        &mut output,
503        &Ready {
504            event: "ready",
505            public_origin: PUBLIC_ORIGIN,
506            unused_invites,
507        },
508    )
509    .map_err(|_| ())?;
510    output.write_all(b"\n").map_err(|_| ())?;
511    output.flush().map_err(|_| ())
512}
513
514fn warn_if_slow(elapsed: Duration, outcome: &'static str) {
515    if elapsed > STARTUP_BOUND {
516        eprintln!(
517            "{{\"module\":\"kcode-k1-daemon\",\"operation\":\"startup\",\"elapsed_us\":{},\"outcome\":\"{outcome}\"}}",
518            elapsed.as_micros()
519        );
520    }
521}
522
523impl Signals {
524    fn install() -> Result<Self, ()> {
525        Ok(Self {
526            interrupt: signal(SignalKind::interrupt()).map_err(|_| ())?,
527            terminate: signal(SignalKind::terminate()).map_err(|_| ())?,
528        })
529    }
530
531    async fn wait(mut self) {
532        tokio::select! {
533            _ = self.interrupt.recv() => {}
534            _ = self.terminate.recv() => {}
535        }
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542
543    #[test]
544    fn public_operation_accepts_only_the_state_root() {
545        let _: fn(PathBuf) -> ExitCode = run;
546    }
547
548    #[test]
549    fn accepted_passphrase_boundary_is_strict_and_protected() {
550        assert!(protect_passphrase(String::new()).is_err());
551        let text = "conspicuous-fake-passphrase-never-real";
552        let protected = protect_passphrase(text.to_owned()).unwrap();
553        assert!(!format!("{protected:?}").contains(text));
554    }
555
556    #[test]
557    fn vault_composition_persists_at_the_fixed_path() {
558        let root =
559            std::env::temp_dir().join(format!("kcode-k1-daemon-vault-test-{}", std::process::id()));
560        let _ = std::fs::remove_dir_all(&root);
561        let state = state_root(&root);
562        assert_eq!(state.join("vault"), root.join("state/vault"));
563        let parts = || {
564            let ordering = Arc::new(K1TxnOrdering::open(&state.join("ordering")).unwrap());
565            let peering =
566                Arc::new(K1Peering::open(&state.join("peering"), ordering.clone()).unwrap());
567            (ordering, peering)
568        };
569        let password = || SecretString::from("fake-test-password-never-real");
570        let (ordering, peering) = parts();
571        let vault = open_vault(&state, password(), ordering.clone(), peering.clone()).unwrap();
572        vault
573            .set(
574                "fake-provider-secret",
575                SecretString::from("conspicuous-fake-value-never-real"),
576            )
577            .unwrap();
578        drop((vault, peering, ordering));
579        let (ordering, peering) = parts();
580        let vault = open_vault(&state, password(), ordering.clone(), peering.clone()).unwrap();
581        drop((vault, peering, ordering));
582        let (ordering, peering) = parts();
583        assert!(
584            open_vault(
585                &state,
586                SecretString::from("wrong-fake-password-never-real"),
587                ordering,
588                peering
589            )
590            .is_err()
591        );
592        std::fs::remove_dir_all(root).unwrap();
593    }
594
595    #[test]
596    fn audio_model_ids_are_fixed_distinct_and_in_order() {
597        assert_eq!(GEMINI_MODEL_BYTES, *b"gemini-3.1-pro-preview..........");
598        assert_eq!(TERRA_MODEL_BYTES, *b"gpt-5.6-terra...................");
599        assert_eq!(GEMINI_MODEL_BYTES.len(), 32);
600        assert_eq!(TERRA_MODEL_BYTES.len(), 32);
601        let models = audio_models();
602        assert_eq!(models[0].as_bytes(), &GEMINI_MODEL_BYTES);
603        assert_eq!(models[1].as_bytes(), &TERRA_MODEL_BYTES);
604        assert_ne!(models[0], models[1]);
605    }
606
607    #[test]
608    fn only_the_fixed_gemini_vault_key_is_selected() {
609        assert_eq!(GEMINI_API_KEY, "gemini-api-key");
610    }
611
612    #[test]
613    fn codex_executable_override_and_default_are_exact_without_environment_mutation() {
614        assert_eq!(CODEX_EXECUTABLE_ENV, "K1_CODEX_EXECUTABLE");
615        assert_eq!(codex_executable(None), PathBuf::from("codex-safe"));
616        let selected = codex_executable(Some(OsString::from("/trusted/bin/k1-codex")));
617        assert_eq!(selected, PathBuf::from("/trusted/bin/k1-codex"));
618    }
619
620    #[test]
621    fn codex_config_fields_and_terra_tool_names_are_exact_without_process_io() {
622        let config = codex_config(
623            PathBuf::from("/trusted/current-directory"),
624            Some(OsString::from("/trusted/bin/k1-codex")),
625        );
626        assert_eq!(config.executable, PathBuf::from("/trusted/bin/k1-codex"));
627        assert_eq!(config.working_directory, "/trusted/current-directory");
628        assert_eq!(config.model, "gpt-5.6-terra");
629        assert_eq!(config.reasoning_effort.as_deref(), Some("medium"));
630        assert!(config.base_instructions.is_empty());
631        assert_eq!(
632            config
633                .tools
634                .iter()
635                .map(|tool| tool.name.as_str())
636                .collect::<Vec<_>>(),
637            ["record_speaker_labels", "record_speaker_analysis"]
638        );
639    }
640
641    #[test]
642    fn startup_error_rendering_includes_safe_adapter_display_but_not_diagnostics() {
643        assert_eq!(
644            StartupError::from(()).to_string(),
645            "kcode-k1-daemon: startup failed"
646        );
647        let error = CodexAdapterError {
648            kind: kcode_k1_codex_adapter::ErrorKind::Unavailable,
649            message: "safe adapter display".to_owned(),
650            diagnostics: b"RAW_SECRET_DIAGNOSTIC".to_vec(),
651        };
652        let rendered = StartupError::CodexAdapter(error).to_string();
653        assert_eq!(
654            rendered,
655            "kcode-k1-daemon: startup failed: safe adapter display"
656        );
657        assert!(!rendered.contains("RAW_SECRET_DIAGNOSTIC"));
658    }
659
660    #[test]
661    fn adapter_only_dependency_and_constructor_identity_are_exact() {
662        const MANIFEST: &str = include_str!("../Cargo.toml");
663        for selected in [
664            "kcode-k1-access-full-audio = \"=0.7.2\"",
665            "kcode-k1-audio-classification = \"=0.5.4\"",
666            "kcode-k1-codex-adapter = \"=0.3.0\"",
667            "kcode-k1-full-audio = \"=0.3.5\"",
668            "kcode-k1-http-audio = \"=0.1.3\"",
669            "kcode-speaker-v3-analysis = { version = \"=0.3.0\", default-features = false, features = [\"adapter-providers\"] }",
670            "kcode-speaker-v3-terra-analysis = { version = \"=0.2.0\", default-features = false, features = [\"adapter\"] }",
671        ] {
672            assert!(MANIFEST.contains(selected));
673        }
674        assert!(!MANIFEST.contains("kcode-codex-terra"));
675
676        fn require_constructor(_: fn(Gemini31Pro, CodexAdapter) -> Analyzer) {}
677        require_constructor(Analyzer::from_codex_adapter);
678    }
679
680    #[cfg(unix)]
681    #[test]
682    fn executable_resolver_returns_an_absolute_path_for_a_fake_ffmpeg() {
683        use std::os::unix::fs::PermissionsExt as _;
684        let root = std::env::temp_dir().join(format!(
685            "kcode-k1-daemon-ffmpeg-test-{}",
686            std::process::id()
687        ));
688        let _ = std::fs::remove_dir_all(&root);
689        std::fs::create_dir(&root).unwrap();
690        let fake = root.join("ffmpeg");
691        std::fs::write(&fake, b"#!/bin/sh\nexit 0\n").unwrap();
692        std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o700)).unwrap();
693        assert_eq!(
694            resolve_executable("ffmpeg", [root.clone()]).unwrap(),
695            std::fs::canonicalize(&fake).unwrap()
696        );
697        std::fs::remove_dir_all(root).unwrap();
698    }
699
700    #[test]
701    fn invite_link_and_backend_origins_remain_distinct() {
702        assert_eq!(
703            INVITE_LINK_URL,
704            "http://localhost:4321/lib/kcode-k1-ui/*/account.html"
705        );
706        assert_eq!(PUBLIC_ORIGIN, "http://localhost:4450");
707        assert_ne!(INVITE_LINK_URL, PUBLIC_ORIGIN);
708    }
709
710    #[test]
711    fn existing_child_message_is_preserved_under_daemon_context() {
712        let body = contextualize_error_body(
713            br#"{"error":"group_failed","message":"load group: child failure","detail":7}"#,
714        )
715        .unwrap();
716        let payload: Value = serde_json::from_slice(&body).unwrap();
717        assert_eq!(payload["error"], "group_failed");
718        assert_eq!(payload["detail"], 7);
719        assert_eq!(
720            payload["message"],
721            "serve API request: load group: child failure"
722        );
723    }
724
725    #[test]
726    fn missing_child_message_is_derived_from_stable_code() {
727        let body = contextualize_error_body(br#"{"error":"invalid_signature"}"#).unwrap();
728        let payload: Value = serde_json::from_slice(&body).unwrap();
729        assert_eq!(payload["error"], "invalid_signature");
730        assert_eq!(
731            payload["message"],
732            "serve API request: error code invalid_signature"
733        );
734    }
735
736    #[test]
737    fn supplied_root_maps_only_to_state() {
738        assert_eq!(
739            state_root(Path::new("/trusted/k1")),
740            PathBuf::from("/trusted/k1/state")
741        );
742    }
743}