Documentation
¶
Overview ¶
Package archive implements the transcript archive: one git repository shared by all of a user's machines, holding each machine's raw transcript trees under a top-level directory per machine (`<machine>/<source>/...`). The repo is the durable home for raw session bytes — local index dbs stay disposable derived caches.
All git knowledge, clone-layout knowledge, and machine-manifest handling live behind this package's small interface; nothing else in the codebase learns git. Git runs via the system binary through the unexported run seam (one real exec adapter, a fake for unit tests).
Index ¶
- Constants
- Variables
- func AcquireAutosyncToken(now time.Time) bool
- func AutosyncLogPath() string
- func SSHDestination(machine string) (string, error)
- type Archive
- func (a *Archive) ClonePath() string
- func (a *Archive) ForeignProjectMatches(project string) []string
- func (a *Archive) ForeignSessionMatches(id string) []string
- func (a *Archive) LookupScopes() []view.Scope
- func (a *Archive) Name() string
- func (a *Archive) Pull(ctx context.Context, throttle bool) (pulled bool, err error)
- func (a *Archive) PushLocal(ctx context.Context) (PushReport, error)
- func (a *Archive) Remote() string
- func (a *Archive) Scopes(ctx context.Context, reindex bool) []view.Scope
- func (a *Archive) SetTagExporter(fn TagExporter) *Archive
- func (a *Archive) Status(ctx context.Context) (StatusReport, error)
- type Config
- type MachineStatus
- type PushReport
- type StatusReport
- type TagExporter
- type TagFile
- type TagSegment
- type TagVerdict
Constants ¶
const PrivacyWarning = `` /* 213-byte string literal not displayed */
PrivacyWarning is printed by `archive init`. Transcripts contain whatever was pasted into sessions; no host-side detection of repo visibility is attempted (unreliable across git hosts) — this warning is the mechanism.
Variables ¶
var ErrBusy = errors.New("another rawclaw archive sync is already running on this machine")
ErrBusy reports that another rawclaw process on this machine holds the archive sync lock. Callers treat it as a clean "someone else is already syncing" no-op, not a failure: the holder's run (or the next sync) covers the same trees.
Functions ¶
func AcquireAutosyncToken ¶
AcquireAutosyncToken reports whether a background sync may spawn now, and atomically claims the slot when it may. The token is claimed at SPAWN time (not child completion), so a burst of invocations spawns one child even while that child is still running. Claiming = removing the stale token and re-creating it O_EXCL: of N racing processes exactly the creators win, and the rare double-winner (remove/create interleaving) costs one extra sync — the token rate-limits, it does not guard correctness (the flock does).
func AutosyncLogPath ¶
func AutosyncLogPath() string
AutosyncLogPath is <state-dir>/archive/autosync.log — where the detached sync child's output lands (its receipt trail). The spawner redirects the child's stdout+stderr here; nothing is ever written to the invoking terminal.
func SSHDestination ¶
SSHDestination resolves a machine name to the ssh destination `rawclaw live` dials: the config's ssh-map entry when one exists, else the name itself (letting ~/.ssh/config aliases carry user/port/key). Live peek works without a configured archive, so this reads the config file leniently — a missing file, or one without the archive's own remote/name fields, still resolves (an ssh-map-only config is valid for live). A config that EXISTS but cannot be parsed is an error: silently dropping a user's ssh map would dial the wrong destination and then blame the name.
Types ¶
type Archive ¶
type Archive struct {
// contains filtered or unexported fields
}
Archive is a configured transcript archive: a remote, this machine's dir name, and the local clone the push/pull verbs operate on. Obtain one via Load (nil when the feature is unconfigured) or Init.
func Init ¶
Init bootstraps the archive: clones the remote (an empty remote clones fine; its default branch is born on the first push), registers this machine (a human-readable dir name + a manifest carrying the stable machine id), pushes the registration, and writes the config Load reads from then on.
Init refuses a machine dir already claimed by a DIFFERENT machine_id — pick another name with --name. A live config also refuses (with a pointer at the file to remove); after state loss (config gone, machine-id intact) re-init against the same remote is idempotent — the machine reclaims its own dir.
func Load ¶
Load resolves the archive configuration (env + config file). It returns (nil, nil) when unconfigured — every caller treats nil as "feature off", so the zero state costs one nil-check. RAWCLAW_ARCHIVE=off force-disables a configured archive. A present-but-unreadable config is an error: the user configured the feature and deserves to know it broke.
func (*Archive) ForeignProjectMatches ¶
ForeignProjectMatches reports the foreign machine names whose dir name or scope labels contain the project substring — the delete verb's guard. Foreign sessions are read-only from every box (no cross-machine delete in v1), so a delete filter that reaches into a foreign machine's scopes must be named, never silently ignored. Matching mirrors what search shows the user: the machine name and the "<machine>/<label>" scope labels — labels rebuilt here from the same layout claudeScopes/codexScopes read, rather than through Scopes() itself, because Scopes() ingests every scope into its cache db (far too heavy for a delete-time guard). Offline, clone-only; an absent clone matches nothing. A machine matches only if it actually holds at least one matching SESSION-bearing scope (or its name matches and it holds any sessions at all): naming a machine with nothing in it would send the user chasing sessions that do not exist.
func (*Archive) ForeignSessionMatches ¶
ForeignSessionMatches reports the foreign machine names holding a session the given id addresses (lifecycle.MatchesSessionID: exact id, or a >=8-char prefix) — the positional delete's counterpart to ForeignProjectMatches. Same posture: offline, clone-only, best-effort — an absent clone matches nothing, and the caller turns a foreign-only hit into the read-only refusal naming the origin machine.
func (*Archive) LookupScopes ¶
LookupScopes enumerates the same foreign scopes as Scopes WITHOUT ingesting anything: each scope's DBP names the cache db a previous search-time ingest would have built; a scope never ingested simply fails to open read-only at the caller. This is the cheap path for point lookups (e.g. resolving a --resume prefix) where walking and indexing every foreign tree would be far too heavy. No staleness git probe runs either (Stale stays false — lookups don't report freshness), which keeps this path free of child processes, so it needs no watchdog ctx.
func (*Archive) Pull ¶
Pull refreshes the clone from the remote (re-cloning it if it is missing — deleting a corrupt clone and pulling is the documented recovery). With throttle=true it no-ops unless the last successful pull is older than pullThrottleWindow, judged by the stamp file's mtime in the state dir; the explicit CLI verb passes false and always pulls. pulled reports whether the remote was actually consulted: true after any successful refresh — including "already up to date" and a still-empty remote (its branch is born on the first push; nothing-there is a verified-fresh state) — false only on a throttled skip, so callers can render the two honestly.
func (*Archive) PushLocal ¶
func (a *Archive) PushLocal(ctx context.Context) (PushReport, error)
PushLocal copies this machine's transcript trees into the clone, commits, and pushes (pull --rebase + push, bounded retries). Idempotent; safe mid-session (transcripts are append-only, so a half-written file in the archive is valid and superseded by the next push). Returns a report for status output and logging.
func (*Archive) Scopes ¶
Scopes enumerates the clone's FOREIGN machine dirs as ready-to-search scopes: each foreign machine's Claude project dirs and Codex cwd-groups, ingested through the existing index paths into their own namespaced cache dbs, with origin_machine stamped from the dir's manifest. Our OWN dir is excluded — the live local tree is fresher and already indexed; that exclusion is what makes cross-machine dedup a non-event. A missing clone yields nil (enumeration never touches the network; `archive pull` is the refresh path). reindex forces a full rebuild of the scope dbs, mirroring the local scopes. ctx bounds the per-machine staleness git probes (dirStale), so they die with the caller's watchdog like every other git child.
func (*Archive) SetTagExporter ¶ added in v0.6.0
func (a *Archive) SetTagExporter(fn TagExporter) *Archive
SetTagExporter wires the local-tag source used by PushLocal. Returns the Archive for call chaining at the cli seam.
func (*Archive) Status ¶
func (a *Archive) Status(ctx context.Context) (StatusReport, error)
Status reports clone path, remote, last push/pull (with own-sync overdue flags), and per-machine last-new-content times. A missing clone is a reported state (CloneOK=false, no machines), not an error — `archive pull` is the repair path.
type Config ¶
type Config struct {
Remote string `json:"remote"` // git remote URL of the archive repository
Name string `json:"name"` // this machine's top-level dir name in the repo
// SSH optionally maps a machine name to the ssh destination `rawclaw live`
// dials (e.g. "box-a": "user@10.0.0.5"). Unmapped names default to the
// name itself, so an ~/.ssh/config Host alias needs no entry here.
SSH map[string]string `json:"ssh,omitempty"`
}
Config is the archive configuration persisted in the state dir by `archive init` and read back by Load.
type MachineStatus ¶
type MachineStatus struct {
Name string // top-level dir name in the archive
MachineID string // stable machine id from the dir's manifest
Own bool // this machine's own dir
LastCommit time.Time // last commit touching the dir (zero = none yet) — last NEW CONTENT, not liveness
}
MachineStatus is one machine dir's recorded state in the clone.
type PushReport ¶
type PushReport struct {
Copied int // files copied into the clone this push
Removed int // tombstoned own sessions removed from the clone this push
TagFiles int // tag files written into <machine>/tags/ this push
Committed bool // a commit was created (false = nothing changed)
Pushed bool // the commit reached the remote
Retries int // rebase-retry rounds needed before the push landed
}
PushReport summarizes one PushLocal run for status output and logging.
type StatusReport ¶
type StatusReport struct {
Remote string // configured remote URL
Clone string // local clone path
CloneOK bool // a COMPLETED clone exists (sentinel present — ensureClone's own predicate)
LastPush time.Time // last successful push sync from this machine, incl. verified no-ops (zero = never)
LastPull time.Time // last successful pull on this machine (zero = never)
PushOverdue bool // a recorded push sync exists but is older than the window (never ≠ overdue)
PullOverdue bool // a recorded pull exists but is older than the window (never ≠ overdue)
Machines []MachineStatus // one entry per machine dir in the clone, own first
TagConflicts []string // sessions whose cross-machine tags disagreed; the winner is deterministic and every side's tag file is retained
}
StatusReport is the raw material for `archive status` and doctor-style output: where the archive lives, when this machine last synced, and when each machine's dir last received new content. Status is an OFFLINE read — recorded state only (config, stamp files, the clone's git history); it never fetches.
The only staleness verdicts here are the own-sync overdue flags: this machine knows first-hand when ITS last successful push/pull ran. A foreign machine's freshness is deliberately NOT judged — from the clone alone an idle-but-healthy machine (nothing new to commit) and a dead one are indistinguishable, so per-machine state is reported as the honest LastCommit ("last new content") and nothing more.
type TagExporter ¶ added in v0.6.0
TagExporter returns THIS machine's locally-authored tag files. It is injected from the cli seam (SetTagExporter) because collecting local tags means enumerating local scopes + reading the store — deps the archive package deliberately does not carry (internal/scopes imports archive; the reverse would cycle). nil = the archive was built without the tag feature wired, so push simply skips tag export (transcripts still sync).
type TagFile ¶ added in v0.6.0
type TagFile struct {
SessionID string `json:"session_id"`
OriginMachine string `json:"origin_machine"`
Segments []TagSegment `json:"segments,omitempty"`
Verdict *TagVerdict `json:"verdict,omitempty"`
}
TagFile is one session's tagging, serialized as `<machine>/tags/<session>.json`. It is the whole authored unit for that session on the writing machine: the segment set plus the optional verdict, stamped with the origin machine id the cross-machine ingest resolves on. One file per session — a re-tag overwrites it (dedup by path), and it diffs cleanly in git.
type TagSegment ¶ added in v0.6.0
type TagSegment struct {
StartUUID string `json:"start_uuid"`
EndUUID string `json:"end_uuid,omitempty"`
Topic string `json:"topic"`
Summary string `json:"summary,omitempty"`
TaggedAt float64 `json:"tagged_at,omitempty"`
}
TagSegment is one topic segment in a TagFile.
type TagVerdict ¶ added in v0.6.0
type TagVerdict struct {
Verdict string `json:"verdict"`
Source string `json:"source"`
TaggedAt float64 `json:"tagged_at,omitempty"`
}
TagVerdict is a session's verdict in a TagFile (e.g. routine + floor|agent).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package archivetest stands up throwaway transcript-archive fixtures: an isolated HOME with one local Claude project, a configured archive against a local bare remote, and one FOREIGN machine dir pushed by a simulated second machine and pulled into the local clone.
|
Package archivetest stands up throwaway transcript-archive fixtures: an isolated HOME with one local Claude project, a configured archive against a local bare remote, and one FOREIGN machine dir pushed by a simulated second machine and pulled into the local clone. |