vault provision: hook passes collection_id but crate resolves by name → CollectionNotFound #88

Closed
opened 2026-06-19 22:31:46 +02:00 by clawdie · 3 comments
Owner

Problem

The vault-provision chain compiles, deploys, and is fail-soft — but it will fail end-to-end with CollectionNotFound, because the spawner hook and the crate disagree on collection identifier type.

  • Hook (feat/vault-spawner-hook, daemon.rs):
    colibri_vault::provision(&tenant.collection_id, &tenant.jail_root_path)
    
    passes the collection_id.
  • Crate (crates/colibri-vault/src/lib.rs):
    pub async fn provision(collection_name: &str, ...)
    // resolve_collection(name) -> bw list collections --search <name>
    
    treats the arg as a name and resolves via name search.

Passing an ID into a name-search returns no match → CollectionNotFound, even after bw auth and a jail/tenant exist.

Per the 1:1:1 design (tenant_id == jail name == Vaultwarden collection): name the collection tenant_id, have the hook pass tenant.tenant_id (the name), and either drop the redundant collection_id column or repurpose it to store the collection name. Alternatively, teach the crate to resolve by collection ID.

Acceptance

A jailed spawn for a registered tenant resolves its collection and writes the .env (no CollectionNotFound).

See docs/HIVE-ONBOARDING.md and docs/CAPABILITY-ROUTING.md (layered-soul) for the design.

🤖 Generated with Claude Code

## Problem The vault-provision chain compiles, deploys, and is fail-soft — but it will fail end-to-end with `CollectionNotFound`, because the spawner hook and the crate disagree on collection identifier type. - **Hook** (`feat/vault-spawner-hook`, daemon.rs): ```rust colibri_vault::provision(&tenant.collection_id, &tenant.jail_root_path) ``` passes the **collection_id**. - **Crate** (`crates/colibri-vault/src/lib.rs`): ```rust pub async fn provision(collection_name: &str, ...) // resolve_collection(name) -> bw list collections --search <name> ``` treats the arg as a **name** and resolves via name search. Passing an ID into a name-search returns no match → `CollectionNotFound`, even after `bw` auth and a jail/tenant exist. ## Fix (recommended) Per the 1:1:1 design (`tenant_id == jail name == Vaultwarden collection`): **name the collection `tenant_id`**, have the hook pass `tenant.tenant_id` (the name), and either drop the redundant `collection_id` column or repurpose it to store the collection *name*. Alternatively, teach the crate to resolve by collection ID. ## Acceptance A jailed spawn for a registered tenant resolves its collection and writes the `.env` (no `CollectionNotFound`). See docs/HIVE-ONBOARDING.md and docs/CAPABILITY-ROUTING.md (layered-soul) for the design. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Trade-offs for the fix

Option A — resolve by NAME, collection named = tenant_id (drop/repurpose collection_id)

  • Matches the 1:1:1 design — one identifier, clean.
  • Human-readable; operator names the collection after the tenant. No opaque IDs at registration.
  • Crate already resolves by name — least code change.
  • bw list collections --search is substringacme matches acme-test; wrong-collection resolution = cross-tenant leak risk.
  • Needs exact-match enforcement (reject any result whose name ≠ tenant_id) + operator naming discipline.

Option B — resolve by ID, store collection_id

  • IDs stable + unique → immune to renames/substring ambiguity. Strongest isolation.
  • Decouples human name from machine mapping.
  • Extra registration step (create collection → read ID → store). Opaque IDs in DB/logs. New crate code path.

Recommendation: A + exact-match enforcement — B's safety with A's simplicity and the 1:1:1 elegance. Go pure-B only if collection renames become a real operational concern.

🤖 Generated with Claude Code

## Trade-offs for the fix **Option A — resolve by NAME, collection named = `tenant_id`** (drop/repurpose `collection_id`) - ✅ Matches the 1:1:1 design — one identifier, clean. - ✅ Human-readable; operator names the collection after the tenant. No opaque IDs at registration. - ✅ Crate already resolves by name — least code change. - ❌ `bw list collections --search` is **substring** → `acme` matches `acme-test`; wrong-collection resolution = cross-tenant leak risk. - ❌ Needs **exact-match enforcement** (reject any result whose name ≠ `tenant_id`) + operator naming discipline. **Option B — resolve by ID, store `collection_id`** - ✅ IDs stable + unique → immune to renames/substring ambiguity. Strongest isolation. - ✅ Decouples human name from machine mapping. - ❌ Extra registration step (create collection → read ID → store). Opaque IDs in DB/logs. New crate code path. **Recommendation:** **A + exact-match enforcement** — B's safety with A's simplicity and the 1:1:1 elegance. Go pure-B only if collection renames become a real operational concern. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
clawdie added the
first-proof blocker
label 2026-06-20 06:38:04 +02:00
Author
Owner

Sequencing + decisions (operator-confirmed)

  • Labeled first-proof blocker (with #89). #92/#93 are hardening, not gates.
  • First-proof policy: the first proven end-to-end uses a scratch jail + throwaway test collection only — no real tenant data until the hardening (#92 canonicalization) lands.
  • collection_id migration caveat: collection_id is NOT NULL UNIQUE and already persisted on main (schema.rs). CREATE TABLE IF NOT EXISTS will not alter initialized DBs, so the "collection name = tenant_id, drop collection_id" option is not a free drop. Either (a) keep the column and default it to tenant_id at registration (simplest, no migration), or (b) ship a real migration/backfill. Resolving by name + exact-match works regardless of which you pick.

🤖 Generated with Claude Code

## Sequencing + decisions (operator-confirmed) - Labeled **first-proof blocker** (with #89). #92/#93 are hardening, not gates. - **First-proof policy:** the first proven end-to-end uses a **scratch jail + throwaway test collection only** — no real tenant data until the hardening (#92 canonicalization) lands. - **`collection_id` migration caveat:** `collection_id` is `NOT NULL UNIQUE` and *already persisted* on main (`schema.rs`). `CREATE TABLE IF NOT EXISTS` will not alter initialized DBs, so the "collection name = tenant_id, **drop** collection_id" option is **not** a free drop. Either (a) keep the column and default it to `tenant_id` at registration (simplest, no migration), or (b) ship a real migration/backfill. Resolving by name + exact-match works regardless of which you pick. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Resolved by #94 (fix(vault): use tenant collection names with per-call unlock) — verified: tenant id is now passed as the Vaultwarden collection name (#88), and colibri-vault does per-call login→unlock→fetch→lock from the daemon's provider env, locking on both success and error paths (#89). Closing.

🤖 Generated with Claude Code

Resolved by #94 (`fix(vault): use tenant collection names with per-call unlock`) — verified: tenant id is now passed as the Vaultwarden collection name (#88), and `colibri-vault` does per-call login→unlock→fetch→lock from the daemon's provider env, locking on both success and error paths (#89). Closing. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: clawdie/colibri#88
No description provided.