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
|
|
@ -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"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue