Skip to content

Commit 2abd5fe

Browse files
committed
docs: add Constraint Inventory for the hard-constraint-contract discussion
Fifteen rules catalogued against what the code already enforces — PlayabilityReport/is_playable and the per-part filter (ADR-0019), PairValidation's coincident-dissonance and register-mud verdicts, the GenerationConstraints pitch window, the Swang seam's typed errors (SWG0301-0304), ExpansionBudget refusals — and classified under a closed four-state taxonomy (hard / soft / opt-in / defer) with RuleScope-style scope, lookback/lookahead, violation evidence, and incremental-evaluation notes per rule. Notable calls: max fret travel stays soft per the standing 'jump thresholds are calibration data, not code' decision; coincident dissonance and register mud are hard within the complement contract only; strong-beat doubling and voice crossing are opt-in per the PR #149 decision and the Cluster Engine posture; chord voicing, tapping reachability, and harmonic-context membership are defer with named prerequisites (ADR-0019 §7, the ADR-0018 technique evidence model, the post-S15 calibrated contract). The MiniZinc oracle spike gets its two real problems: fretboard realization vs infer_positions, and complement pair-cleanliness emptiness. Same normative acceptance gate as the operator inventory: classification licenses specification, nothing ships from this document. Index updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RVCBxL5VWqhxagV21ocCgi
1 parent 323a905 commit 2abd5fe

2 files changed

Lines changed: 301 additions & 0 deletions

File tree

‎docs/proposals/README.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ live contract.
2727
bounded, seed-isolated pattern operators, control curves, and
2828
boundary-applied edits as a layer above the canonical score, extending the
2929
S16 pattern core. Status: for discussion.
30+
- [`constraint-inventory.md`](constraint-inventory.md) — fifteen rules
31+
catalogued against what the code already enforces, classified under a
32+
closed taxonomy (hard / soft / opt-in / defer) with scope, lookback,
33+
evidence, and incremental-evaluation notes; selects the two real
34+
problems for the MiniZinc oracle spike. Companion artifact to the
35+
hard-constraint-contract proposal. Status: for discussion.
3036
- [`hard-constraint-contract.md`](hard-constraint-contract.md) — one typed
3137
rule layer separating legality from preference, plus MiniZinc as a strictly
3238
offline oracle (Constraint Lab). Status: for discussion.
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# Constraint Inventory (hard-constraint-contract discussion)
2+
3+
Working research artifact for the hard-constraint-contract proposal: the
4+
rules Griff already enforces (and where), the rules the research memos
5+
proposed, and a classification of each under a closed taxonomy — so the
6+
future `RuleScope`/`RuleEvaluation` layer formalizes reality instead of
7+
inventing a parallel one, and so the MiniZinc oracle spike gets *real*
8+
problems instead of a demo toy.
9+
10+
Status: for discussion (v1). Companion to
11+
[`hard-constraint-contract.md`](hard-constraint-contract.md); that
12+
proposal is the contract thesis, this file is the catalog.
13+
Scope: docs-only; binds nothing.
14+
15+
**Acceptance gate (normative for this document).** The inventory
16+
*classifies rules*; it promises no production semantics, no API, and no
17+
delivery phase. Every `hard` classification still requires its own spec
18+
section, failure-code vocabulary, characterization tests over today's
19+
behaviour (SPEC hard rule 5 where behaviour already exists), and a
20+
red→green slice. A classification here is a licence to *specify*, never a
21+
decision already taken.
22+
23+
## Classification taxonomy (closed — exactly four states)
24+
25+
- **hard** — admissibility, evaluated in the rule layer
26+
(`RuleEvaluation`); any `Violated` rejects the candidate under the
27+
fail-closed aggregation policy of the contract proposal;
28+
- **soft** — preference; lives in scoring (`Axes`/`WeightPolicy`,
29+
ADR-0017) and never enters the rule layer;
30+
- **opt-in** — a scoped arrangement constraint the user explicitly turns
31+
on for a `RuleScope`; absent from the default rule set;
32+
- **defer** — blocked on a named prerequisite; no classification decision
33+
exists until that prerequisite does.
34+
35+
Qualifiers live in the reason, never in the state. Rules that are hard
36+
*within one subsystem's contract* but not universal are recorded as hard
37+
with an explicit scope — scope is part of the rule, not a fifth state.
38+
39+
## Survey baseline — what is enforced today
40+
41+
- `core/src/fretboard.rs`: `infer_positions` (fingering DP, ADR-0019),
42+
`measure_playability` → `PlayabilityReport` — `is_playable()` is
43+
`unpositionable == 0`; `max_fret_jump` is a **carried fact, not a
44+
verdict**; chords are left unvoiced (deferred, ADR-0019 §7);
45+
`STANDARD_MAX_FRET = 24`.
46+
- `core/src/complement.rs`: `PairValidation` — `coincident_dissonances`
47+
(m2 / tritone / M7 on coincident onsets), `register_mud`, per-part
48+
playability; `is_clean()` requires all four. Doc comment states the
49+
house position explicitly: "jump thresholds are calibration data, not
50+
code". `ComplementError` is a typed-refusal vocabulary
51+
(`InvalidSpec`, `NoGapsToAnswer`, `NonUniformTimeline`, …).
52+
- `core/src/generate.rs`: `GenerationConstraints` — "hard constraints
53+
that all generated phrases must satisfy": `bar_count ≥ 1`, uniform
54+
time signature and tempo, `ticks_per_quarter > 0`,
55+
`pitch_lo ..= pitch_hi`.
56+
- `core/src/tonal.rs` (S15 Phase 1): tonal estimates are **evidence
57+
only** — scoped, optional, and by the accepted S15 contract they
58+
restrict no pitch and change no production behaviour.
59+
- `pattern/` + Swang seam: `ExpansionBudget` (typed refusal on breach),
60+
bar-geometry divisibility and tail policy (`SWG0301`/`SWG0302`), meter
61+
uniformity across a mapped span (`SWG0304`), `density` requires a seed
62+
(`SWG0303`).
63+
- DP layer (ADR-0013/0030): transition and local costs are **soft** by
64+
construction; feasibility is expressed as graph shape, not as cost ∞.
65+
66+
## Summary
67+
68+
| # | Rule | Dimension | State | Enforced today |
69+
| --- | --- | --- | --- | --- |
70+
| 1 | fretboard reachability (line notes) | Fretboard | **hard** | yes — `is_playable`, per-part filter |
71+
| 2 | chord voicing feasibility | Fretboard | **defer** | no — chords left unvoiced (ADR-0019 §7) |
72+
| 3 | max fret travel | Fretboard | **soft** | fact carried, never a verdict |
73+
| 4 | pitch range window | Pitch | **hard** | yes — `GenerationConstraints` |
74+
| 5 | string conflict (simultaneous notes, one string) | Fretboard | **hard** (monophonic scope) | implicit via monophonic lines |
75+
| 6 | coincident dissonance (pair) | Harmony × Complementarity | **hard** (complement scope) | yes — `PairValidation` |
76+
| 7 | register mud (pair) | Pitch × Complementarity | **hard** (complement scope) | yes — `PairValidation` |
77+
| 8 | strong-beat doubling (pair) | Complementarity | **opt-in** | no (decided in PR #149) |
78+
| 9 | timeline uniformity preconditions | Structure | **hard** (per-mode scope) | yes — typed `ComplementError`s |
79+
| 10 | bar-geometry / meter divisibility | Rhythm | **hard** | yes — `SWG0301/0302/0304` |
80+
| 11 | seed presence for stochastic ops | Structure | **hard** | yes — `SWG0303`, `PruneSpec` |
81+
| 12 | expansion / cycle budgets | Structure | **hard** | yes — `ExpansionBudget` |
82+
| 13 | tapping-transition reachability | Technique × Fretboard | **defer** | no — needs the technique evidence model |
83+
| 14 | harmonic-context membership | Harmony | **defer** | no — S15 forbids it by design today |
84+
| 15 | voice crossing (named rule) | Pitch × Complementarity | **opt-in** | partially shadowed by register mud |
85+
86+
## Per-rule detail
87+
88+
Field key: *Scope* is stated in `RuleScope` terms (tracks / voices /
89+
dimension / range / relation). *Lookback / lookahead* is what the rule
90+
must see beyond the event it judges. *Evidence* is what a `Violated`
91+
carries. *Incremental* — whether the rule can be re-evaluated per event
92+
for DP clients (ADR-0013/0030) without whole-candidate recomputation.
93+
*Oracle candidate* — suitability for the MiniZinc offline spike.
94+
95+
### 1. Fretboard reachability — hard
96+
97+
- **Statement**: every line note has at least one playable
98+
`(string, fret)` under the part's tuning within `max_fret`.
99+
- **Scope**: per track/voice; dimension Fretboard; whole line; relation:
100+
per-note.
101+
- **Today**: `PlayabilityReport.is_playable` (`unpositionable == 0`),
102+
used as the per-part filter (ADR-0019); typed loss report on import.
103+
- **Lookback / lookahead**: none — reachability of a pitch is per-note
104+
(path *quality* is rule 3's soft territory).
105+
- **Evidence**: the unpositionable note's musical path + pitch + tuning.
106+
- **Incremental**: yes (per note).
107+
- **Oracle candidate**: **yes — spike problem A.** The fingering DP is a
108+
heuristic over a real solution space; an exact model can (a) confirm
109+
UNSAT claims for unreachable lines and (b) hunt minimal
110+
counterexamples where the DP's greedy-ish path misses a feasible
111+
assignment under tighter travel bounds.
112+
113+
### 2. Chord voicing feasibility — defer
114+
115+
- **Statement**: simultaneous notes admit a non-conflicting joint
116+
`(string, fret)` assignment.
117+
- **Prerequisite (named)**: the chord-voicing model deferred by
118+
ADR-0019 §7 — today chords are deliberately left unvoiced rather than
119+
misvoiced, and a rule cannot precede its model.
120+
- **Note**: when it lands, this is the constraint-shaped problem par
121+
excellence (assignment + mutual exclusion) and an obvious later oracle
122+
target; it inherits rule 5 as a sub-constraint.
123+
124+
### 3. Max fret travel — soft
125+
126+
- **Statement**: consecutive-note fret jumps should stay small.
127+
- **Today**: `max_fret_jump` is a carried fact; the complement doc
128+
comment is the standing decision — "jump thresholds are calibration
129+
data, not code".
130+
- **Classification reason**: a threshold would be taste until calibrated
131+
against evidence (S9 territory); the fact is already stored, which is
132+
all the rule layer needs from it. Stays in scoring/weights.
133+
134+
### 4. Pitch range window — hard
135+
136+
- **Statement**: generated pitches lie in `pitch_lo ..= pitch_hi`.
137+
- **Scope**: per generation request; dimension Pitch; per-note.
138+
- **Today**: `GenerationConstraints` (documented as hard).
139+
- **Lookback / lookahead**: none. **Incremental**: yes.
140+
- **Evidence**: offending note + bounds.
141+
- **Oracle candidate**: no — trivially checkable; needs no solver.
142+
143+
### 5. String conflict — hard (monophonic scope today)
144+
145+
- **Statement**: two simultaneously sounding notes never occupy one
146+
string.
147+
- **Today**: implicit — the generator emits monophonic lines and chords
148+
are unvoiced, so the conflict cannot currently arise; nothing *checks*
149+
it.
150+
- **Classification reason**: hard now with the honest scope note; the
151+
full polyphonic rule activates with rule 2's model and must land as an
152+
explicit check, not remain an accident of monophony.
153+
- **Incremental**: yes (per onset group).
154+
155+
### 6. Coincident dissonance — hard (complement scope)
156+
157+
- **Statement**: no m2 / tritone / M7 between parts on coincident
158+
onsets, within the ComplementArranger's cleanliness contract.
159+
- **Scope**: tracks A×B; dimension Harmony × Complementarity; relation:
160+
simultaneous onsets.
161+
- **Today**: `PairValidation.coincident_dissonances`, part of
162+
`is_clean`.
163+
- **Classification reason**: hard *within the complement contract* — as
164+
a universal rule it would outlaw intentional tension, so its scope is
165+
the contract, not the repertoire. Widening it beyond complement would
166+
be a new opt-in rule, not a widening of this one.
167+
- **Lookback / lookahead**: the coincident onset pair only.
168+
**Incremental**: yes (per coincidence).
169+
- **Evidence**: onset, both pitches, interval class.
170+
- **Oracle candidate**: **yes — part of spike problem B** (below).
171+
172+
### 7. Register mud — hard (complement scope)
173+
174+
- **Statement**: parts A and B do not overlap registers so heavily that
175+
the pair loses separation.
176+
- **Today**: `PairValidation.register_mud`, part of `is_clean`.
177+
- **Note**: the boolean compresses a graded fact; when the rule layer
178+
formalizes it, the underlying overlap measurement should surface as
179+
evidence (same raw-fact-vs-verdict discipline as rule 3).
180+
- **Incremental**: windowed (needs both parts' registers over a span).
181+
- **Oracle candidate**: yes — as a register-window constraint in spike
182+
problem B.
183+
184+
### 8. Strong-beat doubling — opt-in
185+
186+
- **Statement**: the complement does not double the lead on strong
187+
beats.
188+
- **State source**: decided in the accepted PR #149 revision — a
189+
strong-beat unison is a legitimate arrangement device; the rule enters
190+
as an explicitly scoped opt-in, soft penalty by default.
191+
- **Today**: not implemented. **Incremental**: yes (per strong beat).
192+
193+
### 9. Timeline uniformity preconditions — hard (per-mode scope)
194+
195+
- **Statement**: certain arrangement modes require a uniform meter/span
196+
across the arranged range (e.g. `counter_melody`).
197+
- **Today**: typed refusals — `ComplementError::NonUniformTimeline`,
198+
`InvalidSpec`, `NoGapsToAnswer` — already model the contract
199+
proposal's `Unsupported`/`Violated` split in miniature.
200+
- **Classification reason**: these are problem-level preconditions, the
201+
easiest rules to port into `RuleEvaluation` because their vocabulary
202+
is already typed.
203+
204+
### 10. Bar-geometry / meter divisibility — hard
205+
206+
- **Statement**: a declared unit divides the bar exactly and is
207+
tick-representable (`SWG0301`); incomplete tails follow the declared
208+
tail policy (`SWG0302`); a mapped span keeps one meter (`SWG0304`).
209+
- **Today**: typed errors in the Swang seam; the master timeline is the
210+
single source of truth (SPEC hard rule 3).
211+
- **Incremental**: per span; cheap.
212+
213+
### 11. Seed presence — hard
214+
215+
- **Statement**: every stochastic operation names its seed; `density`
216+
without `seed` is refused (`SWG0303`); `PruneSpec` carries the rhythm
217+
seed by law.
218+
- **Classification reason**: determinism (SPEC hard rule 6) expressed as
219+
an admissibility rule on *requests*, not on musical content — the rule
220+
layer should keep that distinction visible.
221+
222+
### 12. Expansion / cycle budgets — hard
223+
224+
- **Statement**: expansion never exceeds its declared budget; breach is
225+
a typed refusal, never truncation.
226+
- **Today**: `ExpansionBudget` in `griff-pattern`; the operator
227+
inventory adds `CycleBudget` to the same family (Kani harnesses for
228+
the budget invariant are already a process-backlog item).
229+
230+
### 13. Tapping-transition reachability — defer
231+
232+
- **Statement**: technique transitions (tapping entries/exits) are
233+
physically reachable in time.
234+
- **Prerequisite (named)**: a technique evidence model over the rich
235+
note model (ADR-0018) that defines *what* a transition is and *when*
236+
reachability is measurable. Classifying it hard before that model
237+
exists would invent the model by side effect (the reset-on-event
238+
lesson from the operator inventory).
239+
240+
### 14. Harmonic-context membership — defer
241+
242+
- **Statement**: notes lie within a scoped harmonic context.
243+
- **State source**: the accepted S15 contract — context is optional
244+
evidence with **no pitch restriction and no production behaviour
245+
change**; chromatic passing tones, borrowed notes and tensions stay
246+
legal by design (recorded in the contract proposal after the PR #149
247+
Codex finding).
248+
- **Prerequisite (named)**: a later, explicitly calibrated contract on
249+
top of the accepted S15 phases. Until then tonal context influences
250+
nothing in the rule layer.
251+
252+
### 15. Voice crossing — opt-in
253+
254+
- **Statement**: part B does not cross above/below part A where the
255+
arrangement declares a register order.
256+
- **Today**: not implemented as a named rule; heavy overlap partially
257+
shadowed by register mud (rule 7), which is a different fact
258+
(co-occupancy, not order inversion).
259+
- **Classification reason**: like rule 8 — a legitimate device in some
260+
arrangements (Cluster Engine treats it as a *selectable* rule, which
261+
is the right prior art posture), so opt-in per `RuleScope`, never
262+
universal.
263+
264+
## Oracle spike selection (the two real problems)
265+
266+
Per the Constraint Lab first increment, the spike models **existing**
267+
rules, not invented toys:
268+
269+
- **Problem A — fretboard realization** (rule 1, with rule 3 as data):
270+
given a monophonic line, tuning, `max_fret`, and a travel bound,
271+
decide satisfiability of a `(string, fret)` assignment and return a
272+
witness or UNSAT. Compared against `infer_positions` on the same
273+
inputs: confirms unreachability claims exactly and hunts minimal
274+
counterexamples where the DP misses feasible assignments under tight
275+
bounds. Fixture export: deterministic lines + tunings, archived with
276+
solver identity.
277+
- **Problem B — complementary pair cleanliness** (rules 6, 7, and
278+
opt-in 15): given part A fixed and a candidate rhythm for part B,
279+
decide whether any pitch assignment for B satisfies no-coincident-
280+
dissonance + register windows (+ optional no-crossing), or prove the
281+
window empty. This checks `PairValidation`'s rule set for emptiness —
282+
exactly the "prove UNSAT honestly" use the Lab exists for.
283+
284+
Both problems archive exact inputs/outputs per the Lab's manifest
285+
discipline; neither touches production (`deny.toml` unchanged).
286+
287+
## What the inventory deliberately does not contain
288+
289+
Workspace-level laws (forbid-unsafe, lint gates, TDD, English-only) —
290+
they are repository constitution (SPEC/AGENTS), not candidate-admissibility
291+
rules, and putting them in a `RuleScope` would confuse two different kinds
292+
of "hard". Scoring axes (`rerank.rs`, relation weights) — soft by
293+
definition, catalogued where they live. Rules whose subject matter is
294+
deferred by other documents (learned-model authority boundaries — the
295+
preference-learning proposal's non-goals govern).

0 commit comments

Comments
 (0)