gate
import "github.com/kitsunium/sdk/framework/gate"
Package gate decides whether one invocation of a distributed binary may run.
A product that verifies an entitlement at start-up answers three questions before it does any work, and they are usually answered by hand, in a root command, in code nobody revisits:
- Is THIS invocation subject to the check at all?
- The vendor mandates a newer build. Refuse, upgrade, or warn?
- When the answer is no, what should the process do about it?
This package owns the first two as values and leaves the third to you.
Decide does not verify — that is framework/entitlement. It does not upgrade — that is framework/selfupdate. And it never ends your process. It reads what your verifier returned and says what to do; the effects stay in your control flow, where they can be refused, logged or tested.
decision := gate.Decide(policy, invocation.Path[1:], func() error {
_, err := service.Verify(time.Now())
return err
})
switch decision.Outcome {
case gate.OutcomeAllow:
// run the command; decision.FloorUnmet may still want reporting
case gate.OutcomeUpgrade:
// upgrade AT MOST ONCE, then re-run
default:
fmt.Fprintln(os.Stderr, decision.Cause)
os.Exit(errs.ExitCodeOf(decision.Cause))
}
The policy refuses to be unsafe
Policy.Validate reports every fault at once rather than the first, and two of them are lockouts rather than typos: a policy that exempts nothing cannot be repaired from inside the binary, and a policy that gates its own recovery command leaves a machine whose entitlement lapsed with no path back short of reinstalling. Policy.RecoveryPaths is what turns the second from a comment into a construction-time refusal.
Index
Constants
const CodePolicyInvalid errs.Code = 140289
CodePolicyInvalid identifies a gate policy that could not be used as written.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.CodePolicyInvalid.
Code 0.2.36.1
const OutcomeAllow Outcome = 1
OutcomeAllow runs the invocation.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.OutcomeAllow.
const OutcomeRefuse Outcome = 2
OutcomeRefuse stops the invocation; Decision.Cause says why.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.OutcomeRefuse.
const OutcomeUpgrade Outcome = 3
OutcomeUpgrade asks the caller to upgrade and re-run.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.OutcomeUpgrade.
const UpdateApply UpdateAction = 2
UpdateApply asks the caller to upgrade and re-run the invocation.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.UpdateApply.
const UpdateRefuse UpdateAction = 1
UpdateRefuse stops the invocation and reports the floor.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.UpdateRefuse.
const UpdateWarn UpdateAction = 3
UpdateWarn runs the invocation anyway and reports the floor alongside it.
Initialised with github.com/kitsunium/sdk/framework/internal/core/gate.UpdateWarn.
func Decide(policy *Policy, path []string, verify func() error) Decision
Decide classifies one invocation.
Exemption is checked FIRST, and verify is not CALLED when it holds — which is why this takes a function rather than an error. A shell-completion hook or a `license status` on a machine whose licence is broken must not pay for a network round trip, and neither should have to remember not to run one.
The version floor is checked BEFORE a refusal is propagated, because an out-of-date binary must be told to upgrade whether or not its entitlement is also in order.
It takes policy, your gate policy — a nil one refuses everything, including a verification that would have passed; path, the command path relative to the root with the root itself NOT included, where nil is the bare root invocation; and verify, your entitlement verification, CALLED AT MOST ONCE and only when the invocation is not exempt, with a nil verify refusing.
It returns what to do, and everything the verifier said.
Decision is what the gate concluded about one invocation. It carries no method that acts.
type Decision = coregate.DecisionValue
Decision is an alias of github.com/kitsunium/sdk/framework/internal/core/gate.DecisionValue, declared as:
type Decision struct {
// Outcome is what to do.
Outcome Outcome
// Cause is the refusal the verifier produced, or nil.
//
// It is carried rather than re-labelled so the caller keeps everything the
// domain said: errors.Is answers on entitlement's sentinels and
// errs.CodeOf on its codes, through this field, exactly as if the caller
// had held the verifier's error itself. A gate that summarised the cause
// would force every consumer to match on text.
//
// Non-nil with OutcomeUpgrade too: the floor refusal is what SAYS which
// version is required, and a caller that upgrades needs to read it.
Cause error
// Exempt records that this invocation was never checked, as opposed to
// checked and allowed.
//
// The two are the same Outcome and a very different fact. A daemon that
// seeds a watchdog from a verification must not seed it from an exemption:
// no verification happened, so there is no grant, no deadline and nothing
// to age against.
Exempt bool
// FloorUnmet records that the vendor mandates a newer build, whatever the
// policy decided to do about it.
//
// It is separate from Outcome because UpdateWarn allows the invocation and
// the caller still has to say so. Reading Outcome alone would lose that.
FloorUnmet bool
}
Outcome is what the caller should do with an invocation. Its zero value is unclaimed, so a decision nobody made never reads as "allow".
type Outcome = coregate.Outcome
Outcome is an alias of github.com/kitsunium/sdk/framework/internal/core/gate.Outcome, declared as:
type Outcome uint8
func (Outcome) String
func (Outcome) String() string
String names the outcome for a diagnostic, spelling the unclaimed zero value "unset" rather than inventing a name for it.
Policy is one product's gate policy: what runs unchecked, what must never be gated, and what a mandated upgrade does. Its zero value is refused by Validate.
type Policy = coregate.PolicyValue
Policy is an alias of github.com/kitsunium/sdk/framework/internal/core/gate.PolicyValue, declared as:
type Policy struct {
// ExemptExact lists command paths, relative to the root, that run without
// the check — matched WHOLE, so a same-named command elsewhere in the tree
// is not silently exempted with them.
//
// The empty string is the bare root invocation: a binary run with no
// subcommand shows its help, and showing help must not require an
// entitlement any more than `help` itself does.
//
// Paths are space-joined, e.g. "skill install". A command name cannot
// contain a space, so the joining is lossless.
ExemptExact []string
// ExemptSubtree lists command paths whose whole subtree runs without the
// check — the path itself and every command under it.
//
// Use it when the exemption is about a CAPABILITY rather than a command:
// everything under `license` repairs the licence, so gating any of it would
// be gating the repair. Use ExemptExact when a parent and its children
// differ, which they usually do.
ExemptSubtree []string
// RecoveryPaths lists the commands an operator runs to repair a refused
// entitlement. Every one of them MUST be exempt, and Validate refuses a
// policy where one is not.
//
// It exists because the failure it prevents is silent and total: a policy
// that gates its own repair command leaves a machine whose licence lapsed
// with no path back short of reinstalling the binary, and nothing about the
// policy LOOKS wrong — the exemption list is simply missing an entry.
// Naming them separately turns that into a construction-time refusal.
RecoveryPaths []string
// OnUpdateRequired is what happens when the entitlement verifies but the
// vendor mandates a newer build than this one.
//
// It has no default. See UpdateAction.
OnUpdateRequired UpdateAction
}
func (Policy) Exempt
func (*Policy) Exempt(path []string) bool
Exempt reports whether the command at path runs without the check.
It takes the command path relative to the root, with the root itself NOT included. A nil path, an empty one, and a single empty element — which is what splitting an empty string yields — are all the bare root invocation.
func (*Policy) Validate() error
Validate reports every way this policy is unusable, rather than the first.
A caller fixing a policy wants the whole list: the checks are independent, and a policy corrected one refusal at a time takes as many start-ups as it has faults.
It returns ONE error naming them all rather than an errors.Join of several, and that is the difference between a promise and a delivered one: a joined error has no single Private detail, so errs.PrivateOf reports the first fault's and a structured-logging caller would still see them one at a time.
It returns one typed refusal whose private detail names every fault, or nil.
UpdateAction is what happens when the vendor mandates a newer build. Its zero value is unclaimed, because refusing and upgrading are opposite answers.
type UpdateAction = coregate.UpdateAction
UpdateAction is an alias of github.com/kitsunium/sdk/framework/internal/core/gate.UpdateAction, declared as:
type UpdateAction uint8
func (UpdateAction) String
func (UpdateAction) String() string
String names the action for a diagnostic, spelling the unclaimed zero value "unset" rather than inventing a name for it.
func (UpdateAction) Valid
func (UpdateAction) Valid() bool
Valid reports whether this action was set to one of the three answers. It is false for the unclaimed zero value, and false for anything past the last action — a value from a newer build of this package that this one cannot honour.
Generated from docs/api by tools/genindex.