generate

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Overview

Package generate composes the engine's extraction, staging, module, facade, type, dependency, and provenance phases into one complete generated module for an upstream release tag or a release-bounded exact commit.

A plan answers what the extracted tree would contain. A generation answers what the published module would be: the same relocated code, plus the go.mod and go.sum a consumer resolves against, the curated facade a consumer imports, and the root evidence that says where the code came from and how it differs from upstream. It stops before history replay and publication, which are later phases with their own gates.

Four properties bound what a generation may do.

It is read-only outward. Nothing here creates a ref, pushes, or contacts a destination repository. The source cache is driven by an anonymous runner, so a credential that exists for publishing cannot travel to the source host, and the run refuses to start at all while such a credential is visible in the environment.

It is contained. Every directory is absolute, checked, and disjoint. The scratch trees the phases need are created below the work root and owned by this run, and the final output tree is written exactly once, at the end, from a file set every gate has already passed.

It fails closed. A generation is a sequence of gates rather than a sequence of steps: the pre-prune and post-prune public APIs must match, the generated go.mod must survive tidying without a pin floating, the type substitution must be provable against upstream package identities, the dependency decision must be reachable from measured evidence, and the root provenance must account for every file in the tree it describes. A gate that cannot be evaluated refuses rather than passes, and the shapes this first engine does not support refuse explicitly rather than approximating an answer.

It is deterministic. Two runs over one source commit with different directory layouts produce byte identical reports and byte identical trees. The report therefore carries no absolute path, no proxy URL, no credential, no source remote override, and no timestamp: what it records is the profile, the source commit, and the content the two produce.

Index

Constants

View Source
const ReportSchema = 1

ReportSchema is the version of the report this package emits.

Variables

View Source
var (
	// ErrCredentialEnvironment reports a publishing credential visible to a run
	// that has no use for one.
	ErrCredentialEnvironment = errors.New("a generation must run without publishing credentials")
	// ErrPathConflict reports directories that overlap when they must not.
	ErrPathConflict = errors.New("the generation directories conflict")
	// ErrUnsupported reports a run shape this engine refuses rather than
	// approximates. Moving branch names remain preview inputs; reconciliation
	// resolves and supplies immutable commits instead.
	ErrUnsupported = errors.New("this generation engine does not support the requested run")
)
View Source
var ErrCopyNotPortable = errors.New("the candidate has Go files excluded by the current build constraints, so a copy would be host-specific")

ErrCopyNotPortable reports a staging package whose Go files are not the same on every platform. A copy that reads only the host-matched files would compile on the build machine and fail elsewhere, so this is refused rather than approximated.

Functions

This section is empty.

Types

type AnalysisReport

type AnalysisReport struct {
	Name     string   `json:"name"`
	Passed   bool     `json:"passed"`
	Evidence []string `json:"evidence"`
	Blockers []string `json:"blockers"`
}

AnalysisReport is one proof's outcome.

type BehaviorChangeReport

type BehaviorChangeReport struct {
	Summary string `json:"summary"`
	Cause   string `json:"cause"`
}

BehaviorChangeReport is one documented difference from upstream.

type CandidateReport

type CandidateReport struct {
	ImportPath  string `json:"importPath"`
	StagingPath string `json:"stagingPath"`
	Module      string `json:"module"`
	Proposed    bool   `json:"proposed"`
	Action      string `json:"action"`
	// FailedGates are the gates that refused it, sorted.
	FailedGates []string `json:"failedGates"`
}

CandidateReport is one candidate's verdict.

type DependencyReport

type DependencyReport struct {
	// Policy is the configured default action.
	Policy string `json:"policy"`
	// Candidates reports every candidate the graph offered, sorted by import
	// path, whether or not the profile proposed it.
	Candidates []CandidateReport `json:"candidates"`
	// Copy lists the staging paths the decision approves, sorted. For a profile
	// whose answer is external it is empty, and that emptiness is the decision
	// rather than the absence of one.
	Copy []string `json:"copy"`
	// Candidates, Copied, and Refused summarize the decision.
	Totals TotalsReport `json:"totals"`
}

DependencyReport records the dependency decision.

It is this package's own shape rather than the decider's, because the decider's records carry an absolute module directory for provenance and this report may not.

type EngineReport

type EngineReport struct {
	// Version is the engine version.
	Version string `json:"version"`
	// Toolchain is the Go toolchain the profile pins for deterministic
	// formatting.
	Toolchain string `json:"toolchain"`
	// ProfileHash is the digest of the output affecting subset of the profile.
	// It is taken from the post-prune extraction rather than recomputed, so the
	// generation and the plan it contains can never disagree about which profile
	// produced them.
	ProfileHash string `json:"profileHash"`
}

EngineReport identifies what produced the generation.

type ExtractReport

type ExtractReport struct {
	// Pre is the unpruned baseline, which exists only to make the facade
	// comparison and the type policy possible.
	Pre PassReport `json:"pre"`
	// Post is the pass that produced the module being generated.
	Post PassReport `json:"post"`
}

ExtractReport records both extraction passes.

type FacadeReport

type FacadeReport struct {
	// Package is the generated root package name.
	Package string `json:"package"`
	// PreManifestHash and PostManifestHash digest the rendered manifests. Equal
	// digests are the proof that pruning changed no published API.
	PreManifestHash  string `json:"preManifestHash"`
	PostManifestHash string `json:"postManifestHash"`
	// Differences are the rendered manifest differences, sorted. A generation
	// that completed has none, because any difference at all refuses.
	Differences []string `json:"differences"`
	// Entries are the published names, sorted.
	Entries []string `json:"entries"`
	// Files are the generated facade files, sorted by module relative path.
	Files []string `json:"files"`
}

FacadeReport records the published API and the comparison that gated it.

type FailureReport

type FailureReport struct {
	// Stage names the phase that refused, matching PolicyError.Stage.
	Stage string `json:"stage"`
	// Message is the rendered failure with every directory this run owns
	// replaced by a stable placeholder, so two runs over different layouts
	// produce the same text.
	Message string `json:"message"`
	// Policy reports whether the refusal was a finding about the profile rather
	// than an engine or environment failure, which is the distinction CI acts
	// on.
	Policy bool `json:"policy"`
	// Unsupported reports a run shape this engine refuses rather than
	// approximates, which is neither a bad profile nor a broken engine.
	Unsupported bool `json:"unsupported"`
}

FailureReport records one refused generation.

type ModulePin

type ModulePin struct {
	// Path is the published module path.
	Path string `json:"path"`
	// Version is the resolved version.
	Version string `json:"version"`
	// Commit is the staging commit that version names, which is the evidence a
	// later run has that the tag still names what it named before.
	Commit string `json:"commit"`
	// Directory is the upstream staging directory the version corresponds to,
	// repository relative.
	Directory string `json:"directory"`
}

ModulePin is one staging module resolved to a published version.

type ModuleReport

type ModuleReport struct {
	// GoModHash and GoSumHash digest the published metadata. The bytes
	// themselves are the tree's rather than the report's, and the tree is
	// already digested by Output.ManifestHash.
	GoModHash string `json:"goModHash"`
	GoSumHash string `json:"goSumHash"`
	// Kept lists the requirements that survived tidying, sorted by path.
	Kept []RequirementReport `json:"kept"`
	// Added lists transitive requirements introduced by an allowed compatibility
	// re-tidy, sorted by path.
	Added []RequirementReport `json:"added,omitempty"`
	// Dropped lists the module paths tidying removed, sorted. A large set is the
	// normal outcome of extracting a few packages out of Kubernetes.
	Dropped []string `json:"dropped"`
	// Reclassified lists requirements tidying kept at the pinned version but
	// marked differently than the source module did, sorted by path.
	Reclassified []ReclassificationReport `json:"reclassified"`
	// BaselineGoModHash digests the unpruned module's metadata. It is recorded
	// because the baseline is what the facade comparison was made against, and a
	// reviewer checking that comparison needs to know which module produced it.
	BaselineGoModHash string `json:"baselineGoModHash"`
}

ModuleReport records the module metadata the toolchain settled on.

type Options

type Options struct {
	// Config is the decoded, validated profile.
	Config *config.Config
	// ProfileDir is the repository directory holding the profile, the patch
	// files its patch entries name, and the closure golden it pins.
	ProfileDir string
	// CacheRoot holds the reusable bare source cache. Both extraction passes
	// share it, so one clone serves the whole run.
	CacheRoot string
	// WorkRoot holds the scratch trees this run creates and removes.
	WorkRoot string
	// OutputRoot is where the generated module is written. It must not exist;
	// relocation never merges into or overwrites a tree.
	OutputRoot string
	// StorePath is the version index file the staging resolution caches into.
	// It is absolute, and the run creates it if it is absent.
	StorePath string
	// Ref selects the upstream source. Release tags are public inputs; the
	// reconciliation engine also supplies exact commits already fetched through a
	// bounded release head. Branches remain unsupported.
	Ref extract.Ref
	// ReleaseContext is the upstream release whose bounded history contains an
	// exact commit. It is required for RefCommit and empty for RefTag, where Ref
	// itself is the release. Dependency override expiry and staging history use
	// this context without claiming the intermediate commit is tagged.
	ReleaseContext string
	// HistoryAnchor and HistoryAnchorRelease bound intermediate source and
	// staging walks to the last published release, inclusive.
	HistoryAnchor        string
	HistoryAnchorRelease string
	// StagingSources supplies the trusted repository for each staging module
	// when an intermediate index entry must be resolved. It must name exactly the
	// required modules; a cached index hit needs no repositories.
	StagingSources map[string]StagingSource
	// PatchBranch is the tracked branch a patch's branch selector is matched
	// against. It is required only when the profile carries patches.
	PatchBranch string
	// SourceRemote overrides the profile's source repository, which is how a
	// test or an air-gapped operator points the run at a local mirror.
	SourceRemote string
	// Fetch updates the cache before the ref is resolved.
	Fetch bool
	// Offline refuses every network operation. A generation still has to read
	// the upstream licence out of the cache, so an offline run whose cache does
	// not hold that blob is a policy failure rather than a silent fetch.
	Offline bool
	// Materialize writes the generated module to OutputRoot. Without it the run
	// computes and gates the same tree and hashes it without touching a disk.
	Materialize bool
	// KeepWorktree leaves the scratch trees in place for inspection.
	KeepWorktree bool
	// Strict turns every advisory notice into a policy failure, which it does
	// before any output is written rather than after.
	Strict bool
	// Git is the runner the extraction phases drive. It must be anonymous: a
	// generation talks to the public source host and to nothing else.
	Git *gitcli.Runner
	// Go is the runner every Go toolchain phase drives. Its isolation and its
	// proxy decide where module state comes from, and the run rebases it onto
	// each scratch module rather than building runners of its own, so the
	// caller owns that environment in one place.
	Go *gocli.Runner
	// LookupEnv reads the process environment. A nil value uses os.LookupEnv.
	// It is injectable so the credential check is testable without mutating the
	// environment of a running test binary.
	LookupEnv func(string) (string, bool)
}

type OutputReport

type OutputReport struct {
	// Module is the destination module path.
	Module string `json:"module"`
	// Files is how many files the complete tree holds, root evidence and module
	// metadata included.
	Files int `json:"files"`
	// Packages is how many Go packages it holds.
	Packages int `json:"packages"`
	// ManifestHash digests the complete tree: every destination path, its mode,
	// and its content. Two generations that agree on it produced the same
	// module.
	ManifestHash string `json:"manifestHash"`
	// Materialized reports that the tree was written to a disk. A generation
	// computes the same tree either way, so the hash above does not depend on
	// it.
	Materialized bool `json:"materialized"`
}

OutputReport records the tree the generation produced.

type PassReport

type PassReport struct {
	// ReportHash digests the pass's complete plan report.
	ReportHash string `json:"reportHash"`
	// ManifestHash digests the relocated tree the pass produced.
	ManifestHash string `json:"manifestHash"`
	Files        int    `json:"files"`
	Packages     int    `json:"packages"`
	// ClosurePackages are the post-prune package import paths of this pass,
	// sorted.
	ClosurePackages []string `json:"closurePackages"`
	// PrunedFiles and DeniedImports are what the pass asserted, sorted. They are
	// empty for the baseline by construction, which is what makes the pair
	// readable as a description of what pruning did.
	PrunedFiles   []string `json:"prunedFiles"`
	DeniedImports []string `json:"deniedImports"`
}

PassReport is one extraction pass, digested and summarized.

The whole plan report is digested rather than embedded, because a generation report that contained two complete plan reports would be dominated by them while answering a different question. The digest is what proves two generations ran the same plan; the sections beside it are the ones a reviewer of a generation actually reads.

type Paths

type Paths struct {
	// Cache is the bare source cache directory.
	Cache string
	// Work is the scratch root this run owns.
	Work string
	// Output is the generated module destination, written only with
	// -materialize.
	Output string
	// Store is the version index file.
	Store string
	// PreModule and PostModule are the scratch relocated modules the two
	// extraction passes produced. PreModule exists only to establish the facade
	// baseline and is never a candidate for the final output.
	PreModule  string
	PostModule string
	// PreWorktree and PostWorktree are the materialized upstream source trees,
	// empty once they were removed. PreWorktree is where the type policy runs,
	// because it holds the upstream package identities the profile names.
	PreWorktree  string
	PostWorktree string
	// Resolver is the isolated scratch module the staging version resolver ran
	// in.
	Resolver string
}

Paths are the absolute directories one generation used.

They are deliberately outside Report, which carries no absolute path.

type PolicyError

type PolicyError struct {
	// Stage names the phase that refused, such as extract, staging, module,
	// facade, types, dependencies, provenance, or output.
	Stage string
	// Err is the underlying failure.
	Err error
}

PolicyError reports a generation that ran correctly and found the profile, its inputs, or the module they produce unacceptable.

It exists for the same reason the extraction phase has one: the command line has to separate the answer "the engine worked and the answer is no" from "the engine could not answer". A drifted public API, a floated pin, an unprovable substitution, an unaccounted file, and a licence that is not the one the profile names are all findings about the profile. Only those exit with the check code CI reads as "review this".

func (*PolicyError) Error

func (e *PolicyError) Error() string

func (*PolicyError) Unwrap

func (e *PolicyError) Unwrap() error

type ProvenanceReport

type ProvenanceReport struct {
	// LicenseID is the SPDX identifier the profile states and this run verified
	// against the upstream text.
	LicenseID string `json:"licenseId"`
	// LicenseHash digests the upstream licence reproduced at the root.
	LicenseHash string `json:"licenseHash"`
	// UpstreamNotice reports whether the upstream commit carried a NOTICE, and
	// NoticeHash digests it when it did. Both are recorded because embedding an
	// upstream NOTICE that exists is a licence obligation, so its absence is a
	// claim rather than a detail.
	UpstreamNotice bool   `json:"upstreamNotice"`
	NoticeHash     string `json:"noticeHash"`
	// Files are the generated root files, sorted by module relative path.
	Files []string `json:"files"`
	// BehaviorChanges are the documented differences from upstream, sorted by
	// summary.
	BehaviorChanges []BehaviorChangeReport `json:"behaviorChanges"`
	// PublicAPI are the names the README states the module publishes, sorted.
	PublicAPI []string `json:"publicApi"`
}

ProvenanceReport records the root evidence.

type ReclassificationReport

type ReclassificationReport struct {
	Path string `json:"path"`
	// Indirect is what tidying decided, and therefore what the generated module
	// carries. What the source module said is its negation.
	Indirect bool `json:"indirect"`
}

ReclassificationReport is one requirement whose directness tidying changed.

type Report

type Report struct {
	Schema       int              `json:"schema"`
	Engine       EngineReport     `json:"engine"`
	Source       SourceReport     `json:"source"`
	Extract      ExtractReport    `json:"extract"`
	Staging      StagingReport    `json:"staging"`
	Module       ModuleReport     `json:"module"`
	Facade       FacadeReport     `json:"facade"`
	Types        TypesReport      `json:"types"`
	Dependencies DependencyReport `json:"dependencies"`
	Provenance   ProvenanceReport `json:"provenance"`
	Output       OutputReport     `json:"output"`
	// Failure records why the generation refused, nil when it did not. A report
	// is produced for a refusal precisely so it is reviewable without rerunning
	// the pipeline.
	Failure *FailureReport `json:"failure"`
	// Notices are advisory findings from every phase, sorted and deduplicated.
	// They never stop a generation on their own; -strict is what turns them into
	// a refusal, and it does so before any output is written.
	Notices []string `json:"notices"`
	// contains filtered or unexported fields
}

Report is the deterministic record of one generation.

It carries no absolute path, no proxy URL, no credential, no source remote override, and no timestamp. That is not tidiness: the report is compared byte for byte between two runs over different directory layouts, it is attached to CI artifacts, and it is the evidence a reviewer reads before approving an outward action. A path from the machine that produced it would break the first use and leak into the second.

Every list is sorted and non-nil, so the encoding depends on the generation alone and never on map iteration order or on whether a list happened to be empty.

func (Report) JSON

func (r Report) JSON() ([]byte, error)

JSON renders the report canonically.

type RequirementReport

type RequirementReport struct {
	Path     string `json:"path"`
	Version  string `json:"version"`
	Indirect bool   `json:"indirect"`
}

RequirementReport is one surviving requirement.

type Result

type Result struct {
	// Report is the deterministic record of what the generation found.
	Report Report
	// Files is the complete generated module: the relocated upstream code, the
	// generated facade, the tidied module metadata, and the root provenance.
	// It is what -materialize writes, and it is empty for a run that refused
	// before it had composed a tree.
	Files relocate.FileSet
	// Paths are the absolute directories the run used.
	Paths Paths
}

Result is one completed generation.

A generation that refused still produces one whenever it measured enough to be worth reading. Report.Failure is what tells the two apart, and it is the reason a refusal is reviewable from an artifact rather than from a stderr line.

func Generate

func Generate(ctx context.Context, opts Options) (*Result, error)

Generate produces the complete generated module for one upstream release tag.

The phases run in a fixed order because each one's inputs are the previous one's proven outputs, and the order is what makes the gates meaningful rather than decorative. The facade cannot be compared before both modules exist, the dependency decision cannot be reached before the facade's imports are in the tree the module graph is loaded from, and the root provenance cannot be cross-checked before the tree it describes has been composed.

The final tree is written last and only once. Every gate runs against the tree in memory, so a run that refuses leaves no output at all rather than a directory an operator has to know not to trust.

func (*Result) Summary

func (r *Result) Summary() string

Summary renders the generation for a person.

It is the one rendering allowed to name absolute directories, because an operator who just ran the command needs to know where the module went, and nothing compares this text byte for byte.

type SourceReport

type SourceReport struct {
	RefKind string `json:"refKind"`
	RefName string `json:"refName"`
	// Commit is the upstream commit both extraction passes read.
	Commit string `json:"commit"`
	// ReleaseTag is the generated module's tag for an upstream release. It is
	// empty for an intermediate exact commit, which publishes no module tag.
	ReleaseTag string `json:"releaseTag"`
	// Fetched, Offline, and RemoteOverridden report how the source was obtained.
	Fetched          bool `json:"fetched"`
	Offline          bool `json:"offline"`
	RemoteOverridden bool `json:"remoteOverridden"`
}

SourceReport records the upstream release the generation covers.

The remote is absent on purpose, including when it was overridden. Only the fact of an override is recorded: its value is frequently a path on the machine that ran the generation, and this report is compared byte for byte between two runs over different layouts.

type StagingReport

type StagingReport struct {
	// SourceModule is the module path the upstream commit declares.
	SourceModule string `json:"sourceModule"`
	// GoDirective is the source module's language version, which the generated
	// module inherits so the extracted code compiles under the semantics
	// upstream compiled it under.
	GoDirective string `json:"goDirective"`
	// Cached reports that the version index already held this source commit, so
	// no version was resolved over the network.
	Cached bool `json:"cached"`
	// Modules are the pinned staging modules, sorted by path.
	Modules []ModulePin `json:"modules"`
}

StagingReport records how the staging modules were pinned.

type StagingSource added in v0.2.0

type StagingSource struct {
	Remote string
}

Options configures one generation.

Every directory is absolute because a generation must name the same directories no matter where the process was started from, and because the run adopts none of them: the cache and work roots are created if absent and owned by the run thereafter, and the output tree must not exist at all. StagingSource names the trusted history used to resolve one intermediate Kubernetes staging module. Reconciliation derives the production URL from the module path; local fixtures may override it only while the source itself is also overridden to a local repository.

type TotalsReport

type TotalsReport struct {
	Candidates int `json:"candidates"`
	Copied     int `json:"copied"`
	Refused    int `json:"refused"`
}

TotalsReport summarizes one decision.

type TypeBehaviorChange

type TypeBehaviorChange struct {
	Kind   string `json:"kind"`
	Symbol string `json:"symbol"`
	Detail string `json:"detail"`
	// Observable reports whether the effect can be reached through the
	// generated public API.
	Observable bool `json:"observable"`
}

TypeBehaviorChange is one import time effect the substitution removes.

The analyzer's source position is deliberately dropped. It names the scratch tree the analysis ran in, and the effect is already identified by the symbol that performed it, which is a fact about the upstream code rather than about where a copy of it happened to be checked out.

type TypePairReport

type TypePairReport struct {
	Internal string `json:"internal"`
	External string `json:"external"`
	// Action is the decision, such as prune-internal or blocked.
	Action string `json:"action"`
	// Analyses are the individual proofs, in the order the analyzer ran them.
	Analyses []AnalysisReport `json:"analyses"`
	// Evidence and Blockers are every proof's findings, sorted.
	Evidence []string `json:"evidence"`
	Blockers []string `json:"blockers"`
	// BehaviorChanges are the import time effects the change removes.
	BehaviorChanges []TypeBehaviorChange `json:"behaviorChanges"`
	// ExternalAlreadyUsed lists the retained packages that already import the
	// external package, sorted. It is the proof that the substitution has in
	// effect already happened upstream and the internal package is simply dead.
	ExternalAlreadyUsed []string `json:"externalAlreadyUsed"`
}

TypePairReport is one pairing's verdict.

func (TypePairReport) Analysis

func (p TypePairReport) Analysis(name string) (AnalysisReport, bool)

Analysis reports one named proof of this pairing.

type TypesReport

type TypesReport struct {
	// Policy is the configured policy.
	Policy string `json:"policy"`
	// Pairs reports every pairing, sorted by internal package path.
	Pairs []TypePairReport `json:"pairs"`
}

TypesReport records the type policy analysis.

It is this package's own shape rather than the analyzer's, and the reason is the same one that governs the whole report. The analyzer's evidence quotes source positions, and a position names the scratch work tree the analysis ran in, which is a directory on the machine that produced the report. Restating the analysis here with those positions rewritten to the tree relative form is what lets two runs over different layouts compare byte for byte while keeping the evidence a reviewer needs.

Jump to

Keyboard shortcuts

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