Read and write a vertex — the data plane (L4 graph)

Two of the three data calls (CONTEXT.md §read / write / await), on a STORED_VALUE vertex. The example writes twice and reads three times, which is enough to pin down the storage contract: one store per vertex, last-writer-wins, and a read serves the latest stored value — never behind a notification, legitimately newer (reference 02 §Subtree subscriptions, branch writes, and write-creates).

What to notice

  • A never-written vertex has no last-known-value. It answers NOT_FOUND, the same status an address that does not resolve answers. The collapse is deliberate and documented (reference 02 §Retirement notification lists the three meanings tr::path::not_found carries); a consumer that must tell them apart carries its own expectation of what should exist.

  • read returns a reference, not a copy. value_ref_t names the published value; the rope is not cloned link-by-link on the way out (RFC-0022 is a different concern — the reference-vs-copy rule and its measurement live on vertex_t::value_ref_t in core/include/libtracer/vertex.hpp). Holding one keeps that value alive, which the example checks: the first read still reads 21.5C after the second write replaced the store.

  • The payload is opaque. L4 never interprets a VALUE’s bytes — the example writes ASCII because it is legible, not because the graph knows what a temperature is.

  • A write is not a primitive. It is assign (state) followed by propagate (edges), RFC-0008. Nothing subscribes here, so only the assign half is observable.

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 — the data plane: `write` then `read`, and the one store per vertex.
 9 *
10 * A `STORED_VALUE` vertex is last-writer-wins: a write replaces the stored value, a read
11 * serves the latest one, and a vertex that was never written has no last-known-value at all
12 * (`NOT_FOUND`, distinct from an address that does not resolve — both spell `NOT_FOUND`
13 * here, `docs/reference/02-graph-model.md` §Vertex lifecycle). `read` hands back a
14 * `value_ref_t` — a REFERENCE to the published value, not a copy of it.
15 *
16 * Runs under ctest as `example_graph_read_write`; returns non-zero on any failed check.
17 */
18
19#include <cstdio>
20#include <cstring>
21#include <span>
22#include <string_view>
23
24#include "libtracer/tracer.hpp"
25
26namespace {
27
28using tr::graph::path_t;
29using tr::graph::role_t;
30using tr::graph::status_t;
31
32/** @brief An owned one-segment view over @p text — a VALUE's bytes are opaque to L4. */
33tr::view::view_t value_of(std::string_view text) {
34    return *tr::view::over_bytes(std::as_bytes(std::span<const char>(text.data(), text.size())));
35}
36
37/** @brief True iff @p v's bytes equal @p text. */
38bool holds(const tr::view::view_t& v, std::string_view text) {
39    return v.bytes().size() == text.size() &&
40           std::memcmp(v.bytes().data(), text.data(), text.size()) == 0;
41}
42
43/** @brief Report expectation @p what and record a failure on @p ok. */
44void check(bool& ok, bool cond, const char* what) {
45    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
46    ok = ok && cond;
47}
48
49}  // namespace
50
51int main() {
52    tr::graph::graph_t g;
53    bool ok = true;
54
55    const auto temp = g.register_vertex(path_t("/sensor/temp"), role_t::STORED_VALUE);
56    const auto unwritten = g.read(temp);
57    check(ok, !unwritten && unwritten.error() == status_t::NOT_FOUND,
58          "a never-written vertex has no last-known-value");
59
60    (void)g.write(temp, value_of("21.5C"));
61    const auto first = g.read(temp);
62    check(ok, first && holds((*first)->only(), "21.5C"), "read serves what was written");
63
64    // Last-writer-wins: the second write REPLACES the store; there is one store per vertex.
65    (void)g.write(temp, value_of("22.0C"));
66    const auto second = g.read(temp);
67    check(ok, second && holds((*second)->only(), "22.0C"),
68          "a second write replaces the stored value");
69
70    // The first read still holds its own reference — the value it names stays alive.
71    check(ok, holds((*first)->only(), "21.5C"), "an outstanding value_ref_t keeps its value alive");
72    std::printf("/sensor/temp now reads %.*s\n", static_cast<int>((*second)->only().bytes().size()),
73                reinterpret_cast<const char*>((*second)->only().bytes().data()));
74    return ok ? 0 : 1;
75}

See also: graph module · views · graph model reference · communication flows.