Skip to content

feat(query): state and event queries over a session, and a page telling the query kinds apart - #359

Merged
HuiJun merged 11 commits into
developfrom
feature/state-and-event-queries
Sep 17, 2026
Merged

HuiJun merged 11 commits into
developfrom
feature/state-and-event-queries

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

What and why

Two parts, both about queries.

A page telling the query kinds apart. docs/manual/query-kinds.md places the project's query surfaces side by side — document queries over elements, object rows, verdict rows, state rows and event rows; the API Query and its OSLC text; Evaluate; solve; the runtime population all T and the collection operations over it — saying what each returns, what each cannot see, and which to reach for. The pages that re-explained the boundary (manual/introduction.md, manual/query-cookbook.md, reference/api.md, reference/oslc-query.md) now point at it; reference/cli.md and reference/repl-commands.md document the new rows where they document -run-query and %run-query.

State and event queries over a session. Three document-query operations in DocumentQueries, beside Objects and Verdicts, refused with a typed error outside a session as Objects is:

  • States(source) — one row per active leaf state of each source object's machine, every orthogonal region, with the composite states enclosing the leaf.
  • InState(name) — the objects the session holds whose machine is in that state, by leaf name, enclosing name or dotted path (on.run); object rows, so WhereType, Project and the rest apply.
  • Events(source, kind, since, before) — the run's trace as a relation in the order recorded: accepts, sends, transitions fired, entry/exit/do steps, choice draws (due-order draws included) and unevaluable guards, filtered by object, by kind and by the interval [since, before).

The trace they read is the runtime's own record. runtime.TraceRecorder now keeps typed TraceRecords and prints -trace's lines from them (TraceRecord.Line), so the printed trace and the Events rows cannot drift; every -trace golden is byte-identical. Clear marks records printed rather than dropping them, so a query reads the whole run while each drain prints only what is new.

Every record names the object and the behavior that made it: an action's sends, choice draws and unevaluable guards are the performer's under the action, a state machine's are under the machine, and a message posted from outside the run names neither. sysml-grpc gives each cached population a query-only recorder bounded by OPENSYSML_GRPC_MAX_HELD_EVENTS (default 100000, docs/reference/environment.md): the oldest records are dropped as newer ones arrive, and an Events query whose interval reaches dropped history fails with the typed trace-truncated error (FAILED_PRECONDITION) rather than answer from a partial trace.

-run-query now runs after -state, -action and -advance, so a state query reads where the run left the machine and an event query reads what it recorded — before, it ran on the initial state and an empty trace.

Row and value design

A row is a value that Value.isRow admits; Project, WhereFeature, OrderBy and Column read its properties through propertyValues. Two row kinds join ValueObject and ValueVerdict; the object column of both reuses queryexec.ObjectValue, so an object projected from a state row is the object row the session prints and crosses RunDocumentQuery as the existing DocumentValue.object arm.

State row (queryexec.State, ValueState; properties declared on DocumentQueries::State):

property type value
object Element[1] the owning object, as ObjectValue(inst, label)
path String[1] the label the session reaches the object by (#1.lp, Garage::car.engine)
machine String[1] the exhibited state usage, by qualified name (Cookbook::dome.control)
name String[1] the active leaf state's name
statePath String[1] the leaf's dotted path through its composites (open.slewing)
state Element[0..1] the leaf's declaration, when the model has one
region String[1] the orthogonal region the leaf stands in, empty for a flat machine
enclosing String[0..*] ordered the active composite states around the leaf, outermost first

A row also answers its state declaration's own properties. Label: lamp1.lp in on.dim. An object with no state machine is ErrorNoStateMachine; an element row that declares no held object is ErrorNotHeld; a state row given to a model-only operation is ErrorStateRow; a state no machine declares is ErrorUnknownState.

Event row (queryexec.Event, ValueEvent; properties on DocumentQueries::Event):

property type value
kind String[1] accept, send, transition, entry, exit, do, choice, guard
time ScalarValue[1] the instant as the clock reads it: a quantity in the clock's unit, a bare real when no unit is reduced
object Element[0..1] the performing object as ObjectValue; absent for a behavior run without one
path String[0..1] the object's label
machine String[0..1] the behavior the record is about, by qualified name
state String[0..1] the state entered, exited or stepped; a choice's or guard's state
from, to String[0..1] a fired transition's endpoints
target Element[0..1] the object a send was addressed to
event String[0..1] the signal or operation accepted or sent, or the trigger a transition fired on
payload String[0..*] ordered an accept's or send's payload, name = value per feature
alternatives String[0..*] ordered a choice's alternatives, in draw order
taken String[0..1] the alternative a choice took
text String[1] the line the trace prints for the record

A row also answers the properties of the behavior that made it. Summary: t=1 lamp1.lp: accept Dim. since and before take a duration (1 [s], 500 [ms], converted through the clock's unit as after and at are) or a bare number of the clock's seconds, either left open; the interval is inclusive of since and exclusive of before. A bound that is no instant on the clock (1 [m]), or before <= since, is ErrorInvalidInterval; a kind the trace does not record is ErrorInvalidArgument; a session recording no trace is ErrorNoTrace; no session is ErrorNoRuntime; an event row given to a model-only operation is ErrorEventRow.

Wire shape. DocumentValue gains two answer-only arms, state (DocumentState) and event (DocumentEvent), mirroring the two tables; DocumentQueryRow carries them beside object and verdict. Binding either is refused like a verdict. The Go and Python clients decode them (Row.State, Row.Event; DocumentState, DocumentEvent) and refuse to bind them; the Java, TypeScript and Rust generated code is regenerated.

Rendering. Markdown prints a state row as <object>.<machine> in <statePath> and an event row as t=<instant> <object>.<machine>: <text>; HTML wraps them in span.sysml-state (data-machine, data-state, data-region) and span.sysml-event (data-event-kind, data-time); the PDF backend lays out the Markdown.

Specification basis

Non-normative OpenSysML document-query vocabulary (DocumentQueries); the states read are the SysML v2 state configurations as the state executor holds them (KerML/SysML v2 §7.20, active states across orthogonal regions), the trace the runtime's own record. docs/project/spec-compliance.md gains three rows under document-query execution: the state operations, the typed trace and Events, and the rendering, API and client surface.

How it was verified

New tests, all passing:

  • internal/core/queryexec/states_test.go, events_test.go — each operation and filter over a fixture with two lamps in different states and a trace with accepts, sends, transitions, entry/exit/do steps, choices and guards; the [since, before) boundary; every refusal.
  • internal/core/runtime-trace goldens byte-identical; trace-record tests.
  • internal/core/docrender/states_test.go with testdata/state_report.sysml and its Markdown/HTML goldens; internal/docpdf layout golden and the WeasyPrint and Pandoc PDF renders run with the toolchain provisioned (Prince absent, skipped).
  • internal/grpc/testdata/conformance/document_query_{states,in_state,events} conformance cases; client/opensysml/states_test.go; clients/python/tests/test_document.py (35 passed).
  • internal/repl/docquery_states_test.go, internal/repl/cookbook_states_test.go (the cookbook recipes, driven as the cookbook drives them); cmd/sysml/run_query_test.go (-run-query after -state/-advance, with and without -trace).

Gates run locally on the merged branch:

go build ./...          clean
go vet ./...            clean
gofmt -l .              (empty)
go test ./...           all packages ok
make lint               ✓ Lint passed (staticcheck + gosec)
make stdlib-snapshot-check   ✓ stdlib.snapshot is current
./scripts/check-grpc-imports.sh   confined
make proto-lint proto-breaking    ✓ Proto lint passed / ✓ No breaking schema changes
OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 go test -count=1 ./internal/core/model -run 'TestTrainingExamples|TestCorpusGates|TestPilotCorpora'
    100/100 training files clean; kerml-examples 55/58, sysml-examples 95/99, sysml-validation 56/56 (unchanged)
OPENSYSML_REQUIRE_PILOT_LIBRARY_XMI=1 go test ./internal/core/identity -run TestPilotLibraryXMI   9823 derived, 9823 agree
OPENSYSML_REQUIRE_PSSM_SUITE=1 go test ./internal/pssm -run 'TestSuiteRead|TestSuiteClassification|TestEmitSuite'   ok
go run ./cmd/pssm-referee -check     baseline reproduced
OPENSYSML_REQUIRE_FUML_SUITE=1 go test ./internal/fuml -run TestSuite   ok
go run ./cmd/fuml-referee -check     baseline reproduced
OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 go test ./internal/core/export -run TestCorpusRoundTrip   ok (ratchet unchanged)
make docs-check         ok
python3 scripts/changelog.py check   ok
mkdocs build --strict   ok

Checked by hand at the prompt: the cookbook sequence (%trace on, two domes, %send/%advance), the refusals for 1 [m], before <= since, an unknown state, an object with no machine and a query with %trace off, and OrderBy descending over event time.

Known limitations

None in the operations. Three boundaries, documented on the page: an event query sees the trace of its own session only (no cross-session queries); it reads records already made — a query does not see events the clock has not reached; and over sysml-grpc it reads the most recent OPENSYSML_GRPC_MAX_HELD_EVENTS records, refusing an interval that reaches older ones.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels in the body, docs, or changelog

devin-ai-integration Bot and others added 2 commits September 17, 2026 03:46
Which query is which distinguishes document queries (element, object and
verdict rows), the API Query and OSLC text, Evaluate, the runtime extent
all T and solve: what each reads, returns and cannot see. The reference
pages that restated a boundary now point at it.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 7 commits September 17, 2026 04:44
…s from

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ade by

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…untime and trace

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…clients, the renderers and -run-query

State and event rows gain arms of DocumentValue, so RunDocumentQuery reports
them and the Go, Python, Java, TypeScript and Rust clients decode them; the
Markdown, HTML and PDF renderers print a state as "object.machine in state"
and an event as its timed trace line. -run-query now runs after the behaviors
named, so a state query reads where -advance left a machine and an event query
reads what -trace recorded.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…e and event queries

Regenerates the library snapshot for the States, InState and Events
operations DocumentQueries declares, and documents the -run-query
ordering after -state, -action and -advance.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…y code

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review September 17, 2026 07:24
devin-ai-integration[bot]

This comment was marked as resolved.

…; bound the events a served population keeps

Send records carry the action or state machine that sent, so an Events row over a send answers `machine` and its behavior-declared properties; a message posted from outside the run names no behavior. Choices and unevaluable guards an action draws are recorded as the performer's under the action, so an Events query bound to a source sees them beside the state machine's; the scheduler's run-wide draws stay unattributed.

sysml-grpc gives each cached population a query-only recorder bounded by OPENSYSML_GRPC_MAX_HELD_EVENTS (default 100000): the oldest records are dropped as newer ones arrive, and an Events query whose interval reaches dropped history fails with `trace-truncated` (FAILED_PRECONDITION) rather than answer from a partial trace. RecordAcceptAt places by whole-run mark so a late accept lands where it belongs after trimming.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…truncation in snapshots

A state row's region is the innermost orthogonal region an enclosing state is declared in, found by walking the parent chain as the executor does, so a leaf nested below a region's own state no longer reads as outside every region.

A snapshot of a bounded trace recorder now captures how many records it had dropped and up to when, and restores them with the records, so an Events query after a restore is not refused for history the explored branch discarded. Trimming cuts the record slice from the front instead of shifting in place, so the slice a snapshot holds still reads what it did at the mark.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit af2f62d into develop Sep 17, 2026
12 checks passed
@HuiJun
HuiJun deleted the feature/state-and-event-queries branch September 17, 2026 14:42
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.

1 participant