Read a resolution back by the key it has

Soul G2: F3's defect survived for `Resolution`, unpinned in both
directions. A resolution's key is `resolution:<subject id>`, and the
reader demanded `<campaign>:resolution:<local>`. The two are disjoint, so
`pipeline_id` refused every id a resolution actually has and accepted one
no resolution can ever carry: a document that can be written but never
referenced, and a reference that names nothing. `PipelineRef` takes any
kind, so both are reachable -- a question's `raised_in`, a follow-up's
`source`, a resolution's own `subject`.

The reader now accepts what `pipeline_key` derives: a `resolution:`
prefix, then the subject's id parsed as an id of the kind that id names
itself as. `declared_kind` reads that kind from the id's own kind
segment, or calls a bare slug a root. A key and an id are the same
string, and now no kind is excused from that.

`keys_read_back_as_ids_of_their_kind` loses its resolution special case:
the loop covers every kind with no exception, which is the pin the last
batch's doc-comment curiosity should have been.

Mutations, each run against the committed suite and reversed after:

- Soul's survivor, `| PipelineKind::Resolution` on the root case: now
  fails `a_resolution_is_named_by_the_key_it_has` and
  `keys_read_back_as_ids_of_their_kind`. It compiles with an unreachable
  pattern warning, because the resolution case is an arm of the same
  match.
- the resolution case falls through to the campaign tail (the landed
  behaviour): the same two tests fail.
- a generous reader that takes the prefix when it is there and falls
  through when it is not: the negative direction fails on its own,
  `eureka-state:resolution:R8` still being accepted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-16 12:53:12 +02:00
commit 4cccb45c5e

View file

@ -523,19 +523,50 @@ pipeline_kinds! {
"epiphany.pipeline.hand_off.v1", "EpiphanyPipelineHandOffDocument";
}
/// Parses a full document id. `<campaign>:<kind>:<local>` for every kind but
/// the two roots, `campaign` and `instance`, which are keyed by their slug
/// alone and so read back the same way. The segment count is exact, the kind
/// segment must match `kind`, and both the campaign and the local segment are
/// The kind an id names itself as: the kind segment of `<root>:<kind>:<local>`,
/// or `Campaign` for a bare slug, since the two roots share one id rule. A
/// resolution's id is its subject's, and this is how that subject is read back.
fn declared_kind(id: &str) -> Option<PipelineKind> {
let mut segments = id.split(':');
match (segments.next(), segments.next(), segments.next(), segments.next()) {
(Some(_), None, ..) => Some(PipelineKind::Campaign),
(Some(_), Some(name), Some(_), None) => {
PipelineKind::ALL.iter().copied().find(|kind| kind.name() == name)
}
_ => None,
}
}
/// Parses a full document id. A key and an id are the same string, so this
/// accepts exactly what `pipeline_key` derives: `<root>:<kind>:<local>` for
/// every kind but the roots and a resolution. The segment count is exact, the
/// kind segment must match `kind`, and both the root and the local segment are
/// validated, so `..`, an empty part, spaces and trailing junk are all refused.
fn pipeline_id<'a>(
field: &str,
id: &'a str,
kind: PipelineKind,
) -> Result<(&'a str, &'a str), PipelineRefusal> {
if matches!(kind, PipelineKind::Campaign | PipelineKind::Instance) {
dotted_text(field, id)?;
return Ok((id, id));
match kind {
// The two roots are keyed by their slug alone, and so read back the
// same way.
PipelineKind::Campaign | PipelineKind::Instance => {
dotted_text(field, id)?;
return Ok((id, id));
}
// A resolution is keyed by its subject, so its id is its subject's id
// behind a `resolution:` prefix. That is the only id a resolution ever
// has: `<campaign>:resolution:<local>` is an id no resolution can
// carry, and accepting it would name a document that cannot exist.
// `PipelineRef` takes any kind, so a resolution is reachable as a
// question's `raised_in`, a follow-up's `source` and a resolution's own
// `subject`.
PipelineKind::Resolution => {
let subject = id.strip_prefix("resolution:").ok_or_else(|| format_error(field, id))?;
let subject_kind = declared_kind(subject).ok_or_else(|| format_error(field, id))?;
return pipeline_id(field, subject, subject_kind);
}
_ => {}
}
let mut segments = id.split(':');
let (Some(campaign), Some(name), Some(local), None) = (
@ -1215,6 +1246,50 @@ mod tests {
);
}
/// Soul G2: a resolution's key is its subject's id behind a `resolution:`
/// prefix, so that -- and only that -- is the id a resolution has. The
/// reader demanded `<campaign>:resolution:<local>` instead, which is
/// disjoint from it in both directions: the id it accepted names a document
/// that cannot exist, and the id every resolution does have was refused, so
/// nothing could reference one. `PipelineRef` takes any kind, so this is
/// reachable; a follow-up sourced from a resolution is the live path.
#[test]
fn a_resolution_is_named_by_the_key_it_has() {
let sourced = |id: &str| {
let PipelineDocument::FollowUp(mut follow_up) = samples().remove(8).0 else { unreachable!() };
follow_up.source = PipelineRef { kind: PipelineKind::Resolution, id: Short(id.into()) };
PipelineDocument::FollowUp(follow_up).validate()
};
let key = pipeline_key(&PipelineDocument::Resolution(resolution_sample())).expect("the sample keys");
assert_eq!(key, format!("resolution:{CAMPAIGN}:question:Q1"));
assert_eq!(sourced(&key), Ok(()), "a resolution is named by the key it has");
for refused in [
// The shape the reader used to demand. No resolution carries it.
format!("{CAMPAIGN}:resolution:R8"),
format!("{CAMPAIGN}:resolution:{CAMPAIGN}:question:Q1"),
// The prefix and no subject, a subject of no kind, a subject whose
// kind segment is not the one its own id declares, and trailing junk.
"resolution:".into(),
format!("resolution:{CAMPAIGN}:nonsense:Q1"),
format!("resolution:{CAMPAIGN}:question:Q1:junk"),
format!("resolution:{CAMPAIGN}:question:.."),
] {
assert!(
matches!(sourced(&refused), Err(PipelineRefusal::InvalidFormat { .. })),
"{refused:?} is not an id any resolution has"
);
}
// A root subject reads back too: a resolution of a campaign is keyed by
// that campaign's own id, which carries no kind segment.
let mut of_campaign = resolution_sample();
of_campaign.subject = PipelineRef { kind: PipelineKind::Campaign, id: s(CAMPAIGN) };
let root_key = pipeline_key(&PipelineDocument::Resolution(of_campaign)).expect("a root subject keys");
assert_eq!(root_key, format!("resolution:{CAMPAIGN}"));
assert_eq!(sourced(&root_key), Ok(()));
}
/// The three kinds a mind is keyed by. The samples above already round-trip
/// every kind; this pins the shapes D2 gives these three specifically: an
/// instance is a root keyed by its own slug, and the other two hang off an
@ -1256,26 +1331,18 @@ mod tests {
/// the key writer and left out of the id reader, and nothing noticed,
/// because the key tests assert strings and never read one back: an
/// instance keys to its slug and then fails to parse as an instance id, so
/// no `PipelineRef` and no resolution could ever name one. A resolution is
/// the one kind whose key is not an id of itself: it is its subject's id
/// behind a `resolution:` prefix, so the subject is what reads back.
/// no `PipelineRef` and no resolution could ever name one. Every kind now
/// reads back with no kind excused, a resolution included.
#[test]
fn keys_read_back_as_ids_of_their_kind() -> Result<()> {
for (document, expected) in samples() {
let kind = document.kind();
let key = pipeline_key(&document)?;
assert_eq!(key, expected, "{kind:?} keys to its sample's key");
let (id, id_kind) = match &document {
PipelineDocument::Resolution(value) => (
key.strip_prefix("resolution:").expect("a resolution key names its subject"),
value.subject.kind,
),
_ => (key.as_str(), kind),
};
assert_eq!(
pipeline_id("read_back", id, id_kind).map(|_| ()),
pipeline_id("read_back", &key, kind).map(|_| ()),
Ok(()),
"{id_kind:?} key {id} does not read back as an id of its kind"
"{kind:?} key {key} does not read back as an id of its kind"
);
}