Skip to content

Commit 495602e

Browse files
authored
Merge pull request conforma#3322 from joejstuart/EC-1784
chore(EC-1851): trim AGENTS.md and add design docs per agentic SDLC best practices
2 parents 1026da7 + 68a5bf8 commit 495602e

3 files changed

Lines changed: 127 additions & 232 deletions

File tree

AGENTS.md

Lines changed: 64 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -1,243 +1,75 @@
1-
# Enterprise Contract CLI - Agent Instructions
1+
# Conforma CLI
22

3-
## Project Overview
3+
Go CLI for verifying software supply chain artifacts — validates container image signatures,
4+
provenance, and evaluates OPA/Rego policies. Built with `CGO_ENABLED=0`.
45

5-
The `ec` (Enterprise Contract) CLI is a command-line tool for verifying artifacts and evaluating software supply chain policies. It validates container image signatures, provenance, and enforces policies across various types of software artifacts using Open Policy Agent (OPA)/Rego rules.
6+
## Build & Test
67

7-
## Essential Commands
8-
9-
### Building
108
```bash
11-
make build # Build ec binary for current platform (creates dist/ec)
12-
make dist # Build for all supported architectures
13-
make clean # Remove build artifacts
14-
DEBUG_BUILD=1 make build # Build with debugging symbols for gdb/dlv
15-
make debug-run # Run binary with delve debugger (requires debug build)
9+
make build # Build for current platform dist/ec_<os>_<arch>
10+
make test # Run unit + integration + generative tests
11+
make lint # golangci-lint + addlicense + tekton-lint (0 warnings enforced)
12+
make lint-fix # Auto-fix lint issues
13+
make ci # Full CI: test + lint-fix + acceptance
1614
```
1715

18-
### Testing
16+
### Acceptance Tests
17+
1918
```bash
20-
make test # Run all tests (unit, integration, generative)
21-
make acceptance # Run acceptance tests (Cucumber/Gherkin, 20m timeout)
22-
make scenario_<name> # Run single acceptance scenario (replace spaces with underscores)
23-
make feature_<name> # Run all scenarios in a single feature file
24-
25-
# Running specific tests
26-
go test -tags=unit ./internal/evaluator -run TestSpecificFunction
27-
cd acceptance && go test -test.run 'TestFeatures/scenario_name'
19+
make acceptance # Run all (Cucumber/Gherkin via Godog, 20m timeout)
20+
make scenario_<name> # Single scenario (replace spaces with underscores)
21+
make feature_<name> # All scenarios in a feature file
2822
```
2923

30-
### Code Quality
31-
```bash
32-
make lint # Run all linters (golangci-lint, addlicense, tekton-lint)
33-
make lint-fix # Auto-fix linting issues
34-
make ci # Run full CI suite (test + lint-fix + acceptance)
24+
Flags: `-persist` keeps test env for debugging, `-restore` reruns against persisted env,
25+
`-tags=@focus` runs tagged scenarios. Update snapshots: `UPDATE_SNAPS=true make acceptance`.
26+
27+
See `acceptance/README.md` for Testcontainers setup, WireMock stubbing, and snapshot testing details.
28+
29+
**macOS:** Acceptance tests require a Podman machine. Run `./hack/macos/setup-podman-machine.sh`
30+
once for automated setup (creates machine with 4 CPUs, 8GB RAM, configures DNS and networks),
31+
then `./hack/macos/run-acceptance-tests.sh` to run tests. See `hack/macos/README.md` for options
32+
and `hack/macos/TROUBLESHOOTING.md` for detailed debugging.
33+
34+
### Test Tags
35+
36+
Tests use build tags with different timeouts:
37+
- `unit` (10s), `integration` (15s), `generative` (30s), `acceptance` (20m)
38+
- Run specific: `go test -tags=unit ./internal/evaluator -run TestName`
39+
40+
## Key Conventions
41+
42+
- **Multi-module project:** root, `acceptance/`, `tools/` each have their own go.mod.
43+
Run `go mod tidy` in the right module.
44+
- **Debug mode:** `--debug` or `EC_DEBUG=1` preserves `ec-work-*` temp directories for inspection.
45+
- Conventional commits with Jira key encouraged (e.g., `feat(EC-1234): description`).
46+
47+
## CGO and DNS Resolution
48+
49+
Binaries are built with `CGO_ENABLED=0` for portability. This uses Go's native DNS resolver,
50+
which **cannot resolve second-level localhost domains** (e.g., `apiserver.localhost`).
51+
Acceptance tests require `/etc/hosts` entries:
52+
3553
```
54+
127.0.0.1 apiserver.localhost
55+
127.0.0.1 rekor.localhost
56+
```
57+
58+
## Design Documents
59+
60+
Read these before modifying the corresponding areas:
61+
62+
- [internal/evaluator/DESIGN.md](internal/evaluator/DESIGN.md) — rule filtering: why two resolvers, two-pass design, scoring precedence, adding filters
63+
- [internal/validate/vsa/DESIGN.md](internal/validate/vsa/DESIGN.md) — VSA: storage backends, DSSE signing rationale, expiration model
64+
- [acceptance/README.md](acceptance/README.md) — acceptance test framework, Testcontainers, WireMock, snapshot testing
65+
66+
## Troubleshooting
67+
68+
System-level issues that surface in acceptance tests:
3669

37-
## Architecture
38-
39-
### Command Structure
40-
Main commands in `cmd/`:
41-
- **validate** - Validate container images, attestations, and policies
42-
- **test** - Test policies against data (similar to conftest)
43-
- **fetch** - Download and inspect attestations
44-
- **inspect** - Examine policy bundles and data
45-
- **track** - Track compliance status
46-
- **sigstore** - Sigstore-related operations
47-
- **initialize** - Initialize policy configurations
48-
49-
### Core Components
50-
51-
#### Policy Evaluation (`internal/evaluator/`)
52-
- **Conftest Evaluator**: Main evaluation engine using OPA/Rego
53-
- **Pluggable Rule Filtering**: Extensible system for filtering which rules run based on:
54-
- Pipeline intentions (build vs release vs production)
55-
- Include/exclude lists (collections, packages, specific rules)
56-
- Custom metadata criteria
57-
- **Result Processing**: Complex rule result filtering with scoring, severity promotion/demotion, and effective time handling
58-
59-
**Key Implementation Details:**
60-
- PolicyResolver interface provides comprehensive policy resolution for pre and post-evaluation filtering
61-
- UnifiedPostEvaluationFilter implements unified filtering logic
62-
- Sophisticated scoring system for include/exclude decisions (collections: 10pts, packages: 10pts per level, rules: +100pts, terms: +100pts)
63-
- Term-based filtering allows fine-grained control (e.g., `tasks.required_untrusted_task_found:clamav-scan`)
64-
- See `.cursor/rules/rule_filtering_process.mdc` and `.cursor/rules/package_filtering_process.mdc` for detailed documentation
65-
66-
#### Attestation Handling (`internal/attestation/`)
67-
- Parsing and validation of in-toto attestations
68-
- SLSA provenance processing (supports both v0.2 and v1.0)
69-
- Integration with Sigstore for signature verification
70-
71-
#### VSA (Verification Summary Attestation) (`internal/validate/vsa/`)
72-
VSA creates cryptographically signed attestations containing validation metadata and policy information after successful image validation.
73-
74-
**Layered Architecture:**
75-
1. Core Interfaces (`interfaces.go`) - Fundamental VSA interfaces
76-
2. Service Layer (`service.go`) - High-level VSA processing orchestration
77-
3. Core Logic (`vsa.go`) - VSA data structures and predicate generation
78-
4. Attestation (`attest.go`) - DSSE envelope creation and signing
79-
5. Storage (`storage*.go`) - Abstract storage backends (local, Rekor)
80-
6. Retrieval (`*_retriever.go`) - VSA retrieval mechanisms
81-
7. Orchestration (`orchestrator.go`) - Complex VSA processing workflows
82-
8. Validation (`validator.go`) - VSA validation with policy comparison
83-
9. Command Interface (`cmd/validate/vsa.go`) - CLI for VSA validation
84-
85-
**Key Features:**
86-
- Policy comparison and equivalence checking
87-
- DSSE envelope signature verification (enabled by default)
88-
- Multiple storage backends (local filesystem, Rekor transparency log)
89-
- VSA expiration checking with configurable thresholds
90-
- Batch validation from application snapshots with parallel processing
91-
92-
See `.cursor/rules/vsa_functionality.mdc` for comprehensive documentation.
93-
94-
#### Input Processing (`internal/input/`)
95-
- Multiple input sources: container images, files, Kubernetes resources
96-
- Automatic detection and parsing of different artifact types
97-
98-
#### Policy Management (`internal/policy/`)
99-
- OCI-based policy bundle loading
100-
- Git repository policy fetching
101-
- Policy metadata extraction and rule discovery
102-
103-
### Key Internal Packages
104-
- `internal/signature/` - Container image signature verification
105-
- `internal/image/` - Container image operations and metadata
106-
- `internal/kubernetes/` - Kubernetes resource processing
107-
- `internal/utils/` - Common utilities and helpers
108-
- `internal/rego/` - Rego policy compilation and execution
109-
- `internal/format/` - Output formatting (JSON, YAML, etc.)
110-
111-
## Module Structure
112-
113-
The project uses multiple Go modules:
114-
- **Root module** - Main CLI application
115-
- **acceptance/** - Acceptance test module with Cucumber integration
116-
- **tools/** - Development tools and utilities
117-
118-
## Testing Strategy
119-
120-
### Test Types
121-
- **Unit tests** (`-tags=unit`, 10s timeout) - Fast isolated tests
122-
- **Integration tests** (`-tags=integration`, 15s timeout) - Component integration
123-
- **Generative tests** (`-tags=generative`, 30s timeout) - Property-based testing
124-
- **Acceptance tests** (20m timeout) - End-to-end Cucumber scenarios with real artifacts
125-
- Use `-persist` flag to keep test environment after execution for debugging
126-
- Use `-restore` to run tests against persisted environment
127-
- Use `-tags=@focus` to run specific scenarios
128-
129-
### Acceptance Test Framework
130-
- Uses Cucumber/Gherkin syntax for feature definitions in `features/` directory
131-
- Steps implemented in Go using Godog framework
132-
- Self-contained test environment using Testcontainers
133-
- WireMock for stubbing HTTP APIs (Kubernetes apiserver, Rekor)
134-
- Snapshots stored in `features/__snapshots__/` (update with `UPDATE_SNAPS=true`)
135-
136-
## Development Environment
137-
138-
### Required Tools
139-
- Go 1.24.4+
140-
- Make
141-
- Podman/Docker for container operations
142-
- Node.js for tekton-lint
143-
144-
### Troubleshooting Common Issues
145-
146-
1. **Go checksum mismatch**
147-
```bash
148-
go env -w GOPROXY='https://proxy.golang.org,direct'
149-
```
150-
151-
2. **Container failures** - Ensure podman runs as user service, not system service
152-
```bash
153-
systemctl status podman.socket podman.service
154-
systemctl disable --now podman.socket podman.service
155-
systemctl enable --user --now podman.socket podman.service
156-
```
157-
158-
3. **Too many containers** - Increase inotify watches
159-
```bash
160-
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
161-
```
162-
163-
4. **Key limits** - Increase max keys
164-
```bash
165-
echo kernel.keys.maxkeys=1000 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
166-
```
167-
168-
5. **Host resolution** - Add to `/etc/hosts`:
169-
```
170-
127.0.0.1 apiserver.localhost
171-
127.0.0.1 rekor.localhost
172-
```
173-
174-
## Key Configuration
175-
176-
### Policy Sources
177-
Policies can be loaded from:
178-
- OCI registries: `oci::quay.io/repo/policy:tag`
179-
- Git repositories: `git::https://github.com/repo//path`
180-
- Local files/directories
181-
182-
### Debug Mode
183-
- Use `--debug` flag or `EC_DEBUG=1` environment variable
184-
- Debug mode preserves temporary `ec-work-*` directories for inspection
185-
186-
## Special Considerations
187-
188-
### CGO and DNS Resolution
189-
Binaries are built with `CGO_ENABLED=0` for OS compatibility, which affects DNS resolution. The Go native resolver cannot resolve second-level localhost domains like `apiserver.localhost`, requiring manual `/etc/hosts` entries for acceptance tests.
190-
191-
### Multi-Architecture Support
192-
The build system supports all major platforms and architectures. Use `make dist` to build for all supported targets or `make dist/ec_<os>_<arch>` for specific platforms.
193-
194-
### Policy Rule Filtering System
195-
The evaluation system includes sophisticated rule filtering that operates at multiple levels:
196-
197-
#### Pre-Evaluation Filtering (Package Level)
198-
1. **Pipeline Intention Filtering** (ECPolicyResolver only)
199-
- When `pipeline_intention` is set: only include packages with matching metadata
200-
- When not set: only include general-purpose rules (no pipeline_intention metadata)
201-
202-
2. **Rule-by-Rule Evaluation**
203-
- Each rule is scored against include/exclude criteria
204-
- Scoring system: collections (10pts), packages (10pts/level), rules (+100pts), terms (+100pts)
205-
- Higher score determines inclusion/exclusion
206-
207-
3. **Package-Level Determination**
208-
- If ANY rule in package is included → Package is included
209-
- Package runs through conftest evaluation
210-
211-
#### Post-Evaluation Filtering (Result Level)
212-
- UnifiedPostEvaluationFilter processes all results using same PolicyResolver
213-
- Filters warnings, failures, exceptions, skipped results
214-
- Applies severity logic (promotion/demotion based on metadata)
215-
- Handles effective time filtering (future-effective failures → warnings)
216-
217-
#### Term-Based Filtering
218-
Terms provide fine-grained control over specific rule instances:
219-
- Example: `tasks.required_untrusted_task_found:clamav-scan` (scores 210pts)
220-
- Can override general patterns like `tasks.*` (10pts)
221-
- Terms are extracted from result metadata during filtering
222-
223-
### Working with Rule Filtering Code
224-
When modifying policy evaluation or filtering logic:
225-
1. Read `.cursor/rules/package_filtering_process.mdc` for architecture overview
226-
2. Read `.cursor/rules/rule_filtering_process.mdc` for detailed filtering flow
227-
3. Main filtering code is in `internal/evaluator/filters.go`
228-
4. Integration point is in `internal/evaluator/conftest_evaluator.go`
229-
230-
### Working with VSA Code
231-
When modifying VSA functionality:
232-
1. Read `.cursor/rules/vsa_functionality.mdc` for complete documentation
233-
2. Understand the layered architecture (9 layers from interfaces to CLI)
234-
3. VSA code is in `internal/validate/vsa/` directory
235-
4. CLI implementation in `cmd/validate/vsa.go`
236-
5. Signature verification is enabled by default and implemented via DSSE envelopes
237-
238-
## Additional Documentation
239-
240-
For detailed implementation guides, see:
241-
- `.cursor/rules/package_filtering_process.mdc` - Pluggable rule filtering system
242-
- `.cursor/rules/rule_filtering_process.mdc` - Complete rule filtering process
243-
- `.cursor/rules/vsa_functionality.mdc` - VSA architecture and workflows
70+
| Problem | Fix |
71+
|---------|-----|
72+
| Go checksum mismatch | `go env -w GOPROXY='https://proxy.golang.org,direct'` |
73+
| Podman container failures | Use user service: `systemctl enable --user --now podman.socket` |
74+
| Too many containers (inotify) | `echo fs.inotify.max_user_watches=524288 \| sudo tee -a /etc/sysctl.conf` |
75+
| Key limit errors | `echo kernel.keys.maxkeys=1000 \| sudo tee -a /etc/sysctl.conf` |

internal/evaluator/DESIGN.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Rule Filtering Design
2+
3+
## Why Two PolicyResolvers
4+
5+
`ECPolicyResolver` handles the full Conforma evaluation path including pipeline intention
6+
filtering — rules declare what pipeline type they apply to (build, release, production) via
7+
metadata, and the resolver matches against the configured intention. This is the default for
8+
`ec validate`.
9+
10+
`IncludeExcludePolicyResolver` skips pipeline intention filtering entirely. It exists for
11+
`ec test` (conftest-compatible mode) where pipeline intentions don't apply. Use this resolver
12+
when evaluating policies outside the Conforma pipeline context.
13+
14+
## Filtering Happens Twice
15+
16+
Pre-evaluation filtering selects which *packages* to load into OPA. Post-evaluation filtering
17+
decides which *results* to keep. Both use the same `PolicyResolver` instance so decisions are
18+
consistent. This two-pass design exists because OPA evaluates all rules in a loaded package —
19+
you can't selectively run individual rules within a package, only control which packages load.
20+
21+
## Scoring Determines Precedence
22+
23+
When include and exclude criteria conflict, the more-specific pattern wins via scoring.
24+
The scoring rules are documented in the `LegacyScore` function docstring in `filters.go`.
25+
The key insight: terms (+100pts) and specific rules (+100pts) always override collection-level
26+
(10pts) or wildcard (1pt) patterns. This lets operators exclude a broad category while
27+
including specific exceptions, or vice versa.
28+
29+
## Adding a New Filter
30+
31+
Follow the pattern in `IncludeExcludePolicyResolver`: embed `basePolicyResolver`, implement
32+
`processPackage` with your filtering logic, and delegate to `baseResolvePolicy`. The
33+
`basePolicyResolver` provides the shared scoring and package inclusion logic.
34+
35+
Key files: `filters.go` (all filtering logic), `conftest_evaluator.go` (integration point).

internal/validate/vsa/DESIGN.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# VSA Design
2+
3+
## Purpose
4+
5+
A VSA (Verification Summary Attestation) is a cryptographically signed record that a specific
6+
image was validated against a specific policy at a specific time. It enables skipping
7+
re-validation when the same image+policy combination has already been verified and the VSA
8+
hasn't expired.
9+
10+
## Why Multiple Storage Backends
11+
12+
VSAs can be stored locally (filesystem) or in Rekor (transparency log). Local storage is for
13+
development and testing. Rekor storage provides tamper-evident public auditability — once a
14+
VSA is logged, it can't be silently modified or deleted. The backend is selected at the CLI
15+
level; the core logic is backend-agnostic via the storage interfaces in `storage.go`.
16+
17+
## DSSE Signing
18+
19+
VSAs are wrapped in DSSE (Dead Simple Signing Envelope) with signature verification enabled
20+
by default. This was a deliberate security decision — unsigned VSAs would allow an attacker
21+
to forge validation results and skip policy enforcement. The signing key is the same key used
22+
for the original image validation.
23+
24+
## Expiration
25+
26+
VSAs have configurable expiration. This ensures that policy changes eventually take effect
27+
even for previously-validated images. When a VSA is expired, the image must be re-validated
28+
against the current policy. The threshold is set by the caller (typically 24h in production).

0 commit comments

Comments
 (0)