algocline-core 0.31.0

algocline domain model and metrics — pure execution state machine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
use async_trait::async_trait;

// ─── Parameter types (transport-independent) ─────────────────────

/// A single query response in a batch feed.
#[derive(Debug)]
pub struct QueryResponse {
    /// Query ID (e.g. "q-0", "q-1").
    pub query_id: String,
    /// The host LLM's response for this query.
    pub response: String,
    /// Token usage reported by the host for this query.
    pub usage: Option<crate::TokenUsage>,
}

// ─── Engine API trait ────────────────────────────────────────────

/// Transport-independent API for the algocline engine.
///
/// Abstracts the full public surface of AppService so that callers
/// (MCP handler, future daemon client, etc.) can operate through
/// `Arc<dyn EngineApi>` without depending on the concrete implementation.
///
/// All methods are async to support both local (in-process) and remote
/// (socket/HTTP) implementations uniformly.
#[async_trait]
pub trait EngineApi: Send + Sync {
    // ─── Core execution ──────────────────────────────────────

    /// Execute Lua code with optional JSON context.
    ///
    /// When `host_mode: Some(true)` is passed, the call is proxied via
    /// `PoolClient` to a long-lived worker subprocess over a Unix domain socket.
    /// When `host_mode` is `None` or `Some(false)` the existing in-process
    /// `Executor::start_session` path is used unchanged.
    async fn run(
        &self,
        code: Option<String>,
        code_file: Option<String>,
        ctx: Option<serde_json::Value>,
        project_root: Option<String>,
        host_mode: Option<bool>,
    ) -> Result<String, String>;

    /// Apply an installed strategy package. Task is optional.
    async fn advice(
        &self,
        strategy: &str,
        task: Option<String>,
        opts: Option<serde_json::Value>,
        project_root: Option<String>,
    ) -> Result<String, String>;

    /// Continue a paused execution — single response (with optional query_id).
    async fn continue_single(
        &self,
        session_id: &str,
        response: String,
        query_id: Option<&str>,
        usage: Option<crate::TokenUsage>,
    ) -> Result<String, String>;

    /// Continue a paused execution — batch feed.
    async fn continue_batch(
        &self,
        session_id: &str,
        responses: Vec<QueryResponse>,
    ) -> Result<String, String>;

    // ─── Session status ──────────────────────────────────────

    /// Query active session status.
    ///
    /// `pending_filter` is a free-form JSON value forwarded from MCP
    /// callers, decoded inside the app layer into either a preset name
    /// (`"meta"` / `"preview"` / `"full"`) or a custom field-filter
    /// object. `None` keeps the legacy count-only snapshot.
    ///
    /// `include_history`: when `true`, each session snapshot includes
    /// `conversation_history` (capped at 10 entries). Default `false`
    /// preserves the lightweight snapshot contract for high-frequency pollers.
    async fn status(
        &self,
        session_id: Option<&str>,
        pending_filter: Option<serde_json::Value>,
        include_history: bool,
    ) -> Result<String, String>;

    // ─── Evaluation ──────────────────────────────────────────

    /// Run an evalframe evaluation suite.
    ///
    /// `auto_card`: when true, emit an immutable Card
    /// (`~/.algocline/cards/{strategy}/{card_id}.toml`) summarizing the run.
    async fn eval(
        &self,
        scenario: Option<String>,
        scenario_file: Option<String>,
        scenario_name: Option<String>,
        strategy: &str,
        strategy_opts: Option<serde_json::Value>,
        auto_card: bool,
    ) -> Result<String, String>;

    /// List eval history, optionally filtered by strategy.
    async fn eval_history(&self, strategy: Option<&str>, limit: usize) -> Result<String, String>;

    /// View a specific eval result by ID.
    async fn eval_detail(&self, eval_id: &str) -> Result<String, String>;

    /// Compare two eval results with statistical significance testing.
    async fn eval_compare(&self, eval_id_a: &str, eval_id_b: &str) -> Result<String, String>;

    // ─── Scenarios ───────────────────────────────────────────

    /// List available scenarios.
    async fn scenario_list(&self) -> Result<String, String>;

    /// Show the content of a named scenario.
    async fn scenario_show(&self, name: &str) -> Result<String, String>;

    /// Install scenarios from a Git URL or local path.
    async fn scenario_install(&self, url: String) -> Result<String, String>;

    // ─── Packages ────────────────────────────────────────────

    /// Link a local directory as a project-local package (symlink to cache).
    ///
    /// Scope selection:
    /// - `scope = None` or `Some("global")` — symlink into `~/.algocline/packages/`
    ///   (visible to all projects).
    /// - `scope = Some("variant")` — record the path in `alc.local.toml`
    ///   at the project root (worktree-scoped override, git-ignored). No
    ///   symlink is created.
    /// - Any other value → `Err("invalid scope: ...")`.
    ///
    /// `project_root` is only consulted when `scope = Some("variant")`.
    /// If `None`, falls back to `ALC_PROJECT_ROOT` env or ancestor walk
    /// from cwd.
    async fn pkg_link(
        &self,
        path: String,
        name: Option<String>,
        force: Option<bool>,
        scope: Option<String>,
        project_root: Option<String>,
    ) -> Result<String, String>;

    /// List installed packages with metadata.
    ///
    /// When `project_root` is provided, project-local packages from `alc.toml`/`alc.lock`
    /// are included with `scope: "project"`. Global packages carry `scope: "global"`.
    ///
    /// Mirrors the list-tool knob contract used by [`Self::hub_search`]
    /// (plan.md §4.1). Parameters are individual JSON-primitive
    /// `Option<T>` values so the `algocline-core` crate stays free of
    /// `algocline-app`-internal types; the impl folds them into its
    /// `pub(crate) ListOpts` struct.
    ///
    /// - `limit` is `Option<i32>` at this layer (MCP/JSON boundary).
    ///   The impl clamps negative values to 0 and casts to `usize`.
    ///   `Some(0)` (and thus clamped negatives) means **no limit**
    ///   (return all entries — empty-means-all idiom); `None` falls
    ///   back to the tool's default cap.
    /// - `filter` is a free-form JSON object; it is `Deserialize`d into
    ///   a `HashMap<String, Value>` inside the app layer. Non-object
    ///   values are logged via `tracing::warn` and treated as no filter.
    /// - `fields` / `verbose` drive projection on each entry of the
    ///   `packages` array; `fields` wins when both are supplied.
    /// - Top-level keys (`packages`, `search_paths`, `project_root`,
    ///   `lockfile_path`) are never projected away.
    #[allow(clippy::too_many_arguments)]
    async fn pkg_list(
        &self,
        project_root: Option<String>,
        limit: Option<i32>,
        sort: Option<String>,
        filter: Option<serde_json::Value>,
        fields: Option<Vec<String>>,
        verbose: Option<String>,
    ) -> Result<String, String>;

    /// Install a package from a Git URL or local path.
    async fn pkg_install(&self, url: String, name: Option<String>) -> Result<String, String>;

    /// Remove a symlinked package from `~/.algocline/packages/`.
    ///
    /// Only removes symlinks; for installed (copied) packages, use `pkg_remove`.
    async fn pkg_unlink(&self, name: String) -> Result<String, String>;

    /// Remove a package entry, scoped by `scope` (`"project"` /
    /// `"global"` / `"all"`, default `"project"`).
    ///
    /// - `"project"`: remove from `alc.toml` + `alc.lock`. Requires an
    ///   `alc.toml` via `project_root` or ancestor walk.
    /// - `"global"`: remove from `~/.algocline/installed.json` only.
    ///   `project_root` is ignored.
    /// - `"all"`: remove from both; succeeds if either scope had the entry.
    ///
    /// Physical files in `~/.algocline/packages/{name}/` are never deleted.
    async fn pkg_remove(
        &self,
        name: &str,
        project_root: Option<String>,
        version: Option<String>,
        scope: Option<String>,
    ) -> Result<String, String>;

    /// Heal broken package state by reinstalling entries whose installed
    /// directory is missing. Other broken kinds (dangling symlink,
    /// declared-path missing) are surfaced as `unrepairable` with a
    /// suggested remediation.
    async fn pkg_repair(
        &self,
        name: Option<String>,
        project_root: Option<String>,
    ) -> Result<String, String>;

    /// Diagnose package state without side effects.
    ///
    /// Read-only counterpart of [`Self::pkg_repair`]. Classifies packages
    /// into four buckets — `healthy`, `installed_missing`, `symlink_dangling`,
    /// `path_missing` — and returns the result as a JSON string. No
    /// filesystem writes, no `pkg_install` calls.
    ///
    /// `name` restricts the report to a single package; `None` inspects
    /// every known package. `project_root` is used for the `alc.toml` /
    /// `alc.local.toml` pass (falls back to ancestor walk from cwd).
    async fn pkg_doctor(
        &self,
        name: Option<String>,
        project_root: Option<String>,
    ) -> Result<String, String>;

    // ─── Logging ─────────────────────────────────────────────

    /// Append a note to a session's log file.
    async fn add_note(
        &self,
        session_id: &str,
        content: &str,
        title: Option<&str>,
    ) -> Result<String, String>;

    /// View session logs.
    async fn log_view(
        &self,
        session_id: Option<&str>,
        limit: Option<usize>,
        max_chars: Option<usize>,
    ) -> Result<String, String>;

    /// Aggregate stats across all logged sessions.
    async fn stats(
        &self,
        strategy_filter: Option<&str>,
        days: Option<u64>,
    ) -> Result<String, String>;

    // ─── Project lifecycle ────────────────────────────────────

    /// Initialize `alc.toml` in the given project root.
    ///
    /// Creates a minimal `alc.toml` (`[packages]` section only).
    /// Fails if `alc.toml` already exists (no overwrite).
    async fn init(&self, project_root: Option<String>) -> Result<String, String>;

    /// Re-resolve all `alc.toml` entries and rewrite `alc.lock`.
    ///
    /// Requires an `alc.toml` to be present. Returns resolved count and errors.
    async fn update(&self, project_root: Option<String>) -> Result<String, String>;

    /// Migrate a legacy `alc.lock` to `alc.toml` + new `alc.lock` format.
    ///
    /// Detects legacy format via `linked_at` / `local_dir` fields.
    /// Backs up the old lock file as `alc.lock.bak`.
    async fn migrate(&self, project_root: Option<String>) -> Result<String, String>;

    // ─── Cards ───────────────────────────────────────────────

    /// List Card summaries, optionally filtered by pkg.
    async fn card_list(&self, pkg: Option<String>) -> Result<String, String>;

    /// Fetch a full Card by id.
    async fn card_get(&self, card_id: &str) -> Result<String, String>;

    /// Filter/sort Cards using the Prisma-style `where` DSL.
    ///
    /// - `pkg`: restricts filesystem scan to a single pkg subdir (I/O hint).
    /// - `where_`: nested-object predicate (see `card::parse_where`).
    /// - `order_by`: array of dotted-path sort keys; `-` prefix = desc.
    /// - `limit` / `offset`: pagination.
    async fn card_find(
        &self,
        pkg: Option<String>,
        where_: Option<serde_json::Value>,
        order_by: Option<serde_json::Value>,
        limit: Option<usize>,
        offset: Option<usize>,
    ) -> Result<String, String>;

    /// List aliases, optionally filtered by pkg.
    async fn card_alias_list(&self, pkg: Option<String>) -> Result<String, String>;

    /// Resolve an alias name to its bound Card and return the full Card JSON.
    async fn card_get_by_alias(&self, name: &str) -> Result<String, String>;

    /// Bind (or rebind) an alias to a Card.
    async fn card_alias_set(
        &self,
        name: &str,
        card_id: &str,
        pkg: Option<String>,
        note: Option<String>,
    ) -> Result<String, String>;

    /// Append new top-level fields to an existing Card (additive-only).
    async fn card_append(&self, card_id: &str, fields: serde_json::Value)
        -> Result<String, String>;

    /// Install Cards from a Card Collection repo (Git URL or local path).
    async fn card_install(&self, url: String) -> Result<String, String>;

    /// Read per-case samples from a Card's sidecar JSONL file.
    ///
    /// `where_` applies the same Prisma-style DSL used by `card_find`
    /// to each sample row; offset/limit page the post-filter stream.
    async fn card_samples(
        &self,
        card_id: &str,
        offset: Option<usize>,
        limit: Option<usize>,
        where_: Option<serde_json::Value>,
    ) -> Result<String, String>;

    /// Walk a Card's lineage tree via `metadata.prior_card_id`.
    ///
    /// - `direction`: `"up"` | `"down"` | `"both"` (default `"up"`).
    /// - `depth`: max traversal depth (default 10).
    /// - `include_stats`: include each node's `[stats]` section.
    /// - `relation_filter`: optional list of accepted `prior_relation` values.
    async fn card_lineage(
        &self,
        card_id: &str,
        direction: Option<String>,
        depth: Option<usize>,
        include_stats: Option<bool>,
        relation_filter: Option<Vec<String>>,
    ) -> Result<String, String>;

    /// Backfill one subscriber (`sink` URI) with all cards from the
    /// primary store. Drift-safe: cards already present on the sink are
    /// skipped (never overwritten). Returns a `SinkBackfillReport`
    /// serialized as a JSON string.
    async fn card_sink_backfill(&self, _sink: String, _dry_run: bool) -> Result<String, String> {
        Err("card_sink_backfill: not implemented by this EngineApi impl".into())
    }

    // ─── Hub ─────────────────────────────────────────────────

    /// Rebuild hub index from a packages directory.
    ///
    /// When `source_dir` is provided, scans that directory directly
    /// (pure metadata, no manifest).  When omitted, scans `~/.algocline/packages/`.
    async fn hub_reindex(
        &self,
        output_path: Option<String>,
        source_dir: Option<String>,
    ) -> Result<String, String>;

    /// Generate human-readable documentation artifacts from a hub index.
    ///
    /// Runs the embedded Lua `gen_docs` pipeline (originally shipped
    /// with `algocline-bundled-packages`) against `source_dir`, which
    /// must contain a fresh `hub_index.json`. Emits
    /// `narrative/{pkg}.md`, `llms.txt`, `llms-full.txt` under
    /// `out_dir` (defaults to `{source_dir}/docs`), plus optional
    /// projections depending on `projections`:
    ///
    /// - `"hub"`       → `{out_dir}/hub/{pkg}.json`
    /// - `"context7"`  → `{source_dir}/context7.json`
    /// - `"devin"`     → `{source_dir}/.devin/wiki.json`
    /// - `"lint"`      → run V0 lint pass (warnings only)
    /// - `"lint_only"` → run lint, skip file generation
    ///
    /// `config_path` — optional path to a TOML config file. When omitted,
    /// the project root's `alc.toml` is auto-explored for `[hub.context7]`
    /// and `[hub.devin]` sections. Core defaults apply when neither a
    /// `config_path` nor `alc.toml` provides projection config. Passing a
    /// `.lua` path is a typed error (retired). See
    /// `docs/hub-gendoc-config.md` for the full schema.
    ///
    /// Projection names are validated strictly and unknown values are
    /// rejected with `Err("gendoc: unknown projection ...")`.
    ///
    /// `lint_strict = true` upgrades lint errors to a hard failure
    /// (equivalent to the `--strict` CLI flag).
    ///
    /// Returns a JSON string containing the collected stdout / stderr
    /// plus the resolved `source_dir` / `out_dir` for observability.
    async fn hub_gendoc(
        &self,
        source_dir: String,
        out_dir: Option<String>,
        projections: Option<Vec<String>>,
        config_path: Option<String>,
        lint_strict: Option<bool>,
    ) -> Result<String, String>;

    /// Run `hub_reindex` followed by `hub_gendoc` as a single facade.
    ///
    /// This is a convenience wrapper for downstream hub repositories that
    /// want to regenerate the index and the public docs in one call. The
    /// composed response is a JSON object:
    ///
    /// ```json
    /// {
    ///   "reindex": <hub_reindex response>,
    ///   "gendoc": <hub_gendoc response>,
    ///   "preset_catalog_version": "...",
    ///   "preset": { "name": ..., "catalog_version": ..., "resolved": { ... } }
    /// }
    /// ```
    ///
    /// Error propagation:
    ///
    /// - If `hub_reindex` fails, `hub_dist` returns immediately with
    ///   `Err("dist: reindex failed: {inner}")` and does not invoke
    ///   `hub_gendoc`.
    /// - If `hub_gendoc` fails, the error text includes the reindex JSON
    ///   that already succeeded:
    ///   `Err("dist: gendoc failed: {inner}\nreindex result (succeeded): {json}")`.
    ///   The reindex-side side effects (written `hub_index.json`) are not
    ///   rolled back.
    ///
    /// `output_path` is the `hub_index.json` destination (reindex arg).
    /// Callers typically pass `{source_dir}/hub_index.json` so the
    /// subsequent gendoc step can read it back.
    ///
    /// Presets (`preset`) are expanded inside `hub_dist` into primitive
    /// `hub_gendoc` arguments (`projections` / `config_path` /
    /// `lint_strict`). When `preset` is set, the successful JSON response
    /// includes a `preset` object with `catalog_version` plus the fully
    /// resolved knobs for observability.
    ///
    /// Merge order (strongest wins):
    /// 1) explicit MCP arguments (`projections` / `config_path` / `lint_strict`)
    /// 2) optional `alc.toml` overrides under `[hub.dist.presets.<name>]`
    ///    (keyed by `project_root`) — only fills **omitted** knobs
    /// 3) builtin `Current` defaults for the selected preset
    #[allow(clippy::too_many_arguments)]
    async fn hub_dist(
        &self,
        source_dir: String,
        output_path: Option<String>,
        out_dir: Option<String>,
        preset: Option<String>,
        project_root: Option<String>,
        projections: Option<Vec<String>>,
        config_path: Option<String>,
        lint_strict: Option<bool>,
    ) -> Result<String, String>;

    /// Show detailed information for a single package.
    async fn hub_info(&self, pkg: String) -> Result<String, String>;

    /// Search packages across remote index + local install state.
    ///
    /// This trait method mirrors the MCP `alc_hub_search` tool. Parameters
    /// are deliberately individual JSON-primitive `Option<T>` values
    /// (rather than an aggregate struct) so that the `algocline-core` crate
    /// stays free of `algocline-app`-internal types (see plan.md §4.1).
    /// The `algocline-app` side of the impl folds these into its
    /// `pub(crate) ListOpts` struct.
    ///
    /// - `limit` is `Option<i32>` at this layer (MCP/JSON boundary). The
    ///   impl casts to `usize` internally.
    /// - `filter` is a free-form JSON object; it is `Deserialize`d into
    ///   a `HashMap<String, Value>` inside the app layer.
    /// - `fields` / `verbose` drive projection; `fields` wins when both
    ///   are supplied.
    #[allow(clippy::too_many_arguments)]
    async fn hub_search(
        &self,
        query: Option<String>,
        category: Option<String>,
        installed_only: Option<bool>,
        limit: Option<i32>,
        sort: Option<String>,
        filter: Option<serde_json::Value>,
        fields: Option<Vec<String>>,
        verbose: Option<String>,
    ) -> Result<String, String>;

    // ─── Package scaffold ─────────────────────────────────────

    /// Generate a minimal package skeleton at `<target_dir>/<name>/init.lua`.
    ///
    /// Writes an `M.meta` / `M.spec.entries.run` / `M.run` template with a
    /// pre-filled `alc_shapes_compat` range derived from the embedded
    /// alc_shapes version.  Optional `category` / `description` are emitted
    /// as uncommented fields in `M.meta` when provided.
    ///
    /// Returns `{ "status": "ok", "path": "...", "bytes_written": N }` on
    /// success. Typed errors (`NameInvalid`, `AlreadyExists`, `IoError`) are
    /// propagated via `Err(String)` to the MCP wire response.
    async fn pkg_scaffold(
        &self,
        name: String,
        target_dir: Option<String>,
        category: Option<String>,
        description: Option<String>,
    ) -> Result<String, String>;

    /// Read the `init.lua` source of an installed package.
    ///
    /// Searches global (`~/.algocline/packages/`) and variant
    /// (`alc.local.toml`) scope in priority order (variant wins).
    /// Returns the raw Lua source on success, or an `Err(String)` describing
    /// why the package was not found or could not be read.
    async fn pkg_read_init_lua(&self, name: &str) -> Result<String, String>;

    /// Read metadata for a single installed package.
    ///
    /// Returns the JSON object string for one package entry (the same shape
    /// `pkg_list` returns inside `packages[*]`). `Err("pkg not found: ...")`
    /// when the package is unknown.
    async fn pkg_meta(&self, name: &str) -> Result<String, String>;

    // ─── Diagnostics ─────────────────────────────────────────

    /// Show server configuration and diagnostic info.
    async fn info(&self) -> String;

    // ─── Hub resources ───────────────────────────────────────

    /// Return the aggregated hub index across all registered sources as a JSON string.
    ///
    /// Merges the cached `hub_index.json` from every discovered source URL.
    /// Sources that fail to load produce warnings that are embedded in the
    /// returned JSON under a `"warnings"` field so the MCP caller can observe
    /// partial failures.
    ///
    /// Returns `Ok(json_string)` where the JSON has shape:
    /// ```json
    /// { "schema_version": "hub_index/v0", "packages": [...], "warnings": [...] }
    /// ```
    /// Returns `Err(message)` only when the hub registries file itself is
    /// corrupt (hard I/O failure), making further index discovery impossible.
    async fn hub_index_aggregate(&self) -> Result<String, String>;

    // ─── Pool management ─────────────────────────────────────────

    /// Ensure pool workers are alive; GC stale entries. Idempotent.
    ///
    /// Returns JSON `{"sessions": [...], "pool_version": "..."}`.
    async fn pool_ensure(&self) -> Result<String, String>;

    /// Return pool worker status (registry.json + live state).
    ///
    /// When `sid` is provided, restricts to a single worker.
    /// Returns JSON `{"sessions": [...], "pool_version": "..."}`.
    async fn pool_status(&self, sid: Option<String>) -> Result<String, String>;

    /// Send SIGTERM to all workers (`sid=None`) or a single worker.
    ///
    /// Returns JSON `{"stopped": [...], "errors": [...]}`.
    async fn pool_stop(&self, sid: Option<String>) -> Result<String, String>;
}