Skip to content
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fe84bae
PR1: ecosystem abstraction (EVM + Solana front-half)
ericeil Jul 23, 2026
93320ca
pipeline: type PreparedSystem.main via a Main generic instead of Any
ericeil Jul 23, 2026
6815140
pipeline: type _batch_cache_key via a result-type witness (drop Any)
ericeil Jul 23, 2026
71a0773
pipeline: document why run_pipeline's ecosystem type is erased to Any
ericeil Jul 23, 2026
37302e4
ecosystem: drop the dead per-property fan-out (property_unit)
ericeil Jul 23, 2026
4e0a154
ecosystem: select extraction mode by callable, drop the redundant boo…
ericeil Jul 23, 2026
0f441b2
ecosystem: unify extraction into one units() path (drop extraction_unit)
ericeil Jul 23, 2026
5c61dbd
Remove unneeded comment
ericeil Jul 23, 2026
7d36604
Comment tweaks
ericeil Jul 23, 2026
29c2a9f
Comment tweaks
ericeil Jul 23, 2026
530ed48
tweaks
ericeil Jul 23, 2026
a259e01
system_model: drop @runtime_checkable, strengthen unit tag/json dicts
ericeil Jul 23, 2026
a2058eb
docs: bring ARCHITECTURE.md up to date with the ecosystem seam
ericeil Jul 23, 2026
6add7a1
docs/ecosystem: rewrite as description-of-what-is; fix misplaced Rust…
ericeil Jul 23, 2026
f15c8ae
docs: move application-abstraction.md out of the ecosystem PR
ericeil Jul 23, 2026
60b2411
tests: golden snapshots of front-half prompt templates
ericeil Jul 23, 2026
24cd987
templates: factor shared prompt boilerplate into shared/ partials
ericeil Jul 23, 2026
97dc97f
tests: remove prompt-snapshot golden test
ericeil Jul 24, 2026
350b4f2
tests: unit-test the null Solana backend
ericeil Jul 24, 2026
34924e9
feat(solana): make the unit an abstract component, mirroring EVM
ericeil Jul 30, 2026
9868035
pipeline: drop main_instance re-export, type ecosystem to the backend
ericeil Jul 30, 2026
377507b
ecosystem: type prompt templates as TypedTemplate instead of bare fil…
ericeil Jul 30, 2026
227ba69
ecosystem: type validate_analysis over App, unify Solana validate on …
ericeil Jul 30, 2026
c0e6641
ecosystem: rename failure_modes partials to vulnerability_patterns
ericeil Jul 30, 2026
bc2301c
ecosystem: drop greenfield variants from the Solana/Rust templates
ericeil Jul 31, 2026
b93fcd7
ecosystem: share one input_phrase macro across the analysis prompts
ericeil Jul 31, 2026
d285112
solana: make sibling_programs a property instead of a Jinja accumulator
ericeil Jul 31, 2026
6f557a7
solana: fix the null backend's report tag, retarget stale doc references
ericeil Jul 31, 2026
faf8ac6
solana: type _solana_validate over SolanaApplication, drop the stale …
ericeil Jul 31, 2026
03d9a91
pipeline: drop the result_type witness from _batch_cache_key
ericeil Jul 31, 2026
2976e93
spec: split SourceIdentifier out of SolidityIdentifier
ericeil Jul 31, 2026
9c075b5
solana: type program_identifier as a new RustIdentifier
ericeil Jul 31, 2026
ff55fb9
solana: name the conceptual-name axis with ProgramName
ericeil Jul 31, 2026
61222da
spec: trim the identity-type comments back to facts
ericeil Jul 31, 2026
8ac4aab
solana: type the null backend to SolanaComponentInstance, not Feature…
ericeil Jul 31, 2026
826397d
spec: require the domain-specific analysis and property inputs
ericeil Jul 31, 2026
5f2ff2a
pipeline: pass the ecosystem through Continuation instead of casting
ericeil Jul 31, 2026
38e03e3
pipeline: take Sequence in Formalizer.begin, dropping the cast
ericeil Jul 31, 2026
7851f12
spec: drop @runtime_checkable from FeatureUnit
ericeil Jul 31, 2026
dd47bd0
pipeline: make the shared-artifact step a type, not a hook
ericeil Jul 31, 2026
eed9f34
Add testing notes to CLAUDE.md
ericeil Jul 31, 2026
1496051
Update pytest command comment in CLAUDE.md
ericeil Jul 31, 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
317 changes: 317 additions & 0 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Repository conventions for coding agents

Guidance for AI coding agents (and humans) working in this repo. Keep changes consistent
with these unless a maintainer says otherwise.

## Python

### Do NOT use `from __future__ import annotations`

Never add `from __future__ import annotations` to a module. If you touch a file that has it
and it's reasonable to do so, remove it.

Why:
- It's a dead-end feature. PEP 563 (the string-annotations behaviour this import enables) was
never made the default and has effectively been superseded by PEP 649 / PEP 749 (lazy
evaluation) from Python 3.14 on. Relying on the `__future__` behaviour is betting on a path
the language is moving away from — it will change/break under you in future versions.
- It doesn't actually solve the problem it's reached for. Stringizing *all* annotations breaks
anything that introspects them at runtime — pydantic, dataclasses, `typing.get_type_hints`,
and our own annotation-driven graph wiring (see `composer/rustapp/_llm_agent.py`, which had
to stay eager precisely because stringized `NotRequired[T]` broke pydantic unwrapping). It
trades one set of problems for a subtler set.

What to do instead (we target Python 3.12+):
- Modern syntax works at runtime without the future import: `X | None`, `list[str]`,
`dict[str, int]`, `tuple[int, ...]`, PEP 695 generics (`class Foo[T]:` / `def f[T]()`).
- For a genuine forward reference (a name not yet defined where the annotation is evaluated —
e.g. a dataclass field typed as a class defined later in the file), quote just that one
annotation: `backend: "RustBackend"`. Quote the specific ref; don't stringize the whole module.
13 changes: 7 additions & 6 deletions composer/foundry/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@
from composer.pipeline.core import (
Formalizer, PreparedSystem, PipelineRun,
GaveUp, SystemAnalysisSpec,
CorePhases, main_instance, CorePipelineResult,
CorePhases, CorePipelineResult,
COMMON_SYSTEM_CACHE_KEY
)
from composer.pipeline.ecosystem import main_instance
from composer.foundry.artifacts import FoundryTestArtifact
from composer.spec.source.report.collect import ReportComponentInput, Verdict
from composer.spec.context import (
Expand Down Expand Up @@ -78,7 +79,7 @@
system (a uint256 being non-negative, etc.) are also uninteresting.
"""
from composer.spec.system_model import (
ContractComponentInstance, SourceApplication,
ContractComponentInstance, ContractInstance, SourceApplication,
)

from composer.io.multi_job import HandlerFactory
Expand Down Expand Up @@ -106,7 +107,7 @@ class FoundryPhase(enum.Enum):
TEST_GENERATION = "test_generation"
REPORT = "report"

class FoundryFormalizer(Formalizer[GeneratedFoundryTest]):
class FoundryFormalizer(Formalizer[GeneratedFoundryTest, ContractComponentInstance]):
def __init__(self, conf: _ForgeRunConfig):
super().__init__(GeneratedFoundryTest, "foundry")
self.conf = conf
Expand Down Expand Up @@ -138,11 +139,11 @@ async def fetch_verdicts(self, inp: ReportComponentInput[GeneratedFoundryTest])
return await _foundry_verdicts(inp)

@dataclass
class FoundrySystem(PreparedSystem[GeneratedFoundryTest]):
class FoundrySystem(PreparedSystem[GeneratedFoundryTest, ContractComponentInstance, ContractInstance]):
form: FoundryFormalizer

@override
async def prepare_formalization(self, run: PipelineRun) -> Formalizer[GeneratedFoundryTest]:
async def prepare_formalization(self, run: PipelineRun) -> Formalizer[GeneratedFoundryTest, ContractComponentInstance]:
return self.form

@dataclass
Expand All @@ -166,7 +167,7 @@ async def prepare_system(
self,
analyzed: SourceApplication,
run: PipelineRun[FoundryPhase, None]
) -> PreparedSystem[GeneratedFoundryTest]:
) -> PreparedSystem[GeneratedFoundryTest, ContractComponentInstance, ContractInstance]:
return FoundrySystem(
main_instance(
analyzed, run.source
Expand Down
23 changes: 15 additions & 8 deletions composer/pipeline/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Protocol, AsyncIterator, TYPE_CHECKING
from typing import Protocol, AsyncIterator, TYPE_CHECKING, cast
import sys
import pathlib
import enum
Expand All @@ -20,8 +20,10 @@
CorePipelineResult
)
from composer.spec.artifacts import ArtifactIdentifier
from composer.spec.system_model import FeatureUnit, BaseApplication
from composer.spec.service_host import ModelProvider
from composer.spec.system_analysis import SolidityIdentifier
from composer.spec.types import SourceIdentifier
from composer.pipeline.ecosystem import Ecosystem, EVM
from .core import PipelineBackend, run_pipeline
from composer.io.multi_job import HandlerFactory, run_task, TaskInfo
from composer.diagnostics.timing import RunSummary, install_run_summary
Expand Down Expand Up @@ -113,10 +115,10 @@ class StagedPipeline:
root_key: str

class Continuation[P: enum.Enum, H](Protocol):
async def __call__[FormT: BackendResult, A: ArtifactIdentifier](
async def __call__[FormT: BackendResult, A: ArtifactIdentifier, U: FeatureUnit, Main, App: BaseApplication](
self,
env: ServiceHost,
backend: PipelineBackend[P, FormT, H, A]
backend: PipelineBackend[P, FormT, H, A, U, Main, App]
) -> CorePipelineResult[FormT]:
...

Expand Down Expand Up @@ -166,7 +168,7 @@ async def cli_pipeline[P: enum.Enum, H](

init_source = SourceFields(
relative_path=relative_path,
contract_name=SolidityIdentifier(contract_name),
contract_name=SourceIdentifier(contract_name),
forbidden_read=FS_FORBIDDEN_READ,
project_root=str(project_root)
)
Expand Down Expand Up @@ -249,9 +251,9 @@ async def cli_pipeline[P: enum.Enum, H](
relative_path=init_source.relative_path
)

async def cont[FormT: BackendResult, A: ArtifactIdentifier](
async def cont[FormT: BackendResult, A: ArtifactIdentifier, U: FeatureUnit, Main, App: BaseApplication](
env: ServiceHost,
backend: PipelineBackend[P, FormT, H, A]
backend: PipelineBackend[P, FormT, H, A, U, Main, App]
) -> CorePipelineResult[FormT]:
full_ctx = WorkflowContext.create(
services=conns.memory,
Expand All @@ -273,7 +275,12 @@ async def cont[FormT: BackendResult, A: ArtifactIdentifier](
run=run,
interactive=args.interactive,
max_bug_rounds=args.max_bug_rounds,
threat_model=threat_model
threat_model=threat_model,
# cli_pipeline is the EVM CLI entry point (Solidity identifiers throughout
# above); App/Main/U are only generic here to admit FoundryBackend and
# ProverBackend (same EVM triple, different FormT/A). Downcast to the
# generic ecosystem parameter rather than widening run_pipeline back to Any.
ecosystem=cast(Ecosystem[App, Main, U], EVM),
)
...

Expand Down
Loading