About the Log
The Log is the record of everything governed that happens in a repository. Each check result, each write to the tracker or the code host, each run of a task and each archive is one event. An event says what happened, to what, who did it, and when.
The Log belongs to the harness, not to a process. It reads the same whatever process runs on top. Studio, a harness feature, is one reader of it.
How it works
- 1
Something governed happens
A check runs, a task round ends, an Issue is written. The part of the harness that owns that act builds one event for it.
- 2
The event is checked
Every event follows one typed contract: a closed family, a fixed event name, a header the harness builds, and strict fields. A line that does not fit is never written.
- 3
Secrets are removed
Secrets are redacted and home paths rewritten before the line is written, and again before it is sent or read back.
- 4
The event is delivered at once
It goes to the destination you configured, as it happens. Events are never batched at the end of a run, and delivery never slows or fails the run.
- 5
Telemetry reads it back
vinaya syncgathers stored events into a local cache, where Telemetry's questions about cost, time and review strictness read them.
In this section
Event contract
Families, header, schema versions
Delivery
Folder or server, live, never the forge
Custom events
Your team's own declared events
Redaction and trust
Secrets, provenance, trusted destination
Record your own events
A repository can declare events of its own under logs.events and record them with vinaya log emit. Each one is checked against its declaration and written like any other event. See Custom events.
The commands
log selftest
$ vinaya log selftestProve log delivery works end to end: send one marked test event and read it back — PASS (exit 0) or FAIL with the one reason (exit 1)
Resolves the destination exactly as an UNATTENDED review-loop run does in this repository — the same trust-anchor read and credential lookup, forced unattended so running it by hand proves the launch path's own resolution, not a human's. It sends one test event to the configured logs.url server, then reads it back from the server by the event's own id through the server's cursor read.
PASS (exit 0) only when the event round-trips. Otherwise FAIL (exit 1) with the ONE reason that stopped it: no server is configured, the default branch's config could not be read to confirm the url (and why), this host holds no ingest or read credential, the server refused with its status, or the event could not be read back. It never prints a credential value.
The read-back needs a READ credential distinct from the write-only ingest one the server refuses on a read route: set logs.readHeaders in vinaya.config.json to the server's read token (referenced by variable name, resolved from the environment exactly as logs.headers is). The test event is marked operation: 'log.selftest' so a reader can exclude it from real telemetry.
log emit
$ vinaya log emitRecord a consumer-declared event (`logs.events` in vinaya.config.json) from a script or agent outside Vinaya
Reads the field values as ONE JSON object — --json '<json>' or piped on standard input, never positional arguments — and records them through emitCustomEvent, the same chokepoint every custom event goes through, so the refusal event and the redaction stay in one place.
A declared event with every field present and of its declared type exits 0 and prints the event name and the kind of destination it was recorded toward (folder, server, or none) — never a field value. A refused event (an undeclared name, or a missing, extra, wrong-typed or too-long field) exits 1 and prints the reason class and the field names — never a value, since a rejected value may be the secret. A missing event name, unreadable JSON, or JSON that is not a flat object is a usage error: exit 2, before anything is recorded.
A process with only VINAYA_WORK_REF and VINAYA_FLOW set, and no Vinaya role or task, still gets a header carrying that work reference and way-of-working id — the same environment read every event already honours (apps/cli/specs/log.md § Attribution).
--json '<json>'The field values as one JSON object (else read from standard input)log send
$ vinaya log sendDeliver this repository's locally-held log events to the configured `logs.url` server, once
For a run whose events ended up on local disk instead of the configured server — an unattended run that fell back to the local folder because the trust-anchor read could not confirm its logs.url (a developer machine slower than the read deadline, or genuinely offline), or a retry queue holding a backlog or a .draining.ndjson a dead drain left behind. It moves the local folder's events into the retry queue and drains every queue for this repository through the SAME webhook delivery a live event uses.
It resolves the destination exactly the way a normal event does — the sink's own decision over vinaya.config.json's logs setting, trust-anchor-gated for an unattended caller — never a URL passed on the command line. A repository whose destination is a folder (or none) has nothing to send to a server and the command says so.
Safe to run more than once: the server stores events by event_id and ignores one it already holds, so a re-sent chunk creates no duplicate, and a second run finds the folder emptied and the queue drained.
--jsonEnveloped JSON output (schema: 1)sync
$ vinaya syncFill, resume and rebuild the local log cache from this repository's configured destination (read-only)
Resolves the destination exactly the way a normal event does — the sink's own decision over vinaya.config.json's logs setting, trust-anchor-gated for an unattended caller — then reads a folder destination through the folder source or a server destination through the server source (with the configured logs.readHeaders, substituted from the environment), and runs the bounded sync engine against the durable cache this repository holds under its runtime directory. Prints pages read, rows stored, duplicates, edits, deletions, gaps, quarantined lines and, for a server destination, its lost-event diagnostic. Reads the destination and writes only its own cache directory — never the folder, the server, or the forge.
Exits 0 when the run completed or stopped cleanly on its page bound (and says more is available), 1 when it failed part way (the progress made is kept — a second run resumes from the stored cursor), and 2 for a usage or configuration problem: no destination is configured, or a server destination is configured with no usable read credential. A missing credential names the environment variable to set and never prints its value.
--rebuild deletes the cache file ALONE — never anything else under the cache directory — and syncs again from the beginning of what the destination still retains; an event already rotated out of a folder's single backup slot, or no longer retained by the destination, cannot be recovered and shows up as a gap, never silently.
--rebuildDelete the cache file and sync from the beginning of what the destination still retains--jsonEnveloped JSON output (schema: 1)Configuration
Set the destination under logs in vinaya.. It names exactly one destination; with no logs set, events go to a folder under runtimeDir.
| Key | What it sets |
|---|---|
logs.folder | A folder destination: an absolute path outside the repository |
logs.url, logs.headers | A server destination and its ingest credential, as ${VAR} filled from the environment |
logs.readHeaders | The server's read credential, used by vinaya sync |
logs.events | Your declared custom events |
An unattended run, such as an agent or a CI job, takes its destination from the default branch only, so a pull request cannot redirect events. See Redaction and trust and Configuration.