google / google/zerocopy

Simplify unsafe Rust skill

Open
#3,683 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2.6k
Forks
179
Avg merge
1d 19h
Merged PRs (30d)
29

Description

*Authored by an AI agent acting on joshlf's behalf.*

**I would keep the skill’s standard of proof and simplify how it expresses and maintains that standard.** The strongest changes are to broaden the design process beyond demonstrated reuse, make the tool-evidence non-vacuity check precise, and consolidate repeated proof rules without removing the checks that make them operational.

I read `skills/unsafe-rust/SKILL.md`, all six references, both audit templates, and the relevant maintainer guidance at `google/zerocopy` revision `2443ec070eb0`. I used your supplied `model-refinement` revision, `0b9ff90c1bff976042e718c4f4430e358adcec54`. The proposals below distinguish changes to the model from changes to its presentation; I have not run fresh-agent behavioral evaluations or modified the repository.

## What should remain unchanged

The skill already has a coherent foundation: recover the exact claim, identify its obligations, derive them from applicable premises, and issue only the verdict justified by the resulting certificate. The maintainer document explicitly rejects a closed taxonomy of unsafe operations, mandatory proof graphs, and additional proof-state machinery. I would retain those decisions rather than introduce another organizing framework alongside them.

In particular, simplification must preserve these independent distinctions:

* **Required cases versus proved cases**, including all relevant configuration, input, state, and execution dimensions.
* **Artifact observations, semantic premises, and accepted trust**, including the distinction between selected dependencies and caller-controlled code.
* **Universal proof, existential refutation, and missing proof**, with separate soundness and postcondition results.
* **Implementation correctness, documentation quality, and proposed redesign**, so a reconstructed proof or better future design does not erase a defect in the current artifact.

These distinctions affect acceptance, not just terminology. The current skill and templates already represent them explicitly.

The opportunity is therefore mostly **fewer independently maintained formulations of the same rules**, rather than fewer semantic distinctions.

## 1. Allow an abstraction to justify itself without demonstrated reuse

**Change:** Revise `references/abstraction-design.md`, especially “Generate Proof-Oriented Candidates” and “Prove and Compare Candidates.”

The design guide currently permits a new reusable abstraction only when demonstrated consumers share a semantic capability. Its comparison criteria also count genericity without demonstrated reuse against a candidate. Those are stronger restrictions than your model-refinement skill, which expressly allows a broader theory to justify itself without repeated existing callers.

Demonstrated reuse is good evidence, but it is not the underlying reason to introduce an abstraction. A new representation can reduce the proof burden within one consumer by eliminating impossible states, deriving several special cases from one invariant, or replacing several mechanisms with one. Conversely, several callers can share an interface without sharing a useful invariant.

The current opening—considering the smallest transformations that remove an unsupported premise—also favors local repairs. I would explicitly permit coordinated changes to representation, contracts, and operations. A useful abstraction may require several changes before any individual part becomes simpler.

### Proposed replacement guidance

> Consider local repairs and coordinated changes to representation, contracts, and operations. Keep the current design as a serious candidate.
>
> Introduce an abstraction when a common rule removes independent assumptions, invariants, mechanisms, or exceptional cases, or explains useful additional cases within the authorized design scope. Demonstrated shared consumers are evidence, not a prerequisite. Show the common rule, its preconditions and exclusions, and the cases it explains. Generic parameters, hypothetical callers, and case-specific adapters alone do not establish a benefit.
>
> Compare the complete designs, including implementation, caller obligations, proof structure, trust, compatibility, performance, and maintenance. A new abstraction may justify its cost by simplifying the whole design even when it increases the size of an individual component.

I would replace the “genericity without demonstrated reuse” criterion with **genericity whose added costs are not justified by a common rule or useful additional coverage**.

This preserves the original resistance to speculative frameworks while changing the admission test from *existing reuse* to *demonstrated explanatory or engineering value*.

**Discriminating cases:** A single-consumer design that replaces several special cases with one proved rule should remain eligible. A parameterized wrapper that merely moves each special case into an adapter should receive no credit for unification. A broader model that imposes more obligations on every caller must account for that cost rather than claim a simplification.

**Classification:** This generalizes the permitted design process. It does not expand the current artifact’s support promise, authorize a redesign during an acceptance audit, or relax the separation between current-artifact findings and future proposals. That separation should remain exactly as it is.

## 2. Make non-vacuity depend on the claim being established

**Change:** Revise item 5 of “Audit a Tool-Derived Proof” in `references/tcb-and-evidence.md`, and the corresponding field in the report template.

The current checklist asks the reviewer to check that the property, assertion, or unsafe operation is reachable. The template reinforces this with a field for reachability and source/model correspondence. This can be read as requiring reachability even when a valid safety argument proves that an operation is unreachable.

There are two different situations:

**Legitimate unreachability:** The required input domain is \(x \geq 0\), and an operation occurs only when \(x < 0\). An applicable proof of that relationship can discharge the operation’s conditional safety obligation.

**Manufactured vacuity:** The required input domain includes negative values, but the verification harness assumes \(x \geq 0\). The same unreachable operation now demonstrates a coverage gap, not safety.

The distinction is whether the proof preserves the required domain. Reachability itself is mandatory when claiming an execution witness, not when proving every universal safety property.

### Proposed replacement

> **Required-domain preservation and non-vacuity:** Check that the model represents the required cases and that its assumptions do not silently exclude any of them. An applicable proof that an operation is unreachable may discharge its conditional safety obligation. Distinguish that proof from an inconsistent harness or an unjustifiably narrowed input domain. Prove reachability when the result asserts an execution witness. Identify any empty required region and justify why it is empty.

In the report template, replace the unconditional reachability prompt with:

> Required-domain preservation, justified reachability or unreachability, and source/model correspondence.

An unreachable site should still receive a disposition. This change must not allow the auditor to omit it from the inventory.

**Why this improves the model:** It makes non-vacuity an application of the same domain-and-certificate discipline already used elsewhere. It also prevents a universal-proof check from accidentally acquiring an existential-proof requirement.

**Classification:** This is a targeted operational clarification. It should prevent false rejection of a legitimate proof technique without accepting a weakened harness.

## 3. Consolidate coverage around case–obligation pairs

**Change:** Give the general coverage rule one canonical explanation, then express configuration coverage, version applicability, and report aggregation as applications of it.

The current skill correctly distinguishes unioning case lemmas for one obligation from combining coverage across different obligations. It also repeatedly warns that configuration projections must not lose input or execution dimensions. These rules appear in the main skill, proof reference, configuration reference, reporting reference, and report template.

A single representation can explain both rules:

> Coverage relates a complete case to a particular obligation. It is not merely a set of configurations, inputs, or successful proof attempts.

For a fixed claim and trust boundary, let:

$$
R=\{(c,o)\mid c\text{ is required and obligation }o\text{ is required in }c\}
$$

and let \(E\) contain the case–obligation pairs discharged by closed, applicable derivations. Universal obligation coverage requires:

$$
R\subseteq E.
$$

This is a proposed normalization of the existing model, not a new standard of proof.

### Why it preserves the existing rule

Define:

$$
\operatorname{Covered}(c)
\iff
\text{every obligation required in }c
\text{ has its pair }(c,o)\text{ in }E.
$$

Then the existing condition, `Required ⊆ Covered`, is equivalent to \(R\subseteq E\).

Case lemmas can be unioned because their obligation identities remain attached. Evidence about a different obligation cannot accidentally fill a gap. Evidence about a different input or execution cannot fill a gap merely because it shares a configuration label.

For example:

| Evidence available | What remains unproved |
| ------------------------------------------------------------------ | ---------------------------------------------------- |
| Alignment proved on target A; initialization proved on target B | The missing obligation on each target |
| A property proved for input 0 on target A and input 1 on target B | The other required target–input combinations |
| A premise proved after a successful build stage | Paths that exited before that stage |
| An older-version premise plus an applicable compatibility argument | Any cases outside that argument’s established domain |

The first two failures arise from losing an index. The latter two arise from consuming a premise outside its applicability. They need different discovery prompts, but not independent theories of coverage.

### What must not disappear

The representation does not prove that \(R\) is complete. The auditor must still recover the domain and obligation inventory from the controlling sources. Otherwise, an empty or incomplete ledger would make inclusion easy to establish.

Likewise, it does not replace the existential certificates for `UNSOUND` and `CONTRACT-BROKEN`. A missing pair establishes a proof gap; it does not by itself establish a counterexample.

I would put the mathematical explanation in the maintainer guidance and use ordinary prose in the runtime instructions:

> Keep the obligation identity and every relevant case dimension attached to each derivation. Before closing the claim, establish that every required obligation is discharged in every case where it applies. Projection is an organizational device, not permission to replace universal coverage of omitted dimensions with an example.

**Tradeoff:** This reduces the number of separate explanations maintainers must keep consistent. It does not yet establish that agents will execute the procedure more reliably. The existing explicit union/intersection warning may still deserve a short reminder in the always-loaded skill.

## 4. Derive contract-evolution advice from one substitution rule

**Change:** Reorganize “Evolve Contracts Deliberately” in `references/api-boundaries-and-evolution.md`.

The current guide separately explains changes to caller preconditions, provider postconditions, unsafe-trait implementer obligations, and guarantees consumed by generic code. Those directions matter, but they can be derived from a common rule rather than maintained as largely separate cases.

The common rule is:

> For each affected boundary and party, a compatible replacement must continue to admit previously valid interactions and preserve previously promised guarantees.

One way to express this is to let \(A\) describe admitted environments and \(B(e)\) describe the behaviors a contract permits in environment \(e\). A substitutable replacement requires:

$$
A_{\text{old}}\subseteq A_{\text{new}}
$$

and, for every previously admitted environment,

$$
B_{\text{new}}(e)\subseteq B_{\text{old}}(e).
$$

Here, “behavior” must include every observation the contract constrains: ordinary results, errors, panic or unwind, termination, temporal obligations, and other promised properties. Comparing only values returned normally could discard a required behavior.

This explains the existing cases. Stronger caller requirements exclude some previously admitted interactions. Weaker provider guarantees permit behavior callers were previously entitled to rule out.

Traits require applying the rule to **both implementers and consumers**. Strengthening a required implementation guarantee can help consumers while invalidating previously valid implementations. Weakening that guarantee can preserve implementations while invalidating consumer reasoning. A one-sided compatibility check remains insufficient.

### Proposed runtime wording

> Analyze a contract change from every affected party’s side of the boundary. Preserve previously valid interactions and every guarantee that party was entitled to consume. For traits and callbacks, check both implementations and consumers. Include temporal and failure behavior, not only type signatures and normal-return values.
>
> Separately establish whether the project authorizes the change under its actual compatibility policy. A compatibility rule does not prove the new implementation correct or retroactively repair the old artifact.

I would retain a short function example and a short trait example. I would not keep every consequence as an independently maintained rule.

**Classification:** This minimizes the explanation without broadening the accepted domain. Its principal risk is hiding a role reversal in a trait or callback boundary, which the retained example and evaluation must exercise.

## 5. Give each operational rule one canonical home, while retaining useful reminders

The preceding changes will not reduce maintenance burden if every reference and template continues to restate the complete rules.

The current maintainer architecture already assigns responsibilities to the files and says templates must not become hidden instruction channels. Nevertheless, proof closure, applicability, coverage aggregation, and certificate requirements are repeated across several runtime files and attestations.

I would make those ownership boundaries more explicit:

| Material | Canonical responsibility |
| ---------------------------------------- | ----------------------------------------------------------------------------- |
| `SKILL.md` | The claim, mandatory gates, workflow, routing, and verdict definitions |
| `proof-obligations.md` | General derivation, applicability, local composition, and valid-use mechanics |
| Configuration, API, and trust references | Domain-specific discovery and application of those mechanics |
| Reporting reference and templates | Recording and exposing the evidence the rules require |

For example, the configuration reference should retain its treatment of controlling support policies, ordered build effects, early exits, generated output, and cache freshness. Those details tell the agent what to inspect. It need not independently restate the entire general proof calculus to explain why a later-stage fact cannot support an earlier exit.

Similarly, a template should prompt for a certificate or link to its canonical record rather than become another place where that certificate’s meaning is defined. It must still expose enough information for review; replacing substantive fields with a checkbox marked “proof complete” would be a regression.

I would **not** aggressively deduplicate every warning. A short reminder at the point of use may help an agent more than a distant general rule. The proposed distinction is between a canonical definition and a deliberate reminder—not between permitted and forbidden repetition.

### Whole-model comparison

Keeping the current structure and making only the targeted corrections is the lowest-risk candidate. It retains the current maintenance cost.

Replacing most of the skill with a generic proof calculus would create a smaller artifact, but would push discovery work onto the agent. The agent would have to independently remember hidden safe APIs, caller-controlled trait behavior, build-stage failures, and other relevant surfaces. That shifts complexity rather than removing it. A closed list of Rust hazards would also be inappropriate: the Reference itself cautions that its undefined-behavior list is not exhaustive. ([[Rust Documentation](https://doc.rust-lang.org/reference/behavior-considered-undefined.html)][1])

**The candidate worth evaluating is one shared proof discipline plus explicit domain-specific discovery prompts.** It retains the existing file structure and ordinary proof records. It needs neither another proof-state machine nor a runtime dependency on `model-refinement`.

## How I would validate and stage the changes

The maintainer guidance already calls for semantic evaluations, fresh agents, isolated expected answers, and traceability from operational decisions to scenarios. It also distinguishes operational changes from foundational changes. These proposals should use that structure rather than add another evaluation framework.

I would test paired cases that distinguish the proposed rules from plausible over-simplifications:

| Boundary being changed | Paired evaluation |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Abstraction admission | A useful single-consumer unification versus genericity that merely relocates special cases |
| Non-vacuity | A proved unreachable operation versus a harness that silently excludes required inputs |
| Obligation-indexed coverage | Complete proofs for every required pair versus different obligations proved in different regions |
| Projection | Universal coverage of omitted dimensions versus one convenient input for each configuration |
| Contract substitution | A change that preserves both implementers and consumers versus one that benefits one side while invalidating the other |
| Canonical rule ownership | The same substantive audit under the original and reorganized package, checking both missed obligations and unsupported conclusions |

The larger reorganization must also retain the existing distinctions around audited versus witness-supplied unsafe obligations, whole-execution UB freedom for postcondition refutations, accepted versus pending trust, material reconstructed proofs, and current findings versus proposed redesigns. Those are already explicit parts of the skill’s design scenarios and reporting contract.

I would judge the results on more than final verdicts. A candidate that says `UNPROVED` to everything avoids some false acceptances but fails the task. A candidate that reaches the correct verdict while omitting a material derivation also fails the skill’s existing objective. Compare obligation discovery, legitimate proof completion, certificate quality, trust accuracy, reviewability, and the effort required to obtain those results.

The examples used to develop these proposals are **design witnesses**, not held-out confirmation cases. Fresh confirmation cases should be reserved before evaluating the revised package.

**My recommendation is to start with the abstraction-admission and non-vacuity changes, then evaluate the coverage, compatibility, and rule-ownership reorganization as a separate candidate.** The first two address specific restrictions or ambiguities. The larger refactor has a defensible mathematical basis, but its value depends on whether agents preserve the current checks with less reading and maintenance overhead—not on how many words disappear.

[1]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html "Behavior considered undefined - The Rust Reference"

Contributor guide

Open the contributing guide

Research direction

Start with skills/unsafe-rust/SKILL.md and the named references, especially references/abstraction-design.md and references/tcb-and-evidence.md, then inspect both audit report templates. Compare repeated proof, coverage, and contract-evolution rules, while preserving the existing distinctions and maintainer guidance. Done means the proposed simplifications are consistently reflected across the cited documents without changing the skill’s support promise.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
documentation
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.