A STREAM vertex and its bounded history ring (L4 graph)¶
The STORED_VALUE contract is “the latest”: k writes leave one value. A STREAM vertex
additionally appends each write to a bounded ring, and its contract is “observe every
buffered entry” — which is why its propagation is a drain of the entries appended since
the previous flush, not a coalesced last-writer-wins flush
(reference 02 §Stream drain semantics).
What to notice¶
The depth has no wire surface at all. It is a retention intent only the application can supply, so it is declared owner-side with
vertex_policy_t::retention— not a:settingsknob. The withdrawnhistory_keep_lastanswersSCHEMA_NOT_FOUNDon read and on write, caller-independently (RFC-0022 §3.B / §3.C).Nothing is inherited. A declaration reaches exactly the vertex it names — no ancestor walk, no subtree push, no propagation question when a parent is reconfigured after its children exist (§3.F).
The ring is drain-only, and indexing never reaches it.
[n]counts children of the stored value, never entries of the history ring (CONTEXT.md §Element addressing). The example reads the ring throughhistory()and separately checks that a plainreadstill serves the latest value.Overflow is not silent, in general. Shedding under pressure carries the flow-gap signal and is accounted for (RFC-0025 §4.5). What this example shows is the ordinary trim to the declared depth on a local producer, which is the retention intent doing its job — not a receiver-visible gap.
Source¶
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
4 */
5
6/**
7 * @file
8 * @brief ONE CONCEPT — a STREAM vertex keeps a bounded history ring, depth declared owner-side.
9 *
10 * A `STORED_VALUE` vertex is last-writer-wins: *k* writes leave one value. A `STREAM` vertex
11 * additionally appends each write to a bounded ring, and its contract is "observe every
12 * buffered entry" rather than "the latest" (`docs/reference/02-graph-model.md` §Stream drain
13 * semantics). The depth is a RETENTION INTENT only the application can supply, so it is an
14 * owner-side call with **no wire surface at all** — `vertex_policy_t::retention`
15 * (`retention_t::N`), not a
16 * `:settings` knob (RFC-0022 §3.C; the withdrawn `history_keep_last` reads `SCHEMA_NOT_FOUND`).
17 *
18 * A plain `read` is unchanged: it still serves the latest value, never the ring.
19 *
20 * Runs under ctest as `example_graph_stream`; returns non-zero on any failed check.
21 */
22
23#include <array>
24#include <cstdio>
25#include <cstring>
26#include <span>
27#include <string_view>
28
29#include "libtracer/tracer.hpp"
30
31namespace {
32
33using tr::graph::path_t;
34using tr::graph::role_t;
35
36/** @brief An owned one-segment view over @p text. */
37tr::view::view_t value_of(std::string_view text) {
38 return *tr::view::over_bytes(std::as_bytes(std::span<const char>(text.data(), text.size())));
39}
40
41/** @brief True iff @p r's single view holds @p text. */
42bool holds(const tr::graph::value_t& r, std::string_view text) {
43 return r.only().bytes().size() == text.size() &&
44 std::memcmp(r.only().bytes().data(), text.data(), text.size()) == 0;
45}
46
47/** @brief Report expectation @p what and record a failure on @p ok. */
48void check(bool& ok, bool cond, const char* what) {
49 std::printf(" [%s] %s\n", cond ? "ok" : "FAIL", what);
50 ok = ok && cond;
51}
52
53} // namespace
54
55int main() {
56 tr::graph::graph_t g;
57 bool ok = true;
58
59 const auto events = g.register_vertex(path_t("/dev/can0/rx"), role_t::STREAM);
60 (void)g.set_policy(events,
61 {.retention = tr::graph::retention_t::N,
62 .depth = 3}); // owner-side retention intent; no peer can read or write it
63
64 for (const char* frame : {"f1", "f2", "f3", "f4"}) (void)g.write(events, value_of(frame));
65
66 // `history` fills caller storage and allocates nothing: each slot is a refcount share of a
67 // retained block. A span as long as the declared depth always holds the whole ring.
68 std::array<tr::graph::value_ref_t, 3> ring;
69 const auto n = g.history(events, ring);
70 check(ok, n.has_value(), "a STREAM vertex serves its history ring");
71 std::printf("ring holds %zu of the 4 writes\n", n ? *n : 0u);
72 check(ok, n && *n == 3, "the ring is bounded at the declared depth (oldest trimmed)");
73 check(ok, n && holds(*ring.front(), "f2"), "history is oldest-first, and f1 was trimmed");
74 check(ok, n && holds(*ring.back(), "f4"), "history is newest-last");
75
76 const auto latest = g.read(events);
77 check(ok, latest && holds(**latest, "f4"),
78 "a plain read still serves the latest value, not the ring");
79 return ok ? 0 : 1;
80}
See also: graph module · vertex roles and aggregation · user data packing.