Help us shape the future of Go! Take the 2026 Go Developer Survey

specgate

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package specgate decides whether somebody may write a spec revision, for the parts of a spec that app.spec.edit alone does not cover: an app's egress rules (R-182 – R-184) and automatic deploys on an app whose deploys need approval (R-158).

One function, called by every path that writes a revision from content the caller supplied, so that no surface — console, CLI, MCP — can save a spec another surface would refuse (R-261). Paths that copy the pinned spec and change something else (routing, resources, a pinned commit) carry its egress and auto-deploy settings unchanged and need nothing from here.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Approval

type Approval struct {
	Required bool             `json:"required"`
	Reasons  []ApprovalReason `json:"reasons"`
}

Approval is whether deploying a spec would need approval, and why (R-154). What the plan shows before anybody asks for the deploy.

func ApprovalFor

func ApprovalFor(doc policy.Document, appID string, running, next *spec.AppSpec) Approval

ApprovalFor is whether deploying next over running needs approval. running is the spec the app runs now, nil for an app that has never deployed.

type ApprovalReason

type ApprovalReason struct {
	Reason  approval.Reason `json:"reason"`
	Message string          `json:"message"`
}

ApprovalReason is one reason a deploy needs approval, with what it means for the person reading the plan.

type Authorizer

type Authorizer interface {
	Allows(ctx context.Context, p authz.Principal, appID string, verb authz.Verb) (bool, error)
	CheckControl(ctx context.Context, p authz.Principal, appID string, verb authz.Verb) error
}

Authorizer is the two questions the gate asks. *authz.Authorizer answers both: Allows without auditing, CheckControl auditing a denial (design 06 §6).

func Quiet

func Quiet(az Previewer) Authorizer

Quiet is an Authorizer for a dry run: every question is answered as it would be for a real save, and no refusal is written to the audit log, because nothing was attempted.

type Change

type Change struct {
	AppID string

	// Policy is host policy as it stands now. The gate judges a change by the
	// rules in force when it is saved; the planner judges the deploy again
	// under the rules in force then (R-183).
	Policy policy.Document

	// Pinned is the app's pinned spec, nil for an app that has none yet. A
	// change is measured from it.
	Pinned *spec.AppSpec

	// Next is the revision being written.
	Next *spec.AppSpec
}

Change is one revision somebody wants to write.

type Previewer

type Previewer interface {
	Allows(ctx context.Context, p authz.Principal, appID string, verb authz.Verb) (bool, error)
	PreviewControl(ctx context.Context, p authz.Principal, appID string, verb authz.Verb) error
}

Previewer answers CheckControl's question without auditing a denial. *authz.Authorizer is one, through PreviewControl.

type Result

type Result struct {
	// EgressChanged is whether the change moves the app's egress at all.
	EgressChanged bool `json:"egress_changed"`

	// Loosenings is what the change loosens that the pinned spec did not
	// (policy.NewLoosenings).
	Loosenings []policy.Loosening `json:"loosenings,omitempty"`

	// NeedsApproval is whether those loosenings make the app's next deploy
	// need approval (R-154): policy gates loosening by approval.
	NeedsApproval bool `json:"needs_approval"`
}

Result is what the gate found about a change it allowed.

func Check

func Check(ctx context.Context, az Authorizer, p authz.Principal, c Change) (Result, error)

Check refuses a change the principal may not make, and otherwise says what it found.

  • Any change to egress needs app.egress.tighten or app.egress.loosen (R-184). Holding app.egress.loosen covers every egress change, so it is asked first and without auditing a denial: somebody who holds loosen and not tighten is not denied anything.
  • A loosening the pinned spec did not already carry is judged by policy (R-183): forbidden refuses it, verb needs app.egress.loosen, approval lets it through for the deploy to be approved.
  • Automatic deploys are refused on an app whose deploys need approval whatever they change (R-158).

Jump to

Keyboard shortcuts

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