Restore the withdrawn resolution: the operator ruled Q17 the other way

The previous commit carried Self's default reading of Q17, that a subject
resolves at most once and a resolution is not itself a subject. The
operator ruled the other way: a withdrawn resolution is kept, attached to
the subject it withdrew, and the subject may then be resolved again.

So the matrix row comes back exactly as it was and
`the_resolution_matrix_is_admissions` returns to admitting the withdrawal
of a resolution. Only the comment above `matrix` is new, and it says whose
ruling this is and what it still needs: a second resolution of one subject
needs a key the first does not already hold, which is a sequence in the
leaf's resolution key and is mapped elsewhere.

`EmptyRepos` and the opener's epoch record stand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-16 17:58:42 +02:00
commit acd32f35cc

View file

@ -645,11 +645,12 @@ fn outcome_name(outcome: &ResolutionOutcome) -> &'static str {
/// The resolution matrix: which outcomes a subject kind admits, and of which
/// kinds its referents must be. Admission's, never the leaf's.
///
/// A subject resolves at most once, and a resolution is not itself
/// resolvable: `resolution` joins `campaign`, `cut_report`, `verdict`,
/// `instance` and `hand_off` in admitting no outcome at all. The key grammar
/// can still spell the resolution of a resolution; this is where it is
/// refused. (Q17, Self's default ruling pending the operator.)
/// A resolution is itself resolvable, by withdrawal alone: the operator's
/// ruling on Q17 keeps a withdrawn resolution attached to its subject rather
/// than erasing it, and a subject whose resolution is withdrawn may be
/// resolved again. The second resolution needs a key the first does not
/// already hold, which is a sequence in the leaf's resolution key and is
/// mapped separately.
fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool {
use PipelineKind as K;
use ResolutionOutcome as O;
@ -669,6 +670,7 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool {
(K::FollowUp, O::Withdrawn { .. }) => true,
(K::Stewardship, O::Superseded { by }) => all(by, K::Stewardship),
(K::Stewardship, O::Withdrawn { .. }) => true,
(K::Resolution, O::Withdrawn { .. }) => true,
_ => false,
};
fits
@ -1035,10 +1037,14 @@ mod tests {
committed(admit(&mut world(), vec![resolution(finding_ref.clone(), ResolutionOutcome::Deferred { to: follow_up_ref.clone() })]));
committed(admit(&mut world(), vec![resolution(follow_up_ref.clone(), fixed())]));
committed(admit(&mut world(), vec![resolution(stewardship_ref.clone(), withdrawn())]));
let mut mind = world();
committed(admit(&mut mind, vec![resolution(question_ref.clone(), withdrawn())]));
let nested = r(K::Resolution, &id("resolution", "question.Q1"));
committed(admit(&mut mind, vec![resolution(nested.clone(), withdrawn())]));
// Refused, one per kind, and every non-resolvable kind.
assert_eq!(refusal(admit(&mut world(), vec![resolution(target_ref, withdrawn())])), incompatible(K::Target, "Withdrawn"));
assert_eq!(refusal(admit(&mut world(), vec![resolution(question_ref.clone(), fixed())])), incompatible(K::Question, "Fixed"));
assert_eq!(refusal(admit(&mut world(), vec![resolution(question_ref, fixed())])), incompatible(K::Question, "Fixed"));
assert_eq!(refusal(admit(&mut world(), vec![resolution(ruling_ref, withdrawn())])), incompatible(K::Ruling, "Withdrawn"));
assert_eq!(
refusal(admit(&mut world(), vec![resolution(spec_ref.clone(), ResolutionOutcome::Answered { by: r(K::Ruling, &id("ruling", "R1")) })])),
@ -1063,16 +1069,8 @@ mod tests {
refusal(admit(&mut world(), vec![resolution(stewardship_ref, ResolutionOutcome::Recorded { reason: "no".into() })])),
incompatible(K::Stewardship, "Recorded")
);
// A resolution records that a subject resolved; it is not itself a
// subject. The nested key exists and is well formed, and admission
// refuses it anyway, whatever the outcome.
let mut mind = world();
committed(admit(&mut mind, vec![resolution(question_ref.clone(), withdrawn())]));
let nested = r(K::Resolution, &id("resolution", "question.Q1"));
assert_eq!(
refusal(admit(&mut mind, vec![resolution(nested.clone(), withdrawn())])),
incompatible(K::Resolution, "Withdrawn")
);
committed(admit(&mut mind, vec![resolution(r(K::Question, &id("question", "Q1")), withdrawn())]));
assert_eq!(
refusal(admit(&mut mind, vec![resolution(nested.clone(), superseded(&[nested]))])),
incompatible(K::Resolution, "Superseded")