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

packages

package
v0.29.6 Latest Latest
Warning

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

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

Documentation

Overview

Package packages is the offline analysis that decides whether a tree is a deployable MemQL package, and what deploying it would do.

Nothing becomes a package by claiming to be one (design D1). Every deploy runs this analysis FIRST, offline, with no cluster and no side effects, and either produces a report a person can read at the confirm gate or refuses with a stable machine-readable code they can act on. The pipeline (integrations/packages) calls it; the OS renders what it returns.

The offline half is the point. "This DSL would refuse boot" is an analysis refusal here (D12), never a crashlooping node discovered three stages later -- so the gates this package runs are literally the gates strict boot runs, reached through the memqllint machinery rather than reimplemented.

Index

Constants

View Source
const (
	KindSPA        = "spa"
	KindStatic     = "static"
	KindStorefront = "shopify_storefront"
)

Deployable kinds, mirroring v1:platform:site.kind exactly. The manifest cannot offer a kind the site row cannot hold, so this list is not independently maintained -- it is that enum, and TestManifestKindsMatchSiteConcept pins the two together.

View Source
const (
	DefaultBuildCommand = "npm ci && npm run build"
	DefaultBuildOutput  = "dist"
)

DefaultBuildCommand and DefaultBuildOutput are the D4 defaults.

View Source
const (
	ResolutionTailFallback = "fallback"
	ResolutionTailNotFound = "not_found"
)

Resolution tails a manifest may name, mirroring v1:platform:site.resolutionTail exactly. The manifest cannot offer a tail the site row cannot hold.

View Source
const (
	StatusAnalyzing       = "analyzing"
	StatusAwaitingConfirm = "awaiting_confirm"
	StatusBuilding        = "building"
	// STATUS SPELLING, and it is not cosmetic. The stage is "stage" in the D6
	// law and every other status here is a plain gerund, so this wants to be
	// "staging" -- and it cannot be. TestNoEnvironmentBranchingInEngineCode
	// fails the build on engine code naming a deployment tier, matching the
	// COMPLETE literal "staging" case-insensitively, and its exemption map is
	// EMPTY by design: an entry there claims a component's JOB is telling
	// deployments apart, which would be false here.
	//
	// So the value says which of several things is being staged, which it
	// should have said anyway -- and the suffix is exactly what takes it out
	// of the gate's match (the gate's own comment names "deploy_staging" as
	// the shape that does not trip it). Do not "tidy" this back to "staging";
	// the build will refuse it, in a file that has nothing to do with deploys.
	StatusStagingDsl = "staging_dsl"
	StatusRolling    = "rolling"
	StatusPublishing = "publishing"
	StatusSucceeded  = "succeeded"
	StatusRefused    = "refused"
	StatusFailed     = "failed"
	// StatusAbandoned is the sweep's (epic memql#4900, task memql#4902): a run
	// whose node stopped saying it was alive. Terminal, and deliberately NOT a
	// flavour of failed -- see sweep.go.
	StatusAbandoned = "abandoned"
	// StatusCancelled is a PERSON's stop (epic memql#4937, D3). Terminal, and
	// deliberately not a flavour of failed for the same reason StatusAbandoned
	// is not -- nothing broke, nothing was published, and the D6 order
	// guarantees every site is still serving what it was serving.
	//
	// The distinction between these two terminal words is WHO DECIDED:
	// abandoned is a loss this cluster OBSERVED, cancelled is a choice
	// somebody MADE. Collapsing them would report a person's own click back to
	// them as a fault.
	StatusCancelled = "cancelled"
)

Deployment statuses, mirroring the enum on v1:platform:packageDeployment.

View Source
const (
	ProbeReasonOK                    = "ok"
	ProbeReasonNotFoundOrPrivate     = "not_found_or_private"
	ProbeReasonCredentialCannotSeeIt = "credential_cannot_see_it"
	ProbeReasonCredentialNotFound    = CodeCredentialNotFound
	ProbeReasonCredentialRevoked     = CodeCredentialRevoked
	ProbeReasonSourceHostUnsupported = CodeSourceHostUnsupported
	ProbeReasonRateLimited           = "rate_limited"

	// The two reasons a GRANT can produce and a pasted token cannot (epic
	// memql#4912, D). Under a token, 401 and 404 are both
	// credential_cannot_see_it because the cluster cannot tell them apart;
	// under a grant it can, and each names a different repair.
	ProbeReasonReconnectRequired      = CodeReconnectRequired
	ProbeReasonRepositoryNotInstalled = CodeRepositoryNotInstalled
)

The probe's reasons (design D11). Three of them ARE refusal codes, spelled as the same string on purpose: the Source stop keys one sentence per condition, and a credential that does not resolve is the same condition whether the probe or the fetch found it.

View Source
const (

	// CodeManifestMissing: no memql-package.yaml at the tree root.
	CodeManifestMissing = "package_manifest_missing"
	// CodeManifestInvalid: unparseable, an unknown key, a missing name, a
	// formatVersion this engine does not know, or a deployable name that is
	// empty or repeated. An unknown version REFUSES rather than best-effort
	// parsing: a format that grew a field whose absence changes behaviour
	// would otherwise deploy something the author did not describe. An
	// unknown KEY refuses for the same reason one step down -- `deployabels:`
	// parses fine and describes a package with nothing in it, which would
	// deploy successfully and do nothing.
	//
	// The name rules live under this code rather than under codes of their
	// own because they are manifest VALIDITY, and section E's catalogue is
	// closed: a rule that arrives without a code in the design gets the
	// nearest catalogued one, never a new string invented at the call site.
	CodeManifestInvalid = "package_manifest_invalid"

	// CodeDeployablePathMissing: a declared path does not exist, or is not a
	// directory, inside the tree.
	CodeDeployablePathMissing = "deployable_path_missing"
	// CodeDeployableKindUnknown: kind is not one of the three live values.
	CodeDeployableKindUnknown = "deployable_kind_unknown"
	// CodeDeployableBindingMissing: a shopify_storefront without its binding.
	//
	// THE MANIFEST'S problem, and only that. This code used to be raised from
	// the publish stage as well, for an app with no hostname -- two conditions
	// with different repairs under one name, so the OS could neither name the
	// failure nor send a person to the right stop. It said "A storefront has no
	// store to talk to" for an app that simply had no address yet.
	CodeDeployableBindingMissing = "deployable_binding_missing"
	// CodeDeployableStoreUnknown: the manifest names a store this cluster has
	// no row for, one this caller may not read, or one they may read and not
	// attach (epic memql#5530; Connect Shopify, D3 and D5).
	//
	// DELIBERATELY ONE CODE FOR ALL THREE. Telling a caller which it was would
	// answer "does a store with this domain exist on this cluster" for
	// somebody who may not read stores, and that is the question the store
	// tier exists to refuse. The repair is the same sentence either way:
	// connect a store on the deployable's Store panel.
	//
	// A NOTE, NEVER FATAL (D5). An unbound storefront is placed as a draft
	// with no store, which may go live for design review; a bound one keeps
	// the store it has. Commerce requires a connection.
	//
	// IT IS RAISED AT PUBLISH, NOT AT ANALYSIS, and that is the split this
	// code records: whether a manifest names a store is a manifest fact
	// (CodeDeployableBindingMissing, decided offline), and whether THIS
	// cluster has that store is a cluster read, which Analyze does not make.
	CodeDeployableStoreUnknown = "deployable_store_unknown"
	// CodeDeployableHostnameUnchosen: an app the manifest declares that has
	// never been deployed and was given no hostname in this run's placements.
	//
	// A PLACEMENT problem rather than a manifest one, which is why it is its
	// own code: the repair is to choose an address (or to skip the app), and
	// the surface that does that is Where-it-lives. Split out of
	// CodeDeployableBindingMissing, whose repair is editing the tree.
	CodeDeployableHostnameUnchosen = "deployable_hostname_unchosen"

	// CodeDslDomainReserved: a discovered dsl/<domain>/ collides with a core
	// engine namespace. The engine's own mount SKIPS such a directory
	// silently (dsl.MountOverlayDomains) -- correct at boot, where the
	// embedded tree must win, and wrong here, where the author believes they
	// shipped that domain. Analysis refuses instead.
	CodeDslDomainReserved = "dsl_domain_reserved"
	// CodeDslRefusesBoot: the package's DSL does not survive the Init-grade
	// gates strict boot runs. Carries the construct-level errors boot would
	// print.
	CodeDslRefusesBoot = "dsl_refuses_boot"

	// CodeSourceTooLarge: the snapshot exceeds the publisher-grade limits.
	CodeSourceTooLarge = "source_too_large"
	// CodeBundlePathInvalid: a zip entry escaping the tree root, absolute, or
	// otherwise not a valid path. Shares its spelling with
	// sitePublishFromArtifact's code for the same condition on purpose: it is
	// the same refusal about the same kind of archive, and one vocabulary
	// across both is what lets the OS render one sentence.
	CodeBundlePathInvalid = "bundle_path_invalid"
	// CodeSourceUnreadable: the fetched snapshot is not an archive this
	// engine can open at all. Spelled in the source_* family beside
	// source_too_large because it is about the SOURCE snapshot rather than a
	// site bundle -- sitePublishFromArtifact's bundle_unreadable is the same
	// condition one layer down, on a different object.
	CodeSourceUnreadable = "source_unreadable"

	// CodeCredentialNotFound: the package names a credential its OWNER
	// cannot read. Resolution runs under the package owner's actor through
	// an owner-scoped query, so "does not exist" and "belongs to somebody
	// else" are the SAME zero rows -- and the sentence must not claim to
	// know which. A package naming another person's credential is refused
	// by name, before any request leaves the cluster.
	CodeCredentialNotFound = "credential_not_found"
	// CodeCredentialRevoked: the credential resolves and is `revoked`. The
	// row stays as history; every source fetching under it refuses here
	// until it is switched to another credential.
	CodeCredentialRevoked = "credential_revoked"
	// CodeSourceHostUnsupported: a credential (or a source) names a host this
	// cluster does not fetch from. github.com is the only host today, and
	// the alternative is the other source form -- a zip of the same tree.
	// Raised by normalizeCredentialHost and parseGitHubRepo alike, and
	// answered by the probe as a typed REASON rather than thrown, so the
	// Source stop renders one repair for the condition however it was
	// reached.
	CodeSourceHostUnsupported = "source_host_unsupported"

	// CodeReconnectRequired: GitHub refused the grant itself -- the user
	// token expired and the refresh token is spent or revoked, or the
	// person revoked the authorization at GitHub. It must NEVER be read
	// as credential_cannot_see_it or as "private, or not there": under a
	// token those three are one 404 and the cluster cannot tell them
	// apart, but under a grant a 401 on the authorization endpoint is a
	// different fact with a different repair -- reconnect, which is one
	// click and no typing.
	CodeReconnectRequired = "reconnect_required"
	// CodeRepositoryNotInstalled: the grant is good, the person can see
	// the repository, and the App is not installed ON it. The repair is
	// an installation link rather than another credential, and answering
	// it as a 404 would send somebody hunting for a permission problem
	// that does not exist.
	CodeRepositoryNotInstalled = "repository_not_installed"
	// CodeInstallationPending: an installation is waiting for an
	// organization owner's approval. Named BY ORGANISATION, because the
	// repair belongs to somebody else and the person's only useful next
	// step is knowing whom to ask. Distinct from
	// repository_not_installed: there the person can act, here they
	// cannot.
	CodeInstallationPending = "installation_pending"
	// CodeGithubAppNotConfigured: this cluster has no GitHub App -- the
	// six MEMQL_GITHUB_APP_* values are absent -- so Connect is not
	// offered at all and the pasted token is the whole Source stop. An
	// operator's condition, not a person's, and the sentence says so
	// rather than implying somebody typed something wrong.
	CodeGithubAppNotConfigured = "github_app_not_configured"

	// CodeConnectStateInvalid: the connect state was expired, replayed,
	// or never issued. Consumed exactly once under a compare-and-swap,
	// so the SECOND click on a link is this code and not a second grant.
	CodeConnectStateInvalid = "connect_state_invalid"

	// CodeGithubAppManagedByEnvironment: the deployment set
	// MEMQL_GITHUB_APP_*, so the app is the operator's. The environment
	// outranks stored rows everywhere they are read, so a registration made
	// from the product would be written and then ignored -- refused instead,
	// naming who does own it.
	CodeGithubAppManagedByEnvironment = "github_app_managed_by_environment"
	// CodeGithubAppSetupForbidden: the caller is not a cluster owner, or
	// stopped being one between beginning a registration and GitHub sending
	// them back. The app is the deployment's -- every person's grant is made
	// against it -- so registering or removing one is an owner's act.
	CodeGithubAppSetupForbidden = "github_app_setup_forbidden"
	// CodeGithubAppSetupInvalid: the organization named is not something
	// GitHub would accept as an account login. Refused before anything is
	// written, because it becomes a path segment of the URL a form posts to.
	CodeGithubAppSetupInvalid = "github_app_setup_invalid"
	// CodeGithubAppSetupStateInvalid: the setup state was expired, replayed,
	// never issued, or belongs to the Connect flow. connect_state_invalid's
	// sibling, kept separate because the repair is different: start setting
	// GitHub up again, rather than pressing Connect again.
	CodeGithubAppSetupStateInvalid = "github_app_setup_state_invalid"
	// CodeGithubAppSetupFailed: GitHub sent the owner back and the app could
	// not be kept -- the one-time code was refused, the app came back asking
	// for more than this cluster's manifest did, the cluster could not name
	// its own domain, or the six values could not be stored. Nothing is
	// half-registered: the stored values are all six or none.
	CodeGithubAppSetupFailed = "github_app_setup_failed"

	// CodeExchangeFailed: the provider did not trade the approval for a token
	// -- it refused the code, could not be reached, or answered without one.
	// Nothing was written, and the repair is to connect again. GitHub Connect
	// answers it too, for the same fault.
	CodeExchangeFailed = "exchange_failed"
	// CodeSignatureInvalid: the callback did not carry Shopify's signature
	// for the shop Connect was begun for -- the HMAC failed under the app's
	// client secret, or `shop` named another store. Checked BEFORE the state
	// is spent, so nothing was: the Connect the person began can still finish.
	// Usually the saved client secret is not the app's.
	CodeSignatureInvalid = "signature_invalid"
	// CodePermissionLost: the person who began no longer may finish. Judged at
	// the callback under their role then, not when they pressed Connect: their
	// account is gone or inactive, or they no longer hold the store part, read
	// the store, or write the storefront.
	CodePermissionLost = "permission_lost"
	// CodeScopesMissing: the approval granted less than the storefront needs
	// -- a Storefront API scope is missing, so its catalog or cart would fail.
	// The app's required scopes are what to fix, in Shopify. A missing Admin
	// read scope is shown, never refused.
	CodeScopesMissing = "scopes_missing"
	// CodeStorefrontTokenFailed: the store IS connected and the Storefront
	// token could not be minted (Shopify allows 100 per app). Pressing Connect
	// again retries only the mint; pasting a token is the other repair.
	CodeStorefrontTokenFailed = "storefront_token_failed"

	// CodeSiteNotWritable: the storefront named is not one the caller may
	// write -- or not one they can read at all -- so Connect acts on nothing.
	// The store is resolved from the site on the server, never from the
	// browser, and this is the first question that resolution asks.
	CodeSiteNotWritable = "site_not_writable"
	// CodeNotAStorefront: the site is not a shopify_storefront, so it has no
	// store to connect.
	CodeNotAStorefront = "not_a_storefront"
	// CodeStoreNotNamed: no run that published this storefront named a store
	// (its deployable's binding.store), or the name is not a myshopify.com
	// domain. Deploying the storefront with its manifest naming the store is
	// the repair.
	CodeStoreNotNamed = "store_not_named"
	// CodeStoreRedacted: the store's data was purged on Shopify's shop/redact,
	// and a purged store is not connected again.
	CodeStoreRedacted = "store_redacted"
	// CodeAppCredentialsInvalid: a Save was missing the app's client ID or its
	// client secret, or carried one far too long to be Shopify's.
	CodeAppCredentialsInvalid = "app_credentials_invalid"
	// CodeSecretNameAmbiguous: more than one row carries the name a credential
	// is kept under, and the resolver reads the first, so no write could say
	// which one it changed. Nothing was written; a cluster owner removes the
	// duplicate.
	CodeSecretNameAmbiguous = "secret_name_ambiguous"
	// CodeStoreNotConnected: a Storefront token needs a store row to point
	// from, and this store has none yet. Connect first.
	CodeStoreNotConnected = "store_not_connected"
	// CodeStorefrontTokenRequired: an empty token clears the store's, and a
	// live storefront is bound to it and would stop serving its catalog.
	CodeStorefrontTokenRequired = "storefront_token_required"
	// CodeStorefrontTokenInvalid: the pasted token did not answer the store's
	// Storefront API. Nothing was kept.
	CodeStorefrontTokenInvalid = "storefront_token_invalid"
	// CodeShopifyAppNotSaved: there is no app to connect with -- nothing is
	// pending, and the store has no current app with its webhook secret. The
	// app's client ID and secret are saved on the Store panel first (D7).
	CodeShopifyAppNotSaved = "shopify_app_not_saved"

	// CodeGoPackNotDeployable: a bff/ with a go.mod. Reported per-half and
	// NON-fatal: the rest of the package deploys, and the report says where
	// Go delivery actually happens today. Full Go delivery is its own epic.
	CodeGoPackNotDeployable = "go_pack_not_deployable"
	// CodeDeployableTargetNotOffered (epic memql#4885, D9): a declared kind
	// the target model KNOWS and does not OFFER -- ios, android, macos
	// (KnownUnofferedKinds). Scoped to the app, fatal to that app and NOT to
	// the package, reported exactly as go_pack_not_deployable is: the build
	// and publish stages skip the app and the rest deploys. Distinct from
	// deployable_kind_unknown, which stays fatal, because the two say
	// opposite things to an author -- "not yet" versus "not a thing".
	CodeDeployableTargetNotOffered = "deployable_target_not_offered"
	// CodeDslLanguageLineUnread (epic memql#5356): a memql.toml at the root of
	// the package's dsl/ directory. No mount reads a root file -- each domain
	// declares its language line in its own dsl/<domain>/memql.toml -- so boot
	// ignores it and the package deploys. Reported so the author learns the
	// file governs nothing; never fatal, and never under dsl_refuses_boot,
	// because it refuses nothing at boot.
	CodeDslLanguageLineUnread = "dsl_language_line_unread"

	// CodeDeployableAccountRefused: updateSiteAccount was refused for the
	// site the deploy just created.
	CodeDeployableAccountRefused = "deployable_account_refused"
	// CodeDeployableDomainRefused: customDomainAdd was refused -- a hostname
	// under the cluster's own domain, a collision, or the per-site cap.
	CodeDeployableDomainRefused = "deployable_domain_refused"

	// CodeDeployableBuildFailed: the build command exited non-zero, or the
	// surface could not read its output back. Carries the log tail. Spelled
	// as a constant now that four codes share the family; the string is
	// unchanged, because it already crossed the wire to the OS.
	CodeDeployableBuildFailed = "deployable_build_failed"
	// CodeDeployableBuildTimeout: the command outlived its timeout and was
	// stopped, process group and all. Distinct from a failure because the
	// repair is: make the build faster, or raise
	// MEMQL_PACKAGES_BUILD_TIMEOUT_SECONDS.
	CodeDeployableBuildTimeout = "deployable_build_timeout"
	// CodeNoWorkbenchPeer: this node is configured to build on a workbench
	// node and cannot reach one. NOT a flavour of build_failed: nothing was
	// built, nothing was wrong with the package, and the fix is a cluster's
	// rather than a person's.
	CodeNoWorkbenchPeer = "no_workbench_peer"
	// CodeNoWorkerAvailable: a deployable whose target builds on the owner's
	// own Fleet, with no machine that matches. The sentence names every
	// machine considered and why each was ruled out (task memql#4904).
	CodeNoWorkerAvailable = "no_worker_available"

	// CodeDeploymentAbandoned: the sweep closed a run whose node stopped
	// answering (epic memql#4900, task memql#4902). Catalogued beside the
	// build codes because a person meets it in the same place, and stated as
	// its own because it is the one refusal in this catalogue that is about
	// the CLUSTER rather than about the package: nothing was wrong with the
	// source, nothing was published, and the repair is Retry.
	CodeDeploymentAbandoned = "deployment_abandoned"

	// CodeDeployableSkipped: an app a person chose to leave out of this run
	// (memql#4930). Recorded on the row rather than omitted, because
	// `deployables` promises one entry per manifest deployable and a missing
	// entry reads as "nothing happened". NOT fatal and NOT a failure: a
	// partial deploy is a complete run, and rollback and update detection
	// read it as "not this run" rather than as removed.
	CodeDeployableSkipped = "deployable_skipped"

	// CodeSiteNotDeletable: a delete asked of a deployable that is still
	// serving, or could. Delete is the fourth rung of the D10 lifecycle and
	// only ever runs from `archived` or `draft` -- the two states nothing is
	// served from. Its sentence names the next step, because "pause it first"
	// is the whole answer.
	CodeSiteNotDeletable = "site_not_deletable"

	// CodeDeleteConfirmationMismatch: the typed name did not match the site's.
	// The label under the cluster's domain or the whole hostname both confirm
	// (confirmationMatches); anything else is this. Verified SERVER-side for
	// the reason siteArchive's is -- a confirmation a client could skip is not
	// one.
	CodeDeleteConfirmationMismatch = "delete_confirmation_mismatch"

	// CodeDeactivateConfirmationMismatch: the typed name did not match the
	// app's manifest name. Deactivating a source's app asks for the APP'S
	// name rather than its hostname -- the hostname is generated and is not
	// what a person calls the thing -- and it is verified here for the same
	// reason the two above are.
	CodeDeactivateConfirmationMismatch = "deactivate_confirmation_mismatch"

	// CodeSiteSystemOwned: a lifecycle write asked of one of the cluster's own
	// surfaces. The write guard beside executeWrite refuses these whoever
	// asks; this refuses earlier so the sentence names the reason rather than
	// the guard.
	CodeSiteSystemOwned = "site_system_owned"

	// CodeDeploymentNotCancellable: a cancel asked of a run with nothing left
	// to stop -- already terminal, or at or past `staging_dsl`. From the roll
	// on there is no cancel, because a roll restarts the cluster onto staged
	// MemQL and stopping half way through is the one outcome worse than either
	// finishing or not starting.
	CodeDeploymentNotCancellable = "deployment_not_cancellable"

	// CodeDeploymentCancelled: the run a person stopped. Its own code and its
	// own terminal status, NOT a flavour of failed -- the same distinction
	// CodeDeploymentAbandoned draws, and for a sharper reason: this one names
	// a choice somebody made, and reporting it as a fault would tell them
	// their own click broke something.
	CodeDeploymentCancelled = "deployment_cancelled"

	// CodeSnapshotUnavailable: a retry named an earlier run whose stored
	// snapshot this cluster cannot read -- most often a run from before
	// snapshots were kept. The repair is an ordinary deploy, which fetches
	// the source again, and the sentence says so.
	CodeSnapshotUnavailable = "snapshot_unavailable"

	// CodeDslRequiresAuthoring (D9): raised by the pipeline at deploy start,
	// before any build or stage, and by Rollback for the same reason.
	//
	// It was dsl_requires_cluster_owner until developers were admitted. The
	// code is renamed rather than kept for compatibility because this repo is
	// pre-release and carries no shims: a code whose name says cluster owner,
	// answering a gate that admits developers, is a message that tells the
	// caller most likely to see it exactly the wrong thing.
	CodeDslRequiresAuthoring = "dsl_requires_authoring"

	// The manifest's block and the plan compiled from it.
	CodePipelineNotDeclared       = "pipeline_not_declared"
	CodePipelineStageInvalid      = "pipeline_stage_invalid"
	CodePipelineStepInvalid       = "pipeline_step_invalid"
	CodePipelineSelectInvalid     = "pipeline_select_invalid"
	CodePipelineSelectMissing     = "pipeline_select_missing"
	CodePipelineEventUnknown      = "pipeline_event_unknown"
	CodePipelineBucketUnknown     = "pipeline_bucket_unknown"
	CodePipelineServiceUnknown    = "pipeline_service_unknown"
	CodePipelineNeedUnknown       = "pipeline_need_unknown"
	CodePipelineSecretInvalid     = "pipeline_secret_invalid"
	CodePipelineSecretNotAllowed  = "pipeline_secret_not_allowed"
	CodePipelineFleetNotConsented = "pipeline_fleet_not_consented"

	// The trigger: a fork head is refused, never queued (D6).
	CodePipelineForkRefused = "pipeline_fork_refused"

	// The run.
	CodePipelineRunnerUnavailable          = "pipeline_runner_unavailable"
	CodePipelineExecutorError              = "pipeline_executor_error"
	CodePipelineExecutionUncertain         = "pipeline_execution_uncertain"
	CodePipelineSecretMissing              = "pipeline_secret_missing"
	CodePipelineDisconnected               = "pipeline_disconnected"
	CodePipelineManualRequiresDisconnected = "pipeline_manual_requires_disconnected"
	CodePipelineBranchInvalid              = "pipeline_branch_invalid"
	CodePipelineCheckPermissionMissing     = "pipeline_check_permission_missing"

	// Skips: a step the plan or the run decided not to execute.
	CodePipelineStageBlocked = "pipeline_stage_blocked"
	CodePipelineNotAffected  = "pipeline_not_affected"

	// The substrate's (epic memql#5478): where a step executes.
	CodePipelineStepTimeout         = "pipeline_step_timeout"
	CodePipelineRunCeiling          = "pipeline_run_ceiling"
	CodePipelineNoMachineForNeed    = "pipeline_no_machine_for_need"
	CodePipelineFleetDisabled       = "pipeline_fleet_disabled"
	CodePipelineJobRejected         = "pipeline_job_rejected"
	CodePipelineJobUnschedulable    = "pipeline_job_unschedulable"
	CodePipelineImagePullFailed     = "pipeline_image_pull_failed"
	CodePipelineCloneFailed         = "pipeline_clone_failed"
	CodePipelineServiceFailed       = "pipeline_service_failed"
	CodePipelineStepCancelled       = "pipeline_step_cancelled"
	CodePipelineNodeLost            = "pipeline_node_lost"
	CodePipelineArtifactTooLarge    = "pipeline_artifact_too_large"
	CodePipelineArtifactMissing     = "pipeline_artifact_missing"
	CodePipelineArtifactUnavailable = "pipeline_artifact_unavailable"
	CodePipelineLogCapped           = "pipeline_log_capped"
	CodePipelineTimingsUnreadable   = "pipeline_timings_unreadable"
	CodePipelineOutcomeTrimmed      = "pipeline_outcome_trimmed"
	CodePipelineIsolationUnenforced = "pipeline_isolation_unenforced"
	CodePipelineStepDiskExceeded    = "pipeline_step_disk_exceeded"
	CodePipelineAlreadyConnected    = "pipeline_already_connected"
)

The refusal catalogue (design section E), stable and machine-readable in the sitePublishFromArtifact tradition.

STABILITY IS THE CONTRACT. These strings cross three boundaries -- they land on packageDeployment.report, ride the wire to the OS, and are keyed there to decide which sentence renders in-surface. Renaming one silently turns a known refusal into an unknown one, which the OS renders as the raw server message rather than its own copy; that is the designed fallback for a fault nobody anticipated, not a place to land a rename.

This package owns the whole catalogue, INCLUDING the code it does not itself raise (dsl_requires_authoring). The pipeline raises that one, and it is declared here because a catalogue split across packages is a catalogue that grows a fourth ad-hoc string the first time somebody cannot find it.

View Source
const (
	CodePipelinePassedEarlier  = "pipeline_passed_earlier"
	CodePipelineNothingToRerun = "pipeline_nothing_to_rerun"
)

Re-running only what failed (epic memql#5479): a step a failed-only re-run carries over (a skip), and the refusal of one with nothing to run again.

View Source
const (
	CodePipelineChannelMissing    = "pipeline_channel_missing"
	CodePipelineChannelArchived   = "pipeline_channel_archived"
	CodePipelineChannelNotAllowed = "pipeline_channel_not_allowed"
	CodePipelineChannelInvalid    = "pipeline_channel_invalid"
	CodePipelineNotifyFailed      = "pipeline_notify_failed"
	CodePipelineNotifyUndelivered = "pipeline_notify_undelivered"
)

The notify stage (epic memql#5480): a channel it may not deliver to, and a delivery that failed or had not arrived when the stage stopped waiting. Each fails the notify step, on the pipeline run, never a deploy.

View Source
const (
	RepositoriesReasonOK                = "ok"
	RepositoriesReasonNotConfigured     = CodeGithubAppNotConfigured
	RepositoriesReasonReconnectRequired = CodeReconnectRequired
	RepositoriesReasonCredentialMissing = CodeCredentialNotFound
	RepositoriesReasonCredentialRevoked = CodeCredentialRevoked
	RepositoriesReasonRateLimited       = ProbeReasonRateLimited
)

The reasons sourceRepositories answers with. Six, all but the first shared with a refusal code, so the OS keys one sentence per condition however it was reached.

View Source
const (
	MaxSourceBytesEnv = "MEMQL_PACKAGES_MAX_SOURCE_BYTES"
	MaxFileBytesEnv   = "MEMQL_PACKAGES_MAX_FILE_BYTES"
	MaxFileCountEnv   = "MEMQL_PACKAGES_MAX_FILE_COUNT"
	// BuildTimeoutEnv bounds ONE deployable's build command (epic memql#4900).
	// A timeout rather than a total: a package of three apps is three
	// independent builds, and a shared budget would make the third one's
	// success depend on how slow the first two were.
	BuildTimeoutEnv = "MEMQL_PACKAGES_BUILD_TIMEOUT_SECONDS"
)

The operator knobs bounding a package source.

Named as constants AND read through them, rather than composed from a prefix, so a plain grep finds the read -- the lesson component/server's env.go records about the MEMQL_SERVER_* family, where a composed spelling made the variables look dead to every grep and to envscan.

View Source
const (
	DefaultMaxSourceBytes int64 = 500 * 1024 * 1024 // 500 MB expanded
	DefaultMaxFileBytes   int64 = 25 * 1024 * 1024  // 25 MB per file
	DefaultMaxFileCount   int   = 20000
)

The publisher-grade defaults (D1). They are sitePublishFromArtifact's numbers deliberately: a package source is an archive of roughly the same kind, arriving over the same kind of link, and two different answers to "how big may an uploaded tree be" is a difference somebody would have to discover by hitting one of them.

View Source
const (
	SurfacePrebuilt  = "prebuilt"
	SurfaceWorkbench = "workbench"
	SurfaceFleet     = "fleet"
)

The build surfaces a deployable can be built on -- the value that lands on the deployment row's `builtOn.surface` and the sentence the Build stop reads.

A CLOSED SET of three, and the third is not a lesser member: a package whose output is committed is BUILT, by whoever's CI produced it, and saying "prebuilt" is what makes a deploy that took four seconds legible as a fast deploy rather than as a build that did not happen.

View Source
const (
	// HeartbeatIntervalEnv tunes how often a running pipeline says it is
	// alive. Named beside the threshold because the two are a PAIR: the
	// threshold must be a multiple of this, or a healthy run gets swept.
	HeartbeatIntervalEnv = "MEMQL_PACKAGES_HEARTBEAT_SECONDS"
	// AbandonedAfterEnv is how old a heartbeat must be before the sweep
	// closes the row.
	AbandonedAfterEnv = "MEMQL_PACKAGES_ABANDONED_AFTER_SECONDS"
)
View Source
const (
	// DefaultHeartbeatSeconds is 15s, the fleet's own heartbeat cadence. A
	// deploy is minutes long, so this costs a handful of writes per run.
	DefaultHeartbeatSeconds = 15
	// DefaultAbandonedAfterSeconds is 90s: SIX heartbeats, not two.
	//
	// The margin is the point. A run that misses one heartbeat is a run whose
	// node had a slow moment -- a GC pause, a database hiccup, a saturated
	// mesh -- and closing it would turn a transient into a permanent record
	// nothing can amend. Six missed heartbeats in a row is a node that is
	// gone. The cost of waiting is ninety seconds of a rail that says
	// "building"; the cost of not waiting is a deploy declared lost while it
	// was still running, which then PUBLISHES -- because the sweep closes the
	// row and the node, still alive, carries on to a publish it can no longer
	// record.
	DefaultAbandonedAfterSeconds = 90
)
View Source
const ActiveSetPath = "packages/active.json"

ActiveSetPath is the well-known pointer document: domain -> content-addressed prefix. One small JSON object, rewritten atomically, read by the dsl-bundle init-container's fetcher mode before a node boots.

A POINTER rather than a manifest of files, and that is what makes the roll reversible: rolling back the DSL half is rewriting this one document and rolling, which is the same shape as a site rollback -- one write, pointing at bytes that still exist.

View Source
const AuditAction = "package_deploy"

AuditAction is the action string this epic writes. Named rather than inlined, because the OS and any operator query filtering the trail key on it.

View Source
const CodeSourceConnectionUnavailable = "source_connection_unavailable"
View Source
const DefaultBuildTimeoutSeconds = 900

DefaultBuildTimeoutSeconds is fifteen minutes: generous for `npm ci && npm run build` on a cold cache, and short enough that a wedged build does not hold a deployment open for an hour.

View Source
const DefaultWebhookSource = defaultWebhookSource

DefaultWebhookSource is defaultWebhookSource, exported for ONE reader: the test in app/ that pins this name to the URL the cluster's GitHub App is told to post to (component/identity/githubconnect.WebhookPath) and to the inbound policy registered for it. Three modules spell it, none can import the others downward, and a drift between them is a webhook nothing reads.

View Source
const DslRoot = "dsl"

DslRoot is where DSL domains are discovered, mirroring MEMQL_DSL_PATH's own layout exactly (D2). Discovered, never declared: a dsl/<domain>/ directory already carries everything the engine needs to know about it, which is what makes the manifest's deployables list the only thing an author maintains.

View Source
const GoPackDir = "bff"

GoPackDir is the one place a Go pack lives in a memql-project tree (D3).

View Source
const InstalledManifestPath = "/etc/memql/package/memql-package.yaml"

The optional ConfigMap is supplied by the instance repository. A projected directory (never subPath) means every replica sees subsequent GitOps updates.

View Source
const ManifestFormatVersion = 1

ManifestFormatVersion is the only format this engine reads.

The field exists so the format can grow; refusing an unknown value is what makes that growth safe. A future engine reading a `2` it understands is the intended path, and THIS engine reading a `2` refuses by name rather than parsing the subset it recognizes and deploying a package described by rules it never saw.

View Source
const ManifestName = "memql-package.yaml"

ManifestName is the one file that makes a tree a package (design D2). At the tree root, and at the ZIP root for the archive form -- the two source forms are the same tree, so they are the same rule.

View Source
const MaxBuildTimeoutSeconds = 2 * 60 * 60

MaxBuildTimeoutSeconds is the ceiling an operator may ask for, mirroring the build surface's own (workbench.MaxBuildTimeout). Two hours.

View Source
const RollTargetsEnv = "MEMQL_PACKAGES_ROLL_TARGETS"

RollTargetsEnv names the workloads a DSL roll restarts.

A LIST rather than "every Deployment", because a roll is a restart and the blast radius must be a decision somebody made rather than whatever happens to be running. Unset means the roll refuses instead of guessing: restarting the wrong set is worse than not restarting.

View Source
const WebhookSourceEnv = "MEMQL_PACKAGES_WEBHOOK_SOURCE"

WebhookSourceEnv names the inbound source segment GitHub deliveries arrive on. A cluster whose allowlist calls it something else sets this; unset means "github", which is what the runbook tells an operator to use.

Variables

View Source
var ErrNoBlobContainer = errors.New(
	"no object storage container is configured (MEMQL_AZURE_BLOB_CONTAINER is unset), " +
		"so the package pointer cannot be read. A node that boots without it serves none of " +
		"the DSL its packages deployed, and answers \"function not found\" to every call that " +
		"used to work. Set it on this container -- the dsl-packages component reads it from " +
		"the memql-storage ConfigMap")

ErrNoBlobContainer is what an unconfigured fetcher answers, and it is an ERROR rather than a third state of FetchResult (memql#4933).

It used to answer PointerAbsent -- "this cluster cannot be hosting packages, so there is nothing to fetch and nothing wrong" -- which is a reasonable reading of an engine process and the wrong one for an init container. The component that runs this ships `envFrom: memql-secrets` and MEMQL_AZURE_BLOB_CONTAINER is not in that Secret anywhere in this repo, so the fetcher saw an empty container name on every instance that applied the component as shipped, exited 0, and the node booted healthy with no package DSL. That is exactly the outcome the exit-code contract's `1` exists to prevent, reached through its `0`.

"Not configured" and "configured, and nothing is deployed" are different answers and only one of them is ordinary.

View Source
var KnownUnofferedKinds = map[string]UnofferedTarget{
	"ios":     {Display: "iOS", Address: "a bundle id and an App Store Connect app"},
	"android": {Display: "Android", Address: "an application id and a Play listing"},
	"macos":   {Display: "macOS", Address: "a bundle id, and a notarized disk image or the Mac App Store"},
}

KnownUnofferedKinds are the manifest kinds the engine KNOWS and does not OFFER. They are deliberately NOT in v1:platform:site.kind (TestSiteKindEnumIsExactlyThreeValues): a site is a hostname the edge resolves, and a store listing is not one, so an ios site row would be a value that never resolves. The three live here instead so the analysis can tell the truth about them -- "not offered yet", scoped to the app and not fatal to the package -- rather than filing `ios` beside `banana` as a kind nobody has heard of, which would tell an author their roadmap item is a typo.

Functions

func AbandonedAfter added in v0.20.13

func AbandonedAfter() time.Duration

AbandonedAfter is how stale a heartbeat must be for the sweep to act.

CLAMPED to at least three heartbeats. An operator who sets the threshold below the cadence has configured a cluster that sweeps its own healthy deploys, and the failure is invisible from the value alone: every run would simply end `abandoned` a minute in, looking like a broken build surface.

func BuildSurfaceFor added in v0.20.13

func BuildSurfaceFor(kind string) string

BuildSurfaceFor reports the surface a kind builds on, or "" for a kind no target claims.

func BuildTimeoutSeconds added in v0.20.13

func BuildTimeoutSeconds() int

BuildTimeoutSeconds resolves the per-deployable build timeout.

Same fallback posture as the caps above -- set-but-unparseable or non-positive falls back to the DEFAULT rather than to "no timeout" -- and for a sharper reason: an unbounded build is a workbench replica held by somebody else's script forever.

func CancellableStage added in v0.20.22

func CancellableStage(status string) bool

CancellableStage reports whether a run at this status can still be stopped (epic memql#4937, D3).

THE LINE IS THE ROLL, and it is drawn by what is unsafe rather than by what is slow. `staging_dsl` writes the package's MemQL into storage and `rolling` restarts this cluster onto it; a run stopped between those two leaves the cluster half-rolled, which is the one outcome worse than either finishing or not starting. Everything before -- the fetch, the analysis, the confirm gate a person is already sitting at, and the build, where the minutes actually go -- has published nothing, so abandoning it leaves every site serving exactly what it was serving.

A CLOSED LIST OF WHAT IS ALLOWED, rather than an exclusion of what is not: a status this function has never heard of is NOT cancellable, so a stage added later is safe by default and has to opt in deliberately.

func ExtractTarGz

func ExtractTarGz(r io.Reader, destDir string, limits Limits) (string, error)

ExtractTarGz expands a gzip'd tar -- what the GitHub tarball API returns -- into destDir under the same limits, and reports the directory holding the package tree.

GitHub wraps the whole repository in ONE synthesized top-level directory named `<owner>-<repo>-<sha>`, which is not part of the tree the author wrote. Stripping it is what makes D1's "a zip of exactly the same tree" literally true: after this, the repo form and the zip form are the same FS with the manifest at the root, and every rule below applies once.

The strip is CONDITIONAL on there being exactly one top-level directory and no top-level files. A tarball that is already root-relative is left alone rather than having its first directory eaten.

func FleetBuildLabels added in v0.20.13

func FleetBuildLabels(kind string) map[string]string

FleetBuildLabels are what a build asks of a machine.

Derived from the deployable's KIND rather than from an environment hint, because unlike the agent tool loop -- which learns what it needs from a refusal it already got -- the pipeline knows before it starts: an iOS build needs macOS because iOS builds need macOS, and that is a property of the target rather than a discovery.

EXACTLY MATCHED, because that is how the Fleet router matches: there is no "any value" form, so `os=darwin` means a machine whose cockpit reported exactly that.

func HeartbeatInterval added in v0.20.13

func HeartbeatInterval() time.Duration

HeartbeatInterval is how often a running pipeline writes heartbeatAt.

func IsTerminal

func IsTerminal(status string) bool

IsTerminal reports whether a deployment status accepts no further writes.

func OfferedKindsBuildOnTheWorkbench added in v0.20.13

func OfferedKindsBuildOnTheWorkbench() bool

OfferedKindsBuildOnTheWorkbench reports whether every kind this cluster actually offers builds in-cluster.

Exported for the gate that pins it (build_surface_test.go): the acceptance for the fleet route is that NO offered kind reaches it, and a boolean a test can read is what makes that assertion about the table rather than about a list the test would otherwise have to keep in step by hand.

func OpenZip

func OpenZip(ra io.ReaderAt, size int64, limits Limits) (fs.FS, error)

OpenZip validates a zip source and returns it as an fs.FS over the tree.

Nothing is expanded to disk: analysis reads through the returned FS, which is what makes re-analysing an already-stored snapshot cheap (design section I -- "an analysis pass is repeatable from the stored snapshot without refetching"). The build stage, which genuinely needs files, extracts separately.

func PlanFingerprint added in v0.20.13

func PlanFingerprint(rep *Report) string

PlanFingerprint is what a person said yes to, as one comparable value.

SORTED and explicit, never a JSON dump of the report: a dump would fold in sourceVersion (which always changes), problem messages (which carry timestamps), and every field a future epic adds -- so it would answer "the plan changed" every time, and the switch would silently never fire.

func RefusalCode

func RefusalCode(err error) string

RefusalCode reports the stable code carried by err, or "" when err is not a Refusal. Exposed so callers -- and tests -- key on the NAME rather than on the prose, which is what makes the prose safe to improve.

func ResolutionTailIsSet added in v0.22.9

func ResolutionTailIsSet(tail string) bool

ResolutionTailIsSet reports whether tail names a tail at all, as opposed to leaving the decision to the kind. The predicate exists so the one caller that has to decide whether to write the argument reads as what it means, rather than as a bare `!= ""` that a later reader could invert.

func ValidKind

func ValidKind(kind string) bool

ValidKind reports whether kind is one of the three live site kinds.

func ValidResolutionTail added in v0.22.9

func ValidResolutionTail(tail string) bool

ValidResolutionTail reports whether tail is one a site row can hold. THE EMPTY STRING IS VALID and is the ordinary state: it means the kind decides, which is what every manifest written before memql#5535 says by saying nothing.

Types

type Actor

type Actor struct {
	UserId       string
	MayDeployDsl bool
}

Actor is what the pipeline knows about who asked.

MayDeployDsl is carried as a resolved boolean rather than re-derived inside each stage: the D9 gate has to answer it at deploy START, before any build or stage, and a gate that re-asked later could pass a package whose first stages had already run.

It is resolved ONCE, at the actorFromContext seam in result.go, and that is forced rather than stylistic -- this file and rollback.go import no component/auth, because Deps exists to keep the D6 state machine testable with no cluster behind it.

== auth.CanAuthor: owner or developer, and admin deliberately NOT (#1529 section 4). The zero value is the fail-closed direction, which is what makes the no-access-context branch in result.go correct with nothing further to write.

type ArtifactProbeResult added in v0.20.13

type ArtifactProbeResult struct {
	// IsPackage: memql-package.yaml sits at the root of the zip. The package
	// path takes it from here, and the analysis decides the rest.
	IsPackage bool `json:"isPackage"`
	// IsBuiltSite: index.html sits at the root and there is NO manifest. The
	// hand-made path takes it and asks for the kind. A manifest beside an
	// index.html is a package -- the manifest is the stronger claim.
	IsBuiltSite bool `json:"isBuiltSite"`
	// FileCount and TotalBytes are the What-it-is stop's two facts for a
	// built site, counted by walking the opened tree.
	FileCount  int   `json:"fileCount"`
	TotalBytes int64 `json:"totalBytes"`
}

ArtifactProbeResult is what artifactProbe answers, and the wire shape the Source stop reads to choose the package path or the hand-made one.

func ProbeArtifact added in v0.20.13

func ProbeArtifact(ctx context.Context, d *Deps, artifactId string) (ArtifactProbeResult, error)

ProbeArtifact opens the caller's zip and reports what kind of tree it is.

Through the same fetch the deploy uses -- the artifact's bytes under the CALLER's actor, expanded by OpenZip under the packages limits -- so a zip the deploy would refuse (too large, an entry escaping the root, not a zip) is refused here, by the same code, before a person commits to it. Nothing is written: the artifact stays exactly the Library row it was.

type AssetImporter added in v0.22.10

type AssetImporter interface {
	Import(context.Context, string, RepoSource, ManifestAsset, Limits) ([]byte, error)
}

AssetImporter verifies and durably caches bytes in the cluster's blob store. Its scope is a package whose authorization the pipeline has already checked.

type Auditor

type Auditor interface {
	Deploy(ctx context.Context, ev DeployAuditEvent)
}

Auditor records the outcome of a deploy on v1:identity:auditEvent.

SUCCESS AND REFUSAL BOTH (design section E), which is the property worth stating: an audit log that only records failures answers "what went wrong" and not "who deployed this", and deploying somebody's code onto a hostname this cluster serves is exactly the second question.

type BuildResult

type BuildResult struct {
	// Bundle is the flat path -> content map the publisher wants.
	Bundle edge.Bundle
	// LogTail is a bounded tail of the build output, captured whether the
	// build succeeded or failed. On failure it is what answers "why did my
	// build fail" inside the OS rather than in a pod log nobody can reach.
	LogTail string
	// BuiltOn is the surface and node this build ran on. Populated even on a
	// FAILURE -- "the build failed on workbench-7" is a more useful sentence
	// than "the build failed", and on a two-replica workbench it is the
	// difference between a bad build script and one sick replica.
	BuiltOn BuiltOn
}

BuildResult is one deployable's built output.

type BuildRun added in v0.20.13

type BuildRun struct {
	DeploymentId string
	OwnerUserId  string
	// PinnedNodeId is the node the previous deployable of this run built on,
	// or empty for the first. A PREFERENCE and never a requirement: a replica
	// that has gone away since must not strand the second app of a package.
	PinnedNodeId string
	// Limits are the pipeline's own, passed in rather than re-read, so the
	// build surface can never enforce a bound the analysis did not.
	Limits Limits
}

BuildRun is what a build needs to know about the RUN it belongs to, beside the deployable it is building.

It exists because the three things here are facts about the attempt rather than about the app: the deployment keys the build's directory and is the subject its log lines carry, the owner is who the build is being run for, and the pin is the workbench replica an earlier deployable of this same run already built on. Passing them as a struct rather than as three arguments is what lets a fourth arrive without every implementation changing shape.

type Builder

type Builder interface {
	Build(ctx context.Context, run BuildRun, snapshot *SourceSnapshot, dep DeployableReport) (BuildResult, error)
}

Builder runs one deployable's build. The production implementation is the workbench (D4): sandboxed, resource-capped, and with NO cluster credentials in its environment -- a package's build script is somebody else's code, and it runs where that is assumed rather than hoped.

func NewFleetBuilder added in v0.20.13

func NewFleetBuilder(fleet FleetDispatcher) Builder

NewFleetBuilder wires the route. Nil dispatcher yields nil, so a node with no Fleet keeps `Deps.FleetBuilder` nil and a target asking for the fleet is refused by the build stage's own "no build surface configured" message.

func NewWorkbenchBuilder added in v0.20.13

func NewWorkbenchBuilder(runner workbenchRunner, logger *slog.Logger) Builder

NewWorkbenchBuilder wires the seam. Returns nil when there is no workbench to run on, which is what keeps `Deps.Builder == nil` -- and therefore the existing "this cluster has no build surface configured" refusal -- the answer on a node that cannot reach one, rather than a nil-pointer three stages later.

type BuiltOn added in v0.20.13

type BuiltOn struct {
	Surface string `json:"surface"`
	NodeId  string `json:"nodeId,omitempty"`
}

BuiltOn is where a deployable was built: the surface, and the node that ran it.

The NODE is on the row because it is the only durable answer to "where did this build happen" once the directory it happened in is gone -- and it is gone by the time the row closes, deliberately. Empty for the prebuilt path, which ran nowhere.

type CredentialResolver added in v0.20.13

type CredentialResolver func(ctx context.Context, credentialId, ownerUserId string) (ResolvedCredential, error)

CredentialResolver unseals a package's credential at the moment of a fetch.

The whole D10 shape is in the signature: the caller holds a credential NAME and the package's OWNER, asks for the value at fetch time, and never stores what comes back. Resolution runs under the OWNER's actor rather than the caller's, so a cluster owner deploying somebody's package fetches under that package's own credential (correct: they are deploying that package), and a package naming another person's credential resolves zero rows and is refused by name.

type DeclaredDeployable added in v0.20.24

type DeclaredDeployable struct {
	DisplayName string `json:"displayName,omitempty"`
	Name        string `json:"name"`
	Kind        string `json:"kind"`
}

DeployableReport is one declared surface's analysis. DeclaredDeployable is the CATALOGUE entry for one manifest deployable, as recorded on the package row (`v1:platform:package.declares`).

Deliberately two fields where DeployableReport has eight. This answers one question -- "what can still be deployed from this source" -- and every other field on the report is about a particular RUN: its build plan, its output path, the problem that analysis found. Copying those here would put a run-shaped fact on a row that outlives every run, where nothing would ever correct it.

NO ADDRESS. A skipped app is not asked where it should live until somebody deploys it, so there is no placement to keep; a site row is what carries a hostname, and a site row means deployed.

type DeployAuditEvent

type DeployAuditEvent struct {
	PackageId     string
	DeploymentId  string
	SourceVersion string
	Status        string
	// FailureReason is the stable refusal code when there is one. A failure
	// with no code is recorded as an internal error rather than as a refusal,
	// so a real fault is never filed as somebody's mistake.
	FailureReason string
	Deployables   []DeployableOutcome
}

DeployAuditEvent is one recorded attempt.

type DeployOutcome

type DeployOutcome struct {
	DeploymentId string
	Status       string
	Report       *Report
	Deployables  []DeployableOutcome
	DslVersion   string
	// AwaitingConfirm is true when the run parked at the confirm gate. It is
	// reported separately from Status so a caller does not have to compare
	// against a string to learn that nothing is wrong.
	AwaitingConfirm bool
	// ConfirmReason says why an AUTO run parked instead of confirming itself
	// -- "this push changes what deploying would do", and the like. Empty for
	// a person's run, which parks because that is what the gate does.
	ConfirmReason string
	Problem       *Problem
	// BuiltOn is where this run's build happened, for the row (epic
	// memql#4900). One value for the run rather than one per app: a run
	// builds every app on one surface, and the pin keeps them on one node,
	// so the honest summary is the surface plus the node -- and the
	// per-deployable record rides on each outcome as well.
	BuiltOn BuiltOn
}

DeployOutcome is what a run produced.

func Deploy

func Deploy(ctx context.Context, d *Deps, req DeployRequest) (*DeployOutcome, error)

Deploy runs one deployment attempt to a terminal status, or parks it at the confirm gate.

=========================================================================== THE WHOLE RUN IS ONE CALL ON ONE NODE, AND THAT IS A CHOICE =========================================================================== The design anticipated stage handoffs as row events with routing rules, and the multi-node rules that follow from that (explicit plumbing, an in-process hop test per handoff). This implementation does not need them: the stages advance inside this function, so there is no stage-to-stage hop to plumb and none to test.

What that costs, stated rather than discovered: a node lost mid-deploy strands its row at a non-terminal status. Nothing is inconsistent -- the D6 order guarantees every site is still serving what it was, because the run died before `publish` or after it finished -- but the row does not advance again on its own. The recovery is the one the append-only rule already prescribes everywhere else: start a new attempt, which opens a new row.

What IS cross-node is the READ, and it is not optional: every row written here is read in an OS window some other node is serving, which is what the broadcast routing rules carry (component/node/routing.go, asserted by TestPackageRowsReachABrowserOnAnotherNode). Without them the packages list is correct on load and frozen afterwards.

It never panics a stage past its predecessor: each step either advances the row and continues, or closes the row and returns. The row IS the state machine's memory, which is what makes a stage idempotent under re-delivery -- re-running a completed stage re-reads the same inputs and reaches the same answer, and re-running a TERMINAL row is refused by the append-only guard rather than by anything here.

func StartAnalysis added in v0.22.10

func StartAnalysis(ctx context.Context, d *Deps, req DeployRequest) (*DeployOutcome, error)

StartAnalysis persists the run before returning its ID. Its lifetime is the cluster's, not the browser request's; Cancel is a durable flag another replica can write. A node lost mid-run is handled by the existing abandoned sweep.

type DeployRequest

type DeployRequest struct {
	PackageId    string
	DeploymentId string
	Actor        Actor
	// Confirmed is D12's always-present gate. A run arriving unconfirmed
	// parks at awaiting_confirm with the report on the row and returns; the
	// OS renders the report and calls again with Confirmed set. A REDEPLOY
	// sets it in one click, which is the same code path with the person's
	// answer already in hand.
	Confirmed bool
	// Placements maps a deployable name to where it goes, supplied at FIRST
	// deploy only (epic memql#4885, D8). Later deploys find the site through
	// (packageId, packageDeployableName) and never re-ask: a placement is
	// chosen once, and a later deploy of the same source keeps the same
	// addresses.
	Placements map[string]Placement
	// Automatic marks a run the source's own auto-deploy switch started
	// rather than a person (epic memql#4900, task memql#4903). On the REQUEST
	// rather than derived, because the only caller who can honestly answer is
	// the one that decided to start it -- and deliberately not readable from
	// the wire, so nothing a client sends can put a run in the timeline
	// marked as something nobody chose.
	Automatic bool
	// FromDeploymentId retries an earlier run from the bytes it already
	// fetched (task memql#4902): the run re-analyses the same snapshot rather
	// than going back to the repository, so a Retry after a lost node deploys
	// exactly what the lost run was deploying -- not whatever the branch has
	// moved to since.
	FromDeploymentId string
}

DeployRequest is one deploy or redeploy.

type DeployableOutcome

type DeployableOutcome struct {
	Name string `json:"name"`
	// BuiltOn is where THIS app was built. Per-app as well as per-run,
	// because a package whose apps built on different surfaces is exactly
	// the case the run-level summary cannot describe.
	BuiltOn   BuiltOn `json:"builtOn,omitzero"`
	SiteId    string  `json:"siteId,omitempty"`
	Hostname  string  `json:"hostname,omitempty"`
	BundleRef string  `json:"bundleRef,omitempty"`
	// CandidateRef is set INSTEAD of BundleRef when this run published the
	// deployable as its candidate version (memql#5601). Never both: rollback
	// re-points every outcome's BundleRef, and a candidate recorded there
	// would make "roll back to this run" serve a version the run never
	// served.
	CandidateRef string   `json:"candidateRef,omitempty"`
	Version      string   `json:"version,omitempty"`
	Created      bool     `json:"created,omitempty"`
	Refusal      *Problem `json:"refusal,omitempty"`

	// The placement halves a first deploy applied (epic memql#4885, D8), and
	// the ones it could not. AccountId and OwnDomain are set only when the
	// write LANDED; a refusal is recorded on the sibling field with the
	// server's own sentence and is NOT fatal -- the site is live at its
	// cluster address regardless, and the Where-it-lives stop renders the
	// refusal in place.
	AccountId      string   `json:"accountId,omitempty"`
	OwnDomain      string   `json:"ownDomain,omitempty"`
	AccountRefusal *Problem `json:"accountRefusal,omitempty"`
	DomainRefusal  *Problem `json:"domainRefusal,omitempty"`
}

DeployableOutcome is one deployable's result on a deployment row. It is the shape the OS reads to link a deployable to the site it produced.

type DeployableReport

type DeployableReport struct {
	DisplayName string              `json:"displayName,omitempty"`
	Deployment  *ManifestDeployment `json:"deployment,omitempty"`
	Name        string              `json:"name"`
	Kind        string              `json:"kind"`
	Path        string              `json:"path"`
	Assets      []ManifestAsset     `json:"assets,omitempty"`

	// BuildPlan is the sentence the confirm gate shows. Either the command
	// that will run, or the D4 fast-path's own answer -- "prebuilt output
	// found -- build skipped".
	BuildPlan string `json:"buildPlan"`
	// Command and Output are the machine-readable half of the same fact; the
	// build stage reads these, the person reads BuildPlan.
	Command string `json:"command,omitempty"`
	Output  string `json:"output"`
	// Prebuilt is the D4 fast-path: the output directory already carries a
	// built tree in the snapshot, so the build stage skips this deployable.
	Prebuilt bool `json:"prebuilt"`

	// Binding is the storefront's connection, carried through so the confirm
	// gate can show which store this deploys against. It names a secret; it
	// never carries one.
	Binding *ManifestBinding `json:"binding,omitempty"`
	Testing *ManifestTesting `json:"testing,omitempty"`

	// ResolutionTail is the manifest's choice of what a path matching no file
	// answers, carried through to EnsureSite. Empty means the kind decides.
	ResolutionTail string `json:"resolutionTail,omitempty"`

	// Problem, when set, is this deployable's own refusal. The package as a
	// whole is refused too -- a declared deployable that cannot deploy is not
	// a partial success -- but the problem is recorded here as well so the OS
	// can render it against the row it is about.
	Problem *Problem `json:"problem,omitempty"`
}

type Deps

type Deps struct {
	Store   *store
	Fetcher Fetcher
	Assets  AssetImporter
	Builder Builder
	// FleetBuilder builds a deployable whose target needs the person's own
	// machine (task memql#4904). Nil on every node that holds no worker
	// streams, and nil is the ANSWER rather than a gap: a target asking for
	// the fleet on a cluster with no fleet route is refused by name at the
	// build stage, which is where every other missing surface is refused.
	FleetBuilder Builder
	Stager       Stager
	Roller       Roller
	Publisher    SitePublisher
	Auditor      Auditor
	Logger       *slog.Logger
	Limits       Limits
	Now          func() time.Time
	// NewId mints deployment and site ids. Injected so a test reads a stable
	// timeline rather than a random one.
	NewId func(prefix string) string
	// Credentials unseals a package's source credential under its owner's
	// actor (epic memql#4885, D10). Shared by the fetcher and the D11 poll,
	// which is the point of it living here: the two paths that present a
	// bearer to GitHub resolve it through ONE function, so "refused for a
	// credential the owner cannot read" cannot be true on one path and false
	// on the other. Nil on a node that cannot resolve credentials; a package
	// naming one is then refused rather than fetched anonymously.
	Credentials CredentialResolver
	// Roles resolves a user's current cluster role.
	//
	// Injected for the same reason Credentials is, and used by the same path:
	// the auto-deploy feed runs on a schedule with nobody attached, so there
	// is no request actor to take an authority from. It cannot take one from
	// the context either -- the feed borrows the owner's IDENTITY through
	// auth.ContextWithUserActor, which deliberately stamps a RANKLESS WRITER,
	// so resolving the D9 answer from that context can only ever say no
	// (see TestBorrowedAuthorityDoesNotCarryTheD9Authority).
	//
	// NIL IS THE ANSWER, NOT A GAP: a DSL-carrying automatic run on a node
	// that cannot resolve roles is refused by the D9 gate rather than
	// deployed under a blank authority. Manual deploys never reach this --
	// they resolve through actorFromContext.
	Roles RoleResolver
	// Stores resolves the v1:shopify:store row a storefront's manifest NAMES,
	// by its myshopify.com domain, to the bare row id (epic memql#5530, issue
	// memql#5540). Returns "" for a miss.
	//
	// It runs under the CALLER's actor, deliberately, which is the same answer
	// updateSiteStoreBinding's Go guard gives: a caller who may not read a
	// store, or may read it but not attach it, may not bind a storefront to
	// it. A store reads at developer and above (Connect Shopify, D3), so
	// resolving under the deployment instead would let anyone who can deploy a
	// package reach the stores the deployment can.
	//
	// NIL IS AN ANSWER, NOT A GAP, as it is for Credentials: a node that
	// cannot resolve stores attaches none, so a first deploy places an
	// unattached draft with a note (Connect Shopify, D5), which the go-live
	// rule keeps away from shoppers until a store is connected.
	Stores StoreResolver

	// PeekCredentials is the PROBE's resolver (epic memql#4885, D11): the
	// same read, the same two refusals, and no lastUsedAt heartbeat -- a
	// probe is a question, not a fetch, and it writes nothing. A separate
	// field rather than a flag on Credentials so a test can see which of the
	// two a path reached for, and so the fetcher can never be handed the one
	// that does not stamp.
	PeekCredentials CredentialResolver
	// HTTP is the client the D11 poll and the source probe ask GitHub with.
	// Nil means a 30s default. Injected so a test can stand a fake GitHub
	// behind it and read which requests carried a bearer -- and which were
	// never made.
	HTTP *http.Client
	// GitHubApp is the cluster's GitHub App (epic memql#4912). It is what
	// turns a grant into the installation token background work runs under,
	// what asks whether the app is installed on a repository at all, and what
	// reads the branch list and manifest summary the probe answers with.
	//
	// NIL MEANS THIS NODE HAS NO APP, and every path that needs one refuses by
	// name (github_app_not_configured) rather than falling through: a cluster
	// with no app offers the pasted-token path alone, and saying so is the
	// operator's answer rather than a person's error.
	GitHubApp *githubapp.Client
}

Deps is the pipeline's whole outside world. Every field is an interface so the state machine is testable end to end with no cluster, no network and no object storage -- which is what the D6 ordering law needs, because "the publish never happened" is only assertable if the test can see the publish.

func (*Deps) ValidateSourceConnectionRepository added in v0.22.10

func (d *Deps) ValidateSourceConnectionRepository(ctx context.Context, connectionID, credentialID, repoURL string) error

ValidateSourceConnectionRepository is the shared authority for probes and package writes. The source's live binding, not browser-supplied metadata, determines which GitHub grant and installation may supply a repository.

type DslDomainReport

type DslDomainReport struct {
	Domain string `json:"domain"`
	// Constructs counts by construct kind (concept, query, mutation, ...),
	// which is the answer to "what does deploying this actually add" that a
	// file count is not.
	Constructs map[string]int `json:"constructs"`
	// Files is how many .memql files carry them.
	Files int `json:"files"`
	// Reserved marks a domain that collides with a core engine namespace.
	// Its constructs are NOT counted -- nothing read them -- and its problem
	// is dsl_domain_reserved.
	Reserved bool `json:"reserved,omitempty"`
}

DslDomainReport is one discovered DSL domain and what it holds.

type Engine

type Engine interface {
	Execute(ctx context.Context, query string) (*memql.ExecuteResult, error)
}

Engine resolves named graph operations and current action authority for each organization targeted by the pipeline.

type EnsureSiteRequest

type EnsureSiteRequest struct {
	PackageId      string
	DeployableName string
	Kind           string
	Hostname       string
	AccountId      string
	// StoreId is the v1:shopify:store row the manifest's `store` domain
	// RESOLVED to on this cluster, never the domain itself: the site's
	// binding is a reference, so the resolution happens once, in publish,
	// and what reaches the row is already an id (epic memql#5530).
	StoreId     string
	OwnerUserId string
	// ResolutionTail is the manifest's choice; empty means the kind decides,
	// which is what createSite writes by omitting the argument entirely.
	ResolutionTail string
}

EnsureSiteRequest is what creating or finding a deployable's site needs.

type FetchResult

type FetchResult struct {
	// PointerAbsent distinguishes "no packages are deployed" from "the
	// pointer said nothing". The first is the ordinary state of a cluster
	// that has never deployed a package; the second would be a fault.
	PointerAbsent bool
	Domains       []string
	Files         int
}

FetchResult is what one fetcher-mode run did.

func FetchActiveSet

func FetchActiveSet(ctx context.Context, root string) (FetchResult, error)

FetchActiveSet copies every package DSL tree named by the active-set pointer into root, which is the node's MEMQL_DSL_PATH.

Called from the `dsl-fetch` subcommand, which runs as an init container before the node boots (D5). Kept here rather than in main so the copy rules live beside the staging rules that produced the layout -- the two halves of one content-addressed convention.

type Fetcher

type Fetcher interface {
	FetchRepo(ctx context.Context, src RepoSource, limits Limits) (*SourceSnapshot, error)
	FetchArtifact(ctx context.Context, artifactId string, limits Limits) (*SourceSnapshot, error)
}

Fetcher fetches a package source. Its two implementations are the two source forms; everything downstream sees one SourceSnapshot either way.

type FleetDispatcher added in v0.20.13

type FleetDispatcher interface {
	// SelectMachine answers which of the owner's machines can take a build
	// with these label requirements, or a sentence naming every machine it
	// considered and why each was ruled out.
	SelectMachine(ctx context.Context, ownerUserId string, requireLabels map[string]string) (machineId string, reason string, err error)
}

FleetDispatcher is the slice of the Fleet the build route needs.

An interface declared HERE rather than an import of the worker package, because that package is behind the `agent` build tag: component/packages compiles into every node type, and a hard dependency would make the bff unbuildable. The agent-tagged file beside this one is what supplies a real one.

type GoPackReport

type GoPackReport struct {
	Path   string `json:"path"`
	Module string `json:"module,omitempty"`
	// Note is the sentence the report shows: what this is and where Go
	// delivery actually happens today.
	Note string `json:"note"`
}

GoPackReport is a detected, deferred Go pack (D3).

type Integration

type Integration struct {
	// contains filtered or unexported fields
}

Integration exposes the package capabilities.

IT LIVES IN component/, NOT integrations/, and the module graph is why. The pipeline publishes through component/edge's Publisher -- the same one POST /sites/{id}/bundles uses -- and component/edge is in the ROOT module, which already requires integrations. component/sitepublish moved here for exactly this reason (memql#4345), and module_taxonomy_test.go records it: importing edge from integrations makes the module graph a cycle, which go.work resolves and CI's module-boundaries lane does not.

The design names integrations/packages/ as the location. This is the same package under the one constraint that location cannot satisfy.

func NewIntegration

func NewIntegration(engine Engine, logger *slog.Logger) *Integration

NewIntegration wires the engine handle. The factory is in init(); this constructor is what tests call with a stub engine.

func (*Integration) Capabilities

func (i *Integration) Capabilities() []memql.IntegrationCapability

Capabilities implements memql.IntegrationProvider.

func (*Integration) GitHub added in v0.25.0

func (i *Integration) GitHub() *githubapp.Client

GitHub is the node's GitHub App client: the one Deps holds, so pipelines share its installation-token cache rather than doubling the mints against the same rate limit.

It is NOT nil on a cluster with no app configured: that client answers githubapp.ErrNotConfigured on every call needing the app, and Configured() reports false -- the cluster's fact, not a nil to remember (a registration made from the product while the node runs is seen by the next call). Nil only when this integration cannot resolve at all.

CALL IT WHEN AN OPERATION NEEDS IT, NEVER WHILE WIRING. The first call builds this integration's dependencies, once, from what app/ has installed by then; called before SetGitHubAppSource, it would fix this node's client to the environment alone for the life of the process -- for Deployables as well as for pipelines.

func (*Integration) InstallationToken added in v0.25.0

func (i *Integration) InstallationToken(ctx context.Context, credentialID, ownerUserID, owner, repo string) (token string, installationID int64, err error)

InstallationToken mints the installation token a source owner's grant reaches for owner/repo, after verifyGrantRepository confirms the grant still reaches it. Memory only.

The credential is resolved under the OWNER's actor (credentialID names their row; ownerUserID is whose name it is resolved under), never the caller's: a driver acting for a pipeline holds no person's authority of its own. The verification runs on EVERY call, including the one this replica could have answered from its token cache, because a person can lose a repository while the app stays installed for others.

Refusals are Deployables' own codes, carried as typed errors: credential_not_found for an empty name, an empty owner or a pasted token -- a pasted token is not a grant, has no installation behind it, and is never grounds to borrow the app's authority -- and for the rest exactly what a fetch refuses with (credential_revoked, reconnect_required, repository_not_installed, repository_not_accessible, github_app_not_configured).

func (*Integration) IntegrationName

func (i *Integration) IntegrationName() string

IntegrationName implements memql.IntegrationProvider.

func (*Integration) SetFleetBuilder added in v0.20.13

func (i *Integration) SetFleetBuilder(b Builder)

SetFleetBuilder installs the machine route (task memql#4904).

func (*Integration) SetGitHubAppSource added in v0.22.9

func (i *Integration) SetGitHubAppSource(source func() githubapp.Config)

SetGitHubAppSource installs where the cluster's GitHub App comes from. Called once, from app/, before the first call that needs it; a later call is ignored for SetWorkbench's reason -- Deps is built once.

func (*Integration) SetGitHubGrantDB added in v0.22.10

func (i *Integration) SetGitHubGrantDB(getter func() *sql.DB)

SetGitHubGrantDB wires the direct shared database used to serialize token rotation, reconnect, and disconnect across all nodes.

func (*Integration) SetWorkbench added in v0.20.13

func (i *Integration) SetWorkbench(runner workbenchRunner)

SetWorkbench installs the in-cluster build surface. Called once, from app/, before the first deploy; a later call is ignored because Deps is built once and a builder that changed under a running pipeline would make two halves of one deploy disagree about where they built.

type Limits

type Limits struct {
	MaxSourceBytes int64
	MaxFileBytes   int64
	MaxFileCount   int
}

Limits bounds an expanded source tree.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits resolves the limits from the environment.

A set-but-unparseable or non-positive value falls back to the DEFAULT rather than to "no limit". An unbounded expansion is the one outcome a misconfigured cap must never produce -- the same reasoning, and the same direction, as LibraryMaxUploadBytes.

type Manifest

type Manifest struct {
	FormatVersion int                    `yaml:"formatVersion" json:"formatVersion"`
	Name          string                 `yaml:"name"          json:"name"`
	Deployables   []ManifestDeployable   `yaml:"deployables"   json:"deployables"`
	Campaigns     *emailconfig.Campaigns `yaml:"campaigns,omitempty" json:"campaigns,omitempty"`
	// Pipeline is the repository's pipeline (epic memql#5477, design record
	// D3): the same file, so a repository has one manifest and one place the
	// platform looks.
	//
	// DECODED HERE, NEVER VALIDATED HERE. The strict decoder below holds the
	// block's SHAPE to the rule every key is held to -- an unknown key inside
	// it is package_manifest_invalid -- and stops there. What the block MEANS
	// (a need nobody offers, a timeout that is no duration, a bucket nobody
	// declared) is pipelines.Validate's question, asked when a run compiles,
	// and its answer fails that RUN with a typed refusal (D9). A typo in a
	// pipeline must never refuse a deploy of the source it sits in.
	//
	// Not part of PlanFingerprint either: the block decides what a pipeline
	// run executes and nothing about what deploying the source does, so
	// editing it never parks an automatic deploy.
	Pipeline *pipelines.Spec `yaml:"pipeline,omitempty" json:"pipeline,omitempty"`
}

Manifest is memql-package.yaml.

It describes software plus optional deployment defaults. Slugs are relative to the receiving cluster; existing site addresses remain stable on redeploy.

func ParseManifest added in v0.24.0

func ParseManifest(raw []byte) (*Manifest, error)

ParseManifest validates the same manifest for repository sources and settings imports.

func ReadInstalledManifest added in v0.24.0

func ReadInstalledManifest() (*Manifest, error)

func ReadManifest

func ReadManifest(tree fs.FS) (*Manifest, error)

ReadManifest reads and validates the manifest at the root of tree.

Every failure is a *Refusal carrying a catalogued code, so the caller never has to distinguish "no manifest" from "bad manifest" by inspecting an error string -- the OS keys on the code and renders its own sentence for each.

type ManifestAsset added in v0.22.10

type ManifestAsset struct {
	Path   string `yaml:"path" json:"path"`
	Source string `yaml:"source" json:"source"`
	SHA256 string `yaml:"sha256" json:"sha256"`
	Size   int64  `yaml:"size" json:"size"`
}

ManifestAsset is a file supplied separately from source and imported only after confirmation. Path is relative to the published site, not the build workspace. The digest pins the bytes even if a release asset is replaced.

type ManifestBinding

type ManifestBinding struct {
	Store string `yaml:"store,omitempty" json:"store,omitempty"`
}

ManifestBinding is the per-kind connection to the system a deployable fronts. Only shopify_storefront declares one, and since epic memql#5530 it NAMES the store rather than describing it: `store` is a v1:shopify:store row's myshopify.com domain, which the pipeline resolves to a row id at deploy and writes onto the site as {storeId}.

WHY THE DOMAIN AND NOT THE ROW ID. A manifest is committed to a product's repository and read by whoever deploys it; a row id is a fact about one cluster's database and means nothing in another. The myshopify.com domain is the one identifier Shopify never changes and the one an operator can check by eye. It is not a hostname in the sense the manifest refuses -- that rule is about THIS cluster's addresses, which are chosen at deploy.

It still carries no secret. The Storefront token reference moved to the store row with the domain; the manifest names neither.

type ManifestBuild

type ManifestBuild struct {
	Command string `yaml:"command,omitempty" json:"command,omitempty"`
	Output  string `yaml:"output,omitempty"  json:"output,omitempty"`
}

ManifestBuild overrides the defaults. The zero value IS the default pair, so a deployable that omits the block entirely gets `npm ci && npm run build` into `dist` -- which is what the overwhelming majority of trees want, and what makes the block optional rather than ceremonial.

type ManifestDeployable

type ManifestDeployable struct {
	Name        string              `yaml:"name"    json:"name"`
	DisplayName string              `yaml:"displayName,omitempty" json:"displayName,omitempty"`
	Deployment  *ManifestDeployment `yaml:"deployment,omitempty" json:"deployment,omitempty"`
	Path        string              `yaml:"path"    json:"path"`
	Kind        string              `yaml:"kind"    json:"kind"`
	Build       *ManifestBuild      `yaml:"build,omitempty"   json:"build,omitempty"`
	Binding     *ManifestBinding    `yaml:"binding,omitempty" json:"binding,omitempty"`
	Testing     *ManifestTesting    `yaml:"testing,omitempty" json:"testing,omitempty"`
	// Assets are immutable files imported after build, at paths relative to
	// the published site. Analysis reads these declarations, never the bytes.
	Assets []ManifestAsset `yaml:"assets,omitempty" json:"assets,omitempty"`
	// ResolutionTail is what the edge answers for a path matching no file in
	// this deployable's built output: "fallback" (index.html) or "not_found"
	// (404). OMITTED means the kind decides, which is every manifest written
	// before memql#5535 and the overwhelming majority after.
	//
	// IT IS HERE BECAUSE OTHERWISE THE CHOICE IS UNREACHABLE. A package
	// deploy is the only way most sites are created, so a site field no
	// manifest can declare is a field the product cannot use -- and the
	// product this exists for is a multi-page prerendered storefront that
	// declared `kind: static` precisely to get the 404 back, giving up the
	// store binding and the policy that admits Shopify along with it.
	//
	// SET ON CREATE ONLY, like Kind: EnsureSite finds an existing site by
	// (packageId, deployableName) and returns it untouched, so changing this
	// in the manifest does not rewrite a deployed site's row.
	// updateSiteResolutionTail is how an existing one is changed.
	//
	// BINDING IS NO LONGER IN THIS SENTENCE (epic memql#5530). A redeploy
	// re-points a storefront whose manifest now names a different store,
	// because the store is what the source is ABOUT rather than a property of
	// a site somebody deployed -- a manifest saying one store while the site
	// serves another is a storefront quietly talking to the wrong merchant.
	ResolutionTail string `yaml:"resolutionTail,omitempty" json:"resolutionTail,omitempty"`
}

ManifestDeployable is one declared web surface inside the package.

DECLARED, not discovered, and that asymmetry with DSL domains is deliberate (D2): a directory holding a package.json could be a site, a component library, or tooling, and its KIND -- whether a mistyped path 404s or falls back to index.html -- and its storefront BINDING are facts no walk can recover. DSL domains carry both facts in their own layout, so they are discovered exactly as the engine's own MEMQL_DSL_PATH mount discovers them.

func (ManifestDeployable) BuildPlanFor

func (d ManifestDeployable) BuildPlanFor() (command, output string)

BuildPlanFor reports the command and output directory a deployable builds with, applying the D4 defaults for anything the manifest leaves out.

type ManifestDeployment added in v0.22.12

type ManifestDeployment struct {
	Slug    string   `yaml:"slug,omitempty" json:"slug,omitempty"`
	Domains []string `yaml:"domains,omitempty" json:"domains,omitempty"`
}

ManifestDeployment carries portable placement defaults. Slug is relative to the receiving cluster. Domains are additive; omission never removes bindings.

type ManifestSummary added in v0.20.13

type ManifestSummary struct {
	Name        string                      `json:"name"`
	Deployables []ManifestSummaryDeployable `json:"deployables"`
	// DslDomains is the directory names directly under dsl/, which IS the
	// declaration (analyze.go's DslRoot -- domains are discovered, never
	// declared). Empty when the repository has no dsl/ at all, which is the
	// ordinary SPAs-only package.
	DslDomains []string `json:"dslDomains"`
}

ManifestSummary is the probe's preview of a package's own manifest.

EMPTY IS A VALID ANSWER and never a refusal. A repository with no manifest, a manifest that does not parse, a manifest this cluster's format version does not read -- all of them answer an empty summary and let Analyze report the real problem against the real snapshot. Refusing here would turn a courtesy into a gate, and it would report a manifest problem twice with two different sentences.

type ManifestSummaryDeployable added in v0.20.13

type ManifestSummaryDeployable struct {
	DisplayName string `json:"displayName,omitempty"`
	Name        string `json:"name"`
	Kind        string `json:"kind"`
	Path        string `json:"path"`
}

ManifestSummaryDeployable is one declared deployable, in the three facts the preview shows. Deliberately not the build plan or the binding: those are Analyze's to report against a tree it has actually read.

type ManifestTesting added in v0.23.0

type ManifestTesting struct {
	Binding *ManifestBinding `yaml:"binding,omitempty" json:"binding,omitempty"`
}

ManifestTesting configures the storefront's independent testing store. Its stable URL is test--<production hostname>; both destinations serve the same bundle. Omission preserves the store chosen in MemQL OS on redeploy.

type Options

type Options struct {
	// SourceVersion is the commit SHA or content hash the snapshot is, echoed
	// into the report. Analysis never derives it: the fetch stage knows what
	// it fetched, and a hash computed here would be a second answer to a
	// question that already has one.
	SourceVersion string
	// Limits bounds the tree. Zero fields take their defaults.
	Limits Limits
	// Logger receives the DSL gate's own diagnostics. Optional.
	Logger *slog.Logger
}

Options tunes an analysis pass.

type PickerInstallation added in v0.20.13

type PickerInstallation struct {
	Id                  string `json:"id"`
	AccountId           string `json:"accountId"`
	Account             string `json:"account"`
	AccountType         string `json:"accountType"`
	RepositorySelection string `json:"repositorySelection"`
	// Suspended is an installation an owner has suspended. Carried rather
	// than filtered out: it explains why nothing under it works, and hiding
	// it would make those repositories simply absent.
	Suspended bool `json:"suspended"`
}

PickerInstallation is one installation the grant reaches.

type PickerRepository added in v0.20.13

type PickerRepository struct {
	FullName       string `json:"fullName"`
	Owner          string `json:"owner"`
	Name           string `json:"name"`
	Url            string `json:"url"`
	Private        bool   `json:"private"`
	Visibility     string `json:"visibility"`
	DefaultBranch  string `json:"defaultBranch"`
	PushedAt       string `json:"pushedAt"`
	InstallationId string `json:"installationId"`
}

PickerRepository is one repository in the shape the Source stop needs: enough to group it, search it, show it and prefill a ref from it without a second call.

type Placement added in v0.20.13

type Placement struct {
	// Nil inherits manifest domains; an explicit empty list opts out for this run.
	Domains   []string
	Hostname  string
	AccountId string
	OwnDomain string
	// Skip leaves this deployable out of the run (memql#4930, epic
	// memql#4937). A PLACEMENT-TIME CHOICE, never a manifest one: the manifest
	// describes the SOFTWARE, and a repository that ships a storefront and a
	// starter SPA should not have to change to let somebody host only the
	// storefront.
	//
	// A skipped deployable is RECORDED, not omitted. The row's `deployables`
	// list promises one entry per manifest deployable, and a missing entry
	// reads as "nothing happened" where the truth is "you chose not to, and
	// nothing it already serves was touched". It reuses the same skipped
	// outcome shape a target the cluster does not offer produces, because a
	// reader asking "why is there no site for this" wants one answer shape,
	// not two.
	Skip bool
	// Target is which of the site's versions this run's build becomes
	// (memql#5601): the serving version, which is the default and what every
	// run did before the field existed, or the CANDIDATE, which writes
	// candidateRef and leaves bundleRef -- and so every visitor -- where it
	// was. A placement-time choice for skip's reason. Empty until
	// prepareDeployment reads it; an unknown value refuses the request before
	// a run opens, and a storefront refuses the candidate after analysis,
	// because a storefront has no candidate version (refuseUnservedCandidates).
	Target edge.Target
}

Placement is where one deployable goes on its first deploy (D8): the hostname its site is created at, the client it is FOR, and the client's own domain. The hostname is the only required part -- a placement naming neither of the other two is exactly the first deploy there was before them.

The pipeline APPLIES the two optional parts itself, after the site exists, as the same two calls the page would make (updateSiteAccount and customDomainAdd) under the caller's own actor -- so the guards behind them decide exactly as they do from the page, and there is no client-side follow-up write for a closed window to lose.

type Problem

type Problem struct {
	// Code is a catalogued refusal code (refusal.go). Stable and
	// machine-readable; the OS keys its own sentence on it.
	Code string `json:"code"`
	// Message is the server's sentence, which the OS renders VERBATIM when it
	// has no copy of its own for the code.
	Message string `json:"message"`
	// Scope names the half this is about -- a deployable name, a DSL domain,
	// a path -- and is empty for a package-wide problem.
	Scope string `json:"scope,omitempty"`
	// Fatal reports whether this problem blocks the deploy. A Go pack is the
	// designed non-fatal case (D3): it is reported, the rest deploys.
	Fatal bool `json:"fatal"`
}

Problem is one thing the analysis found.

type PublishResult

type PublishResult struct {
	SiteId    string
	Hostname  string
	BundleRef string
	Version   string
	Created   bool
}

PublishResult is one deployable's published site.

type Refusal

type Refusal struct {
	// Code is one of the catalogue constants above.
	Code string
	// Detail is the sentence a person reads. The OS renders the server's
	// sentence VERBATIM, in-surface, so this text is the product copy for
	// this failure -- write it for the person who typed the repo URL.
	Detail string
	// Scope names the half the refusal is about when the package has halves:
	// a deployable name, a dsl domain, a path. Empty for package-wide.
	Scope string
}

Refusal is an analysis or pipeline failure carrying a stable Code.

It is an error so it can travel the ordinary Go path, and it carries the code separately so no caller has to parse a message to key on it.

func (*Refusal) Error

func (r *Refusal) Error() string

type RepoSource added in v0.20.13

type RepoSource struct {
	RepoUrl      string
	Ref          string
	CredentialId string
	OwnerUserId  string
}

RepoSource is what a repository fetch is told: the four facts off the package row, and nothing resolved.

CredentialId is a NAME and OwnerUserId is whose name it is resolved under. The pair travels together because resolution is owner-scoped by construction (epic memql#4885, D10): the fetcher asks for the value at the moment of the fetch, under the PACKAGE OWNER's actor rather than the caller's, and never stores what comes back. A cluster owner deploying somebody's package fetches under that package's own credential, and a package naming another person's credential is refused by name.

type Report

type Report struct {
	Manifest *Manifest `json:"manifest,omitempty"`
	// Name is the manifest's name. Empty when the manifest could not be read
	// at all -- which is a report worth keeping, because it carries the
	// problem that explains why.
	Name string `json:"name,omitempty"`
	// FormatVersion echoes the manifest's declared version.
	FormatVersion int `json:"formatVersion,omitempty"`
	// SourceVersion is the commit SHA or content hash this snapshot is.
	SourceVersion string `json:"sourceVersion,omitempty"`
	// UpstreamBaseline preserves the observation associated with this fetch,
	// so later confirmation/retry can distinguish an old poll from a new head.
	// Pointer distinguishes an observed empty baseline from an old report with none.
	UpstreamBaseline *string `json:"upstreamBaseline,omitempty"`

	// Deployables is every declared web surface and what deploying it would
	// do. Present even for a deployable that carries a problem, because
	// "storefront: path missing" is more useful beside the others than
	// instead of them.
	Deployables []DeployableReport `json:"deployables"`

	// DslDomains is every DISCOVERED dsl/<domain>/, with construct counts.
	DslDomains []DslDomainReport `json:"dslDomains"`

	// DslChanges says whether deploying this plan would change the cluster's
	// ACTIVE DSL set (memql#5601): true when some domain's tree differs from
	// what the pointer names -- so the run would stage and roll every node
	// that reads DSL -- false when every domain is already active or the plan
	// ships none. ABSENT WHEN UNKNOWN, and a consumer must read it so: a node
	// with no stager, or one that could not read the pointer, does not know,
	// and false there would offer what the confirm then refuses.
	//
	// Computed when the run is analysed, before the confirm gate, from the
	// stager's own answer (Stager.PrefixFor against the pointer) -- the answer
	// the candidate refusal acts on. That refusal asks again at confirm,
	// because the pointer can move in between; this is what lets MemQL OS
	// offer "Deploy as candidate" only where the engine would take it.
	DslChanges *bool `json:"dslChanges,omitempty"`

	// GoPacks is every bff/ with a go.mod (D3): reported so nobody wonders
	// where their Go went, and deferred so the rest of the package deploys.
	GoPacks []GoPackReport `json:"goPacks,omitempty"`

	// Problems is every problem found, fatal or not, in the order the
	// analysis reached them.
	Problems []Problem `json:"problems"`

	// OK reports whether this package can deploy: no FATAL problem. It is
	// stored rather than derived at read time so a report rendered from an
	// old row cannot disagree with the decision that row recorded.
	OK bool `json:"ok"`
}

Report is what an analysis pass learned about one package source.

func Analyze

func Analyze(tree fs.FS, opts Options) (*Report, error)

Analyze decides whether tree is a deployable package, and what deploying it would do.

It ALWAYS returns a report, and returns an error only for the first FATAL problem. That pairing is deliberate: the pipeline lands the report on the deployment row so the OS can show every problem at once, and stamps the error's code as the row's typed refusal so a caller keying on one thing has one thing to key on. A caller that only wants the verdict reads err; a caller rendering the confirm gate reads the report.

Nothing here writes, fetches, or reaches a cluster. That is D12: "this DSL would refuse boot" is an answer produced here, offline, before a pod is ever asked to run it.

func (*Report) FirstFatal

func (rep *Report) FirstFatal() *Problem

FirstFatal returns the first fatal problem, or nil. It is what the pipeline stamps as the deployment row's typed error.

type ResolvedCredential added in v0.20.13

type ResolvedCredential struct {
	// Id is the row this came from, for a refusal that can name it.
	Id string
	// Kind is "token" or "github_app". An ABSENT kind on the row reads as
	// "token" -- every credential written before Connect existed carries no
	// value, and the pre-Connect behaviour is the reading that asserts least.
	Kind string
	// OwnerUserId is whose row it is, as stored. Read back rather than echoed
	// from the argument, because the argument is who the caller BELIEVED owns
	// it and this is what the read gate actually admitted.
	OwnerUserId string
	// Bearer is what goes in Authorization: Bearer. For a token credential it
	// is the pasted value; for a grant it is the person's USER token, already
	// refreshed if it had expired. Background work swaps it for an
	// installation token (C6) -- see installationBearer.
	Bearer string
	// Login and ExternalId identify the GitHub account behind a grant. Empty
	// for a token credential.
	Login      string
	ExternalId string
	// Installations is the grant's stored installation ids -- a DISPLAY CACHE
	// refreshed on owner-actor paths, never the thing a fetch decides by. The
	// fetcher asks GitHub which installation covers a repository, live,
	// because a stored list cannot answer for a repository added since.
	Installations []string
}

ResolvedCredential is one credential, unsealed, in the shape every caller that presents a bearer to GitHub needs.

It carries the KIND as well as the bearer, and that is the whole reason it is a struct rather than the string it used to be (epic memql#4912). Under a pasted token a 401, a 403 and a 404 are one fact -- "this token does not reach that repository" -- because the cluster genuinely cannot tell them apart. Under a GitHub App grant it can: a 401 is the AUTHORIZATION being refused, whose repair is one click of Connect, and a 404 may be the app not being installed on that repository, whose repair is an installation link. A caller holding only a string cannot make either distinction, so it makes neither, and both facts arrive as "private, or not there".

Nothing here is stored by any caller. Bearer is a function-local for the length of one request, exactly as the plain token was.

func (ResolvedCredential) IsGrant added in v0.20.13

func (r ResolvedCredential) IsGrant() bool

IsGrant reports whether this is a GitHub App grant rather than a pasted token. One predicate rather than a `== "github_app"` at six call sites, so the absent-kind rule is written once.

type RestoredState

type RestoredState struct {
	DeploymentId string              `json:"deploymentId"`
	DslVersion   string              `json:"dslVersion,omitempty"`
	Deployables  []DeployableOutcome `json:"deployables"`
	Rolled       bool                `json:"rolled"`
}

RestoredState is what a rollback put back.

func Rollback

func Rollback(ctx context.Context, d *Deps, req RollbackRequest) (*RestoredState, error)

Rollback executes the D6 order REVERSED: publish back FIRST, then the pointer back and a roll.

The reversal is the entire point and it is not symmetry for its own sake. Forward, the schema must arrive before the app that uses it -- so stage and roll come before publish. Backward, the app must stop using the schema before the schema goes away -- so the publish comes back first. Doing it in the forward order on the way back would leave, for the width of the rollout, new application code talking to old DSL: the one window D6 exists to close.

A rollback restores a TUPLE (dslVersion + each site's bundleRef), not a sequence of events. The prior deployment row already records exactly that tuple, which is why the timeline being append-only is what makes rollback possible at all.

type RoleResolver added in v0.21.18

type RoleResolver func(ctx context.Context, userId string) (auth.Role, error)

RoleResolver answers what cluster role a user currently holds.

Declared HERE rather than beside Deps because pipeline.go imports no component/auth -- Deps exists to keep the D6 state machine testable with no cluster behind it, and a type alias would have dragged the import in for nothing. CredentialResolver sits in credentials.go for the same reason.

The zero Role is the fail-closed answer, and is what a user who no longer exists resolves to.

type RollbackRequest

type RollbackRequest struct {
	PackageId    string
	DeploymentId string
	Actor        Actor
}

RollbackRequest restores a package to what an earlier deployment left live.

type Roller

type Roller interface {
	Roll(ctx context.Context, reason string) error
}

Roller drives the rolling restart that makes a staged tree live.

type SitePublisher

type SitePublisher interface {
	// EnsureSite finds the site this package deployable published to last
	// time, or creates it (draft) at the requested hostname. Reports whether
	// it created one.
	EnsureSite(ctx context.Context, req EnsureSiteRequest) (siteId, hostname string, created bool, err error)
	// PublishBundle uploads under a fresh content-addressed prefix and only
	// then points the target version at it -- so a failed publish leaves the
	// site serving exactly what it was serving. TargetCandidate writes
	// candidateRef and leaves the serving version alone (memql#5601).
	PublishBundle(ctx context.Context, siteId string, bundle edge.Bundle, target edge.Target) (PublishResult, error)
	// BindSiteToStore re-points an EXISTING site at the store its manifest
	// names. Called only when the two differ.
	BindSiteToStore(ctx context.Context, siteId, storeId string) error
	BindSiteTestingStore(ctx context.Context, siteId, storeId string) error
	// RepointSite points a site back at a bundle version that already exists.
	// THE rollback operation, and it is the same write updateSiteBundle makes
	// forward -- which is the whole reason bundles live under versioned
	// content-addressed prefixes rather than being overwritten.
	RepointSite(ctx context.Context, siteId, bundleRef string) error
	// StoreSnapshot lands a fetched source archive as a content-addressed
	// Library artifact (D8).
	StoreSnapshot(ctx context.Context, packageId, version string, raw []byte) (artifactId string, err error)
	// ReadSnapshot reads back what StoreSnapshot wrote (epic memql#4900,
	// task memql#4902) -- the bytes a retry re-analyses rather than fetching
	// the source again.
	//
	// Declared BESIDE the write rather than on the Fetcher, because it is the
	// same object under the same key by the same client: a reader on the
	// fetch side would be a second answer to where a snapshot lives, and the
	// two would drift the first time either moved.
	ReadSnapshot(ctx context.Context, ref string) ([]byte, error)
}

SitePublisher is the publish half: create a site on first deploy, republish on later ones, and store a source snapshot in the Library.

type SourceProbeResult added in v0.20.13

type SourceProbeResult struct {
	// Host is the repository's host as parsed -- github.com for either
	// spelling GitHub answers on, and the unsupported host verbatim when the
	// reason is source_host_unsupported.
	Host string `json:"host"`
	// Reachable is true only when GitHub answered the repository: the one
	// case the fetch would succeed in.
	Reachable bool `json:"reachable"`
	// Private and DefaultBranch come from GitHub's answer and are meaningful
	// only when Reachable is true; a repository the probe could not read has
	// no known visibility, and false is "not known", never "public".
	Private       bool   `json:"private"`
	DefaultBranch string `json:"defaultBranch"`
	// Reason is exactly one of the ProbeReason* values.
	Reason string `json:"reason"`

	// Branches is every branch the repository has, up to the first 1,000
	// (githubapp.BranchPagesMax pages), DEFAULT BRANCH FIRST, and it is
	// answered only under a grant (epic memql#4912). It fills the ref picker on
	// the Source stop and Other branch on a pipeline, so a person chooses a
	// branch that exists instead of typing one that does not.
	Branches []string `json:"branches"`
	// Manifest is what memql-package.yaml says about itself, read through the
	// contents API before anything is fetched. It is a PREVIEW: the What-it-is
	// stop shows it so a person recognises the package they picked, and
	// Analyze over the real snapshot remains the authority on every question
	// it answers.
	Manifest ManifestSummary `json:"manifest"`
}

SourceProbeResult is what sourceProbe answers. Every field is JSON-tagged because the struct IS the wire shape the Source stop reads.

func ProbeSource added in v0.20.13

func ProbeSource(ctx context.Context, d *Deps, repoUrl, credentialId string) (SourceProbeResult, error)

ProbeSource asks GitHub whether repoUrl can be read, under the caller's credential when one is named.

type SourceRepositoriesResult added in v0.20.13

type SourceRepositoriesResult struct {
	// Reason is exactly one of the RepositoriesReason* values. Everything
	// below is empty for anything but ok.
	Reason string `json:"reason"`
	// Repositories is what the grant reaches, across every installation, for
	// the requested page. Grouped by the client rather than here: `owner` is
	// carried on every row, and a server-side grouping would fix an order the
	// picker's search then has to undo.
	Repositories []PickerRepository `json:"repositories"`
	// Installations is every installation this grant can reach, so the picker
	// can say WHY a repository is missing -- an installation limited to
	// selected repositories, or suspended, is a different answer from one that
	// simply has none.
	Installations []PickerInstallation `json:"installations"`
	// Pending names the organizations whose installation is still waiting for
	// an owner's approval. By NAME, because the repair belongs to somebody
	// else and knowing whom to ask is the person's only useful next step.
	Pending []string `json:"pending"`
	// NextPage is the page to ask for next, or 0 when there is no more. Zero
	// rather than -1 or absent: the argument's own "empty or 0 means the first
	// page" makes 0 the value that cannot be mistaken for a page.
	NextPage int `json:"nextPage"`
}

SourceRepositoriesResult is the picker's whole answer.

func SourceRepositories added in v0.20.13

func SourceRepositories(ctx context.Context, d *Deps, credentialId string, page int) (SourceRepositoriesResult, error)

SourceRepositories lists what the caller's GitHub App grant can reach.

type SourceSnapshot

type SourceSnapshot struct {
	// Tree is the package tree with the manifest at its root -- the same FS
	// shape for both source forms (D1), which is what makes every rule below
	// the fetch apply exactly once.
	Tree fs.FS
	// Version is the commit SHA (repo) or content hash (zip) this snapshot is.
	Version string
	// UpstreamBaseline is the feed observation before the ORIGINAL fetch.
	// It travels with retained snapshots; nil means the observation is unknown.
	UpstreamBaseline *string
	// Bytes is the archive as fetched, for storing as the content-addressed
	// Library snapshot (D8). Nil when the source WAS a stored artifact --
	// re-storing bytes the Library already holds would give one snapshot two
	// identities.
	Bytes []byte
	// ArtifactId is the snapshot this tree ALREADY has in the Library, set
	// when the bytes came from an earlier run rather than from the source.
	// It is what lets the retry's own row point at the same content-addressed
	// artifact instead of recording none (memql#4955): a row with no snapshot
	// cannot be resumed at its confirm and cannot be retried again, so a
	// retry-of-a-retry was refused for bytes sitting in blob storage.
	ArtifactId string
	// Root is the on-disk directory when the fetch expanded to disk, empty
	// when the tree is read straight out of an archive. The build stage needs
	// real files; analysis does not.
	Root string
	// contains filtered or unexported fields
}

SourceSnapshot is one fetched package source, expanded and validated.

func (*SourceSnapshot) Close

func (s *SourceSnapshot) Close()

Close releases anything the fetch expanded to disk.

type Stager

type Stager interface {
	// StageDomain writes one domain's tree under packages/<domain>/<hash>/ and
	// returns the prefix. Idempotent by construction: the same tree produces
	// the same hash and therefore the same prefix, so re-staging overwrites
	// bytes with themselves.
	StageDomain(ctx context.Context, domain string, tree fs.FS) (prefix string, err error)
	// PrefixFor is the prefix StageDomain WOULD write tree under, computed
	// without writing anything. It is how a run asks "would this change the
	// active set" before the confirm gate and the build (memql#5601), and it
	// is the stager's to answer because the stager is what names a prefix.
	PrefixFor(domain string, tree fs.FS) (prefix string, err error)
	ReadActiveSet(ctx context.Context) (map[string]string, error)
	WriteActiveSet(ctx context.Context, set map[string]string) error
}

Stager writes content-addressed DSL trees and rewrites the active-set pointer.

type StoreResolver added in v0.22.9

type StoreResolver func(ctx context.Context, domain, bound, account string) (string, error)

StoreResolver answers which v1:shopify:store row a myshopify.com domain names on THIS cluster, as a bare row id, or "" when there is no such row the caller may read and attach. `bound` is the store the site is bound to now ("" for none): answering that same store is leaving the binding alone, which takes no store part, so the resolver asks for the part only on a change. `account` is the organization the site belongs to, or will on create; the part is asked there as well as of the caller (memql#5598).

Declared here rather than beside its siblings, and the difference is the reason they are scattered: CredentialResolver lives in credentials.go because it returns a type that file owns, and RoleResolver lives in autodeploy.go because it returns an auth.Role and pipeline.go imports no component/auth. This one takes and returns strings, so it has no such pull and belongs beside the field that holds it.

A MISS IS "", NOT AN ERROR. An error means the read itself failed and the run stops on it; an empty answer means the question was asked and this cluster has no store by that name, which is the refusal a person repairs.

type SweepResult added in v0.20.13

type SweepResult struct {
	Checked   int `json:"checked"`
	Abandoned int `json:"abandoned"`
}

SweepResult is what one pass found.

func SweepAbandoned added in v0.20.13

func SweepAbandoned(ctx context.Context, d *Deps) (SweepResult, error)

SweepAbandoned closes every run whose heartbeat has stopped.

The read is every in-flight run in the cluster, which the composite tier admits only to a cluster owner -- so this runs under the maintenance actor (component/auth/maintenance_actor.go names it and says why). Under the default automation reader the read answers zero rows and no error, and a sweep that closes nothing looks exactly like a cluster with nothing stranded.

type UnofferedTarget added in v0.20.13

type UnofferedTarget struct {
	// Display is the name the not-offered sentence is built from -- "iOS",
	// never the manifest's `ios`.
	Display string
	// Address is the address stop the target will carry once it is offered,
	// so the sentence can say where the app is headed rather than only that
	// it is not going anywhere today.
	Address string
}

UnofferedTarget is a kind the target model has written down and not registered (design section B, epic memql#4885 D9): what a person sees it called, and where the design's table says it will go.

Jump to

Keyboard shortcuts

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