traceupload

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 22 Imported by: 0

README

traceupload

Builds the redacted session archive baum upload and baum setup --upload send to the hosted server the user logged in to. The server reads exactly one ManifestVersion and rejects any other; servers and CLIs of different releases meet, so changing it is an API change (see "Compatibility" in the baumcli README).

Archive contract

  • Redaction runs locally, before anything is archived, with the goredact settings in redactionConfig. It masks detected secrets and does not guarantee that all sensitive content is gone. Repository and worktree roots are sent verbatim, because attribution needs them.
  • Each session has exactly one root transcript and unique non-empty file names. NUL bytes fail the bundle; binary content is not supported. Member paths are opaque and hide the local directory layout. Staging uses private files.
  • The first regular tar member is manifest.json, at most MaxManifestBytes. It lists every file with its redacted size and SHA-256 and records the redaction configuration and finding counts.
  • The server accepts only confined relative paths, each listed once with the declared size and digest. Directories, links and devices are ignored and never satisfy a listed file; any other entry that is not a regular file carries data the manifest does not check, so it is rejected.
  • MaxUnpackedBytes (512 MiB) bounds the decompressed tar stream: manifest, members and tar headers. The builder measures that stream (Bundle.UnpackedSize) as it writes it, and the CLI refuses an archive over the bound before uploading it, because the server's worker would refuse to unpack it.

Documentation

Overview

Package traceupload builds locally redacted archives of agent sessions.

Index

Constants

View Source
const ManifestVersion = "1"

ManifestVersion is the archive schema the CLI emits and the server's ingest worker reads. There is exactly one, and the worker rejects anything else. CLIs and servers of different releases meet, so a new version is an API change that raises apiclient.Protocol.

View Source
const MaxUnpackedBytes int64 = 512 << 20

MaxUnpackedBytes bounds an archive's decompressed tar stream: the manifest, every member and the tar headers between them. The ingest worker holds every member in memory and counts every byte it decompresses against this bound, so it is what one upload can cost the shared worker. Bundle.UnpackedSize is the same stream measured as it is written, so the CLI can refuse an archive the worker would reject before uploading it.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bundle

type Bundle struct {
	File     *os.File
	Manifest Manifest
	Size     int64
	// UnpackedSize is the length of the decompressed tar stream, which the
	// worker bounds by MaxUnpackedBytes.
	UnpackedSize int64
	// contains filtered or unexported fields
}

Bundle owns a temporary tar.gz archive. Close removes all temporary data.

func BuildWithProgress

func BuildWithProgress(
	ctx context.Context,
	sessions []collector.Session,
	repository Repository,
	cliVersion string,
	progress ProgressFunc,
) (_ *Bundle, err error)

BuildWithProgress redacts sessions, writes a manifest, and calls progress after each session is staged. Calls to progress are serialized.

func (*Bundle) Close

func (b *Bundle) Close() error

Close closes the archive and removes its private temporary directory.

type Manifest

type Manifest struct {
	SchemaVersion   string            `json:"schema_version"`
	CreatedAt       time.Time         `json:"created_at"`
	CLIVersion      string            `json:"cli_version"`
	Repository      Repository        `json:"repository"`
	RepositoryRoots []RepositoryRoots `json:"repository_roots,omitempty"`
	Redaction       RedactionSummary  `json:"redaction"`
	Sessions        []SessionManifest `json:"sessions"`
}

Manifest is the metadata stored alongside the redacted sessions.

type ProgressFunc

type ProgressFunc func(completed, total int)

ProgressFunc reports how many sessions have been prepared.

type RedactionSummary

type RedactionSummary struct {
	Tool           string         `json:"tool"`
	Profile        string         `json:"profile"`
	RuleSet        string         `json:"rule_set"`
	Strategy       string         `json:"strategy"`
	FindingsByType map[string]int `json:"findings_by_type"`
}

RedactionSummary describes the local redaction applied to the archive. Tool, Profile, RuleSet, and Strategy pin exactly which detector produced the counts, so an archive records what it was scanned with rather than leaving it to be inferred from its upload date.

type Repository

type Repository struct {
	ID            string            `json:"id"`
	FullName      string            `json:"full_name"`
	RootInventory []RepositoryRoots `json:"-"`
}

Repository identifies the connected repository receiving the sessions.

type RepositoryRoots

type RepositoryRoots struct {
	ID       string   `json:"id"`
	FullName string   `json:"full_name"`
	Roots    []string `json:"roots"`
}

RepositoryRoots binds Git-verified roots from this upload invocation to one connected repository.

type SessionFileManifest

type SessionFileManifest struct {
	// ID identifies the file within its session.
	ID string `json:"id"`
	// Name is the file name the source gave it.
	Name string `json:"name"`
	// Kind is what the file is to its source.
	Kind model.FileKind `json:"kind"`
	// Member is the tar member holding the redacted bytes.
	Member string `json:"member"`
	// Root marks the session's own transcript. Exactly one file has it.
	Root           bool           `json:"root,omitempty"`
	SizeBytes      int64          `json:"size_bytes"`
	SHA256         string         `json:"sha256"`
	FindingsByType map[string]int `json:"findings_by_type"`
}

SessionFileManifest describes one redacted file of a session tree.

The archive stores it under an opaque member the builder chose, not under the path it had on the uploader's machine: reproducing a vendor's directory layout only exists to let a path-based reader run unchanged, and both readers take a file set instead. What survives is what a reader actually needs — the name the source gave the file, which carries its session and agent ids, and what kind of file it is. A source needing a relationship its own files do not record should add a source-owned relation record to the manifest rather than encode it in a directory name.

type SessionManifest

type SessionManifest struct {
	Source    vendors.Slug `json:"source"`
	SessionID string       `json:"session_id"`
	// RepoRoot and WorktreeRoot are the roots the collector resolved on the
	// uploading machine. Optional: an older CLI copy sends neither, and such a
	// manifest stays valid at schema_version "1".
	RepoRoot       string                `json:"repo_root,omitempty"`
	WorktreeRoot   string                `json:"worktree_root,omitempty"`
	ModifiedAt     time.Time             `json:"modified_at"`
	Files          []SessionFileManifest `json:"files"`
	SizeBytes      int64                 `json:"size_bytes"`
	FindingsByType map[string]int        `json:"findings_by_type"`
}

SessionManifest describes one redacted session tree in the archive. It is the file index: nothing outside it is ingestable, and nothing in it may be absent.

func (SessionManifest) At

func (m SessionManifest) At(i int) (string, string, string)

func (SessionManifest) Len

func (m SessionManifest) Len() int

func (SessionManifest) RootFile

func (m SessionManifest) RootFile() (SessionFileManifest, bool)

RootFile returns the session's own transcript.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL