From 4b2bda0013e85830ff6aa748dfa92dedf2554fc1 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 6 Sep 2026 14:48:08 +0200 Subject: [PATCH] Report unsatisfied dependencies on add and install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- README.md | 14 +++++++ reference/README.md | 3 ++ reference/canned_prompts.py | 34 ++++++++++++++++ reference/tests/test_canned_prompts.py | 23 +++++++++++ workplans/ADHOC-2026-09-06.md | 56 ++++++++++++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 workplans/ADHOC-2026-09-06.md diff --git a/README.md b/README.md index 71bce90..47cb30c 100644 --- a/README.md +++ b/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 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 `add`, `publish` and `install` copy the reserved paths (`prompt.yaml`, diff --git a/reference/README.md b/reference/README.md index 3eebc94..cb59ef8 100644 --- a/reference/README.md +++ b/reference/README.md @@ -84,6 +84,9 @@ rather than resolved by guessing. failed render check exits non-zero. - `resolve` lists required capabilities and context dependencies, which this 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. `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 410e75b..675c38e 100755 --- a/reference/canned_prompts.py +++ b/reference/canned_prompts.py @@ -678,6 +678,38 @@ def copy_package(src: Path, dst: Path, manifest: dict[str, Any], what: str) -> l 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: if skipped: print( @@ -705,6 +737,7 @@ def cmd_add(args: argparse.Namespace) -> None: registry = check_registry_name(args.as_registry) dst = catalog_package_path(catalog, registry, manifest["id"], manifest["version"]) report_skipped(copy_package(src.resolve(), dst, manifest, "catalog package")) + report_missing_dependencies(catalog, manifest) print(f"added {registry}:{manifest['id']}@{manifest['version']} -> {dst}") @@ -745,6 +778,7 @@ def cmd_install(args: argparse.Namespace) -> None: manifest = validate_package(src) dst = catalog_package_path(catalog, name, manifest["id"], manifest["version"]) copy_package(src, dst, manifest, "catalog package") + report_missing_dependencies(catalog, manifest) print(f"installed {name}:{manifest['id']}@{manifest['version']} -> {dst}") diff --git a/reference/tests/test_canned_prompts.py b/reference/tests/test_canned_prompts.py index 6a4a26d..e14385d 100644 --- a/reference/tests/test_canned_prompts.py +++ b/reference/tests/test_canned_prompts.py @@ -785,3 +785,26 @@ def test_unknown_format_is_rejected(tmp_path: Path) -> None: ) with pytest.raises(cp.CannedPromptError, match="unsupported format"): 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"] diff --git a/workplans/ADHOC-2026-09-06.md b/workplans/ADHOC-2026-09-06.md new file mode 100644 index 0000000..e47b192 --- /dev/null +++ b/workplans/ADHOC-2026-09-06.md @@ -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.