Skip to content

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

@avrabe

Description

@avrabe

Context

While building a V-model gap-hunt pipeline that uses `rivet query --sexpr` to enumerate gaps (e.g., "every attack-scenario must have at least one outbound `exploits` link"), I hit two ergonomic gaps in the s-expression DSL.

1. No clean predicate for "artifact has no outbound link of type X"

The natural V-model question is: "find every attack-scenario with zero outbound `exploits` links." I tried the obvious shapes:

```
(and (= type "attack-scenario") (not (linked-from "exploits")))
(and (= type "attack-scenario") (not (linked-via "exploits"))) # unknown form
(and (= type "attack-scenario") (not (linked-as "exploits"))) # unknown form
(and (= type "attack-scenario") (not (out-link "exploits"))) # unknown form
(and (= type "attack-scenario") (not (has-link "exploits"))) # unknown form
```

`(not (linked-from "exploits"))` returns ALL attack-scenarios — including AS-1 which has `exploits → UCA-9` and `exploits → DF-1`. So `linked-from` does not mean "is the source of an outbound link of this type." Its semantics are something else (target-of-inbound-from-a-typed-source?), but the docs don't say.

`linked-to ` works clearly ("has outbound link to anything of this type/id") but doesn't let me filter by link-type. So `(linked-to "uca")` finds attack-scenarios that link to any UCA via any link-type, not specifically via `exploits`.

Suggestion: introduce `(linked-via )` (or pick a name) meaning "has at least one outbound link of this link-type." This is the inverse of the existing `(linked-by )` (which I think means "has at least one inbound link of this link-type"). With both, gap-hunt becomes one-line s-expr per category:

```
(and (= type "attack-scenario") (not (linked-via "exploits"))) ; AS missing outbound exploits
(and (= type "requirement") (not (linked-by "verifies"))) ; REQ missing inbound verifies
(and (= type "hazard") (not (linked-by "prevents"))) ; H missing inbound prevents
```

2. `linked-*` operator family needs documentation

The error message lists the operator family vaguely:

```
note: unknown head symbol; see docs/getting-started.md for the supported forms
(and/or/not/implies/excludes/=/!=/>/</has-tag/has-field/in/matches/contains/linked-*)
```

`linked-*` suggests a family but doesn't enumerate it. `rivet docs query` returns "Unknown topic". I had to discover `linked-to`, `linked-by`, `linked-from` empirically via trial-and-error, and even after discovery the semantic distinction between them isn't clear without inspecting source.

Suggestion: add a `rivet docs query` topic (and possibly a CLI flag like `rivet query --list-operators`) that enumerates every operator with a one-line example. Bonus: add the s-expr DSL reference to the README so it's discoverable.

Real-world impact

This came up while building `scripts/vmodel/` in sigil — a Mythos-style V-model gap-hunt pipeline (PR #86 / #87). The pipeline wants to enumerate gaps via `rivet query --sexpr`, but the missing primitive forced a fallback to `rivet validate --format json | python3 ...` parsing. That works but defeats the whole point of having a query DSL.

Concrete sample I tried (sigil repo)

```bash
rivet query --sexpr '(and (= type "attack-scenario") (not (linked-from "exploits")))' --format ids

Returned all 37 AS-N, including AS-1 which clearly has 'exploits' outbound links.

Expected: AS-34, AS-35, AS-36 (the three with 0 exploits, per rivet validate).

```

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions