Skip to content

Standalone Cast format

A native recording stores CBOR events, not Asciicast lines. Offline export reconstructs one signed asciicast v3 .cast for playback and full verification. Consult the native vectors for real bytes and the operator guide before exporting sensitive content.

See the byte-exact signed Cast example or the Cast exports in the native recording vectors. Shared container framing belongs to container.md; audit.md describes the other native family.

Canonical standalone Cast

The native writer and offline exporter use the same asciicast v3 rendering rules. Every line ends in one LF byte (0a); there is no BOM, CRLF, extra whitespace, or automatic plaintext file.

Header and metadata

The first line is compact JSON with fields in this order: version (3), term (cols, rows, optional type), and timestamp (Unix seconds).

The second line starts with the literal # bifroest:metadata:v1 and compact JSON. Its fields are schema, recordingId, connectionId, sessionId, operationId, flow, task, pty, producerId, and startedAt, in that order.

Timestamps in these comments use Go's JSON encoding of UTC time.Time (RFC 3339 with the necessary fractional digits). UUIDs and the producer ID use canonical lowercase text. Comment objects use the established Go JSON encoding of the fixed-field structs, including JSON escaping of HTML characters, U+2028, and U+2029.

Split and annotate output

Output is divided into events of at most 65,536 raw bytes. A split does not divide a valid multibyte UTF-8 rune when a complete rune fits within the limit.

For stderr, or for an event containing invalid UTF-8, immediately before its o event emit # bifroest:event:v1 followed by compact JSON fields schema, sequence, stream and, only for invalid UTF-8, raw. raw is the original bytes encoded as JSON Base64. The displayed event string replaces each maximal contiguous run of invalid UTF-8 bytes with one U+FFFD (the strings.ToValidUTF8 rule). The sequence counts all events, not only output events.

Render event lines

The event line has the exact shape [seconds.mmm,"code",json-string], without spaces. Codes o, r, m, and x denote output, resize (colsxrows), marker, and decimal exit status. Absolute event time is rounded to the nearest millisecond, with half a millisecond rounded up. The line contains the difference from the previously rounded event.

Strings escape quotes, backslashes, standard JSON controls, non-printing Unicode and U+2028/U+2029. They use lowercase hex in \u escapes and UTF-16 surrogate pairs where necessary. They do not use comment JSON's HTML escaping.

Align each checkpoint

Every nonfinal native chunk ends with a logical padding event. Only the Cast renderer emits its line: # becast-checkpoint-padding:v1, zero or more ASCII 0 bytes, then LF. Choose the number of zeros from 0..63 so that the Cast SHA-256 input byte count, including its domain prefix, becomes divisible by 64.

The native chunk stores no Cast or JSON line. See recording checkpoints for the signed chaining state.

Finish and sign the Cast

An optional x exit event precedes # bifroest:result:v1 plus compact JSON fields schema, status, endedAt, and optional reason. The domain-separated SHA-256 digest includes all lines from the header through this result line, with each LF, but not the signature line.

The final line is # bifroest:signature:v1 followed by compact JSON fields schema, recordingId, producerId, digest, publicKey, and signature, then LF. digest is lowercase hex; publicKey is the Base64-encoded binary SSH public key. signature is Base64 of the Ed25519 signature over the specified domain and the compact JSON of the preceding five fields.

The standalone Cast keeps schema bifroest.asciicast-signature/v1 and domains BIFROEST-ASCIICAST-CONTENT-HASH/v1\x00 and BIFROEST-ASCIICAST-SIGNATURE/v1\x00. The signed native final chunk and seal already bind this digest and signature.

VerifyCast accepts some non-canonical standalone Cast header/event spellings, but native export always produces the bytes defined here. Parser tolerance does not alter the signed native export.

Keep export explicit

Terminal bytes, stdout/stderr identity, resize dimensions, markers, and other Cast event content remain inside native CBOR chunks. .becast encrypts these chunks; .bcast stores them unencrypted after Zstd compression. Outer metadata necessarily reveals at least recording identity, timing, status, sizes, and recipient identity.

Outer verification without decryption does not claim encrypted inner Cast content is semantically valid.

Offline recovery of .becast remains possible without the recipient private key by using signed continuation state. It does not invent terminal output or override a final chunk's completed status.

A valid .cast export is reconstructed deterministically and checked against the sealed digest and signature. It is never emitted without explicit --with-sensitive. It is the only Asciicast representation; no .cast or .jsonl is generated automatically on disk or at remote targets.