sessionmap

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package sessionmap is the hook-written state that binds a Claude Code process to its Brigade session (plan 3.2, 6.5): the by-pid map at ${stateDir}/sessions/by-pid/<claude_pid>.json, from which the session-bound commands and the watcher resolve EVERYTHING (profile, config dir, adapter argv, Brigade session id, team, socket path), and the by-native map at ${stateDir}/sessions/by-native/<claude_session_id>.json, which lets a later `claude --resume` re-open the same Brigade session (3.7). Neither map ever holds the messaging token (3.2).

Trust: the by-pid map is an unauthenticated trust boundary guarded by filesystem permissions alone (E0-7) — whoever can write it decides which profile and team a session acts as. The reader therefore applies every check available and refuses anything else with `config`, details.reason "map_not_private": a regular file opened without following a symlink, mode granting nothing to group or other, owned by the current uid, within the size cap, valid JSON of the expected shape whose claude_pid matches its file name. A hook that rewrites the map on every SessionStart (E0-8: it re-fires on /clear) is what keeps a planted map from surviving to the first Bash call; this package supplies the overwrite semantics that idempotence needs.

Every side effect here is a file under the caller's Store.StateDir; the clock is the caller's (RegisteredAt/UpdatedAt are set by the hook) and the owner check is injectable through Store.Stat, so tests stay hermetic.

Index

Constants

View Source
const (
	// ReasonMapNotPrivate: the file failed the privacy checks (mode, owner,
	// symlink, regular file). details.check names which.
	ReasonMapNotPrivate = "map_not_private"
	// ReasonMapMalformed: the file is not a JSON object of the expected
	// shape, or exceeds MaxMapBytes.
	ReasonMapMalformed = "map_malformed"
	// ReasonMapInvalid: the JSON parsed but a member fails Validate;
	// details.field names it.
	ReasonMapInvalid = "map_invalid"
	// ReasonMapMismatch: the by-pid file's claude_pid is not the pid in its
	// file name (a copied or planted file).
	ReasonMapMismatch = "map_mismatch"
	// ReasonMapUnreadable: an I/O failure other than "missing" — the
	// underlying error is never echoed.
	ReasonMapUnreadable = "map_unreadable"
	// ReasonStateDirRelative: the Store's StateDir is empty or relative;
	// the harness always computes an absolute one (3.2).
	ReasonStateDirRelative = "state_dir_relative"
)

The details.reason values of the `config` errors this package returns.

View Source
const MaxMapBytes = 64 << 10

MaxMapBytes caps what the strict reader will read of a map file. A real map is well under 2 KiB; anything larger is not one.

View Source
const MaxNativeIDLen = 80

MaxNativeIDLen is the length cap of a native session id used as a file name.

Variables

This section is empty.

Functions

func CheckAdapterCommand

func CheckAdapterCommand(argv []string) error

CheckAdapterCommand validates a resolved adapter argv prefix: every element non-empty and, when there is one, the first an absolute path. An empty argv is valid and means the bundled adapter. A relative executable is refused because a planted map must not be able to make the harness exec a path relative to whatever the cwd happens to be.

func CheckNativeID

func CheckNativeID(id string) error

CheckNativeID validates a native session id before it becomes a path component: 1–80 characters from [A-Za-z0-9_-]. Claude Code's ids are UUIDs, which pass; anything with a separator, a dot or a non-ASCII rune is refused, so a hostile id in hook stdin cannot escape the by-native directory. The offending value is never echoed.

Types

type ByNative

type ByNative struct {
	BrigadeSessionID string    `json:"brigade_session_id"`
	TeamRef          string    `json:"team_ref"`
	SessionName      string    `json:"session_name"`
	UpdatedAt        time.Time `json:"updated_at,omitzero"`
}

ByNative is the by-native map (plan 3.2, 3.7): keyed by the native Claude Code session id, it survives session end so that `claude --resume <id>` finds the Brigade session to re-open. It is OVERWRITTEN on every write: a native id RECURS within one process (E0-5 item 6: `/clear` then `/resume` returns the id to its pre-clear value), so the map tolerates a returning id by design rather than assuming ids are monotonic.

func (*ByNative) Validate

func (m *ByNative) Validate() error

Validate requires the one member a resume hint cannot do without.

type ByPID

type ByPID struct {
	// ClaudePID is the Claude Code process id; it is also the file name.
	ClaudePID int `json:"claude_pid"`
	// ClaudeSessionID is the native session id from the hook's stdin; it
	// is never sent to any backend (T10).
	ClaudeSessionID string `json:"claude_session_id"`
	// BrigadeSessionID is the adapter-assigned session id (4.4.3).
	BrigadeSessionID string `json:"brigade_session_id"`
	// TeamRef and TeamName are the profile's team binding at registration.
	TeamRef  string `json:"team_ref"`
	TeamName string `json:"team_name"`
	// SessionName is the display name registered (6.5).
	SessionName string `json:"session_name"`
	// PermissionMode is recorded for diagnostics only; the inbound policy
	// never depends on it (D18).
	PermissionMode string `json:"permission_mode"`
	// NonInteractive is true for a `claude -p` session (CLAUDE_CODE_ENTRYPOINT
	// sdk-cli, 6.5); diagnostics only.
	NonInteractive bool `json:"non_interactive"`
	// Inbound is the EFFECTIVE policy the hook chose: accept, hold or
	// refuse (6.8).
	Inbound string `json:"inbound"`
	// FrameLevel is the instruction paragraph the frame carries: open,
	// guarded, strict, or custom for a user-supplied clause (P5-12). The
	// hook resolves it ONCE at SessionStart and freezes it here, so the
	// watcher never re-reads a user file minutes or hours later.
	FrameLevel string `json:"frame_level"`
	// FrameText is the resolved custom clause, EMPTY unless FrameLevel is
	// custom: a named level's text is a Go constant and is never carried
	// here, so a rewritten map cannot smuggle a different paragraph in
	// under a level's name.
	FrameText string `json:"frame_text"`
	// SocketPath is CLAUDE_CODE_MESSAGING_SOCKET as the hook saw it, or ""
	// on a host without an inbox socket. The token is NOT here.
	SocketPath string `json:"socket_path"`
	// Profile is the resolved profile name.
	TeamKey string `json:"team_key"`
	// ConfigDir is the resolved absolute Brigade config directory.
	ConfigDir string `json:"config_dir"`
	// AdapterCommand is the resolved argv prefix prepended verbatim to
	// every adapter invocation; [] means the bundled adapter, which the
	// adapterclient resolves through os.Executable() at spawn time and
	// which is deliberately never pinned to a path here.
	AdapterCommand []string `json:"adapter_command"`
	// PluginBin is the bootstrap's resolved realpath when the hook knows
	// it (the shadowing check of 6.2), else "".
	PluginBin string `json:"plugin_bin"`
	// HarnessVersion is the Claude Code version from the registry's
	// `version` member when present, else "unknown".
	HarnessVersion string `json:"harness_version"`
	// RegisteredAt is when the Brigade session was registered; UpdatedAt
	// when this file was last rewritten. Both are the hook's clock.
	RegisteredAt time.Time `json:"registered_at,omitzero"`
	UpdatedAt    time.Time `json:"updated_at,omitzero"`
}

ByPID is the by-pid map (plan 3.2): everything a session-bound command or the watcher needs, resolved by the hook once at SessionStart. It carries RESOLVED values only — the profile after its default applied, the config dir as an absolute path, the adapter as the argv prefix ResolveAdapter produced — never a raw option value and never the token.

func (*ByPID) Instruction

func (m *ByPID) Instruction() frame.Instruction

Instruction is the frame instruction the map carries, for the two injectors (the watcher and the prompt-hook poll) to hand to inbound.Config. It is built, never validated, here: ReadByPID has already validated the members, and inbound.New validates it again as its own layer.

func (*ByPID) Validate

func (m *ByPID) Validate() error

Validate checks the members every by-pid map must carry before it is written or trusted: a positive pid, a Brigade session id, a valid profile name, an absolute config dir, an inbound value this harness implements (accept, hold or refuse), a well-formed adapter argv, an absolute or empty socket path, and the frame members of P5-12 (a level among the four; a text only under custom, and then a non-empty one within frame.MaxCustomBytes that passes frame.CheckClause). The failure is `config` with details.field naming the member; the value is never echoed.

type Store

type Store struct {
	// StateDir is ${BRIGADE_STATE_DIR} as resolved by the caller.
	StateDir string
	// Stat, when non-nil, replaces the fstat of the opened file in the
	// privacy check. It exists for tests: a foreign-uid file cannot be
	// created without privileges, so the foreign-owner refusal is
	// exercised through a FileInfo whose Sys reports another uid. Leave
	// it nil everywhere else — the fstat on the open descriptor is what
	// makes the check free of a check-then-open race.
	Stat func(path string) (fs.FileInfo, error)
}

A Store reads and writes the two maps under one state directory. The zero Store is not usable: StateDir must be absolute (the harness always computes it, 3.2; config.BrigadeStateDir applies the in-session rule).

func (Store) ByNativeDir

func (s Store) ByNativeDir() string

ByNativeDir is ${stateDir}/sessions/by-native.

func (Store) ByNativePath

func (s Store) ByNativePath(id string) (string, error)

ByNativePath is the by-native map file for id, after validating id as a path component (CheckNativeID).

func (Store) ByPIDDir

func (s Store) ByPIDDir() string

ByPIDDir is ${stateDir}/sessions/by-pid.

func (Store) ByPIDPath

func (s Store) ByPIDPath(pid int) (string, error)

ByPIDPath is the by-pid map file for pid, after checking the Store and the pid.

func (Store) DeleteByPID

func (s Store) DeleteByPID(pid int) error

DeleteByPID removes the by-pid map at SessionEnd (3.8). A missing file is not an error; the by-native map is deliberately left in place.

func (Store) ReadByNative

func (s Store) ReadByNative(id string) (*ByNative, error)

ReadByNative reads the by-native map for id through the strict reader. A missing file satisfies errors.Is(err, fs.ErrNotExist): no resume hint. Other failures are `config` as for ReadByPID.

func (Store) ReadByPID

func (s Store) ReadByPID(pid int) (*ByPID, error)

ReadByPID reads the by-pid map for pid through the strict reader and validates it. A missing file is returned as an error for which errors.Is(err, fs.ErrNotExist) holds (the caller maps it to `not_registered`); every other failure is a *protocol.Error with the `config` code and one of this package's Reason* details.

func (Store) WriteByNative

func (s Store) WriteByNative(id string, m *ByNative) error

WriteByNative validates m and writes the by-native map for id, overwriting any previous entry (a native id recurs, E0-5).

func (Store) WriteByPID

func (s Store) WriteByPID(m *ByPID) error

WriteByPID validates m and writes it atomically (0600 in a 0700 directory chain), overwriting any previous file for the same pid: the hook rewrites the map on every SessionStart, including the one `/clear` re-fires (E0-8). A nil AdapterCommand is written as [].

Jump to

Keyboard shortcuts

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