Documentation
¶
Overview ¶
Package kbconfig vendors the canonical KBConfig Go types from sageox-mono's packages/kb/config.go into the ox CLI. The mono package is the source of truth — ox imports nothing from mono (mono is not a Go module dependency), so this is a hand-maintained vendored copy.
Drift between this file and packages/kb/config.go in mono is a security concern: ResolveEffectiveMode encodes a privacy invariant (safety inversion on session_recording) and field names participate in the wire format read by ox. Run scripts/check-kbconfig-drift.sh on every PR that touches this directory.
ox is a READER of the KB config envelope. The mono service writes .sageox/config.yaml during reconcile; the public PATCH /config endpoint validates inputs. ox does NOT write the envelope and does NOT carry the banner-comment generation or the committed_at field on writes.
Index ¶
- Constants
- Variables
- func DefaultSessionRecordingMode(kbType string) string
- func ResolveEffectiveMode(userMode, kbMode string) string
- func ValidateConfig(c KBConfig) error
- func ValidateSessionRecordingMode(m string) error
- func ValidateVisibility(v string) error
- type ConfigYAMLEnvelope
- type FeaturesConfig
- type KBConfig
- type MurmursConfig
- type SessionRecordingConfig
- type SystemConfig
Constants ¶
const ( SessionRecordingAuto = "auto" SessionRecordingManual = "manual" SessionRecordingDisabled = "disabled" )
Session recording modes. Text slugs per project convention (no enums). These mirror sageox-mono's packages/kb/config.go constants. Drift checked by scripts/check-kbconfig-drift.sh.
const ( VisibilityPrivate = "private" VisibilityTeam = "team" )
Visibility values. Text slugs per project convention (no enums).
const ( KBTypePersonal = "personal" KBTypeProfile = "profile" KBTypeTeam = "team" KBTypeRepo = "repo" KBTypeCustom = "custom" KBTypeChannel = "channel" // pre-shipped before mono rollout )
KB type constants vendored from mono. The canonical ox KBType definition lives in internal/api/kb.go (typed as api.KBType); this package keeps a parallel set of untyped string constants so kbconfig-internal logic (defaults, validation, drift) doesn't pull in the api package and create an import cycle.
KBTypeChannel is pre-shipped here ahead of mono's channel rollout per /grill-me Q12: defaulting unknown-type-to-manual is the privacy-safe behavior, but having the constant lets us hard-code the expected default and exercise it in tests.
const ConfigVersion = 1
ConfigVersion mirrors mono's packages/kb/config.go ConfigVersion. Bump only when the schema semantics change (additive fields are backward-compatible).
const ConfigYAMLPath = ".sageox/config.yaml"
ConfigYAMLPath is the path inside a KB's git repo for the canonical config file. Mirrors mono's packages/kb/config_yaml.go ConfigYAMLPath.
Variables ¶
var ValidSessionRecordingModes = []string{ SessionRecordingAuto, SessionRecordingManual, SessionRecordingDisabled, }
ValidSessionRecordingModes is the closed set of allowed values.
var ValidVisibilities = []string{VisibilityPrivate, VisibilityTeam}
ValidVisibilities is the closed set of allowed visibility slugs.
Functions ¶
func DefaultSessionRecordingMode ¶
DefaultSessionRecordingMode returns the per-KB-type default recording mode. Mirrors mono's packages/kb/config.go DefaultSessionRecordingMode byte-for-byte.
Work surfaces default to auto (personal, team, repo) — these are the bubbles people actively work *in* and recording is the value.
Presence surfaces default to manual (profile, custom, channel) — these are curated public/broadcast bubbles where silent auto-recording would surprise contributors.
Unknown / forward-compat KB types fall through to manual: when in doubt, the privacy-safer choice is to require an explicit opt-in to recording.
Changing a default here flips behavior for every bubble that omits the field. Don't move a type from manual to auto without a comms plan and a one-shot migration that stamps the previous default into still-unset rows.
func ResolveEffectiveMode ¶
ResolveEffectiveMode combines the user-layer and KB-layer recording modes into the single mode that governs a session. THE only function call site for combining recording layers — every consumer (ox CLI session start, daemon recording gate, future MCP handlers) MUST route through here so the safety-inversion invariant cannot be sidestepped.
INVARIANT (load-bearing — privacy guarantee):
If EITHER userMode == "disabled" OR kbMode == "disabled", the result is "disabled". This is a logical OR, NOT a precedence rule. A user opting out of recording cannot be overridden by a KB admin; a KB admin disabling recording cannot be overridden by an individual user. Both sides have veto power. Standard precedence (user > kb) only applies when neither side is "disabled".
Empty string for either layer means "use the other layer's value"; callers that want a default should resolve it before calling this function (see DefaultSessionRecordingMode).
This function MUST stay byte-identical to mono's packages/kb/config.go ResolveEffectiveMode. Drift here is a security bug; scripts/check-kbconfig-drift.sh runs in CI to catch it.
func ValidateConfig ¶
ValidateConfig is the semantic gate for a parsed KBConfig. Every code path that reads a config from disk or the wire and intends to act on it should route through here. ox does not write configs, but ValidateConfig still runs on every read so an upstream-malformed value surfaces before downstream code makes a decision based on it.
Intentionally LENIENT on unknown fields — forward compat for fields a newer mono build wrote that this ox binary doesn't know about. The YAML / JSON decoder enforces structure; ValidateConfig enforces semantics of known fields.
Returns a wrapped error naming the offending field so the message threads through to UI / log without ambiguity.
func ValidateSessionRecordingMode ¶
ValidateSessionRecordingMode returns nil if m is one of the known mode slugs. Empty string is rejected — callers that want "not set" should not invoke validation in the first place (Mode is a *string for that reason).
func ValidateVisibility ¶
ValidateVisibility returns nil if v is a known visibility slug. Empty string is rejected — Visibility uses omitempty on its tag, so an unset value never reaches validation.
Types ¶
type ConfigYAMLEnvelope ¶
type ConfigYAMLEnvelope struct {
// CommittedAt is tolerated on read. Treat as advisory metadata only.
CommittedAt *time.Time `yaml:"committed_at,omitempty"`
Version int `yaml:"version,omitempty"`
Features FeaturesConfig `yaml:"features"`
System SystemConfig `yaml:"system,omitempty"`
}
ConfigYAMLEnvelope is the on-disk shape of .sageox/config.yaml in a KB tree (NOT the project-side binding file). Mirrors mono's envelope.
ox reads this format; ox does NOT write it. Writes are server-side (mono reconcile) or hand-edit only. The banner header that mono prepends is a YAML comment — yaml.v3 ignores comments on parse so no special handling is needed.
committed_at is tolerated on read for forward/backward compatibility with older mono builds that may have written the field; it is intentionally surfaced at the envelope level (rather than embedded inside SystemConfig) because mono's current schema places it at the file root. ox callers should ignore this value — git history is the canonical source for "when did this last change".
func UnmarshalConfigYAML ¶
func UnmarshalConfigYAML(data []byte) (*ConfigYAMLEnvelope, error)
UnmarshalConfigYAML parses a .sageox/config.yaml body into an envelope.
Strict by default via KnownFields(true) — mirrors mono's read-side strictness so a typo or wrong field name surfaces immediately rather than silently zeroing out a value. The envelope itself is intentionally a superset of mono's KBConfig (it carries committed_at), so the strict decode still tolerates files written by mono today.
func (*ConfigYAMLEnvelope) ToKBConfig ¶
func (e *ConfigYAMLEnvelope) ToKBConfig() KBConfig
ToKBConfig projects an envelope onto the canonical KBConfig type, dropping the read-only committed_at envelope field. Callers that need the parsed KBConfig (for ValidateConfig, ResolveEffectiveMode inputs, etc.) should go through this method so committed_at semantics stay isolated to the envelope.
type FeaturesConfig ¶
type FeaturesConfig struct {
// Murmurs controls the murmur feature on this bubble.
Murmurs MurmursConfig `json:"murmurs" yaml:"murmurs"`
// Visibility controls bubble discoverability. See VisibilityPrivate /
// VisibilityTeam. Empty string is treated as VisibilityPrivate by
// downstream consumers.
Visibility string `json:"visibility,omitempty" yaml:"visibility,omitempty"`
// SessionRecording controls AI-coworker session recording for the
// bubble. Tri-state per ox CLI's existing vocabulary so the two
// surfaces stay in lockstep. Defaults vary by KB type — see
// DefaultSessionRecordingMode.
SessionRecording SessionRecordingConfig `json:"session_recording" yaml:"session_recording"`
}
FeaturesConfig is the user-facing toggle set. Mirrors mono.
type KBConfig ¶
type KBConfig struct {
// Version tracks the schema shape so future migrations can detect old
// payloads. Reads tolerate Version==0 (treat as current).
Version int `json:"version" yaml:"version"`
// Features is the user-controllable namespace.
Features FeaturesConfig `json:"features" yaml:"features"`
// System is the internal namespace. Mono mutates it via internal code
// paths only; ox treats it as read-only and tolerates new fields.
System SystemConfig `json:"system" yaml:"system"`
}
KBConfig is the per-bubble dynamic configuration. Two namespaces with different write paths in mono:
- Features (user-facing) — owners toggle these from the KB settings page.
- System (internal) — set by mono workflows / watchman only.
ox reads both namespaces; ox never writes either.
type MurmursConfig ¶
type MurmursConfig struct {
Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
}
MurmursConfig holds murmur-feature toggles. Enabled is a *bool for the same "omitted vs explicit false" reason as SessionRecordingConfig.Mode.
type SessionRecordingConfig ¶
type SessionRecordingConfig struct {
Mode *string `json:"mode,omitempty" yaml:"mode,omitempty"`
}
SessionRecordingConfig holds the per-bubble recording policy.
Mode is *string so a patch can distinguish "field omitted, keep current value" from "explicitly set". After defaults are applied the pointer is non-nil.
type SystemConfig ¶
type SystemConfig struct {
// ConfigCommittedAt is tolerated on read. ox does not populate it on
// any write path. Treat as advisory metadata only — git history is the
// canonical "when did this last change" source.
ConfigCommittedAt *time.Time `json:"config_committed_at,omitempty" yaml:"config_committed_at,omitempty"`
}
SystemConfig is the internal namespace.
Mono's current SystemConfig is an empty struct — the earlier ConfigCommittedAt field was removed in favor of relying on git history. ox keeps a tolerated, read-only ConfigCommittedAt pointer here so that envelopes written by older mono builds (or by hand-edited test files) don't trip strict-unmarshal errors. ox never writes this field; it is preserved on read for forward/backward compatibility and ignored by all downstream consumers. See bead ox-jzlm and the [HUMAN] pushback bead.