submilli-engine 0.3.0

Compiles Submilli programs, a TypeScript subset, to WebAssembly and runs them under capability policy
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
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
//! Catalog of the semantic-security capabilities the stdlib host functions
//! gate via `check_security`. This is the single source of truth behind
//! `submilli blueprint init`, the `submilli blueprint capability` verbs, and
//! the server's `GET /v1/capabilities`, so an operator sees every capability
//! the runtime can gate.
//!
//! **Keep in sync with the host functions.** When a host fn in `stdlib::fs` /
//! `stdlib::http` (or a new gated module) starts or stops gating a capability,
//! update the matching entry here — see AGENTS.md. The `capabilities` test in
//! the `submilli` CLI asserts every `example_filter` below parses.

use super::{OptionalPackage, Stdlib};

/// One context field a capability's `filter:` expression can match on.
pub struct FilterField {
    pub name: &'static str,
    /// The operand's shape in a filter comparison: `string`, `number`, or
    /// `boolean`.
    pub ty: &'static str,
    /// One line on what the field carries.
    pub doc: &'static str,
    /// How the runtime rewrites the value before the policy sees it.
    pub normalization: FieldNormalization,
}

/// A rewrite the runtime applies to a field's value before checking it. Call-site
/// derivation applies the same rewrite to a literal argument, so the filter it
/// writes into `requires` names the value the policy is asked about.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FieldNormalization {
    /// Checked exactly as the caller passed it.
    Verbatim,
    /// Absolute guest path with `.` and `..` collapsed, as VFS I/O resolves it.
    VfsPath,
    /// The serialized form of the parsed HTTPS repository URL: lowercase host,
    /// no default port, `/` for an empty path, percent-encoded.
    RepositoryUrl,
}

impl FieldNormalization {
    /// The value the runtime checks for `value`, or why the runtime refuses it.
    pub(crate) fn apply(self, value: &str) -> Result<String, String> {
        match self {
            Self::Verbatim => Ok(value.to_string()),
            Self::VfsPath => {
                crate::runtime::fs::guest_normalize("/", value).map_err(|error| error.to_string())
            }
            Self::RepositoryUrl => {
                crate::stdlib::git::canonical_url(value).map_err(|error| error.to_string())
            }
        }
    }
}

const fn field(name: &'static str, ty: &'static str, doc: &'static str) -> FilterField {
    FilterField {
        name,
        ty,
        doc,
        normalization: FieldNormalization::Verbatim,
    }
}

const fn vfs_path_field(name: &'static str, doc: &'static str) -> FilterField {
    normalized_string_field(name, doc, FieldNormalization::VfsPath)
}

const fn normalized_string_field(
    name: &'static str,
    doc: &'static str,
    normalization: FieldNormalization,
) -> FilterField {
    FilterField {
        name,
        ty: "string",
        doc,
        normalization,
    }
}

/// One gated capability and the policy `filter:` surface it exposes.
pub struct Capability {
    /// The name passed to `check()` — exactly what a policy rule's
    /// `capability:` must equal (capability names are matched verbatim).
    pub name: &'static str,
    /// Why the runtime refuses this capability to `main` outright, ahead of the
    /// policy engine — `None` for an ordinary capability a rule can grant to any
    /// caller. A rule granting a marked capability to `main` is dead, so the
    /// surfaces that scaffold, add, and lint rules read this one fact rather
    /// than each restating the carve-out, and each shows the reason verbatim.
    pub main_denial: Option<&'static str>,
    /// One-line description of what gating the capability controls.
    pub summary: &'static str,
    /// Context fields a `filter:` expression can match on, as supplied by a
    /// representative call. Documents what a rule can constrain.
    pub filter_fields: &'static [FilterField],
    /// A ready-to-uncomment example `filter:` expression for the scaffold.
    pub example_filter: &'static str,
}

impl Capability {
    /// Whether the name is a template (`mcp.<server>`) rather than a concrete
    /// name a policy rule can use verbatim.
    pub fn is_template(&self) -> bool {
        self.name.contains('<')
    }

    /// Whether a policy rule granting this capability to `main` can take effect.
    pub fn grantable_to_main(&self) -> bool {
        self.main_denial.is_none()
    }

    /// The filter fields' names, for surfaces that don't show types.
    pub fn field_names(&self) -> impl Iterator<Item = &'static str> {
        self.filter_fields.iter().map(|f| f.name)
    }
}

/// A group of capabilities sharing a source module, for scaffold sectioning.
pub struct CapabilityGroup {
    /// The owning stdlib module, e.g. `submilli:fs`.
    pub module: &'static str,
    pub capabilities: &'static [Capability],
}

const PATH: FilterField = vfs_path_field("path", "Normalized absolute VFS path the call targets");
const RECURSIVE: FilterField = field(
    "recursive",
    "boolean",
    "Whether the operation applies recursively",
);
const FROM: FilterField = vfs_path_field("from", "Normalized absolute source VFS path");
const TO: FilterField = vfs_path_field("to", "Normalized absolute destination VFS path");
/// The code module checks workspace access as recursive.
const CODE_RECURSIVE: FilterField = field(
    "recursive",
    "boolean",
    "Always true; supplied by the code module only",
);
const KEY: FilterField = field("key", "string", "Session key the call targets");
const PREFIX: FilterField = field("prefix", "string", "Session key prefix being listed");

const FS: &[Capability] = &[
    Capability {
        name: "fs.read",
        main_denial: None,
        summary: "Read files and code workspace content (including search and ignore rules)",
        filter_fields: &[
            PATH,
            field(
                "length",
                "number",
                "Bytes requested; supplied by `readBytes` only",
            ),
            field(
                "chunkSize",
                "number",
                "Chunk size in bytes; supplied by `bytes` only",
            ),
            CODE_RECURSIVE,
        ],
        example_filter: "path glob \"*.csv\"",
    },
    Capability {
        name: "fs.write",
        main_denial: None,
        summary: "Create, write, append, or apply code edits to files",
        filter_fields: &[
            PATH,
            field(
                "length",
                "number",
                "Content size in bytes; supplied by `write`, `writeText`, `append`, \
                 `appendText`, and code edits",
            ),
            field(
                "max_bytes",
                "number",
                "Requested download size cap in bytes; supplied by `http.download` and packages that stream downloads",
            ),
            field(
                "diff",
                "string",
                "Unified diff of the edit; supplied by code edits only",
            ),
        ],
        example_filter: "path glob \"/out/*\"",
    },
    Capability {
        name: "fs.stat",
        main_denial: None,
        summary: "Inspect metadata (including code workspace discovery)",
        filter_fields: &[PATH, CODE_RECURSIVE],
        example_filter: "path glob \"/data/*\"",
    },
    Capability {
        name: "fs.list",
        main_denial: None,
        summary: "List directory entries (including code search, glob and tree)",
        filter_fields: &[PATH, RECURSIVE],
        example_filter: "path glob \"/data/*\"",
    },
    Capability {
        name: "fs.mkdir",
        main_denial: None,
        summary: "Create directories",
        filter_fields: &[PATH, RECURSIVE],
        example_filter: "path glob \"/tmp/*\"",
    },
    Capability {
        name: "fs.remove",
        main_denial: None,
        summary: "Delete files or directories",
        filter_fields: &[PATH, RECURSIVE],
        example_filter: "path glob \"/tmp/*\"",
    },
    Capability {
        name: "fs.move",
        main_denial: None,
        summary: "Move or rename a path",
        filter_fields: &[FROM, TO],
        example_filter: "to glob \"/archive/*\"",
    },
    Capability {
        name: "fs.copy",
        main_denial: None,
        summary: "Copy a path",
        filter_fields: &[FROM, TO, RECURSIVE],
        example_filter: "to glob \"/backup/*\"",
    },
];

const BRANCH: FilterField = field("branch", "string", "Local or requested remote branch name");
const REMOTE: FilterField = normalized_string_field(
    "remote",
    "Canonical HTTPS repository URL",
    FieldNormalization::RepositoryUrl,
);
const REMOTE_NAME: FilterField = field("remoteName", "string", "Named remote, for example origin");
const GIT: &[Capability] = &[
    Capability {
        name: "git.init",
        main_denial: None,
        summary: "Create a local repository and its VFS directory",
        filter_fields: &[PATH],
        example_filter: "path == \"/repo\"",
    },
    Capability {
        name: "git.clone",
        main_denial: None,
        summary: "Clone an HTTPS repository into a VFS directory",
        filter_fields: &[PATH, REMOTE_NAME, REMOTE, BRANCH],
        example_filter: "path == \"/repo\" and remote == \"https://github.com/acme/project.git\"",
    },
    Capability {
        name: "git.fetch",
        main_denial: None,
        summary: "Fetch or pull HTTPS remote branches into an existing repository",
        filter_fields: &[PATH, REMOTE_NAME, REMOTE, BRANCH],
        example_filter: "path == \"/repo\" and remote == \"https://github.com/acme/project.git\"",
    },
    Capability {
        name: "git.commit",
        main_denial: None,
        summary: "Commit staged changes with Blueprint identity",
        filter_fields: &[PATH, BRANCH],
        example_filter: "path == \"/repo\" and branch == \"main\"",
    },
];

const HTTP_VERB_FIELDS: &[FilterField] = &[
    field("host", "string", "Destination host, without port"),
    field("path", "string", "URL path component"),
    field("body_size", "number", "Request body size in bytes"),
    field("timeout_ms", "number", "Request timeout in milliseconds"),
];

const HTTP: &[Capability] = &[
    Capability {
        name: "http.get",
        main_denial: None,
        summary: "HTTP GET",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.post",
        main_denial: None,
        summary: "HTTP POST",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.put",
        main_denial: None,
        summary: "HTTP PUT",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.patch",
        main_denial: None,
        summary: "HTTP PATCH",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.delete",
        main_denial: None,
        summary: "HTTP DELETE",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.head",
        main_denial: None,
        summary: "HTTP HEAD",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.options",
        main_denial: None,
        summary: "HTTP OPTIONS",
        filter_fields: HTTP_VERB_FIELDS,
        example_filter: "host == \"api.example.com\"",
    },
    Capability {
        name: "http.download",
        main_denial: None,
        summary: "Download a URL straight to the VFS",
        filter_fields: &[
            field("host", "string", "Download host, without port"),
            field("url_path", "string", "URL path component"),
            vfs_path_field(
                "vfs_path",
                "Normalized absolute destination path in the VFS",
            ),
            field(
                "max_bytes",
                "number",
                "Requested download size cap in bytes",
            ),
            field(
                "overwrite",
                "boolean",
                "Whether an existing file may be clobbered",
            ),
            field(
                "decompress",
                "boolean",
                "Whether the response is decompressed on write",
            ),
        ],
        example_filter: "host == \"cdn.example.com\" and overwrite == false",
    },
];

/// Every other method `http.request` takes, gated as `http.<method>` with the
/// method lowercased: `http.request("TRACE", …)` checks `http.trace`. Kept
/// out of [`CORE_CATALOG`], whose consumers read a templated name as
/// `mcp.<server>` and concretize it per declared server; see
/// [`uncataloged_http_method`].
pub const HTTP_OTHER_METHOD: Capability = Capability {
    name: "http.<method>",
    main_denial: None,
    summary: "Any other HTTP method, through `http.request`: `http.trace` gates TRACE",
    filter_fields: HTTP_VERB_FIELDS,
    example_filter: "host == \"api.example.com\"",
};

/// Outbound MCP calls — one capability per declared server, `mcp.<server>` (the
/// `<server>` placeholder is filled in per blueprint, whose `mcp:` block names its
/// servers). The tool being called is in the filter context, so a rule can allow a
/// server broadly or constrain it to specific tools.
const MCP: &[Capability] = &[Capability {
    name: "mcp.<server>",
    main_denial: None,
    summary: "Call tools on a declared outbound MCP server (streamable_http)",
    filter_fields: &[
        field("tool", "string", "Name of the MCP tool being called"),
        field(
            "transport",
            "string",
            "MCP transport; always \"streamable_http\" today",
        ),
    ],
    example_filter: "tool == \"create_issue\"",
}];

const EMBEDDING: &[Capability] = &[Capability {
    name: "embedding.embed",
    // No `main_denial`: main-module access is the point. The program names an
    // alias and supplies text; the credential is resolved inside the provider
    // and never returned.
    main_denial: None,
    summary: "Embed text through a declared embedding alias (embed) and enumerate the aliases \
              it may use (models). Narrowing `model` also narrows what `models()` reveals: \
              every candidate is filtered through this same rule, so a listing never offers \
              an alias the caller would be denied at call time. A policy allowing no \
              candidates returns an empty listing",
    filter_fields: &[
        field(
            "model",
            "string",
            "Alias the call targets or the candidate being listed. The runtime preflight \
             does not ask policy about an empty name",
        ),
        field(
            "input_count",
            "number",
            "Texts in this call — N for embed, 0 for models. Input text is never in this \
             context",
        ),
    ],
    example_filter: "model glob \"memory-*\"",
}];

const LLM: &[Capability] = &[Capability {
    name: "llm.call",
    // No `main_denial`: main-module access is the point. A model call is not a
    // secret read — the program names a model and supplies a prompt, and the
    // credential is resolved inside the provider and never returned.
    main_denial: None,
    summary: "Call a model (call, batch) and enumerate the models it may call (models). \
              Narrowing `model` also narrows what `models()` reveals: every candidate is \
              filtered through this same rule, so a listing never offers a model the \
              caller would be denied at call time. A policy allowing no candidates \
              returns an empty listing",
    filter_fields: &[
        field(
            "model",
            "string",
            "Model name the call targets or the candidate being listed. \
             The runtime preflight does not ask policy about an empty name",
        ),
        field(
            "prompt_count",
            "number",
            "Prompts in this dispatch — 1 for call, N for batch, 0 for models. Prompt text \
             is never in this context",
        ),
    ],
    example_filter: "model glob \"claude-*\"",
}];

const SECRETS: &[Capability] = &[Capability {
    name: "secrets.get",
    main_denial: Some(
        "secret values are never available to main-module code, and no policy can \
         grant this. The package that needs this credential resolves it internally \
         and never returns it — pass the secret NAME to that package's API instead",
    ),
    summary: "Read a Blueprint-declared secret value",
    filter_fields: &[field("name", "string", "Declared secret name being read")],
    example_filter: "name == \"STRIPE_API_KEY\"",
}];

const SESSION: &[Capability] = &[
    Capability {
        name: "session.read",
        main_denial: None,
        summary: "Read session state (get, has), and decide which keys a list may reveal",
        filter_fields: &[KEY],
        example_filter: "key glob \"triage/*\"",
    },
    Capability {
        name: "session.write",
        main_denial: None,
        summary: "Store or overwrite a session value (set)",
        filter_fields: &[KEY],
        example_filter: "key glob \"triage/*\"",
    },
    Capability {
        name: "session.remove",
        main_denial: None,
        summary: "Delete a session key",
        filter_fields: &[KEY],
        example_filter: "key glob \"triage/*\"",
    },
    Capability {
        name: "session.list",
        main_denial: None,
        summary: "Enumerate session keys under a prefix",
        filter_fields: &[PREFIX],
        example_filter: "prefix == \"triage/\"",
    },
];

/// The capabilities of the core packages, which every embedder has.
const CORE_CATALOG: &[CapabilityGroup] = &[
    CapabilityGroup {
        module: "submilli:embedding",
        capabilities: EMBEDDING,
    },
    CapabilityGroup {
        module: "submilli:fs",
        capabilities: FS,
    },
    CapabilityGroup {
        module: "submilli:git",
        capabilities: GIT,
    },
    CapabilityGroup {
        module: "submilli:http",
        capabilities: HTTP,
    },
    CapabilityGroup {
        module: "submilli:llm",
        capabilities: LLM,
    },
    CapabilityGroup {
        module: "@mcp",
        capabilities: MCP,
    },
    CapabilityGroup {
        module: "submilli:secrets",
        capabilities: SECRETS,
    },
    CapabilityGroup {
        module: "submilli:session",
        capabilities: SESSION,
    },
];

const AGENT: FilterField = field(
    "agent",
    "string",
    "The sub-agent's name, as `submilli:agents` list() names it",
);

const AGENTS: CapabilityGroup = CapabilityGroup {
    module: "submilli:agents",
    capabilities: &[Capability {
        name: "agent.run",
        main_denial: None,
        summary: "Hand work to a harness sub-agent, or list the agents",
        filter_fields: &[AGENT],
        example_filter: "agent == \"researcher\"",
    }],
};

const SKILL: FilterField = field(
    "name",
    "string",
    "The skill's name, as `submilli:skills` list() names it",
);

const SKILLS: CapabilityGroup = CapabilityGroup {
    module: "submilli:skills",
    capabilities: &[Capability {
        name: "skill.load",
        main_denial: None,
        summary: "Load a harness skill, read its files, or list the skills",
        filter_fields: &[SKILL],
        example_filter: "name == \"code-review\"",
    }],
};

/// The capabilities of one optional package.
fn optional_group(package: OptionalPackage) -> &'static CapabilityGroup {
    match package {
        OptionalPackage::Agents => &AGENTS,
        OptionalPackage::Skills => &SKILLS,
    }
}

/// Every capability the core packages gate, grouped by source module. An
/// optional package's capabilities are listed only by [`catalog_for`].
pub fn catalog() -> &'static [CapabilityGroup] {
    CORE_CATALOG
}

/// Every capability the packages of `stdlib` gate, grouped by source module,
/// in [`catalog`]'s order: by module name without its `submilli:` or `@` prefix.
pub fn catalog_for(stdlib: Stdlib) -> Vec<&'static CapabilityGroup> {
    let mut groups: Vec<&'static CapabilityGroup> = CORE_CATALOG.iter().collect();
    groups.extend(stdlib.optional_packages().map(optional_group));
    groups.sort_by_key(|group| catalog_order(group.module));
    groups
}

fn catalog_order(module: &str) -> &str {
    module
        .strip_prefix("submilli:")
        .or_else(|| module.strip_prefix('@'))
        .unwrap_or(module)
}

/// Every group the runtime can gate, whichever packages an embedder enables.
/// Runtime checks use this: a capability reaches them only from a host
/// function, which is installed only when its package is enabled.
pub(crate) fn all_groups() -> impl Iterator<Item = &'static CapabilityGroup> {
    CORE_CATALOG.iter().chain(
        OptionalPackage::ALL
            .iter()
            .map(|package| optional_group(*package)),
    )
}

/// Look up a core capability by its exact name (templates included, by their
/// literal `mcp.<server>` spelling).
pub fn find(name: &str) -> Option<&'static Capability> {
    find_for(Stdlib::core(), name)
}

/// [`find`] among the capabilities of `stdlib`.
pub fn find_for(stdlib: Stdlib, name: &str) -> Option<&'static Capability> {
    catalog_for(stdlib)
        .into_iter()
        .flat_map(|group| group.capabilities)
        .find(|cap| cap.name == name)
}

/// [`find`] among every group the runtime can gate.
pub(crate) fn find_any(name: &str) -> Option<&'static Capability> {
    all_groups()
        .flat_map(|group| group.capabilities)
        .find(|cap| cap.name == name)
}

/// The method of a name that fills [`HTTP_OTHER_METHOD`]: `http.` and a method
/// token in the lowercase form the runtime checks, which the catalog has no
/// entry for.
pub fn uncataloged_http_method(name: &str) -> Option<&str> {
    let method = name.strip_prefix("http.")?;
    (is_lowercase_http_token(method) && find(name).is_none()).then_some(method)
}

/// The entry describing `name`: its catalog entry, or [`HTTP_OTHER_METHOD`]
/// for a name that fills it.
pub fn find_gating(name: &str) -> Option<&'static Capability> {
    find_gating_for(Stdlib::core(), name)
}

/// [`find_gating`] among the capabilities of `stdlib`.
pub fn find_gating_for(stdlib: Stdlib, name: &str) -> Option<&'static Capability> {
    find_for(stdlib, name).or_else(|| uncataloged_http_method(name).map(|_| &HTTP_OTHER_METHOD))
}

/// An RFC 9110 method token, the set `http.request` accepts, in lowercase.
fn is_lowercase_http_token(method: &str) -> bool {
    !method.is_empty()
        && method.bytes().all(|byte| {
            byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"!#$%&'*+-.^_`|~".contains(&byte)
        })
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;

    /// Marking a capability not-grantable-to-`main` changes the runtime's
    /// answer for every caller, so growing the set is a deliberate act.
    #[test]
    fn only_secrets_get_is_refused_to_main() {
        let refused: Vec<&str> = all_groups()
            .flat_map(|group| group.capabilities)
            .filter(|cap| !cap.grantable_to_main())
            .map(|cap| cap.name)
            .collect();
        assert_eq!(refused, ["secrets.get"]);
    }

    /// The core set lists exactly the core catalog, in the same order, so every
    /// surface that reads `catalog()` agrees with one that reads `catalog_for`.
    #[test]
    fn core_set_lists_the_core_catalog() {
        let core: Vec<&str> = catalog().iter().map(|group| group.module).collect();
        let for_core: Vec<&str> = catalog_for(Stdlib::core())
            .iter()
            .map(|group| group.module)
            .collect();
        assert_eq!(for_core, core);
    }

    #[test]
    fn catalog_entries_are_well_formed() {
        let mut seen = HashSet::new();
        for group in all_groups() {
            for cap in group.capabilities {
                assert!(
                    cap.name.contains('.') && !cap.name.is_empty(),
                    "capability name '{}' must be `module.action`",
                    cap.name
                );
                assert!(seen.insert(cap.name), "duplicate capability '{}'", cap.name);
                assert!(!cap.summary.is_empty(), "{}: empty summary", cap.name);
                // `filter_fields` may be empty for a capability gated by name alone
                // (e.g. the `@mcp` family, whose name already pins server + tool).
                assert!(
                    !cap.example_filter.is_empty(),
                    "{}: empty example_filter",
                    cap.name
                );
                assert!(
                    cap.main_denial.is_none_or(|reason| !reason.is_empty()),
                    "{}: empty main_denial reason",
                    cap.name
                );
                for f in cap.filter_fields {
                    assert!(!f.name.is_empty(), "{}: unnamed filter field", cap.name);
                    assert!(
                        matches!(f.ty, "string" | "number" | "boolean"),
                        "{}.{}: unknown field type '{}'",
                        cap.name,
                        f.name,
                        f.ty
                    );
                    assert!(!f.doc.is_empty(), "{}.{}: empty doc", cap.name, f.name);
                }
            }
        }
    }

    #[test]
    fn session_group_is_cataloged() {
        let group = catalog()
            .iter()
            .find(|g| g.module == "submilli:session")
            .expect("submilli:session group");
        let names: Vec<&str> = group.capabilities.iter().map(|cap| cap.name).collect();
        assert_eq!(
            names,
            [
                "session.read",
                "session.write",
                "session.remove",
                "session.list"
            ]
        );
        assert_eq!(
            find("session.list")
                .unwrap()
                .field_names()
                .collect::<Vec<_>>(),
            ["prefix"]
        );
    }

    #[test]
    fn embedding_group_is_cataloged() {
        let group = catalog()
            .iter()
            .find(|g| g.module == "submilli:embedding")
            .expect("submilli:embedding group");
        let names: Vec<&str> = group.capabilities.iter().map(|cap| cap.name).collect();
        assert_eq!(names, ["embedding.embed"]);
        let capability = find("embedding.embed").unwrap();
        assert_eq!(
            capability.field_names().collect::<Vec<_>>(),
            ["model", "input_count"]
        );
        assert_eq!(capability.example_filter, "model glob \"memory-*\"");
        assert!(capability.main_denial.is_none());
        assert!(
            capability.summary.contains("models()"),
            "the summary must say narrowing `model` narrows discovery: {}",
            capability.summary
        );
    }

    #[test]
    fn llm_group_is_cataloged() {
        let group = catalog()
            .iter()
            .find(|g| g.module == "submilli:llm")
            .expect("submilli:llm group");
        let names: Vec<&str> = group.capabilities.iter().map(|cap| cap.name).collect();
        assert_eq!(names, ["llm.call"]);
        assert_eq!(
            find("llm.call").unwrap().field_names().collect::<Vec<_>>(),
            ["model", "prompt_count"]
        );
        // The double gate lives in the summary wording and the host fn, not in
        // the struct, so an operator reading only the catalog still has to learn
        // that narrowing `model` also shortens what `models()` returns.
        let summary = find("llm.call").unwrap().summary;
        assert!(
            summary.contains("models()"),
            "the summary must say narrowing `model` narrows discovery: {summary}"
        );
    }

    #[test]
    fn uncataloged_http_methods_fill_the_template() {
        assert_eq!(uncataloged_http_method("http.trace"), Some("trace"));
        assert_eq!(uncataloged_http_method("http.propfind"), Some("propfind"));
        // Cataloged, the template itself, uppercase (the runtime lowercases),
        // empty, or not a token.
        for name in [
            "http.get",
            "http.<method>",
            "http.TRACE",
            "http.",
            "http.a b",
            "fs.trace",
        ] {
            assert!(uncataloged_http_method(name).is_none(), "{name}");
        }
        assert!(HTTP_OTHER_METHOD.is_template());
        assert_eq!(
            find_gating("http.trace").map(|c| c.name),
            Some("http.<method>")
        );
        assert_eq!(find_gating("http.get").map(|c| c.name), Some("http.get"));
    }

    #[test]
    fn find_and_is_template() {
        assert_eq!(find("fs.read").map(|c| c.name), Some("fs.read"));
        assert!(find("fs.nope").is_none());
        assert!(find("mcp.<server>").is_some_and(Capability::is_template));
        assert!(!find("fs.read").unwrap().is_template());
    }
}