From 602ffd9fcf7fb24027ba13bdf6af43bb9c496b9e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 16 Sep 2026 13:39:39 +0200 Subject: [PATCH] Cut 6b: delete the three key-shape exceptions The key space had four shapes because three of thirteen kinds returned a key the composer never saw, and the reader undid them with a per-kind match and a kind-guessing helper. Every confirmed defect lived in one of those exceptions. This commit removes them before anything replaces them, so no commit holds both the exceptions and the new composer: - `declared_kind`, the inference that hardcoded `Campaign` for a bare slug - the `match kind` block in `pipeline_id` (root arm, resolution arm and its recursion) - `KeyParts`, its constructors, `key`, and the "only the head may carry the separator" doctrine comment - the three early `return`s in `pipeline_key` (instance, campaign, resolution) This commit does not build: `pipeline_key` references `KeyParts` and the tests reference the deleted shapes. The next commit lands the grammar. Co-Authored-By: Claude Fable 5.1 --- epiphany-pipeline/src/lib.rs | 100 ----------------------------------- 1 file changed, 100 deletions(-) diff --git a/epiphany-pipeline/src/lib.rs b/epiphany-pipeline/src/lib.rs index 1e19279b..74641d6c 100644 --- a/epiphany-pipeline/src/lib.rs +++ b/epiphany-pipeline/src/lib.rs @@ -523,20 +523,6 @@ pipeline_kinds! { "epiphany.pipeline.hand_off.v1", "EpiphanyPipelineHandOffDocument"; } -/// The kind an id names itself as: the kind segment of `::`, -/// 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 { - 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: `::` for /// every kind but the roots and a resolution. The segment count is exact, the @@ -547,27 +533,6 @@ fn pipeline_id<'a>( id: &'a str, kind: PipelineKind, ) -> Result<(&'a str, &'a str), PipelineRefusal> { - 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: `:resolution:` 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) = ( segments.next(), @@ -635,76 +600,11 @@ fn repo_segment(field: &str, repo: &OrgRepo) -> Result Ok(repo.0.replace('_', "__").replace('/', "_-").replace('.', "_d")) } -/// The parts of a `::` key, each with the field it came from -/// so that a refusal names it. A root is an instance or a campaign; `head` and -/// the tail parts are the local's parts. -struct KeyParts<'a> { - root: (String, &'a str), - head: (String, String), - tail: Vec<(String, String)>, -} - -impl<'a> KeyParts<'a> { - /// A document inside a campaign, with a local of one part so far. - fn campaign(kind: &str, campaign: &'a Slug, head: String) -> Self { - Self { - root: (format!("{kind}.campaign"), &campaign.0), - head: (format!("{kind}.key"), head), - tail: Vec::new(), - } - } - - /// One more local part. A tail part is a `Label`, never a `Slug`. - fn then(mut self, field: &str, part: String) -> Self { - self.tail.push((field.into(), part)); - self - } - - /// Validates the root and composes the key. The local's parts are joined by - /// `.`, and only the head may carry that separator: every tail part is a - /// `Label`, and a kind's tail length is fixed by the arm that built it, so a - /// reader recovers the parts by splitting from the right once per tail part - /// and the local segments one way only. A tail part admitting a dot would - /// give the boundary before it two readings, and two documents could compose - /// one key. Every key but a root's and a resolution's is composed here, so - /// the rule holds for whatever kind is added next. - fn key(self, kind: &str) -> Result { - let (field, root) = self.root; - dotted_text(&field, root)?; - let (field, mut local) = self.head; - dotted_text(&field, &local)?; - for (field, part) in &self.tail { - label_text(field, part)?; - local.push('.'); - local.push_str(part); - } - dotted_text(&format!("{kind}.key"), &local)?; - Ok(format!("{root}:{kind}:{local}")) - } -} - /// Derives a document's identity key (D1, "Keys: identity, not convenience"). pub fn pipeline_key(document: &PipelineDocument) -> Result { use PipelineDocument as D; let kind = document.kind().name(); let parts = match document { - // An instance is keyed by its own slug, as a campaign is: it is a root, - // not a document inside one. - D::Instance(value) => { - dotted_text("instance.instance", &value.instance.0)?; - return Ok(value.instance.0.clone()); - } - D::Campaign(value) => { - dotted_text("campaign.slug", &value.slug.0)?; - return Ok(value.slug.0.clone()); - } - // A resolution is keyed by its subject, so it composes no local of its - // own: the subject's id is the key behind a `resolution:` prefix. - D::Resolution(value) => { - let field = "resolution.subject.id"; - pipeline_id(field, &value.subject.id.0, value.subject.kind)?; - return Ok(format!("resolution:{}", value.subject.id.0)); - } // Stewardship and hand-off hang off an instance rather than a campaign. // The root is the whole difference; the key shape is the same, so they // compose through `KeyParts` like every other kind.