printobserver-types.
Owns: the vocabulary every domain and every client must agree on, and only
that — the identity rule ([ids]: the exported [identifier!] macro, its
error, the UUID-v7 rule, and the three identifiers the envelope reaches),
the representation rules ([Timestamp], [RawBytes], [FileName],
[Reported] and [Range]), the event log's envelope ([event], with the
image handle [ImageRef] it carries), and the schema toolkit
([contract]). It holds data and total functions over that data, never
I/O.
A type belongs here only if adding or changing one domain's concept does
not require editing it. The event log is the case in point: [event]
declares the envelope — an open [EventKind] name and an opaque payload —
and no kind. Each kind is declared by the domain that owns the event, as
a payload type implementing [EventPayload] under a KIND of its own, so
that a domain adding an event edits its own crate and nothing central. The
same holds for a domain's own shapes: a provider's wire format lives in its
adapter, a port's vocabulary in the port, and the supervision domain's
records — the print, the action, the intervention, the policy, the image —
and the identifiers it mints live in the supervision domain, each declared
into the schema set through [TypeContract::of] rather than through a
list here.
May depend on: no crate of this workspace. It is the root of the graph, so a type it does not hold is a type the rest of the workspace cannot agree on.
One authoritative source per restated fact
The types here are declared once and imported by every consumer rather than
re-declared. The JSON Schemas the clients and the agent's structured answer
are validated against are generated from these types by the
printobserver-types:schemas graph target, which the gate runs: a schema
checked into schemas/ that no longer matches what these types generate
fails that target. Nothing transcribes a schema by hand, and nothing
restates a field.
[contract] is where that generation reads the declared set from: it is
this crate's own claim about which types it declares and what a canonical
value of each looks like.
The representations, stated once
- Identifiers. Every identifier this system mints is a UUID version 7,
minted by the store, held as a distinct newtype per record, and serialized
as its lowercase hyphenated string. See [
ids]; a domain declares its own through the [identifier!] macro. - Numbers. Every temperature, factor, multiplier, percentage and fraction is a 64-bit float. Every byte count and every integer identifier an external system supplies is a 64-bit signed integer. Every duration is whole seconds as a 64-bit signed integer.
- Timestamps. Every timestamp is an instant in UTC, serialized as an RFC
3339 string with a zero offset. See [
Timestamp]. - Optionality. An optional field absent means the source did not report it, and is never rendered as a zero, an empty string, or a default. Absent serializes as absent and parses back as absent.
- Reported numbers. A numeric field a domain declares a plausibility
range for is typed [
Reported<f64>]. See [reported] for the whole rule, including why a non-finite value is out of range and why one is refused on emission; the ranges themselves are the declaring domain's.
Why serde, schemars and serde_json are re-exported
The three port crates depend on this crate and on nothing else, and each of
them declares request and answer shapes that cross a process boundary and so
must derive the same traits these types derive. They reach the derive macros
through [serde], [schemars] and [serde_json] here rather than by
declaring a dependency of their own.