Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
63b304d
cvlr: resolve a Cargo project for verification
ericeil Sep 21, 2026
e6af50c
cvlr: import dataclass directly, as the rest of the repo does
ericeil Sep 22, 2026
e323b0e
comments: describe the CVLR preflight code as it is
ericeil Sep 22, 2026
76ebc6a
Simplify comment
ericeil Sep 22, 2026
2027c18
cvlr: strengthen weakly typed values in the preflight code
ericeil Sep 22, 2026
c6116d3
Update comment
ericeil Sep 22, 2026
420d2eb
Update comments
ericeil Sep 22, 2026
e3c02d5
Comment
ericeil Sep 22, 2026
ba9db2d
prover: share conf infrastructure between CVL and Solana
ericeil Sep 22, 2026
4a6c37b
cvlr: rename munge to forks
ericeil Sep 22, 2026
cab3b22
Comments
ericeil Sep 22, 2026
b52a7b1
cvlr: scaffold only the harness files the backend uses
ericeil Sep 22, 2026
5bb67a3
cvlr: own the starting tuning files, and regenerate the composites
ericeil Sep 22, 2026
dec90f6
cvlr: overwrite the harness files, and drop the package tuning layer
ericeil Sep 22, 2026
885fb96
cvlr: build every Solana conf, and stop reading the project's
ericeil Sep 22, 2026
2993ad6
cvlr: drop the nonlinear solver portfolio, and fold the prover args i…
ericeil Sep 22, 2026
ec36b5a
cvlr: explain the Anchor and fixed forks for a reader new to Anchor
ericeil Sep 23, 2026
a396088
cvlr: pin one CVLR line, and refuse a project on another
ericeil Sep 24, 2026
3a5bf37
cvlr: describe the reference set without the corpus that reads it
ericeil Sep 24, 2026
5d67ecc
prover: make applying a rule selection a method of the selection
ericeil Sep 28, 2026
88e5a2f
cvlr: drop the empty Anchor layer from the summaries family
ericeil Sep 28, 2026
50bd97b
Revert accidental removal
ericeil Sep 28, 2026
1146751
cvlr: rename ProverSettings to TunableConf
ericeil Sep 28, 2026
f668d2f
cvlr: rename PlatformGeneration.crates to sdk_crates
ericeil Sep 28, 2026
0b8031b
cvlr: say what no-entrypoint is actually for
ericeil Sep 28, 2026
d04c3f1
cargo: validate cargo metadata and Cargo.toml with pydantic
ericeil Sep 28, 2026
79ec91c
cargo: parse git sources instead of splitting their spelling
ericeil Sep 28, 2026
92d54c4
cargo: report every resolved copy of a crate, not the first
ericeil Sep 28, 2026
0411609
cargo: drop read_workspace_sync
ericeil Sep 28, 2026
96a0edd
cargo: make read_workspace a classmethod, Workspace.read
ericeil Sep 28, 2026
0b59680
cvlr: drop the sbf log level from the base conf
ericeil Sep 28, 2026
1ded0fb
cvlr: trim CvlrSources.roots's docstring to what it returns
ericeil Sep 28, 2026
bdeeee6
cvlr: make resolve a classmethod, CvlrSources.of
ericeil Sep 28, 2026
b436bb4
cvlr: dedupe tuning spellings with dict.fromkeys
ericeil Sep 28, 2026
b0c4efe
cvlr: trim ForkOverride's docstring to what its fields mean
ericeil Sep 28, 2026
ec51c60
cvlr: shorten the Anchor fork's explanation in the manifest
ericeil Sep 28, 2026
1830109
cvlr: shorten the fixed fork's explanation in the manifest
ericeil Sep 28, 2026
3b1a9ef
cvlr: prepare_workspace takes the chain's reference set, required
ericeil Sep 28, 2026
521279d
cvlr: document CvlrPreflight.applied; plain-language cargo failure
ericeil Sep 28, 2026
fbb32bf
cargo: say why cargo metadata failed instead of returning None
ericeil Sep 29, 2026
8068202
cargo: type cargo feature names as CargoFeature
ericeil Sep 29, 2026
74d7826
cvlr: edit manifests as TOML documents, not text
ericeil Sep 29, 2026
6d8d810
cvlr: scaffold docstrings; workspace-pin note no longer claims a gate…
ericeil Sep 29, 2026
3d94b47
cvlr: scaffold planning steps record into one plan builder
ericeil Sep 29, 2026
96454a8
cvlr: refuse a library source that does not exist instead of creating it
ericeil Sep 29, 2026
57dbd18
cvlr: a refused fork plan is its own type
ericeil Sep 29, 2026
fd4ba89
cvlr: composite env files describe themselves in the target's terms
ericeil Sep 29, 2026
bc3cb5e
cvlr: ChainReference.chain is chain_crate
ericeil Sep 29, 2026
e5cd47d
cvlr: move the reference set into the cvlr package
ericeil Sep 29, 2026
9a159d6
cvlr: each fork names what verifying without it fails on
ericeil Sep 29, 2026
5c629cd
cvlr: an uncovered-version refusal names the crate it refuses
ericeil Sep 29, 2026
6aca60e
cvlr: a chain's reference set names its forks
ericeil Sep 29, 2026
284c914
cvlr: line refusals say what to change in the project, not in AutoProver
ericeil Sep 29, 2026
f588f87
cvlr: the platform refusal states the version gap and the fix
ericeil Sep 29, 2026
5f5ea74
cvlr: every refusal states the problem and the fix, nothing else
ericeil Sep 29, 2026
fe38d36
cargo: declare [package.metadata.certora] instead of a dict[str, object]
ericeil Sep 29, 2026
99ca3da
cvlr: the workspace-pin note does not name _check_pins
ericeil Sep 29, 2026
7394d61
cvlr: a unit layer passed to compose_env is appended as given
ericeil Sep 29, 2026
051d471
cvlr: two copies of one CVLR version sort the same on every run
ericeil Sep 29, 2026
86e8ab5
cvlr: build the certora feature in one place; hash the conf with stri…
ericeil Oct 1, 2026
37280d6
Merge origin/master into eric/cvlr-preflight
ericeil Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer/cargo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tools for interacting with Cargo builds and workspaces."""
9 changes: 9 additions & 0 deletions composer/cargo/features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""The name of a cargo feature."""

from typing import TYPE_CHECKING

# ``CargoFeature``: a feature's name, as ``[features]`` declares it and ``--features`` takes it.
if TYPE_CHECKING:
class CargoFeature(str): ...
else:
CargoFeature = str
244 changes: 244 additions & 0 deletions composer/cargo/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
"""Reading and editing a ``Cargo.toml``.

Only the tables the CVLR scaffold consults are declared. Everything else in a manifest is
accepted and ignored, so a manifest cargo takes is not refused here for a table this module
never reads.

Parsing is ``tomlkit``'s for reading and editing alike, so what is validated is what is edited.
:class:`ManifestEditor` keeps everything it does not change as it was, comments included.
"""

from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path

import tomlkit
from pydantic import BaseModel, ConfigDict, ValidationError, model_validator
from tomlkit.exceptions import TOMLKitError
from tomlkit.items import InlineTable, Item, Table

from composer.cargo.features import CargoFeature


class MalformedManifest(RuntimeError):
"""A ``Cargo.toml`` could not be read, parsed, or does not have cargo's shape."""


class _ManifestModel(BaseModel):
model_config = ConfigDict(frozen=True, extra="ignore")


class Dependency(_ManifestModel):
"""One dependency entry, from ``[dependencies]``, ``[workspace.dependencies]``, or a patch
table.

The fields are not exclusive: cargo allows ``version`` beside ``path`` or ``git``, where the
version is what a publish of the dependent uses.
"""

version: str | None = None
git: str | None = None
path: str | None = None
#: ``workspace = true``: the entry inherits the root's ``[workspace.dependencies]`` one.
workspace: bool = False

@model_validator(mode="before")
@classmethod
def _bare_requirement(cls, data: object) -> object:
"""``foo = "1.0"`` is cargo's shorthand for ``foo = { version = "1.0" }``."""
return {"version": data} if isinstance(data, str) else data


class CertoraMetadata(_ManifestModel):
"""``[package.metadata.certora]``, the prover's build settings. Its keys are not read here."""


class PackageMetadata(_ManifestModel):
"""``[package.metadata]``. Free-form: cargo passes it to tools without reading it, so only the
tool tables something here consults are declared."""

certora: CertoraMetadata | None = None


class PackageTable(_ManifestModel):
metadata: PackageMetadata = PackageMetadata()


class WorkspaceTable(_ManifestModel):
dependencies: dict[str, Dependency] = {}


class Manifest(_ManifestModel):
#: ``None`` for a virtual workspace manifest.
package: PackageTable | None = None
#: ``None`` when the manifest has no ``[workspace]``. An empty ``[workspace]`` still makes
#: the manifest a workspace root.
workspace: WorkspaceTable | None = None
dependencies: dict[str, Dependency] = {}
features: dict[CargoFeature, list[str]] = {}
#: Keyed by the source being patched (``crates-io``, or a registry or git URL), then by crate.
patch: dict[str, dict[str, Dependency]] = {}

@property
def workspace_dependencies(self) -> dict[str, Dependency]:
return self.workspace.dependencies if self.workspace is not None else {}

@property
def certora_metadata(self) -> CertoraMetadata | None:
return self.package.metadata.certora if self.package is not None else None


_UNPARSEABLE = (TOMLKitError, ValidationError)


def _parsed(text: str) -> tuple[tomlkit.TOMLDocument, Manifest]:
document = tomlkit.parse(text)
return document, Manifest.model_validate(document.unwrap())


def parse_manifest(text: str) -> Manifest:
return ManifestEditor(text).manifest


def read_manifest(path: Path) -> Manifest:
return ManifestEditor.read(path).manifest


#: A table's name, one key per level: ``("package", "metadata", "certora")``.
type TablePath = tuple[str, ...]

#: A value :class:`ManifestEditor` writes. A mapping is written as an inline table.
type TomlValue = str | bool | Sequence[str] | Mapping[str, str | bool]


@dataclass(frozen=True)
class Comment:
"""A comment line inside a table :class:`AddTable` creates."""

text: str


type TableItem = tuple[str, TomlValue] | Comment


@dataclass(frozen=True)
class AddEntries:
"""Keys added to ``table``, which is created when the manifest has none."""

table: TablePath
entries: tuple[tuple[str, TomlValue], ...]

def describe(self) -> str:
return f"[{'.'.join(self.table)}] {', '.join(key for key, _ in self.entries)}"


@dataclass(frozen=True)
class AddTable:
"""A table the manifest does not have yet, created with ``body`` in order. The tables above
it are created as needed, without headers of their own."""

table: TablePath
body: tuple[TableItem, ...]

def describe(self) -> str:
return f"[{'.'.join(self.table)}]"


type ManifestAddition = AddEntries | AddTable


class ManifestConflict(ValueError):
"""An addition collides with what the manifest already has."""


class ManifestEditor:
"""One ``Cargo.toml``, parsed once, to read and to add to.

Edits only add. A new table is built whole before it is placed, which is what keeps its keys
together and its spacing like the tables around it.
"""

def __init__(self, text: str, *, origin: str = "Cargo.toml") -> None:
"""``origin`` names the file in a :class:`MalformedManifest`."""
try:
self._document, self.manifest = _parsed(text)
except _UNPARSEABLE as exc:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this works????? Holy heck! Wow TIL.

raise MalformedManifest(f"{origin}: {exc}") from exc
self._original = text

@classmethod
def read(cls, path: Path) -> "ManifestEditor":
try:
text = path.read_text()
except OSError as exc:
raise MalformedManifest(f"{path}: {exc}") from exc
return cls(text, origin=str(path))

def apply(self, addition: ManifestAddition, *, note: str) -> None:
"""Make ``addition``. ``note`` is a comment on each added line, or once on a new table's
header. Raises :class:`ManifestConflict` when a key or table it adds is already there."""
match addition:
case AddEntries(table=table, entries=entries):
existing = self._find(table)
if existing is None:
self._add_table(table, entries, note=note)
else:
for key, value in entries:
_insert(existing, table, key, value, note=note)
case AddTable(table=table, body=body):
self._add_table(table, body, note=note)

def text(self) -> str:
"""The manifest with the edits, ending in as many newlines as it did."""
edited = tomlkit.dumps(self._document)
ending = len(self._original) - len(self._original.rstrip("\n"))
return edited.rstrip("\n") + "\n" * ending

def _add_table(self, table: TablePath, body: Sequence[TableItem], *, note: str) -> None:
if self._find(table) is not None:
raise ManifestConflict(f"[{'.'.join(table)}] already exists")
parent: tomlkit.TOMLDocument | Table = self._document
for name in table[:-1]:
if name not in parent:
parent.add(name, tomlkit.table(is_super_table=True))
found = parent[name]
if not isinstance(found, Table):
raise ManifestConflict(f"{name} in [{'.'.join(table)}] is not a table")
parent = found
created = tomlkit.table()
created.comment(note)
for item in body:
match item:
case Comment(text=text):
created.add(tomlkit.comment(text))
case (key, value):
created.add(key, _item(value))
created.add(tomlkit.nl())
parent.add(table[-1], created)

def _find(self, table: TablePath) -> Table | InlineTable | None:
container: object = self._document
for name in table:
if not isinstance(container, tomlkit.TOMLDocument | Table | InlineTable):
return None
if name not in container:
return None
container = container[name]
return container if isinstance(container, Table | InlineTable) else None


def _insert(
container: Table | InlineTable, table: TablePath, key: str, value: TomlValue, *, note: str
) -> None:
if key in container:
raise ManifestConflict(f"[{'.'.join(table)}] already has {key}")
container[key] = _item(value)
container[key].comment(note)


def _item(value: TomlValue) -> Item:
if isinstance(value, Mapping):
inline = tomlkit.inline_table()
inline.update(value)
return inline
return tomlkit.item(value)
Loading
Loading