CANP-WP-0002 T03: composition by reference, two kinds

T01 had already delivered half of composition without naming it: a derived
default binds an input to a prompt dependency, which is transclusion — run
package B, use its output. What was missing was the deterministic half.

`include` inlines another package's rendered template as text. No model is
involved, so the reference CLI can actually perform it, and a shared preamble,
rubric or style block becomes a versioned package instead of copied text. This
is the concrete way to honor INTENT principle 9 without any runtime. `derive`
stays as it was. Both are input defaults, so composition reuses the resolution
machinery rather than adding a second one.

No template inheritance. Four of this repo's own documents argue against it:
INTENT principle 3 (hidden context defeats reuse), section 19's "make package
contents visible before execution", section 17's requirement that behavior
changes produce a new version, and the non-goal on range resolution.

Version selectors: an exact pin is the expected form, with `any`, `newest` and
`>= X.Y.Z` as explicit opt-ins so looseness is written rather than implied by
absence. Selectors are evaluated per dependency against what is available —
no solver, no cross-dependency constraint satisfaction — which is what keeps
them outside the range-resolution non-goal, and the spec says so.

Also defines `type` (template | fragment), which appeared once in the section 4
manifest surface and was specified nowhere.

Spec: 3.2 (type), 5.1 (inclusion resolution rule, renumbered), 6.1 (included
default), 10.1 and 10.2 (new), 18 (rules 14-16), 21.

Reference CLI: validate_version_selector, select_version,
prompt_dependencies replacing prompt_dependency_ids,
check_composition_reference, CatalogComposer with cycle detection, and
resolve_inputs gaining composer= and inherited=. Tests 21 -> 42.

Examples: house-style is a real fragment package; pqrst-estimate composes it
and is bumped 0.1.0 -> 0.2.0 per section 17.

Fixes an ordering bug found while testing: inputs resolved before parameters,
so an included package could not see the including package's parameters and
silently fell back to its own defaults — the fragment rendered tone=neutral
where the including package said blunt. Parameters now resolve first; the
report still lists inputs first.

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:
tegwick 2026-09-06 01:31:58 +02:00
parent 352ed5b30a
commit 4a56f20209
11 changed files with 697 additions and 58 deletions

View file

@ -179,7 +179,7 @@ non-goals and were not touched.
```task
id: CANP-WP-0002-T03
status: todo
status: done
priority: high
state_hub_task_id: "0505c790-e57e-568c-9c8c-376957c9b08e"
```
@ -189,15 +189,62 @@ capture") and the `dependencies.prompts` field both promise composition, but
v0.1 defines no mechanism — `dependencies` is a declared field with no
semantics.
T01 has since settled one corner of this: a derived default binds an input to
a prompt dependency declared `requirement: generate`, so package-to-package
reference already exists for that one case. Build on it rather than around it.
**Found during the work.** T01 had already delivered half of composition
without it being named as such: a derived default binds an input to a prompt
dependency, which is transclusion — run package B, use its output. What was
missing was the *deterministic* half. Separately, `type: template` appeared
once in the § 4 manifest surface and was defined nowhere in the specification.
Decide what composition means at the *artifact* level: how one package
references another, whether references are includes, extends, or plain
declared prerequisites, and how versions are pinned. Leaning: declaration only
— a package states what it needs, and resolution stays with the consumer, per
the INTENT boundary. Do not introduce range resolution (INTENT non-goal).
**Decisions (operator, 2026-09-06):**
- *No inheritance.* A package never extends another, overrides its sections,
or inherits its inputs. Four of this repo's own documents argue against it:
INTENT principle 3 (hidden context defeats reuse); § 19's "make package
contents visible before execution", which an inheritance chain prevents;
§ 17's requirement that behavior changes produce a new version, which an
inherited change bypasses; and the non-goal on range resolution, which an
override chain would drag in.
- *Two composition kinds.* `include` inlines another package's **rendered
template** as text — deterministic, no model, and the reference CLI performs
it. `derive` (T01) uses another package's **result** — non-deterministic and
only available to a consumer that can run it. Both are expressed as input
defaults, so composition reuses the resolution machinery instead of adding a
second one.
- *Version selectors.* An exact pin is the expected form; `any`, `newest` and
`>= X.Y.Z` are explicit opt-ins, so looseness is always written rather than
implied by absence. These are per-dependency selectors evaluated
independently against what is available. There is no solver and no
cross-dependency constraint satisfaction, which is the line that keeps this
outside the range-resolution non-goal — the spec says so explicitly.
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
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
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
inclusion (rules renumbered); § 18 gains rules 1416; § 21 notes that the
reference tool satisfies inclusions and reports derivations.
5. `reference/canned_prompts.py`: `validate_version_selector`,
`select_version`, `prompt_dependencies` (replacing
`prompt_dependency_ids`), `check_composition_reference`, `CatalogComposer`
with cycle detection, and `resolve_inputs(composer=, inherited=)`.
Tests 21 → 42.
6. `examples/house-style/` (new) is a real `type: fragment` package, and
`examples/pqrst-estimate` composes it — bumped 0.1.0 → 0.2.0 per § 17,
since including a style block changes intended behavior.
**Ordering bug found and fixed during the work.** Inputs were resolved before
parameters, so an included package could not see the including package's
parameters and silently fell back to its own defaults — the fragment rendered
`tone: neutral` where the including package said `blunt`. Parameters now
resolve first; the report still lists inputs before parameters.
## Canonical eval schemas