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

detection

package
v0.17.0 Latest Latest
Warning

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

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

Documentation

Overview

Package detection runs the detection job for an app and records the result.

It is the seam between detection (which knows about repositories and detectors) and the rest of Pando (which knows about apps, policy and audit). Sequence A's numbered steps 6 through 12 live in internal/detect; the parts that need an app row, the source allowlist and a place to write the answer live here.

Index

Constants

View Source
const ActionRevise = "detection.revise"

ActionRevise is the audit event a revision writes (R-337): repository contents went to the provider because a person asked.

View Source
const ActionScreen = "detection.screen"

ActionScreen is R-337's event.

View Source
const MaxInstruction = 2_000

MaxInstruction is the longest message a person may send, in characters.

View Source
const MaxWait = 60 * time.Second

MaxWait is the longest a caller may ask Await to hold a request open.

Long enough that a client waiting out a trial run makes a handful of requests rather than hundreds, and short enough to finish well inside the idle timeout of any proxy in front of Pando — a held request cut off by one is a failure the caller did nothing to cause (issue #80).

View Source
const ReadTimeout = 10 * time.Minute

ReadTimeout is what one detection gets to read an app: a clone, an auction and a trial run that may pull a base image over a slow connection.

Generous: the cost of being too short is a detection that fails for a large repository on a home connection, which is exactly the user R-005 describes.

Variables

View Source
var Timeout = ReadTimeout + time.Duration(spec.StandardDefaults().BuildTimeoutSeconds)*time.Second

Timeout bounds one detection run: reading the app, and building what it would deploy, which detection has done since R-311. The build gets what a deploy's build gets by default. Held to the reading's ten minutes, detection failed builds a deploy would have finished — on a six-CPU host with a dozen apps added at once, four in ten of them (issue #90).

Functions

func Accepted added in v0.17.0

func Accepted(p detect.Proposal, answers map[string]string, appID, slug string, defaults *spec.Defaults, pinned *spec.AppSpec) spec.AppSpec

Accepted is the spec accepting proposal would pin, before the values a person sets in the same step: the answers given, then the install's defaults, then the decisions carried from the pinned spec.

One function because two places need the same answer. Accepting pins it, and the detection diff shows what accepting changes (R-022). The diff used to compare the raw proposal, so it reported every variable, slot fill, hostname and limit that Carry keeps as removed (issue #350).

defaults may be nil, where the install supplies none. pinned is nil on the first acceptance, and then nothing carries.

func Await

Await returns an app's detection once it has moved on from what it was when Await was called — a new stage, or an outcome — or once wait has passed, whichever is first (issue #80).

A detection that is not running is returned at once: there is nothing to wait for, and holding a finished answer back would only look like a hang. So is one with no wait asked for, which is the plain GET.

A long poll rather than a stream, because it works through every proxy and fits MCP's one-request-one-answer shape, and every surface uses the same one (R-261). Reading the row on an interval rather than listening for a notification, because detection is recorded in exactly one place and this is the boring way to watch it.

func DefaultConcurrency

func DefaultConcurrency() int

DefaultConcurrency is how many detections one replica runs at once when configuration does not say [P]: one per CPU, at least two. A detection is a clone and, sometimes, a trial run — lighter than a deploy, and just as unbounded when every app added at once starts one.

Types

type AuditEvent

type AuditEvent struct {
	Action string
	AppID  string
	Detail map[string]any
}

AuditEvent is what a screening records.

type Auditor

type Auditor interface {
	Write(ctx context.Context, e AuditEvent) error
}

Auditor records that a repository's contents left the host (R-337).

type DetectionReader

type DetectionReader interface {
	Get(ctx context.Context, appID string) (state.Detection, error)
}

DetectionReader reads an app's current detection.

type ImageBuilder added in v0.5.0

type ImageBuilder interface {
	BuildImages(ctx context.Context, s *spec.AppSpec, checkout *source.Checkout, sink io.Writer, tag string) (string, map[string]string, error)
	BuiltImageAuth(ctx context.Context, image string) *api.RegistryAuth
}

ImageBuilder builds an app's images the way a deploy does. deploy.Runner is one.

type Installation

type Installation interface {
	// Defaults returns the install's adapters, routing mode and limits. The
	// routing mode comes from the routing adapter's own declared default
	// (R-162): adding an app uses it without asking.
	Defaults(ctx context.Context) spec.Defaults
}

Installation supplies the answers a repository cannot give about itself.

A repository says it builds from a Dockerfile and listens on 3000. It cannot say which runtime this install uses or how apps here are addressed, and R-104 is explicit that none of that is a question worth asking — it is configuration, and configuration gets a default.

type PortAllocator

type PortAllocator interface {
	Allocate(ctx context.Context, adapterRef, appID string, from, to int) (int, error)
}

PortAllocator hands out host ports for port-mode routing.

type Queue

type Queue struct {
	Detections *state.Detections

	// Detect runs one claimed detection to completion. In production,
	// Runner.RunQueued.
	Detect func(ctx context.Context, appID string) (state.Detection, error)

	// Limit is how many detections this replica runs at once.
	// DefaultConcurrency when zero.
	Limit int

	// Poll is how often to look for detections another replica queued. Two
	// seconds when zero.
	Poll time.Duration

	// Timeout overrides the package Timeout. Zero is the default.
	Timeout time.Duration

	Logger *zap.Logger
	// contains filtered or unexported fields
}

Queue runs queued detections on this replica (issue #72, O-32).

Enqueue marks an app's detection running and unclaimed; every replica's queue claims what it has room for. Creating a hundred apps at once used to start a hundred clones in the process that took the requests.

func (*Queue) Enqueue

func (q *Queue) Enqueue(ctx context.Context, appID string) (state.Detection, error)

Enqueue queues a detection for an app — marked running, so the first poll cannot read the previous outcome as this one's — and tells this replica's queue to look now. It returns the detection as queued, from the statement that queued it (state.Detections.Queue): any replica may claim and finish it the moment it is written, and a later read could return that outcome.

func (*Queue) Running

func (q *Queue) Running() int

Running is how many detections this replica is running now.

func (*Queue) Serve

func (q *Queue) Serve(ctx context.Context)

Serve runs queued detections until ctx ends.

type RegistryInstallation

type RegistryInstallation struct {
	Registry *api.Registry

	// BaseDomain is what a subdomain is carved out of, in subdomain mode.
	BaseDomain string

	// Fallback supplies the values that come from neither an adapter nor host
	// policy — the [P] defaults in the requirements.
	Fallback spec.Defaults
}

RegistryInstallation reads an install's defaults from its configured adapters.

func NewInstallation

func NewInstallation(registry *api.Registry, baseDomain string) *RegistryInstallation

NewInstallation returns an installation reading from registry.

func (*RegistryInstallation) Defaults

Defaults resolves the install's adapters and routing mode.

The routing mode is asked of the routing adapter rather than configured centrally. R-162: each adapter declares a default mode, and adding an app uses it without asking — which is what makes an install feel like proxy mode or per-hostname without either being a global setting (design 03 §4.1).

type RegistryScreeners

type RegistryScreeners struct{ Registry *api.Registry }

RegistryScreeners reads assignments from the adapter registry.

func (RegistryScreeners) ScreenerFor

type Runner

type Runner struct {
	Apps       *state.Apps
	Detections *state.Detections
	Job        *detect.Job

	// Sources fetches the source detection reads, including an uploaded one
	// (R-262).
	Sources source.Sources

	// Policy is checked again here, not only at app creation. The allowlist can
	// change between the two, and a re-detection (R-022) of an app whose source
	// is no longer allowed must not clone it.
	Policy SourcePolicy

	// Install fills in everything the repository cannot answer. Without it a
	// detected spec describes the app and says nothing about where it runs,
	// which is a spec that cannot be planned — the gap that made detection
	// produce proposals nobody could deploy.
	Install Installation

	// Ports assigns a host port in port-mode routing. Only consulted when the
	// routing adapter's default mode is `port`, which the loopback adapter that
	// ships as the laptop default is.
	Ports PortAllocator

	// PortRange bounds that allocation.
	PortRangeStart int
	PortRangeEnd   int

	// Screeners says which AI adapter performs each function, and on which
	// model (R-259). Nil is the ordinary case: an install with no AI adapter
	// configured is not a degraded install, because everything the auction
	// produced is there either way (R-106, R-335).
	Screeners Screeners

	// Screener, when Screeners is nil, performs every function under
	// ScreenerRef. For an embedding with one adapter and nothing to assign.
	Screener     screening.Screener
	ScreenerRef  string
	ScreenPolicy ScreenPolicy

	// Auditor records that a repository's contents left the host (R-337).
	Auditor Auditor

	// Screening budget (R-339). Zero means the package default.
	ScreenMaxFiles int
	ScreenMaxBytes int64
	ScreenTimeout  time.Duration

	// Clock stamps conversation turns. Nil is the system clock.
	Clock clock.Clock

	// Scanner scores the checkout while it is still on disk (R-312).
	//
	// Detection is the first and, for a while, the only moment Pando holds an
	// app's source: an app that has been added and not yet deployed has no
	// image to look at, and waiting for one means the first thing anybody sees
	// about a new app is "not scanned yet". A committed key or a vulnerable
	// lockfile is exactly what somebody wants to know *before* deciding to
	// deploy it.
	//
	// Optional, and never fatal. An installation with no scanner detects
	// exactly as before, and a scanner that fails does not fail a detection —
	// the proposal is the thing being produced here.
	Scanner SourceScanner

	// Builder builds what the app would deploy, so the plan can show the
	// score of the image that will run (R-311). Nil builds nothing: the plan
	// shows the source's findings and waits for the deploy to score the
	// image.
	Builder ImageBuilder

	// NewBuildTag names a detection's build. Nil is a time-based name.
	NewBuildTag func() string

	// Background runs a rebuild after an answer (Rebuild). Nil is a
	// goroutine.
	Background func(func())

	// Egress resolves the rules the trial run is held to (R-097): the app's
	// own, from its current spec where it has one, against host policy. Nil
	// restricts nothing, and is for tests and embeddings with no policy.
	Egress TrialEgresser
}

Runner detects for an app and stores the proposal.

func (*Runner) Built added in v0.5.0

func (r *Runner) Built(ctx context.Context, appID string) (*detect.Built, error)

Built is what detection built for an app, nil when it built nothing: what a deploy of the same build inputs runs instead of building (deploy DetectionBuildFinder).

func (*Runner) Check

func (r *Runner) Check(ctx context.Context, appID string) error

Check reports whether detection may run for an app, without starting it: the app exists, has a source, and the source is allowed (R-092).

Separate so a caller that runs detection in the background can refuse at once, and write nothing, when it would be refused anyway.

func (*Runner) Detect

func (r *Runner) Detect(ctx context.Context, appID string) (state.Detection, error)

Detect runs detection for an app and records the result.

The allowlist is checked before anything touches disk, and the assertion in Sequence A is exact: a blocked source produces zero disk writes, because git clone is never invoked. That is why this check is here and not inside source.Fetch — a check that runs after the call has already started is not the same promise.

func (*Runner) Rebuild added in v0.5.0

func (r *Runner) Rebuild(ctx context.Context, appID string) (bool, error)

Rebuild builds a finished proposal again when what it would build has changed since it was built — an answer to a question the build waited on, a build argument, a revised plan — and scans the new image, in the background. True when it started; the detection reads as running, at the building stage, until it has finished. Accepting waits for it, so the score a person accepts is the score of what the first deploy runs.

func (*Runner) Revise

func (r *Runner) Revise(ctx context.Context, appID, message string) (state.Detection, error)

Revise asks the AI adapter to change an app's proposal as a person described, records the exchange on the proposal, and stores it.

func (*Runner) RunQueued

func (r *Runner) RunQueued(ctx context.Context, appID string) (state.Detection, error)

RunQueued runs a detection the detection queue claimed: Detect without marking it running again, which would hand it back to the queue (issue #72, O-32). The allowlist is checked again here, since it can change while a detection waits (R-092).

type ScreenPolicy

type ScreenPolicy interface {
	// AllowsScreening returns a reason when host policy forbids it, and an
	// empty string when it does not. A reason, not an error: this is not a
	// failure, it is a posture, and it is shown in the review as one.
	AllowsScreening(ctx context.Context) string
}

ScreenPolicy is host policy's veto over screening (R-336).

Its own interface rather than a method on SourcePolicy, because a nil one means "allowed" and every existing implementation of SourcePolicy would otherwise have had to grow a method saying so.

type Screeners

type Screeners interface {
	ScreenerFor(fn api.AIFunction) (s screening.Screener, ref, model string, ok bool)
}

Screeners resolves the adapter assigned an AI function (R-259).

type SourcePolicy

type SourcePolicy interface {
	AllowsSource(ctx context.Context, src spec.Source) error
	IsolationFloors(ctx context.Context) (build, runtime spec.IsolationClass, err error)
}

SourcePolicy is the source allowlist check (R-092), plus the isolation floors a detected spec inherits (R-024, R-114).

type SourceScanner

type SourceScanner interface {
	// Scans reports whether there is a scanner to scan with.
	Scans() bool

	// ScanSource scans and records in one call.
	ScanSource(ctx context.Context, req api.ScanRequest)

	// ScanPart scans one part, the source or the image, recording nothing;
	// RecordParts records the parts as one scan of both (R-311).
	ScanPart(ctx context.Context, req api.ScanRequest) (api.ScanResult, error)
	RecordParts(ctx context.Context, req api.ScanRequest, parts []api.ScanResult, failed error)
}

SourceScanner scans what detection read: the checkout, and the image the proposal runs — a published one, or the one detection built. Narrow, so detection cannot reach into the rest of the security service.

type TrialEgresser added in v0.14.0

type TrialEgresser interface {
	TrialEgress(ctx context.Context, s *spec.AppSpec) (api.EgressRules, error)
}

TrialEgresser resolves the egress rules a trial of an app runs under. The planner is one; nil s is an app with no spec yet.

Jump to

Keyboard shortcuts

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