Report unsatisfied dependencies on add and install
Found while verifying the repo against INTENT.md's eight success criteria for a first practical release, after CANP-WP-0002 finished. Criterion 5 is "publish a version to a registry and install it elsewhere". Installing practice/pqrst-estimate into a fresh catalog reported success, and rendering it then failed with "included package not found: practice/house-style". CANP-WP-0002-T03 introduced this — composition made a package installable but unrenderable, and nothing said so at install time. The error was honest, but reaching it after a successful-looking install is the silently-incomplete failure section 23 now names as the thing this format avoids. `add` and `install` now name declared prompt dependencies the target catalog cannot satisfy, including a package present at no matching version. They do not fetch anything: section 10 leaves dependency resolution to the consumer, and auto-installing transitively is a resolver — a separate decision from refusing to hand over a package that looks fine and is not. Whether `install` should offer `--with-dependencies` is left open as a design question rather than smuggled in as a bug fix. Tests 81 -> 84. Recorded as CANP-WP-ADHOC-2026-09-06. 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
526e9abdfd
commit
4b2bda0013
5 changed files with 130 additions and 0 deletions
14
README.md
14
README.md
|
|
@ -181,6 +181,20 @@ signing and trust scoring are explicit non-goals. Ownership lives with the
|
||||||
registry rather than in the package, so no package carries an unverifiable
|
registry rather than in the package, so no package carries an unverifiable
|
||||||
assertion of authority.
|
assertion of authority.
|
||||||
|
|
||||||
|
## Dependencies are reported, not fetched
|
||||||
|
|
||||||
|
Installing a package that composes another warns when the dependency is
|
||||||
|
absent, including when it is present but no version matches:
|
||||||
|
|
||||||
|
```text
|
||||||
|
declared dependencies not in this catalog: practice/house-style@>= 0.1.0
|
||||||
|
— install them, or composition referencing them will not resolve
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing is fetched automatically. Dependency resolution is left to the
|
||||||
|
consumer, but a package that installs cleanly and then cannot render is worse
|
||||||
|
than one that says what it is waiting for.
|
||||||
|
|
||||||
## Packaging and versions
|
## Packaging and versions
|
||||||
|
|
||||||
`add`, `publish` and `install` copy the reserved paths (`prompt.yaml`,
|
`add`, `publish` and `install` copy the reserved paths (`prompt.yaml`,
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,9 @@ rather than resolved by guessing.
|
||||||
failed render check exits non-zero.
|
failed render check exits non-zero.
|
||||||
- `resolve` lists required capabilities and context dependencies, which this
|
- `resolve` lists required capabilities and context dependencies, which this
|
||||||
tool cannot verify, rather than implying it checked them.
|
tool cannot verify, rather than implying it checked them.
|
||||||
|
- `add` and `install` name declared prompt dependencies the catalog cannot
|
||||||
|
satisfy. They do not fetch them — resolution is the consumer's job (§ 10) —
|
||||||
|
but a package that looks installed and cannot render should say so.
|
||||||
- An optional `registry.yaml` names a registry and records namespace claims.
|
- An optional `registry.yaml` names a registry and records namespace claims.
|
||||||
`publish` warns when a namespace is declared `closed` — it cannot
|
`publish` warns when a namespace is declared `closed` — it cannot
|
||||||
authenticate a publisher, and says so rather than implying it checked.
|
authenticate a publisher, and says so rather than implying it checked.
|
||||||
|
|
|
||||||
|
|
@ -678,6 +678,38 @@ def copy_package(src: Path, dst: Path, manifest: dict[str, Any], what: str) -> l
|
||||||
return skipped
|
return skipped
|
||||||
|
|
||||||
|
|
||||||
|
def missing_dependencies(catalog: Path, manifest: dict[str, Any]) -> list[str]:
|
||||||
|
"""Declared prompt dependencies that this catalog cannot satisfy.
|
||||||
|
|
||||||
|
CPF leaves dependency *resolution* to the consumer (§ 10), so this tool does
|
||||||
|
not fetch anything. It does refuse to hand over a package that looks
|
||||||
|
installed but cannot render.
|
||||||
|
"""
|
||||||
|
missing: list[str] = []
|
||||||
|
for reference, entry in prompt_dependencies(manifest).items():
|
||||||
|
registry, package_id = parse_reference(reference)
|
||||||
|
names = [registry] if registry is not None else catalog_registries(catalog)
|
||||||
|
selector = entry.get("version")
|
||||||
|
if any(
|
||||||
|
select_version(versions_at(id_path(catalog / name, package_id)), selector)
|
||||||
|
for name in names
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
missing.append(f"{reference}@{selector}" if selector else reference)
|
||||||
|
return missing
|
||||||
|
|
||||||
|
|
||||||
|
def report_missing_dependencies(catalog: Path, manifest: dict[str, Any]) -> None:
|
||||||
|
missing = missing_dependencies(catalog, manifest)
|
||||||
|
if missing:
|
||||||
|
print(
|
||||||
|
"declared dependencies not in this catalog: "
|
||||||
|
+ ", ".join(missing)
|
||||||
|
+ " — install them, or composition referencing them will not resolve",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def report_skipped(skipped: list[str]) -> None:
|
def report_skipped(skipped: list[str]) -> None:
|
||||||
if skipped:
|
if skipped:
|
||||||
print(
|
print(
|
||||||
|
|
@ -705,6 +737,7 @@ def cmd_add(args: argparse.Namespace) -> None:
|
||||||
registry = check_registry_name(args.as_registry)
|
registry = check_registry_name(args.as_registry)
|
||||||
dst = catalog_package_path(catalog, registry, manifest["id"], manifest["version"])
|
dst = catalog_package_path(catalog, registry, manifest["id"], manifest["version"])
|
||||||
report_skipped(copy_package(src.resolve(), dst, manifest, "catalog package"))
|
report_skipped(copy_package(src.resolve(), dst, manifest, "catalog package"))
|
||||||
|
report_missing_dependencies(catalog, manifest)
|
||||||
print(f"added {registry}:{manifest['id']}@{manifest['version']} -> {dst}")
|
print(f"added {registry}:{manifest['id']}@{manifest['version']} -> {dst}")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -745,6 +778,7 @@ def cmd_install(args: argparse.Namespace) -> None:
|
||||||
manifest = validate_package(src)
|
manifest = validate_package(src)
|
||||||
dst = catalog_package_path(catalog, name, manifest["id"], manifest["version"])
|
dst = catalog_package_path(catalog, name, manifest["id"], manifest["version"])
|
||||||
copy_package(src, dst, manifest, "catalog package")
|
copy_package(src, dst, manifest, "catalog package")
|
||||||
|
report_missing_dependencies(catalog, manifest)
|
||||||
print(f"installed {name}:{manifest['id']}@{manifest['version']} -> {dst}")
|
print(f"installed {name}:{manifest['id']}@{manifest['version']} -> {dst}")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -785,3 +785,26 @@ def test_unknown_format_is_rejected(tmp_path: Path) -> None:
|
||||||
)
|
)
|
||||||
with pytest.raises(cp.CannedPromptError, match="unsupported format"):
|
with pytest.raises(cp.CannedPromptError, match="unsupported format"):
|
||||||
cp.validate_package(pkg)
|
cp.validate_package(pkg)
|
||||||
|
|
||||||
|
|
||||||
|
# --- unsatisfied dependencies are reported, not fetched (§ 10) ---
|
||||||
|
|
||||||
|
def test_missing_dependency_is_reported(composed: Path) -> None:
|
||||||
|
"""Installing a composing package without its fragment must not look fine."""
|
||||||
|
package_dir, _ = cp.resolve_installed(composed, "review/change", None)
|
||||||
|
manifest = cp.validate_package(package_dir)
|
||||||
|
empty = composed.parent / "empty-catalog"
|
||||||
|
assert cp.missing_dependencies(empty, manifest) == ["style/house@1.0.0"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_present_dependency_is_not_reported(composed: Path) -> None:
|
||||||
|
package_dir, _ = cp.resolve_installed(composed, "review/change", None)
|
||||||
|
manifest = cp.validate_package(package_dir)
|
||||||
|
assert cp.missing_dependencies(composed, manifest) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_dependency_present_but_wrong_version_is_reported(composed: Path) -> None:
|
||||||
|
package_dir, _ = cp.resolve_installed(composed, "review/change", None)
|
||||||
|
manifest = cp.validate_package(package_dir)
|
||||||
|
manifest["dependencies"]["prompts"][0]["version"] = "9.9.9"
|
||||||
|
assert cp.missing_dependencies(composed, manifest) == ["style/house@9.9.9"]
|
||||||
|
|
|
||||||
56
workplans/ADHOC-2026-09-06.md
Normal file
56
workplans/ADHOC-2026-09-06.md
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
---
|
||||||
|
id: CANP-WP-ADHOC-2026-09-06
|
||||||
|
type: workplan
|
||||||
|
title: "Ad hoc fixes — 2026-09-06"
|
||||||
|
domain: agents
|
||||||
|
repo: canned-prompts
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: practice
|
||||||
|
created: "2026-09-06"
|
||||||
|
updated: "2026-09-06"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ad hoc fixes — 2026-09-06
|
||||||
|
|
||||||
|
Low-risk fixes found while verifying the repo against `INTENT.md`'s eight
|
||||||
|
success criteria for a first practical release, after `CANP-WP-0002` finished.
|
||||||
|
|
||||||
|
## Report unsatisfied dependencies on add and install
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-ADHOC-2026-09-06-T01
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
**Found by verification.** INTENT success criterion 5 is "publish a version to
|
||||||
|
a registry and install it elsewhere". Installing `practice/pqrst-estimate` into
|
||||||
|
a fresh catalog reported success, and rendering it then failed with
|
||||||
|
`included package not found: practice/house-style`. `CANP-WP-0002-T03`
|
||||||
|
introduced this: composition made a package installable but unrenderable, and
|
||||||
|
nothing said so at install time.
|
||||||
|
|
||||||
|
The error itself was honest, but reaching it *after* a successful-looking
|
||||||
|
install is the "silently incomplete" failure that § 23 now names as the thing
|
||||||
|
this format avoids.
|
||||||
|
|
||||||
|
`add` and `install` now check declared prompt dependencies against the target
|
||||||
|
catalog and name any the catalog cannot satisfy, including the case where the
|
||||||
|
package is present but no version matches the selector:
|
||||||
|
|
||||||
|
```text
|
||||||
|
declared dependencies not in this catalog: practice/house-style@>= 0.1.0
|
||||||
|
— install them, or composition referencing them will not resolve
|
||||||
|
```
|
||||||
|
|
||||||
|
They do **not** fetch anything. § 10 leaves dependency resolution to the
|
||||||
|
consumer, and auto-installing transitively is a resolver, which is a different
|
||||||
|
decision from refusing to hand over a package that looks fine and is not.
|
||||||
|
|
||||||
|
`missing_dependencies` and `report_missing_dependencies` in
|
||||||
|
`reference/canned_prompts.py`; tests 81 → 84.
|
||||||
|
|
||||||
|
**Left open deliberately:** whether `install` should offer to fetch
|
||||||
|
dependencies (`--with-dependencies`) is a real design question, not a bug fix.
|
||||||
|
It wants its own decision rather than being smuggled in here.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue