Documentation
¶
Overview ¶
Posture-gated autonomy, arithmetic half. The api half — the gate that refuses a request shape and derives a hold — is internal/api/runs_autonomy.go; everything here is PURE, so launch and the Review dry run fold the same inputs to the same answer by construction.
The comparator for "which ceiling grant bounds this proposed grant". Lives here because composer holds the RUNTIME clamp and internal/api (the WRITE-TIME comparator) imports composer, not the reverse — one definition, two callers.
SECURITY invariant: both callers must ask it the same way — some single ceiling grant must dominate the proposal on every axis. A per-kind map must never supply the bound instead: the LAST same-kind ceiling grant would win, so a ceiling listing two ssh_key grants (normal for two forges) could clamp a proposal naming the STRICT forge's pairing to the PERMISSIVE forge's posture, answering differently by slice order.
Index ¶
- Constants
- func AutonomyPostureOf(spec types.RunPolicySpec, enforced types.ConfinementClass, b Baseline) types.AutonomyPosture
- func CapDiskMiB(disk, ceil int) int
- func CapResources(r *types.ResourceLimits, limits types.GovernanceLimits, ...) (*types.ResourceLimits, bool)
- func CeilingGrantsCovering(g types.GrantSpec, ceiling []types.GrantSpec) []types.GrantSpec
- func Clamp(proposed, ceiling types.RunPolicySpec, limits types.GovernanceLimits) (types.RunPolicySpec, []string)
- func ClampMember(proposed, ceiling types.RunPolicySpec, limits types.GovernanceLimits, ...) (types.RunPolicySpec, []string)
- func ClampRunConfinement(runClass string, floor types.ConfinementClass) (string, string)
- func EffectiveConfinementFloor(policyMin, floor, cap types.ConfinementClass) types.ConfinementClass
- func FoldAutonomy(rubric types.AutonomyRubric, posture types.AutonomyPosture) (types.AutonomyLevel, []string)
- func GitHubScopeWithin(proposed, ceiling json.RawMessage) error
- func GrantPairing(g types.GrantSpec) (host, secretRef, knownHostsRef string, covered, ok bool)
- func GrantWithin(g types.GrantSpec, ceiling []types.GrantSpec) error
- func Leq(a, b types.RunPolicySpec, la, lb types.GovernanceLimits) bool
- func MemberDefault(deployment types.ResourceLimits, ceiling *types.ResourceLimits) types.ResourceLimits
- func PATScopeMeet(g json.RawMessage, bounds []json.RawMessage, warns *[]string) (json.RawMessage, bool)
- func PATScopeWithin(proposed, ceiling json.RawMessage) error
- func PairingInCeiling(g types.GrantSpec, ceiling []types.GrantSpec) bool
- func RequiredConfinementFloor(spec types.RunPolicySpec, b Baseline) types.ConfinementClass
- func TightenRunLimits(captured, profile types.RunLimits) types.RunLimits
- func TighterSec(a, b int) int
- func ValidateOverlay(base Authority, ov Overlay) error
- type Authority
- type Baseline
- type Overlay
- type OverlayError
- type RiskItem
- type RiskLevel
- type RunInput
Constants ¶
const ( ReasonOverlayInvalid = "governance_overlay_invalid" ReasonOverlayUnsatisfiable = "governance_overlay_unsatisfiable" )
The wire reasons an overlay refusal carries. governance_overlay_invalid is a 400 at write; governance_overlay_unsatisfiable is the explicit deny-all state (a base that narrowed under an overlay until nothing satisfies both).
const WarnResourcesCapped = "resources capped to operator maximum"
ClampRunConfinement raises a proposed run's confinement class to the policy floor (never lowers) so the composer can't emit a run that handleCreateRun would 422 for advertising a weaker class than its own inline_policy floor (invariant 5); empty/unknown ranks 0 and is also raised. WarnResourcesCapped is shared with dispatch's own ephemeral-disk clamp (runs_dispatch_ceiling.go) so the same fact about the same number isn't hand-typed twice and drift apart.
Variables ¶
This section is empty.
Functions ¶
func AutonomyPostureOf ¶ added in v0.8.0
func AutonomyPostureOf(spec types.RunPolicySpec, enforced types.ConfinementClass, b Baseline) types.AutonomyPosture
AutonomyPostureOf grades a run's three-axis posture from its RESOLVED spec and the confinement class the run will actually enforce. It keys on the same inputs as Grade and RequiredConfinementFloor, so posture and risk grade never disagree about what "powerful" or "beyond baseline" means on the same Review screen. Purely a function of the spec, never of anything a model claimed.
func CapDiskMiB ¶ added in v0.7.2
CapDiskMiB bounds a non-zero ephemeral-disk size by one operator ceiling; 0 on either side means "no bound". Shared by Clamp (preview) and api.ephemeralDiskFor (dispatch) so both ceilings agree — previously each applied a different ceiling (profile vs org), so a 100000 MiB policy under a 4096 MiB org max previewed 100000 but ran on 4096; pinned by TestPreflightAndDispatchAgreeOnEphemeralDisk. A zero disk stays zero: a ceiling bounds a request, it never invents one.
func CapResources ¶ added in v0.8.6
func CapResources(r *types.ResourceLimits, limits types.GovernanceLimits, floor types.ResourceLimits) (*types.ResourceLimits, bool)
CapResources bounds a run's CPU and memory by the profile's maximums and reports whether it changed anything. It is the resource-only half of Clamp, for the member launch that carries no policy (its spec is the profile's own ceiling, which Clamp must not process because it drops workspace mounts), and dispatch's re-assertion of the same bound. Each maximum is the member cap with no ceiling size (runnerpool.MemberCap): the maximum, never below floor, the member default (MemberDefault), so it never cuts what preflight said a run gets (#1949); a zero floor field leaves the maximum as it is. A zero field of r is the platform default (sizing.EffectiveLimits), the size the runner gives it. Returns a FRESH block, never an in-place write, so one shared spec cannot re-size every later run that reads it.
func CeilingGrantsCovering ¶ added in v0.7.0
CeilingGrantsCovering returns the ceiling grants whose identity covers g (same kind, and for a stored-secret kind the same host/secret/known_hosts pairing) — exported so api's write-time comparator selects from the same set the runtime clamp bounds against, one definition not two. Identity only, never bounds: an undecodable proposal scope is covered by nothing (fail closed).
func Clamp ¶
func Clamp(proposed, ceiling types.RunPolicySpec, limits types.GovernanceLimits) (types.RunPolicySpec, []string)
Clamp tightens a proposed RunPolicySpec to the operator's policy ceiling so the composer can never propose something more permissive than the operator allows, returning the clamped spec and a warning for every tightening performed. Defense-in-depth alongside the deterministic risk grade: the grade informs the human, this enforces the operator's hard limits regardless of what an untrusted-input-driven analyzer or a member's own inline_policy proposed.
The returned spec owns its memory (see cloneProposal): it shares no backing array or pointee with either argument, so a later mutation of either side can never move an already-enforced ceiling.
limits is the acting principal's GovernanceLimits; only its size caps (MaxCPUMillis, MaxMemoryMiB, MaxEphemeralDiskMiB; 0 = unlimited) are read here. Disk is applied via CapDiskMiB, shared with dispatch's own ephemeral-disk clamp so preview and the actual run agree, and only clamps a non-zero request since 0 means unbounded scratch. CPU and memory fold into the resource ceiling below.
func ClampMember ¶ added in v0.9.0
func ClampMember(proposed, ceiling types.RunPolicySpec, limits types.GovernanceLimits, size, memberDefault types.ResourceLimits) (types.RunPolicySpec, []string)
ClampMember is Clamp for a governed member's own run policy. CPU and memory are bounded by the member cap from size, the profile's AUTHORED size (a resolved ceiling carries the deployment's size where its profile sets none, #1949), and memberDefault, the member's size before any ask (from the deployment's own default, which Clamp alone cannot see). A cut of either is not warned here: the run's placement step applies the ask within every limit and says exactly when it was cut, so the warning and the run's resources_applied agree.
func ClampRunConfinement ¶
func ClampRunConfinement(runClass string, floor types.ConfinementClass) (string, string)
func EffectiveConfinementFloor ¶
func EffectiveConfinementFloor(policyMin, floor, cap types.ConfinementClass) types.ConfinementClass
EffectiveConfinementFloor caps the per-run floor (the Getting Started default tier, sent raise-only) at the strongest class this host's runner can enforce, then returns max(policyMin, cappedFloor). The cap degrades only the per-run floor, never policyMin — an unenforceable policy minimum still fails closed at launch (invariant 5); degrading it here would make compose the one path that bypasses an operator security control. cap=="" means "do not cap". Feeding the result into Clamp's ceiling reuses its existing "confinement raised" warning.
func FoldAutonomy ¶ added in v0.8.0
func FoldAutonomy(rubric types.AutonomyRubric, posture types.AutonomyPosture) (types.AutonomyLevel, []string)
FoldAutonomy folds a rubric against a posture: the level is the MINIMUM over the three fields the posture selects, and boundBy names EVERY field tied at that minimum (its rubric wire name), not just the first — so an admin who raises one capping row can see the level not move because another field tied it. An unset field caps nothing and is skipped, so an all-unset rubric returns ("", nil), matching the "empty means unrestricted" rule every GovernanceLimits field follows. Order is applicableAutonomyCaps's fixed field order, so the audit row and the Review response agree byte for byte.
func GitHubScopeWithin ¶ added in v0.7.0
func GitHubScopeWithin(proposed, ceiling json.RawMessage) error
GitHubScopeWithin reports whether a proposed github_token scope stays inside a ceiling's, by the same rules clampGitHubScope enforces (ceiling repo list is an allowlist, empty = deny-all; an absent permission is ungrantable; a present one bounds the level by permRank) — one shared copy so accept-here can't drift from widen-there.
Differs in one way: an undecodable scope is an error here, not empty, since a comparator reading "no constraint" would fail open (the clamp instead intersects toward empty, fail-closed).
func GrantPairing ¶ added in v0.7.0
GrantPairing returns the pairing a grant names and whether its kind names a stored secret at all. covered=false is honest for github_token/cloud_sts, which name no stored secret. ok=false means the scope didn't decode into a usable pairing — deliberately not an error return, since fail-closed here means an unreadable scope never MATCHES anything. env_secret puts the env var NAME in the host slot, file_secret the file name.
func GrantWithin ¶ added in v0.8.6
GrantWithin is the one grant-dominance contract: g is within the ceiling when SOME single ceiling grant dominates it on EVERY axis, and the refusal names the most specific reason none did. internal/api's write-time and resolve-time bounds call it, and Leq calls it, so a profile, a composed overlay and a dispatched run are bounded by one rule rather than copies that drift.
Why this bound exists. A profile's ceiling is otherwise freely narrower OR wider than the deployment's. Eligible grants are the one axis where it cannot be: they are DEPLOYER-PROVISIONED material (a stored operator secret, a GitHub App installation), and the principal authoring profiles is not necessarily the principal who provisioned them. A profile may narrow credential eligibility; it may never mint it.
Monotone, not membership and not equality:
- Membership alone ("the pairing is listed") would let a grant keep the pairing while STRIPPING RequiresApproval, and a stripped approval flag auto-mints the injection at proxy boot with no human in the loop. So ceiling.RequiresApproval implies g.RequiresApproval (forcing it ON is a narrowing).
- Equality would refuse a legitimately STRICTER grant: a shorter TTL, approval forced on, a smaller repo set.
Axes, all in the narrowing direction: identity (CeilingGrantsCovering, the SAME selection the runtime clamp bounds against and the same pairing rule filterUserGrants enforces); approval; owner_only; TTL, both sides normalised by normalizeClampTTL (0 means the broker maximum, so a raw "<=" would accept a TTL of 0 under a ceiling of 300, which mints credentials twelve times as long); github_token repos/permissions (GitHubScopeWithin), which pairing cannot see because github_token names no stored secret; and the git_pat scope axes repos, access, api and forge (PATScopeWithin).
Checking the axes against different ceiling grants would let a grant pair one ceiling grant's approval posture with another's TTL, which is precisely the widening a per-axis check misses; the RUNTIME clamp bounds from the same candidate set, because both sides call CeilingGrantsCovering.
An undecodable scope or an unknown kind is refused, never passed: the pairing cannot be computed, so nothing can be said about whether it is in the ceiling.
func Leq ¶ added in v0.8.6
func Leq(a, b types.RunPolicySpec, la, lb types.GovernanceLimits) bool
Leq reports whether policy a, under limits la, is at most as permissive as policy b under limits lb: every request, grant, size and duration a admits, b admits too.
It is CONSERVATIVE. It reads values the way the runtime does (the same normalisers as the overlay meet: a zero that means a finite default is replaced by that default before two values are compared), and it answers false for anything it cannot prove narrower. A false negative costs an extra approval; a false positive is a silent widening, because GOV4 exempts a change Leq proves narrowing from second-person approval. So a field no rule below covers must not differ at all (see leq_fallback.go), and the coverage guard fails a field with no rule here.
Each rule is the inverse of the meet's for the same field: ApplyOverlay(b, o) is at most as permissive as b, so Leq(ApplyOverlay(b, o), b) holds. eligible_grants are judged by GrantWithin, the one grant-dominance contract.
func MemberDefault ¶ added in v0.9.0
func MemberDefault(deployment types.ResourceLimits, ceiling *types.ResourceLimits) types.ResourceLimits
MemberDefault is a governed member's size before anything asks for one: the deployment's own default size (its default policy's, else the platform's; a zero field of deployment is sizing.EffectiveLimits'), lowered per field to the ceiling's own size where it sets one. It is the untouched size runnerpool.MemberCap starts from when no admin-authored policy names one, so the member cap is never raised past a ceiling below the default (a resolved ceiling carries the deployment's own size where its profile sets none).
func PATScopeMeet ¶ added in v0.8.6
func PATScopeMeet(g json.RawMessage, bounds []json.RawMessage, warns *[]string) (json.RawMessage, bool)
PATScopeMeet intersects proposal g with every bounding ceiling grant's scope and reports whether the grant survives. It never widens: repos are intersected, access takes the lower, api the lower, and the forge is g's own. The result does not depend on the order of bounds.
The grant is DROPPED (keep=false, with a warning) when:
- the repos intersection is empty, because an empty list reads as "none" only where it was authored, and one left behind by the meet must never become an unnarrowed grant;
- a bound that sets repos or api: true has a different forge from g;
- any scope is undecodable (fail closed).
An empty repos list that g itself authored is not an intersection result and stays as written.
func PATScopeWithin ¶ added in v0.8.6
func PATScopeWithin(proposed, ceiling json.RawMessage) error
PATScopeWithin reports whether ceiling grant cg dominates proposal g on every PAT axis, naming the first axis that does not:
- repos: g.repos is a subset of cg.repos under the forge's identity rule (types.PATRepoKey). An omitted cg.repos dominates anything; an omitted g.repos is dominated only by an omitted cg.repos; a "group/*" in g is covered only by the same "group/*", or by an omitted cg.repos.
- access: read is below write.
- api: false is below true.
- forge: equal, but only when cg sets repos or api: true.
An undecodable scope on either side is an error, not "no constraint": a comparator reading nothing would fail open.
func PairingInCeiling ¶ added in v0.7.0
PairingInCeiling reports whether some ceiling grant of the SAME kind names the same stored-secret pairing — the identity axis of the one comparator (internal/api's storedSecretPairingInCeiling forwards to it). Takes the whole proposed grant, not a destructured pairing, so the pairing rule lives in exactly ONE decoder. Fail-closed: a grant whose kind names no stored secret, or whose scope doesn't decode, is in no pairing.
func RequiredConfinementFloor ¶
func RequiredConfinementFloor(spec types.RunPolicySpec, b Baseline) types.ConfinementClass
RequiredConfinementFloor returns the deterministic minimum confinement class a run's blast radius requires, independent of model/operator choice: SECURITY: a run holding a write-capable grant or an api_key to a non-baseline host is a high-value target and must run in CC3 so an escape is contained. "" means no floor. Enforced both at composer proposal and (defense-in-depth) at run.create.
func TightenRunLimits ¶ added in v0.8.6
TightenRunLimits is captured with each bound that binds a live run replaced by the profile's current one wherever that is tighter, and only there. The defaults are left alone: they shape a new run, not a live one.
Shared by the live-run reclamp and by the overlay meet, so a running child is reclamped to exactly the bounds a new run under the same composition gets.
func TighterSec ¶ added in v0.8.6
TighterSec is the tighter of two bounds where 0 is no bound.
func ValidateOverlay ¶ added in v0.8.6
ValidateOverlay is the write-time reading of the same meet: it refuses an overlay that names anything its base does not permit (a domain outside the base, a method the base excludes, allow_all_egress on a base without it, a looser bound), where ApplyOverlay would silently narrow it. A write is the author's own act and can be sent back; a base edit arriving between two runs is not, which is why resolve takes the lenient path.
Types ¶
type Authority ¶ added in v0.8.6
type Authority struct {
Ceiling types.RunPolicySpec
Limits types.GovernanceLimits
}
Authority is one resolved governance ceiling: the policy a run is bounded by and the limits that bound the request.
func ApplyOverlay ¶ added in v0.8.6
ApplyOverlay returns base narrowed by ov, plus a warning for each thing the overlay named that the base does not permit (an entry the base no longer covers, a bound looser than the base's): the meet drops those rather than widen. This is the resolve-time reading, where the base may have narrowed since the overlay was written. It returns an error, and a zero Authority, when no representable result exists: a disjoint allowed_methods or azure_devops_capabilities or github_capabilities (never [], which all read as "all"), two different llm_inspection values, or limits the writer would refuse.
type Baseline ¶ added in v0.9.0
Baseline is the operator's extension of safeBaselineDomains: exact hosts, and suffixes (an internal_hosts entry marked baseline matches itself and any subdomain, by label). The zero value is the built-in set alone, so a deployment that declares nothing grades exactly as before. Pure data: the API layer reads the site config and hands it in.
type Overlay ¶ added in v0.8.6
type Overlay struct {
Ceiling types.CeilingOverlay
Limits types.LimitsOverlay
}
Overlay is what a composed profile says on top of its base. Presence is typed, so a field the overlay does not name is inherited unchanged.
type OverlayError ¶ added in v0.8.6
OverlayError is a refusal to compose. Field is the wire name of the field (or field.sub) that caused it.
func (*OverlayError) Error ¶ added in v0.8.6
func (e *OverlayError) Error() string
type RiskItem ¶
type RiskItem struct {
Field string `json:"field"`
Value string `json:"value"`
Level RiskLevel `json:"risk_level"`
Rationale string `json:"rationale"`
InvariantRef string `json:"invariant_ref,omitempty"`
}
RiskItem is one graded config choice. Field/Value identify what was graded, Level is Wardyn's deterministic grade, Rationale explains it to the human, and InvariantRef (optional) cites the security invariant the choice bears on.
func Grade ¶
func Grade(run RunInput, spec types.RunPolicySpec) []RiskItem
Grade computes the deterministic risk assessment of a proposed run setup purely from its fields. SECURITY: it never consults any LLM self-assessment — a prompt-injected attachment cannot lower the grade, since the grade is a function of the spec, not of anything the model claims about it. Emits one item per notable choice (including LOW ones), sorted riskiest-first.
type RiskLevel ¶
type RiskLevel string
RiskLevel is Wardyn's deterministic grade for a single config choice.
func OverallLevel ¶
OverallLevel returns the highest level among items (low if none).
type RunInput ¶
type RunInput struct {
Agent string `json:"agent"`
Repo string `json:"repo"`
Task string `json:"task"`
ConfinementClass string `json:"confinement_class,omitempty"`
Interactive bool `json:"interactive,omitempty"`
DevcontainerRepo string `json:"devcontainer_repo,omitempty"`
// Baseline is the deployment's operator-declared extension of the egress
// baseline. Deployment data, not a run field: the API layer fills it from
// the site config so Grade stays pure.
Baseline Baseline `json:"-"`
}
RunInput is the scalar create-run fields graded by Grade — the same fields the wizard's buildSpec puts on `run`. Mapped onto the create-run request by the API layer (this package must not import internal/api).