CANP-WP-0002 T05: required versus observed, and typed context dependencies
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 <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
This commit is contained in:
parent
074fd79d53
commit
c580bf63c9
8 changed files with 323 additions and 32 deletions
|
|
@ -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 `<registry>:<id>` 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).
|
||||
|
|
|
|||
23
README.md
23
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`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ output:
|
|||
description: A 100% PQRST effort allocation with concise interpretation.
|
||||
compatibility:
|
||||
capabilities:
|
||||
- session-review
|
||||
- long-context
|
||||
tags:
|
||||
- pqrst
|
||||
- retrospective
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue