From c580bf63c9110a07471e6d2e2b0801aad64f4b25 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 6 Sep 2026 08:11:13 +0200 Subject: [PATCH] CANP-WP-0002 T05: required versus observed, and typed context dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The answer to the capability question was conditional: keep both fields if they carry the required/observed distinction, fix the terminology if they do not. They did not. Section 9 opened with "records known requirements or observations", mixing both in one field — `models` was observational ("known to be compatible or evaluated") while `capabilities` was prescriptive ("expected from the execution environment"). Section 10 then described dependencies as what a prompt "expects". Both fields said expected, so the overlap was real ambiguity rather than redundancy, and the fix is terminology. `dependencies` now means **required**; `compatibility` means **observed**. A consumer must not refuse to run a package because its environment is absent from a compatibility list. The same capability name may legitimately appear in both: required to run at all, and separately observed to work well on particular models. `compatibility.aliases` records the same capability under other names, so a consumer can recognize a requirement its environment labels differently. Dependencies now have three kinds, separated by what the format can do about them: `prompts` it resolves by id and version; `context` names what it does not package at all; `capabilities` are what the environment must be able to do. Context entries use `name` rather than `id`, because nothing can look them up, and `description` is required because nothing else can explain an unpackaged dependency. A capability takes no version and no `requirement: generate` — it is not an artifact and cannot be fetched, pinned or generated. Capability names are free-form kebab-case, validated for shape and not membership, exactly as tags are. Section 10.1 also draws the line the format had never stated: an input is content the caller passes for one use; a context dependency is a standing fact about the environment. Spec: 9 rewritten, 9.1 and 10.1 and 10.2 new, 10 reframed, 18 (rules 19-20), 4 updated. Former 10.1/10.2 renumbered to 10.3/10.4 with cross-references. Reference CLI: validate_capabilities, validate_context_dependencies, and a `resolve` section listing required capabilities and context under "this tool cannot verify these" rather than implying it checked. Tests 51 -> 65. Also drops an invented `session-review` capability from the example package in favour of an honest `long-context` observation. Co-Authored-By: Claude Opus 5 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 --- CannedPromptFormat-v0.1.md | 115 +++++++++++++++--- README.md | 23 ++++ examples/house-style/README.md | 2 +- examples/pqrst-estimate/prompt.yaml | 2 +- reference/README.md | 2 + reference/canned_prompts.py | 84 +++++++++++-- reference/tests/test_canned_prompts.py | 65 ++++++++++ .../CANP-WP-0002-format-open-questions.md | 62 ++++++++-- 8 files changed, 323 insertions(+), 32 deletions(-) diff --git a/CannedPromptFormat-v0.1.md b/CannedPromptFormat-v0.1.md index e8b3930..38f24f6 100644 --- a/CannedPromptFormat-v0.1.md +++ b/CannedPromptFormat-v0.1.md @@ -181,7 +181,7 @@ Declares what kind of artifact the package is. Optional; defaults to | Value | Meaning | |---|---| | `template` | A complete prompt, intended to be used on its own | -| `fragment` | A reusable block intended for inclusion in other packages (§ 10.1) | +| `fragment` | A reusable block intended for inclusion in other packages (§ 10.4) | `type` is advisory. A fragment is a perfectly valid package and MAY be rendered on its own; the field records the author's intent so that a consumer @@ -233,7 +233,7 @@ output: compatibility: capabilities: - - code-analysis + - long-context models: [] providers: [] @@ -242,8 +242,12 @@ dependencies: - id: context/repository-summary version: 1.0.0 requirement: generate - context: [] - capabilities: [] + context: + - name: repository-tree + description: A listing of the repository under review. + requirement: optional + capabilities: + - code-analysis examples: - examples/basic.yaml @@ -313,7 +317,7 @@ always produce the same output. A consumer that satisfies a derived default static fallback `value` when one is declared, and otherwise leaves the input unresolved. 6. An included input default is satisfied by rendering the included package - (§ 10.2). This is deterministic, so a consumer that can render can satisfy + (§ 10.4). This is deterministic, so a consumer that can render can satisfy it; one that cannot locate the package uses the static fallback `value` when declared, and otherwise leaves the input unresolved. 7. Resolution MUST report which values were derived or included, so that a @@ -449,7 +453,7 @@ and draft packages. #### Included default A default may instead **include** another package's rendered template as text -(§ 10.2). Unlike a derived default this is deterministic and needs no model, +(§ 10.4). Unlike a derived default this is deterministic and needs no model, so every implementation that can render can also include: ```yaml @@ -538,13 +542,18 @@ Registries MAY index output format for discovery. ## 9. Compatibility -`compatibility` records known requirements or observations without binding the package to one runtime. +`compatibility` records **observations**, never requirements. + +Everything here describes where the package has been seen to work. Nothing here +is a precondition for using it, and a consumer MUST NOT refuse to run a package +because its environment is absent from these lists. ```yaml compatibility: capabilities: - - code-analysis - long-context + aliases: + web-search: [browsing, tool-search] models: - example/model-family providers: [] @@ -552,15 +561,43 @@ compatibility: Semantics: -- `capabilities`: abstract capabilities expected from the execution environment; +- `capabilities`: capabilities the package is known to work well with, whether + or not it requires them; +- `aliases`: the same capability as known by other names, so that a consumer + can recognize a requirement its environment labels differently; - `models`: model identifiers known to be compatible or evaluated; - `providers`: provider identifiers when provider-specific behavior matters. An empty list means "not constrained/unspecified", not "compatible with nothing". +### 9.1 Required versus compatible + +Earlier drafts described `compatibility` as recording "requirements or +observations" and `dependencies` as recording what a prompt "expects", so both +appeared to say the same thing about capabilities. They do not, and the +distinction is worth stating in one word each: + +| Field | Meaning | Absence means | +|---|---|---| +| `dependencies` | **required** — the package does not work without it | a consumer SHOULD warn or refuse before running | +| `compatibility` | **observed** — the package is known to work with it | nothing; it is information, not a gate | + +A capability the prompt cannot function without belongs in +`dependencies.capabilities`. A capability it merely benefits from, or has been +evaluated against, belongs in `compatibility.capabilities`. The same name may +legitimately appear in both: required to run at all, and separately observed to +work well on particular models. + ## 10. Dependencies -Dependencies describe external artifacts or capabilities expected by the prompt. +Dependencies describe what a package **requires** in order to work (§ 9.1). +There are three kinds, separated by what CPF can do about them: + +| Kind | Names | CPF can resolve it | +|---|---|---| +| `prompts` | other CPF packages | yes — by id and version (§ 10.3) | +| `context` | things CPF does not package | no — the consumer supplies them | +| `capabilities` | what the environment must be able to do | no — the environment either can or cannot | ```yaml dependencies: @@ -570,13 +607,55 @@ dependencies: requirement: optional context: - - id: policy/security + - name: repository-tree + kind: information-space + description: A listing of the repository under review. requirement: required capabilities: - web-search ``` +### 10.1 Context dependencies + +`context` names something the package needs that CPF neither packages nor +resolves: a live information space, an API, a corpus, a document the caller +supplies. Anything CPF *can* package is a package — a reusable policy or style +block belongs in `prompts` as a `type: fragment` package (§ 3.2), not here. + +Entries use `name` rather than `id`, precisely because they are not package +identifiers and nothing can look them up. + +| Field | Required | Meaning | +|---|---:|---| +| `name` | yes | What the context is called, in kebab-case | +| `description` | yes | What it is and what it must contain | +| `kind` | no | Free-form hint, e.g. `information-space`, `api`, `document`, `corpus` | +| `requirement` | no | `required` or `optional`; defaults to `required` | + +`description` is required because nothing else can explain an unpackaged +dependency. A consumer meeting this entry has only the text to go on, and +INTENT principle 3 puts hidden context at odds with reuse. + +A context dependency is not an input. An input is content the caller passes for +one use (§ 6); a context dependency is a standing fact about the environment +the package needs to be run in. + +### 10.2 Capability dependencies + +`capabilities` lists what the execution environment must be able to do, as +free-form lowercase kebab-case names — validated for shape, not for membership +in any vocabulary, exactly as `tags` are (§ 15). Standardizing model capability +vocabularies remains deferred; no real usage has yet asked for one. + +A capability is not an artifact: it cannot be fetched, pinned or generated, so +it takes no version and no `requirement: generate`. It is either present or it +is not. A consumer that knows it lacks a required capability SHOULD say so +before running rather than after. + +Where an environment knows the same capability by another name, +`compatibility.aliases` (§ 9) can record the correspondence. + Recommended requirement values: - `required` @@ -585,7 +664,7 @@ Recommended requirement values: `generate` means that a resolver MAY satisfy a missing dependency by invoking an appropriate generation process. **CPF v0.1 does not define how generation or dependency resolution works.** -### 10.1 Naming and pinning a dependency +### 10.3 Naming and pinning a dependency A dependency's `id` MAY be a qualified `:` reference (§ 3.2). An unqualified id is resolved by the consumer against whatever registries it @@ -614,7 +693,7 @@ solver: each dependency is selected independently against what is available, and no consumer is expected to satisfy constraints across a dependency graph. A single lower bound is a selector, not a constraint system. -### 10.2 Composition +### 10.4 Composition A package composes another in one of two ways, both expressed as an input default (§ 6.1) so that composition reuses the resolution machinery rather @@ -736,7 +815,7 @@ Each entry is a single-key mapping, so a check may appear more than once. `output.criteria` is a list of statements about a good result. Judging them requires running the prompt and assessing what comes back, which CPF does not specify and most consumers cannot do. They are **declared, not run** — the -same division as `include` and `derive` in § 10.2, and for the same reason. +same division as `include` and `derive` in § 10.4, and for the same reason. #### Results are not part of the package @@ -838,13 +917,17 @@ A v0.1 validator SHOULD verify at least: 14. no default declares both `include` and `derive`; 15. every dependency referenced by a composition or derived default declares a `version`, and that version is a semver literal, `any`, `newest`, or a - `>=` lower bound (§ 10.1); + `>=` lower bound (§ 10.3); 16. inclusion does not form a cycle; 17. every eval file parses and declares a `schema`; 18. an eval declaring `canned-prompts/eval-rubric/v0.1` has a `name`, asserts something via `render` or `output`, uses only known render checks, and — when it declares an `example` — names a path listed in the manifest's - `examples`. + `examples`; +19. every `dependencies.context` entry declares a `name` and a `description`, + and a `requirement` of `required` or `optional` if present; +20. capability names in `dependencies.capabilities` and + `compatibility.capabilities` are lowercase kebab-case. A validator SHOULD additionally warn when a package intended for publication declares an inline derivation prompt (§ 6.1). diff --git a/README.md b/README.md index c57e343..7ef1ab7 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,29 @@ local:practice/pqrst-estimate@0.2.0 An eval declares assessment, never results. Results are run evidence and live outside the immutable package. +## Required versus compatible + +Two fields used to say overlapping things about capabilities. They now differ +in one word each: + +| Field | Meaning | Absence means | +|-------|---------|---------------| +| `dependencies` | **required** — does not work without it | a consumer should warn or refuse | +| `compatibility` | **observed** — known to work with it | nothing; it is information | + +Dependencies come in three kinds, separated by what the format can do about +them: `prompts` are packages it resolves by id and version, `context` names +things it does not package at all (an information space, an API, a corpus the +caller supplies), and `capabilities` are what the environment must be able to +do. `resolve` lists the last two, since the reference tool cannot verify +either: + +```text +requires (this tool cannot verify these): + capability web-search + context repository-tree — A listing of the repository under review. +``` + ## Registries and identity An id names a package *within a registry* (`CannedPromptFormat-v0.1.md` diff --git a/examples/house-style/README.md b/examples/house-style/README.md index 0ca9c97..3af72b2 100644 --- a/examples/house-style/README.md +++ b/examples/house-style/README.md @@ -21,7 +21,7 @@ inputs: Inclusion is deterministic — the fragment's template is rendered and inlined as text, with no model involved — so the reference CLI performs it during -`resolve`. See `CannedPromptFormat-v0.1.md` § 10.2. +`resolve`. See `CannedPromptFormat-v0.1.md` § 10.4. Its `tone` parameter is inherited from the including package when that package declares one, and otherwise falls back to the default here. diff --git a/examples/pqrst-estimate/prompt.yaml b/examples/pqrst-estimate/prompt.yaml index 8444a02..4993cdd 100644 --- a/examples/pqrst-estimate/prompt.yaml +++ b/examples/pqrst-estimate/prompt.yaml @@ -39,7 +39,7 @@ output: description: A 100% PQRST effort allocation with concise interpretation. compatibility: capabilities: - - session-review + - long-context tags: - pqrst - retrospective diff --git a/reference/README.md b/reference/README.md index 5ed0c61..909f44f 100644 --- a/reference/README.md +++ b/reference/README.md @@ -76,6 +76,8 @@ rather than resolved by guessing. `canned-prompts/eval-rubric/v0.1` schema, and reports output criteria as declared but not run. Unrecognized schemas are skipped, not rejected. A failed render check exits non-zero. +- `resolve` lists required capabilities and context dependencies, which this + tool cannot verify, rather than implying it checked them. - 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. diff --git a/reference/canned_prompts.py b/reference/canned_prompts.py index 9bd6bf6..f43d120 100755 --- a/reference/canned_prompts.py +++ b/reference/canned_prompts.py @@ -25,6 +25,7 @@ LOCAL_REGISTRY = "local" REGISTRY_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") EVAL_RUBRIC_SCHEMA = "canned-prompts/eval-rubric/v0.1" RENDER_CHECKS = ("contains", "not_contains", "resolves_all") +CAPABILITY_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$") PLACEHOLDER_RE = re.compile(r"{{\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*}}") SEMVER_RE = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+].*)?$") REQUIRED_FIELDS = ("format", "id", "name", "version", "summary", "template") @@ -137,6 +138,50 @@ def prompt_dependencies(manifest: dict[str, Any]) -> dict[str, dict[str, Any]]: return declared +def validate_capabilities(names: Any, where: str) -> list[str]: + """Validation rule 20 of § 18: shape is checked, membership is not (§ 10.2).""" + if names is None: + return [] + if not isinstance(names, list): + raise CannedPromptError(f"{where} must be a list") + for name in names: + if not isinstance(name, str) or not CAPABILITY_RE.match(name): + raise CannedPromptError(f"{where}: {name!r} must be lowercase kebab-case") + return list(names) + + +def validate_context_dependencies(manifest: dict[str, Any]) -> list[dict[str, Any]]: + """Validation rule 19 of § 18. + + A context dependency names something CPF does not package and cannot + resolve, so `description` is the only thing a consumer has to go on. + """ + dependencies = manifest.get("dependencies") or {} + entries = dependencies.get("context") or [] + if not isinstance(entries, list): + raise CannedPromptError("dependencies.context must be a list") + for entry in entries: + if not isinstance(entry, dict): + raise CannedPromptError("each context dependency must be a mapping") + name = entry.get("name") + if not isinstance(name, str) or not CAPABILITY_RE.match(name): + raise CannedPromptError( + f"context dependency name {name!r} must be lowercase kebab-case; " + "context entries use 'name', not 'id', because nothing can look them up" + ) + if not isinstance(entry.get("description"), str) or not entry["description"].strip(): + raise CannedPromptError( + f"context dependency {name!r} must declare a description; nothing " + "else can explain a dependency the format does not package" + ) + requirement = entry.get("requirement", "required") + if requirement not in ("required", "optional"): + raise CannedPromptError( + f"context dependency {name!r}: requirement must be 'required' or 'optional'" + ) + return list(entries) + + def read_eval(package_dir: Path, relative: str) -> dict[str, Any]: path = safe_relative_file(package_dir, relative, "evals") try: @@ -208,7 +253,7 @@ def check_composition_reference( if "version" not in declared[reference]: raise CannedPromptError( f"input {name!r} {field}s {reference!r}, so that dependency must " - "declare a version (§ 10.1)" + "declare a version (§ 10.3)" ) @@ -303,6 +348,16 @@ def validate_package(package_dir: Path) -> dict[str, Any]: for item in manifest.get("inputs") or []: validate_input_default(item, declared) + validate_context_dependencies(manifest) + validate_capabilities( + (manifest.get("dependencies") or {}).get("capabilities"), + "dependencies.capabilities", + ) + validate_capabilities( + (manifest.get("compatibility") or {}).get("capabilities"), + "compatibility.capabilities", + ) + declared_examples = [str(path) for path in (manifest.get("examples") or [])] for relative in manifest.get("evals") or []: validate_eval(relative, read_eval(package_dir, relative), declared_examples) @@ -402,7 +457,7 @@ def catalog_package_path(catalog: Path, registry: str, package_id: str, version: def validate_version_selector(value: Any, where: str) -> str: - """A semver literal, `any`, `newest`, or a `>=` lower bound (§ 10.1).""" + """A semver literal, `any`, `newest`, or a `>=` lower bound (§ 10.3).""" if not isinstance(value, str) or not value.strip(): raise CannedPromptError(f"{where}: version must be a non-empty string") selector = value.strip() @@ -422,7 +477,7 @@ def validate_version_selector(value: Any, where: str) -> str: def select_version(available: list[str], selector: str | None) -> str | None: - """Pick a version from `available` (newest first) per a § 10.1 selector. + """Pick a version from `available` (newest first) per a § 10.3 selector. Each dependency is selected independently against what is present. There is no constraint solving across a dependency graph; that stays a non-goal. @@ -700,12 +755,12 @@ def resolve_inputs( """Resolve inputs and parameters per § 5.1. `composer`, when supplied, satisfies `include` defaults by rendering the - included package (§ 10.2). Inclusion is deterministic, so a tool that can + included package (§ 10.4). Inclusion is deterministic, so a tool that can render can satisfy it; derivation is not, and this tool never calls a model, so a derived default is satisfied only by its static fallback `value`. `inherited` carries the including package's already-resolved values into an - included package (§ 10.2). Those values are used as-is rather than coerced, + included package (§ 10.4). Those values are used as-is rather than coerced, and do not count as caller-supplied. """ result = Resolution() @@ -729,7 +784,7 @@ def resolve_inputs( result.underivable.append(name) # Parameters resolve first: a composed input inherits the including - # package's resolved values (§ 10.2), so those must already be settled. + # package's resolved values (§ 10.4), so those must already be settled. parameters = manifest.get("parameters") or {} for name, spec in parameters.items(): known.add(name) @@ -794,7 +849,7 @@ def resolve_values(manifest: dict[str, Any], raw_values: dict[str, str]) -> dict class CatalogComposer: - """Satisfies `include` defaults from the catalog (§ 10.2). + """Satisfies `include` defaults from the catalog (§ 10.4). Inclusion is text composition: the included package is rendered and its text is inlined. No model is involved, so this is deterministic. @@ -935,6 +990,21 @@ def cmd_resolve(args: argparse.Namespace) -> None: print(f"{name:<{width}} {origin:<{origin_width}} {preview}") else: print(f"{name:<{width}} {origin}") + dependencies = manifest.get("dependencies") or {} + capabilities = dependencies.get("capabilities") or [] + context = [ + entry + for entry in (dependencies.get("context") or []) + if entry.get("requirement", "required") == "required" + ] + if capabilities or context: + print() + print("requires (this tool cannot verify these):") + for name in capabilities: + print(f" capability {name}") + for entry in context: + print(f" context {entry['name']} — {entry['description'].strip()}") + if resolution.underivable: print() plural = len(resolution.underivable) > 1 diff --git a/reference/tests/test_canned_prompts.py b/reference/tests/test_canned_prompts.py index 98a0041..4d5b73e 100644 --- a/reference/tests/test_canned_prompts.py +++ b/reference/tests/test_canned_prompts.py @@ -615,3 +615,68 @@ def test_typed_fixture_values_are_not_reparsed(tmp_path: Path) -> None: """A YAML fixture carries real types; only CLI strings need parsing.""" manifest = {"parameters": {"flag": {"type": "boolean", "default": False}}} assert cp.resolve_inputs(manifest, {"flag": True}).values["flag"] is True + + +# --- context and capability dependencies (§ 10.1, § 10.2) --- + +def ctx_pkg(tmp_path: Path, body: str) -> Path: + return write_pkg(tmp_path / "p", BASE + body, "hello\n") + + +def test_context_dependency_requires_name_and_description(tmp_path: Path) -> None: + pkg = ctx_pkg( + tmp_path, + "dependencies:\n context:\n - name: repository-tree\n" + " description: A listing of the repository.\n", + ) + assert cp.validate_package(pkg)["id"] == "demo/defaults" + + +def test_context_dependency_without_description_is_rejected(tmp_path: Path) -> None: + pkg = ctx_pkg(tmp_path, "dependencies:\n context:\n - name: repository-tree\n") + with pytest.raises(cp.CannedPromptError, match="must declare a description"): + cp.validate_package(pkg) + + +def test_context_dependency_using_id_is_rejected(tmp_path: Path) -> None: + """Context entries use `name`; `id` would imply resolvable package identity.""" + pkg = ctx_pkg( + tmp_path, + "dependencies:\n context:\n - id: policy/security\n" + " description: A policy.\n", + ) + with pytest.raises(cp.CannedPromptError, match="not 'id'"): + cp.validate_package(pkg) + + +def test_context_requirement_generate_is_rejected(tmp_path: Path) -> None: + pkg = ctx_pkg( + tmp_path, + "dependencies:\n context:\n - name: repository-tree\n" + " description: A listing.\n requirement: generate\n", + ) + with pytest.raises(cp.CannedPromptError, match="required' or 'optional"): + cp.validate_package(pkg) + + +@pytest.mark.parametrize("name", ["web-search", "code-analysis", "vision", "a1-b2"]) +def test_valid_capability_names(name) -> None: + assert cp.validate_capabilities([name], "dependencies.capabilities") == [name] + + +@pytest.mark.parametrize("name", ["Web-Search", "web_search", "-lead", "trail-", ""]) +def test_invalid_capability_names(name) -> None: + with pytest.raises(cp.CannedPromptError, match="kebab-case"): + cp.validate_capabilities([name], "dependencies.capabilities") + + +def test_capability_may_be_both_required_and_observed(tmp_path: Path) -> None: + """§ 9.1: required to run at all, and separately observed to work well.""" + pkg = ctx_pkg( + tmp_path, + "dependencies:\n capabilities:\n - web-search\n" + "compatibility:\n capabilities:\n - web-search\n - long-context\n", + ) + manifest = cp.validate_package(pkg) + assert manifest["dependencies"]["capabilities"] == ["web-search"] + assert "long-context" in manifest["compatibility"]["capabilities"] diff --git a/workplans/CANP-WP-0002-format-open-questions.md b/workplans/CANP-WP-0002-format-open-questions.md index bf67c51..77b5ef9 100644 --- a/workplans/CANP-WP-0002-format-open-questions.md +++ b/workplans/CANP-WP-0002-format-open-questions.md @@ -222,10 +222,10 @@ Delivered: 1. § 3.2 defines `type` (`template` | `fragment`), closing the undefined-field gap. It is advisory: a fragment is a valid package and may be rendered alone; the field records intent so a consumer can warn. -2. § 10.1 (new) covers naming and pinning: qualified dependency ids, the four +2. § 10.3 (new) covers naming and pinning: qualified dependency ids, the four version selectors, `version` required for anything composed, and an explicit statement of why this is not range resolution. -3. § 10.2 (new) defines both composition kinds, parameter pass-through into an +3. § 10.4 (new) defines both composition kinds, parameter pass-through into an included package, mandatory cycle detection, and the reasoned refusal of inheritance. 4. § 6.1 gains the included-default form; § 5.1 gains resolution rule 6 for @@ -305,7 +305,7 @@ actually has. ```task id: CANP-WP-0002-T05 -status: todo +status: done priority: medium state_hub_task_id: "5beec7ba-e6c9-58b1-a065-b06aba015034" ``` @@ -316,10 +316,58 @@ manifest surface with no semantics whatsoever in v0.1, and § 9 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. +free strings in v0.2 (model capability vocabularies stay deferred). **Decisions (operator, 2026-09-06):** + +- *`context` names what CPF cannot package.* `prompts` names artifacts the + format resolves by id and version; `context` names what it does not and will + not package — a live information space, an API, a corpus, a document the + caller supplies. Anything CPF *can* package is a package: a reusable policy + or style block belongs in `prompts` as a `type: fragment`. +- *Capability names are free strings*, lowercase kebab-case, validated for + shape and not for membership, exactly as `tags` are. Model capability + vocabularies stay deferred. +- *Terminology, not deduplication, for the capability overlap.* The operator's + answer was conditional — keep both fields if they carry the required/observed + distinction, and fix the terminology if they do not. **They did not.** § 9 + opened with "records known requirements **or** observations", mixing both in + one field: `models` was observational ("known to be compatible or + evaluated") while `capabilities` was prescriptive ("expected from the + execution environment"). § 10 then described dependencies as what a prompt + "expects". Both fields said *expected*, so the ambiguity was real and the + fix was terminology. + +Delivered: + +1. § 9 rewritten: `compatibility` records **observations, never + requirements**, and a consumer MUST NOT refuse to run a package because its + environment is absent from those lists. Adds `aliases`, recording the same + capability under other names — the operator's point that a capability can + be "known by another name". +2. § 9.1 (new) states the distinction in one word each — `dependencies` means + *required*, `compatibility` means *observed* — with what absence implies + for each, and notes that the same name may legitimately appear in both. +3. § 10 opens with the three dependency kinds separated by what CPF can do + about them, and § 10.1 (new) specifies context dependencies: `name` rather + than `id` because nothing can look them up, a **required** `description` + because nothing else can explain an unpackaged dependency, optional `kind`, + and `requirement` limited to `required`/`optional`. +4. § 10.2 (new) specifies capability dependencies: free-form kebab-case, no + version and no `requirement: generate`, because a capability is not an + artifact and cannot be fetched, pinned or generated. +5. § 10.1 also draws the input/context line: an input is content passed for one + use; a context dependency is a standing fact about the environment. +6. § 18 gains validation rules 19–20; § 4's manifest surface updated. Former + § 10.1/10.2 renumbered to § 10.3/10.4 with all cross-references updated. +7. `reference/canned_prompts.py`: `validate_capabilities`, + `validate_context_dependencies`, and a `resolve` section listing required + capabilities and context under "this tool cannot verify these" rather than + implying it checked. Tests 51 → 65. + +**Caught in review of my own change.** I first added a `session-record` +context dependency to `examples/pqrst-estimate` to demonstrate the feature, +then removed it: it described the `session_summary` *input*, which § 10.1 +explicitly says a context dependency is not. The example now declares only +what it genuinely has. Illustrations live in the spec; examples stay honest. ## Rewrite specification section 23