Skip to content

feat(sexpr): add linked-via operator + document the linked-* family (#190)#265

Merged
avrabe merged 2 commits into
mainfrom
fix/issue-190-linked-via-operator
May 11, 2026
Merged

feat(sexpr): add linked-via operator + document the linked-* family (#190)#265
avrabe merged 2 commits into
mainfrom
fix/issue-190-linked-via-operator

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 9, 2026

Summary

Closes #190 — the issue reported that the s-expr DSL has no discoverable way to express "this artifact has at least one outbound link of type T." linked-by already does that semantically, but the issue documents that authors (the issue author included) read it as inbound and reach for spellings that don't exist (linked-via, out-link, has-link, linked-as). The four linked-* operators also have no semantic table in the user-facing docs, so users can't reverse-engineer the right form from the listing.

This PR adds the discoverable spelling, documents the family, and regenerates the parse-error hint from the lowerer's source of truth so the next added operator cannot leave the hint stale.

What changed

  • rivet-core/src/sexpr_eval.rs
    • New (linked-via "T") form. Lowers to Expr::LinkedBy(T, Wildcard) so the evaluator + links-count complement remain one code path. Arity = 1.
    • HEADS extended with "linked-via".
    • classify_filter_error now builds the unknown-head hint from HEADS (sorted) rather than a hand-maintained string. The previous opaque linked-* is replaced with the explicit family.
    • 6 new unit tests + the existing parse_error_unknown_head_surfaces_note updated for the regenerated hint.
  • docs/getting-started.md
    • 7-row semantic table covering linked-via, linked-by (1- and 2-arg), linked-from (1- and 2-arg), linked-to, links-count — direction, filter axis, truth condition. Plus a mnemonic paragraph.
    • 3 gap-hunt examples: AS missing outbound exploits, REQ missing inbound verifies, hazard missing inbound prevents.
    • Predicate listing and the linked-* cross-reference both extended with linked-via.
  • rivet-core/tests/sexpr_doc_examples.rs
    • 2 new doc-example regression tests: (linked-via "T")(linked-by "T" _) and the gap-hunt (and (= type …) (not (linked-via "T"))).
    • docs_listed_predicates_all_parse_as_forms extended with (linked-via "satisfies").

Acceptance — per the 2026-04-26 triage comment on #190

  • New s-expr predicate (linked-via "<link-type>") returns true iff the artifact has at least one outbound link of that type. Tests filter_linked_via_outbound_present / filter_linked_via_outbound_absent / filter_linked_via_arity in sexpr_eval.rs; (linked-via "T")(linked-by "T" _) proven by filter_linked_via_equivalent_to_linked_by_wildcard and docs_example_linked_via_outbound_membership.
  • Existing operator family (linked-from, linked-to, linked-by, linked-via) documented in docs/getting-started.md with a one-line semantic per operator and a worked example. New "Link predicates: which one do I want?" section + 3 worked gap-hunt examples + mnemonic paragraph.
  • Error message in rivet query --sexpr lists the full operator family, regenerated from a single source of truth. classify_filter_error now emits HEADS.sort_unstable(); pinned by the regression test unknown_head_hint_lists_linked_via.
  • Tests cover: artifact with the link-type → present; artifact without it → absent; (not (linked-via "X")) finds gaps. filter_linked_via_outbound_present, filter_linked_via_outbound_absent, filter_not_linked_via_finds_gap, docs_example_not_linked_via_finds_gap.
  • Snapshot/doctest covers each operator's documented semantics. docs_listed_predicates_all_parse_as_forms now exercises linked-via; the two new doc-example tests assert (linked-via …) against the getting-started.md examples directly.
  • Commit trailer Implements: REQ-007 (CLI/query). Trailer present; also Verifies: REQ-007 for the test additions.

Test plan

  • cargo test -p rivet-core --lib — 932 pass (+6 sexpr cases)
  • cargo test -p rivet-core --test sexpr_doc_examples — 11 pass (+2 cases)
  • cargo test -p rivet-cli — pass
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo fmt --all -- --check — clean
  • cargo run --release -p rivet-cli -- validate6 errors / 140 warnings / 0 broken cross-refs — byte-identical to pristine main; the 6 errors live in the spar external fixture and are unaffected.
  • CI on this PR — verified by GHA.

Out of scope

The issue body's section #2 ("linked-* operator family needs documentation") is fully addressed by the new section in getting-started.md; the optional split into a dedicated docs/sexpr.md (mentioned in the triage comment as an alternative) is not done — the existing s-expr filtering section already lives in getting-started.md and adding a parallel page would duplicate. Happy to lift it out as a follow-up if reviewers prefer.

🤖 Generated with Claude Code — issue-triage agent run 2026-05-09.


Generated by Claude Code

…#190)

Issue #190 reported that the s-expr DSL has no obvious way to express
"this artifact has at least one outbound link of type T". `linked-by`
already does that semantically, but the issue author (and others) read
it as "linked-by some inbound thing", so the discoverable spelling is
missing — and the four linked-* operators have no semantic table in the
docs.

Changes:

- New `(linked-via "T")` operator in `rivet-core/src/sexpr_eval.rs`,
  lowering to `Expr::LinkedBy(T, Wildcard)` so the evaluator and the
  `links-count` complement remain a single code path. Arity = 1.
- Error-hint generated from the `HEADS` array (the lowerer's source of
  truth) instead of a hand-maintained string, so the next operator
  added cannot leave the hint stale. Hint now enumerates the linked-*
  family explicitly rather than the opaque `linked-*` glob.
- `docs/getting-started.md`: 7-row semantic table covering
  `linked-via` / `linked-by` / `linked-from` / `linked-to` /
  `links-count` with direction + filter axis + truth condition, plus
  three gap-hunt examples (`(not (linked-via "T"))`, etc.).
- 6 new unit tests in `sexpr_eval.rs` covering: outbound-present,
  outbound-absent, `(not …)` finds gap, arity guard (0 / 1 / 2 args),
  `linked-via "T"` ≡ `linked-by "T" _` equivalence, and a regression
  test on the parse-error hint enumerating the family.
- 2 new integration tests in `sexpr_doc_examples.rs` covering the new
  doc table + gap-hunt example.
- `docs_listed_predicates_all_parse_as_forms` updated to include
  `linked-via`.
- `parse_error_unknown_head_surfaces_note` updated for the regenerated
  hint format (anchor + representative selection).

Acceptance (per the 2026-04-26 triage comment on #190):

- [x] `(linked-via "<T>")` returns true iff the artifact has at least
      one outbound link of type T
- [x] `linked-from` / `linked-to` / `linked-by` / `linked-via`
      documented in `docs/getting-started.md` with one-line semantics
      and a worked example
- [x] Error message in `rivet query --sexpr` lists the full operator
      family, regenerated from a single source of truth (`HEADS`)
- [x] Tests cover present / absent / `(not …)` finds gap
- [x] Doctest covers the documented `linked-via` semantics

Verification:

- `cargo test -p rivet-core --lib` — 932 pass (+6)
- `cargo test -p rivet-core --test sexpr_doc_examples` — 11 pass (+2)
- `cargo test -p rivet-cli` — pass
- `cargo clippy --workspace --all-targets -- -D warnings` — clean
- `cargo fmt --all -- --check` — clean
- `cargo run --release -p rivet-cli -- validate` — 6 errors / 140
  warnings / 0 broken cross-refs (byte-identical to pristine main; the
  6 errors live in the spar external fixture)

Closes #190

Implements: REQ-007
Verifies: REQ-007
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

📐 Rivet artifact delta

No artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 8a4ead7 Previous: 6dfdca6 Ratio
store_insert/10000 16211948 ns/iter (± 833212) 10777546 ns/iter (± 201358) 1.50
link_graph_build/10000 35579185 ns/iter (± 1758762) 22905310 ns/iter (± 293676) 1.55
validate/10000 22584395 ns/iter (± 1292492) 12167984 ns/iter (± 144437) 1.86
diff/10000 9229216 ns/iter (± 164621) 7561473 ns/iter (± 37002) 1.22

This comment was automatically generated by workflow using github-action-benchmark.

@avrabe avrabe merged commit 121642b into main May 11, 2026
19 of 39 checks passed
@avrabe avrabe deleted the fix/issue-190-linked-via-operator branch May 11, 2026 03:23
avrabe added a commit that referenced this pull request May 11, 2026
Three queued feature requests now land: rivet bundle (#266), rivet
coverage --matrix (#243), s-expr linked-via operator (#265). Plus
externals load their own schemas (#267) and STPA TCL numbering is
corrected to ISO 26262-8 (#257).

Infrastructure: CI concurrency control across all workflows (#258),
migration to self-hosted smithy runners (#262), release-npm trigger
fix that retroactively unblocked v0.7.0/v0.8.0 npm publication (#261),
weekly dependabot (#216), and the wasmtime 42→43 upgrade that retires
the RUSTSEC-2026-0114 suppression introduced in v0.8.0 (#260).

#125 (provenance-lifecycle) intentionally deferred — 5-week-old branch
with conflicts in heavily-churned files (CLAUDE.md, ci.yml, settings).
Needs its own attention session, not safe to autonomously rebase.

Refs: FEAT-001

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

s-expr DSL: no clean way to express 'artifact missing outbound link of type X', and linked-* operator semantics need docs

2 participants