canned-prompts/reference
tegwick ea70a59610 CANP-WP-0002 T02: registry-scoped identity and namespace ownership
Section 17 already defined what a registry does when the same id@version is
republished; nothing defined what a *consumer* does. That is where namespace
conflict actually bites — in the catalog, after installing from two
registries.

Identity is now registry-scoped: an id names a package within a registry, the
way a path names a file within a repository. A local-first format with no
signing, no federation and no central authority cannot enforce global
uniqueness, and an unenforceable guarantee is worse than none — it invites
consumers to conflate two packages that merely share a name. A qualified
`<registry>:<id>` reference distinguishes them, and `:` is now barred from ids
so the separator stays available.

Installing the same id from two registries is therefore not a conflict. The
catalog is namespaced by registry and keeps both.

Ownership is registry policy, not package data. An optional `registry.yaml`
names a registry and records namespace claims. Those claims are explicitly
descriptive — a filesystem registry cannot authenticate a publisher, and
`publish` says so rather than implying it checked. Keeping the claim out of
packages leaves artifacts free of unverifiable assertions of authority, and
means package semantics do not change when a hosted registry appears later.

Spec: 3.2 (registry-scoped identity, qualified references), 17 (immutability
scoped to a registry), 20.1 and 20.2 (new), 18 (registry-manifest
validation), 21 (qualified references, reserved `local` name).

Reference CLI: parse_reference, check_registry_name, read_registry_manifest,
registry_name, namespace_policy; registry_package_path and
catalog_package_path split; resolve_installed reports ambiguity and returns
the source registry; iter_catalog; `add --as`; closed-namespace warning on
publish. Tests 11 -> 21.

The catalog layout changed. An existing catalog is detected and reported with
instructions rather than failing as "package not found". Signing, trust
scoring and federation remain non-goals and were not touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 388925@bnt-lap001
Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 01:14:54 +02:00
..
tests CANP-WP-0002 T02: registry-scoped identity and namespace ownership 2026-09-06 01:14:54 +02:00
canned_prompts.py CANP-WP-0002 T02: registry-scoped identity and namespace ownership 2026-09-06 01:14:54 +02:00
pyproject.toml Register with Custodian State Hub and seed format open-questions workplan 2026-09-06 00:45:28 +02:00
README.md CANP-WP-0002 T02: registry-scoped identity and namespace ownership 2026-09-06 01:14:54 +02:00
requirements-dev.txt Register with Custodian State Hub and seed format open-questions workplan 2026-09-06 00:45:28 +02:00
requirements.txt Register with Custodian State Hub and seed format open-questions workplan 2026-09-06 00:45:28 +02:00

canned-prompts reference CLI

This is intentionally a small reference implementation, not the intended final architecture.

It demonstrates seven verbs:

add      PATH
search   QUERY
show     ID
resolve  ID --set key=value
render   ID --set key=value
install  ID [--version VERSION]
publish  PATH

The implementation uses a local catalog plus a filesystem registry and performs no model calls.

resolve and render are separate because the specification separates them (§ 5.1): resolution decides each value and may be non-deterministic, rendering substitutes and always is. resolve prints where every value came from — supplied, default, or fallback — before any prompt is produced.

Stores

Default locations:

~/.canned-prompts/catalog
~/.canned-prompts/registry

A registry stores packages flat, because an id is unambiguous within one registry:

<registry>/<id path>/<version>/...

A catalog is namespaced by registry, because identity is registry-scoped (§ 3.2) and the same id may be installed from more than one place:

<catalog>/<registry name>/<id path>/<version>/...

For example:

~/.canned-prompts/catalog/house/practice/pqrst-estimate/0.1.0/
~/.canned-prompts/catalog/local/practice/pqrst-estimate/0.1.0/

A registry's name comes from its optional registry.yaml, and otherwise from its directory basename. add takes a package from a path rather than a registry, so it files it under local (override with --as).

Commands that take an ID accept a bare id or a qualified <registry>:<id>. A bare id installed from more than one registry is reported as ambiguous rather than resolved by guessing.

Design choices

  • YAML manifest via PyYAML.
  • {{ name }} template substitution only.
  • No arbitrary expression/code execution.
  • Published versions are immutable by default.
  • install copies from registry to catalog.
  • add copies a package directly to catalog.
  • search, show, resolve, and render operate on catalog packages, and print qualified <registry>:<id> references.
  • An optional registry.yaml names a registry and records namespace claims. publish warns when a namespace is declared closed — it cannot authenticate a publisher, and says so rather than implying it checked.
  • Static input defaults are applied; derived defaults (§ 6.1) are not. This tool never calls a model, so a derived default is satisfied only by its static fallback value. Without one, resolve reports the input as unresolved and render refuses rather than substituting empty text.

Use this implementation to challenge the format. Replace it once real usage reveals the right architecture.