Skip to main content

Module managed

Module managed 

Source
Expand description

The “managed primitive” storage shape shared by standalone Skills and MCP Servers (v1 composable model, SPEC_V1_MCP_SKILLS_PRIMITIVES_2026_06_30.md; bundle-level refs per SPEC_BUNDLE_AS_CONTAINER_V2_2026_08_17.md).

Both primitives are the same pattern: one catalog table whose rows are either global (visible to everyone) or private, an agent-level ref table binding rows to agents, and a bundle-level ref table binding rows to bundles. (Only skills use theirs now: a bundle carries no MCP servers, SPEC_BUNDLE_CONTENTS_MEMORY_NOT_MCP_2026_10_07.md §3.1.) Before this module skills.rs and mcp_servers.rs each carried their own copy of the eighteen list / get / delete / bind / unbind / upsert / access-check methods — identical except for table and column names and the noun in error messages (REPORT_DRY_AND_MODULARITY_AUDIT_2026_09_06.md §2.4). The per-primitive modules keep their public methods, response structs, doc comments and tests; only the bodies delegate here. Every SQL statement below is the one the hand-written methods used, with the names substituted.

SQL is assembled with format! from the trait’s const names only — never from runtime input — so the generated statements are exactly as fixed as the literals they replace. Values still go through ?N parameters.

§self vs catalog (Phase 2 of SPEC_DURABLE_BINDINGS_2026_09_10.md)

As of Phase 2’s application-layer redirect (§5.4), self and the catalog table’s store are no longer guaranteed to be the same physical SQLite file: db_skills/db_mcp_servers are authoritatively identity_store now, while db_agent_skills_ref/db_bundle_skills_ref (and the MCP equivalents) stay on self (mstore) until Phase 3/6 promotes them too. Every method that used to join the catalog table against a ref table in one SQL statement now takes an explicit catalog: &Store parameter and does two queries composed in Rust instead — fetch the relevant ref ids from self, fetch the relevant catalog rows from catalog, combine and sort in memory. This is not a new pattern — managed_bind_bundle/managed_upsert_unique_for_bundle already took an id_store: &Store parameter for bundle-existence checks, for the identical reason. managed_get needs no such change — it is genuinely single-table, so callers invoke it directly on whichever store actually owns the table (catalog for a Skill/McpServer row).

managed_upsert_unique additionally loses single-transaction atomicity by this same split: the catalog insert and the ref-table bind are now two separate connections, so they can’t share a rusqlite::Transaction. This is a documented, accepted interim gap (§5.4) — narrowed by a best-effort compensating delete on the catalog row if the ref-bind fails, so a failed bind cannot leave a permanent, undiscoverable orphan catalog row behind.

Enums§

Owner
Which ref table a scoped query is keyed on.

Traits§

ManagedResource
One managed primitive: its catalog row type plus the names that differ between primitives.

Functions§

insert_sql 🔒
placeholders 🔒
?1, ?2, ... ?n.
placeholders_from 🔒
?{start}, ?{start+1}, ... ?{start+n-1} — for a ?N list that must continue numbering after some earlier bound parameters (managed_upsert_unique’s dup-check binds name/id at ?1/?2 before any owner-bound ids).
update_set 🔒
values_with_global 🔒
row.values() with is_global replaced when a method hard-codes it (the global upsert forces 1, the bundle-scoped upsert forces 0).