Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .github/workflows/geval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
run: |
./geval/target/release/geval check \
--contract geval/examples/contract.yaml \
--contract geval/examples/contract-b.yaml \
--signals signals.json \
--env prod
continue-on-error: true
Expand Down
10 changes: 5 additions & 5 deletions geval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Geval consumes **signals** (JSON) and **policy** (YAML), evaluates rules in prio
Geval is **not** an npm or pip package. It is a **single static binary** you run locally or in CI.

- **Install:** Download a [release binary](https://github.com/geval/geval/releases) for your OS, or build from source with `cargo build --release`.
- **Integrate:** Run `geval check --signals signals.json --policy policy.yaml` in your repo; use exit codes (0/1/2) in CI or scripts. Your pipeline produces `signals.json` (e.g. via Node or Python); Geval only reads files and writes artifacts.
- **Integrate:** Run `geval check --contract contract.yaml --signals signals.json` (repeat `--contract` for multiple gates on one PR); use exit codes (0/1/2) in CI or scripts. Your pipeline produces `signals.json`; Geval only reads files and writes artifacts.

See **[Installation](docs/installation.md)** for download links, build-from-source steps, and local/CI integration.

Expand All @@ -31,7 +31,7 @@ cargo build --release
**If you have a release binary:** ensure `geval` is on your PATH, then:

```bash
geval check --signals signals.json --policy policy.yaml --env prod
geval check --contract contract.yaml --signals signals.json --env prod
```

Exit codes: `0` = PASS, `1` = REQUIRE_APPROVAL, `2` = BLOCK.
Expand All @@ -40,15 +40,15 @@ Exit codes: `0` = PASS, `1` = REQUIRE_APPROVAL, `2` = BLOCK.

| Command | Description |
|--------|-------------|
| `geval check` | Evaluate signals against policy; exit 0/1/2 |
| `geval check` | Evaluate signals against one or more contracts; exit 0/1/2 |
| `geval approve` | Record human approval (e.g. for REQUIRE_APPROVAL) |
| `geval reject` | Record human rejection |
| `geval explain` | Print human-readable decision report |
| `geval validate-policy` | Validate policy file syntax |
| `geval validate-contract` | Validate contract file(s) and referenced policies |

## Artifacts

- **Decisions:** `.geval/decisions/<timestamp>.json` (policy_hash, signals_hash, decision, matched_rule)
- **Decisions:** `.geval/decisions/<timestamp>.json` (v3: per-contract results, `bundle_hash`, overall decision, signals_hash)
- **Approval:** e.g. `.geval/approval.json` (approved_by, reason, timestamp)

## Docs
Expand Down
15 changes: 8 additions & 7 deletions geval/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Geval is **contract-centric**: a **contract** is a named, versioned set of **pol
1. **Load contract** – Parse contract YAML; resolve policy paths relative to the contract file; load each policy.
2. **Load signals** – Parse signals JSON; build an in-memory signal graph (metric → value lookup).
3. **Evaluate each policy** – For each policy, evaluate rules in priority order; first matching rule gives that policy’s outcome (PASS / REQUIRE_APPROVAL / BLOCK).
4. **Combine** – Apply the contract’s combination rule to the list of policy outcomes → single combined decision.
5. **Artifact** – Write `.geval/decisions/<timestamp>.json` with contract identity, per-policy results, combined decision, and hashes.
4. **Combine (policies)** – Apply the contract’s combination rule to the list of policy outcomes → one combined decision **per contract**.
5. **Combine (contracts)** – If multiple contract files are passed (`geval check -c a.yaml -c b.yaml`), apply **`--combine-contracts`** to each contract’s combined outcome → one **overall** PR-level decision (same rule vocabulary: `all_pass`, `any_block_blocks`).
6. **Artifact** – Write `.geval/decisions/<timestamp>.json` (v3) with `bundle_hash`, each contract block, `contracts_combine_rule`, and overall outcome + hashes.

## Module layout

Expand All @@ -27,25 +28,25 @@ geval/src/
model.rs # ContractDef, PolicyRef
combine.rs # CombineRule (all_pass, any_block_blocks), apply_combine_rule
loader.rs # load_contract, load_contract_and_policies, parse_contract_str
runner.rs # run_contract → ContractResult (per-policy + combined)
runner.rs # run_contract, load_run_contracts → ContractResult / MultiContractRun
policy/ # Single policy model and parser
model.rs # Policy, Rule, RuleCondition, RuleConsequence, Action, Operator
parser.rs # parse_policy, parse_policy_str
evaluator/ # Single-policy evaluation
engine.rs # evaluate(policy, graph) → Decision; evaluate_with_trace
signal_graph/ # Build lookup from signals for rule matching
signals/ # Load signals JSON (name, version, signals array)
hashing/ # SHA256 for contract, policy, signals (audit)
artifact/ # Write decision artifact (contract + per-policy + combined)
explanation/ # Human-readable report (explain_contract_result, explain_decision)
hashing/ # SHA256 for contract, policy, signals, contract bundle (audit)
artifact/ # write_multi_contract_artifact (v3: multi-contract + overall)
explanation/ # explain_contract_result, explain_multi_contract_result, explain_decision
approval/ # Approval/rejection artifact (versioned)
cli/ # Commands: check, init, demo, explain, validate-contract, approve, reject
```

## Invariants

- **Nothing unversioned** – Contract, policies, and signals have name/version; artifact records them and hashes.
- **Deterministic** – Same contract + same signals → same combined decision.
- **Deterministic** – Same contract set (order) + same signals → same overall decision.
- **No remote calls** – All inputs and outputs are local files.

## Adding a new combination rule
Expand Down
21 changes: 13 additions & 8 deletions geval/docs/auditing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Geval is designed so **nothing is unversioned**: every decision and every action

- **Why was this deployed?** – Decision report and matched rule (and optional approval reason).
- **Who approved it?** – `geval approve` writes an artifact with `approved_by`, `reason`, and artifact `version`.
- **What policy (contract) was used?** – Artifact stores `policy_name`, `policy_version`, and `policy_hash` (SHA256).
- **What policy (contract) was used?** – Artifact stores each contract’s identity, `contract_hash`, per-policy hashes, and (v3) `bundle_hash` for the ordered set of contracts.
- **What signals were used?** – Artifact stores `signals_name`, `signals_version`, and `signals_hash` (SHA256).
- **Which Geval binary?** – Artifact stores `geval_version`.

Expand All @@ -17,18 +17,23 @@ Geval is designed so **nothing is unversioned**: every decision and every action
Each `geval check` run writes:

- **Path:** `.geval/decisions/<timestamp>.json`
- **Contents (artifact_version 2, contract-centric):**
- `artifact_version` – schema version (e.g. `"2"`)
- **Contents (artifact_version 3, multi-contract):**
- `artifact_version` – schema version (`"3"`)
- `geval_version` – binary version that produced the decision
- `contract_name`, `contract_version`, `contract_hash` – contract identity and content hash
- `bundle_hash` – SHA256 over the ordered list of `(contract_path, contract_hash)` (audit the exact contract set)
- `contracts_combine_rule` – how each contract’s **combined** outcome was merged (e.g. `all_pass`, `any_block_blocks`)
- `contracts` – array of blocks, each with:
- `contract_path`, `contract_name`, `contract_version`, `contract_hash`
- `combine_rule` (policies within that contract)
- `policy_results` – `{ policy_path, policy_name?, policy_version?, policy_hash, outcome, matched_rule? }[]`
- `combined_decision`, `combined_matched_rule`, `combined_reason` (outcome for that contract)
- `overall_combined_decision`, `overall_matched_rule`, `overall_reason` – PR-level outcome after `contracts_combine_rule`
- `signals_name`, `signals_version`, `signals_hash` – signals identity and content hash
- `combine_rule` – how policy outcomes were merged (e.g. `all_pass`, `any_block_blocks`)
- `policy_results` – array of `{ policy_path, policy_name?, policy_version?, policy_hash, outcome, matched_rule? }` for each policy
- `combined_decision` – final outcome (PASS | REQUIRE_APPROVAL | BLOCK)
- `combined_matched_rule` – first non-PASS policy and rule (if any)
- `timestamp` – ISO8601
- `approval` – optional; set when an approval is recorded for this decision

Older tooling may still reference **artifact_version 2** (single flat contract); Geval now writes v3 only.

### Approval artifact

`geval approve` / `geval reject` write:
Expand Down
4 changes: 2 additions & 2 deletions geval/docs/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ This document describes how to change or extend Geval in a consistent, testable
- **Contract** – `contract/model.rs`, `contract/loader.rs`, `contract/runner.rs`, `contract/combine.rs`.
- **Policy** – `policy/model.rs`, `policy/parser.rs`; then `evaluator/engine.rs` if rule semantics change.
- **Signals** – `signals/loader.rs`, `signal_graph/` if lookup behavior changes.
- **Artifact** – `artifact/writer.rs`; bump `DECISION_ARTIFACT_VERSION` if the JSON shape changes.
- **Artifact** – `artifact/writer.rs` (`write_multi_contract_artifact`); bump `DECISION_ARTIFACT_VERSION` if the JSON shape changes.
- **CLI** – `cli/commands.rs`; add or update subcommands/args.

Keep functions small and pure where possible; use `anyhow::Result` and `Context` for errors.
Expand Down Expand Up @@ -59,7 +59,7 @@ Run: `cargo test --manifest-path geval/Cargo.toml`.

1. **Model** – Add the field to `ContractDef` or `Policy` in the appropriate `model.rs`; use `Option<T>` and `#[serde(default)]` for backward compatibility if we still support old files.
2. **Parser** – If the field comes from YAML, ensure the parser (contract loader or policy parser) reads it and fills the model.
3. **Artifact** – If the field should be audited, add it to `DecisionArtifact` (and to the code that builds the artifact from `ContractResult` and versions).
3. **Artifact** – If the field should be audited, add it to `DecisionArtifactV3` / `ContractDecisionBlock` in `artifact/writer.rs` (and the code that builds from `MultiContractRun`).
4. **Tests** – Parse a sample YAML with the new field and assert it’s present; if the field affects evaluation, add an evaluator or runner test.

## Adding a new CLI command
Expand Down
10 changes: 7 additions & 3 deletions geval/docs/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ jobs:
run: |
./geval/target/release/geval check \
--contract contract.yaml \
--contract other-team/contract.yaml \
--signals signals.json \
--env prod
```

Repeat `--contract` for each gate YAML attached to the PR. Optional: `--combine-contracts all_pass` (default) or `any_block_blocks`.

## Option B: Download released binary

Use when you rely on an official Geval release:
Expand All @@ -57,6 +60,7 @@ Use when you rely on an official Geval release:
run: |
./geval check \
--contract contract.yaml \
--contract other-team/contract.yaml \
--signals signals.json
```

Expand All @@ -72,7 +76,7 @@ Use these in a later step to fail the job on BLOCK or REQUIRE_APPROVAL if desire
- name: Run Geval
id: geval
run: |
./geval check --contract contract.yaml --signals signals.json --env prod
./geval check --contract contract.yaml --contract other-team/contract.yaml --signals signals.json --env prod
echo "exitcode=$?" >> $GITHUB_OUTPUT
```

Expand All @@ -81,13 +85,13 @@ Then `if: steps.geval.outputs.exitcode == '0'` for merge gates.
## Post result to PR (GitHub CLI)

```bash
RESULT=$(./geval check --contract contract.yaml --signals signals.json)
RESULT=$(./geval check --contract contract.yaml --contract other-team/contract.yaml --signals signals.json)
gh pr comment $PR_NUMBER --body "$RESULT"
```

Or capture the explain output:

```bash
RESULT=$(./geval explain --contract contract.yaml --signals signals.json)
RESULT=$(./geval explain --contract contract.yaml --contract other-team/contract.yaml --signals signals.json)
gh pr comment $PR_NUMBER --body "$RESULT"
```
9 changes: 5 additions & 4 deletions geval/docs/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ The decision artifact records `signals_name` and `signals_version` when present.

Every `geval check` writes a versioned artifact to `.geval/decisions/<timestamp>.json`:

- **artifact_version** – Schema version of the artifact format.
- **artifact_version** – Schema version (current: **3** — multi-contract).
- **geval_version** – Geval binary version that produced the decision.
- **policy_name**, **policy_version** – From the policy (contract) file.
- **contracts** – Each contract’s `contract_name`, `contract_version`, `contract_hash`, and per-policy `policy_name` / `policy_version` / `policy_hash` when present.
- **bundle_hash** – Hash of the ordered contract set (paths + content hashes).
- **signals_name**, **signals_version** – From the signals file.
- **policy_hash**, **signals_hash** – Content hashes (SHA256) for integrity.
- **signals_hash** – Content hash (SHA256) for integrity.

So every decision is fully traceable: which contract version, which signals version, which binary.
So every decision is fully traceable: which contract versions (one or many), which signals version, which binary.

## Approval artifact

Expand Down
10 changes: 9 additions & 1 deletion geval/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Example contract, policies, and signals for Geval (decision orchestration and re
## Files

- **contract.yaml** – Contract: name, version, combine rule, and list of policy paths. This example references a single policy.
- **contract-b.yaml** – Second contract (distinct `name`), same `policy.yaml` — used to demo **multiple contracts** on one PR.
- **policy.yaml** – One policy with priority-ordered rules: business block, hallucination guard, retrieval quality.
- **signals.json** – Example signals (eval metrics, A/B metrics, component-level).

Expand All @@ -16,11 +17,18 @@ cargo build --release --manifest-path geval/Cargo.toml
# Check: evaluate signals against contract (exit 0=PASS, 1=REQUIRE_APPROVAL, 2=BLOCK)
./geval/target/release/geval check --contract geval/examples/contract.yaml --signals geval/examples/signals.json --env prod

# Multiple contracts (same signals): repeat --contract / -c
./geval/target/release/geval check \
--contract geval/examples/contract.yaml \
--contract geval/examples/contract-b.yaml \
--signals geval/examples/signals.json --env prod

# Explain: human-readable report (per-policy + combined)
./geval/target/release/geval explain --contract geval/examples/contract.yaml --signals geval/examples/signals.json --env prod

# Validate contract and all referenced policies
# Validate one or more contract files
./geval/target/release/geval validate-contract geval/examples/contract.yaml
./geval/target/release/geval validate-contract geval/examples/contract.yaml geval/examples/contract-b.yaml
```

With the example data, the policy matches `business_block`: `engagement_drop` 0.03 > 0, so the decision is **BLOCK**.
Expand Down
6 changes: 6 additions & 0 deletions geval/examples/contract-b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Second example contract: same policy file, distinct contract identity (multi-contract PR demo).
name: demo-secondary
version: "1.0.0"
combine: all_pass
policies:
- path: policy.yaml
5 changes: 4 additions & 1 deletion geval/src/artifact/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod writer;

pub use writer::{write_decision_artifact, DECISION_ARTIFACT_VERSION};
pub use writer::{
write_multi_contract_artifact, ApprovalPayload, ContractDecisionBlock, DecisionArtifactV3,
PolicyResultRecord, DECISION_ARTIFACT_VERSION,
};
Loading
Loading