Skip to content

Code review findings for pull request #113 #133

Description

[Copilot speaking]

Review findings for pull request #113

This issue contains the approved findings from the review of #113 at source commit 3edf357. The report continues in comments because the complete rendered Markdown exceeds GitHub's per-body limit.

Title: Align the design comment-directive spelling with the supported syntax

[Copilot speaking]

The design says the attribute accepts exactly the text accepted by an equivalent // gamma: comment directive. The supported and documented comment form is instead an attribute-shaped comment such as // #[gamma::test_timeout_multiplier(...)]; the shorthand-looking // gamma: form is not the directive syntax. Because this paragraph defines the user-visible accepted syntax and emphasizes character-for-character equivalence, the backticked spelling reads as a contractual example rather than an informal channel name.

Location: crates/cargo-gamma-attrs-impl/docs/DESIGN.md:21-24

Reproducible reasoning: The public attribute documentation's “Relationship to the comment form” section consistently shows // #[gamma::...]. The cross-channel agreement test also constructs // #[gamma::test_timeout_multiplier({arguments})] and describes the two channels as differing only by the leading comment characters. Separately, the suppression regression tests explicitly treat shorthand without the attribute brackets as unsupported. Reading those sources together shows that // gamma: does not identify a valid literal directive form, while this design paragraph presents it in code formatting immediately before claiming exact textual equivalence.

Consequence: A reader can copy or preserve the shorthand-looking form and believe Gamma will apply a timeout override, while the tool ignores or rejects that text instead of applying the documented contract.

Recommended action: Make the design wording and accepted syntax agree. If // gamma: is only a label for the comment channel, replace the code-form spelling with “comment-form directive” or show a supported representative such as // #[gamma::test_timeout_multiplier(...)]. If the shorthand is intended as literal syntax, implement it in the comment parser and cover it in the cross-channel agreement tests before claiming exact equivalence.

References:

  • crates/cargo-gamma-attrs/src/lib.rs, “Relationship to the comment form”
  • crates/cargo-gamma-lib/tests/agreement.rs, the_two_channels_agree_on_timeout_multiplier_arguments

Impacted locations:

  • crates/cargo-gamma-attrs-impl/docs/DESIGN.md:21-24

Reviewed in: #113


Title: Document or separate the workspace-wide lint policy change

[Copilot speaking]

The pull request is titled and described as an end-to-end hardening of cargo-gamma, but Cargo.toml also changes the lint policy inherited across the entire workspace: it enables empty_structs_with_brackets and too_long_first_doc_paragraph, and globally allows literal_string_with_formatting_args. This repository-wide policy change is not identified in the pull request description.

Location: Cargo.toml:211-219

Reproducible reasoning: The new entries are under [workspace.lints], so their scope is every workspace member that inherits the workspace lint table rather than only the cargo-gamma crates. The pull request description lists mutation discovery, process containment, runtime census, reporting, CLI diagnostics, and contract coverage, but does not mention changing static-verification policy for unrelated crates. Because both the title and description frame the work around cargo-gamma, a reviewer has no basis to tell whether this workspace-wide change is an intentional part of the mission or unrelated work included accidentally.

Consequence: Maintainers can merge the change believing they are reviewing only cargo-gamma hardening while also changing which constructs produce warnings or are permitted by default in future work across unrelated workspace crates.

Recommended action: If the lint policy change is intentional, add it to the pull request's motivation and substantive-change summary and explain why it belongs with the cargo-gamma work. If it is independent, move it to a separate pull request; that provides isolated policy review at the cost of repeating the small amount of validation associated with these manifest entries.

Impacted locations:

  • Cargo.toml:211-219

Reviewed in: #113


Title: Define the attribute vocabulary before stating parser boundaries

[Copilot speaking]

The added boundary rules introduce the specialized terms "stated value", "comment directive", and "positional timeout multiplier", then refer to selectors and the reason and tag arguments, without defining that vocabulary or directing the reader to its definitions before use. The sibling public README has dedicated "The vocabulary" and "Stating a value" sections, but this design document does not establish that relationship.

Location: crates/cargo-gamma-attrs-impl/docs/DESIGN.md:17-28

Reproducible reasoning: The Purpose section only explains that this crate parses and validates inert attributes. A reader then reaches the new rules and must already know that a stated value is the expression supplied through #[gamma::value(...)], that the // gamma: form is a source-comment equivalent of an attribute directive, and that a timeout multiplier is one argument accepted alongside selector, reason, and tag arguments. Those are domain-specific concepts rather than ordinary Rust terminology. Their definitions elsewhere in the repository confirm that they require explanation, but the design document neither defines them nor links to those definitions before making contractual claims about their ordering and rejection behavior. A linear reading therefore cannot recover the precise meaning of the new rules from this document.

Consequence: A maintainer can misread "stated value" as any literal supplied to an attribute, or infer that a "positional timeout multiplier" has position-dependent semantics despite the following sentence denying positional meaning. That ambiguity makes future parser, diagnostic, and documentation changes more likely to use the terms inconsistently.

Recommended action: Add a short terminology paragraph before the boundary rules, or an explicit preceding link to the public vocabulary sections, defining the stated-value attribute, comment directive, timeout multiplier, selectors, and named reason and tag arguments. Then express the boundary rules with those established terms.

References:

  • crates/cargo-gamma-attrs/README.md, "The vocabulary"
  • crates/cargo-gamma-attrs/README.md, "Stating a value"

Impacted locations:

  • crates/cargo-gamma-attrs-impl/docs/DESIGN.md:17-28

Reviewed in: #113


Title: Align empty-body eligibility with the documented semantic rationale

[Copilot speaking]

The new empty-body documentation and diagnostic claim that substituting any stated expression for an empty function body produces an identical program. That is false for valid unit-compatible expressions with observable behavior, such as panic!() or a side-effecting call returning (). The implementation rejects these annotations because collection excludes empty-body sites before consulting the stated value, so the user-facing rationale is stronger than the behavior the code establishes.

Location: crates/cargo-gamma-engine/src/ops/collect/stated.rs:54-56

Reproducible reasoning: An empty body evaluates to (), but Rust also accepts expressions that return () after observable effects and diverging expressions that coerce to the function's return type. Replacing {} with either kind can change behavior. The collector's early exclusion of an empty body is therefore the reproducible reason no stated-value mutant is produced; behavioral equivalence of every accepted expression is not. The incorrect semantic claim is repeated across implementation documentation, diagnostics, and test rationale.

Consequence: Users can be told that an observable mutant is inherently identical to the original program, while maintainers may preserve the exclusion for a false semantic reason and overlook that it is an eligibility policy or a possible future extension point.

Recommended action: Choose one contract and make collection, attribute validation, public documentation, diagnostics, and tests agree. If empty bodies remain ineligible, describe that as an eligibility rule applied before the stated expression is read, without claiming that every unit-compatible or diverging expression is behaviorally identical. If those expressions should be eligible, change collection accordingly and cover side-effecting and diverging examples. Preserve a semantic-equivalence claim only if the accepted syntax actually enforces it.

References:

  • crates/cargo-gamma-attrs-impl/docs/DESIGN.md
  • crates/cargo-gamma-attrs/src/lib.rs
  • crates/cargo-gamma/docs/MUTATORS.md, "Stating the value yourself"
  • crates/cargo-gamma-engine/src/ops/collect/collector.rs:482-513

Impacted locations:

  • crates/cargo-gamma-attrs-impl/src/implementation.rs:70-75
  • crates/cargo-gamma-attrs-impl/src/implementation.rs:344-347
  • crates/cargo-gamma-engine/src/ops/collect/stated.rs:54-56
  • crates/cargo-gamma-engine/src/ops/collect/stated.rs:150-154
  • crates/cargo-gamma-engine/src/ops/collect/stated.rs:457-459

Reviewed in: #113


Title: Validate timeout-multiplier arguments without materializing and rebuilding the whole list

[Copilot speaking]

The new timeout-multiplier path clones the attribute stream, splits every top-level argument into an allocated Vec<Vec<TokenTree>>, and then clones every non-positional argument into a second TokenStream. validate_shape immediately materializes that rebuilt stream into another token vector. This duplicates the token storage before validation even begins, although only one argument needs to be classified at a time.

Location: crates/cargo-gamma-attrs-impl/src/implementation.rs:598

Reproducible reasoning: arguments_of allocates an outer vector and a separate vector for each nonempty comma-delimited argument. validate_timeout_multiplier retains all of those vectors while it creates rest by cloning the non-positional tokens and synthesizing comma tokens. It then passes rest to validate_shape, whose existing random-access parser collects the stream into its own vector. The design contract requires top-level splitting and per-argument classification, but it does not require retaining all segments simultaneously or reconstructing their original stream. Processing each completed top-level segment as it is encountered, while carrying the stated/strict-reading state between segments, preserves the accepted syntax and duplicate detection while avoiding the new outer collection and reconstruction allocations. This path runs inside rustc for every gamma, timeout_multiplier, and test_timeout_multiplier macro expansion, so the added work scales with annotated items.

Consequence: A crate with many timeout-annotated tests performs multiple avoidable heap allocations and token clones for every macro expansion, increasing compile-time allocation pressure and latency in rustc.

Recommended action: Fold top-level splitting, positional classification, and shape validation into a streaming pass. Reuse one segment buffer (or bounded-lookahead cursor), carry multiplier-seen state across segments, and validate non-positional segments directly instead of retaining Vec<Vec<TokenTree>> and rebuilding rest. Preserve the syntax contract documented in the crate design while eliminating the duplicate token materialization.

References:

  • crates/cargo-gamma-attrs-impl/docs/DESIGN.md, Boundaries

Impacted locations:

  • crates/cargo-gamma-attrs-impl/src/implementation.rs:540-643

Reviewed in: #113


Title: Centralize newly referenced items in module-level imports

[Copilot speaking]

New code across the reviewed modules introduces dependencies inside declarations and function bodies by spelling qualified paths inline, placing use declarations inside functions or below module items, importing production siblings through super::, or bypassing a crate’s public composition path. These references do not require path-based disambiguation and can be represented consistently in each file or module import block.

Location: crates/cargo-gamma-attrs-impl/src/implementation.rs:633

Reproducible reasoning: The affected instances share one maintainability issue: newly referenced types, traits, functions, macros, constants, and modules were not incorporated into the owning module’s import surface. Module-level imports make dependencies visible in one place and avoid coupling production modules to nesting depth. Where an item is re-exported by the crate that owns the composition boundary, importing through that public crate path also keeps callers independent of the underlying dependency. Existing cfg gates and aliases can remain on the imports where platform availability or name collisions require them.

Consequence: Dependencies are scattered through signatures, declarations, and function bodies instead of being visible at module boundaries. This makes the affected modules harder to scan and creates many separate path spellings to update when items move or imports are reorganized.

Recommended action: Move the affected references into the owning file or module import block and use the imported names at call sites. In production modules, prefer crate-root paths over super::; across crates, prefer the composing crate’s public re-export over its private dependency path. Preserve necessary cfg attributes and aliases, and keep qualified call-site paths only where they genuinely disambiguate names.

Impacted locations:

  • crates/cargo-gamma-attrs-impl/src/implementation.rs:633
  • crates/cargo-gamma-attrs/tests/consumer.rs:75
  • crates/cargo-gamma-attrs/tests/diagnostics.rs:63-111
  • crates/cargo-gamma-engine/src/error.rs:255
  • crates/cargo-gamma-engine/src/ops/collect/collector.rs:1334
  • crates/cargo-gamma-engine/src/ops/collect/defaults.rs:261-280
  • crates/cargo-gamma-engine/src/ops/collect/defaults.rs:968-980
  • crates/cargo-gamma-engine/src/ops/collect/tests.rs:115
  • crates/cargo-gamma-engine/src/ops/collect/tests.rs:2777
  • crates/cargo-gamma-lib/src/ci/sarif.rs:187-195
  • crates/cargo-gamma-engine/src/text.rs:212
  • crates/cargo-gamma-lib/src/commands/list.rs:44-45
  • crates/cargo-gamma-lib/src/commands/run.rs:800
  • crates/cargo-gamma-lib/src/config.rs:1033-1051
  • crates/cargo-gamma-lib/src/config.rs:1098-1105
  • crates/cargo-gamma-lib/src/config.rs:1140-1146
  • crates/cargo-gamma-lib/src/config.rs:1195-1200
  • crates/cargo-gamma-lib/src/config.rs:1275
  • crates/cargo-gamma-lib/src/discover/record.rs:501-505
  • crates/cargo-gamma-lib/src/error.rs:136-142
  • crates/cargo-gamma-lib/src/exec/census.rs:990-1001
  • crates/cargo-gamma-lib/src/exec/census.rs:1524
  • crates/cargo-gamma-lib/src/exec/measure.rs:493
  • crates/cargo-gamma-lib/src/exec/sweep.rs:119
  • crates/cargo-gamma-lib/src/exec/sweep.rs:1531-1571
  • crates/cargo-gamma-lib/src/exec/workspace.rs:1119
  • crates/cargo-gamma-lib/src/exec/workspace.rs:1277
  • crates/cargo-gamma-lib/src/exec/workspace.rs:1762
  • crates/cargo-gamma-lib/src/exec/workspace.rs:1769
  • crates/cargo-gamma-lib/src/exec/workspace.rs:1805
  • crates/cargo-gamma-lib/src/exec/workspace.rs:1832
  • crates/cargo-gamma-lib/src/merge/union.rs:447
  • crates/cargo-gamma-lib/tests/agreement.rs:239
  • crates/cargo-gamma-lib/tests/agreement.rs:308-311
  • crates/cargo-gamma-lib/tests/session.rs:389
  • crates/cargo-gamma-lib/tests/session.rs:413
  • crates/cargo-gamma-lib/tests/units.rs:143-146
  • crates/cargo-gamma-process/src/testing.rs:99
  • crates/cargo-gamma-rt/src/runtime.rs:675-701
  • crates/cargo-gamma-rt/src/runtime.rs:1030-1055
  • crates/cargo-gamma-rt/src/runtime.rs:2968-3035
  • crates/cargo-gamma-unsafe/src/cgroup.rs:83
  • crates/cargo-gamma-unsafe/src/cgroup.rs:108
  • crates/cargo-gamma-unsafe/src/cgroup.rs:1604
  • crates/cargo-gamma-unsafe/src/job.rs:798
  • crates/cargo-gamma-unsafe/src/job.rs:817-821
  • crates/cargo-gamma-unsafe/src/native_faults.rs:126
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:12-13
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:576
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:606
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:642
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:673
  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:41
  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:44
  • crates/cargo-gamma-lib/src/exec/sync.rs:22
  • crates/cargo-gamma-process/src/process_tree.rs:18
  • crates/cargo-gamma-process/src/process_tree.rs:68
  • crates/cargo-gamma-process/src/process_tree.rs:1058
  • crates/cargo-gamma-process/src/process_tree.rs:2069
  • crates/cargo-gamma-process/src/process_tree.rs:2410
  • crates/cargo-gamma-process/src/process_tree.rs:2526
  • crates/cargo-gamma-rt/src/runtime.rs:2835-2882

Reviewed in: #113


Title: Document the value rejection as a contract, not a guard implementation

[Copilot speaking]

The newly expanded public rustdoc correctly states that #[gamma::value] is rejected on const fn and empty bodies, but it explains that rule through the collector's reachability and the current run-time guard insertion mechanism. Those details belong to the attribute implementation; the user-facing contract is the accepted placement and diagnostic behavior.

Location: crates/cargo-gamma-attrs/src/lib.rs:435-439

Reproducible reasoning: Lines 435-439 describe collection reaching a value and a mutant being spliced behind a run-time guard. crates/cargo-gamma-attrs/docs/DESIGN.md defines this crate as the user-facing attribute namespace and identifies supported syntax and diagnostics as its public contract, while crates/cargo-gamma-attrs-impl/docs/DESIGN.md owns parsing and validation. The rejection can remain stable if collection or instrumentation is later reorganized, so publishing the current mechanism in the macro API documentation turns an implementation choice into an apparent promise.

Consequence: A future instrumentation or collection refactor can preserve the rejection contract while making this rustdoc false, and downstream readers may incorrectly rely on guard placement or collector behavior as stable API behavior.

Recommended action: Keep the public rustdoc focused on the requirement that value is rejected on declarations, const fn, and empty bodies. Move the collector and run-time guard explanation to the implementation crate's design documentation or an implementation comment.

References:

  • crates/cargo-gamma-attrs/docs/DESIGN.md
  • crates/cargo-gamma-attrs-impl/docs/DESIGN.md

Impacted locations:

  • crates/cargo-gamma-attrs/src/lib.rs:435-439

Reviewed in: #113


Title: Describe the diagnostic tests as fragment checks rather than exact-output contracts

[Copilot speaking]

The new diagnostics test module says it pins each macro's exact diagnostic, but every test only checks that stderr contains the macro prefix and one message fragment. It does not pin the complete diagnostic, spans, severity, formatting, or additional emitted errors.

Location: crates/cargo-gamma-attrs/tests/diagnostics.rs:5-14

Reproducible reasoning: The module documentation at lines 5-14 claims exactness and then explains that validation is performed through substrings. The helper returns the complete stderr, but the assertions at lines 131-192 use only contains. Therefore many changes to the actual diagnostic output can pass while the documentation tells maintainers that the exact output is protected.

Consequence: Maintainers can rely on a stronger test contract than exists and overlook regressions in diagnostic shape because the suite remains green despite the documented exact-output guarantee.

Recommended action: Rename and document these as checks for diagnostic identity and the essential reason. If byte-for-byte diagnostic stability is genuinely required, use a normalized golden comparison instead, recognizing that exact compiler stderr is more brittle across supported Rust versions.

Impacted locations:

  • crates/cargo-gamma-attrs/tests/diagnostics.rs:5-14
  • crates/cargo-gamma-attrs/tests/diagnostics.rs:131-192

Reviewed in: #113


Title: Make timeout-multiplier example reasons match the demonstrated code

[Copilot speaking]

The two newly added successful examples attach operational reasons that their functions do not demonstrate. digest accepts an arbitrary byte slice and computes a sum, but its reason says it hashes a megabyte. accumulate performs an ordinary conversion and u64 addition, but its reason says widening arithmetic is slow. Because these examples teach how to use auditable reason metadata, the reasons should remain verifiable from the scenario they describe.

Location: crates/cargo-gamma-attrs/src/lib.rs:564-573

Reproducible reasoning: Reading the example bodies reproduces the mismatch directly: digest has no input-size constraint and invokes no hash operation, while accumulate only folds bytes into a u64 total. The examples therefore demonstrate the attribute syntax with explanations unrelated to the shown workload. The same syntax can be taught without this shortcut by choosing scenario names, bodies, and reasons that describe one another accurately.

Consequence: Consumers copying the examples may treat reason as placeholder prose rather than durable justification for a timeout override, leaving future reviewers unable to determine why a multiplier is needed or whether it is still appropriate.

Recommended action: Revise these examples so each function and reason describe a coherent, realistic workload while preserving the positional-order and trailing-comma forms being demonstrated. For example, either show an operation and input contract that genuinely justify the multiplier, or use reasons that accurately describe the simple accumulation bodies.

Impacted locations:

  • crates/cargo-gamma-attrs/src/lib.rs:564-573

Reviewed in: #113


Title: Select the proc-macro artifact without filesystem timestamps

[Copilot speaking]

gamma_artifact scans the target and host dependency directories for hashed dynamic libraries whose filenames have a gamma prefix, sorts the candidates by filesystem modification time, and silently passes the newest candidate to rustc. A Cargo target directory can retain matching artifacts from other source revisions, feature combinations, compiler versions, checkouts, or concurrent Cargo activity, so the newest file is not necessarily the artifact Cargo selected for this test run.

Location: crates/cargo-gamma-attrs/tests/diagnostics.rs:80-82

Reproducible reasoning: Cargo artifact hashes distinguish builds, but this lookup does not recover the hash of the dependency used by the current integration test. Cargo can reuse an already-valid current artifact without updating its modification time, while an unrelated artifact can have a later timestamp. A file's modification time records when it was written, not whether it belongs to the running test binary's build graph. Sorting by metadata.modified() and taking the last candidate therefore makes fixture compilation depend on filesystem time and target-directory history rather than the reviewed source, without documenting an invariant that makes multiple candidates safe.

Consequence: After switching branches or build configurations that share a target directory, the diagnostic tests can compile malformed fixtures against stale or unrelated proc-macro code. This can hide a regression in the current implementation or cause branch-history-dependent failures while leaving maintainers without explicit assumptions for recognizing or safely changing the selection behavior.

Recommended action: Arrange for Cargo to identify or load the exact proc-macro artifact for the current build, such as by compiling fixtures through a dedicated Cargo or trybuild-style crate with a path dependency, or by consuming Cargo's structured build output to obtain the artifact path. Cache that deterministic path for the test process instead of rescanning and ranking target contents by time. If the recency heuristic must remain, document the assumptions that make it valid and reject ambiguous candidate sets rather than silently selecting one.

References:

  • crates/cargo-gamma-lib/tests/instrumented_compiles.rs (the attrs_crate rationale explains why selecting a Cargo proc-macro artifact from a direct rustc invocation is fragile)

Impacted locations:

  • crates/cargo-gamma-attrs/tests/diagnostics.rs:34-82
  • crates/cargo-gamma-attrs/tests/diagnostics.rs:34-80

Reviewed in: #113


Title: Exclude the new integration tests from the published crate

[Copilot speaking]

The change adds consumer.rs and diagnostics.rs as development-only integration tests, but cargo-gamma-attrs/Cargo.toml has no include or exclude package-file policy. Cargo therefore includes these tracked test sources in the crate archive even though downstream users do not compile or need them.

Location: crates/cargo-gamma-attrs/tests/consumer.rs

Reproducible reasoning: Both added files live under crates/cargo-gamma-attrs/tests/ and exist solely to validate this repository's proc-macro implementation. Cargo's default packaging file selection includes tracked test sources unless the manifest narrows the package contents. The package manifest currently declares neither an allowlist nor an exclusion for tests/**, so this diff directly expands the published archive with development-only source.

Consequence: Every release of cargo-gamma-attrs would distribute the external-consumer fixtures and the compiler-spawning diagnostic harness to users, increasing the package payload and exposing test infrastructure as published content without any runtime or documentation benefit.

Recommended action: Add an explicit package include list containing only the files required to compile and document the proc-macro crate, or at minimum exclude tests/**. An allowlist prevents future development artifacts from entering the archive, at the cost of updating it when a genuinely required package file is added.

References:

  • crates/cargo-gamma-attrs/Cargo.toml

Impacted locations:

  • crates/cargo-gamma-attrs/tests/consumer.rs:1-85
  • crates/cargo-gamma-attrs/tests/diagnostics.rs:1-193

Reviewed in: #113


Title: Condense overlong Rust documentation summaries

[Copilot speaking]

New and rewritten Rust item and module documentation across the affected crates and tests uses opening paragraphs whose rendered visible text exceeds the 80-character summary limit. Wrapping the source across multiple documentation-comment lines does not create a paragraph break in rustdoc, so the affected comments still render as overlong summaries.

Location: crates/cargo-gamma-engine/src/cfg.rs:160-161

Reproducible reasoning: Every source finding identifies the same root cause: detailed or multi-clause prose remains in the uninterrupted first documentation paragraph, making its rendered summary longer than the required limit. The findings differ only in the affected items and files. A blank documentation line is required to separate a concise summary from the additional explanation.

Consequence: The generated documentation wraps these opening summaries instead of presenting the concise, consistently scannable first line required by the Rust documentation convention.

Recommended action: Rewrite each affected first paragraph to at most 80 visible rendered characters. Where the removed detail remains useful, retain it in a separate following paragraph after a blank documentation line, measuring rendered text rather than Markdown source width.

Impacted locations:

  • crates/cargo-gamma-engine/src/cfg.rs:160-161
  • crates/cargo-gamma-engine/src/model/identity.rs:145-152
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:279-280
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:298-299
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:306-307
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:327-328
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:432-433
  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:98-99
  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:117-118
  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:125-126
  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:145-146
  • crates/cargo-gamma-engine/src/parse/comment.rs:33
  • crates/cargo-gamma-engine/src/schema.rs:246
  • crates/cargo-gamma-engine/src/text.rs:128-129
  • crates/cargo-gamma-engine/src/text.rs:152
  • crates/cargo-gamma-lib/src/ci/sarif.rs:24-25
  • crates/cargo-gamma-lib/src/ci/sarif.rs:200
  • crates/cargo-gamma-lib/src/discover/survey.rs:1030
  • crates/cargo-gamma-lib/src/discover/survey.rs:1039
  • crates/cargo-gamma-lib/src/exec/copy.rs:62
  • crates/cargo-gamma-lib/src/exec/copy.rs:123
  • crates/cargo-gamma-lib/src/exec/sweep.rs:827
  • crates/cargo-gamma-lib/src/exec/workspace.rs:743
  • crates/cargo-gamma-lib/src/parse.rs:9
  • crates/cargo-gamma-lib/src/report/progress.rs:357
  • crates/cargo-gamma-lib/tests/agreement.rs:6-7
  • crates/cargo-gamma-lib/tests/agreement.rs:143-144
  • crates/cargo-gamma-lib/tests/agreement.rs:182-183
  • crates/cargo-gamma-lib/tests/agreement.rs:249-250
  • crates/cargo-gamma-lib/tests/agreement.rs:293-294
  • crates/cargo-gamma-lib/tests/agreement.rs:305-306
  • crates/cargo-gamma-lib/tests/agreement.rs:316-319
  • crates/cargo-gamma-lib/tests/docs.rs:269
  • crates/cargo-gamma-lib/tests/docs.rs:582
  • crates/cargo-gamma-lib/tests/docs.rs:596
  • crates/cargo-gamma-lib/tests/docs.rs:720
  • crates/cargo-gamma-lib/tests/sarif_contract.rs:36-37
  • crates/cargo-gamma-lib/tests/sarif_contract.rs:79
  • crates/cargo-gamma-process/src/process_tree.rs:38
  • crates/cargo-gamma-process/src/process_tree.rs:223
  • crates/cargo-gamma-process/src/process_tree.rs:279
  • crates/cargo-gamma-process/src/process_tree.rs:289
  • crates/cargo-gamma-rt/src/runtime.rs:585-613
  • crates/cargo-gamma-rt/src/runtime.rs:670-718
  • crates/cargo-gamma-unsafe/src/cgroup.rs:84
  • crates/cargo-gamma-unsafe/src/cgroup.rs:109
  • crates/cargo-gamma-unsafe/src/cgroup.rs:262
  • crates/cargo-gamma-unsafe/src/cgroup.rs:276
  • crates/cargo-gamma-unsafe/src/cgroup.rs:563
  • crates/cargo-gamma-unsafe/src/cgroup.rs:592
  • crates/cargo-gamma-unsafe/src/cgroup.rs:778
  • crates/cargo-gamma-unsafe/src/cgroup.rs:899
  • crates/cargo-gamma-unsafe/src/cgroup.rs:1982
  • crates/cargo-gamma-unsafe/src/job.rs:152-164
  • crates/cargo-gamma-unsafe/src/platform_error.rs:73-75
  • crates/cargo-gamma/src/real_host.rs:47-53
  • crates/cargo-gamma/tests/binary.rs:22
  • crates/cargo-gamma/tests/binary.rs:30
  • crates/cargo-gamma/tests/binary.rs:111
  • crates/cargo-gamma-rt/src/runtime.rs:259-271
  • crates/cargo-gamma-rt/src/runtime.rs:297-316
  • crates/cargo-gamma-rt/src/runtime.rs:368-400

Reviewed in: #113


Title: Keep all Error implementation blocks together

[Copilot speaking]

The new Parts type is placed between Error's inherent implementation and its Display, StdError, and From<io::Error> implementations. This splits the blocks belonging to the primary error type with a different type definition.

Location: crates/cargo-gamma-engine/src/error.rs:95-110

Reproducible reasoning: The code-layout guidance requires different blocks associated with the same type to stay together. In this file, impl Error ends before Parts, but the subsequent impl Display for Error, impl StdError for Error, and impl From<io::Error> for Error remain below Parts. A reader following Error therefore has to cross the unrelated Parts definition before reaching the rest of Error's behavior.

Consequence: Future maintenance of the engine error can overlook one of its separated trait implementations, and readers cannot inspect the complete behavior of the primary type as one contiguous unit.

Recommended action: Move Parts below the Display, StdError, and From<io::Error> implementations so every block associated with Error is contiguous, while keeping the Parts definition and its own future implementations together.

Impacted locations:

  • crates/cargo-gamma-engine/src/error.rs:95-136

Reviewed in: #113


Title: Define the discovery pre-pass terminology before using it

[Copilot speaking]

The new design boundary introduces several implementation-specific terms—“pre-pass”, “stated-value audit”, “numeric/import indexes”, and “fused entry point”—without defining them or identifying the fused API. The preceding Purpose and Boundaries text describes mutation discovery generally, but it does not give a reader enough context to distinguish these passes or understand how they relate.

Location: crates/cargo-gamma-engine/docs/DESIGN.md:23-30

Reproducible reasoning: A reader encounters this vocabulary for the first time in lines 23–30. The paragraph then contrasts the unnamed “fused entry point” with check_stated, but neither entry point is introduced and the role of the indexes is not explained. Understanding the boundary therefore requires finding and reverse-engineering implementation modules outside the design document, contrary to the requirement that specialized terms be defined before use.

Consequence: A reader can miss that the configuration-aware combined operation and the standalone stated-value checker intentionally inspect different regions, or may mistake internal pass names for previously established design concepts.

Recommended action: Introduce these concepts in plain language before this boundary, or rewrite the boundary to define each pass as it is named. Identify and link the combined API if “fused entry point” means check_stated_and_collect_with, and briefly state what evidence the numeric/import indexes provide.

Impacted locations:

  • crates/cargo-gamma-engine/docs/DESIGN.md:23-30

Reviewed in: #113


Title: Describe MutantId compatibility at the actual serialization boundary

[Copilot speaking]

The new rustdoc attributes artifact compatibility to two different mechanisms more broadly than the implementation supports. The type documentation says Serde transparency means every plan, record, and report written by an earlier version still reads unchanged, although transparency only preserves this field's string-shaped wire representation and cannot guarantee compatibility of the surrounding artifact schema. The MutantId::new documentation then says accepting arbitrary text prevents future-version records from failing to read, but derived transparent deserialization constructs the wrapped CompactString directly and does not call new.

Location: crates/cargo-gamma-engine/src/model/identity.rs:31-38

Reproducible reasoning: The type derives Deserialize with #[serde(transparent)], so Serde deserializes the single CompactString field without routing the value through the inherent constructor. Consequently, validation added or omitted in MutantId::new has no effect on reading persisted records. Transparency also establishes only that MutantId itself has the same serialized representation as its inner string; successful deserialization of a complete plan, record, or report still depends on every other field and schema rule in that artifact. The current wording therefore promises artifact-wide backward compatibility and explains constructor behavior using a path the implementation does not take.

Consequence: A maintainer can rely on the comment as evidence that arbitrary older or future artifacts are supported, or preserve the constructor's permissive behavior to protect deserialization, even though neither conclusion follows from this code. That can turn later schema or validation work into an accidental compatibility promise and obscure where persisted-input compatibility is actually enforced.

Recommended action: Narrow the type documentation to the contractual fact that MutantId remains serialized as its underlying string. Document new only as constructing an opaque identity from already rendered text and state the actual reason it accepts arbitrary text, without tying that choice to Serde record reads. Put any whole-artifact backward- or forward-compatibility guarantee on the artifact formats that implement and test it.

Impacted locations:

  • crates/cargo-gamma-engine/src/model/identity.rs:23-38

Reviewed in: #113


Title: Describe the identity contract without repeating its numeric version

[Copilot speaking]

The documentation for MUTANT_ID_VERSION names the newly assigned numeric version immediately above the constant that stores the same value. The useful part of the comment is the normalization change and its compatibility effect; repeating the numeric literal creates a second copy that must be kept synchronized.

Location: crates/cargo-gamma-engine/src/model/identity.rs:185-191

Reproducible reasoning: The constant declaration is the authoritative version value. Its new documentation says Version 5 while the declaration says MUTANT_ID_VERSION: u32 = 5. If a later identity change increments only the constant or only the prose, readers receive conflicting information. The surrounding explanation can remain independently useful by describing the current normalization contract and, if historical compatibility needs to be retained, referring to the preceding contract without restating the current literal.

Consequence: A future version increment can leave the comment claiming a different emitted identity version than the value serialized into campaign artifacts, making compatibility investigations harder.

Recommended action: Rewrite the newly added paragraph to explain the current self-type normalization and compatibility boundary without spelling out the constant's numeric value. Keep the declaration as the single source of truth for that value.

Impacted locations:

  • crates/cargo-gamma-engine/src/model/identity.rs:185-191

Reviewed in: #113


Title: Document the trait implementation metadata on MutantDefinition

[Copilot speaking]

The new public trait_impl field on MutantDefinition has no field documentation, even though its representation is intentionally narrower than a full trait path and it exists for a specific policy boundary. The corresponding Candidate field explains that it stores the terminal trait name separately from item_path so policy can select implementations without parsing the identity, but that contract is absent where the metadata becomes part of the exported mutation definition.

Location: crates/cargo-gamma-engine/src/model/mutant_definition.rs:22

Reproducible reasoning: MutantDefinition is publicly re-exported from the engine model, and trait_impl: Option<Arc<str>> alone does not reveal whether the text is qualified, normalized, or intended for display, identity, or selection. The collector records only the last path segment, then the definition carries that value across the engine boundary. Without a local explanation, a maintainer or consumer must trace the collector and candidate types to reproduce the field's meaning and may reasonably infer the wrong representation.

Consequence: A consumer can treat trait_impl as a fully qualified trait path or fold it into identity logic, causing qualified traits with the same terminal name to be interpreted differently from the documented selection policy.

Recommended action: Add field documentation to MutantDefinition::trait_impl stating that it is the optional terminal name of the enclosing implemented trait and explaining that it is kept separately for trait-implementation selection policy rather than as a full path or identity component.

References:

  • crates/cargo-gamma-engine/src/ops/collect/candidate.rs

Impacted locations:

  • crates/cargo-gamma-engine/src/model/mutant_definition.rs:22
  • crates/cargo-gamma-engine/src/ops/collect/candidate.rs:34-40

Reviewed in: #113


Title: Inline the new public trivial forwarders and accessors

[Copilot speaking]

New public APIs across the cargo-gamma crates add methods and trait implementations whose complete bodies only return a field, expose an underlying scalar, compare wrapped text, or forward to one existing operation. These include the supported MutantId and SiteIndex accessors and conversions, engine error and source-position accessors, control-encoding wrappers, the RunRecord iterator adapter, the Unix effective-user wrapper, and PlatformError accessors. They lack #[inline] even though downstream crates call them across crate boundaries.

Location: crates/cargo-gamma-engine/src/model/identity.rs:43-54

Reproducible reasoning: For a trivial public wrapper, #[inline] makes the body available to a downstream optimizer without requiring link-time optimization; it is an optimization opportunity rather than a guarantee that every call disappears. Each affected body has no independent algorithm or error path, so exposing it does not duplicate substantial code. This consolidation excludes test-only or representation-probing APIs that should instead be removed or narrowed rather than optimized.

Consequence: Ordinary non-LTO downstream builds may retain avoidable calls at small abstraction boundaries used while scanning source, comparing identities, rendering output, iterating records, or classifying platform failures.

Recommended action: Add #[inline] to the listed production-supported trivial public methods and trait implementations. Do not use #[inline(always)], and do not annotate an API that is being removed or narrowed for visibility reasons; the recommendation applies only to the supported cross-crate forwarders that remain.

Impacted locations:

  • crates/cargo-gamma-engine/src/error.rs:68-70
  • crates/cargo-gamma-engine/src/model/identity.rs:43-140
  • crates/cargo-gamma-engine/src/model/identity.rs:164-179
  • crates/cargo-gamma-engine/src/parse/source_file.rs:161-191
  • crates/cargo-gamma-engine/src/schema.rs:205-215
  • crates/cargo-gamma-engine/src/text.rs:44-46
  • crates/cargo-gamma-engine/src/text.rs:56-58
  • crates/cargo-gamma-lib/src/discover/record.rs:528-530
  • crates/cargo-gamma-unsafe/src/identity.rs:16-20
  • crates/cargo-gamma-unsafe/src/platform_error.rs:53-62

Reviewed in: #113


Title: Version mutation reports before changing the mutant-ID scheme

[Copilot speaking]

MUTANT_ID_VERSION advances to version 5 because implementation scopes now produce different IDs, but cargo-gamma reports do not record the identity scheme that produced their mutant IDs. The report reader and merger accept reports across cargo-gamma versions and union them by the opaque ID text alone. SARIF explicitly versions the fingerprint key, so that output recognizes the namespace change, while the report/merge path has no equivalent transition marker.

Location: crates/cargo-gamma-engine/src/model/identity.rs:187-194

Reproducible reasoning: For an implementation affected by the new scope normalization, the same logical mutant has one ID in a version-4 report and another in a version-5 report. merge keys verdicts by mutant.id and can withdraw an absent old ID only when an unsharded report supplies a complete current population. Its documented shard behavior deliberately keeps every ID admissible when only shards are available. Because neither Report nor RunInfo carries MUTANT_ID_VERSION, and the merge does not use framework.version to establish identity compatibility, a rotation containing pre-upgrade and post-upgrade shards cannot determine that the two IDs represent one mutant. Both survive as distinct entries even when they refer to the same source construct.

Consequence: A normal sharded CI rotation spanning the upgrade can count an affected implementation mutant twice, retain its stale version-4 verdict beside its version-5 result, and distort the merged score until a complete version-5 population happens to withdraw the legacy ID.

Recommended action: Record the mutant-ID scheme version in cargo-gamma report metadata and define an explicit merge policy for mixed schemes. For example, treat reports without the field as the legacy scheme and refuse or isolate mixed-version shard merges unless a complete current population can retire the legacy IDs. Document the upgrade effect so users know whether they need a complete run or must start a new report rotation.

References:

  • crates/cargo-gamma/docs/DESIGN.md — Identity and knowledge across campaigns

Impacted locations:

  • crates/cargo-gamma-engine/src/model/identity.rs:187-194
  • crates/cargo-gamma-lib/src/elements/report.rs:44-81
  • crates/cargo-gamma-lib/src/merge/union.rs:15-20
  • crates/cargo-gamma-lib/src/merge/union.rs:72-81

Reviewed in: #113


Title: Call item_path an item path rather than a human-readable identity

[Copilot speaking]

The new trait_impl field documentation first names item_path and then calls the same value a "human-readable identity." In this module, identity already has the distinct, explicitly defined meaning represented by MutantId, while the surrounding APIs consistently call this value an item path.

Location: crates/cargo-gamma-engine/src/ops/collect/candidate.rs:38-39

Reproducible reasoning: The documentation says trait_impl avoids parsing a human-readable identity, but the value it is contrasted with is specifically item_path. A reader therefore has to decide whether "identity" means the item path, the hashed MutantId, or another representation. Reusing the established term "item path" removes that ambiguity and preserves the distinction between the descriptive scope text and the content-addressed mutant identity.

Consequence: A maintainer can incorrectly infer that trait-implementation selection would otherwise parse MutantId, or introduce more code and documentation using two terms for the same item-path concept.

Recommended action: Replace "human-readable identity" with "item path" (or refer directly to item_path) so the documentation uses the established term and leaves "identity" for MutantId.

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/candidate.rs:36-40

Reviewed in: #113


Title: Preserve token boundaries when normalizing implementation self types

[Copilot speaking]

impl_scope now passes an arbitrary implementation self type through compact_path, which removes every whitespace character and comment without preserving lexical token boundaries. That normalization is safe for ordinary paths separated by punctuation, but it is not injective for the full Rust Type syntax accepted as an implementation self type.

Location: crates/cargo-gamma-engine/src/ops/collect/collector.rs:466

Reproducible reasoning: For example, dyn Marker is a trait-object type while dynMarker can be a distinct nominal type identifier. Both normalize to the same text, dynMarker. Consequently, implementations such as impl Subject for dyn Marker and impl Subject for dynMarker receive the same scope string when their method names match. into_definitions then disambiguates identical site keys with a source-order occurrence index, so reordering those implementation blocks can transfer stable mutant identifiers between different self types. This contradicts the new identity design's purpose of representing the complete self type and eliminating source-order dependence.

Consequence: Cached verdicts, shard assignment, configured expectations, or report fingerprints can become associated with the wrong implementation after otherwise behavior-preserving implementation reordering.

Recommended action: Normalize the self type with a token-aware representation that removes trivia while retaining separators wherever removing trivia would merge adjacent Rust tokens. Preserve the current formatting-insensitivity, and add a regression that gives dyn Marker and a nominal dynMarker identical method bodies and verifies their identities remain distinct and stable when the implementation blocks are reordered.

References:

  • crates/cargo-gamma-engine/docs/DESIGN.md
  • crates/cargo-gamma-engine/src/model/identity.rs

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/collector.rs:466-467
  • crates/cargo-gamma-engine/src/ops/collect/collector.rs:1194-1229

Reviewed in: #113


Title: Use the established “terminal identifier” term for trait matching

[Copilot speaking]

The new trait_impl field documentation calls the stored value the trait's “terminal name”, while the user-facing configuration contract consistently defines the same value as the implemented trait's “terminal identifier”. Both refer to the final written path segment, such as Debug in core::fmt::Debug, so the two terms do not identify different concepts.

Location: crates/cargo-gamma-engine/src/ops/collect/collector.rs:93-94

Reproducible reasoning: The configuration documentation introduces “terminal identifier” as the term for the value matched by trait-impl rules and explains its qualification and alias behavior. The new collector comment describes the value populated from path.segments.last() as a “terminal name”. Because the implementation stores exactly the documented terminal identifier and does not define a separate meaning for “name”, using a synonym here makes readers determine whether the wording signals a semantic distinction.

Consequence: Maintainers tracing trait-exclusion behavior from configuration into candidate collection can incorrectly suspect that trait_impl uses a different normalization rule from the documented terminal-identifier contract.

Recommended action: Change the field comment to call the value the “terminal identifier of the enclosing implemented trait”, matching the established configuration terminology.

References:

  • crates/cargo-gamma/docs/CONFIG.md, [[exclude-mutants]] contract
  • crates/cargo-gamma/README.md, trait implementation exclusions

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/collector.rs:93-94

Reviewed in: #113


Title: Describe the prefilter without duplicating its literal

[Copilot speaking]

The new prefilter comment repeats the exact leak string used by both contains calls. The rationale is useful, but spelling the implementation value again in prose creates two representations of the same decision that must remain synchronized.

Location: crates/cargo-gamma-engine/src/ops/collect/collector/noop.rs:57-62

Reproducible reasoning: Lines 57-61 explain that matching depends on a literal identifier and name that identifier explicitly; line 62 independently embeds the same spelling twice in executable code. A future change to the recognized method name or to a shared identifier can update the code while leaving the prose behind, so the comment no longer reliably communicates the decision it is intended to justify.

Consequence: A maintainer can read a stale method name in the rationale after changing the prefilter, obscuring which identifier the optimization is actually required to detect.

Recommended action: Keep the correctness and performance rationale, but refer to the required terminal method identifier checked by path_ends_with instead of repeating its spelling. Alternatively, introduce one suitably named constant and have the code refer to that single source of truth.

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/collector/noop.rs:57-62

Reviewed in: #113


Title: Borrow the configuration set in the fused pre-pass

[Copilot speaking]

The fused phase-one pass makes two owned copies of the build configuration for every source file: Walk::new(selection, cfg) clones it into Walk, and PhaseOne clones it again for the shared traversal gate. CfgSet owns three hash sets containing strings and string pairs, while both visitors only read it.

Location: crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:66-67

Reproducible reasoning: traversal::collect invokes phase_one::run once per parsed source file. At lines 66-67, constructing PhaseOne calls Walk::new and also explicitly clones cfg; Walk::new performs its clone at indexes.rs:479. Cloning a populated CfgSet allocates new hash-table storage and copies its owned names and key/value strings. This happens even when the selected mutators consult no numeric or import indexes, because Walk::new clones the set before the visitor can determine that those indexes are disabled. The configuration reference passed to run remains valid for the entire synchronous traversal, so neither owned copy is required.

Consequence: On a workspace with many source files, discovery performs repeated allocations and copies proportional to the number of active configuration names and feature pairs for every file. This can offset part of the allocation and traversal reduction that the fused pre-pass is intended to provide, including for selections that do not use the indexes at all.

Recommended action: Give Walk and PhaseOne a lifetime and store the existing &CfgSet in both visitors instead of cloning it. This adds lifetime parameters to these internal visitor types but preserves the current behavior while removing the per-file hash-set and string copies.

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/collector/phase_one.rs:66-67
  • crates/cargo-gamma-engine/src/ops/collect/collector/indexes.rs:464-479

Reviewed in: #113


Title: Avoid re-interning the trait name for every candidate

[Copilot speaking]

Each candidate inside a trait implementation already carries an Arc<str> cloned from the collector's implementation-level trait_impl. into_definitions discards that sharing boundary and calls Interner::text again for every candidate. That call performs a hash-table lookup and another reference-counted clone for every mutant, and the first occurrence also allocates a replacement Arc<str> plus the interner's owned key.

Location: crates/cargo-gamma-engine/src/ops/collect/definitions.rs:83

Reproducible reasoning: The collector creates one trait-name Arc<str> when entering an impl, clones that same pointer into every emitted Candidate, and restores the previous value when leaving the impl. In into_definitions, line 83 borrows each candidate's already-shared pointer, probes Interner::texts through Interner::text, creates or clones a second pointer, and then drops the candidate's pointer. The repository's own interner documentation says a run can contain hundreds of thousands of mutations, so this converts implementation-level work into per-mutant hashing and atomic reference-count churn. Cross-implementation deduplication only needs work once per distinct incoming trait value or implementation scope, not once per candidate.

Consequence: A source file with large trait implementations pays an avoidable hash lookup and extra Arc clone/drop cycle for every generated mutant, increasing discovery CPU cost on the same high-cardinality path that the surrounding sharing logic is intended to optimize.

Recommended action: Preserve the existing candidate-owned Arc<str> when constructing MutantDefinition, or intern/cache trait names at implementation scope before candidates are emitted and then move the candidate pointer into the definition. If collapsing identical trait names across separate implementations is important, retain that deduplication while ensuring the hash lookup happens once per distinct implementation-level pointer rather than once per mutant.

References:

  • crates/cargo-gamma-engine/src/model/interner.rs

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/definitions.rs:83

Reviewed in: #113


Title: Keep the Audit implementation blocks adjacent

[Copilot speaking]

The new inert_reason free function is placed between Audit's inherent implementation and its Visit implementation. This separates two blocks that together define the behavior of the same type, even though the layout guideline requires associated blocks for a type to remain together.

Location: crates/cargo-gamma-engine/src/ops/collect/stated.rs:221-229

Reproducible reasoning: impl Audit ends at line 212, the newly added inert_reason function occupies lines 214-229, and impl Visit for Audit begins at line 235. Reading or searching through Audit therefore reaches an intervening free function before the remainder of the type's implementation. Moving the helper before the Audit declaration or after the Visit implementation preserves the exact dependency and behavior while restoring one contiguous type-oriented block.

Consequence: A maintainer inspecting Audit can overlook its visitor behavior or must jump across unrelated item boundaries to understand the complete type, making this diagnostic traversal harder to navigate and maintain.

Recommended action: Move inert_reason outside the sequence of Audit implementation blocks, for example immediately after the Visit implementation, so the inherent and trait implementations remain adjacent.

Impacted locations:

  • crates/cargo-gamma-engine/src/ops/collect/stated.rs:148-235

Reviewed in: #113

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions