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:
parent
352ed5b30a
commit
4a56f20209
11 changed files with 697 additions and 58 deletions
|
|
@ -173,6 +173,21 @@ A short description of the intended purpose. A consumer SHOULD be able to decide
|
|||
|
||||
Relative path to the primary prompt template inside the package. The path MUST remain within the package directory.
|
||||
|
||||
#### `type`
|
||||
|
||||
Declares what kind of artifact the package is. Optional; defaults to
|
||||
`template`.
|
||||
|
||||
| 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) |
|
||||
|
||||
`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
|
||||
can warn when a package is used against it — rendering a fragment as a
|
||||
standalone prompt, or including a whole template where a fragment was meant.
|
||||
|
||||
## 4. Complete v0.1 manifest surface
|
||||
|
||||
```yaml
|
||||
|
|
@ -297,22 +312,27 @@ always produce the same output. A consumer that satisfies a derived default
|
|||
permitted to derive it. A consumer that does not derive uses the default's
|
||||
static fallback `value` when one is declared, and otherwise leaves the
|
||||
input unresolved.
|
||||
6. Resolution MUST report which values were derived, so that a caller can see
|
||||
what was added on its behalf before the prompt is used.
|
||||
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
|
||||
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
|
||||
caller can see what was added on its behalf before the prompt is used.
|
||||
|
||||
**Rendering rules:**
|
||||
|
||||
7. Values are substituted as text in v0.1.
|
||||
8. A placeholder with no resolved value is an error.
|
||||
9. Template evaluation MUST NOT execute arbitrary code.
|
||||
10. Rendering MUST NOT derive values. A tool offering derivation MUST perform
|
||||
8. Values are substituted as text in v0.1.
|
||||
9. A placeholder with no resolved value is an error.
|
||||
10. Template evaluation MUST NOT execute arbitrary code.
|
||||
11. Rendering MUST NOT derive values. A tool offering derivation MUST perform
|
||||
it as a distinct resolve step whose results are visible to the caller
|
||||
before rendering.
|
||||
before rendering. Inclusion likewise happens during resolution; rendering
|
||||
only substitutes.
|
||||
|
||||
A minimal implementation may implement resolution for supplied values and
|
||||
static defaults only. Such a tool is conforming: it reports an input with an
|
||||
unsatisfied derived default and no static fallback as unresolved, which
|
||||
rule 8 makes an error.
|
||||
rule 9 makes an error.
|
||||
|
||||
CPF v0.1 does not define conditionals, loops, filters, or functions. Implementations MAY offer richer rendering modes only when explicitly declared by an extension; they MUST NOT silently reinterpret a v0.1 template as executable code.
|
||||
|
||||
|
|
@ -426,10 +446,41 @@ the situation this format exists to replace. Validators SHOULD warn when a
|
|||
**published** package derives inline. Inline derivation is intended for local
|
||||
and draft packages.
|
||||
|
||||
`value` inside a derived default is an optional **static fallback**. Its
|
||||
#### 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,
|
||||
so every implementation that can render can also include:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
prompts:
|
||||
- id: style/house
|
||||
version: 1.0.0
|
||||
requirement: required
|
||||
|
||||
inputs:
|
||||
- name: house_style
|
||||
type: content
|
||||
required: false
|
||||
default:
|
||||
include: style/house
|
||||
```
|
||||
|
||||
`include` names a package declared in `dependencies.prompts`, exactly as a
|
||||
`derive` reference does. A single default MUST declare at most one of
|
||||
`include` or `derive`.
|
||||
|
||||
An included default MAY also declare a static fallback `value`, used by a
|
||||
consumer that cannot locate the included package.
|
||||
|
||||
#### Static fallback
|
||||
|
||||
`value` inside a derived or included default is an optional **static fallback**. Its
|
||||
meaning is defined by resolution rule 5: a consumer that does not derive uses
|
||||
it, and an input with neither a derived value nor a fallback stays unresolved,
|
||||
which rule 8 makes an error. Derivation never silently yields empty content.
|
||||
The same holds for an inclusion that cannot be resolved.
|
||||
|
||||
Declaring a derived default does not oblige any consumer to derive anything,
|
||||
and does not make the package depend on a particular resolver, model or
|
||||
|
|
@ -534,6 +585,68 @@ 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
|
||||
|
||||
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
|
||||
draws on, and an id matching packages from more than one registry MUST be
|
||||
reported as ambiguous rather than chosen.
|
||||
|
||||
`version` selects which version satisfies the dependency:
|
||||
|
||||
| `version` | Selects |
|
||||
|---|---|
|
||||
| a semver literal, e.g. `1.2.0` | exactly that version |
|
||||
| `any` | any available version; a consumer SHOULD prefer one it already holds |
|
||||
| `newest` | the newest available version |
|
||||
| `>= 1.2.0` | the newest available version that is at least `1.2.0` |
|
||||
|
||||
**An exact pin is the expected form.** The other three are explicit opt-ins,
|
||||
visible in the manifest, so that looseness is always something an author wrote
|
||||
rather than something absence implied.
|
||||
|
||||
`version` is REQUIRED for any dependency referenced by a composition or a
|
||||
derived default (§ 6.1), and RECOMMENDED otherwise.
|
||||
|
||||
This is deliberately **not** semantic version range resolution, which remains
|
||||
a non-goal. There are no unions, no caret or tilde operators, and above all no
|
||||
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
|
||||
|
||||
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
|
||||
than adding a second one:
|
||||
|
||||
| Form | Produces | Deterministic |
|
||||
|---|---|---|
|
||||
| `include` | the other package's **rendered template**, inlined as text | yes |
|
||||
| `derive` | the other package's **result**, obtained by running it | no |
|
||||
|
||||
`include` is text composition: a shared preamble, rubric or house-style block
|
||||
becomes a real versioned package instead of copied text, and inlining it needs
|
||||
no model. `derive` is output composition: the value is whatever running the
|
||||
other package produces, and only a consumer able to run it can supply one.
|
||||
|
||||
When rendering an included package, its placeholders are resolved from the
|
||||
including package's already-resolved values by name, falling back to the
|
||||
included package's own defaults. An included package with a required input
|
||||
that the including package does not supply is an error naming both packages.
|
||||
|
||||
Implementations MUST detect inclusion cycles and report them rather than
|
||||
recursing.
|
||||
|
||||
CPF does **not** define template inheritance. A package does not extend
|
||||
another, override its sections, or inherit its inputs. Composition is by
|
||||
reference only, for four reasons drawn from this specification and from
|
||||
`INTENT.md`: hidden context defeats reuse (INTENT principle 3); § 19 asks that
|
||||
package contents be visible before execution, which an inheritance chain
|
||||
prevents; § 17 asks that behavior changes produce a new version, which an
|
||||
inherited change would bypass; and resolving an override chain is the kind of
|
||||
graph problem the non-goals exclude.
|
||||
|
||||
This allows richer systems to integrate prompt resolution without forcing simple tools to implement an agent runtime.
|
||||
|
||||
A prompt dependency declared `requirement: generate` is the mechanism behind a
|
||||
|
|
@ -667,7 +780,13 @@ A v0.1 validator SHOULD verify at least:
|
|||
11. no input declares both `default` and `required: true`;
|
||||
12. a derived default declares either a `derive` reference or an inline
|
||||
`derive.prompt`, never both;
|
||||
13. a `derive` reference names a package declared in `dependencies.prompts`.
|
||||
13. a `derive` or `include` reference names a package declared in
|
||||
`dependencies.prompts`;
|
||||
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);
|
||||
16. inclusion does not form a cycle.
|
||||
|
||||
A validator SHOULD additionally warn when a package intended for publication
|
||||
declares an inline derivation prompt (§ 6.1).
|
||||
|
|
@ -837,6 +956,10 @@ rather than resolved by guessing.
|
|||
`add` takes a package from a path rather than from a registry, so it files the
|
||||
package under the reserved registry name `local`.
|
||||
|
||||
The reference tool satisfies `include` defaults, because inclusion is
|
||||
deterministic and needs no model, and reports `derive` defaults it cannot
|
||||
satisfy.
|
||||
|
||||
The reference tool never calls a model, so its `resolve` handles supplied
|
||||
values and static defaults only and reports any input whose derived default it
|
||||
cannot satisfy. `render` performs the same resolution and then substitutes;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue