--- id: CANP-WP-0002 type: workplan title: "Resolve CPF v0.1 open questions promoted for v0.2" domain: agents repo: canned-prompts status: proposed owner: codex topic_slug: practice created: "2026-09-06" updated: "2026-09-06" reviewed_at: "2026-09-06" reviewed_by: "claude" context_paths: - "INTENT.md" - "CannedPromptFormat-v0.1.md" - "reference/canned_prompts.py" - "examples/pqrst-estimate/" state_hub_workstream_id: "c87c8e27-8b11-5306-b687-86779ef3f1be" --- # Resolve CPF v0.1 open questions promoted for v0.2 `CannedPromptFormat-v0.1.md` § 23 currently lists twelve items as "experience should determine". A review of the seed on 2026-09-06 (spec + reference CLI + `examples/pqrst-estimate`; 3/3 tests pass, full local lifecycle smokes clean) plus an operator interview promoted five of them from "wait and see" to "decide, with a stated leaning". This workplan carries those decisions. Unpromoted § 23 items stay deferred and unchanged: content macros, cryptographic integrity/signing, model capability vocabularies, run manifests and evidence formats, deterministic compilation manifests, trust/reputation signals, federated discovery, richer template syntax. **Guardrail for every task below:** `INTENT.md` § Deliberate boundary. The format may *declare* a requirement; it must not specify the resolver, runtime, or execution engine that satisfies it. ## Optional inputs need defaults ```task id: CANP-WP-0002-T01 status: done priority: high state_hub_task_id: "e49bf036-0aee-5bb2-abbd-3d3f683df298" ``` **Gap found in review.** Rendering rule § 5.1(4) makes any unresolved placeholder an error, and § 6 gives `inputs` no `default` field. An input declared `required: false` and referenced from the template therefore makes `render` fail unless a value is supplied — optional inputs are effectively unusable. The spec's own § 4 manifest example hits this with `repository_context`. `reference/canned_prompts.py` is conformant here; the defect is in the spec. **Decision (operator, 2026-09-06):** add a `default` field to `inputs`, and allow that default to be either - a **static** value, or - a **derived** default — a prompt that produces the value from available context. Unresolved-with-no-default remains an error. **Design note.** § 10 already carried the derivation mechanism: `dependencies` accepts `requirement: generate`, defined as "a resolver MAY satisfy a missing dependency by invoking an appropriate generation process", with resolution left undefined. A derived default therefore did not need a new concept, only a binding — the input's default names the dependency, and the dependency records what may be generated and at which version. **Follow-up decisions (operator, 2026-09-06):** - *Both declaration forms are allowed.* A derived default may reference a declared prompt dependency, or carry an inline `prompt`. The reference form is preferred and the spec says why (an inline prompt is anonymous — unversioned, unprovenanced, un-evaluable), and a validator SHOULD warn when a **published** package derives inline. Inline is for local and draft packages. - *Error unless static fallback.* A derived default MAY declare a static `value`. A consumer that does not derive uses it; with no fallback the input stays unresolved, which is an error. Derivation never silently yields empty content. - *Split render from resolve.* Resolution decides values and may be non-deterministic; rendering substitutes and always is. Rendering MUST NOT derive. This preserves INTENT success criterion 4 verbatim — no change to `INTENT.md` was needed. Work: Delivered: 1. § 5.1 rewritten as "Resolution and rendering" — six resolution rules and four rendering rules, with rendering explicitly deterministic and forbidden from deriving (rule 10). Resolution must report which values were derived (rule 6). A tool that resolves only supplied values and static defaults is stated to be conforming. 2. § 6 gains `default` in the field table plus a new § 6.1 covering both declaration forms, the static fallback, and why the reference form is preferred. 3. § 4 manifest surface updated: `repository_context` — the field that demonstrated the original defect — now carries a derived default with a fallback, backed by a `requirement: generate` prompt dependency. 4. § 10 states the binding between `requirement: generate` and a referenced derived default. 5. § 18 gains validation rules 11–13 and the publish-time inline warning. 6. § 19 gains two MUST NOTs: a derived default's prompt text is not an instruction addressed to the consuming tool, and derivation must be visible to the caller. 7. § 21 documents the new `resolve` verb. 8. `reference/canned_prompts.py`: `Resolution` dataclass, `resolve_inputs` (with `resolve_values` kept as a wrapper), `input_default_kind`, `prompt_dependency_ids`, `validate_input_default`, a `resolve` command, and a specific render-time error naming the underivable inputs. Tests: 3 → 11, all passing. Both READMEs document the split. ## Registry namespaces and ownership ```task id: CANP-WP-0002-T02 status: done priority: high state_hub_task_id: "a52cd41e-2bcc-50ee-96a1-d645a06df922" ``` An id like `practice/pqrst-estimate` has a namespace prefix with no owner. Two authors publishing `practice/…` into one registry collide today; `add` and `publish` only refuse an exact `id@version` that already exists. **Sharpened during the work.** § 17 already said what a *registry* does on collision; nothing said what a *consumer* does. That is where the conflict actually bites — in the catalog, after installing from two registries. **Decisions (operator, 2026-09-06):** - *Identity is registry-scoped.* An id names a package within a registry, the way a path names a file within a repository. The same id from two registries may be two different packages. 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 `:` reference distinguishes them. - *Ownership is registry policy.* An optional `registry.yaml` names the registry and records namespace claims (`owner`, `policy: open | closed`). Packages never assert who owns their namespace, keeping unverifiable authority claims out of artifacts (§ 19) and leaving package semantics unchanged when a filesystem registry is later replaced by a hosted one. - *Installing the same id from two registries is allowed, not a conflict.* The catalog is namespaced by registry, so both are retained. This follows from registry-scoped identity rather than working around it: two packages that merely share a name were never in conflict to begin with. Delivered: 1. § 3.2 gains "Identity is registry-scoped" — the reasoning, the qualified reference form, and a ban on `:` in ids so the separator stays available. A tool finding one id in several registries MUST report the ambiguity. 2. § 17 scopes immutability to a registry, consistent with identity. 3. § 20.1 (new) specifies the optional `registry.yaml`: `format`, `name`, `description`, `namespaces`. Claims are explicitly descriptive — a filesystem registry cannot authenticate a publisher. A bare directory remains a valid registry, named by its basename. 4. § 20.2 (new) requires consumers to keep registries distinct and documents the reference catalog layout. 5. § 18 gains registry-manifest validation; § 21 documents qualified references and the reserved `local` registry name. 6. `reference/canned_prompts.py`: `parse_reference`, `check_registry_name`, `read_registry_manifest`, `registry_name`, `namespace_policy`, split `registry_package_path` / `catalog_package_path`, a `resolve_installed` that reports ambiguity and returns the source registry, `iter_catalog`, `--as` on `add`, a publish-time warning on closed namespaces, and a specific error for the pre-registry-scoped catalog layout. Tests 11 → 21. **Migration note:** the catalog layout changed. An existing catalog from before this change 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. ## Prompt composition and inheritance ```task id: CANP-WP-0002-T03 status: todo priority: high state_hub_task_id: "0505c790-e57e-568c-9c8c-376957c9b08e" ``` Largest gap between `INTENT.md` and the spec. Principle 9 ("composition without capture") and the `dependencies.prompts` field both promise composition, but v0.1 defines no mechanism — `dependencies` is a declared field with no semantics. T01 has since settled one corner of this: a derived default binds an input to a prompt dependency declared `requirement: generate`, so package-to-package reference already exists for that one case. Build on it rather than around it. Decide what composition means at the *artifact* level: how one package references another, whether references are includes, extends, or plain declared prerequisites, and how versions are pinned. Leaning: declaration only — a package states what it needs, and resolution stays with the consumer, per the INTENT boundary. Do not introduce range resolution (INTENT non-goal). ## Canonical eval schemas ```task id: CANP-WP-0002-T04 status: todo priority: medium state_hub_task_id: "0daf1097-6599-563f-8965-739cbc874ddf" ``` Cheapest item to pin down. `evals/` is a reserved path and `evals:` is a manifest list, but § 12 defines no schema, so an eval file is an unvalidated blob that no tool can act on. Define a minimal eval-spec schema: identity, what is being asserted, the fixture it runs against, and how a result is reported. Keep it declarative and engine-neutral — "universal prompt evaluation" is an explicit INTENT non-goal, so this specifies the *file*, not an evaluation engine. Extend § 18 to validate eval files that declare the schema, and add one eval to `examples/pqrst-estimate` as a worked case. ## Typed context and dependency contracts ```task id: CANP-WP-0002-T05 status: todo priority: medium state_hub_task_id: "5beec7ba-e6c9-58b1-a065-b06aba015034" ``` `dependencies.context` and `dependencies.capabilities` appear in the § 4 manifest surface with no semantics whatsoever in v0.1, and § 9 `compatibility.capabilities` overlaps them without a stated relationship. Decide: what a context dependency declares, how it differs from an input, how it relates to `compatibility.capabilities`, and whether capability names are free strings in v0.2 (model capability vocabularies stay deferred). T01 is now settled and partly answers this: a derived default is a consumer-resolved context requirement expressed through `dependencies.prompts` rather than through `dependencies.context`. Decide whether `context` is still a distinct concept or collapses into the prompt-dependency mechanism. ## Rewrite specification section 23 ```task id: CANP-WP-0002-T06 status: todo priority: medium state_hub_task_id: "06032689-fc19-5fcd-a76e-5dbc87d02cd4" ``` After T01–T05 land, replace § 23's flat twelve-item list with two sections: questions **being decided for v0.2** (each with its scoped question and stated leaning, referencing this workplan) and questions **still deferred** (the eight unpromoted items listed at the top of this file). Bump the spec status line if the format revision warrants it. ## Reference implementation conformance fixes ```task id: CANP-WP-0002-T07 status: todo priority: low state_hub_task_id: "f642abe6-8222-5958-88ee-7a53454f2344" ``` Two defects found in the same review, independent of the format questions: 1. **Prerelease versions sort as newest.** `parse_semver` in `reference/canned_prompts.py` returns `(major, minor, patch, raw_string)`, so `1.0.0-rc1` and `1.0.0` tie on the numeric fields and then compare as strings — `"1.0.0-rc1" > "1.0.0"`. `resolve_installed` with no `--version` therefore selects a prerelease over its own release. Order prerelease below release, or state in § 17 that v0.1 ignores prerelease ordering. 2. **`copy_immutable` copies everything.** `add`/`publish` use `shutil.copytree` over the whole source directory, so a stray `.git`, `.venv`, or scratch file lands in the catalog and registry. § 2 says tools MUST ignore unknown non-reserved files unless a manifest field references them. Decide whether packaging is reserved-paths-only or an explicit ignore list, then align the implementation and § 2.