Where subtree-scoped write-time contracts live

Type: ../types/design-proposal.md · Tags: kb-maintenance, document-system

Commonplace has two write-time contract surfaces, and both are enforced: a type spec binds every artifact declaring that type, a COLLECTION.md binds every artifact in that collection, and each is the gate side of a derivable review pair whose text is snapshot-hashed. Neither surface has a scope that fits a clause binding one subtree of a collection. The live instance is kb/reference/proposals/: seven authoring clauses sit in the area's README.md, where no skill reads them and no pair hashes them.

ADR 041 named the distortion this produces before the third scope appeared — when one surface has teeth, "clauses migrate to where the machinery is rather than where their scope says they belong." A subtree clause has nowhere with teeth to migrate to, so it does the opposite and settles where nothing consumes it. The design question is whether the two-surface model absorbs subtree scope by reframing it (as collection scope or as a kind), or grows a third surface, or keeps the current honest-but-inert arrangement.

Current state (as of 2026-07-25)

  • Two enforced surfaces, both location- or declaration-derived. ADR 038 puts the type spec on the gate side of a pair derived from the note's type:; ADR 041 puts COLLECTION.md on the gate side of a pair derived from the note's location. Editing either stales exactly its cohort through the existing gate-changed path.
  • Areas are explicitly not contract surfaces. Collections and types states that "subdirectories inside a collection are normally areas under the same contract."
  • cp-skill-write reads exactly two contract documents — the target collection's COLLECTION.md (Step 2, hard fail if absent) and the selected type spec (Step 3). No step reads an area README.md. cp-skill-connect does read a destination's README.md, but as a curated navigation head for link prospecting, not as a contract.
  • One live area contract exists. proposals/README.md carries seven clauses (type and trait, no decision, first version stays conceptual, requirements live in theory, dated current-state anchor, operativity and warrant, unmistakably proposed) plus a lifecycle rule that partial adoption moves content out. ADR 028 sited it there deliberately and accepted the cost: "No template or schema enforces the proposal contract yet; review relies on the trait until structure earns codification."
  • The trait routes no gate. No review gate declares requires_trait: design-proposal. The trait's only enforced text is one row in kb/types/note.md's traits table — which is gate text, since that spec is the type-conformance gate for every note-typed artifact. So a compressed one-sentence version of the proposal contract is enforced against the whole note cohort, while the full seven clauses are enforced against nobody.
  • The collection gate reaches the area only by summary and link. A proposal's collection-conformance pair resolves to kb/reference/COLLECTION.md (note_collection_md_pathcollection_for_path, nearest ancestor). Its "Proposal exception" paragraph restates three clauses — proposals live only under proposals/, carry the design-proposal trait, description leads with "Proposal:" — and links out for the rest. The README's text is outside the freshness hash.
  • The gap is measurable in the commit log. Commit 495dd8d added the operativity-and-warrant requirement to two contracts at once: kb/reference/types/adr.md, which staled the ADR cohort's type-conformance pairs, and proposals/README.md, which staled nothing. Commits d36f8fc and 685959f edited the proposals contract alone; no note became stale against either.
  • Nesting is foreclosed in code, not in the resolution machinery. validate_collection_structure in src/commonplace/lib/validation.py fails any COLLECTION.md below a collection root except under types/; the repo's one nested instance, the illustrative kb/work/dialectical-sample/COLLECTION.md, is a live validation FAIL. But collection_for_path already resolves the nearest contract-bearing ancestor, so nothing structural forecloses nesting — ADR 041 leans on the ban to guarantee that the nearest contract is the only one.
  • Adopted while this proposal was being written: option 3 for the mechanical core. A collection-local design-proposal type now exists (kb/reference/types/design-proposal.md); its schema enforces the Proposal description lead and the dated current-state anchor for the proposal cohort, replacing the note + trait arrangement. The judgment clauses stay in the area README, unenforced. The dead design-proposal row in kb/types/note.md's traits table was removed with the migration — an edit accepted at the cost of staling the entire note cohort's type-conformance pairs.
  • The same problem is already solved differently one collection over. kb/agent-memory-systems/COLLECTION.md puts its per-area rules for reviews/ and lightweight/ in a ## Structure section of the enforced contract, and pushes the structural half into a collection-local type spec carrying a source-tier field. Both halves land on enforced surfaces. kb/articles/README.md takes the other clean route: it holds no conventions and routes readers to COLLECTION.md.

The design space

Options 2 through 5 change behavior-determining organization; each states what would consume the change, through which channel, with what force.

1. Retain the area README, unenforced (status quo). The clauses stay documentation that a careful author finds by following the link in COLLECTION.md or by opening the directory. Operativity path: partial and already known — the three restated clauses and the note traits-table row reach authors through documents the write skill and both conformance gates do read; the remaining four reach only a reader who follows a link, with advisory force. Cost: those four are precisely the clauses that carry the most judgment (first version stays conceptual, requirements live in theory, dated anchor, operativity and warrant), and an author who skips the link is not detectably non-conformant. Benefit: zero machinery, and the area README stays cheap to revise — which is why the contract could be sharpened three times in a month.

2. Fold the area clauses into COLLECTION.md. The kb/agent-memory-systems/ precedent, applied to kb/reference/. Operativity path: immediate and free — cp-skill-write already reads COLLECTION.md at Step 2, and the collection-conformance pair already hashes it, so the clauses acquire both a write-time consumer and a review-time gate with no new derivation rule and no storage change; force is binding on every note in the collection. Cost is context, paid by everyone: the proposals contract is roughly half the length of kb/reference/COLLECTION.md again, and every reference write and every reference conformance review would carry rules that can apply only to one subtree. That cost scales with the number of areas, and kb/reference/ has adr/ and definitions/ waiting behind proposals/.

3. Push the area into a collection-local type. Replace note + design-proposal trait with a design-proposal type spec in kb/reference/types/. Operativity path: the type-conformance pair makes the spec the gate for exactly the proposal cohort, and cp-skill-write reads the type spec at Step 3 — the tightest cohort match available, with the freshness hash following the clauses. ADR 028 declined this under ADR 012's boundary test (a distinction is a type only if it changes required structure), but ADR 038 removed that test's premise: a type spec's natural-language authoring instructions are now enforced whether or not the schema adds required sections, so a type no longer needs new structure to earn its keep. Two costs remain. A type binds by declaration, not location — a proposal-typed note could sit anywhere, so the placement clause stays a COLLECTION.md concern under this option too. And the route works only where the area is a kind; an area that is a place (a gate bundle deliberately kept out of --all-gates, a directory of gitignored reports) has no kind to name and this option does not reach it.

4. Nested contracts. Lift the nesting ban and let an area carry its own COLLECTION.md. Operativity path: the write skill and the pair-derivation rule are the consumers, both needing a decision the current model does not have — whether a note gets one pair per ancestor contract or one pair against the nearest, and whether a nested contract extends or replaces the parent's quality goal, Types menu, and outbound-linking table. Cost: composition semantics are the whole design, collection stops naming one thing, and ADR 041's "the nearest contract above a note is the only one" guarantee is spent — for a single instance.

5. A distinct area-contract surface with its own pair kind. Generalize location-derived conformance below the collection root: a third gate kind, derived like collection-conformance but scoped to a subtree, plus a write-skill step that reads the nearest area contract. Operativity path: a new derivation rule in the review layer and a new step in cp-skill-write; binding force on the subtree cohort only. This is the honest general form of the problem and the most machinery for the least proven need — one instance does not establish that subtree scope recurs, and option 2 or 3 absorbs today's instance without new concepts.

The live instance has since been absorbed exactly this way: option 3 shipped for the mechanical core, while the judgment clauses remain natural-language README instructions — option 1's residue. Options 2 and 3 are both available and cheap, and any single area contract — this one or the next — is absorbed per instance without reopening the design: option 3 where the area is a kind, option 2 where it is a place. A second instance would therefore settle nothing; it would just be fixed the same way. What the per-instance fix cannot settle is recurrence: the pull toward area READMEs re-emerging after instances have been absorbed case-by-case is the evidence that subtree scope is a genuine recurring scope in this KB, and only that would warrant a general mechanism — option 5, or lifting the nesting ban for option 4. Under every option, one piece of work is identical and can ship first: make the COLLECTION.md restatement and the README agree, since a summary that drifts from the contract it summarizes is worse than either alone.

Free choices

  • Whether "area" becomes a first-class concept or stays an ordinary directory whose contract, wherever it lives, is a collection concern. Options 4 and 5 make it first-class; 2 and 3 refuse to.
  • What the enforced surface must restate. Whether COLLECTION.md's exception paragraph must carry the whole area contract (a derived copy, so checked or absent) or may stay a routing summary with a link.
  • Whether the trait acquires a gate instead. A requires_trait: design-proposal gate in the review-gate catalog would enforce the clauses without moving them — but it puts judgment criteria in the gate catalog rather than in the contract document, which is the placement question again in a different directory.
  • Where placement clauses live under options 3 and 5, given that neither a type nor an area contract can bind an artifact that never enters the area.
  • Whether cp-skill-write gains a nearest-README read independent of which surface wins; that step is cheap and helps under option 1, but it makes an unenforced document operative at write time without making it reviewable.

Adoption criteria

  • Every clause that binds an author reaches cp-skill-write through a document the skill actually reads.
  • Editing a clause stales the cohort it binds and no more — no silent edits, no cohort-wide invalidation for a subtree rule.
  • An author writing an ordinary artifact does not load contract text for an area they are not writing into, or that cost is named and accepted.
  • The duplication between the COLLECTION.md exception paragraph and the area README either disappears or becomes a checked derived copy.
  • Nothing in the outcome depends on a reader following a link to learn what binds them.

Risks

  • Decorative contract. The status quo's failure mode is already live: four clauses persist with no consumer, which is inert persistence rather than operative change. Time makes this worse, not stable — each revision of an unread contract is effort spent on a document with no channel.
  • Clause migration to where the machinery is. ADR 041's named distortion has a specific form here: the tempting shortcut is to expand the design-proposal row in kb/types/note.md, since that text is already enforced — which would bind every note-typed artifact in the KB with rules for one subtree.
  • Context cost creep. Option 2 is free today and gets more expensive per area added; adopting it for proposals/ sets the precedent that adr/ and definitions/ follow.
  • Silent divergence. As long as two documents state the same contract, the summary can fall behind the full text with nothing checking, and a reader who trusts the enforced surface gets a partial contract believing it complete.

Relevant Notes: