appcontract

package
v1.0.0-rc.7 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package appcontract owns AppTown's native .apptown/app.yaml declaration, parsing, validation, and preparation of independent execution inputs.

Parse accepts source bytes; callers own file and repository lookup. Prepare accepts already-resolved service connections; callers own provisioning, image selection, scoped secret delivery, health rendering, and execution. This package has no runtime collaborators or persistence dependencies. App-part replicas may explicitly declare 1 (also the omitted/null default); larger counts are refused during parsing, before any runtime effects.

Index

Constants

View Source
const BrowserEnvVar = "APPTOWN_BROWSER_ENV"

BrowserEnvVar carries a static part's browser-safe values. AppTown alone sets it; a contract or secret that names it is refused, so nothing can replace what the page is given.

View Source
const DefaultProfile = "default"

DefaultProfile is the base: the parts as `app:` declares them.

View Source
const MinMemoryBytes = 32 << 20

MinMemoryBytes is the smallest memory a part may declare: below it a container cannot start reliably.

View Source
const PreviewProfile = "preview"

PreviewProfile is the profile a preview uses when a file declares it.

View Source
const ReleaseEnvVar = "APPTOWN_RELEASE"

ReleaseEnvVar carries a static part's release identity (ReleaseIdentity); its file server answers it at ReleasePath, so a target whose front door AppTown can only reach from outside (App Platform) can witness that the part it deployed, in this release, is what answers. The AppTown sets it.

View Source
const ReleasePath = "/_apptown/release"

ReleasePath is where a static site's file server answers ReleaseEnvVar.

View Source
const ReservedEnvPrefix = "APPTOWN_"

ReservedEnvPrefix begins the names AppTown keeps for itself: a variable AppTown adds in a 1.x release starts with it, so no app that followed the contract already uses that name.

View Source
const TrustEnvVar = trust.EnvVar

TrustEnvVar carries the extra certificate authorities an app's own containers trust, as the script that installs them (internal/trust), which the app's image runs at start. AppTown sets it.

Variables

View Source
var AppTownSetEnv = []string{"PORT", "APPTOWN_ORIGINS", "DATA_DIR", ReleaseEnvVar, BrowserEnvVar, TrustEnvVar}

AppTownSetEnv are the environment names AppTown alone sets on a part. An app that set one would win over AppTown's value on one target and lose on another, so the contract refuses it in a part's or a profile's environment (the contract reference, "The environment an app gets").

View Source
var JSONSchema []byte

JSONSchema is the JSON Schema for .apptown/app.yaml, served to models and editors. It describes the structure the parser accepts; rules a schema cannot express (exactly one public part, needs in the catalog) stay in Parse. A test holds its properties to the parser's own document types.

Functions

func CheckSpecPath

func CheckSpecPath(path string) error

CheckSpecPath refuses a secrets.spec path that is absolute or leaves the project's tree: the declaration is read from the app's own source.

func FormatMemory

func FormatMemory(b int64) string

FormatMemory writes bytes as app.yaml does: Gi when whole, else Mi.

func MergeEnv

func MergeEnv(base, override map[string]string) map[string]string

MergeEnv is a new environment: base, with override's values taking precedence. Neither input is changed, so a part's declared environment stays as written.

func ParseCPU

func ParseCPU(v any) (float64, error)

ParseCPU reads a cpu as app.yaml writes it: a positive number of CPUs ("0.5", "2"), or Kubernetes millicores ("500m").

func ParseDataCheck

func ParseDataCheck(line string) (path string, expect int, err error)

ParseDataCheck reads one rehearsal data check: "GET <path>", answered 200, or "GET <path> expect <status>". It is the GET line of the probe grammar (internal/check), and only that: a data check asks one path.

func ParseHealthDuration

func ParseHealthDuration(s string) (time.Duration, error)

ParseHealthDuration accepts Go duration strings ("90s", "10s", "3s"); a plain integer would be ambiguous, so require a unit.

func ParseMemory

func ParseMemory(s string) (int64, error)

ParseMemory reads a memory as the current contract writes it, in Kubernetes binary units: a whole number of mebibytes or gibibytes, "512Mi", "1Gi". A bare "512M" is refused, not guessed: version 1 read it as MiB, Kubernetes and Score read it as decimal megabytes.

func ParseMemoryV1

func ParseMemoryV1(s string) (int64, error)

ParseMemoryV1 reads a memory as a version-1 contract or a pack default writes it: "Mi"/"Gi", or "M"/"G" meaning the same (MiB, GiB).

func ParseSizeName

func ParseSizeName(value any) (string, error)

ParseSizeName reads a size selector without coercing numbers or booleans.

func ReleaseIdentity

func ReleaseIdentity(part, revision string) string

ReleaseIdentity is what a static part's file server is told to answer: the part by name and the revision its release was cut from.

func ReservedEnvName

func ReservedEnvName(name string) string

ReservedEnvName says why an app may not set name in a part's environment or declare it as a secret, or "" when it may: AppTown sets it on every part (AppTownSetEnv), or it starts with ReservedEnvPrefix.

func WireEnv

func WireEnv(version int, owner string, env, values map[string]string) (map[string]string, error)

WireEnv is env with each need reference replaced by the value the need gives (values: the need variables the target has resolved, by name) and each $${needs. written as ${needs.. Version-1 environments are returned unchanged. A reference to a variable the target did not give is an error naming it: every target gives every need's documented variables.

func WiresFrom

func WiresFrom(version int, env map[string]string) map[string][]string

WiresFrom says which of values' keys env's references read, so a target can deliver a wired value with the care its source needs (a credential stays a secret).

func WithoutNeedRefs

func WithoutNeedRefs(version int, v string) string

WithoutNeedRefs is v with its need references removed and each escaped one written as the literal it delivers: what is left is what the app wrote literally, for a target that must check literal text (App Platform rewrites any ${…} it delivers).

Types

type Contract

type Contract struct {
	Needs      []string
	NeedParams map[string]map[string]string
	// NeedSizes are catalogue size selections; Command is filled only by execution resolution.
	NeedSizes map[string]NeedSize
	// Version is the contract's shape version: 1 (or none written) or 2
	// (parse.go, currentContractVersion).
	Version int
	// NeedResources is the most each need's service may use, by need;
	// a need that declares nothing is absent (no limit). It is not a
	// parameter: the catalogue never sees it.
	NeedResources map[string]Resources

	// Parts is the canonical declaration; exactly one is public.
	Parts []Part
	// Init are ordered run-to-completion containers (migrations, asset builds)
	// that must succeed BEFORE the app parts start. app.yaml-only.
	Init []InitContainer
	// Secrets metadata: the declaration file's path
	// (default secretspec.toml) and which generated secrets are stable per
	// project rather than per deployment. The declaration itself is read
	// from the tree at boot (secretspec.go); values never live here.
	SecretSpecPath string
	SecretGenerate map[string]string
	// RehearsalChecks are the app's declared data checks, each "GET <path>"
	// or "GET <path> expect <status>": paths it answers without its own
	// sign-in that read its database. A restore rehearsal asks them after
	// readiness and the front page (design/subsystems/deployment.md,
	// "Restore rehearsals").
	RehearsalChecks []string
	// Profiles are the declared profiles by name, each part's or need's
	// changes by its name (resources.go); a need's are its resources alone.
	// The base, default, is the declaration itself.
	Profiles map[string]map[string]ProfilePart
}

Contract is the canonical declaration parsed from .apptown/app.yaml. Callers retain it as source intent and use Prepare for mutable execution inputs.

func Parse

func Parse(raw []byte) (*Contract, error)

Parse parses .apptown/app.yaml into the internal contract. Errors name the offending field and are written for a human or an agent to fix.

func (*Contract) CheckNeedSizes

func (c *Contract) CheckNeedSizes() error

CheckNeedSizes refuses competing source declarations. Applied operator cap overrides are separate recorded decisions, not source declarations.

func (*Contract) Disposable

func (c *Contract) Disposable(need string) bool

Disposable says the app declared need's data not worth keeping (`redis: {disposable: true}`, a cache): it is never copied, starts empty, and reads as disposable rather than as a gap in its protection.

func (*Contract) ForProfile

func (c *Contract) ForProfile(name string) *Contract

ForProfile is the contract under profile name: each part's resources replaced field by field and its environment merged key by key by what the profile declares for it. A name app.yaml does not declare, default among them, is the base unchanged. It performs no I/O and changes nothing of c.

func (*Contract) NeedRefs

func (c *Contract) NeedRefs() []NeedRef

NeedRefs lists the contract's wiring references in a stable order: none for a version-1 contract.

func (*Contract) PartResources

func (c *Contract) PartResources() map[string]Resources

PartResources is each part's and each need's resources, by name: one map, since a name is one or the other.

func (*Contract) ProfileNames

func (c *Contract) ProfileNames() []string

ProfileNames are the profiles app.yaml declares, sorted; the base (default) is not among them.

func (*Contract) PublicPart

func (c *Contract) PublicPart() *Part

PublicPart returns the declared front door, or nil if none is present. The returned part belongs to the declaration; use Prepare before modifying it.

func (*Contract) PublicRelease

func (c *Contract) PublicRelease(revision string) string

PublicRelease is the release identity c's front door answers when it is served from revision: the public part's, when that part is a static site (its file server answers ReleasePath), and "" otherwise, or when there is no revision, whose release is not witnessed. Every target that runs a release sets and witnesses this one value.

func (*Contract) WithResources

func (c *Contract) WithResources(over map[string]Resources) *Contract

WithResources is c with each named part's or need's resources replaced field by field by over (an operator's override). What it does not name is unchanged; it changes nothing of c.

type Health

type Health struct {
	Readiness *Probe `yaml:"readiness" json:"readiness,omitempty"`
	Liveness  *Probe `yaml:"liveness" json:"liveness,omitempty"`
}

Health is the per-part `health:` block.

type InitContainer

type InitContainer struct {
	Name    string
	Build   string
	Image   string
	Command []string
	Env     map[string]string
}

InitContainer runs once to completion before the app parts (ECS: an essential:false container the app parts dependsOn with condition SUCCESS).

type MemoryDefault

type MemoryDefault struct {
	Memory string `json:"memory,omitempty" yaml:"memory,omitempty"`
}

MemoryDefault is a positive memory fallback supplied by adopted content. Unlimited memory is an operator decision, never a content default.

func (MemoryDefault) Bytes

func (d MemoryDefault) Bytes() (int64, error)

func (*MemoryDefault) UnmarshalJSON

func (d *MemoryDefault) UnmarshalJSON(raw []byte) error

func (*MemoryDefault) UnmarshalYAML

func (d *MemoryDefault) UnmarshalYAML(n *yaml.Node) error

type NeedRef

type NeedRef struct {
	Part, Key, Need, Var string
}

NeedRef is one wiring reference: Part's environment Key names Need's Var. Part is a part's name, "init-<name>" for an init step, or "<profile>/<part>" for a profile's environment.

type NeedSize

type NeedSize struct {
	Name    string   `json:"name"`
	Command []string `json:"command,omitempty"`
}

NeedSize selects a catalogue-owned container configuration. Source YAML sets only Name; Command is resolved by the dependency owner or restored from the generation's applied record, never supplied through the app contract YAML.

type Part

type Part struct {
	Name   string
	Build  string // build context (compose `build:`)
	Image  string // pushed ref (compose `image:`)
	Static string // client-only: serve this committed folder
	// StaticBuild is a static site built first: its output folder is
	// served as Static is. Exactly one of Build, Image, Static, StaticBuild.
	StaticBuild *StaticBuild
	Command     []string          // compose `command:`
	Port        int               // container port (from compose `ports:`)
	Env         map[string]string // compose `environment:`
	DataDir     string            // AppTown: a named volume that survives redeploys
	Health      string            // LEGACY healthcheck shell command; exit 0 = healthy
	Intent      *Health           // structured per-part health intent (design/subsystems/healthchecks.md); nil = none declared
	Public      bool              // the front door the preview/deploy URL points at
	// Resources is the most the part may use; zero fields are no limit.
	Resources Resources
	// ServesStatic marks a static part after its files are packed into the
	// file-server image (Static is cleared then): it is still a static site,
	// and AppTown still gives its page browser values.
	ServesStatic bool
}

Part is one app-owned container from .apptown/app.yaml's `app:` map. Compose conventions for the run mechanics; Public marks the front door (exactly one).

func (*Part) IsStatic

func (p *Part) IsStatic() bool

IsStatic says whether the part is a static site, served by AppTown's file server: a committed folder, a declared build, or an image AppTown already packed from either. It classifies; it does not select a source (an image already packed is never a folder to mount or rebuild).

type Plan

type Plan struct {
	Parts []Part
	Init  []InitContainer
}

Plan holds one execution's private inputs. Runtime owners select images and apply scoped secrets to this plan without changing the source declaration.

func Prepare

func Prepare(c *Contract, connections map[string]string) *Plan

Prepare copies a declaration into an independent execution plan. Resolved service connections override each consumer's own environment; public-only values never flow into workers or init containers. It performs no I/O.

func PrepareWired

func PrepareWired(c *Contract, connections map[string]string) (*Plan, error)

PrepareWired is Prepare after each part's and init step's need references are replaced with the values connections gives (WireEnv), the path every container target takes so wiring is the same everywhere.

func (*Plan) PublicOnly

func (p *Plan) PublicOnly() *Plan

PublicOnly is the plan with its public part and init steps alone: what a restore rehearsal runs, so workers and schedulers stay off.

func (*Plan) PublicPart

func (p *Plan) PublicPart() *Part

PublicPart returns the plan's mutable front door, or nil if none is present.

type Probe

type Probe struct {
	// exactly one target:
	HTTP string `yaml:"http" json:"http,omitempty"` // a path, e.g. /healthz
	TCP  int    `yaml:"tcp" json:"tcp,omitempty"`   // a port (0 = infer from the part)
	Exec []any  `yaml:"exec" json:"exec,omitempty"` // a command run INSIDE the container

	// readiness only:
	RequestContext string `yaml:"requestContext" json:"requestContext,omitempty"` // frontDoor | direct
	Expect         int    `yaml:"expect" json:"expect,omitempty"`                 // HTTP status (default 200)
	ChecksDeps     bool   `yaml:"checksDeps" json:"checksDeps,omitempty"`         // ASSERTION (metadata, not proven)

	// execution policy (design §4.1):
	StartupGrace     string `yaml:"startupGrace" json:"startupGrace,omitempty"` // e.g. 90s (a FLOOR)
	Period           string `yaml:"period" json:"period,omitempty"`
	Timeout          string `yaml:"timeout" json:"timeout,omitempty"`
	FailureThreshold int    `yaml:"failureThreshold" json:"failureThreshold,omitempty"`
	SuccessThreshold int    `yaml:"successThreshold" json:"successThreshold,omitempty"`
	// contains filtered or unexported fields
}

Probe is one liveness or readiness probe's intent.

func (*Probe) UnmarshalYAML

func (p *Probe) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML decodes a probe and notes which keys it wrote.

type ProfileChoice

type ProfileChoice string

ProfileChoice is how a deployment's profile was chosen.

const (
	ProfileChosen  ProfileChoice = "chosen"  // an explicit choice for the deployment
	ProfilePreview ProfileChoice = "preview" // a preview, and a file declares preview
	ProfileBase    ProfileChoice = "default" // nothing else applies
)

func DeploymentProfile

func DeploymentProfile(chosen, deployment string, declared map[string]bool) (string, ProfileChoice)

DeploymentProfile is the one profile a deployment uses, whichever file declares it: the explicit choice when app.yaml or secretspec.toml declares it; otherwise, for the preview deployment, preview when either declares it; otherwise default. Each file then applies the name when it declares it and its own base when it does not.

type ProfilePart

type ProfilePart struct {
	Size      string            `json:"size,omitempty"`
	Resources Resources         `json:"resources,omitzero"`
	Env       map[string]string `json:"env,omitempty"`
}

ProfilePart is one profile's change to one part.

type Resources

type Resources struct {
	MemoryBytes int64   `json:"memoryBytes,omitempty"` // swap included
	CPUs        float64 `json:"cpus,omitempty"`
}

Resources is the most a part may use. Zero in a field is no limit of that kind in a declaration or historical applied record. Fresh container execution supplies defaults separately; zero is never proof a running container is capped.

func (Resources) CPUWords

func (r Resources) CPUWords() string

CPUWords is the cpu as app.yaml writes it, or "no limit".

func (Resources) Declared

func (r Resources) Declared() bool

Declared says whether any limit is declared.

func (Resources) MemoryWords

func (r Resources) MemoryWords() string

MemoryWords is the memory as app.yaml writes it, or "no limit".

func (Resources) Over

func (r Resources) Over(other Resources) Resources

Over is r with each field other declares replacing r's: a profile that declares memory alone keeps the base's cpu.

type StaticBuild

type StaticBuild struct {
	Context, Target, Output string
}

StaticBuild declares a static site AppTown builds before serving it: the Dockerfile in Context is built to its Target stage (the last when empty), and the folder Output inside that stage's files is the site.

Jump to

Keyboard shortcuts

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