Docs

Log

Event contract

The typed shape every Log line must have: ten closed event families, a versioned header, strict objects, and rules that keep old lines readable as the schema grows.

10 families
Schema 1–3
Strict
Additive only

About the event contract

Every line in the Log is one event, and every event follows one contract. A single schema defines every valid line. Each line is checked against it before it is written, and again before it is sent or read back. A line that does not fit is never written.

The contract is pure: building and checking a line touches no file, network or process. It ships in the @attalabs/aeg-core/log package, with one schema per family, one for the header, and one for the whole line.

This page states the rules. For every field of every event, see Events.

How it works

Each line has three parts:

  • The header (meta and subject): who wrote the line, when, where, and about what. The harness builds it. A caller passes only the event itself and can never set its own header.
  • The family and event (kind and event): what happened.
  • The family's own fields: the details of that event.

Every line reads, in order: meta, subject, kind, event, duration_ms (optional), payload, then the family's fields. payload is always an empty object {}; families carry their content in named fields.

Families

kind is one of ten families. It is a closed list. event is one of a fixed set within each family.

FamilyEventsHas a producer
dispatchdispatched, outcome_received, dispatch_failedYes
dev_review_looploop_started, round_started, gate_result_read, verdicts_read, findings_compared, stop_condition_met, paused, resumed, cancelled, unpushed_work_resume, round_ended, journal_finalized, infrastructure_retry, driver_heartbeat, driver_exitedYes
forge_writevalidated, refused, writtenNo, kept in the schema only
gatesummary, checkedYes
operationcompletedYes
usageobservedYes
role_attemptattemptedYes
handoffraised, resolvedYes
effectverified; attempted and observed in the schema onlyverified only
customrecordedYes

The old families command and tokens are refused by name: command became operation, and tokens became usage.

The header

The header has a version, meta.schema. Each version adds fields to the one before.

VersionAdds
1ts, run_id, seq, repo, vinaya, doctrine, host, machine
2event_id, process_id, actor_id, lineage { run, attempt, parent }, input_versions { manifest_version?, objectives_version, brief_hash, ruling_ordinal, pause_bound_ruling?, policy_digest }, provenance
3work { ref, repo, change, revision }, flow { id, version }, runtime, source

subject always holds issue (null when none) and role; optionally pr, sha, round and objectives_version.

  1. 1

    New lines are always schema 3

    The harness writes schema: 3 on every new line. Versions 1 and 2 exist only so old lines keep parsing.

  2. 2

    Missing data is null

    Data the harness cannot find is null, never invented. The one default is flow.id, which reads vinaya when unset.

  3. 3

    Foreign values pass through unchanged

    subject.role is one of the known roles, or unattributed. meta.actor_id, work.ref and flow.id are opaque, so an actor from another tool, a work reference that is not an Issue, and a flow that is not Vinaya are written and read back as they are.

  4. 4

    Unsafe run ids are refused

    run_id must match ^[A-Za-z0-9_.-]{1,128}$. Any other value is refused when the line is written.

Strictness

  • No extra keys. Every object is strict. An extra key anywhere, including inside payload, fails the check.
  • A failing line is dropped. It is not written, and the harness warns once per process.
  • Closed lists. Every list of values is closed, except where the schema keeps a free string on purpose: severity_scale, paused.reason_code (a code token), operation, usage.source and role_attempt.actor.
  • Bounded strings.
ValueLimit
confidence_reason280 characters
Finding description1000 characters
Code tokens^[A-Za-z0-9._-]{1,64}$
Custom event text500 characters

Compatibility

The schema only grows, so a line written today can still be read tomorrow, and a line written long ago can still be read today.

Compatibility is proven by tests that replay saved example lines of every version.

Roll out a schema change

The Log server is strict about unknown keys: a line carrying a field the server does not know is stored as an unknown-version record. So the order matters.

  1. 1

    Deploy the server's schema first

    Redeploy the Log server with the new schema: run bunx wrangler deploy from apps/log-server.

  2. 2

    Then release the CLI

    Release the CLI version that writes the new fields only after the server knows them.

  3. 3

    Check that they match

    Run vinaya log selftest, or vinaya doctor with logs.readHeaders set. Both compare the server's schema digest with the CLI's. A mismatch or a missing digest is a warn in doctor and a FAIL in selftest, and the message names the redeploy command.

Configuration

There is nothing to configure. The contract is fixed. The only way to add event names is logs.events, which adds names inside the custom family; see Custom events.

What's next