docs(adr-0087): rhetorical style as selection axis + writing-chain harvester spec (#72)

Pre-work for a writing-curriculum extension to CORE.  Two companion
documents, both Proposed status (no code shipped).

docs/decisions/ADR-0087-rhetorical-style-axis.md
  Pins rhetorical style as a third selection axis — sibling to anchor
  lens (ADR-0073), orthogonal to register (ADR-0070).  Substantive
  axis: trace_hash DISTINCT across styles (style changes which moves
  the composer requires and which frames the realizer emits, which
  changes the propositional plan, which changes the trace).

  Four anti-patterns explicitly named and rejected:
    - style as motor (re-couples realizer to geometry; same shape as
      the ADR-0085 fusion-operator rejection)
    - style as register variant (conflates substantive with stylistic)
    - style as identity axis (bloats identity doctrine)
    - style auto-detected from user input (operator-chosen only)

  Pack shape mirrors packs/anchor_lens/.  default_unstyled_v1 is the
  null-lift pack identical to no-style behavior.  Three CI invariants
  proposed: rhetorical_style_null_lift, schema validation, three-axis
  orthogonality.

  Substrate-only ADR — no consumer code, no genre packs.  Consumer
  integration is a follow-up ADR (composer + realizer extensions
  that read permitted_frames + required_moves_per_claim +
  forbidden_moves).

docs/curriculum/writing-chain-harvester-spec.md
  Layer 0 of the writing curriculum.  A deterministic tool that
  extracts candidate (subject, predicate, object) triples from
  reviewed expert prose and surfaces them as proposals to the
  existing teaching/review pipeline.

  Five stages (segment → classify → extract → propose → audit) —
  pure-Python rule-based, no LLM generation, no auto-acceptance.
  Trust boundary: reviewer accept/reject via the existing
  core teaching propose/review path.  No bypass permitted.

  The harvester is a proposal PRODUCER, not a proposal CONSUMER.
  Plugs into the existing pipeline without inventing a new review
  mechanism.  Each proposal carries source_id + source_line + the
  exact source_clause it came from for reviewer verification.

  First-implementation acceptance criteria deliberately tight:
  Stage 0+1 with dry-run only.  Stages 2-5 are follow-up PRs.

Substrate-first sequencing pattern (ADR-0084 → 0085) reused
throughout.  Both documents acknowledge open questions deferred to
implementation phase rather than pre-deciding.

Why now: a writing curriculum is being scoped.  Without this ADR,
every downstream PR faces the same "should style be a motor?"
question and the temptation to reach for the geometry will recur
every time the realizer produces a stilted surface.  Pinning the
axis up front prevents that recurrence.
This commit is contained in:
Shay 2026-05-20 16:09:16 -07:00 committed by GitHub
parent 537d73e394
commit ac75cfc659
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 603 additions and 0 deletions

View file

@ -0,0 +1,294 @@
# Writing-Chain Harvester — Specification
**Status:** Proposed (specification only — no code shipped)
**Date:** 2026-05-20
**Author:** Shay
**Companion to:** [ADR-0087](../decisions/ADR-0087-rhetorical-style-axis.md)
---
## Purpose
The writing curriculum needs *thousands* of ratified rhetorical
chains (claim → evidence → warrant; evidence → support → conclusion;
hedge → uncertainty → revision; etc.) to operate at PhD level.
Today the entire `cognition_chains_v1` corpus has ~21 active chains
for ~22 lemmas — a number chosen so each chain could be hand-authored
and reviewed by the project author. PhD-level writing operates on
orders of magnitude more.
Two paths to fill that gap:
| Path | Cost | Risk |
|---|---|---|
| Hand-author every chain | Prohibitive | None (matches existing discipline) |
| LLM-generate then accept | Cheap | Violates the no-LLM-content rule; reintroduces drift |
Neither is viable. This spec defines a **third path**: a *harvester*
that proposes candidate rhetorical chains by extracting them from
reviewed expert prose (peer-reviewed journals, ratified technical
documentation, the project author's own canonical writing) — one
proposed `(subject, predicate, object)` triple at a time, each
surfaced to a human reviewer for accept/reject via the existing
teaching/review loop.
The harvester does no LLM-style generation. It *extracts* structure
from already-reviewed text, surfaces it as a proposal, and lets the
existing `core teaching propose / review / accept` pipeline carry the
proposal to ratification. The reviewer's accept/reject is the load-
bearing trust boundary, identical in shape to the one ADR-0084
content used.
The harvester is the writing curriculum's **Layer 0**.
---
## Trust boundary
This spec touches a high-risk surface: external prose ingestion. The
input is text the author did not write. The output is candidate
content for a corpus that downstream surfaces draw from. Every step
must enforce that no input prose token ends up on a user surface
without passing through a human review gate.
| Boundary | Enforcement |
|---|---|
| Input prose | Read-only from a *manifest* of approved sources (each source pinned by checksum). No web fetch at harvest time. No automatic source admission. |
| Candidate proposals | Land in the existing teaching-proposal queue. NEVER auto-accepted. NEVER ratified without `core teaching review --accept`. |
| Tokens in proposals | Restricted to lemmas already in mounted packs (lexicon residency check) + a small "novel-token" candidate flag that requires explicit operator promotion before the proposal is ratifiable. |
| Output corpus | Existing teaching-corpus path. Harvester proposals are tagged with provenance `harvester:<source_id>:<line_no>` so any accepted chain can be traced back to the prose it came from. |
**No bypass of the review path is permitted.** The harvester
produces *proposals*, never *content*.
---
## Architecture
### Stage 0 — Source manifest
`packs/writing_sources/<corpus_id>/manifest.json` declares an
approved-source corpus:
```jsonc
{
"corpus_id": "scientific_method_canon_v1",
"version": 1,
"issued_at": "2026-05-21T00:00:00Z",
"sources": [
{
"source_id": "popper_logic_of_scientific_discovery_1959_ch01",
"title": "The Logic of Scientific Discovery, Ch.1",
"author": "Karl Popper",
"license": "fair-use:short-quotation",
"path": "raw/popper_1959_ch01.txt",
"checksum": "<sha256 of raw text bytes>"
},
...
],
"checksum": "<sha256 of this manifest minus checksum field>",
"provenance": "writing-harvester:reviewed:2026-05-21"
}
```
Sources are added by *human admission only*. The harvester never
auto-discovers a source. The manifest is checksum-pinned the same
way every other CORE pack is.
### Stage 1 — Sentence segmentation + clause extraction
`writing_curriculum/harvester/segment.py`:
- Tokenize raw text into sentences (deterministic, rule-based — same
discipline as existing pack compilers; no statistical tokenizer).
- For each sentence, identify candidate clauses by punctuation +
conjunction markers.
- Output: a typed stream of `(source_id, line_no, sentence_no,
clause_no, raw_clause_text)` records.
This stage produces zero CORE content. It only structures the input.
### Stage 2 — Rhetorical-move classification
`writing_curriculum/harvester/classify.py`:
- For each clause, apply a *deterministic* pattern matcher (rules,
not learned) to classify the clause's likely rhetorical move:
- `claim`: declarative assertion without explicit warrant marker.
- `evidence`: clause introduced by `"because"`, `"as shown by"`,
`"the data indicate"`, etc.
- `warrant`: clause introduced by `"therefore"`, `"hence"`,
`"this implies"`, etc.
- `concession`: clause introduced by `"although"`, `"while"`,
`"granted"`, etc.
- `hedge`: clause containing `"may"`, `"suggests"`, `"appears"`,
`"possibly"`, etc.
- `definitional_move`: clause matching `"X is Y"` pattern with X
being a candidate technical term.
- Output: typed records `(source_id, ..., clause_text, move,
confidence)`.
The classifier is deliberately conservative. Ambiguous clauses
output move `unknown` and are skipped at the proposal stage. No
classifier weight is trained — the pattern set is hand-maintained
and reviewed like any other ratified discipline.
### Stage 3 — Triple extraction
`writing_curriculum/harvester/extract.py`:
For each clause classified as a known move, attempt to extract a
`(subject, predicate, object)` triple where:
- `subject` and `object` resolve to lemmas in mounted packs (via
`chat.pack_resolver.resolve_lemma`).
- `predicate` is one of the existing relation predicates the
cognition / relations corpora already use (`requires`,
`supports`, `grounds`, `precedes`, `entails`, `contrasts_with`,
`evidences`, `causes`, `implies`).
- A new predicate is allowed ONLY if proposed as a separate
candidate with explicit `new_predicate: true` flag — never
silently introduced.
Extraction outputs candidate `TeachingProposal` records ready for
the existing `core teaching propose` path. Each proposal carries:
```jsonc
{
"proposal_id": "harvester:scientific_method_canon_v1:popper_1959_ch01:L42",
"chain_id": "<auto-generated, prefixed with `harvested_`>",
"subject": "evidence",
"predicate": "supports",
"object": "claim",
"intent_tag": "cause",
"source_clause": "<the original prose clause, verbatim>",
"source_id": "popper_logic_of_scientific_discovery_1959_ch01",
"source_line": 42,
"rhetorical_move": "evidence",
"extractor_confidence": "high|medium|low",
"extracted_at": "<ISO timestamp>",
"review_state": "pending"
}
```
The `source_clause` field is REQUIRED. Every proposal carries the
exact prose it came from so a reviewer can verify the extraction.
### Stage 4 — Review queue integration
The harvester writes proposals to the existing teaching-proposal
pipeline (`teaching/store.py`). No new review mechanism. No new
ratification gate. The proposal is exactly the same shape as a
hand-authored one with two extra metadata fields (`source_id`,
`source_line`) that the existing pipeline preserves but doesn't
require.
This is the load-bearing design choice: **the harvester adds a
proposal producer, not a proposal consumer.** Reviewers see
harvested proposals in the same queue as hand-authored ones, judge
them with the same criteria, accept/reject with the same commands.
### Stage 5 — Provenance audit
`writing_curriculum/harvester/audit.py`:
A diagnostic tool that, given any chain in any teaching corpus,
reports whether it was hand-authored or harvested, and if harvested,
which source/line. Operators can run this to spot-check the
provenance of any surface the system produces.
---
## Determinism + replay
- Sentence segmentation, classification, and extraction are all
deterministic functions of `(source_text, harvester_version)`.
- Running the harvester twice on the same input manifest produces
byte-identical proposals.
- Harvester version is pinned in the proposal `extracted_at` metadata.
- Re-running the harvester against an updated input manifest is
additive — it generates new proposals for new sources, never
retroactively modifies prior proposals.
This is the same determinism discipline the rest of CORE follows.
---
## What the harvester is NOT
| Not | Why |
|---|---|
| A summarizer | It extracts triples, not summaries. No prose generation, no paraphrase. |
| A learned model | Pattern rules, hand-maintained. No statistical training. |
| An auto-ratifier | Every proposal goes through human review. The reviewer is the trust boundary. |
| A source admitter | Sources are admitted by `core writing sources add <path>` (human-initiated) only. No web crawler. |
| A revision engine | Revision (proposing edits to ratified chains) is a separate spec — possibly the next one. |
| A style detector | Source corpus selection encodes the style; the harvester doesn't classify "is this scientific?" — operators decide which corpora to harvest from. |
---
## Acceptance criteria for the spec → first implementation PR
When the first implementation PR for this spec opens, it must:
1. Ship Stage 0 (source manifest schema + loader) and Stage 1
(segmentation) only. Not Stages 2-5.
2. Include one tiny ratified source corpus (e.g., a public-domain
short essay) as the fixture.
3. Produce a deterministic dry-run report
(`core writing harvest --dry-run --source <id>`) that shows the
segmented sentences but does NOT write to the teaching-proposal
queue.
4. Pass a "no surface emission" test: nothing the harvester produces
reaches a user surface in this first PR.
This sequencing — substrate, then dry-run, then propose pipeline,
then accept-loop integration — mirrors ADR-0084 → 0085's
substrate-first discipline.
Stages 2-5 land in follow-up PRs, each with the same gate (no
surface emission until human review explicitly accepts the
proposals).
---
## Open questions for the implementation phase
These are deliberately left for the implementation PR to resolve,
not pre-decided here:
1. **Segmentation library choice.** Pure-Python rule-based (zero
dependency, matches CORE's no-statistical-tokenizer discipline)
vs `nltk`/`spacy` sentence segmenters (battle-tested but pull in
model weights). Recommendation: pure-Python. Open for
reconsideration only if the rule-based output is demonstrably
worse on the fixture corpus.
2. **Source-license boundary.** What counts as an admissible
source? Public-domain only? Fair-use short quotation? Operator-
licensed corpora? Worth an explicit policy doc before any
non-public-domain source is admitted.
3. **Reviewer UX.** Harvested proposals carry source-clause context
that hand-authored ones don't. The review tool should display
this context. Whether that's a CLI flag or a separate review
surface is an implementation choice.
4. **Cross-corpus chain composition.** Can a harvested chain whose
subject is in `en_core_cognition_v1` and whose object is in
`en_core_relations_v1` be ratified into a third corpus, or must
each pack stay in its own corpus? Likely answer: yes, via the
existing cross-pack-chain mechanism (ADR-0064), but worth
exercising on a real example before committing.
---
## Cross-References
- [ADR-0087](../decisions/ADR-0087-rhetorical-style-axis.md) — the
axis this harvester ultimately feeds.
- [ADR-0084](../decisions/ADR-0084-definitional-layer.md) /
[ADR-0085](../decisions/ADR-0085-gloss-aware-cause.md) — substrate-
before-consumer sequencing pattern this spec adopts.
- [ADR-0064](../decisions/ADR-0064-cross-pack-teaching.md) — cross-
pack teaching corpora mechanism the harvester will reuse.
- `teaching/store.py` — the proposal-queue integration point.
- `core teaching propose / review / supersede` — the existing review
pipeline the harvester producer feeds.

View file

@ -0,0 +1,309 @@
# ADR-0087 — Rhetorical Style as Selection Axis (Pre-Work for Writing Curriculum)
**Status:** Proposed
**Date:** 2026-05-20
**Author:** Shay
---
## Context
A writing-curriculum extension to CORE is being scoped (informal name:
"PhD-level reading/writing"). Before any composer, content, or eval-lane
work begins, one architectural decision must be pinned: **what kind of
mechanism is rhetorical style?**
Two answers are plausible:
1. **A motor.** Style is encoded as a versor applied to the field at
compose time. A "scientific" style is a different operator than a
"popular" style; the operator transforms the realized state before
surface emission.
2. **A selection axis.** Style is encoded as a pack the operator
mounts at configuration time. A "scientific" deployment loads one
pack; a "popular" deployment loads another. The realizer does not
change behavior at compose time; it draws from a different
substrate.
These look superficially similar — both can produce different
surfaces for the same prompt. They are not similar at all
structurally. The choice we make here propagates into every
downstream design decision in the writing curriculum.
### Why this is the right moment to decide
Two prior results constrain the answer:
- **The φ-separation result** (memory:
[`phi-separation-falsified`](../../.claude/projects/-Users-kaizenpro-Projects-core/memory/MEMORY.md))
established that semantic capability lives in *chain composition*,
not in φ geometry. By extension, *style* capability — to the
extent it is rhetorical-mode selection rather than semantic-content
change — should not require a φ change either.
- **ADR-0085's "fusion operator" rejection.** The proposal to fuse
realized template with geometric state before lexical collapse was
declined on the grounds that it re-couples the realizer to field
dynamics, breaks deterministic replay in a new place, and re-imports
the transformer-style coupling CORE deliberately is not. A
style-as-motor design has the same anti-pattern shape.
The current decoupling — geometric field → propositional plan →
deterministic realizer → realized surface → register decoration — is
load-bearing. Every prior ADR that improved capability did so by
*expanding the substrate the realizer draws from*, not by reaching
into the geometry. ADR-0083 added depth (transitive chains). ADR-0084
added definitions (gloss substrate). ADR-0085 added a frame for the
same substrate (gloss-aware CAUSE). None of them changed the field.
Rhetorical style should follow the same pattern.
### Existing axes for context
Two selection axes are already pinned by prior ADRs:
| Axis | ADR | What varies | `trace_hash` behavior | Engagement point |
|---|---|---|---|---|
| Register | [ADR-0070](./ADR-0070-register-terse-v1.md) | Surface decoration (terse / convivial / formal) | **CONSTANT** | post-composer (decorate_surface) |
| Anchor lens | [ADR-0073](./ADR-0073-anchor-lens-substrate.md) | Semantic-vocabulary tradition (Greek philosophical / Hebrew covenant) | **DISTINCT** | pre-realizer (substrate composer) |
These are CI-pinned as orthogonal:
[`anchor_lens_byte_identity_null_lift`](../../tests/test_anchor_lens_byte_identity.py)
holds the unanchored-vs-null identity; the register tour holds the
register-vs-trace invariance; the anchor-lens tour holds the
lens-vs-trace distinctness. Both invariants hold simultaneously
(memory: `adr-0073d-anchor-lens-telemetry-tour`).
Rhetorical style needs to slot into this taxonomy without breaking
either axis.
---
## Decision
**Rhetorical style is a selection axis, not a motor.** It is a
*third* substantive axis, sibling to anchor lens, orthogonal to
register.
### Axis definition
| Property | Value |
|---|---|
| **Mechanism** | Pack the operator mounts at runtime configuration time. Not a geometric operator, not a φ change, not a fusion at compose time. |
| **What it varies** | The rhetorical-mode frames the realizer is permitted to emit AND the rhetorical-move requirements the composer applies (e.g., academic style requires an evidence-bearing chain for every assertion; popular style does not). |
| **What it does NOT vary** | The semantic content (that's anchor-lens territory). The surface decoration (that's register territory). The field dynamics, versor closure, vault recall, or trace machinery (those are non-negotiable invariants). |
| **`trace_hash` behavior** | **DISTINCT across styles.** Style selection is substantive: it changes which moves the composer requires and which frames the realizer emits, which changes the propositional plan that feeds the trace. (CONTRAST with register, which holds trace constant.) |
| **Engagement point** | Pre-realizer, alongside anchor lens. The composer consults the active rhetorical-style pack when assembling the proposition graph; the realizer consults it when choosing frames. |
| **Default** | A `default_unstyled_v1` pack identical in effect to the absence of styling — null-lift, byte-identical traces. Mirrors ADR-0073b's `default_unanchored_v1`. |
### Pack shape (substrate, not consumer)
Analogous to `packs/anchor_lens/<id>/`:
```
packs/rhetorical_style/<id>/
manifest.json — pack identity, version, mastery_report seal
rhetorical_style.json — the pack content (see schema below)
<id>.mastery_report.json — companion self-seal, verified at load
```
`rhetorical_style.json` schema (proposed v1):
```jsonc
{
"pack_id": "en_academic_v1",
"version": 1,
"issued_at": "2026-05-21T00:00:00Z",
"default_unstyled": false, // true only for the no-op pack
"permitted_frames": [ // realizer frame allow-list
"warrant", // "Therefore X, because Y."
"concession", // "While X, Y."
"hedge", // "This suggests X, though Q."
"definitional_move" // "By X we mean {gloss}."
],
"required_moves_per_claim": [ // composer must include
"evidence", // every assertion needs evidence
"warrant" // bridging the evidence-claim gap
],
"forbidden_moves": [ // moves the style refuses
"bare_assertion" // no claims without warrant
],
"provenance": "adr-0087:reviewed:2026-05-21"
}
```
Three named frames in the v0 vocabulary (`warrant`, `concession`,
`hedge`, `definitional_move`) are the *minimum* set the realizer
needs to express the claim-evidence-warrant triad. More frames are
added as the writing curriculum proves their necessity, not
preemptively.
### Composer & realizer contract
This ADR pins the *contract*; it does not implement the
consumers. The consumer ADR (call it ADR-0089 in the writing
sequence) will add:
- A `RuntimeConfig.rhetorical_style_id: str | None = None` field
(default → `default_unstyled_v1` per mounting discipline).
- A `chat/runtime.py` plumb to make the active style pack available
to the composer and realizer.
- Realizer extensions that read `permitted_frames` and refuse to
emit frames outside the allow-list.
- Composer extensions that read `required_moves_per_claim` and
refuse to ratify a surface that omits a required move.
The *substrate* lands first (this ADR + pack schema + loader +
`default_unstyled_v1` pack). The *consumer* lands second after the
substrate ratifies. This is the same sequencing discipline as
ADR-0084 → 0085.
### Forbidden alternatives
These are explicitly **not** the design, named here so future PRs
recognize them as anti-patterns:
| Anti-pattern | Why rejected |
|---|---|
| **Style as motor.** A versor applied to the field at compose time. | Re-couples realizer to geometry. Breaks deterministic replay (`trace_hash` now depends on compose-time field state). Same shape as the rejected ADR-0085 fusion proposal. |
| **Style as register.** Treating "academic" as a register-pack variant. | Conflates substantive (what moves must appear) with stylistic (how to decorate the surface). Would either bloat register packs with rhetorical-mode logic or silently broaden the trace-constant invariant. |
| **Style as identity axis.** A `precision: high` axis under `IdentityPack`. | Identity should be load-bearing and falsifiable (memory: `identity-doctrine`), not a place to encode per-deployment preferences. Adding "is academic style" as an identity axis is the bloat that doctrine refuses. |
| **Style detected from user input.** Auto-routing prompts to "scientific" vs "popular" lens based on heuristics. | Substantive variation should be operator-chosen, not auto-detected. The system shouldn't decide that this user "wants science"; the deployment decides what the system is. |
---
## Verification
### Required tests for the substrate
- **Pack loader**:
- `default_unstyled_v1` ratifies with the same mastery-report
self-seal discipline as `default_unanchored_v1`.
- Missing pack → `RhetoricalStylePackError` (fail-closed, sister to
`SafetyPackError`).
- `permitted_frames` keys validated against a known-frame allow-list.
- **Schema validation**:
- Unknown keys inside the rhetorical-style block → ratification
failure.
- `default_unstyled: true` only valid when `permitted_frames`,
`required_moves_per_claim`, and `forbidden_moves` are all empty.
- **Null-lift invariant (CI-pinned)**:
- `rhetorical_style_null_lift`: under `rhetorical_style_id=None`
(resolves to `default_unstyled_v1`), every cognition lane case
produces a byte-identical surface + `trace_hash` to today's
no-style baseline.
- **Three-axis orthogonality (CI-pinned)**:
- With register + anchor lens + rhetorical style all selectable
independently, the orthogonality tour must show:
- Register varies → `trace_hash` CONSTANT (existing invariant).
- Anchor lens varies → `trace_hash` DISTINCT (existing invariant).
- Rhetorical style varies → `trace_hash` DISTINCT (new invariant).
- All three axes vary simultaneously without breaking either
style or lens axis.
### Lanes (regression check)
```
core test --suite smoke -q
core test --suite cognition -q
core test --suite packs -q
core test --suite teaching -q
core test --suite runtime -q
core eval cognition (byte-identical at default config)
```
### What this ADR does NOT verify
- Realizer frame-emission correctness — that's the consumer ADR.
- `required_moves_per_claim` enforcement — that's the consumer ADR.
- The harvester (Layer 0 of the writing curriculum) — separate spec.
- Genre-specific gloss content for any style other than
`default_unstyled_v1` — separate content effort.
---
## Consequences
### What changes
- New `packs/rhetorical_style/<id>/` directory layout, sister to
`packs/anchor_lens/` and `packs/safety/`.
- New `packs/rhetorical_style/loader.py` with `RhetoricalStylePack`
dataclass and ratification gates.
- New `default_unstyled_v1` pack that produces the null-lift baseline.
- `docs/runtime_contracts.md` updated to declare rhetorical style as
the third substantive axis with its `trace_hash` invariant.
### What does not change
- Composer or realizer behavior. Substrate-only ADR; consumers are a
follow-up.
- Register or anchor-lens axes. Both remain operational with their
current invariants.
- Versor / vault / recall / field dynamics. Untouched.
- The non-negotiable `versor_condition(F) < 1e-6` invariant.
- Identity / safety / ethics packs. Style is a fourth pack family
(`identity` | `safety` | `ethics` | `rhetorical_style`), not a
modification to any of them.
---
## Scope limits
- **No consumer code.** Realizer + composer integration is the next
ADR's job.
- **No genre packs.** Only `default_unstyled_v1` ships in this ADR.
`en_academic_v1`, `en_technical_v1`, etc., are content efforts that
follow once the substrate ratifies and the consumer ADR lands.
- **No prompt-routing.** Style selection is operator-set at
`RuntimeConfig` time only. Auto-detection is explicitly out of scope.
- **No retroactive renaming of anchor lens.** Anchor lens stays
substantive-vocabulary; rhetorical style is the substantive-frame
axis. They are siblings, not subsets.
- **No motor variant accepted.** Future PRs proposing
style-as-motor must rebut this ADR; the burden of proof is on the
proposer.
---
## Why now
A writing curriculum is being scoped. Before any composer extension,
content harvest, or eval lane lands for that curriculum, the
mechanism of rhetorical style needs to be fixed in writing. Without
this ADR, every downstream PR will face the same question ("should
this be a motor?") and the temptation to reach for the geometry will
recur every time the realizer produces a stilted surface. Pinning
the axis up front prevents that recurrence.
ADR-0087 is also the smallest possible commitment: pack schema +
loader + one null-lift pack + invariants. No consumer code, no
content, no realizer change. It's the most reversible-yet-
load-bearing step in the writing-curriculum sequence.
---
## Cross-References
- [ADR-0070](./ADR-0070-register-terse-v1.md) — register axis
(stylistic, trace CONSTANT). Rhetorical style is the substantive
sibling.
- [ADR-0073](./ADR-0073-anchor-lens-substrate.md) — anchor lens axis
(substantive vocabulary, trace DISTINCT). Rhetorical style adopts
the same substrate-pack discipline.
- [ADR-0084](./ADR-0084-definitional-layer.md) — substrate-then-
consumer sequencing pattern. Reused here verbatim.
- [ADR-0085](./ADR-0085-gloss-aware-cause.md) — frame-swap-not-fusion
precedent. The "fusion operator" rejection is the load-bearing
prior art for this ADR's "motor rejection."
- Memory: `phi-separation-falsified` — semantic capability lives in
chain composition, not φ geometry. Style capability is the natural
next case of the same principle.
- Memory: `identity-doctrine` — why style does not belong on the
identity axis.
- Future ADR-0088: rhetorical-style consumer (composer + realizer
integration).
- Future ADR-0089: writing-chain harvester (Layer 0 of the writing
curriculum, separately specified).
- Future spec: `docs/curriculum/writing-chain-harvester-spec.md`
the harvester specification this ADR's substrate ultimately serves.