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
26 changes: 25 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,31 @@
"Bash(perl -0777 -i -pe 's/\\(TraceabilityRule \\\\{[^}]*severity: [^,}]+,\\)\\(\\\\s*\\\\}\\)/$1\\\\n alternate_backlinks: vec![],$2/g' rivet-core/src/coverage.rs rivet-core/src/export.rs rivet-core/src/proofs.rs rivet-core/src/lifecycle.rs rivet-core/src/validate.rs)",
"Bash(perl -0777 -i -pe 's/\\(TraceabilityRule \\\\{[^}]*severity: [^,}]+,\\)\\(\\\\s*\\\\}\\)/$1\\\\n alternate_backlinks: vec![],$2/g' rivet-core/tests/proptest_operations.rs)",
"Bash(node scripts/diff-to-markdown.mjs --diff /tmp/malformed.json --pr 1 --run 1 --repo x/y)",
"Bash(node -e \"require\\('typescript'\\).transpileModule\\(require\\('fs'\\).readFileSync\\('rivet-delta.spec.ts','utf8'\\), { compilerOptions: { target: 'es2022', module: 'nodenext' } }\\)\")"
"Bash(node -e \"require\\('typescript'\\).transpileModule\\(require\\('fs'\\).readFileSync\\('rivet-delta.spec.ts','utf8'\\), { compilerOptions: { target: 'es2022', module: 'nodenext' } }\\)\")",
"Bash(node scripts/diff-to-markdown.mjs --diff /tmp/df.json --pr 1 --run 1 --repo x/y --mmd-out /tmp/diag.mmd --svg-url \"https://raw.githubusercontent.com/x/y/rivet-delta-renders/pr-1/run-1/diagram.svg\")",
"Bash(node scripts/diff-to-markdown.mjs --diff /tmp/df2.json --pr 1 --run 1 --repo x/y --svg-url \"https://example.com/g.svg\")",
"Bash(git -C /Users/r/git/pulseengine/rivet/.claude/worktrees/agent-aa9070c3 push -u origin test/sexpr-audit)",
"Bash(git -C /Users/r/git/pulseengine/rivet/.claude/worktrees/agent-a6c5e422 push -u origin worktree-agent-a6c5e422)",
"Bash(git -C /Users/r/git/pulseengine/rivet fetch origin)",
"Bash(git -C /Users/r/git/pulseengine/rivet checkout main)",
"Bash(git -C /Users/r/git/pulseengine/rivet pull)",
"Bash(git -C /Users/r/git/pulseengine/rivet checkout -b feat/v043-sexpr-followups)",
"Bash(git -C /Users/r/git/pulseengine/rivet log --oneline -5)",
"Bash(git -C /Users/r/git/pulseengine/rivet status --short)",
"Bash(git -C /Users/r/git/pulseengine/rivet stash push -- .claude/settings.local.json)",
"Bash(git -C /Users/r/git/pulseengine/rivet branch -D feat/v043-sexpr-followups)",
"Bash(git -C /Users/r/git/pulseengine/rivet add -A rivet-core/src/sexpr_eval.rs rivet-core/tests/sexpr_fuzz.rs rivet-core/tests/sexpr_predicate_matrix.rs docs/getting-started.md)",
"Bash(git -C /Users/r/git/pulseengine/rivet commit -m ' *)",
"Bash(git -C /Users/r/git/pulseengine/rivet push -u origin feat/v043-sexpr-followups)",
"Bash(awk '/cmd_variant_solve/{flag=1; print; next} flag && /^}/ {print; exit}')",
"Bash(awk *)",
"Bash(/Users/r/git/pulseengine/rivet/target/debug/rivet variant *)",
"Bash(echo \"exit=$?\")",
"Bash(/Users/r/git/pulseengine/rivet/target/debug/rivet validate *)",
"Bash(/Users/r/git/pulseengine/rivet/target/debug/rivet list *)",
"Bash(git *)",
"Bash(kill 19359 19358 19355)",
"Bash(ps -p 19358 19359 -o pid)"
]
}
}
161 changes: 161 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,44 @@ Quantifiers: `forall`, `exists`, `count`.

Graph: `reachable-from`, `reachable-to`.

### Count comparisons

`(count <scope>)` as a standalone form matches artifacts that exist in
the scope (equivalent to `(exists <scope> true)`). Wrapped in a
comparison, it counts artifacts and compares to a threshold:

```bash
# At least one failing test?
rivet list --filter '(> (count (and (= type "test") (= status "failed"))) 0)'

# Exactly three approved requirements with the safety tag?
rivet list --filter '(= (count (and (= type "requirement") (= status "approved") (has-tag "safety"))) 3)'
```

All six operators (`>`, `<`, `>=`, `<=`, `=`, `!=`) accept `(count …)` on
the left and an integer literal on the right. Any other shape produces
a parse error at lower time — no silent match failures.

### Regex patterns in `matches`

`(matches <field> "<regex>")` validates the regex **at parse time**. An
invalid pattern produces an error with the compiler's complaint, not a
silent empty result. Doubled backslashes are needed inside the s-expr
string (`"\\d+"` not `"\d+"`):

```bash
rivet list --filter '(matches id "^REQ-\\d+$")' # OK
rivet list --filter '(matches id "[unclosed")' # parse error with clear message
```

### Field accessors

Only single-name field accessors are supported today. Dotted forms like
`links.satisfies.target` parse as a single symbol and currently resolve
to the empty string — they do not navigate nested structure. To filter
on links, use the purpose-built predicates (`linked-by`, `linked-from`,
`linked-to`, `links-count`) rather than field-path navigation.

---

## Variant Management (Product Line Engineering)
Expand Down Expand Up @@ -833,8 +871,131 @@ rivet variant solve --model fm.yaml --variant v.yaml --binding bindings.yaml

# Validate only variant-scoped artifacts
rivet validate --model fm.yaml --variant v.yaml --binding bindings.yaml

# Emit effective features for a build system (see "Build-system emitters" below)
rivet variant features --model fm.yaml --variant v.yaml --format cargo

# Query single feature state (exit 0/1/2 = on/off/unknown)
rivet variant value --model fm.yaml --variant v.yaml asil-c

# Print a single attribute value
rivet variant attr --model fm.yaml --variant v.yaml asil-c asil-numeric
```

### Feature attributes

Each feature may declare an `attributes:` map — typed key/value metadata
that a release script can turn into build-system configuration. Example:

```yaml
# feature-model.yaml (excerpt)
features:
asil-c:
group: leaf
attributes:
asil-numeric: 3
reqs: "fmea-dfa"
core:
group: leaf
attributes:
version: "1.2.3"
```

Attribute values can be strings, integers, booleans, or null. Lists and
maps are accepted, but only the JSON emitter preserves them — every
other emitter returns an error rather than invent a silent flattening
rule.

### Build-system emitters

`rivet variant features --format <fmt>` emits the resolved feature set
plus per-feature attributes in a build-system-specific format. Every
identifier is long and namespaced (`RIVET_FEATURE_*`, `RIVET_ATTR_*`)
so multiple rivet models can coexist in one project.

All formats are **loud on failure**: if the variant violates a
constraint, the command exits non-zero with the violation list, never
a partial emission.

Supported formats:

| `--format` | Output | Intended consumer |
|--------------|--------|-------------------|
| `json` | structured JSON | scripts, other tools |
| `env` (`sh`) | `export NAME=value` | shell, Docker, CI |
| `cargo` | `cargo:rustc-cfg=…` / `cargo:rustc-env=…` | `build.rs` |
| `cmake` | `set(...)` + `add_compile_definitions(...)` | CMake projects |
| `cpp-header` | `#define` guarded by `RIVET_VARIANT_H` | C / C++ projects |
| `bazel` | `.bzl` constants (`RIVET_FEATURES`, `RIVET_ATTRS`) | Bazel projects |
| `make` | `NAME := value` | GNU Make |

Worked example — with the `asil-c`/`core` attributes shown above:

```bash
# Rust build.rs integration
rivet variant features --model fm.yaml --variant prod.yaml --format cargo
# cargo:rustc-env=RIVET_VARIANT=prod
# cargo:rustc-cfg=rivet_feature="asil-c"
# cargo:rustc-env=RIVET_FEATURE_ASIL_C=1
# cargo:rustc-env=RIVET_ATTR_ASIL_C_ASIL_NUMERIC=3
# cargo:rustc-env=RIVET_ATTR_ASIL_C_REQS=fmea-dfa

# CMake integration
rivet variant features --model fm.yaml --variant prod.yaml --format cmake
# set(RIVET_VARIANT "prod")
# set(RIVET_FEATURE_ASIL_C ON)
# set(RIVET_ATTR_ASIL_C_ASIL_NUMERIC "3")
# add_compile_definitions(RIVET_FEATURE_ASIL_C=1 RIVET_ATTR_ASIL_C_ASIL_NUMERIC=3 …)

# C/C++ header
rivet variant features --model fm.yaml --variant prod.yaml --format cpp-header
# #ifndef RIVET_VARIANT_H
# #define RIVET_VARIANT_H
# #define RIVET_FEATURE_ASIL_C 1
# #define RIVET_ATTR_ASIL_C_ASIL_NUMERIC 3
# #define RIVET_ATTR_ASIL_C_REQS "fmea-dfa"
# #endif

# Bazel (.bzl)
rivet variant features --model fm.yaml --variant prod.yaml --format bazel
# RIVET_FEATURES = ["asil-c", "core", …]
# RIVET_ATTRS = {
# "asil-c": { "asil-numeric": 3, "reqs": "fmea-dfa" },
# …
# }

# GNU Make
rivet variant features --model fm.yaml --variant prod.yaml --format make
# RIVET_VARIANT := prod
# RIVET_FEATURES := asil-c core …
# RIVET_ATTR_ASIL_C_ASIL_NUMERIC := 3

# Shell env (sourceable)
rivet variant features --model fm.yaml --variant prod.yaml --format env
# export RIVET_FEATURE_ASIL_C=1
# export RIVET_ATTR_ASIL_C_ASIL_NUMERIC='3'

# JSON (preserves list/map attributes)
rivet variant features --model fm.yaml --variant prod.yaml --format json
```

### Single-feature queries

```bash
# Probe one feature — ideal for shell conditionals
if rivet variant value --model fm.yaml --variant prod.yaml asil-c >/dev/null; then
echo "ASIL-C variant detected"
fi

# Read one attribute — scalar printed bare, list/map as JSON
asil_num=$(rivet variant attr --model fm.yaml --variant prod.yaml asil-c asil-numeric)
```

Exit codes for `value` / `attr`:
- `0` — feature selected / attribute present
- `1` — feature not selected (defined but absent from this variant)
- `2` — unknown feature, missing attribute key, or variant fails to solve

### Variants in the dashboard

`rivet serve` auto-discovers variant configuration when these files exist
Expand Down
Loading
Loading