Documentation
¶
Overview ¶
Package grants turns Legant's delegated authority into a declarative, version-controllable file. Instead of minting authority imperatively (CLI flags or a browser consent click), a team writes a `legant.grants.yaml` that says "principal P delegates to agent A the scopes S, capped by these constraints," reviews it in a pull request, lints it in CI, and `apply`s it to materialize the signed tokens offline.
It is deliberately NOT a policy language: the schema is a fixed, 1:1 serialization of Legant's own constraint dimensions (max_amount, categories, tools, resources/RFC-8707 audiences, time_window). That fixedness is the whole point — it travels inside the token and verifies offline at any resource server, which an OPA/Kyverno-style content gate cannot do. Keep it from growing into a Turing-complete DSL.
Index ¶
Constants ¶
const StarterYAML = `` /* 1457-byte string literal not displayed */
StarterYAML is the commented template `legant init grants` writes.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type ApplyResult ¶
type ApplyResult struct {
Changes []Change
Orphans []string // token files in the dir not in the declared set
Minted int
}
ApplyResult is the outcome of reconciling a grants file into a setup dir.
type Change ¶
type Change struct {
Name string
File string
Action string // "create" | "update" | "unchanged"
Subject string
Agent string // leaf actor
Audience string
}
Change is one entry in an apply diff.
type ChildSpec ¶
type ChildSpec struct {
Agent string `yaml:"agent"`
Scopes []string `yaml:"scopes"`
Constraints *ConstraintSpec `yaml:"constraints,omitempty"`
}
ChildSpec is an attenuated sub-delegation of its parent grant.
type ConstraintSpec ¶
type ConstraintSpec struct {
MaxAmount *float64 `yaml:"max_amount,omitempty"`
Categories []string `yaml:"categories,omitempty"`
Tools []string `yaml:"tools,omitempty"`
Resources []string `yaml:"resources,omitempty"`
TimeWindow *TimeWindowSpec `yaml:"time_window,omitempty"`
}
ConstraintSpec maps 1:1 to delegation.Constraints (Legant's fixed dimensions).
type Defaults ¶
type Defaults struct {
TTL string `yaml:"ttl,omitempty"`
}
Defaults are applied to any grant that omits the field.
type File ¶
type File struct {
Version int `yaml:"version"`
Issuer string `yaml:"issuer,omitempty"`
Audience string `yaml:"audience,omitempty"` // default audience for grants that omit one
Defaults Defaults `yaml:"defaults,omitempty"`
Grants []GrantSpec `yaml:"grants"`
}
File is a parsed legant.grants.yaml.
func Parse ¶
Parse reads and unmarshals a grants file. It rejects unknown fields so a typo'd key fails loudly at lint time instead of being silently ignored.
func (*File) Apply ¶
Apply reconciles the declared grants into the setup dir: it mints a signed token per grant (and per declared sub-delegation), writing one file each, and reports a created/updated/unchanged diff against what is already there. It is idempotent — a grant whose declared shape matches the existing token is left untouched unless force is set — so re-applying an unchanged file is a no-op.
func (*File) Lint ¶
Lint validates the file semantically WITHOUT side effects: it catches the errors that would otherwise silently 401/403 at runtime — an empty scope list, a child that widens past its parent, a time window that never opens, a missing audience, a duplicate grant. It returns every issue found (errors and warnings) so a CI gate can fail on errors.
func (*File) WhoCan ¶
WhoCan answers "which declared grants would permit this action?" by minting each grant in memory and running the SAME offline verify+authorize a resource server runs — so the answer is exactly what production would decide, not a re-implemented guess. An empty Resource/Tool/etc. on the action skips that dimension.
Authorization is time-sensitive, but a reviewer asking "who can touch finance?" should not have a structurally-capable grant hidden just because they ran the query after hours. So a grant that passes every dimension EXCEPT its time window is still returned, flagged TimeBoxed.
type GrantSpec ¶
type GrantSpec struct {
Name string `yaml:"name,omitempty"`
Principal string `yaml:"principal"`
Agent string `yaml:"agent"`
Scopes []string `yaml:"scopes"`
Audience string `yaml:"audience,omitempty"`
TTL string `yaml:"ttl,omitempty"`
Constraints *ConstraintSpec `yaml:"constraints,omitempty"`
Delegate []ChildSpec `yaml:"delegate,omitempty"`
}
GrantSpec is one root delegation: principal -> agent, with constraints and any further attenuated hand-offs in Delegate.
type Issue ¶
Issue is one lint finding. Error issues fail the lint (non-zero exit); Warn issues are advisory.
type Match ¶
type Match struct {
Name string
Provenance string
Audience string
// TimeBoxed is set when the grant only permits the action inside a time window
// that is NOT open at the evaluation instant — so it is structurally capable but
// blocked right now. The reviewer still needs to see it.
TimeBoxed bool
}
Match is one grant that authorizes a queried action in WhoCan.
type Setup ¶
type Setup struct {
Dir string
Issuer string
Signer *delegation.Signer
Keys map[string]*rsa.PublicKey
FeedPath string
// contains filtered or unexported fields
}
Setup is the offline trust material apply/mint operate on: a local signing key, its JWKS, and the signed revocation feed, all under one directory.
func EnsureSetup ¶
EnsureSetup loads the local key/JWKS/feed from dir, creating them on first use. This is the general (non-coding-agent) analog of `legant guard init`: it makes a self-contained offline trust domain so apply/mint/show/revoke need no server or database. The private key is a LOCAL key — in a real deployment tokens come from a token-exchange against your Legant issuer and the JWKS/feed are its endpoints.
type TimeWindowSpec ¶
type TimeWindowSpec struct {
Weekdays []int `yaml:"weekdays,omitempty"` // 0=Sun..6=Sat; empty = any day
Start string `yaml:"start"` // "09:00"
End string `yaml:"end"` // "17:00"
TZ string `yaml:"tz,omitempty"` // IANA name; empty = UTC
}
TimeWindowSpec is a friendlier surface over delegation.TimeWindow: HH:MM strings instead of minute-of-day integers.