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§
- Managed
Resource - 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?Nlist that must continue numbering after some earlier bound parameters (managed_upsert_unique’s dup-check bindsname/idat?1/?2before any owner-bound ids).- update_
set 🔒 - values_
with_ 🔒global row.values()withis_globalreplaced when a method hard-codes it (the global upsert forces 1, the bundle-scoped upsert forces 0).