policyexceptions

package
v0.148.1 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package policyexceptions keeps the local policy exceptions of one DataTug project: time-boxed allow rules that the policy author adds to their own local policy, on this machine, for one role, one table of one source and an exact list of fields. An agent proposes one; the person applies, rejects or revokes it in their own terminal. An exception is never an approval that is valid anywhere else, and the policy files themselves are never changed: an applied, unexpired exception is overlaid on the policy document it names each time policies are loaded, and only while that document is byte for byte the one the exception was bound to.

The store lives at <project>/.datatug/policy-exceptions/: one JSON file per exception and an append-only audit.jsonl, which records names and scopes and never a row value.

Local Perimeter keeps an honest agent inside the lines. A real security boundary needs DB-native permissions or DataTug Cloud approvals.

Index

Constants

View Source
const (
	DefaultDuration = 15 * time.Minute
	MaxDuration     = 8 * time.Hour
	// PendingTTL is how long a proposal waits for the person.
	PendingTTL = 30 * time.Minute
	// RulePrefix starts the id of the rule and rule set an exception adds.
	RulePrefix = "exception-"
)

Limits of an exception.

View Source
const (
	// LifetimeSession limits an exception to the harness session that
	// proposed it (and its deadline): only a reader reporting the same
	// harness and session gets it. The session is self-reported, so this
	// narrows who can use an exception; it does not authenticate anyone.
	LifetimeSession = "session"
	// LifetimeDeadline limits an exception to its deadline only.
	LifetimeDeadline = "deadline"
)

Lifetimes of an exception.

View Source
const AuditFile = "audit.jsonl"

AuditFile is the audit log's file name inside the store.

View Source
const HonestLimit = "" /* 275-byte string literal not displayed */

HonestLimit is the sentence every exception screen carries.

View Source
const Kind = "local-exception"

Kind is the kind every exception and audit event carries.

Variables

View Source
var DirName = filepath.Join(".datatug", "policy-exceptions")

DirName is the store's folder, relative to the project directory.

View Source
var ErrChanged = errors.New("the policy exception changed while it was being decided; nothing was recorded")

ErrChanged is the failure of a commit whose exception changed on disk since it was read: the decision is not recorded.

View Source
var ErrNotApplicable = errors.New("the policy binds no roles to rule sets, so an exception cannot name a role in it")

ErrNotApplicable marks a policy document an exception cannot be added to: it binds no roles to rule sets.

View Source
var ErrNotFound = errors.New("policy exception not found")

ErrNotFound is the failure of a lookup of an exception the store does not hold.

Functions

func Applicable

func Applicable(data []byte, codec access.Codec) error

Applicable reports whether a document can carry exceptions: it is a principal policy set (rule sets bound to roles).

func CheckDuration

func CheckDuration(d time.Duration) error

CheckDuration refuses a duration outside (0, MaxDuration].

func CheckName

func CheckName(what, name string) error

CheckName refuses a name that is not one literal identifier: a pattern (*, ?, [], {}), a path separator, white space or a control character could widen an exception beyond the one table or field it names.

func Fingerprint

func Fingerprint(data []byte) string

Fingerprint is the hex sha256 of a policy document's bytes.

func NewID

func NewID() (string, error)

NewID returns a random 64-bit id as 16 hex digits.

func Overlay

func Overlay(data []byte, codec access.Codec, binding Binding, exceptions []Exception, now time.Time) ([]byte, []string, error)

Overlay adds to the policy document data one allow rule for each exception that is active at now, matches binding in full, and was bound to exactly these bytes. Each rule is its own rule set, named like the rule, bound to the exception's role: operations [query], path /<table>, fields as applied, and nothing else. It returns data itself and no ids when nothing applies, else the re-encoded document and the ids of the exceptions it added.

func Scope

func Scope(e Exception, fields []string, expires time.Time) string

Scope renders the exact scope screen apply shows before the person confirms. expires is the expiry the decision records, fixed before the screen is shown.

func SourceFingerprint

func SourceFingerprint(sourceURL string) string

SourceFingerprint is the identity of a resolved source: the hex sha256 of the source reference the project resolves its id to (a SQLite or inGitDB path, or the environment variable of a server source).

func Subset

func Subset(sub, set []string) bool

Subset reports whether every name of sub is in set.

func ValidID

func ValidID(id string) bool

ValidID reports whether id is an exception id: 16 lower-case hex digits.

Types

type Binding

type Binding struct {
	ProjectID, Env, Source, SourceIdentity, PolicyPath string
	// Reader is the self-reported harness session of the reading process;
	// a session exception applies only to exactly the session it is bound to.
	Reader SessionBinding
}

Binding is what a read must match for an exception to apply: the project, its environment, the source id and identity, and the project-relative path of the policy document being loaded.

type Event

type Event struct {
	At        time.Time  `json:"at"`
	Kind      string     `json:"kind"`
	Event     string     `json:"event"` // proposed, applied, narrowed, rejected, revoked, expired, used
	ID        string     `json:"id"`
	ProjectID string     `json:"projectId"`
	Env       string     `json:"env"`
	Source    string     `json:"source"`
	Policy    string     `json:"policy"`
	Role      string     `json:"role"`
	Table     string     `json:"table"`
	Fields    []string   `json:"fields,omitempty"`
	ExpiresAt *time.Time `json:"expiresAt,omitempty"`
	Reason    string     `json:"reason,omitempty"`
	// Lifetime and BoundSession are the exception's: which harness session,
	// if any, the permission was given to.
	Lifetime     string          `json:"lifetime,omitempty"`
	BoundSession *SessionBinding `json:"session,omitempty"`
	// Caller is the self-reported harness of the process that wrote the
	// event (Store.Caller): attribution only. On a used event it is the
	// session that read under the exception.
	Caller *callercontext.Caller `json:"caller,omitempty"`
}

Event is one audit record. It names scopes and never holds a row value.

func NewEvent

func NewEvent(kind string, e Exception, fields []string, at time.Time) Event

NewEvent is an event about e at at.

type Exception

type Exception struct {
	ID        string `json:"id"`
	Kind      string `json:"kind"`
	ProjectID string `json:"projectId"`
	Env       string `json:"env"`
	Source    string `json:"source"`
	// SourceIdentity is SourceFingerprint of the source the exception was
	// proposed for, as the project resolved it: the same source id pointed at
	// another database is another source.
	SourceIdentity   string     `json:"sourceIdentity"`
	Policy           PolicyRef  `json:"policy"`
	Role             string     `json:"role"`
	Table            string     `json:"table"`
	Fields           []string   `json:"fields"`
	AppliedFields    []string   `json:"appliedFields,omitempty"`
	Reason           string     `json:"reason"`
	DurationSeconds  int64      `json:"durationSeconds"`
	State            State      `json:"state"`
	ProposedAt       time.Time  `json:"proposedAt"`
	PendingExpiresAt time.Time  `json:"pendingExpiresAt"`
	DecidedAt        *time.Time `json:"decidedAt,omitempty"`
	ExpiresAt        *time.Time `json:"expiresAt,omitempty"`
	DecisionReason   string     `json:"decisionReason,omitempty"`
	ExpiryRecorded   bool       `json:"expiryRecorded,omitempty"`
	// Caller is the self-reported harness the proposal came from:
	// attribution only, never authority.
	Caller *callercontext.Caller `json:"caller,omitempty"`
	// Lifetime is session or deadline; an exception stored before lifetimes
	// existed is a deadline one.
	Lifetime string `json:"lifetime,omitempty"`
	// BoundSession is the harness session a session exception applies to.
	BoundSession *SessionBinding `json:"session,omitempty"`
}

Exception is one stored local policy exception.

func (Exception) Active

func (e Exception) Active(now time.Time) bool

Active reports whether the exception adds its rule at now.

func (Exception) Duration

func (e Exception) Duration() time.Duration

Duration is the time an applied exception lasts.

func (Exception) IsSession

func (e Exception) IsSession() bool

IsSession reports whether the exception is bound to a harness session.

func (Exception) LifetimeLine

func (e Exception) LifetimeLine(until *time.Time) string

LifetimeLine is the one line the apply and reject screens show about how long the exception lasts; until is the deadline when it is known.

func (Exception) ProposedBy

func (e Exception) ProposedBy() string

ProposedBy is the one line the apply and reject screens show about who proposed the exception.

func (Exception) RuleID

func (e Exception) RuleID() string

RuleID is the id of the rule (and of the rule set) the exception adds.

func (Exception) Status

func (e Exception) Status(now time.Time) State

Status is the state at now: a pending proposal past its wait, or an applied exception past its expiry, is expired.

type PolicyRef

type PolicyRef struct {
	Name   string `json:"name"`
	Path   string `json:"path"` // relative to the project directory, slash-separated
	SHA256 string `json:"sha256"`
}

PolicyRef binds an exception to one policy document of the project.

type SessionBinding

type SessionBinding struct {
	Harness string `json:"harness"`
	Session string `json:"session"`
}

SessionBinding is the harness session a session exception is bound to.

func (SessionBinding) Short

func (s SessionBinding) Short() string

Short is the binding as screens show it: "claude-code session 0f88afc6…".

type State

type State string

State is where an exception is in its life.

const (
	StatePending  State = "pending"
	StateApplied  State = "applied"
	StateRejected State = "rejected"
	StateRevoked  State = "revoked"
	StateExpired  State = "expired"
	// StateEnded is a session exception whose session was ended explicitly.
	StateEnded State = "ended"
)

States of an exception.

type Store

type Store struct {
	ProjectDir string
	// Caller is stamped on every event this store writes that names none.
	Caller *callercontext.Caller
}

Store is the exception store of one project directory.

func (Store) Audit

func (s Store) Audit() ([]Event, error)

Audit reads the audit log in order. A line that does not decode fails it.

func (Store) Commit

func (s Store) Commit(id string, expected []byte, change func(*Exception) (Event, error)) error

Commit changes one exception under the store's lock, only if its file still holds exactly expected. change may refuse by returning an error. The event change returns is appended to the audit log first and the new state is written only after it: a decision that cannot be audited is never recorded, and a write that fails after the event leaves the old state in force.

func (Store) EndSession

func (s Store) EndSession(binding SessionBinding, now time.Time) ([]string, error)

EndSession marks every pending or applied session exception bound to exactly this harness session as ended, with one ended event each, under the store's lock, and returns their ids. It only ever narrows access.

func (Store) Get

func (s Store) Get(id string) (Exception, []byte, error)

Get reads one exception and the exact bytes it was read from.

func (Store) List

func (s Store) List() (exceptions []Exception, unreadable []string, err error)

List reads every exception, sorted by proposal time, and names the files it could not read: an unreadable file contributes nothing, and the others still count. Only a store folder that cannot be listed is an error.

func (Store) Propose

func (s Store) Propose(e Exception) error

Propose stores a new pending exception and records the proposed event.

func (Store) Record

func (s Store) Record(event Event) error

Record appends one event to the audit log under the store's lock.

func (Store) RecordExpiries

func (s Store) RecordExpiries(exceptions []Exception, now time.Time) error

RecordExpiries marks every exception that expired at now and was not yet recorded, writing one expired event each, in one pass under the store's lock, and returns the first failure. It is best effort: it never changes what is applied, because expiry is evaluated against the clock on every load.

Jump to

Keyboard shortcuts

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