Documentation
¶
Index ¶
- Variables
- func RequiresApproval(c contract.Contract, commandName string) (bool, error)
- type Broker
- func (b *Broker) ActiveGrants() []Grant
- func (b *Broker) Check(scope Scope) provenance.Status
- func (b *Broker) CheckToken(token string, scope Scope) provenance.Status
- func (b *Broker) DryRunCheck(scope Scope) provenance.Status
- func (b *Broker) DryRunCheckToken(token string, scope Scope) provenance.Status
- func (b *Broker) Grant(scope Scope, approvedBy string, ttl time.Duration) (Grant, error)
- type Grant
- type Scope
Constants ¶
This section is empty.
Variables ¶
var ErrCommandNotFound = errors.New("approval: command not found in contract")
ErrCommandNotFound is returned by RequiresApproval when commandName does not match any github.com/mediusfy/modulex/contract.CommandDecl.Name in the given contract. See RequiresApproval's doc comment for why a caller must treat this as "requires approval", not as "does not require approval" — the (false, err) return shape keeps the boolean itself always strictly meaningful, but the error is not a green light.
Functions ¶
func RequiresApproval ¶
RequiresApproval reports whether commandName, as declared by c.Commands, requires human approval before running: true if its github.com/mediusfy/modulex/provenance.CommandClass is provenance.ClassApprovalRequired or provenance.ClassDestructive, false for provenance.ClassSafe, ClassMutating, or ClassNetworked.
Types ¶
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker is the live, in-memory decision point for whether an elevated action identified by a Scope is currently authorized. See the package doc comment for the guarantees it makes: nothing is approved by default, every decision fails closed, and a matched grant is scoped exactly (never by action alone) and single-use.
func NewBroker ¶
func NewBroker() *Broker
NewBroker returns a Broker with zero grants. No configuration, environment variable, or argument can start a Broker in an already-approved state: every Broker.Check/Broker.CheckToken call against a freshly constructed Broker returns provenance.StatusApprovalRequired until Broker.Grant is called explicitly. This is the concrete mechanism behind ADR-0032's "no elevated operation is enabled by default" requirement.
func (*Broker) ActiveGrants ¶
ActiveGrants returns every currently active grant — unexpired and unused — sorted deterministically by ApprovedAt then TokenHash, for auditing. The returned slice is a snapshot: mutating it, or the Grant values in it, does not affect the Broker's internal state. Always non-nil.
Remember that each returned Grant still carries its raw Token; see the package doc comment's "Token sensitivity" section before logging or displaying these — prefer Grant.String()/the default %v formatting, which never prints Token.
func (*Broker) Check ¶
func (b *Broker) Check(scope Scope) provenance.Status
Check reports whether scope is currently authorized by any stored grant, searching across every grant regardless of which token it was issued under. If exactly one matching, unexpired, unused grant exists, Check consumes it (marks it Used) and returns provenance.StatusPass. In every other case — no grant at all, a grant for a different action or resource, an expired grant, an already-used grant, or any state this package did not anticipate — Check returns provenance.StatusApprovalRequired. There is no code path in Check that can return anything other than one of these two statuses.
Use Broker.CheckToken instead when the caller has (and should be required to present) the specific token a human handed it, rather than relying on the broker's own bookkeeping to find a matching grant.
func (*Broker) CheckToken ¶
func (b *Broker) CheckToken(token string, scope Scope) provenance.Status
CheckToken reports whether token, presented as the credential for scope, is currently valid: token must identify a stored grant that is unexpired, unused, and whose Scope exactly matches scope (both Action and Resource). An empty token is always denied outright — it never falls back to Check's any-matching-grant search. On a match, the grant is consumed (marked Used) and CheckToken returns provenance.StatusPass; every other case returns provenance.StatusApprovalRequired.
func (*Broker) DryRunCheck ¶
func (b *Broker) DryRunCheck(scope Scope) provenance.Status
DryRunCheck reports what Broker.Check would return for scope right now, without consuming a matching grant. Use this to answer "would this be approved" (e.g. to preview a plan before actually attempting the elevated action) without spending a single-use grant.
func (*Broker) DryRunCheckToken ¶
func (b *Broker) DryRunCheckToken(token string, scope Scope) provenance.Status
DryRunCheckToken reports what Broker.CheckToken would return for token and scope right now, without consuming a matching grant.
func (*Broker) Grant ¶
Grant creates and stores a new approval for scope, attributed to approvedBy, valid for ttl from now. ttl must be strictly positive and approvedBy must be non-empty; scope.Action must be non-empty. Any violation returns an error and stores nothing — there is no partial or best-effort grant creation.
The returned Grant's Token is the only way to later authorize via Broker.CheckToken; the caller is responsible for handing it to whatever party is expected to present it, and for treating it as sensitive (see the package doc comment's "Token sensitivity" section).
type Grant ¶
type Grant struct {
// Token is an unguessable, crypto/rand-generated bearer credential
// identifying this grant. Treat it as sensitive: never log, print, or
// persist it unredacted. See the package doc comment's "Token
// sensitivity" section. Excluded from JSON encoding; see TokenHash.
Token string `json:"-"`
// TokenHash is the SHA-256 hex digest of Token: a stable identifier
// that is safe to log, display, or embed in an audit artifact,
// because it cannot be reversed back into Token. This is what is
// JSON-marshaled and printed in place of Token.
TokenHash string `json:"token_hash"`
// Scope is the specific action/resource pair this grant authorizes.
Scope Scope `json:"scope"`
// ApprovedBy identifies who granted this approval (e.g. an email
// address or username). Required — an unattributed approval cannot be
// audited.
ApprovedBy string `json:"approved_by"`
// ApprovedAt is when the grant was created.
ApprovedAt time.Time `json:"approved_at"`
// ExpiresAt is when the grant stops being valid, regardless of Used.
// Required and non-zero: [Broker.Grant] rejects any attempt to create
// a Grant without a positive TTL, and every match performed by this
// package additionally treats a zero-value ExpiresAt as already
// expired (never as "no expiry"), as a defense-in-depth measure
// against a Grant that reached a Broker's internal state some other
// way than through Broker.Grant.
ExpiresAt time.Time `json:"expires_at"`
// Used records whether this grant has already been consumed by a
// matching Broker.Check/Broker.CheckToken call. Grants in this
// package are single-use by design — see the package doc comment's
// "Single-use grants" section for why: it is the safer default for
// "prevents approval reuse outside its scope", since a multi-use
// grant would remain valid for repeated actions within its Scope for
// its entire TTL, widening the window an approval covers beyond what
// a human explicitly approved once. A caller who legitimately wants
// to authorize N actions should request N grants.
Used bool `json:"used"`
}
Grant is an approval that has actually been given: unlike github.com/mediusfy/modulex/provenance.Approval (a flat audit record), a Grant carries the Scope it covers and a required expiry, and is the unit Broker matches against and consumes. See the package doc comment's "A broker, not a record" and "Token sensitivity" sections.
The only supported way to create a Grant is Broker.Grant (or Broker.Check/Broker.DryRunCheck, which return copies of an existing Grant to describe a decision) — Broker.Grant is what enforces "a Grant cannot exist without an expiry" and "every Grant is attributable to an approver". A caller that builds a Grant{} struct literal directly bypasses those checks entirely and must not do so; nothing in this package accepts a hand-built Grant as input to a Broker.
func (Grant) String ¶
String renders g without ever including the raw Token, so that %v, %+v, and %s (fmt's Stringer-triggering verbs) are always safe to log or print. Always prefer this (or the default %v/%+v formatting, which uses it automatically) over printing g.Token directly.
func (Grant) ToProvenanceApproval ¶
func (g Grant) ToProvenanceApproval() provenance.Approval
ToProvenanceApproval converts g to a github.com/mediusfy/modulex/provenance.Approval record, for continuity with the handoff/provenance schema (e.g. so a granted, consumed approval can be recorded in a provenance.Envelope's Approvals list). The resource (if any) and the grant's TokenHash (never the raw Token) are folded into Notes so the audit trail can still be tied back to this specific grant without exposing the bearer credential itself.
type Scope ¶
type Scope struct {
// Action is the specific action class or name this scope covers (e.g.
// "push", "release", "delete-branch", or a provenance.CommandClass
// value such as string(provenance.ClassDestructive)).
Action string
// Resource is what Action applies to (e.g. a branch name, a PR
// number, a migration name), or "" if Action is not further scoped to
// a specific resource.
Resource string
}
Scope identifies exactly what an approval covers: a specific action (e.g. "push", "release", or a provenance.CommandClass value used as a string) and, optionally, a specific resource that action applies to (e.g. a branch name or PR number). Both fields participate in every match a Broker performs — a Scope is never matched on Action alone. An empty Resource ("") is itself a specific value (meaning "this action, unscoped to any particular resource"), not a wildcard that matches any Resource: a grant for Scope{Action: "push", Resource: ""} does not authorize Scope{Action: "push", Resource: "branch-a"}, and vice versa.