From 7cd1a38bc8aff45bddc0675fcc2d877ba6f80fd0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 16 Sep 2026 15:28:23 +0200 Subject: [PATCH] Pin the reader against a trimmed trailing dot on the root and the local Soul's X1 and X11 survived the second fix batch: a reader that trimmed a trailing dot from the root or from the local before its dotted_text check passed every test, because the only fixtures with a trailing dot went through the writer, which never composes one. Four malformed ids now read through pipeline_id in keys_read_back_as_ids_of_their_kind and are refused: a trailing dot on the root, a trailing dot, a leading dot and an empty part on the local. X1 and X11 join the Cut 6b suite as entries, each killed by that test. The resolution arm's comment named "the 64-byte bound in local"; the bound is LOCAL_MAX and the comment now says so. Co-Authored-By: Claude Fable 5.1 --- epiphany-pipeline/src/lib.rs | 16 ++++++++++++++-- tools/eureka-cut6b-mutations.psd1 | 25 +++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/epiphany-pipeline/src/lib.rs b/epiphany-pipeline/src/lib.rs index b3193a58..c7acff60 100644 --- a/epiphany-pipeline/src/lib.rs +++ b/epiphany-pipeline/src/lib.rs @@ -648,8 +648,8 @@ pub fn pipeline_key(document: &PipelineDocument) -> Result { let field = "resolution.subject.id"; let (subject_root, subject_local) = pipeline_id(field, &value.subject.id.0, value.subject.kind)?; @@ -1307,6 +1307,18 @@ mod tests { "a campaign key is not an instance id" ); + // Soul X1 and X11: the reader's root and local are each `dotted_text` + // whole, so a trailing dot on either is refused by the reader itself, + // not only by the writer that never composes one. A leading dot and + // an empty part are the same rule on the local. + for malformed in ["c.:target:x", "c:target:x.", "c:target:.x", "c:target:a..b"] { + let read = pipeline_id("read_back", malformed, PipelineKind::Target); + assert!( + matches!(&read, Err(PipelineRefusal::InvalidFormat { field, .. }) if field == "read_back"), + "{malformed:?} is not an id of any kind, got {read:?}" + ); + } + // The instance root's own key segment is validated, not merely bounded. // Nothing else stands between a `Slug` and a key: a space would compose // a store key carrying a byte no `Label` may hold. diff --git a/tools/eureka-cut6b-mutations.psd1 b/tools/eureka-cut6b-mutations.psd1 index 43c1e34b..678906ce 100644 --- a/tools/eureka-cut6b-mutations.psd1 +++ b/tools/eureka-cut6b-mutations.psd1 @@ -12,8 +12,11 @@ # pass: N7 and N8 re-anchor Cut 6's M12 and M13 on `key_segment`, N3 is the # analogue of Cut 6's M5 on `local`, N6 of Cut 6's M15 on the reader, and N9 # and N10 are the dotted-root mutations that survived until -# `dotted_roots_key_and_read_back`. The file holds an `é` literal in -# `bounds_refuse_in_utf8_bytes`, which is why the harness I/O is byte-exact. +# `dotted_roots_key_and_read_back`. X1 and X11 are Soul's third pass, the +# reader trimming a trailing dot from the root or the local, killed by the +# malformed fixtures in `keys_read_back_as_ids_of_their_kind`. The file holds +# an `é` literal in `bounds_refuse_in_utf8_bytes`, which is why the harness +# I/O is byte-exact. # # Soul's N4 moved the bound in `local` and both 64s in the depth test to 60 # together and survived: the test restated the literal. `LOCAL_MAX` now names @@ -169,6 +172,24 @@ Test = 'tests::dotted_roots_key_and_read_back' Old = ' dotted_text(field, root)?;' New = ' label_text(field, root)?;' + }, + # Soul X1 and X11, the reader's halves of S4: a trailing dot on the + # root or on the local was refused only by the writer, which never + # composes one, so a reader that trimmed it survived. The malformed + # fixtures read through `pipeline_id` kill both. + @{ + Id = 'X1' + Rule = 'The reader''s root check refuses a trailing dot; it does not trim one.' + Test = 'tests::keys_read_back_as_ids_of_their_kind' + Old = ' dotted_text(field, root)?;' + New = ' dotted_text(field, root.trim_end_matches(''.''))?;' + }, + @{ + Id = 'X11' + Rule = 'The reader''s local check refuses a trailing dot; it does not trim one.' + Test = 'tests::keys_read_back_as_ids_of_their_kind' + Old = ' dotted_text(field, local)?;' + New = ' dotted_text(field, local.trim_end_matches(''.''))?;' } ) }