diff --git a/dag/gunbc/recurring_failure_mode/incidental_denominator_as_wall.dag b/dag/gunbc/recurring_failure_mode/incidental_denominator_as_wall.dag index 18a60395f26..262fb19ebdb 100644 --- a/dag/gunbc/recurring_failure_mode/incidental_denominator_as_wall.dag +++ b/dag/gunbc/recurring_failure_mode/incidental_denominator_as_wall.dag @@ -37,6 +37,13 @@ data incidental_denominator_as_wall: RecurringFailureMode = RecurringFailureMode "Beside it sat a second, CALLER-LESS installer, `install_candidate_paths`, equally unguarded and invisible to review because a crate-wide dead-code allow kept it compiling: an unguarded operation with no consumer still reads as sanctioned precedent for the next author. ", + + "**SECOND RECEIPT (2026-09-04), AND IT GENERALIZES THE ROW PAST MUTATING OPERATIONS TO IDENTITY KEYS.** gunbc#10337 keys brand declaration identity on `span.file` plus the authored name. Reviewed as a section 3 violation -- a String nickname beside `std.decl_ref.DeclarationRef` -- and offered as a bounded disposition on the ground that the key is FILE-keyed where the authority is MODULE-keyed. Measured before admitting the debt: across dag/ and src/v2, 4754 `.dag` files carry ZERO files declaring more than one module, with a control confirming the regex matches. File to module is therefore INJECTIVE and `span.file` IS identity-equivalent to a module path, so the offered gap does not exist and the drop row was refused rather than written. ", + + "**But the equivalence is a CORPUS PROPERTY, not a structural guarantee, and that is this row's shape rather than a fact about modules.** Nothing makes a two-module file unwritable; no authority declares the one-module-per-file relation; no diagnostic fires if it is broken. So an identity key is correct today because of an invariant whose owner does not know it is load-bearing, and it would dissolve silently the first time a file declares two modules -- a change nobody would read as touching brand identity. The generalization: the row is not about DESTRUCTION, it is about CORRECTNESS RESTING ON A COINCIDENCE, and the mutating-bytes framing of the first receipt is one instance of it. ", + + "Recognition rule extended to this form: when a key, join or identity comparison is sound, ask WHICH UNSTATED INVARIANT makes it sound, then name the authority that enforces that invariant. If the answer is 'nothing does, it just happens to hold', the soundness is a coincidence with no authority, no diagnostic and no test -- exactly the state this row names. The next-rung trigger for THIS instance is a wall refusing a second module declaration in one file, which would convert the coincidence into a structural guarantee. ", + "**Recognition rule: for every operation that mutates authoritative bytes, ask what it would do if handed the worst member of the population its input is DRAWN FROM — not the population it is called with today — then name the function that forbids that member and the question that function was written to answer. ", "If those two questions differ, the wall is incidental and the operation has none.** The remedy is an admission at the mutation boundary itself, over the whole roster before the first byte moves and refusing rather than skipping, since a per-item skip re-exports the same silence one layer in; and it is honestly a mechanically-preventable rung, whose ceiling is structural impossibility once the plan projects a typed surface identity instead of a `List`. ", diff --git a/dag/gunbc/recurring_failure_mode/refusal_reabsorbed_by_intermediate_default.dag b/dag/gunbc/recurring_failure_mode/refusal_reabsorbed_by_intermediate_default.dag new file mode 100644 index 00000000000..b96d8dfd020 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/refusal_reabsorbed_by_intermediate_default.dag @@ -0,0 +1,26 @@ +module gunbc.recurring_failure_mode.refusal_reabsorbed_by_intermediate_default + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data refusal_reabsorbed_by_intermediate_default: RecurringFailureMode = RecurringFailureMode { + identity: "refusal_reabsorbed_by_intermediate_default" as NonEmptyStr, + + receipts: [ + "**a refusal is only a refusal if it SURVIVES ITS CALLER -- a producer can be correct at its inability boundary while the composed path still fabricates an answer** (INVALID STATE: a producer returns its typed empty or refusal at an explicit inability boundary, and an intermediate consumer reads that outcome as permission to substitute a default, a nickname, a shallower identity, or an empty-success. The composed path widens even though the producing site is correct in isolation. HARM: the fabricated value reaches a judgment site that believes it is comparing real data, so a mismatch that should have been loud is silently admitted -- section 5's forbidden state, reached by a route in which no single frame did anything obviously wrong.)", + + "WHY SITE-LOCAL REVIEW IS STRUCTURALLY BLIND TO IT, WHICH IS THE WHOLE REASON IT IS A CLASS. Inspection at the producer sees the intended refusal. A unit test at the producer sees the intended refusal. A reviewer reading the diff that ADDS the refusal sees a correct refusal being added. Only following the outcome through EVERY consumer reveals that a later frame absorbs it and recreates an answer -- and nothing in the producer's diff points at the frame that will do so. The defect is not in any line you would think to look at.", + + "MEASURED INSTANCE, gunbc#10146: carried_structural_type_name was repaired to return empty at its depth-16 ceiling -- correct, and correct in isolation. Its PARENT FRAME converted that empty back to `here`, so declared_alias_target_matches_produced continued to receive a fabricated shallow identity and alias admission continued to succeed. The first repair was therefore indistinguishable from no repair at the only site that decides, and it was caught on a full-path reread rather than at the edit. THE FIRST FIX WAS ITSELF AN INSTANCE OF THE CLASS IT WAS FIXING.", + + "DISTINGUISHING WITNESS, and note that it must assert at the JUDGMENT SITE and not at the producer: construct a chain that reaches the inability boundary, then assert AT THE FINAL JUDGMENT SITE that no fallback identity or value is admitted and the typed refusal or loud mismatch survives unchanged. A witness that asserts at the producer passes against both the broken and the repaired composition and therefore discriminates nothing.", + + "REPAIR RULE: A REFUSAL IS ABSORBING ACROSS THE FULL CALLER CHAIN. Consumers may propagate a typed cause or enrich it; none may translate it into a default, a nickname, a shallower identity, an empty-success, or an acceptance. The rule is stated over the chain rather than over any frame, because every frame in the observed instance was locally defensible.", + + "BOUNDARY AGAINST THE ABSORBING-FALLBACK ROW, WHICH IS ADJACENT AND NOT THE SAME. There, a FAILURE ARM widens instead of refusing -- the producer is the defect, and the repair is to make that arm refuse. Here the producer ALREADY REFUSES CORRECTLY and a CONSUMER undoes it. Different site, different repair, and no single capability retires both: making failure arms refuse does nothing about a caller that re-absorbs a correct refusal.", + + "RUNG FOUND AT: mitigatable, and the mitigation was a human performing a full-path reread after a review flagged the site. CEILING: structurally guaranteed -- a typed refusal whose absorption by a consumer is not expressible cannot appear in an Accepted program, and the decidable fact is whether any frame maps a refusal outcome onto a non-refusal value. NEXT-RUNG TRIGGER, stated as a CAPABILITY and deliberately not as an artifact: the compiler can decide, for a producer's refusal outcome, whether every consumer on every path either propagates it or refuses -- and refuses the program when one converts it to a value. Adding the depth-ceiling witness, or repairing this one parent frame, satisfies none of that and leaves every other producer-consumer pair in the tree exposed identically.", + ], + + evidence: [], +} diff --git a/dag/gunbc/recurring_failure_mode/roster.dag b/dag/gunbc/recurring_failure_mode/roster.dag index 520d3a5cbfe..c9c9bcd6052 100644 --- a/dag/gunbc/recurring_failure_mode/roster.dag +++ b/dag/gunbc/recurring_failure_mode/roster.dag @@ -117,6 +117,8 @@ import gunbc.recurring_failure_mode.absent_reads_identically_to_never_looked { a import gunbc.recurring_failure_mode.append_only_carrier_whose_serialization_shares_a_merge_region { append_only_carrier_whose_serialization_shares_a_merge_region } import gunbc.recurring_failure_mode.located_refusal_renders_its_span_in_the_wrong_units { located_refusal_renders_its_span_in_the_wrong_units } import gunbc.recurring_failure_mode.dispatch_correctness_resting_on_arm_order { dispatch_correctness_resting_on_arm_order } +import gunbc.recurring_failure_mode.refusal_reabsorbed_by_intermediate_default { refusal_reabsorbed_by_intermediate_default } +import gunbc.recurring_failure_mode.upstream_carrier_substituted_for_the_consumer_selected_subject { upstream_carrier_substituted_for_the_consumer_selected_subject } import gunbc.recurring_failure_mode.discriminating_arm_built_but_never_enrolled { discriminating_arm_built_but_never_enrolled } data recurring_failure_mode_roster: List = [ @@ -226,5 +228,7 @@ data recurring_failure_mode_roster: List = [ append_only_carrier_whose_serialization_shares_a_merge_region, located_refusal_renders_its_span_in_the_wrong_units, dispatch_correctness_resting_on_arm_order, + refusal_reabsorbed_by_intermediate_default, + upstream_carrier_substituted_for_the_consumer_selected_subject, discriminating_arm_built_but_never_enrolled, ] diff --git a/dag/gunbc/recurring_failure_mode/upstream_carrier_substituted_for_the_consumer_selected_subject.dag b/dag/gunbc/recurring_failure_mode/upstream_carrier_substituted_for_the_consumer_selected_subject.dag new file mode 100644 index 00000000000..7be27249f7b --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/upstream_carrier_substituted_for_the_consumer_selected_subject.dag @@ -0,0 +1,28 @@ +module gunbc.recurring_failure_mode.upstream_carrier_substituted_for_the_consumer_selected_subject + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data upstream_carrier_substituted_for_the_consumer_selected_subject: RecurringFailureMode = RecurringFailureMode { + identity: "upstream_carrier_substituted_for_the_consumer_selected_subject" as NonEmptyStr, + + receipts: [ + "**the producer's countable artifact is not necessarily the consumer's subject** (INVALID STATE: a claim about a downstream consumer's behaviour, coverage or resource use is derived from an UPSTREAM CARRIER because that carrier is easy to inspect or count, while no receipt binds the carrier to the exact subject or event the consumer actually processed -- and the carrier-to-consumer mapping may transform, suppress, coalesce, repeat or fan out. HARM: every measurement of the carrier can be perfectly pinned and perfectly correct, and the conclusion about the consumer still false, because the two name different objects. The error survives arbitrary care applied to the wrong side of the boundary.)", + + "THIS IS AN IDENTITY SUBSTITUTION ACROSS A CONSUMPTION BOUNDARY, AND NOT A PROXY METRIC BEING IMPRECISE. The distinction is load-bearing: an imprecise proxy is improved by measuring it better, which is exactly the move that entrenches this defect. Measuring the carrier harder produces a more confident statement about an object nobody asked about.", + + "MEASURED INSTANCE ONE -- branch tree substituted for the CI subject. A required workflow runs on `pull_request`, so its subject is the COMPOSED tree T = merge(M, H), never the branch head H. A projection census run on H reported 96/96/96 and was reported as a live safety gap in what CI would accept. Re-measured on T: merge-tree CLEAN, every row present, the relation covering the case exactly. The H-side measurement was TRUE OF H and was never a statement about the consumer. The consumer transforms the carrier before judging it.", + + "MEASURED INSTANCE TWO -- head SHAs substituted for paid validation epochs. Merge latency was modelled as raw head count times one CI cycle. The mapping is not a bijection in either direction: a head superseded before its run starts consumes ZERO started executions (the workflow groups by PR with cancel-in-progress false, so several pushes collapse into one pending member), while retries and separate events can make one head consume more than one. Measured across live PRs, started-runs / distinct-heads ranged 0.29 to 0.88 and was never 1. The resource is consumed by STARTED epochs; heads are merely what is easy to count.", + + "DISTINGUISHING WITNESS, and it must be taken on the CONSUMER'S side: name the exact subject the consumer processed -- the composed tree's oid, the started run's id -- and assert the claim against THAT, not against the artifact the producer emitted. A witness that measures the carrier passes identically whether or not the substitution occurred, and therefore discriminates nothing.", + + "RECOGNITION RULE, phrased so it fires before the measurement is taken: ask WHAT OBJECT THE CONSUMER ACTUALLY READS, and whether any receipt binds your measured artifact to it. If the consumer transforms, filters, batches or supersedes its input, the carrier is a different object and no amount of rigour on the carrier repairs the inference. The tell is a sentence of the form 'I measured X, therefore the consumer of X will Y' with no step establishing that the consumer's subject IS X.", + + "BOUNDARY AGAINST instrument_output_read_as_subject_content, WHICH IS ADJACENT AND NOT THE SAME: there a SET is derived from a report whose COMPLETENESS is a property of the reporting tool, and the repair is to know the tool's coverage. Here the carrier may be reported completely and correctly, and the defect is that the consumer's subject is a TRANSFORMATION of it. Knowing your instrument's coverage perfectly does not tell you that CI checks out a merge commit.", + + "RUNG FOUND AT: mitigatable, and the mitigation was re-measuring on the consumer's subject after a peer challenged the inference -- twice, both times catching a claim already stated as a finding. CEILING: mechanically preventable -- whether a claimed subject is the one a named consumer processes is decidable wherever the consumer's subject is itself an identified artifact (a run id, a tree oid). NEXT-RUNG TRIGGER, stated as a CAPABILITY and not as an artifact: a receipt that binds each measurement to the identified subject the consumer processed, so a claim measured on the carrier cannot be stated about the consumer without the join failing loudly. Re-measuring either instance by hand satisfies none of that and leaves every other producer-consumer pair unbound.", + ], + + evidence: [], +} diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 7facb66034e..237aed3a20f 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -110,6 +110,8 @@ The exact `RecurringFailureMode.identity` population, which no other projection - `append_only_carrier_whose_serialization_shares_a_merge_region` - `located_refusal_renders_its_span_in_the_wrong_units` - `dispatch_correctness_resting_on_arm_order` +- `refusal_reabsorbed_by_intermediate_default` +- `upstream_carrier_substituted_for_the_consumer_selected_subject` - `discriminating_arm_built_but_never_enrolled` --- @@ -175,7 +177,7 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **accepted source emits uncompilable target** (INVALID STATE: a .dag construction the front end accepts with zero blocking diagnostics, whose emission is a target program the target compiler refuses. The instance filed here is a coproduct's UNIT VARIANT standing in a NON-APPLIED TYPE POSITION -- a field, parameter or return type spelled with a constructor rather than a type. HARM: section 5 silent wrongness on the source-to-target path. gunbc says Accepted and hands over a program that cannot build, so the only wall that fires belongs to the target's compiler; where a target has no such wall, or where the emitted artifact is never compiled, nothing fires at all. Section 7 makes this the seed's floor and not the target's problem: the .dag graph is the authority and Rust is one realization, so 'rustc catches it' is exactly the outsourcing this project exists to end. THE SPECIMEN IS CARRIED IN THIS ROW RATHER THAN CITED, because the fixture that produced it was never committed to this repository (neat-otter-332, 2026-09-01). Its emitted artifacts are held by that lane's own receipts; no path is given here, since a path outside this repository is not a citation a later reader can resolve. Authored source, two modules: `scope.provider` declares `type Quantity = Time | Memory`; `scope.consumer` does `import scope.provider { Quantity, Time }`, `type NonApplied { value: Time }`, `fn field_as_quantity(subject: NonApplied) -> Quantity { subject.value }`. gunbc accepts it. DISTINGUISHING FACT, and the reason this class must not be read off the target's error code: WHAT rustc says is decided by what else the emitter minted, not by the source defect. Under the NARROW emitter classifier the consumer emits `pub use crate::scope_provider::{Quantity};` then `use crate::scope_provider::Quantity::{Time};` with no marker binding, and `pub struct NonApplied { pub value: Time }` refuses `error[E0573]: expected type, found variant Time` at `src/scope_consumer.rs:15:16` on `pub value: Time,`, label `not a type`, help `consider importing this struct instead`. Under the BROAD classifier the consumer instead emits `pub use crate::scope_provider::{Time};` beside `{Quantity}` -- the provider having emitted `pub enum Quantity { Time, Memory }` AND `pub struct Time;` -- so the field declaration COMPILES, and the refusal moves to the function: `error[E0308]: mismatched types` at `src/scope_consumer.rs:20:5` on `subject.value.clone()`, `expected Quantity, found Time`, against `expected Quantity because of return type`. INDEPENDENTLY REPRODUCED AT PARAMETER POSITION on this branch, 2026-09-01 on gunbc baeabbbf80, by a single-file source handed to the compiler: `type StampMode = StampClass | StampOther` with `fn take(stamp: StampClass) -> StampMode { stamp }`. `gunbc compile --target rust` exits 0 with 0 blocking errors, emits `pub fn take(stamp: StampClass) -> StampMode` beside `pub struct StampClass;`, and `cargo check` on the emitted crate refuses `E0308 expected StampMode, found StampClass`. One source defect; E0573, E0308-at-the-parent, and E0308-at-the-body depending on emission. THE MASK IS THE SHARP HALF, and it is fabricated plausible output rather than a lucky green: the broad classifier's marker import made the FIELD-ONLY source compile by substituting a distinct struct for a variant. It never preserved the modelled meaning, and extending the SAME accepted source across its declared parent boundary -- the function returning `Quantity` -- is what exposes it. So the narrow classifier's E0573 is this class becoming VISIBLE, not a regression the narrowing introduced. GENERAL FORM: a green obtained because the emitter manufactured a target-only entity for a source name is not evidence the source is well-typed, and the discriminator is to extend the source past the boundary the manufactured entity does not model. THE ONE .dag-SIDE SIGNAL IS ABOUT THE WRONG QUESTION: on the parameter reproduction the compile printed the ADVISORY `unlisted import use 'StampClass' (referenced but not in any import's name list)`. It fires because the name was not found among types -- the front end reached the exact fact that decides this case and reported it as import hygiene. It is not a partial wall: it is advisory, it names listing rather than type position, and the field specimen above IMPORTS `Time` explicitly, so it does not fire there at all (see `diagnostic_name_mechanism_silent`). RUNG FOUND AT: below the ladder, established by execution at the emission boundary -- the compile accepts and the emitted crate refuses. Section 4b's rung-1 mitigations are absent: nothing at the .dag boundary is typed, located or countable about this construction. CEILING: 4, structurally impossible, and the reason is that constructor identity and type identity are two distinct modelled facts whose membership is decidable from the coproduct declaration -- a variant name is reachable from that declaration as an ARM and never as a type, so the type-position slot has no constructor that admits it. No undecidable predicate is involved, so this is a wall now and not a ratchet. NEXT-RUNG TRIGGER, PHRASED AT CLASS GRAIN, phrased as the capability that retires the row rather than an artifact that would contribute to one: FRONT-END TYPE AGREEMENT for every construction whose emission carries a type obligation, sufficient that NO Accepted program emits a target program its compiler refuses on a type ground. That capability has one component per mechanism this row carries, and the row retires only when ALL of them hold: (i) type-position name resolution that consults the TYPE namespace alone and refuses a name resolving to a constructor with a located diagnostic, sufficient that no Accepted program contains a variant name in any non-applied type position -- field, parameter or return; (ii) arm-type agreement across a match, sufficient that no Accepted program mixes a coproduct constructor with a kernel literal across the arms of one match; (iii) agreement between the two renderings of an arrow at a call argument, sufficient that no Accepted program passes a function VALUE where the emitted parameter's rendering cannot accept it; (iv) agreement between the carrier a declared type names and the carrier the primitive families over that type are defined on, across BOTH the accepting and the emitting path, sufficient that no Accepted program emits a primitive call whose argument carrier its own runtime cannot accept; (v) exhaustiveness that reads a pattern to its LEAVES, sufficient that no Accepted program marks a variant covered on the strength of an arm restricting one of its fields to a single inner constructor; (vi) a constructor roster that distinguishes a genuine open scalar, a closed single-constructor product and an unresolved lookup, sufficient BOTH that no Accepted program has an unresolved type adjudicated as open AND that a closed single-constructor product is adjudicated closed, its total destructure accepted rather than refused -- the two halves of that specimen's own restoration condition, which are separate failures in opposite directions and are not discharged by either alone; (vii) a COMPLETE per-target realization of a declared optional -- its type spelling, its constructors, its return convention, and its representation as a collection element -- sufficient that no Accepted program emits, for any enrolled target, a module that target's compiler refuses. The four parts are enumerated rather than summarised as one carrier decision because the specimen measured them as four separate seams and NO source receipt establishes that one producer owns all four: the optional TYPE SPELLING is undefined on the Python arm and is `[]*int64` on the Go arm; the CONSTRUCTOR `Present` is undefined on both; the RETURN CONVENTION returns two values from single-return Go signatures; and the COLLECTION-ELEMENT representation declares `[]*int64` while returning `[]interface{}`. Naming them as one mechanism would assert a shared root the measurement does not carry, and would let a repair to any one of them read as discharging the component. THE PYTHON ARM OF THAT SPECIMEN DOES NOT INHABIT THIS ROW, AND COMPONENT (vii) NO LONGER PRETENDS IT DOES. This row's invalid state, stated in its first line, requires the target compiler to REFUSE. The Python measurement is that `py_compile` PASSES and `import` SUCCEEDS, the failure arriving only when annotations are evaluated or the function is called -- so on the class's own identity that arm is a NONMEMBER, and my earlier wording of (vii) ('or, in the Python arm, one it silently ACCEPTS') was a trigger component widened to admit it. A trigger component cannot make a nonmember fit a class: the retirement condition would then range over a state the identity excludes, and the class would read as covering a failure it does not define. (vii) is therefore scoped to the compiler-refusal identity, which the GO arm of the same specimen does satisfy. WHAT REMAINS OPEN, AND WHY IT IS NOT DECIDED HERE: the Python paragraph was appended by an earlier lane (still-swift-363, cool-ferret-679) and its membership defect is PRE-EXISTING -- my change did not create it, it made it legible by trying to cover it. Its two honest dispositions both exceed a specimen append: broaden and rename this class, its opening invariant, its projection and its class-grain trigger to cover an accepted source emitting a target realization that cannot uphold the source type contract even where compilation and import succeed; or home that specimen in a class whose identity already includes that state. Either touches this row's identity and every citation of it, so it is recorded as an open question against the row rather than performed inside a change that was appending a different specimen. The paragraph is deliberately left standing and unedited: deleting another lane's measured receipt to make this row internally consistent would destroy the evidence the disposition needs. THE ROSTER IS MAINTAINED BY A RULE, NOT BY A COUNT, and that rule exists because this roster has now been WRONG TWICE in the same direction. It first read as component (i) alone while the population had grown to three. Then, appending the carrier mechanism, I extended it to (iv) and left the sentence claiming one component per mechanism the row carries -- while nested patterns, the constructor-roster conflation and the per-target optional emission were all already documented here and none was covered. Both errors retire the row early, and both are the same shape: a completeness claim asserted at a moment, about a population that grows underneath it. THE RULE: appending a specimen whose mechanism is not already a component OBLIGES adding one, in the same change. The roster is the row's retirement condition, so a specimen documented without a component is a failure the row would retire while leaving authorable -- and the appending lane is the only one that knows the mechanism is new. Reviewers can check this mechanically: every specimen paragraph names a mechanism, and every mechanism must appear in the list above.Repairing any specimen, narrowing the emitter classifier, or adding a fixture satisfies less than any one component and retires no part of this row. WHY THE TRIGGER IS STATED THIS WAY, recorded because the row previously said otherwise and a reviewer was right to refuse it: the trigger read as (i) ALONE while the row's population had grown to three mechanisms, so satisfying it would have RETIRED THE ROW WHILE TWO OF ITS OWN DOCUMENTED FAILURES REMAINED AUTHORABLE -- DESIGN section 4b(3)'s grain mismatch exactly, a plural loss under a singular trigger. Noting the mismatch inside the row, which is what the function-value paragraph originally did, DOCUMENTS the defect rather than repairing it: a trigger that a row's own prose admits is too narrow is still the trigger the row will be retired by, since a drop is retired BY ITS TRIGGER AND BY NOTHING ELSE. RECOGNITION RULE: when the target compiler names a symbol at a type position, check whether that symbol is declared as an ARM of a coproduct in the source; if it is, the defect is in accepted .dag and the target compiler is the only wall that fired. SCOPE STATED RATHER THAN GENERALISED: executed for a unit arm at a field type and at a parameter type, Rust target only. Record-shaped arms, applied positions such as `List