Documentation
¶
Overview ¶
forge:exclude-contract
internal/config is a pure schema/data/constants package: it declares the canonical forge.yaml types plus package-level lookup tables (e.g. ExperimentalFeatureNames — the stable display order shared by `forge audit`, the startup warning, and `forge features`). It has no Service seam and no contract.go, so it is not a contract-shaped package. The directive opts it out of the contract lint rules — specifically the exported-vars rule, which would otherwise demand ExperimentalFeatureNames become a getter. A getter is the ideal fix but its call sites reach into internal/cli (an ordered slice spread as `config.ExperimentalFeatureNames...`); converting it there is a cross-package change outside this pass. This is the accepted suppression for a genuine data/catalogue package.
Package config defines the canonical forge.yaml types shared across forge.
The data types (ProjectConfig, ComponentConfig, etc.) are proto-like data carriers — they ship with accessor methods (Effective*, Is*) directly on the struct values. Those methods are part of the data, not behavior to mock. The Service interface below is the package's narrow behavior surface (currently kind classification); future YAML load/save behavior moves behind it as it lands (Load lives today in internal/cli and internal/generator and is ported in those phases).
derive.go — shape-derived defaults for forge.yaml.
A freshly scaffolded forge.yaml is minimal: name, module_path, forge_version, services, frontends. Everything else — the features: block and the section blocks (database, ci, lint, contracts, auth, deploy, docker, k8s) — is DERIVED from the project shape at load time:
- feature flags derive from kind / database / frontends (see DeriveFeatureDefaults for the per-flag rule);
- absent section blocks are filled with the canonical scaffold defaults for the project kind (see sectionDefaults).
Anything the user writes explicitly is taken literally; derivation never overrides a present value. The features: block and the section blocks therefore remain valid override surfaces — they are just no longer required boilerplate.
The write side is symmetric: NormalizeForWrite drops values that are byte-identical to what derivation would produce, so a load → mutate → write round-trip keeps forge.yaml minimal instead of materializing every derived default back into the file.
Code generated by forge. DO NOT EDIT. forge:hash=e245f1114b774684381c8bb3491d39193921debeb88d9f3db6248e052e1e0cb1 forge-owned: regenerated every run — do not edit (forge disown to take ownership) Source: contract.go in this package.
To customize: edit contract.go (the interface IS the public surface) and re-run "forge generate". This file is regenerated unconditionally.
Index ¶
- Constants
- Variables
- func ApplyDerivedDefaults(c *ProjectConfig)
- func DeriveFeatureDefaults(c *ProjectConfig) map[FeatureName]bool
- func DeriveProjectKind(components []ComponentConfig, hasComponentsFile bool) string
- func DisabledFeatureError(name string) error
- func EffectiveProjectBinary(binary string) string
- func EffectiveProjectKind(kind string) string
- func FeatureDependencies(name FeatureName) []string
- func IsExperimentalFeature(name FeatureName) bool
- func LoadEnvironmentConfig(projectDir, envName string) (map[string]any, error)
- func MatchExclude(patterns []string, pkgPath string) bool
- func SetConfigWarningSink(w io.Writer) io.Writer
- func ValidateBasePath(bp string) (string, bool)
- type APIConfig
- type APIKeyConfig
- type AuthConfig
- type CIConfig
- type CIE2EConfig
- type CIExtraJob
- type CIExtraJobStep
- type CILintConfig
- type CIPermConfig
- type CITestConfig
- type CIVulnConfig
- type CRDConfig
- type ComponentConfig
- type ConfigGuardConfig
- type ContractsConfig
- type DatabaseConfig
- type DeployConcurrency
- type DeployConfig
- type DeployEnvConfig
- type Deps
- type DockerConfig
- type DocsConfig
- type ExperimentalConfig
- type FeatureName
- type FeaturesConfig
- func (f FeaturesConfig) BuildEnabled() bool
- func (f FeaturesConfig) CIEnabled() bool
- func (f FeaturesConfig) CodegenEnabled() bool
- func (f FeaturesConfig) ContractsEnabled() bool
- func (f FeaturesConfig) DeployEnabled() bool
- func (f FeaturesConfig) DiagnosticsEnabled() bool
- func (f FeaturesConfig) DocsEnabled() bool
- func (f FeaturesConfig) EffectiveFeatures() map[string]bool
- func (f FeaturesConfig) EnabledExperimentalFeatures() []FeatureName
- func (f FeaturesConfig) ExternalBuildsEnabled() bool
- func (f FeaturesConfig) FrontendEnabled() bool
- func (f FeaturesConfig) HotReloadEnabled() bool
- func (f FeaturesConfig) IngressEnabled() bool
- func (f FeaturesConfig) IsZero() bool
- func (f FeaturesConfig) MigrationsEnabled() bool
- func (f FeaturesConfig) ORMEnabled() bool
- func (f FeaturesConfig) ObservabilityEnabled() bool
- func (f FeaturesConfig) OperatorsEnabled() bool
- func (f FeaturesConfig) PacksEnabled() bool
- func (f FeaturesConfig) StrictWiringEnabled() bool
- type FrontendConfig
- type FrontendLintConfig
- type FrontendProjectConfig
- type JWTConfig
- type K8sConfig
- type LintConfig
- type MigrationSafetyConfig
- type MockService
- type MultiTenantConfig
- type PackOverride
- type PackageConfig
- type PlanEntity
- type PlanEntityField
- type PlanField
- type PlanFile
- type PlanFrontend
- type PlanPackage
- type PlanRPC
- type PlanService
- type PortSpec
- type ProjectConfig
- func (c ProjectConfig) BinaryComponents() []ComponentConfig
- func (c ProjectConfig) Crons() []ComponentConfig
- func (c ProjectConfig) EffectiveBinary() string
- func (c ProjectConfig) EffectiveForgeVersion() string
- func (c *ProjectConfig) EffectiveHotReload() bool
- func (c ProjectConfig) EffectiveKind() string
- func (c ProjectConfig) HasReactNativeFrontend() bool
- func (c ProjectConfig) IsBinaryShared() bool
- func (c ProjectConfig) IsCLIKind() bool
- func (c ProjectConfig) IsFrontendWorkspacesEnabled() bool
- func (c ProjectConfig) IsLibraryKind() bool
- func (c ProjectConfig) IsServiceKind() bool
- func (c ProjectConfig) Operators() []ComponentConfig
- func (c ProjectConfig) Servers() []ComponentConfig
- func (c ProjectConfig) Workers() []ComponentConfig
- type Service
- type SmokeConfig
- type SmokeFlowCheck
- type StackConfig
- type StackFrontend
- type ValidationError
Constants ¶
const ( ProjectKindService = "service" ProjectKindCLI = "cli" ProjectKindLibrary = "library" )
ProjectKind identifies the shape of a forge project. The default, "service", produces a Connect-RPC service scaffold (handlers, middleware, deploy manifests). "cli" produces a Cobra-based CLI binary with no server-shaped scaffolding. "library" produces a pure Go module with no cmd/ entry point.
const ( ProjectBinaryPerService = "per-service" )
ProjectBinary describes the binary packaging shape for a service project. "per-service" (the default) emits the canonical layout: one `cmd/server.go` cobra root with `server [services...]` filtering at the runtime layer, and one Application per service in deploy/ KCL. "shared" emits one cobra subcommand per service so callers can invoke `./project <svc>` directly, and KCL emits a single MultiServiceApplication (one image, N Deployments) instead of N Applications. See FORGE_BACKLOG.md "Layer B" + the migrations/v0.x-to-binary-shared/ skill for tradeoffs.
const ( ComponentKindServer = "server" ComponentKindWorker = "worker" ComponentKindCron = "cron" ComponentKindOperator = "operator" ComponentKindBinary = "binary" )
Component kind constants. Kind is the single discriminator on a ComponentConfig — it replaces the old `services[].type` + `services[].kind` pair and the separate `binaries:` block.
- server — Connect-RPC handlers + authorizer + client + frontend hooks + bootstrap row + cobra subcommand (was type=go_service).
- worker — in-process ContextWorker goroutine; bootstrap Workers row.
- cron — scheduled job; Schedule drives it. In-process scheduled goroutine for dev, CronJob in deploy. First-class (was worker + kind:cron).
- operator — controller-runtime manager + CRDs.
- binary — standalone cobra subcommand cmd/<name>.go (one image, run `./app <name>`); no bootstrap wiring (was the binaries: block).
const ( EnforceTypedAccessOff = "off" EnforceTypedAccessWarn = "warn" EnforceTypedAccessError = "error" )
Typed-access enforcement levels for ConfigGuardConfig.EnforceTypedAccess.
- off — emit no env-reading guardrail at all.
- warn — emit the forbidigo guardrail as an ADVISORY check: violations are reported (during `forge lint` and in the generated .golangci.yml) but never fail the build. This is the default for an ABSENT `config:` block, so existing projects upgrade without a flag-day.
- error — emit the forbidigo guardrail as a GATING check: violations fail `forge lint` / CI. `forge new` scaffolds this for greenfield projects, which carry no legacy env-reading debt.
const DefaultHandlerFileMaxLOC = 1000
DefaultHandlerFileMaxLOC is the built-in threshold used by the forgeconv-handler-file-size analyzer when the project does not set lint.handler_file_max_loc in forge.yaml. Picked at 1000 because that roughly tracks "two screens of any modern editor" plus generous buffer — files past that point materially harm review velocity and almost always benefit from the per-RPC split that `forge add handler-file` is intended to support.
const DefaultLoaderPackage = "pkg/config"
DefaultLoaderPackage is the package path forge's config-loader codegen emits into a consuming project (generated from proto/config/v1/config.proto via the (forge.v1.config) annotation). It is the ONE package allowed to read the environment directly; the typed-access guardrail allowlists it so the generated loader can legitimately call os.Getenv / os.LookupEnv.
const HTTPPortName = "http"
HTTPPortName is the conventional name for a component's primary HTTP port in the Ports map. Server components serve their Connect mux here.
Variables ¶
var ErrEnvironmentNotFound = errors.New("environment not found: no config.<env>.yaml sibling file")
ErrEnvironmentNotFound is returned when no sibling `config.<env>.yaml` file exists for envName.
var ExperimentalFeatureNames = []FeatureName{ FeatureIngress, FeatureExternalBuilds, FeatureOperators, FeatureStrictWiring, }
ExperimentalFeatureNames lists every Feature* constant that lives under `features.experimental:`. Iteration order is the stable display order used by `forge audit`, the startup warning, and `forge features`.
Functions ¶
func ApplyDerivedDefaults ¶
func ApplyDerivedDefaults(c *ProjectConfig)
ApplyDerivedDefaults resolves the shape-derived state of a freshly unmarshalled ProjectConfig:
- every section block that is entirely absent (zero value) is filled with the canonical scaffold default for the project kind;
- the features block gets its derivation context attached so the *Enabled() accessors can resolve absent flags from shape.
Called by the loader (LoadStrict) — code that hand-constructs a ProjectConfig in tests without calling this keeps the historical zero-value semantics.
func DeriveFeatureDefaults ¶
func DeriveFeatureDefaults(c *ProjectConfig) map[FeatureName]bool
DeriveFeatureDefaults computes the default enabled/disabled state of every stable feature from the project shape. The rules:
orm ⇔ kind == service AND a database driver is configured codegen ⇔ kind == service migrations ⇔ kind == service AND a database driver is configured ci ⇔ kind != library build ⇔ kind != library contracts ⇔ always on (contract.go works for every kind) docs ⇔ always on frontend ⇔ frontends list non-empty observability ⇔ kind == service hot_reload ⇔ kind == service packs ⇔ kind == service deploy ⇔ kind == service
"database driver configured" means Database.Driver after section defaulting — i.e. postgres for a service project unless the user explicitly set `database: driver: none`. For the canonical service shape every rule resolves to enabled, matching the historical all-enabled default; for cli/library kinds the rules reproduce the per-kind matrix that `forge new --kind` used to write out explicitly.
deploy derives from kind, NOT from a deploy/kcl/ directory probe. This is deliberate: derivation is intentionally pure project-shape — config load must not become order- or cwd-dependent by sniffing the filesystem. The scaffold ships deploy/kcl/ for every service project, so kind==service is the honest proxy; and the per-env deploy-config generate step is already a no-op when no deploy/kcl/<env>/ dirs exist on disk, so a user who deleted the deploy tree loses nothing (the steps simply find no envs to render). "deploy dir exists" was considered and rejected for those reasons.
Experimental features (ingress, external_builds, operators, strict_wiring) and diagnostics are NOT derived — they stay default-off opt-ins regardless of shape.
func DeriveProjectKind ¶
func DeriveProjectKind(components []ComponentConfig, hasComponentsFile bool) string
DeriveProjectKind infers the project kind from its components — kind is no longer a forge.yaml field. The components themselves are derived from the project's real sources (proto descriptor, the pkg/app service registry, the deploy/kcl tree, internal/handlers, and cmd/ binaries), not an authored manifest. The rule:
- any server/worker/cron/operator component → "service" (server-shaped: they need handlers/bootstrap/deploy)
- only binary component(s), no server-shaped → "cli" (a binary-only project is a cobra CLI — the binary IS the cli main)
- no components, but a service marker present → "service" (the canonical "service shell": `forge new` with no --service. The binary boots an empty appkit table and the user grows it with `forge add service`.)
- no components at all → "library" (a pure Go module with no buildable entrypoint)
hasComponentsFile distinguishes the empty-service-shell (service marker present, zero entries → service) from a pure library (no marker → library). It is the ONE filesystem fact the kind decision needs that the slice alone can't carry; the loader supplies it.
An unknown/empty component kind counts as server (EffectiveKind defaults to server), so it pulls the project toward "service".
func DisabledFeatureError ¶
DisabledFeatureError returns the canonical user-facing error for a disabled feature. Centralised so every gate site emits the same wording — sub-agents and humans grepping for the string find one authoritative format. The name argument is the lowercased feature name as it appears in forge.yaml (e.g. "deploy", "build", "packs").
func EffectiveProjectBinary ¶
EffectiveProjectBinary returns the binary mode, defaulting to "per-service" so projects predating the field keep their existing codegen shape.
func EffectiveProjectKind ¶
EffectiveProjectKind returns the project kind, defaulting to "service" so that older forge.yaml files without a kind: field continue to behave as service projects.
func FeatureDependencies ¶
func FeatureDependencies(name FeatureName) []string
FeatureDependencies returns the feature-graph edges for name as a flat list of human-readable dependency labels (other feature names and shape preconditions). Stable order. Used by `forge features` to print each feature's deps. Returns an empty slice for a feature with no edges.
func IsExperimentalFeature ¶
func IsExperimentalFeature(name FeatureName) bool
IsExperimentalFeature reports whether a feature name lives under the `features.experimental:` block (i.e. is default-OFF, opt-in, subject to schema change). Centralised so audit, the gate helper, and the startup-warning emitter share one source of truth.
func LoadEnvironmentConfig ¶
LoadEnvironmentConfig returns the per-environment config map loaded from the sibling file `config.<env>.yaml` next to forge.yaml.
projectDir is the directory containing forge.yaml. envName names the environment (`dev`, `staging`, `prod`, …).
Returns ErrEnvironmentNotFound when no `config.<env>.yaml` is present. Returns an empty map (and nil error) when the file is present but empty.
Per-env app config (runtime AppConfig values keyed by snake_case proto field names) lives exclusively in sibling files now — forge.yaml `environments[]` is gone. Per-env deploy config (cluster/namespace/registry/domain) lives in KCL `forge.K8sCluster` blocks.
func MatchExclude ¶
MatchExclude reports whether pkgPath matches any of the configured exclude patterns. A pattern matches only on whole path-segment boundaries — the rule for each pattern is the union of:
- equality (pattern == pkgPath)
- "/"-suffix (pkgPath ends with "/"+pattern — the `mypkg` shorthand for `internal/mypkg`, and deeper suffixes like `linter/contract`)
- "/"-prefix (pkgPath starts with pattern+"/" — excluding a directory excludes its whole subtree)
- mid-path (pkgPath contains "/"+pattern+"/" — the pattern names interior segment(s) of a longer path)
Empty patterns are skipped — see the package doc for why. Both pkgPath and patterns are normalised to forward-slash form before comparison (and a trailing "/" on a pattern is tolerated) so the helper behaves the same on every OS and on sloppy YAML input.
History: this used to be a raw strings.Contains substring rule. That was lenient on purpose (it caught the `mypkg` shorthand), but it also matched PARTIAL segments — the cp-forge project excluded `internal/auth` and silently lost codegen for its SIBLING package `internal/authutil`, because "internal/authutil" contains "internal/auth" as a substring. Crucially there was no fuller spelling that could escape the over-match: the pattern already was the full path of the package the owner wanted excluded. The segment-boundary rules above keep every legitimate match the substring rule supported (shorthand leaf, subtree, interior segments) while making "exclude X" stop meaning "exclude anything whose name merely starts with X".
func SetConfigWarningSink ¶
SetConfigWarningSink overrides the destination for non-fatal config warnings and returns the previous sink so callers can restore it. Used by tests to capture warning output; production code leaves the default (os.Stderr). Swapping the sink also resets the per-process dedup set so each test starts from a clean slate.
func ValidateBasePath ¶
ValidateBasePath checks the shape of a non-empty frontends[].base_path value. Returns (reason, false) on failure, ("", true) when valid.
Valid: "/admin", "/internal/admin", "/v2.1_beta" Invalid: "admin" (no leading slash), "/admin/" (trailing slash),
"/" (root mount — omit the field instead), "/ad min", "/a%2Fb".
Types ¶
type APIConfig ¶
APIConfig holds project-level API protocol-skin toggles. Both fields default to false, so projects that omit the `api:` block continue to expose only the canonical Connect/gRPC handlers without any runtime transcoding or generated spec files.
REST=true installs connectrpc.com/vanguard as middleware in front of the Connect mux. Vanguard transcodes REST↔Connect at runtime based on `google.api.http` annotations on RPCs; the CRUD proto scaffolder also emits standard REST-shaped annotations on Get/List/Create/Update/Delete RPCs so the default CRUD surface gains REST URLs without hand-editing.
OpenAPI=true is owned by a sibling agent and emits an OpenAPI spec alongside the proto compile step. The two fields compose: with both on, the generated spec reflects the REST URLs.
type APIKeyConfig ¶
type APIKeyConfig struct {
Header string `yaml:"header,omitempty"` // default: "X-API-Key"
}
APIKeyConfig holds API key authentication settings.
func (APIKeyConfig) EffectiveAPIKeyHeader ¶
func (a APIKeyConfig) EffectiveAPIKeyHeader() string
EffectiveAPIKeyHeader returns the API key header, defaulting to "X-API-Key".
type AuthConfig ¶
type AuthConfig struct {
Provider string `yaml:"provider"` // "jwt", "api_key", "both", "none"
JWT JWTConfig `yaml:"jwt,omitempty"`
APIKey APIKeyConfig `yaml:"api_key,omitempty"`
MultiTenant *MultiTenantConfig `yaml:"multi_tenant,omitempty"`
}
AuthConfig holds authentication provider settings.
type CIConfig ¶
type CIConfig struct {
Provider string `yaml:"provider"` // "github" (default)
GoVersion string `yaml:"go_version,omitempty"` // e.g. "1.26"
Lint CILintConfig `yaml:"lint,omitempty"`
Test CITestConfig `yaml:"test,omitempty"`
VulnScan CIVulnConfig `yaml:"vuln_scan,omitempty"`
E2E CIE2EConfig `yaml:"e2e,omitempty"`
Permissions CIPermConfig `yaml:"permissions,omitempty"`
ExtraJobs []CIExtraJob `yaml:"extra_jobs,omitempty"` // user extension point
}
CIConfig holds CI/CD settings.
func (*CIConfig) EffectiveGoVersion ¶
EffectiveGoVersion returns the Go version for CI, defaulting to "1.26".
func (*CIConfig) EffectivePermContents ¶
EffectivePermContents returns the contents permission, defaulting to "read".
func (*CIConfig) IsLintEnabled ¶
IsLintEnabled returns true if any linter is enabled. Zero value (all false) is treated as "all enabled" (sensible default).
func (*CIConfig) IsTestRaceEnabled ¶
IsTestRaceEnabled returns true if the race detector should be used. Zero value is treated as enabled.
func (*CIConfig) IsVulnScanEnabled ¶
IsVulnScanEnabled returns true if any vulnerability scanner is enabled. Zero value is treated as "all enabled".
type CIE2EConfig ¶
type CIE2EConfig struct {
Enabled bool `yaml:"enabled"` // default false
Runtime string `yaml:"runtime,omitempty"` // "docker-compose" or "k3d"
}
CIE2EConfig controls end-to-end testing in CI.
type CIExtraJob ¶
type CIExtraJob struct {
Name string `yaml:"name"`
RunsOn string `yaml:"runs_on,omitempty"` // default "ubuntu-latest"
Steps []CIExtraJobStep `yaml:"steps"`
}
CIExtraJob defines a user-provided additional CI job.
func (*CIExtraJob) EffectiveRunsOn ¶
func (j *CIExtraJob) EffectiveRunsOn() string
EffectiveRunsOn returns the runner for an extra job, defaulting to "ubuntu-latest".
type CIExtraJobStep ¶
type CIExtraJobStep struct {
Name string `yaml:"name,omitempty"`
Uses string `yaml:"uses,omitempty"`
Run string `yaml:"run,omitempty"`
With map[string]string `yaml:"with,omitempty"`
}
CIExtraJobStep defines a single step within a CIExtraJob.
type CILintConfig ¶
type CILintConfig struct {
Golangci bool `yaml:"golangci"` // default true
Buf bool `yaml:"buf"` // default true
BufBreaking bool `yaml:"buf_breaking"` // default true
Frontend bool `yaml:"frontend"` // default true
MigrationSafety bool `yaml:"migration_safety"` // default true
}
CILintConfig controls which linters run in CI.
type CIPermConfig ¶
type CIPermConfig struct {
Contents string `yaml:"contents,omitempty"` // default "read"
}
CIPermConfig controls CI workflow permissions.
type CITestConfig ¶
type CITestConfig struct {
Race bool `yaml:"race"` // default true
Coverage bool `yaml:"coverage"` // default false
}
CITestConfig controls test settings in CI.
type CIVulnConfig ¶
type CIVulnConfig struct {
Go bool `yaml:"go"` // govulncheck, default true
Docker bool `yaml:"docker"` // trivy, default true
NPM bool `yaml:"npm"` // npm audit, default true
}
CIVulnConfig controls vulnerability scanning in CI.
type CRDConfig ¶
type CRDConfig struct {
// Name is the PascalCase CRD type name. e.g. "Workspace".
Name string `yaml:"name"`
// Group is the API group, defaulting to the parent operator's
// Group. Stored explicitly so a single operator can manage CRDs
// from multiple groups.
Group string `yaml:"group,omitempty"`
// Version is the API version. Defaults to the parent operator's
// Version.
Version string `yaml:"version,omitempty"`
// Shape is the reconciler scaffold style. One of
// "state-machine" (phase-driven), "config" (declarative-only,
// no state), "composite" (manages sub-resources). Drives which
// template is rendered for the controller shim.
Shape string `yaml:"shape,omitempty"`
}
CRDConfig represents a single Custom Resource Definition reconciled by an operator. CRDs are scaffolded via `forge add crd <name> --operator <op>`.
type ComponentConfig ¶
type ComponentConfig struct {
Name string `yaml:"name"`
// Kind is THE discriminator: server|worker|cron|operator|binary.
// See the ComponentKind* constants.
Kind string `yaml:"kind"`
Path string `yaml:"path"`
// Ports is a named port map (http/grpc/metrics/proxy/…). Each entry
// unmarshals from EITHER a scalar int (`http: 8080`) or a struct
// (`http: {port: 8080, protocol: tcp, expose: true}`). Consumers
// reference ports BY NAME; a server's primary HTTP port is ports.http.
Ports map[string]PortSpec `yaml:"ports,omitempty"`
Schedule string `yaml:"schedule,omitempty"` // cron expression for kind=cron
ProtoPackages []string `yaml:"proto_packages,omitempty"`
// Group is the API group for kind=operator components. e.g.
// "reliant.dev". Set when scaffolded via `forge add operator`.
Group string `yaml:"group,omitempty"`
// Version is the API version for kind=operator components. e.g.
// "v1alpha1". Set when scaffolded via `forge add operator`.
Version string `yaml:"version,omitempty"`
// CRDs lists the CRDs reconciled by this operator. Each entry is
// a CRD added via `forge add crd <name>` and lives under
// operators/<operator>/<crd-name>_controller.go plus
// api/<version>/<crd-name>_types.go.
CRDs []CRDConfig `yaml:"crds,omitempty"`
}
ComponentConfig represents one buildable/runnable unit of a forge project. Its Kind field selects which scaffold + deploy treatment the component receives; see the ComponentKind* constants.
Host vs cluster placement (was services[].dev_target):
An earlier revision (commit cd25640) put per-component host/cluster placement on this struct. The decision moved to the KCL layer in the feat/kcl-orchestration batch: deployment target is an environment concern (which env runs this on the host, which arch, which runner), not a component-shape concern. Per-env placement is now declared in `deploy/kcl/<env>/main.k`.
func (ComponentConfig) EffectiveKind ¶
func (c ComponentConfig) EffectiveKind() string
EffectiveKind returns the lowercased, trimmed kind, defaulting to "server" for empty input (a component with no kind is a Connect server — the historical `type: go_service` default).
func (ComponentConfig) IsBinary ¶
func (c ComponentConfig) IsBinary() bool
IsBinary reports whether the component is a standalone binary subcommand.
func (ComponentConfig) IsCron ¶
func (c ComponentConfig) IsCron() bool
IsCron reports whether the component is a scheduled cron job.
func (ComponentConfig) IsOperator ¶
func (c ComponentConfig) IsOperator() bool
IsOperator reports whether the component is a controller-runtime operator.
func (ComponentConfig) IsServer ¶
func (c ComponentConfig) IsServer() bool
IsServer reports whether the component is a Connect-RPC server.
func (ComponentConfig) IsWorker ¶
func (c ComponentConfig) IsWorker() bool
IsWorker reports whether the component is an in-process worker.
func (ComponentConfig) PrimaryPort ¶
func (c ComponentConfig) PrimaryPort() int
PrimaryPort returns the component's primary HTTP port number (the ports.http entry), or 0 when no http port is declared. This is the port a server serves its Connect mux on and the one most consumers (dev loop, frontend nav, readiness) want.
type ConfigGuardConfig ¶
type ConfigGuardConfig struct {
// EnforceTypedAccess selects the env-access guardrail strictness:
// "off" | "warn" | "error". Empty resolves to "warn" — use
// [ConfigGuardConfig.EffectiveEnforceTypedAccess], don't read the raw
// string. Unknown values are rejected at load time (validate.go).
EnforceTypedAccess string `yaml:"enforce_typed_access,omitempty"`
// LoaderPackage is the allowlisted package path that may read the
// environment directly — the home of forge's generated config loader.
// Empty resolves to [DefaultLoaderPackage] ("pkg/config"); use
// [ConfigGuardConfig.EffectiveLoaderPackage].
LoaderPackage string `yaml:"loader_package,omitempty"`
}
ConfigGuardConfig is the `config:` section of forge.yaml. It steers humans and LLMs away from reading the environment directly and toward forge's generated, dependency-injected typed config object.
The block is OPTIONAL. When absent, EnforceTypedAccess resolves to "warn" (see EnforceTypedAccessWarn) so existing projects gain the advisory guardrail without a flag-day; `forge new` writes an explicit `enforce_typed_access: error` for greenfield projects.
ADOPTION / re-render: the strictness is projected into the generated .golangci.yml (forbidigo in `linters.enable` for error, settings-only for warn, absent for off). That file is a SCAFFOLD-ONCE, user-owned Tier-2 artifact — `forge generate` never re-renders it, and `forge upgrade` only auto-updates it when the on-disk copy is an unedited forge render (a verifying forge:hash marker). A freshly-scaffolded .golangci.yml carries no marker and is "user-owned from birth", so after changing enforce_typed_access in forge.yaml the user must explicitly re-render: `rm .golangci.yml && forge upgrade` (re-scaffold), or `forge upgrade --force` when they have no local .golangci.yml edits. This is the deliberate Tier-2 contract — forge will not silently stomp a hand-tuned linter config — not a bug. The forbidigo `msg`, the template's warn-mode comment, and `forge lint`'s advisory line all teach this path.
func (ConfigGuardConfig) EffectiveEnforceTypedAccess ¶
func (c ConfigGuardConfig) EffectiveEnforceTypedAccess() string
EffectiveEnforceTypedAccess returns the resolved guardrail strictness, defaulting an absent/empty value to "warn" (advisory). It normalizes case and the "warning" alias. Validation (validate.go) has already rejected any other non-empty value, so this never silently swallows a typo.
func (ConfigGuardConfig) EffectiveLoaderPackage ¶
func (c ConfigGuardConfig) EffectiveLoaderPackage() string
EffectiveLoaderPackage returns the allowlisted loader package path, defaulting an absent/empty value to DefaultLoaderPackage.
func (ConfigGuardConfig) TypedAccessGuardEnabled ¶
func (c ConfigGuardConfig) TypedAccessGuardEnabled() bool
TypedAccessGuardEnabled reports whether the env-access guardrail should be emitted at all (true unless the strictness is "off").
func (ConfigGuardConfig) TypedAccessGuardGates ¶
func (c ConfigGuardConfig) TypedAccessGuardGates() bool
TypedAccessGuardGates reports whether the guardrail FAILS the build (true only in "error" mode; "warn" is advisory, "off" emits nothing).
type ContractsConfig ¶
type ContractsConfig struct {
Strict bool `yaml:"strict"` // require contract.go for all internal packages with exported methods (default: true)
AllowExportedVars bool `yaml:"allow_exported_vars"` // allow exported package vars (default: false)
AllowExportedFuncs bool `yaml:"allow_exported_funcs"` // allow exported funcs without contract (default: true)
Exclude []string `yaml:"exclude"` // packages that opt out
// InterfaceTypes lists additional cross-package interface types (over
// and above the built-in list in internal/generator/contract) that the
// mock generator should treat as mockable — i.e. emit "nil" as the
// fallback zero value instead of the invalid composite literal "T{}".
//
// Entries are matched against the rendered Go type expression of a
// contract method's return value, e.g. "billing.MeterClient" or
// "myproject.SomeProjectLocalInterface". Use this when a contract
// method returns a project-local interface that the mock generator
// would otherwise mistakenly treat as a struct.
InterfaceTypes []string `yaml:"interface_types"`
}
ContractsConfig controls contract enforcement linter behavior.
func (ContractsConfig) IsExcluded ¶
func (c ContractsConfig) IsExcluded(pkgPath string) bool
IsExcluded returns true if the given package path matches any exclude pattern. Delegates to MatchExclude — the shared matcher used by the contract analyzer and the forgeconv lint surface so all three places agree on what "excluded" means. See the doc on MatchExclude for the matching rules and the deliberate exit from the pre-2026-06 inline implementation (empty-pattern handling + slash-normalisation).
func (ContractsConfig) IsStrict ¶
func (c ContractsConfig) IsStrict() bool
IsStrict returns whether strict contract enforcement is enabled (default: true). When the config is zero-value (not explicitly set), strict defaults to true.
type DatabaseConfig ¶
type DatabaseConfig struct {
Driver string `yaml:"driver"` // "postgres" or "none"
MigrationsDir string `yaml:"migrations_dir"`
MigrationSafety MigrationSafetyConfig `yaml:"migration_safety,omitempty"`
}
DatabaseConfig holds database-related settings.
The driver is pinned to postgres: forge generates postgres-only data layers (the runtime ORM, the generate-time schema introspection, and the test harness all target real postgres). The only meaningful choice is postgres vs "none" (no database).
type DeployConcurrency ¶
type DeployConcurrency struct {
Enabled bool `yaml:"enabled"` // default true
CancelInProgress bool `yaml:"cancel_in_progress,omitempty"` // default false
}
DeployConcurrency controls deployment concurrency settings.
type DeployConfig ¶
type DeployConfig struct {
Registry string `yaml:"registry,omitempty"` // DEPRECATED: prefer docker.registry + per-env KCL; tolerated pending the §4 CI rewire
Environments []DeployEnvConfig `yaml:"environments,omitempty"`
Concurrency DeployConcurrency `yaml:"concurrency,omitempty"`
FrontendDeploy string `yaml:"frontend_deploy,omitempty"` // "firebase", "vercel", "none"
MigrationTest bool `yaml:"migration_test,omitempty"` // test migrations before deploy
// TargetArch is the GOARCH the deploy target cluster runs on. When
// unset, forge defaults to amd64 (the predominant k8s host arch).
// Setting this at the project level means Mac/arm64 dev machines
// will cross-compile the Go binary (GOOS=linux GOARCH=<target>
// CGO_ENABLED=0) and pass --platform=linux/<target> to docker
// buildx so the image kubelet pulls actually runs on the node.
//
// Without cross-compile, an arm64-built image deployed onto an
// amd64 node fails at pod startup with the opaque kernel-level
// "exec format error". The CLI's --target-arch flag overrides
// this per-invocation.
TargetArch string `yaml:"target_arch,omitempty"`
}
DeployConfig holds deployment PIPELINE-CONTROL settings (target_arch, migration_test, concurrency, frontend_deploy). It deliberately does NOT own *where* images go or *which* clusters exist:
- the CI provider (github/gitlab/…) lives in `ci.provider` — the dead `deploy.provider` field was removed (see removedSchemaKeys); nothing ever read it (generate_ci.go reads cfg.CI.Provider).
- the image registry lives in `docker.registry` (build-time) and is pinned per-env in KCL — `deploy.registry` is being retired in favour of those two sources (FORGE_SHAPE_REDESIGN §4). It is still read by the CI generator today, so it is TOLERATED (kept, with this note) rather than removed in this pass; the CI-data-flow rewire that lets it go is the deferred half of §4(b).
- the deployable environment set is derived from the on-disk deploy/kcl/<env>/ directories (see buildDeployWorkflowData's ListEnvs fallback); `deploy.environments` is the optional override for auto/protection/url metadata and is likewise tolerated pending the same rewire.
func (*DeployConfig) EffectiveRegistry ¶
func (d *DeployConfig) EffectiveRegistry() string
EffectiveRegistry returns the deploy registry, defaulting to "ghcr".
func (*DeployConfig) EffectiveTargetArch ¶
func (d *DeployConfig) EffectiveTargetArch(override string) string
EffectiveTargetArch returns the deploy-target GOARCH. Order of precedence: explicit override (caller-provided), forge.yaml's deploy.target_arch, then the default "amd64". The "amd64" default reflects the empirical reality that the vast majority of k8s nodes are amd64; arm64 deployments must opt in via forge.yaml or --target-arch.
func (*DeployConfig) IsConcurrencyEnabled ¶
func (d *DeployConfig) IsConcurrencyEnabled() bool
IsConcurrencyEnabled returns true if deploy concurrency is enabled. Zero value is treated as enabled.
type DeployEnvConfig ¶
type DeployEnvConfig struct {
Name string `yaml:"name"` // staging, preprod, prod
Auto bool `yaml:"auto,omitempty"` // auto-deploy
Protection bool `yaml:"protection,omitempty"` // environment protection gates
URL string `yaml:"url,omitempty"` // environment URL
}
DeployEnvConfig defines a deployment environment.
type Deps ¶
type Deps struct{}
Deps is the dependency set for the config Service. Empty today; expanded when file-loading behavior moves into this package.
type DockerConfig ¶
type DockerConfig struct {
Registry string `yaml:"registry"`
// BuildContexts maps a build-context name to anything `docker buildx
// --build-context name=value` accepts:
//
// - A local filesystem path. Relative paths are resolved against the
// project root (the directory holding forge.yaml). The typical
// case is a sibling-checkout local replace directive (e.g. a
// `replace x => ../x` in go.mod where ../x lives outside the
// project's build context).
// - A `docker-image://<image>` ref. Passed through verbatim so a
// Dockerfile `FROM <name>` can be overridden with a specific image
// at build time (local override of a base image during dev,
// pin-by-digest in CI, etc.).
// - Any other scheme buildkit understands (e.g. `oci-layout://`,
// `https://`). Anything containing `://` is passed through
// unchanged.
//
// Each entry becomes a `--build-context name=value` arg to `docker
// build`, letting Dockerfiles consume it via `FROM <name>` or
// `COPY --from=<name>`. Empty when not set; existing projects with no
// contexts see no change in build behaviour or output.
BuildContexts map[string]string `yaml:"build_contexts,omitempty"`
}
DockerConfig holds Docker build configuration for the PROJECT image (the root `Dockerfile` built by `forge build`). Per-service DockerBuild services declared in KCL carry their OWN registry + build_contexts on the KCL DockerBuild block; these are the project-image defaults / fallback.
forge is FULLY base-image-AGNOSTIC: it does NOT discover, mirror, pin, or inject base images, and offers no mirror/pull-through setting. A Dockerfile's `FROM` lines are the COMPLETE source of truth — pin with `FROM …@sha256:…`, and route through a pull-through mirror by writing the mirror host into the `FROM` ref directly (e.g. `FROM us-docker.pkg.dev/<p>/dockerhub/alpine:3.21`). forge never rewrites a FROM.
type DocsConfig ¶
type DocsConfig struct {
Enabled *bool `yaml:"enabled,omitempty"` // nil = true (enabled by default)
OutputDir string `yaml:"output_dir,omitempty"` // default: "docs/generated"
Format string `yaml:"format,omitempty"` // "markdown" (default) or "hugo"
Generators []string `yaml:"generators,omitempty"` // e.g. ["api", "architecture", "config", "contracts"]
CustomTemplatesDir string `yaml:"custom_templates_dir,omitempty"` // user template overrides
}
DocsConfig holds documentation generation settings.
func (DocsConfig) EffectiveFormat ¶
func (d DocsConfig) EffectiveFormat() string
EffectiveFormat returns the output format, defaulting to "markdown".
func (DocsConfig) EffectiveOutputDir ¶
func (d DocsConfig) EffectiveOutputDir() string
EffectiveOutputDir returns the output directory, defaulting to "docs/generated".
func (DocsConfig) IsEnabled ¶
func (d DocsConfig) IsEnabled() bool
IsEnabled returns whether docs generation is enabled (default: true).
type ExperimentalConfig ¶
type ExperimentalConfig struct {
Ingress bool `yaml:"ingress,omitempty"`
ExternalBuilds bool `yaml:"external_builds,omitempty"`
Operators bool `yaml:"operators,omitempty"`
StrictWiring bool `yaml:"strict_wiring,omitempty"`
}
ExperimentalConfig gates features that are not yet promised. Fields are plain bool (not *bool) — the zero value IS the default, and the default IS off. Loud-warning policy on startup when any field is true.
What lives here today:
- Ingress: Gateway API codegen + cert-manager + Envoy Gateway wiring. Provider matrix is fragile and not yet proven across real cloud providers.
- ExternalBuilds: RETIRED gate (kept as an accepted, inert key for back-compat). `Service.build_cmd` is the build-side mirror of `External.deploy_cmd`; since `forge deploy` of an External target never required an opt-in, gating `forge build` of the same target behind this flag left the build/deploy pair with mismatched maturity gates (fr-da9a6614fb). The build path no longer consults this flag — build_cmd just builds. Setting it true is harmless (and still accepted so existing forge.yaml files don't trip the unknown-key check); a future major can drop the field.
- Operators: controller-runtime managers + CRD codegen. Niche, under-exercised, the API may need to change as we learn what real operator authors want.
- StrictWiring: diagnostics fail-fast — any registered diagnostic terminates the process after Bootstrap. Implies Diagnostics: true. Stays experimental because the diagnostics catalogue itself is still settling.
func (ExperimentalConfig) IsZero ¶
func (e ExperimentalConfig) IsZero() bool
IsZero reports whether the experimental block carries nothing explicit.
type FeatureName ¶
type FeatureName = string
FeatureName is the canonical feature key. Stays a string alias so the constants below are usable directly anywhere the feature name shows up as a config key, a `--disable` flag value, or a `forge audit` field.
const ( FeatureORM FeatureName = "orm" FeatureCodegen FeatureName = "codegen" FeatureMigrations FeatureName = "migrations" FeatureCI FeatureName = "ci" FeatureBuild FeatureName = "build" FeatureContracts FeatureName = "contracts" FeatureDocs FeatureName = "docs" FeatureFrontend FeatureName = "frontend" FeatureObservability FeatureName = "observability" FeatureHotReload FeatureName = "hot_reload" FeaturePacks FeatureName = "packs" FeatureDeploy FeatureName = "deploy" // Experimental feature names — opt-in under // `features.experimental.<name>: true`. Default OFF. FeatureIngress FeatureName = "ingress" FeatureExternalBuilds FeatureName = "external_builds" FeatureOperators FeatureName = "operators" FeatureStrictWiring FeatureName = "strict_wiring" )
Feature name constants. These are the wire format — both YAML field names under `features:` (top-level) or `features.experimental:` (nested) and the strings emitted by `forge audit --json | jq '.features'`. Kept exported so external tooling can match against them without re-encoding the spelling. The Experimental* constants live under the nested block in YAML but flatten back to a single per-name keyspace at the audit-JSON layer.
type FeaturesConfig ¶
type FeaturesConfig struct {
ORM *bool `yaml:"orm,omitempty"` // ORM projection of db/migrations (internal/db/*_orm.go)
Codegen *bool `yaml:"codegen,omitempty"` // service/handler codegen from protos
Migrations *bool `yaml:"migrations,omitempty"` // auto-generate SQL migrations
CI *bool `yaml:"ci,omitempty"` // generate CI/CD workflows
Build *bool `yaml:"build,omitempty"` // `forge build` Go binary + docker image pipeline
Contracts *bool `yaml:"contracts,omitempty"` // contract linter enforcement
Docs *bool `yaml:"docs,omitempty"` // documentation generation
Frontend *bool `yaml:"frontend,omitempty"` // frontend scaffolding + codegen
Observability *bool `yaml:"observability,omitempty"` // alloy, grafana dashboards, otel wiring
HotReload *bool `yaml:"hot_reload,omitempty"` // air config generation
Packs *bool `yaml:"packs,omitempty"` // forge packs (install/list/info), pack-generate hooks
Deploy *bool `yaml:"deploy,omitempty"` // deploy pipeline: KCL render → kubectl apply, per-env deploy config codegen
// Diagnostics enables runtime emission of pkg/diagnostics records at
// Bootstrap time — slog warn lines for every unwired scaffold the
// codegen pipeline registered (Tier-1 stubs, nil-wired Deps fields).
// Default OFF: existing projects don't suddenly start logging warns on
// regen. Opt-in by setting `features.diagnostics: true` in forge.yaml.
Diagnostics *bool `yaml:"diagnostics,omitempty"`
// Experimental gates surface that hasn't been battle-tested across
// real projects + cloud providers. Everything inside is default-OFF
// (opt-in), every gated CLI invocation prints a one-line warning the
// first time per process, and the schema is allowed to break between
// forge versions without a deprecation cycle. Graduates to the
// top-level FeaturesConfig (with the usual opt-out default-ON
// semantics) when the feature has shipped through enough real
// deployments to earn a backwards-compatibility promise.
Experimental ExperimentalConfig `yaml:"experimental,omitempty"`
// contains filtered or unexported fields
}
FeaturesConfig controls which forge features are active. The `features:` block in forge.yaml gates major subsystems (deploy, build, frontend, packs, ci, docs, observability, ...).
THE BLOCK IS AN OVERRIDE SURFACE, NOT REQUIRED CONFIGURATION. Scaffolded forge.yaml files do not contain it. All fields are *bool so the loader can distinguish three states:
- absent (nil): the value is DERIVED from the project shape at load time — kind (service/cli/library), whether a database driver is configured, whether the frontends list is non-empty. See DeriveFeatureDefaults in derive.go for the exact rule per feature. For the canonical shape (kind=service, postgres, frontends present) every derived value is "enabled", matching the historical all-enabled default for projects without a features: block.
- explicitly true / explicitly false: taken literally; derivation never overrides an explicit value.
A FeaturesConfig that was not produced by the config loader (zero value in tests, hand-constructed) has no derivation context and resolves nil → enabled, preserving the historical zero-value semantics.
Effect on the CLI surface and codegen pipeline:
- Direct invocations of a disabled subsystem's cobra command return a clear `feature '<name>' is disabled in forge.yaml. Set features.<name>: true to enable.` error.
- Implicit invocations from orchestrators (e.g. `forge up` driving the build/deploy/frontend phases) log a skip line and continue — letting `forge up` succeed on whatever subsystems ARE enabled.
- Codegen pipeline steps gated on a feature skip silently when off, mirroring the existing gate function shape under internal/cli/generate_pipeline.go.
New project scaffolding (`forge new --kind`) sets defaults per kind:
- service (default): all features enabled (preserves today's behavior).
- cli: build/ci/docs enabled; everything else disabled.
- library: ci/docs enabled; everything else disabled.
func (FeaturesConfig) BuildEnabled ¶
func (f FeaturesConfig) BuildEnabled() bool
BuildEnabled reports whether `forge build` is enabled (default: on). Direct `forge build` invocations error when off; orchestrators like `forge up` log a skip line and continue.
func (FeaturesConfig) CIEnabled ¶
func (f FeaturesConfig) CIEnabled() bool
CIEnabled reports whether the CI feature is on (default: on).
func (FeaturesConfig) CodegenEnabled ¶
func (f FeaturesConfig) CodegenEnabled() bool
CodegenEnabled reports whether codegen is on (default: on).
func (FeaturesConfig) ContractsEnabled ¶
func (f FeaturesConfig) ContractsEnabled() bool
ContractsEnabled reports whether contract enforcement is on (default: on).
func (FeaturesConfig) DeployEnabled ¶
func (f FeaturesConfig) DeployEnabled() bool
DeployEnabled reports whether the deploy feature is on. Stable flag: absent derives from project shape (deploy ⇔ kind == service — see DeriveFeatureDefaults), explicit `features.deploy: true|false` wins. Service scaffolds ship a deploy/kcl tree, so deploy is ON for the canonical service shape; cli/library kinds derive OFF.
func (FeaturesConfig) DiagnosticsEnabled ¶
func (f FeaturesConfig) DiagnosticsEnabled() bool
DiagnosticsEnabled reports whether the pkg/diagnostics runtime emit is wired by bootstrap (default: OFF). When OFF, codegen still emits pkg/app/diagnostics_gen.go (so `forge audit` can roll the data up from the file), but Bootstrap does not call diagnostics.Default.Boot — no slog lines, no strict-mode exit.
Strict-wiring implies Diagnostics: enabling strict without diagnostics is a no-op, so we treat StrictWiringEnabled as forcing diagnostics on.
func (FeaturesConfig) DocsEnabled ¶
func (f FeaturesConfig) DocsEnabled() bool
DocsEnabled reports whether the docs feature is on (default: on).
func (FeaturesConfig) EffectiveFeatures ¶
func (f FeaturesConfig) EffectiveFeatures() map[string]bool
EffectiveFeatures projects the resolved enabled/disabled state of every feature into a stable name→bool map. Used by `forge audit` to surface the project's feature configuration at a glance, and by tests to assert per-kind scaffold defaults. The map is keyed by Feature* constants and is safe to JSON-marshal directly. Experimental features are flattened in alongside the stable set under their own keys — audit consumers can branch on IsExperimentalFeature(name) when they need to distinguish the two tiers.
func (FeaturesConfig) EnabledExperimentalFeatures ¶
func (f FeaturesConfig) EnabledExperimentalFeatures() []FeatureName
EnabledExperimentalFeatures returns the names of experimental features currently turned on, in ExperimentalFeatureNames order. Used by the startup warning and `forge features`.
func (FeaturesConfig) ExternalBuildsEnabled ¶
func (f FeaturesConfig) ExternalBuildsEnabled() bool
ExternalBuildsEnabled reports the raw value of the RETIRED `features.experimental.external_builds` flag. It no longer gates the build path: `build_cmd` is the build-side mirror of `External.deploy_cmd` (which needs no opt-in), so `forge build` of a build_cmd service runs unconditionally (fr-da9a6614fb). The accessor is retained for the startup warning / `forge audit` surface and any consumer still keyed off the flag; the build dispatcher in internal/cli/build.go no longer calls it.
func (FeaturesConfig) FrontendEnabled ¶
func (f FeaturesConfig) FrontendEnabled() bool
FrontendEnabled reports whether the frontend feature is on (default: on).
func (FeaturesConfig) HotReloadEnabled ¶
func (f FeaturesConfig) HotReloadEnabled() bool
HotReloadEnabled reports whether the hot-reload feature is on (default: on).
func (FeaturesConfig) IngressEnabled ¶
func (f FeaturesConfig) IngressEnabled() bool
IngressEnabled reports whether Gateway API ingress is wired (default: OFF — opt-in under `features.experimental.ingress: true`). When off, forge skips ingress codegen, `forge cluster up` skips the Envoy Gateway + GatewayClass install, `forge cluster urls` returns nothing, and the audit ingress category is suppressed.
func (FeaturesConfig) IsZero ¶
func (f FeaturesConfig) IsZero() bool
IsZero reports whether the features block carries no explicit user choices — every stable flag nil and no experimental opt-ins. Implements yaml.IsZeroer so `features,omitempty` omits the block entirely from a marshalled forge.yaml when there is nothing explicit to record (the derived field is resolution context, not content).
func (FeaturesConfig) MigrationsEnabled ¶
func (f FeaturesConfig) MigrationsEnabled() bool
MigrationsEnabled reports whether the migrations feature is on (default: on).
func (FeaturesConfig) ORMEnabled ¶
func (f FeaturesConfig) ORMEnabled() bool
ORMEnabled reports whether the ORM feature is on (default: on).
func (FeaturesConfig) ObservabilityEnabled ¶
func (f FeaturesConfig) ObservabilityEnabled() bool
ObservabilityEnabled reports whether the observability feature is on (default: on).
func (FeaturesConfig) OperatorsEnabled ¶
func (f FeaturesConfig) OperatorsEnabled() bool
OperatorsEnabled reports whether controller-runtime operator codegen + CRD manifest generation is wired (default: OFF — opt-in under `features.experimental.operators: true`). When off, the operator binary codegen + CRD scaffold steps skip silently and `forge add operator` errors.
func (FeaturesConfig) PacksEnabled ¶
func (f FeaturesConfig) PacksEnabled() bool
PacksEnabled reports whether the pack subsystem is enabled (default: on). Disables `forge pack list/info/install/remove` and skips the pack generate-hooks step in the codegen pipeline.
func (FeaturesConfig) StrictWiringEnabled ¶
func (f FeaturesConfig) StrictWiringEnabled() bool
StrictWiringEnabled reports whether the diagnostics strict-mode exit is wired by bootstrap (default: OFF — opt-in under `features.experimental.strict_wiring: true`). Used in tandem with DiagnosticsEnabled — strict-mode wraps the LogEmitter with StrictEmitter so any registered diagnostic terminates the process after the summary line.
type FrontendConfig ¶
type FrontendConfig struct {
Name string `yaml:"name"`
Type string `yaml:"type"` // "nextjs", "react-native", "vite-spa"
Kind string `yaml:"kind,omitempty"` // "web" (default/Next.js), "mobile" (React Native), "vite-spa" (Vite + React + tanstack-router)
Path string `yaml:"path"`
Port int `yaml:"port"`
// Output selects the Next.js build/runtime shape for this frontend.
// Only meaningful when Type == "nextjs"; ignored for react-native and
// vite-spa (those have their own production shapes).
//
// Valid values:
// - "standalone" (default): production builds emit a self-contained
// Node server at `.next-prod/standalone/server.js`. This is the shape
// the shipped Dockerfile copies into its runner image, and the
// only default that supports the dynamic `[id]` CRUD detail/edit
// routes forge generates for every entity.
// - "static": production builds emit a static export
// (`output: "export"` gated on NODE_ENV=production) — pure HTML +
// JS + CSS the user can drop on a CDN or object store. The dev
// server stays unchanged (`next dev`). EXPLICIT OPT-IN ONLY:
// `output: "export"` requires generateStaticParams() on every
// dynamic route segment, and the generated CRUD detail/edit
// pages (`/<slug>/[id]`) are dynamic client routes whose ids
// only exist at runtime — `npm run build` fails on any project
// with a CRUD entity unless those pages are removed or given
// hand-written static params.
// - "server": full Next.js dev AND prod (no `output:` set). Use
// when you want `next start` semantics in prod for custom edge /
// ISR workflows.
//
// Defaults to "standalone" when empty. Pre-existing projects keep
// their checked-in `next.config.ts`; the field doesn't
// retroactively rewrite it.
Output string `yaml:"output,omitempty"`
// BasePath is the URL path prefix this frontend is mounted under
// when it is NOT served from the host root — e.g. "/admin" for an
// admin UI that a reverse proxy blends with another app on the same
// host. Only meaningful when Type == "nextjs".
//
// Shape rules (validated by `forge validate` / LoadStrict):
// - must start with "/" ("/admin", "/internal/admin")
// - must not end with "/" ("/admin/" is rejected)
// - must not be bare "/" (root mount == leave it empty)
// - segments are limited to [A-Za-z0-9._-]
//
// What it drives:
// - next.config.ts: rendered as the build-time default for both
// `basePath` and `assetPrefix` (same value — assetPrefix is what
// keeps RSC/chunk URLs under the prefix so hydration works).
// - src/lib/basepath_gen.ts (Tier-1, regenerated every `forge
// generate`): exports BASE_PATH + joinBasePath() for URLs Next.js
// can't rewrite (window.location-built redirects, share links).
//
// The single runtime override is the NEXT_PUBLIC_BASE_PATH env var —
// the ONLY base-path variable forge ever reads or writes. Empty
// (the default) means the frontend is served from the host root.
BasePath string `yaml:"base_path,omitempty"`
}
FrontendConfig defines a frontend application (e.g. Next.js, React Native).
type FrontendLintConfig ¶
type FrontendLintConfig struct {
CSSHealth bool `yaml:"css_health,omitempty"` // enable stylelint-backed CSS health checks
NoImportant string `yaml:"no_important,omitempty"` // error, warn, off
NoInlineStyles string `yaml:"no_inline_styles,omitempty"` // error, warn, off
}
FrontendLintConfig configures the frontend slice of `forge lint`: whether the stylelint-backed CSS health checks run, and which severity ("error"/"warn"/"off") the `no-important` and `no-inline-styles` rules use.
func (FrontendLintConfig) EffectiveNoImportant ¶
func (c FrontendLintConfig) EffectiveNoImportant() string
EffectiveNoImportant returns the configured severity for the no-important rule, falling back to "warn" when unset/invalid.
func (FrontendLintConfig) EffectiveNoInlineStyles ¶
func (c FrontendLintConfig) EffectiveNoInlineStyles() string
EffectiveNoInlineStyles returns the configured severity for the no-inline-styles rule, falling back to "warn" when unset/invalid.
type FrontendProjectConfig ¶
type FrontendProjectConfig struct {
// Workspaces opts the project into the pnpm-workspaces layout. When
// true:
//
// - A `pnpm-workspace.yaml` is emitted at the project root listing
// `packages/*` and `frontends/*` as members.
// - `packages/api/` contains the buf-generated Connect TS clients
// and proto types as a single workspace package (`@<scope>/api`).
// - `packages/hooks/` contains the React Query wrappers
// (`use-api-query.ts` / `use-api-mutation.ts`) and the generated
// per-service hooks (`packages/hooks/src/generated/`), exposed as
// `@<scope>/hooks`.
// - Each frontend `package.json` declares the workspace deps via
// `"@<scope>/api": "workspace:*"` and imports them by package name
// rather than by relative path.
//
// When false (the default), forge emits the historic per-frontend
// layout — `frontends/<name>/src/gen/` for buf output, hooks
// templated into each `frontends/<name>/src/hooks/` — byte-identical
// to projects scaffolded before this flag landed.
Workspaces bool `yaml:"workspaces,omitempty"`
}
FrontendProjectConfig holds project-level frontend settings — fields that apply to the whole project rather than a single frontend entry. Distinct from FrontendConfig (per-frontend) and from the cli loader's "did the user pass --frontend" notion. Today the only field is Workspaces, the opt-in pnpm workspaces toggle.
The flag is intentionally project-level (not per-frontend) because the workspace layout reshapes the whole project tree (packages/api, packages/hooks, pnpm-workspace.yaml at root), not just one frontend.
type JWTConfig ¶
type JWTConfig struct {
Issuer string `yaml:"issuer,omitempty"`
Audience string `yaml:"audience,omitempty"`
JWKSURL string `yaml:"jwks_url,omitempty"`
SigningMethod string `yaml:"signing_method,omitempty"` // HS256, RS256, ES256
}
JWTConfig holds JWT-specific authentication settings.
func (JWTConfig) EffectiveSigningMethod ¶
EffectiveSigningMethod returns the JWT signing method, defaulting to "RS256".
type K8sConfig ¶
type K8sConfig struct {
KCLDir string `yaml:"kcl_dir"`
}
K8sConfig holds Kubernetes configuration.
type LintConfig ¶
type LintConfig struct {
Contract bool `yaml:"contract"`
Frontend FrontendLintConfig `yaml:"frontend,omitempty"`
// HandlerFileMaxLOC is the per-file LOC threshold above which the
// forgeconv-handler-file-size analyzer warns. Counts non-blank, non-
// comment Go source lines under handlers/<svc>/*.go. A value of 0 (or
// the field unset) is treated as the built-in default — see
// [LintConfig.EffectiveHandlerFileMaxLOC] for the canonical value.
HandlerFileMaxLOC int `yaml:"handler_file_max_loc,omitempty"`
}
LintConfig holds lint-related settings.
func (LintConfig) EffectiveHandlerFileMaxLOC ¶
func (c LintConfig) EffectiveHandlerFileMaxLOC() int
EffectiveHandlerFileMaxLOC returns the LOC threshold for the handler-file-size analyzer, defaulting to DefaultHandlerFileMaxLOC when the config value is zero or unset.
type MigrationSafetyConfig ¶
type MigrationSafetyConfig struct {
Enabled *bool `yaml:"enabled,omitempty"` // nil = enabled
UnsafeAddColumn string `yaml:"unsafe_add_column,omitempty"` // error, warn, off
DestructiveChange string `yaml:"destructive_change,omitempty"` // error, warn, off
VolatileDefault string `yaml:"volatile_default,omitempty"` // warn, error, off
AllowedDestructive []string `yaml:"allowed_destructive,omitempty"` // file globs that may contain destructive changes
}
MigrationSafetyConfig controls migrationlint's three severity dials (unsafe add-column, destructive change, volatile default) and its list of allowlisted destructive migrations.
func (MigrationSafetyConfig) EffectiveDestructiveChange ¶
func (c MigrationSafetyConfig) EffectiveDestructiveChange() string
EffectiveDestructiveChange returns the configured severity for the destructive-change rule, falling back to "error" when unset/invalid.
func (MigrationSafetyConfig) EffectiveUnsafeAddColumn ¶
func (c MigrationSafetyConfig) EffectiveUnsafeAddColumn() string
EffectiveUnsafeAddColumn returns the configured severity for the unsafe-add-column rule, falling back to "error" when unset/invalid.
func (MigrationSafetyConfig) EffectiveVolatileDefault ¶
func (c MigrationSafetyConfig) EffectiveVolatileDefault() string
EffectiveVolatileDefault returns the configured severity for the volatile-default rule, falling back to "warn" when unset/invalid.
func (MigrationSafetyConfig) IsEnabled ¶
func (c MigrationSafetyConfig) IsEnabled() bool
IsEnabled reports whether migration safety linting is on. Nil Enabled means "on by default" so opt-in is implicit.
type MockService ¶
type MockService struct {
contractkit.Recorder
EffectiveKindFunc func(string) string
}
MockService is a test mock for the Service interface.
The embedded contractkit.Recorder records every call so tests can assert call counts and captured arguments. Set XxxFunc fields to override per-method behaviour; unset methods return the canonical "MockService.<Method>Func not set" error.
func (*MockService) EffectiveKind ¶
func (m *MockService) EffectiveKind(kind string) string
type MultiTenantConfig ¶
type MultiTenantConfig struct {
Enabled bool `yaml:"enabled"`
ClaimField string `yaml:"claim_field,omitempty"` // JWT claim to extract tenant ID from, default: "org_id"
ColumnName string `yaml:"column_name,omitempty"` // DB column name for tenant scoping, default: "org_id"
}
MultiTenantConfig holds multi-tenancy settings for row-level tenant isolation.
func (MultiTenantConfig) EffectiveClaimField ¶
func (m MultiTenantConfig) EffectiveClaimField() string
EffectiveClaimField returns the claim field, defaulting to "org_id".
func (MultiTenantConfig) EffectiveColumnName ¶
func (m MultiTenantConfig) EffectiveColumnName() string
EffectiveColumnName returns the column name, defaulting to "org_id".
type PackOverride ¶
type PackOverride struct {
// SkipMigrations skips rendering the pack's `migrations:` block at
// install time. Useful when the project's own migrations supersede
// the pack's (typical during forge migrations of an existing repo
// where the schema is already in place).
SkipMigrations bool `yaml:"skip_migrations,omitempty"`
}
PackOverride is a project-level override block for an installed pack, keyed by pack name under `pack_overrides:` in forge.yaml. It lets the project decline pack-shipped artifacts when its own code already supersedes them — e.g. an audit-log pack ships migrations the project has already authored under different names.
type PackageConfig ¶
type PackageConfig struct {
Name string `yaml:"name"`
Kind string `yaml:"kind,omitempty"` // "" (default/generic), "client", "eventbus"
// Type captures the hexagonal-architecture role chosen at scaffold
// time: "service" (default — bootstrap-wired contract package),
// "adapter" (outbound boundary, marked `// forge:adapter`),
// "interactor" (use-case orchestrator, marked `// forge:interactor`).
// Empty (omitted) is treated as "service" for backward compatibility
// with packages scaffolded before the --type flag landed.
Type string `yaml:"type,omitempty"`
}
PackageConfig represents an internal package with a Go interface contract.
type PlanEntity ¶
type PlanEntity struct {
Name string `yaml:"name" json:"name"` // PascalCase message name, e.g. "Project"
TableName string `yaml:"table_name,omitempty" json:"table_name,omitempty"` // override; defaults to pluralized snake_case
SoftDelete bool `yaml:"soft_delete,omitempty" json:"soft_delete,omitempty"`
Timestamps bool `yaml:"timestamps,omitempty" json:"timestamps,omitempty"`
Fields []PlanEntityField `yaml:"fields" json:"fields"`
}
PlanEntity describes a database entity to scaffold.
type PlanEntityField ¶
type PlanEntityField struct {
Name string `yaml:"name" json:"name"` // snake_case proto field name
Type string `yaml:"type" json:"type"` // "string", "int64", "bool", "google.protobuf.Timestamp"
PrimaryKey bool `yaml:"primary_key,omitempty" json:"primary_key,omitempty"`
NotNull bool `yaml:"not_null,omitempty" json:"not_null,omitempty"`
Unique bool `yaml:"unique,omitempty" json:"unique,omitempty"`
Default string `yaml:"default,omitempty" json:"default,omitempty"`
References string `yaml:"references,omitempty" json:"references,omitempty"` // "users.id"
TenantKey bool `yaml:"tenant_key,omitempty" json:"tenant_key,omitempty"`
// Generated marks a GENERATED ALWAYS AS (...) STORED column: the DB
// computes it, so the ORM emits ,scanonly (never written on
// INSERT/UPDATE).
Generated bool `yaml:"generated,omitempty" json:"generated,omitempty"`
}
PlanEntityField describes a field on an entity.
type PlanField ¶
type PlanField struct {
Name string `yaml:"name" json:"name"`
Type string `yaml:"type" json:"type"`
}
PlanField describes a field in a proto message.
type PlanFile ¶
type PlanFile struct {
ProjectName string `yaml:"project_name"`
GoModule string `yaml:"go_module"`
GoVersion string `yaml:"go_version,omitempty"`
License string `yaml:"license,omitempty"`
MockData bool `yaml:"mock_data,omitempty"`
Services []PlanService `yaml:"services,omitempty"`
Packages []PlanPackage `yaml:"packages,omitempty"`
Frontends []PlanFrontend `yaml:"frontends,omitempty"`
Entities []PlanEntity `yaml:"entities,omitempty" json:"entities,omitempty"`
}
PlanFile represents a forge plan for batch scaffolding.
type PlanFrontend ¶
type PlanFrontend struct {
Name string `yaml:"name"`
Kind string `yaml:"kind,omitempty"` // "mobile" for React Native; empty/default = Next.js web
}
PlanFrontend describes a frontend to scaffold.
type PlanPackage ¶
type PlanPackage struct {
Name string `yaml:"name"`
Kind string `yaml:"kind,omitempty"` // "eventbus", "client", or empty
Description string `yaml:"description,omitempty"`
}
PlanPackage describes an internal package to scaffold.
type PlanRPC ¶
type PlanRPC struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Request []PlanField `yaml:"request,omitempty" json:"request,omitempty"`
Response []PlanField `yaml:"response,omitempty" json:"response,omitempty"`
}
PlanRPC describes an RPC to scaffold in a service proto.
type PlanService ¶
type PlanService struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
RPCs []PlanRPC `yaml:"rpcs,omitempty" json:"rpcs,omitempty"`
}
PlanService describes a service to scaffold.
type PortSpec ¶
type PortSpec struct {
Port int `yaml:"port"`
Protocol string `yaml:"protocol,omitempty"` // tcp (default), udp
Expose bool `yaml:"expose,omitempty"` // surface on the k8s Service / Dockerfile EXPOSE
}
PortSpec describes one named port. It unmarshals from EITHER a YAML scalar int — `http: 8080` (the common case, Protocol/Expose default) — OR a struct — `http: {port: 8080, protocol: tcp, expose: true}`. See UnmarshalYAML.
func (PortSpec) MarshalYAML ¶
MarshalYAML emits the terse scalar form (`http: 8080`) when protocol and expose are at their defaults, and the full mapping otherwise. This keeps a freshly-scaffolded forge.yaml's ports: block as terse as the single-port common case allows, while round-tripping the struct form for ports that set protocol/expose.
func (*PortSpec) UnmarshalYAML ¶
UnmarshalYAML accepts a bare scalar int (`http: 8080`) or a full mapping (`http: {port: 8080, protocol: tcp, expose: true}`). The scalar form is sugar for `{port: N}` with default protocol/expose so the common single-port case stays terse.
type ProjectConfig ¶
type ProjectConfig struct {
Name string `yaml:"name"`
ModulePath string `yaml:"module_path"`
// Kind is the project shape (service|cli|library). It is NO LONGER a
// forge.yaml field: as of the ProjectStore Phase-2 data move it DERIVES
// from the components (see DeriveProjectKind) — a project with a
// server-shaped component is a service, a binary-only project is a cli,
// an empty one is a library. The loader sets this field after reading
// components.json; the yaml tag is "-" so a stale `kind:` in forge.yaml
// is rejected with a migration hint. Every consumer that reads cfg.Kind
// is unchanged.
Kind string `yaml:"-"`
Binary string `yaml:"binary,omitempty"` // "per-service" (default), "shared" — one Go binary, cobra subcommand per service
Version string `yaml:"version,omitempty"`
// ForgeVersion records the forge binary version that this project's
// generated artifacts were last produced against. It is set at
// `forge new` time, bumped after a successful `forge upgrade`, and
// consulted by `forge generate` to warn when the forge binary on
// PATH has drifted from the version pinned by the project. Empty
// (legacy) projects are treated as "0.0.0".
ForgeVersion string `yaml:"forge_version,omitempty"`
// HotReload toggles the air-based hot-reload dev loop for `forge run`.
// *bool so "absent" (nil → derived: on for service kind, off otherwise)
// is distinguishable from an explicit `hot_reload: false` opt-out. Use
// EffectiveHotReload; don't read the pointer directly.
HotReload *bool `yaml:"hot_reload,omitempty"`
// Components is the unified list of everything this project builds and
// runs: Connect-RPC servers, in-process workers, scheduled crons,
// controller-runtime operators, and standalone binaries. The Kind field
// on each entry is THE discriminator (server|worker|cron|operator|binary).
//
// Components are DERIVED from the project's real sources (proto
// descriptor, the pkg/app service registry, the deploy/kcl tree,
// internal/handlers, and cmd/ binaries) — NOT authored in forge.yaml.
// The yaml tag is therefore "-": forge.yaml can no longer carry a
// `components:` block (a stale one is rejected with a migration hint —
// see removedSchemaKeys). The loader introspects the real sources and
// populates this field, so every consumer that reads cfg.Components (and
// the ProjectStore wrapping it) is unchanged.
Components []ComponentConfig `yaml:"-"`
Packages []PackageConfig `yaml:"packages,omitempty"`
Frontends []FrontendConfig `yaml:"frontends,omitempty"`
// Frontend holds project-level frontend settings — distinct from
// the per-frontend `Frontends []FrontendConfig` slice above. Today
// it only carries the opt-in `workspaces:` flag that turns on the
// pnpm-workspace + packages/api + packages/hooks layout so multiple
// frontends (web + mobile) can share generated Connect clients and
// React Query hook wrappers. When the flag is false (the default)
// forge keeps the historic per-frontend layout exactly as before.
Frontend FrontendProjectConfig `yaml:"frontend,omitempty"`
// The section blocks below are all omitempty: a freshly scaffolded
// forge.yaml leaves them absent and the loader fills shape-derived
// defaults (see ApplyDerivedDefaults in derive.go). A present block
// is taken literally — write the block (or a single key) to override.
Database DatabaseConfig `yaml:"database,omitempty"`
CI CIConfig `yaml:"ci,omitempty"`
// NOTE: there is intentionally NO `build:` field. Build is declared
// per-service, per-env in KCL (the polymorphic Build union —
// GoBuild | DockerBuild | ShellBuild on each forge.Service); forge.yaml
// carries zero build config. A `build:` key in forge.yaml is REJECTED
// as an unknown key by the strict loader (walkUnknownKeys) rather than
// silently ignored — version stamping moves to a KCL GoBuild.ldflags
// `-X` entry.
Deploy DeployConfig `yaml:"deploy,omitempty"`
Docker DockerConfig `yaml:"docker,omitempty"`
K8s K8sConfig `yaml:"k8s,omitempty"`
Lint LintConfig `yaml:"lint,omitempty"`
Contracts ContractsConfig `yaml:"contracts,omitempty"`
// Config steers humans and LLMs away from reading the environment
// directly (os.Getenv / os.LookupEnv / os.Environ) and toward forge's
// generated, dependency-injected typed config object. See
// [ConfigGuardConfig] for the enforce_typed_access / loader_package
// semantics and the absent-key default (warn).
Config ConfigGuardConfig `yaml:"config,omitempty"`
Auth AuthConfig `yaml:"auth,omitempty"`
Docs DocsConfig `yaml:"docs,omitempty"`
Features FeaturesConfig `yaml:"features,omitempty"`
Stack StackConfig `yaml:"stack,omitempty"`
// API toggles project-level API protocol skins layered on top of the
// Connect mux. Default zero-value leaves both REST and OpenAPI off so
// existing projects regenerate identically. See [APIConfig] for the
// per-field semantics.
API APIConfig `yaml:"api,omitempty"`
// Packs lists the installed forge packs by name. Two KINDS of pack are
// recorded here, distinguished NOT by a forge.yaml flag but by the
// pack's own manifest (see internal/packs Pack.Generate):
//
// - ONGOING packs declare a `generate:` block (generate hooks). They
// are re-run on every `forge generate` and stay coupled to the
// project's codegen — listing them here is load-bearing: the
// pipeline replays their hooks. Removing the entry stops the hooks.
// - INSTALL-ONCE starters declare only `files:` (no `generate:`). They
// copy their scaffold in once and the project OWNS the result after;
// the forge.yaml entry is a provenance record, not a re-run trigger.
//
// The distinction is derivable from the manifest at install/generate
// time (a pack with a non-empty Generate is ongoing); it is documented
// here rather than encoded as a per-entry field so existing forge.yaml
// files keep their plain string list. See the `packs` vs `starters`
// skills for the user-facing split.
Packs []string `yaml:"packs,omitempty"`
PackOverrides map[string]PackOverride `yaml:"pack_overrides,omitempty"`
// Smoke declares APP-FLOW health checks that `forge smoke <env>` runs in
// addition to its built-in ingress route / dev-port probes. A route probe
// only proves listeners are up; a flow check proves the APP actually works
// (an end-to-end invariant only the app can express). See [SmokeConfig].
Smoke SmokeConfig `yaml:"smoke,omitempty"`
}
ProjectConfig represents the forge.yaml file. Fields align with proto/forge/project/v1/project.proto.
func LoadProject ¶
func LoadProject(forgeYAML []byte, path string) (*ProjectConfig, error)
LoadProject is the canonical project loader: it parses the global forge.yaml bytes, then runs the full LoadStrict validation + kind derivation + shape-derived defaults. There is no components.json manifest — the component inventory and project kind derive from the project's real sources (proto descriptor, service registry, KCL deploy tree, cmd/ binaries) read relative to `path` (see deriveProjectKindFromSources). When `path` points at no on-disk project (byte-only test loads), the kind falls back to library.
This is the entry point both the CLI loader and the generator's ReadProjectConfig route through, so the load rules live in one place.
func LoadStrict ¶
func LoadStrict(data []byte, path string, components ...ComponentConfig) (*ProjectConfig, error)
LoadStrict parses a forge.yaml byte stream into a ProjectConfig with strict validation: unknown keys (typos, dropped fields) and missing required fields are reported in a single error rather than silently succeeding or failing on the first issue.
path is used purely for error-message context (e.g. "forge.yaml" or the absolute path); it is not opened. Pass an empty string for inline data without a file backing.
Behaviour:
- The YAML is decoded into a yaml.Node tree, then walked against the ProjectConfig struct shape. Unknown keys are collected with their YAML line number and parent path; a Levenshtein-based suggestion is attached when a known sibling key is within edit distance 2 (or 3 for keys >= 8 chars).
- The same bytes are then decoded into a ProjectConfig via the standard yaml decoder so that scalar-type mismatches (e.g. port: "8080") surface as their own error class.
- Required-field validation runs on the populated struct.
All issues across the three phases are batched into a single ValidationError; the caller sees the full list rather than just the first failure. The variadic components argument carries the per-component entities the caller has already parsed from the project-root components.json (see LoadProject). They are no longer part of forge.yaml: the loader injects them into the returned config and DERIVES the project kind from them (DeriveProjectKind) before running shape-derived defaults + the feature graph. Callers with no components (the common test path, or a pure library) pass none — and the project kind derives to library (no components.json signal). Callers that need the empty-service-shell (components.json present but empty → service) must go through LoadProject.
func NormalizeForWrite ¶
func NormalizeForWrite(c *ProjectConfig) *ProjectConfig
NormalizeForWrite returns a copy of c with every derivable value that matches its shape-derived default removed, so marshalling produces the minimal forge.yaml. Explicit values that DIFFER from derivation are preserved — overrides survive round-trips; boilerplate does not.
Dropping a value that equals its derived default is behavior-preserving by construction: the loader re-derives the identical value on the next read. The original c is not mutated.
func (ProjectConfig) BinaryComponents ¶
func (c ProjectConfig) BinaryComponents() []ComponentConfig
BinaryComponents returns the binary-kind components.
func (ProjectConfig) Crons ¶
func (c ProjectConfig) Crons() []ComponentConfig
Crons returns the cron-kind components.
func (ProjectConfig) EffectiveBinary ¶
func (c ProjectConfig) EffectiveBinary() string
EffectiveBinary returns the binary mode, defaulting to "per-service" so legacy forge.yaml files without the field keep producing the canonical cmd/server.go shape.
func (ProjectConfig) EffectiveForgeVersion ¶
func (c ProjectConfig) EffectiveForgeVersion() string
EffectiveForgeVersion returns the forge version pinned by this project, defaulting to "0.0.0" for legacy projects that predate the field. Callers can use the "0.0.0" sentinel to detect "no baseline yet" and nudge the user toward `forge upgrade`.
func (*ProjectConfig) EffectiveHotReload ¶
func (c *ProjectConfig) EffectiveHotReload() bool
EffectiveHotReload resolves the top-level hot_reload toggle: explicit value wins; absent derives to "on for service kind" (the value the scaffold used to write).
func (ProjectConfig) EffectiveKind ¶
func (c ProjectConfig) EffectiveKind() string
EffectiveKind returns the project kind, defaulting to "service".
func (ProjectConfig) HasReactNativeFrontend ¶
func (c ProjectConfig) HasReactNativeFrontend() bool
HasReactNativeFrontend reports whether any frontend in the project is a React Native (Expo) app. Used to gate features that only apply to mobile — e.g. the `@<scope>/ui-native` workspace package.
Returns true for frontends declared with `type: react-native` (or the historic `type: react_native` underscore form the validator also accepts).
func (ProjectConfig) IsBinaryShared ¶
func (c ProjectConfig) IsBinaryShared() bool
IsBinaryShared reports whether the project uses the shared-binary codegen mode (one Go binary, cobra subcommand per service, KCL MultiServiceApplication for deploy).
func (ProjectConfig) IsCLIKind ¶
func (c ProjectConfig) IsCLIKind() bool
IsCLIKind reports whether the project is a CLI binary (no server scaffolding).
func (ProjectConfig) IsFrontendWorkspacesEnabled ¶
func (c ProjectConfig) IsFrontendWorkspacesEnabled() bool
IsFrontendWorkspacesEnabled reports whether the project opted in to the pnpm-workspaces layout. Wraps ProjectConfig.Frontend.Workspaces so callers can read the effective flag without poking into the nested struct (and so we have one place to enforce future invariants — e.g. requiring at least 2 frontends before enabling).
func (ProjectConfig) IsLibraryKind ¶
func (c ProjectConfig) IsLibraryKind() bool
IsLibraryKind reports whether the project is a pure Go library (no cmd/).
func (ProjectConfig) IsServiceKind ¶
func (c ProjectConfig) IsServiceKind() bool
IsServiceKind reports whether the project is a Connect-RPC service.
func (ProjectConfig) Operators ¶
func (c ProjectConfig) Operators() []ComponentConfig
Operators returns the operator-kind components.
func (ProjectConfig) Servers ¶
func (c ProjectConfig) Servers() []ComponentConfig
Servers returns the server-kind components — the Connect-RPC surfaces that get handlers, the served-set registration, and frontend hooks.
func (ProjectConfig) Workers ¶
func (c ProjectConfig) Workers() []ComponentConfig
Workers returns the worker-kind components.
type Service ¶
type Service interface {
// EffectiveKind normalizes a raw kind string to one of the canonical
// ProjectKind* constants, defaulting to "service" for empty/unknown input.
EffectiveKind(kind string) string
}
Service is the behavioral surface of the config package.
Today it wraps EffectiveProjectKind so the require-contract analyzer is satisfied without forcing the data-type accessor methods onto an interface they have no business being on. Future stateful behavior (file loading, validation, defaulting against forge.yaml on disk) will land here as internal/cli and internal/generator are ported and their config-touching helpers consolidate into this package.
type SmokeConfig ¶
type SmokeConfig struct {
// FlowChecks are the declared app-flow health endpoints. Each is an HTTP
// endpoint smoke probes; 2xx = PASS, anything else (typically 503) = FAIL
// (RED), and any FAIL fails the whole smoke run (non-zero exit), exactly
// like a failed route probe.
FlowChecks []SmokeFlowCheck `yaml:"flow_checks,omitempty"`
}
SmokeConfig declares APP-FLOW health checks `forge smoke <env>` probes alongside its built-in ingress/dev-port probes. The built-in probes only verify TRANSPORT (a listener answered); they can be GREEN while the app is functionally broken. A flow check lets the app DECLARE an end-to-end invariant that the OWNING SERVICE asserts INTERNALLY and exposes as an HTTP flow-health endpoint (200 healthy / 503 unhealthy). smoke just CURLS that endpoint and folds the status into its PASS/FAIL/exit report — so a green smoke means the app actually works, not just that ports are open.
WHY AN ENDPOINT, NOT A COMMAND. The owning service already holds the access (DB creds, cluster vantage point) the assertion needs; running the check inside it avoids handing smoke privileged creds. smoke needs only a URL + reachability. The endpoint is STATUS-ONLY in public (200/503 + aggregate counts) so it leaks nothing sensitive anonymously; per-entity DETAIL lives behind auth or an internal-only port.
The daemon-flow case that motivated this: `forge smoke dev` was GREEN while the managed-daemon flow was broken, because no built-in probe could assert "every Ready daemon is attached to the gateway". reliant's daemon-gateway owns that state, so it exposes `/flow-health` (200/503) and smoke curls it.
type SmokeFlowCheck ¶
type SmokeFlowCheck struct {
// Name labels the check in the smoke table / JSON (e.g. "daemon-flow").
Name string `yaml:"name"`
// URL is the flow-health endpoint smoke GETs. It may be a per-env literal
// (e.g. "http://localhost:28091/flow-health" for dev) — scope it with Envs
// when the URL differs per env. A 2xx is PASS; any other status (or a
// transport failure) is FAIL.
URL string `yaml:"url"`
// Envs optionally scopes the check to specific smoke environments (by
// name). Empty = probe in every env. Use it when the endpoint URL is
// env-specific (the usual case — different host/port per env).
Envs []string `yaml:"envs,omitempty"`
// Description is an optional human note shown in the smoke detail column.
Description string `yaml:"description,omitempty"`
}
SmokeFlowCheck is one declared app-flow health endpoint `forge smoke <env>` probes. The owning service asserts the invariant internally and returns 200 (healthy) / 503 (unhealthy) at this endpoint; smoke curls it and merges the verdict into its summary + exit logic. It is the HTTP-endpoint analogue of a route probe — same machinery, declared by the app.
func (SmokeFlowCheck) RunsInEnv ¶
func (c SmokeFlowCheck) RunsInEnv(env string) bool
RunsInEnv reports whether this flow check should be probed for the given smoke environment. An empty Envs list means "every env".
type StackConfig ¶
type StackConfig struct {
Frontend StackFrontend `yaml:"frontend,omitempty"`
}
StackConfig declares the technology choices for the project.
Historically this block carried six sub-sections (backend, frontend, database, proto, deploy, ci) of "forward-looking declarations". Five of those (backend/database/proto/deploy/ci) were never consumed by any codegen path and merely DUPLICATED the canonical sources — `database.driver`, `ci.provider`, `docker.registry` + per-env KCL — so they were removed in the forge.yaml schema cleanup (FORGE_SHAPE_REDESIGN §4). Old keys parse with a migration warning (see removedSchemaKeys: stack.backend etc.).
Only `stack.frontend.framework` remains: it is genuinely load-bearing (read by `forge add frontend` and the frontend-build skip in build.go to know whether the project ships a frontend framework at all).
func (StackConfig) EffectiveFrontendFramework ¶
func (s StackConfig) EffectiveFrontendFramework() string
EffectiveFrontendFramework returns the frontend framework, defaulting to "nextjs".
type StackFrontend ¶
type StackFrontend struct {
Framework string `yaml:"framework,omitempty"` // "nextjs" (default), "react-native", "svelte", "none"
}
StackFrontend declares the frontend framework.
type ValidationError ¶
type ValidationError struct {
Path string
Issues []validationIssue
}
ValidationError aggregates all forge.yaml validation issues into a single error so callers see the full picture instead of fail-fast on the first problem. Implements error.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string