admin

package
v0.9.1 Latest Latest
Warning

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

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

Documentation

Overview

Package admin holds the control plane's use cases: the lifecycle of a tenant, as multi-tenancy.md §5 draws it.

Authorisation here is the scope alone, checked in this layer (rule 2): the role matrix is a tenant's internal order, and the operator acting on the control plane is deliberately not a member of the tenants they administer. `admin:tenants` is carried only by a personal access token minted for exactly this - never by a session (identity.md §15.4, catalogue.SessionScopes).

Index

Constants

View Source
const (
	CancelTenantDeletionName = "CancelTenantDeletion"

	// TenantDeletionCancelledAction is written into the tenant's own trail: its members are
	// entitled to see that their workspace was to be deleted and was not (audit.md §6).
	TenantDeletionCancelledAction audit.Action = "tenant.deletion_cancelled"
)
View Source
const (
	RequestTenantDeletionName = "RequestTenantDeletion"

	// TenantDeletionRequestedAction is the last entry a tenant's trail can carry about its own
	// end: the hard delete itself has no trail left to write into (audit.md §6).
	TenantDeletionRequestedAction audit.Action = "tenant.deletion_requested"
)
View Source
const (
	ExportTenantName = "ExportTenant"

	// TenantExportedAction is the trail's record that a copy of the whole workspace left the
	// installation - a warning, the DSR export's reasoning: "who took a copy of whose data, and
	// where to" is a question with an answer. It goes into the exported workspace's own trail,
	// where its administrator can see it (audit.md §5).
	TenantExportedAction audit.Action = "tenant.exported"
)
View Source
const (
	ListInstallationPresetsName    = "ListInstallationPresets"
	CreateInstallationPresetName   = "CreateInstallationPreset"
	ChangeInstallationPresetName   = "ChangeInstallationPreset"
	WithdrawInstallationPresetName = "WithdrawInstallationPreset"
)
View Source
const (
	ReadInstanceSettingsName  = "ReadInstanceSettings"
	WriteInstanceSettingsName = "WriteInstanceSettings"
	ListOperatorsName         = "ListOperators"
	AddOperatorName           = "AddOperator"
	RemoveOperatorName        = "RemoveOperator"
)
View Source
const (
	ListInstanceAiProvidersName     = "ListInstanceAiProviders"
	ConfigureInstanceAiProviderName = "ConfigureInstanceAiProvider"
	RemoveInstanceAiProviderName    = "RemoveInstanceAiProvider"
)
View Source
const (
	ListInstanceBackupTargetsName  = "ListInstanceBackupTargets"
	CreateInstanceBackupTargetName = "CreateInstanceBackupTarget"
	TestInstanceBackupTargetName   = "TestInstanceBackupTarget"
	DeleteInstanceBackupTargetName = "DeleteInstanceBackupTarget"
)
View Source
const (
	ReadInstanceOverviewName = "ReadInstanceOverview"
	ListInstanceJournalName  = "ListInstanceJournal"
)
View Source
const (
	ListInstanceIdentityProvidersName     = "ListInstanceIdentityProviders"
	ConfigureInstanceIdentityProviderName = "ConfigureInstanceIdentityProvider"
	RemoveInstanceIdentityProviderName    = "RemoveInstanceIdentityProvider"
)
View Source
const (
	WithdrawInstanceIdentityProviderName         = "WithdrawInstanceIdentityProvider"
	CancelInstanceIdentityProviderWithdrawalName = "CancelInstanceIdentityProviderWithdrawal"
)
View Source
const (
	ListTenantsName   = "ListTenants"
	SuspendTenantName = "SuspendTenant"
	ResumeTenantName  = "ResumeTenant"

	// TenantSuspendedAction and TenantResumedAction are written into the tenant's own trail:
	// its members are entitled to see that their workspace was suspended, and when it came back
	// (audit.md §6). The suspension is WARNING - it is the entry somebody investigating "why
	// did every call fail at 14:02" is looking for.
	TenantSuspendedAction audit.Action = "tenant.suspended"
	TenantResumedAction   audit.Action = "tenant.resumed"
)
View Source
const (
	MoveTenantPlanName = "MoveTenantPlan"

	// TenantPlanChangedAction is written into the workspace's own trail and the instance journal:
	// its people are entitled to see when their limits and features changed and from which plan to
	// which (UC-INS-13 check 6).
	TenantPlanChangedAction audit.Action = "tenant.plan_changed"
)
View Source
const (
	OpenTenantPasswordName  = "OpenTenantPassword"
	CloseTenantPasswordName = "CloseTenantPassword"

	// TenantPasswordOpenedAction and TenantPasswordClosedAction are written into the workspace's own
	// trail. The opening is WARNING: it is the entry somebody asking "how did a password get in while
	// we only use our directory" is looking for.
	TenantPasswordOpenedAction audit.Action = "tenant.password_opened"
	TenantPasswordClosedAction audit.Action = "tenant.password_closed"
)
View Source
const (
	// OpeningEndedByOperator is an operator closing it before its time.
	OpeningEndedByOperator = "OPERATOR"
	// OpeningEndedExpired is its time passing. The password closed at that moment whatever was
	// recorded; this is the entry that says so afterwards.
	OpeningEndedExpired = "EXPIRED"
)

How an opening ended, as the close's entries say it. Named without "password", NewOpening's reason: these are codes, not credentials.

View Source
const (
	ListPlansName  = "ListPlans"
	ReadPlanName   = "ReadPlan"
	CreatePlanName = "CreatePlan"
	ChangePlanName = "ChangePlan"
	RemovePlanName = "RemovePlan"
)
View Source
const (
	ListPlatformEventsName = "ListPlatformEvents"

	// PlatformEventsRetainDays is how long the installation's events are kept, and so how far back
	// a cursor may reach (docs/privacy/data-catalog.md): long enough for a platform down over a
	// holiday, short enough not to keep a reference longer than the deliveries do.
	PlatformEventsRetainDays = 30
)
View Source
const (
	ListPlatformSubscriptionsName  = "ListPlatformSubscriptions"
	CreatePlatformSubscriptionName = "CreatePlatformSubscription"
	ReadPlatformSubscriptionName   = "ReadPlatformSubscription"
)
View Source
const (
	UpdatePlatformSubscriptionName       = "UpdatePlatformSubscription"
	DeletePlatformSubscriptionName       = "DeletePlatformSubscription"
	RotatePlatformSubscriptionSecretName = "RotatePlatformSubscriptionSecret"
)
View Source
const (
	ListPlatformDeliveriesName        = "ListPlatformDeliveries"
	RedeliverPlatformSubscriptionName = "RedeliverPlatformSubscription"
)
View Source
const (
	ProvisionTenantName = "ProvisionTenant"

	// TenantProvisionedAction is the audit code, written into the new tenant's own trail: its
	// members are entitled to see how their workspace began (audit.md §6).
	TenantProvisionedAction audit.Action = "tenant.provisioned"
)
View Source
const (
	UpdateTenantQuotasName = "UpdateTenantQuotas"

	// TenantQuotasChangedAction is the trail's record that the wall moved. Warning, the backup
	// schedule's reasoning: a quota decides what a workspace may hold from now on, without
	// anybody being asked again.
	TenantQuotasChangedAction audit.Action = "tenant.quotas_changed"
)
View Source
const (
	UpdateTenantName = "UpdateTenant"
	ReadTenantName   = "ReadTenant"
	QueryTenantsName = "QueryTenants"

	// TenantReferenceChangedAction is written into the workspace's trail - masked - and the journal,
	// without the value.
	TenantReferenceChangedAction audit.Action = "tenant.reference_changed"
)
View Source
const AccessibilityCommand = "hubctl admin settings set legal.accessibility_url <address>"

AccessibilityCommand is how an operator sets the statement from a terminal, which the hint names beside the screen. Not a sentence: the catalogue says what to do with it.

View Source
const DeletionGrace = 30 * 24 * time.Hour

DeletionGrace is §5's 30 days, by the clock: the hard delete job's RunAt, and the purge_after the guard re-reads when that moment comes.

View Source
const HeldPurgeRetry = 24 * time.Hour

HeldPurgeRetry is how often the grace job of a workspace under a legal hold looks again. Nobody can lift a hold in a workspace whose people are shut out, so the release seeds nothing; the job that waits is what makes the deletion follow within a day of the last release, whatever lifted it.

View Source
const PlatformEventsRetention = PlatformEventsRetainDays * 24 * time.Hour

PlatformEventsRetention is the window as a duration.

View Source
const (

	// PlatformSweepInterval is how often the sweep comes back. Daily would do for a 30-day window;
	// hourly keeps each pass small.
	PlatformSweepInterval = time.Hour
)
View Source
const (
	ReadUsageName = "ReadUsage"
)
View Source
const RestoreCommand = "hubtask operator add --workspace <short name> --email <address>"

RestoreCommand is the command the health report names for an installation nobody can run (UC-INS-03 check 4). Not a sentence: the catalogue says what to do with it.

Variables

This section is empty.

Functions

func ExportArchiveName

func ExportArchiveName(tenantID shared.ID, at time.Time) string

ExportArchiveName is the archive's name at the target. Its own prefix, deliberately outside `hubtask-backup-<tenant>-`: the generation pruning counts and deletes under that prefix, and an export must not be pruned as though it were a generation (tenant-export.md §1). A restore can still name it by path.

func InstanceLevelOf

func InstanceLevelOf(document map[string]any) (identityrepo.InstanceLevel, error)

InstanceLevelOf is `instanceLevelFrom` for the one caller outside this package: the instance file, the third of §5's three doors, which goes through the same reading the other two do.

func InstanceProviderOutput

func InstanceProviderOutput(configured domainidentity.IdentityProvider) usecase.Output

InstanceProviderOutput is the operator's projection: a workspace's, and the count beside it. The count is in this one and in no other, because only the installation may know it (ADR-0076 §1).

func OfferedAiOutput

func OfferedAiOutput(offered integration.AiProvider) usecase.Output

OfferedAiOutput is what an operator reads of an offered model: everything but the key.

func PlatformDeliveryOutput

func PlatformDeliveryOutput(d integration.PlatformDelivery) usecase.Output

PlatformDeliveryOutput is the contract's PlatformDelivery.

func PlatformSubscriptionOutput

func PlatformSubscriptionOutput(s integration.PlatformSubscription) usecase.Output

PlatformSubscriptionOutput is the contract's PlatformSubscription.

Types

type AccessibilityWarnings

type AccessibilityWarnings struct {
	Tenancy    env.TenancyMode
	Settings   identityrepo.InstanceSettings
	UnitOfWork persistence.UnitOfWork
}

AccessibilityWarnings is the health report's hint that an installation serving other people names no accessibility statement (UC-ID-18).

Only where the installation runs workspaces for others - multi mode, a provider for consumers or for companies - and never on a private installation, which owes nobody a statement (P-10). Only the installation's own link counts: a provider whose workspaces each set their own still offers the sign-in card of every workspace that set none. Advice rather than a fault, because a provider may lawfully owe none (UC-ID-18, *Where it ends*): a level that cannot be read gives no hint, and says so in the log.

func (AccessibilityWarnings) Warnings

Warnings answers the hint where it holds.

type AccountSeeder

type AccountSeeder interface {
	Insert(ctx context.Context, account domain.Account) error
}

The remaining collaborators, each as the one-method slice provisioning actually uses - a seeded row is inserted and never read back, and a fake of one method is a test that stays about provisioning.

type AddOperator

type AddOperator struct{ Writer InstanceWriter }

AddOperator puts an account in the register.

func (AddOperator) Descriptor

func (h AddOperator) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (AddOperator) Execute

func (h AddOperator) Execute(
	ctx context.Context, actor appshared.ActorContext, accountID shared.ID,
) error

Execute adds. An account that is already an operator is not an error: the caller asked for somebody to be one, and they are.

func (AddOperator) ExecuteByAddress

func (h AddOperator) ExecuteByAddress(
	ctx context.Context, actor appshared.ActorContext, slug, email string,
) error

ExecuteByAddress names the account the way a person can: the workspace it is in, and the address it signs in with.

The identifier form stays — a script that already has one keeps working — and this is the form a screen uses, because an account id is not something an operator can look up at all: `account` is behind row level security, so the control plane cannot list accounts across workspaces. Resolution happens through the register's own narrow door and answers one identifier or nothing.

type BucketSeeder

type BucketSeeder interface {
	Insert(ctx context.Context, bucket work.Bucket) error
}

The remaining collaborators, each as the one-method slice provisioning actually uses - a seeded row is inserted and never read back, and a fake of one method is a test that stays about provisioning.

type CancelInstanceIdentityProviderWithdrawal

type CancelInstanceIdentityProviderWithdrawal struct{ Writer InstanceProviderWriter }

CancelInstanceIdentityProviderWithdrawal keeps offering it.

func (CancelInstanceIdentityProviderWithdrawal) Descriptor

func (CancelInstanceIdentityProviderWithdrawal) Execute

Execute cancels.

type CancelTenantDeletion

type CancelTenantDeletion struct {
	Tenants     adminrepo.Tenants
	Automations PausedAutomations
	Journal     adminrepo.Journal
	Audit       audit.Sink
	UnitOfWork  persistence.UnitOfWork
	Clock       clock.Clock
	IDs         clock.IDGenerator
	// Platform tells a purchase platform the deletion was called off (UC-INS-14).
	Platform platform.Recorder
}

CancelTenantDeletion is §5's edge back from PENDING_DELETION (UC-INS-08 check 5): the workspace returns to the status it was requested from - SUSPENDED where none was kept, so nobody regains access the operator did not choose - the deadline is cleared, and the rules the request switched off are switched back on.

No step-up and no typed name: it removes nothing and returns access, as ResumeTenant does. No hold check either: a legal hold keeps a pending workspace pending, and this is the way out of that state (data-protection.md §5) - the holds stay as they are. What it may not meet is a purge that has begun; the grace job's mark in the row refuses it from then on.

func (CancelTenantDeletion) Descriptor

func (h CancelTenantDeletion) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (CancelTenantDeletion) Execute

Execute calls the deletion off and answers the status the workspace is in afterwards.

type ChangeInstallationPreset

type ChangeInstallationPreset struct{ Writer InstallationPresetWriter }

ChangeInstallationPreset changes one.

func (ChangeInstallationPreset) Descriptor

Descriptor is the catalogue entry.

func (ChangeInstallationPreset) Execute

Execute changes it. With `expected`, a preset somebody changed since it was read is refused.

type ChangePlan

type ChangePlan struct{ Writer PlanWriter }

ChangePlan replaces a plan's display name and values.

func (ChangePlan) Descriptor

func (h ChangePlan) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ChangePlan) Execute

func (h ChangePlan) Execute(
	ctx context.Context, actor appshared.ActorContext, cmd PlanCommand, expected *int,
) (PlanView, error)

Execute changes it. With `expected`, a plan somebody saved since it was read is refused.

type CloseTenantPassword

type CloseTenantPassword struct{ Writer PasswordOpeningWriter }

CloseTenantPassword ends an opening before its time.

func (CloseTenantPassword) Descriptor

func (h CloseTenantPassword) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (CloseTenantPassword) Execute

Execute closes it. A workspace with no opening answers as it is.

type ConfigureInstanceAiCommand

type ConfigureInstanceAiCommand struct {
	ID              shared.ID
	DisplayName     string
	Processor       string
	Kind            integration.AiProviderKind
	BaseURL         string
	CompletionModel string
	EmbeddingModel  string
	Jurisdiction    integration.AiJurisdiction
	// APIKey is present only when the caller sent one: absent keeps the stored key on a change,
	// present and empty clears it.
	APIKey        secret.Secret
	APIKeyPresent bool
}

ConfigureInstanceAiCommand is the input, typed. A zero ID offers a new model.

type ConfigureInstanceAiProvider

type ConfigureInstanceAiProvider struct{ Writer InstanceAiWriter }

ConfigureInstanceAiProvider offers a model, or changes one.

func (ConfigureInstanceAiProvider) Descriptor

func (ConfigureInstanceAiProvider) Execute

Execute validates, checks the transfer, seals the key and stores the lot - in that order, so a refused model leaves no envelope and no row behind.

type ConfigureInstanceIdentityProvider

type ConfigureInstanceIdentityProvider struct{ Writer InstanceProviderWriter }

ConfigureInstanceIdentityProvider adds or replaces one of them.

func (ConfigureInstanceIdentityProvider) Descriptor

func (ConfigureInstanceIdentityProvider) Execute

Execute writes it.

The system scope rather than the installation's, for `WriteInstanceSettings`' reason: an installation scope is read-only by construction, and this is the scope with no tenant that may write. What bounds it is the same as there - every table with a tenant column stays invisible, and the provider row this reaches is the one whose tenant is NULL.

type ContainerSeeder

type ContainerSeeder interface {
	Insert(ctx context.Context, container work.Container) (work.Container, error)
}

The remaining collaborators, each as the one-method slice provisioning actually uses - a seeded row is inserted and never read back, and a fake of one method is a test that stays about provisioning.

type CreateInstallationPreset

type CreateInstallationPreset struct{ Writer InstallationPresetWriter }

CreateInstallationPreset adds one.

func (CreateInstallationPreset) Descriptor

Descriptor is the catalogue entry.

func (CreateInstallationPreset) Execute

Execute creates it.

type CreateInstanceBackupTarget

type CreateInstanceBackupTarget struct{ Writer InstanceBackupTargetWriter }

CreateInstanceBackupTarget configures one.

func (CreateInstanceBackupTarget) Descriptor

func (CreateInstanceBackupTarget) Execute

Execute validates, opens, seals and stores - the step-up first, so a refused proof leaves no envelope behind - and journals the configuration, never the credential.

type CreatePlan

type CreatePlan struct{ Writer PlanWriter }

CreatePlan writes a new plan.

func (CreatePlan) Descriptor

func (h CreatePlan) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (CreatePlan) Execute

func (h CreatePlan) Execute(ctx context.Context, actor appshared.ActorContext, cmd PlanCommand) (PlanView, error)

Execute creates it.

type CreatePlatformSubscription

type CreatePlatformSubscription struct{ Writer PlatformWriter }

CreatePlatformSubscription subscribes a purchase platform: every installation event is sent to the target, signed with the secret answered here once.

func (CreatePlatformSubscription) Descriptor

func (CreatePlatformSubscription) Execute

Execute checks, seals and stores - the step-up first, so a refused proof leaves nothing behind.

type DeleteInstanceBackupTarget

type DeleteInstanceBackupTarget struct{ Writer InstanceBackupTargetWriter }

DeleteInstanceBackupTarget removes one, and its sealed credential. Nothing at the target is touched. One that is not there is not an error.

func (DeleteInstanceBackupTarget) Descriptor

func (DeleteInstanceBackupTarget) Execute

func (h DeleteInstanceBackupTarget) Execute(
	ctx context.Context, actor appshared.ActorContext, id shared.ID, stepUpToken string,
) error

Execute removes it.

type DeletePlatformSubscription

type DeletePlatformSubscription struct{ Writer PlatformWriter }

DeletePlatformSubscription unsubscribes; the deliveries go with it, the events stay readable.

func (DeletePlatformSubscription) Descriptor

func (DeletePlatformSubscription) Execute

Execute removes it.

type DeletionScheduled

type DeletionScheduled struct {
	TenantID   shared.ID
	PurgeAfter time.Time
}

DeletionScheduled is the answer: when the grace runs out.

type DeliveryCursors

type DeliveryCursors interface {
	EncodeID(id shared.ID) string
	DecodeID(cursor string) (shared.ID, error)
}

DeliveryCursors turn the last delivery of a page into an opaque cursor and back.

type EndPasswordOpening

type EndPasswordOpening struct{ Writer PasswordOpeningWriter }

EndPasswordOpening records the end of an opening whose time has passed (KindPasswordOpeningEnd).

The password closed at that moment whatever this does: the end is honoured where the opening is read. What is left is what a person can see - the trail entry, the journal entry, the notices - written here, in the workspace's own transaction, once.

func (EndPasswordOpening) Execute

func (h EndPasswordOpening) Execute(ctx context.Context, tenantID shared.ID) (time.Duration, error)

Execute ends what is due and answers how long until it should look again: zero where nothing is left to wait for - the opening ended now, or was closed before, or never was.

type EntitlementGate

type EntitlementGate struct {
	Plans      repository.Entitlements
	UnitOfWork persistence.UnitOfWork
}

EntitlementGate refuses an operation whose feature the caller's workspace's plan leaves out (multi-tenancy.md §4.2): one place, on the registry every door comes through - REST, MCP and a rule's action - rather than a check in each of the operations that declare a feature.

It asks before the operation's own authorisation does. What a plan leaves out is no secret in the workspace - `/meta/capabilities` answers it to every signed-in person there - and the refusal names only the feature.

func (EntitlementGate) LeftOut

LeftOut answers what the actor's workspace's plan leaves out, in its own read - which joins the transaction the caller already runs in, as a rule's action does. An actor without a workspace, or a gate wired without a reader, reads an empty set.

func (EntitlementGate) Wrap

Wrap puts the gate inside the composition root's middleware, so that the metric and the span still count an operation the plan refuses.

type EventRendering

type EventRendering interface {
	Render(envelope event.Envelope) map[string]any
}

EventRendering turns an envelope into the CloudEvent a subscriber receives - the adapter's mapping, so the feed answers the very document a delivery sends.

type ExportAccepted

type ExportAccepted struct {
	JobID    shared.ID
	ExportID shared.ID
}

ExportAccepted is the 202: the job, and the export's own identity (which is also the archive_id in the manifest at the target).

type ExportArchiveRequest

type ExportArchiveRequest struct {
	ExportID shared.ID
	TenantID shared.ID
	TargetID shared.ID
}

ExportArchiveRequest is what the job carries, decoded here rather than in the worker (ADR-0008): the payload's shape belongs beside the code that wrote it.

func ExportRequestOf

func ExportRequestOf(payload map[string]any, tenantID shared.ID) (ExportArchiveRequest, error)

ExportRequestOf reads the job payload back.

type ExportQuota

type ExportQuota interface {
	ExportJobs(ctx context.Context, tenant string) error
}

ExportQuota is the concurrency ceiling of multi-tenancy.md §4, resolved per workspace by the quota engine rather than held as this use case's own constants.

type ExportTargets

type ExportTargets interface {
	Usable(ctx context.Context, tenantID, targetID shared.ID) error
}

ExportTargets answers whether an export could write to a target now: the workspace's own or the installation's, existing and enabled (backup.ExportStoreOpener).

type ExportTenant

type ExportTenant struct {
	Tenants adminrepo.Tenants
	// Targets checks the target when the export is accepted, so a wrong identifier is a refusal
	// the operator sees rather than a job that fails. Nil skips the check.
	Targets    ExportTargets
	Quota      ExportQuota
	Jobs       JobQueue
	Audit      audit.Sink
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
}

ExportTenant accepts one workspace export: a job that writes the complete, documented archive of tenant-export.md to a configured backup target. It works for ACTIVE, SUSPENDED and PENDING_DELETION workspaces alike - the suspended and the leaving are exactly who needs it - and it is audited with its target, never its content.

func (ExportTenant) Descriptor

func (h ExportTenant) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ExportTenant) Execute

Execute enqueues the export.

type ExportTenantCommand

type ExportTenantCommand struct {
	TenantID shared.ID
	TargetID shared.ID
}

ExportTenantCommand is the input, typed.

type FileSubscription

type FileSubscription struct {
	TargetURL string
	Secret    secret.Secret
}

FileSubscription is a subscription the instance file declares, its secret read from the file it names.

type HardDeleteOutcome

type HardDeleteOutcome struct {
	// Deleted is false when the guard held the act back: the deadline moved, the status is not
	// PENDING_DELETION any more, or the tenant is already gone. Nothing was changed then.
	Deleted bool
	// Held is a workspace a legal hold keeps: it stays pending until the last hold is lifted
	// (data-protection.md §5), and nothing of it was touched.
	Held bool
	// RunAgainIn is when the job is to come back: a day while a hold stands, the time left while
	// the deadline still runs. Zero ends the job.
	RunAgainIn   time.Duration
	Footprint    adminrepo.Footprint
	BytesObjects int
	TrailEntries int64
	JobsRemoved  int
}

HardDeleteOutcome is what one pass reports: counts, for the job's own log line and nothing else - the durable record is the evidence entry.

type HardDeleteTenant

type HardDeleteTenant struct {
	Tenants adminrepo.Tenants
	// Holds are the workspace's legal holds in force; while any stands nothing falls.
	Holds      lifecyclerepo.LegalHolds
	Purge      adminrepo.Purge
	Journal    adminrepo.Journal
	Store      storage.ObjectStore
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
	// Platform tells a purchase platform the workspace is gone, and removes the platform's
	// reference from the workspace's kept events with it (UC-INS-14, multi-tenancy.md §4.3).
	Platform platform.Recorder
}

HardDeleteTenant is §5's final act, run by the grace job the deletion request seeded.

Not a use case in the registry: no credential can ask for it, only the clock. It deletes the media bytes store-first (ReconcileMedia's order), then in one transaction fells the structure, clears what no cascade reaches, writes the evidence entry, purges the trail through migration 0067's narrow act, and lets the tenant row's cascade take the rest - evidence and act commit together, so there is never a deletion without evidence.

It is safe to run twice: the byte deletion tolerates absent objects, and the final guard re-reads the two facts the grace could have changed before anything falls. Before the first byte it marks the row, which is what a cancel of the deletion is refused by from then on.

func (HardDeleteTenant) Execute

func (h HardDeleteTenant) Execute(
	ctx context.Context, tenantID, jobID shared.ID,
) (HardDeleteOutcome, error)

Execute performs one hard delete. jobID names the queue row running this pass, which survives to report its own completion.

type InstallationPresetCommand

type InstallationPresetCommand struct {
	Name *string
	// From is one of Hubtask's presets whose values the preset takes, before Settings.
	From     domain.PresetKey
	Settings domain.StructureValues
	// IsDefault makes the preset the default, or none the default.
	IsDefault *bool
	// Locks is the whole set of locks, where LocksSet says the caller named it.
	Locks    []string
	LocksSet bool
	// Whole is the instance file's reading: the document describes the preset entirely, so its
	// settings start from Hubtask's default where it names no preset of Hubtask's.
	Whole bool
}

InstallationPresetCommand is what a create or a change carries. A field left nil or unset is left as it is; a create starts from Hubtask's default.

type InstallationPresetFileOutcome

type InstallationPresetFileOutcome struct {
	Skipped                     bool
	Created, Changed, Withdrawn int
}

InstallationPresetFileOutcome is what writing the file's presets came to: nothing in single mode, and how many were created, changed and withdrawn.

type InstallationPresetWriter

type InstallationPresetWriter struct {
	Instance InstanceWriter
	Presets  workrepo.InstallationPresets
	Profiles metarepo.CapabilityProfiles
	Tenancy  env.TenancyMode
	Text     text.Normalizer
}

InstallationPresetWriter is what the preset use cases share: the instance writer for the register, the level and the journal; the presets; the profiles they are held to; the mode.

func (InstallationPresetWriter) ApplyFile

func (w InstallationPresetWriter) ApplyFile(
	ctx context.Context, documents []map[string]any, enforce bool, source string,
) (InstallationPresetFileOutcome, error)

ApplyFile writes the instance file's presets, the third door (ADR-0070 §5) for them: `seed` writes them where the installation has none yet; `enforce` writes them at every start and withdraws the presets the file no longer names. A preset is known by its name. Each passes the reading and the checks the API's writes pass, as the file (UC-INS-18 check 1).

type InstanceAiWriter

type InstanceAiWriter struct {
	// Instance carries the scope, the register, the journal, the clock and the identifiers.
	Instance InstanceWriter
	Offered  integrationrepo.OfferedAiProviders
	// Encryptor seals the key between the request and the store.
	Encryptor crypto.Encryptor
	// ThirdCountryConfirmed is the installation's confirmation of third-country transfers, the
	// same one a workspace's own model is held to: an offered model outside the EEA is a transfer
	// whoever chooses it makes.
	ThirdCountryConfirmed bool
}

InstanceAiWriter is the control plane's half of the AI provider store.

func (InstanceAiWriter) ApplyFile

func (w InstanceAiWriter) ApplyFile(
	ctx context.Context, models []ConfigureInstanceAiCommand, enforce bool, source string,
) error

ApplyFile is the instance file's door (ADR-0070 §5, D9 of the readiness record): the models the file names, matched to offered ones by name. `seed` offers them only where nothing is offered yet; `enforce` makes the file the list - named models are offered or changed, and every other offered model is withdrawn. A model whose key the file names by variable gets it; one whose key it does not name keeps the sealed one. The journal names the file.

type InstanceBackupTargetWriter

type InstanceBackupTargetWriter struct {
	Instance InstanceWriter
	Targets  backuprepo.Targets
	// Backup prepares and probes a target exactly as a workspace's is prepared and probed.
	Backup backupservice.Writer
	StepUp stepup.Verifier
}

InstanceBackupTargetWriter is the control plane's half of the targets: the backup context's validation, sealing and probe, under the installation's scope and the operator's authorisation.

type InstanceProviderWriter

type InstanceProviderWriter struct {
	// Instance carries the pair every operation here demands - the scope, then the register - and
	// the journal the installation's own events are read from.
	Instance InstanceWriter
	// Providers is the store, read and written under the scope that has no tenant.
	Providers identityrepo.IdentityProviders
	// Configure is the writer the workspace's own half uses. Shared on purpose.
	Configure identityservice.IdentityProviderWriter
	// Tenants lists the workspaces a removal tells its people in (ADR-0078 §6). It reads through
	// the installation's enumerator, so where that answers none - an owner without the right to
	// read across workspaces (multi-tenancy.md §2.1.1) - no workspace is told. Nil tells nobody.
	Tenants adminrepo.Tenants
}

InstanceProviderWriter is the control plane's half of the provider store.

type InstanceWriter

type InstanceWriter struct {
	Settings identityrepo.InstanceSettings
	// Tenancy is the mode: in multi mode plans exist, and the account link's lock binds them.
	Tenancy    env.TenancyMode
	Operators  identityrepo.Operators
	Journal    adminrepo.Journal
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
}

InstanceWriter is what the control plane's instance use cases share.

type JobQueue

type JobQueue interface {
	Enqueue(ctx context.Context, request queue.Request) (shared.ID, error)
}

JobQueue is the one-method slice the deletion request needs of the queue: seeding the grace job, and pulling the tenant's backup poller forward.

type JournalPage

type JournalPage struct {
	Entries []adminrepo.InstanceEvent
	Page    adminrepo.PageInfo
}

JournalPage is one page of it.

type LabelSeeder

type LabelSeeder interface {
	Insert(ctx context.Context, label work.Label) error
}

The remaining collaborators, each as the one-method slice provisioning actually uses - a seeded row is inserted and never read back, and a fake of one method is a test that stays about provisioning.

type LifecycleShift

type LifecycleShift struct {
	Tenants    adminrepo.Tenants
	Journal    adminrepo.Journal
	Audit      audit.Sink
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
	// Tenancy is read by the suspension alone: in single mode it is refused (requireMulti).
	Tenancy env.TenancyMode
	// Platform tells a purchase platform the workspace was suspended or is active again (UC-INS-14).
	Platform platform.Recorder
}

LifecycleShift is the shared shape of the two one-write edges: suspend and resume.

type LimitExceeded

type LimitExceeded struct {
	Quota string
	Limit int64
	Used  int64
}

LimitExceeded is one capacity over its resolved limit, the operator's exception included.

type ListInstallationPresets

type ListInstallationPresets struct{ Writer InstallationPresetWriter }

ListInstallationPresets answers the installation's presets.

func (ListInstallationPresets) Descriptor

Descriptor is the catalogue entry.

func (ListInstallationPresets) Execute

Execute lists them.

type ListInstanceAiProviders

type ListInstanceAiProviders struct{ Writer InstanceAiWriter }

ListInstanceAiProviders answers what the installation offers.

func (ListInstanceAiProviders) Descriptor

func (ListInstanceAiProviders) Execute

Execute reads them, without a key.

type ListInstanceBackupTargets

type ListInstanceBackupTargets struct{ Writer InstanceBackupTargetWriter }

ListInstanceBackupTargets answers the installation's targets, without a credential.

func (ListInstanceBackupTargets) Descriptor

func (ListInstanceBackupTargets) Execute

Execute reads them.

type ListInstanceIdentityProviders

type ListInstanceIdentityProviders struct{ Writer InstanceProviderWriter }

ListInstanceIdentityProviders answers what the installation offers.

func (ListInstanceIdentityProviders) Descriptor

func (ListInstanceIdentityProviders) Execute

Execute reads them.

Under the installation's own scope, which answers exactly the rows that belong to no workspace: the read policy admits a tenant's own rows and the tenant-less ones, and there is no tenant here.

type ListInstanceJournal

type ListInstanceJournal struct{ Writer InstanceWriter }

ListInstanceJournal answers the installation's own record, newest first.

func (ListInstanceJournal) Descriptor

func (h ListInstanceJournal) Descriptor() usecase.Descriptor

func (ListInstanceJournal) Execute

func (h ListInstanceJournal) Execute(
	ctx context.Context, actor appshared.ActorContext, filter adminrepo.JournalFilter, cursor string, size int,
) (JournalPage, error)

Execute reads one page.

type ListOperators

type ListOperators struct{ Writer InstanceWriter }

ListOperators answers the register.

func (ListOperators) Descriptor

func (h ListOperators) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ListOperators) Execute

Execute lists it.

type ListPlans

type ListPlans struct{ Writer PlanWriter }

ListPlans answers the plans not removed.

func (ListPlans) Descriptor

func (h ListPlans) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ListPlans) Execute

func (h ListPlans) Execute(ctx context.Context, actor appshared.ActorContext) ([]PlanView, error)

Execute lists them.

type ListPlatformDeliveries

type ListPlatformDeliveries struct {
	Writer     PlatformWriter
	Deliveries adminrepo.PlatformDeliveries
	Cursors    DeliveryCursors
}

ListPlatformDeliveries answers what was sent to a subscription and what became of it, newest first, a page at a time (UC-INS-14 check 5).

func (ListPlatformDeliveries) Descriptor

func (h ListPlatformDeliveries) Descriptor() usecase.Descriptor

func (ListPlatformDeliveries) Execute

func (h ListPlatformDeliveries) Execute(
	ctx context.Context, actor appshared.ActorContext, id shared.ID, status, cursor string, size int,
) (PlatformDeliveriesPage, error)

Execute reads one page.

type ListPlatformEvents

type ListPlatformEvents struct {
	Writer    InstanceWriter
	Events    adminrepo.PlatformEvents
	Cursors   PlatformCursors
	Rendering EventRendering
	Tenancy   env.TenancyMode
}

ListPlatformEvents reads the installation's events forward from a cursor (UC-INS-14 check 6): the same events the installation's subscriptions are sent, so a platform that was down reads what it missed. Numbered in commit order, so a page never skips an event that commits later.

func (ListPlatformEvents) Descriptor

func (h ListPlatformEvents) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ListPlatformEvents) Execute

func (h ListPlatformEvents) Execute(
	ctx context.Context, actor appshared.ActorContext, cursor string, size int,
) (PlatformEventsPage, error)

Execute reads one page.

type ListPlatformSubscriptions

type ListPlatformSubscriptions struct{ Writer PlatformWriter }

ListPlatformSubscriptions answers the installation's subscriptions.

func (ListPlatformSubscriptions) Descriptor

func (ListPlatformSubscriptions) Execute

Execute reads them.

type ListTenants

type ListTenants struct {
	Tenants    adminrepo.Tenants
	UnitOfWork persistence.UnitOfWork
	// Clock judges whether an operator's opening still stands. Nil answers none: without a moment
	// an ended opening would read as one in force.
	Clock clock.Clock
}

ListTenants answers the installation's workspaces - the one legitimate enumerator (multi-tenancy.md §2.1), behind the admin scope, through the installation-scoped read path.

func (ListTenants) Descriptor

func (h ListTenants) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ListTenants) Execute

Execute lists the workspaces, oldest first.

type MembershipSeeder

type MembershipSeeder interface {
	Grant(ctx context.Context, grant domain.Grant) (held domain.Grant, created bool, err error)
}

The remaining collaborators, each as the one-method slice provisioning actually uses - a seeded row is inserted and never read back, and a fake of one method is a test that stays about provisioning.

type MintedPlatformSubscription

type MintedPlatformSubscription struct {
	Subscription integration.PlatformSubscription
	Secret       secret.Secret
}

MintedPlatformSubscription is the subscription with its secret, in clear, for the only time.

type MoveTenantPlan

type MoveTenantPlan struct {
	Writer  PlanWriter
	Tenants adminrepo.Tenants
	Quotas  quotarepo.Store
	// Usage counts what the workspace holds, for the limits it is now over. Nil answers none.
	Usage quotarepo.Usage
	Audit audit.Sink
	// Platform tells a purchase platform the workspace moved (UC-INS-14).
	Platform platform.Recorder
}

MoveTenantPlan moves a workspace between plans. Nothing is copied into the workspace and nothing is deleted: what the plan decides applies at the workspace's next request (UC-INS-13 checks 2, 4).

func (MoveTenantPlan) Descriptor

func (h MoveTenantPlan) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (MoveTenantPlan) Execute

Execute moves it.

type MoveTenantPlanCommand

type MoveTenantPlanCommand struct {
	TenantID shared.ID
	Plan     string
	// ExpectedVersion is the workspace's version as the caller last read it; nil moves whatever
	// stands. A stale one is refused, so a late retry of an older move cannot undo a newer one.
	ExpectedVersion *int
	// DryRun answers what the move would change and writes nothing.
	DryRun bool
}

MoveTenantPlanCommand puts one workspace on a plan, or on none where Plan is empty.

type MovedTenant

type MovedTenant struct {
	Tenant          adminrepo.TenantRecord
	FeaturesResting []plan.Feature
	ExceptionsKept  []string
	// LimitsExceeded is every counted capacity the workspace now holds more of than its limit
	// allows: nothing is removed, the next creation is refused (P-03).
	LimitsExceeded []LimitExceeded
}

MovedTenant is what a move answers: the workspace on its plan, the features its plan now leaves out, and the limits for which it keeps an operator's exception - the exception stays the last word, so a platform that sold an add-on sees where to set it again (multi-tenancy.md §4.3).

type OpenTenantPassword

type OpenTenantPassword struct{ Writer PasswordOpeningWriter }

OpenTenantPassword opens the password for one named workspace.

func (OpenTenantPassword) Descriptor

func (h OpenTenantPassword) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (OpenTenantPassword) Execute

Execute opens it.

type OpenTenantPasswordCommand

type OpenTenantPasswordCommand struct {
	TenantID shared.ID
	// Hours is how long it stands. The channel fills in the default day where the caller named none,
	// so that a zero somebody sent is refused rather than read as "the default".
	Hours     int
	Requester string
	Reason    string
	// StepUpToken is the step-up proof (identity.md §16), consumed by this one request.
	StepUpToken string
}

OpenTenantPasswordCommand is the input, typed.

type OpeningNotices

type OpeningNotices interface {
	PasswordOpened(ctx context.Context, tenantID shared.ID, until time.Time) error
	PasswordClosed(ctx context.Context, tenantID shared.ID, ended string) error
}

OpeningNotices tells a workspace's administrators about an opening of its password (ADR-0078 §3): when it opens, with its end, and when it closes, with how. Called inside the transaction that opened or closed it, so the act and its notices commit together.

type OperatorsFromEnvironment

type OperatorsFromEnvironment struct {
	Entries    []env.OperatorEntry
	Operators  identityrepo.Operators
	Single     SingleOperatorResolver
	Journal    adminrepo.Journal
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
}

OperatorsFromEnvironment is HUBTASK_OPERATORS (UC-INS-02 checks 4, 5): every entry whose account exists is put into the register, at start and again as accounts come to exist; an entry whose account does not exist yet waits, and the health report names it.

The environment adds and never removes: an operator added through the screen stays when the variable no longer names them, and one the variable names stays when somebody removes it from the variable. Authorisation is the configuration itself - whoever sets the server's environment runs the installation - so nothing here asks for a scope, and nothing here is on a route.

func (OperatorsFromEnvironment) Apply

Apply adds every entry whose account exists, records each one that was not in the register yet, and answers the entries still waiting for their account.

func (OperatorsFromEnvironment) Pending

Pending answers, without writing, the entries whose account does not exist yet.

type PasswordOpeningWriter

type PasswordOpeningWriter struct {
	// Instance is the scope and the register (ADR-0070 §1), checked as for every instance act.
	Instance InstanceWriter
	Tenants  adminrepo.Tenants
	Journal  adminrepo.Journal
	Audit    audit.Sink
	// Jobs seeds the job that records the end once the time has passed - in the workspace's own
	// write, because nothing may enumerate workspaces to find openings that ended.
	Jobs JobQueue
	// Notices tells the workspace's administrators. Nil tells nobody, which is the shape of a test
	// about something else, never of the server (cmd/server wires it).
	Notices    OpeningNotices
	StepUp     stepup.Verifier
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
	// Text brings the requester and the reason to normal form C.
	Text text.Normalizer
}

PasswordOpeningWriter is what opening and closing share.

type PausedAutomations

type PausedAutomations interface {
	Resume(ctx context.Context, tenantID shared.ID, now time.Time) (int, error)
}

PausedAutomations switches back on the rules a deletion request switched off, inside the cancel's transaction, and reports how many. Implemented by the automation package, which owns how a schedule's next moment is worked out.

type PlanCommand

type PlanCommand struct {
	Name        string
	DisplayName string
	Values      plan.Values
}

PlanCommand is what a create or a change carries.

type PlanFileOutcome

type PlanFileOutcome struct {
	Skipped bool
	Kept    []string
}

PlanFileOutcome is what writing the file's plans came to: nothing in single mode, where plans do not exist, and the plans an enforcing file no longer names that stayed because workspaces are on them.

type PlanView

type PlanView struct {
	Plan       plan.Plan
	Workspaces int64
}

PlanView is a plan as the control plane answers it: the plan and how many workspaces are on it.

type PlanWriter

type PlanWriter struct {
	Instance InstanceWriter
	Plans    adminrepo.Plans
	Tenancy  env.TenancyMode
}

PlanWriter is what the plan use cases share: the instance writer for the register, the level and the journal, the plans, and the mode - plans exist in multi mode only.

func (PlanWriter) ApplyFile

func (w PlanWriter) ApplyFile(
	ctx context.Context, documents []map[string]any, enforce bool, source string,
) (PlanFileOutcome, error)

ApplyFile writes the instance file's plans, the third door (ADR-0070 §5) for plans: `seed` writes them where no plan exists yet; `enforce` writes them at every start and removes the plans the file no longer names - except one a workspace is on, which no door can remove and which is answered as kept. It goes through the same reading and the same checks the API does, as the file, so a plan a file may set is one the API accepts (UC-INS-13 check 7).

type PlatformCursors

type PlatformCursors interface {
	Encode(position adminrepo.PlatformPosition) string
	Decode(cursor string) (adminrepo.PlatformPosition, error)
}

PlatformCursors turns a position in the installation's events into an opaque cursor and back. An interface here and a keyed HMAC in the adapters, for the trigger cursor's reason: the installation secret has no business above them.

type PlatformDeliveriesPage

type PlatformDeliveriesPage struct {
	Deliveries []integration.PlatformDelivery
	Next       shared.ID
	More       bool
}

PlatformDeliveriesPage is one page.

type PlatformEventsPage

type PlatformEventsPage struct {
	Events []map[string]any
	Cursor adminrepo.PlatformPosition
	More   bool
}

PlatformEventsPage is one read: the rendered events, oldest first, the position after them, and whether to read again at once.

type PlatformFileOutcome

type PlatformFileOutcome struct {
	Skipped bool
	// Seeded is false where `seed` found subscriptions standing and left them as they are.
	Seeded                          bool
	Created, Rotated, Kept, Removed int
}

PlatformFileOutcome is what applying the file did.

type PlatformJobs

type PlatformJobs interface {
	Enqueue(ctx context.Context, request queue.Request) (shared.ID, error)
}

PlatformJobs is where a redelivered dead letter's attempt is asked for.

type PlatformRegister

type PlatformRegister interface {
	RegisteredServiceAccount(ctx context.Context, accountID shared.ID) (bool, error)
}

PlatformRegister answers whether the operator register holds an account's own row as a service account's - not the empty register's owner, whom `Holds` also admits (migration 0106), and not a person.

type PlatformWriter

type PlatformWriter struct {
	Instance      InstanceWriter
	Subscriptions adminrepo.PlatformSubscriptions
	Encryptor     crypto.Encryptor
	Entropy       clock.Entropy
	StepUp        stepup.Verifier
	// Register waives the step-up for a service account it names as one, as the deletion request
	// does: the platform runs its own subscription. Nil demands it of everybody.
	Register PlatformRegister
	Tenancy  env.TenancyMode
	// Enforced is the instance file deciding the subscriptions: a change through the API is
	// refused, as the file's plans are.
	Enforced bool
}

PlatformWriter is the control plane's half of the installation's subscriptions for a purchase platform (UC-INS-14): the operator's authorisation, multi mode, the sealing of the secret and the step-up a new address demands.

func (PlatformWriter) ApplyFile

func (w PlatformWriter) ApplyFile(ctx context.Context, declared []FileSubscription, enforce bool) (PlatformFileOutcome, error)

ApplyFile writes the file's subscriptions, as the file.

`seed` writes them once, where no subscription stands yet, and leaves them the operator's from then on - a later start that put the file's secret back would undo a rotation the platform was already given. `enforce` makes the file the whole set at every start: a target it names is kept and marked as the file's, its secret rotated only where the file's differs, so a restart writes nothing; every other subscription goes. Single mode keeps none. Every subscription the file creates, rotates or removes is journalled.

type ProvisionTenant

type ProvisionTenant struct {
	Tenants adminrepo.Tenants
	// Plans holds the plan a workspace starts on. Nil provisions on none, as an installation
	// without plans does.
	Plans    adminrepo.Plans
	Journal  adminrepo.Journal
	Accounts AccountSeeder
	// Domains brings the owner's address to its stored form.
	Domains text.DomainEncoder
	// Text brings the seeded names to normal form C: they are rendered from the catalogue in the
	// workspace's language, and a translator's file is as free to carry combining marks as a
	// keyboard is (i18n-l10n.md §5).
	Text       text.Normalizer
	Redemption RedemptionTokens
	Grants     MembershipSeeder
	Containers ContainerSeeder
	Buckets    BucketSeeder
	Labels     LabelSeeder
	Events     outbox.Events
	Changes    changelog.ChangeLog
	Audit      audit.Sink
	// Platform tells a purchase platform the workspace exists (UC-INS-14).
	Platform platform.Recorder
	// Renderer names the seeded structure in the workspace's own locale: the names are content
	// once created, so they are written in the language the workspace declared, not in a code
	// (i18n-l10n.md §2, rule 8 - the backend renders here because creation is the boundary).
	Renderer   i18n.Renderer
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
	HLC        clock.HLCSource
	Entropy    clock.Entropy
	// Tenancy is the installation's mode. Provisioning a second workspace only exists in multi
	// mode: single mode's whole contract is "exactly one tenant, no selection" (§1).
	Tenancy env.TenancyMode
	// Hosts records the one host the new workspace answers at. Optional: a build wired
	// without it provisions without a host - nothing resolves through the table, so a missing row
	// costs nothing.
	Hosts identityrepo.TenantHosts
	// Jobs seeds the new workspace's retention sweep in the provisioning transaction
	// (data-retention.md §5): nothing may enumerate tenants (multi-tenancy.md §2.1), so the write
	// that creates the workspace asks for it. Nil is a build without a queue, which only a test makes.
	Jobs JobQueue
	// RetentionInterval is the sweep's own cadence, the first run one interval out.
	RetentionInterval time.Duration
	// InstallationHost is this installation's own host, from which the canonical one is derived: the
	// slug in front of it in multi mode, and the host itself in single mode. From the configured
	// base URL at composition, never from a request - a host a caller chooses is a host a caller
	// could claim.
	InstallationHost string
	// Instance is the installation's level, whose language and zone a workspace provisioned without
	// its own starts with. Nil is a build without the level: `en` and `UTC`.
	Instance identityrepo.InstanceSettings
}

ProvisionTenant creates a workspace with its defaults, exactly the §5 table: the tenant row, the owner as an invited account with a redemption token, the OWNER membership, a default hub, an example collection, standard buckets and labels - one transaction, so a workspace either exists whole or not at all. Idempotent under `Idempotency-Key` through the middleware every POST already has.

func (ProvisionTenant) Descriptor

func (h ProvisionTenant) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry (usecase.Descriptor's contract).

func (ProvisionTenant) Execute

Execute provisions the workspace and answers the owner's way in.

func (ProvisionTenant) SeedWorkspace

func (h ProvisionTenant) SeedWorkspace(
	ctx context.Context, actor appshared.ActorContext, tenant domain.Tenant, owner domain.Account,
	now time.Time,
) (shared.ID, shared.ID, error)

SeedWorkspace writes a new workspace as multi-tenancy.md §5 draws it, inside the caller's transaction, which must run in the new tenant's own scope: the tenant row, the owner account as given, the OWNER membership, the canonical host, the default hub and example collection with their buckets and labels, the provisioning in the new trail and the first retention sweep. It answers the default hub and the example collection.

Provisioning and the first start's setup both call it, so a workspace is the same however it began; what differs - the owner's way in and the journal's entry - stays with each caller.

type ProvisionTenantCommand

type ProvisionTenantCommand struct {
	Slug            string
	DisplayName     string
	DefaultLocale   string
	DefaultTimeZone string
	OwnerEmail      string
	// OwnerDisplayName may be empty; the address stands in until the person introduces
	// themselves.
	OwnerDisplayName string
	// Plan is the short name of the plan the workspace starts on, empty for none
	// (multi-tenancy.md §4.2).
	Plan string
	// ExternalReference is a purchase platform's own reference for the workspace, empty for none
	// (multi-tenancy.md §4.3).
	ExternalReference string
}

ProvisionTenantCommand is the input, typed.

type ProvisionedTenant

type ProvisionedTenant struct {
	Tenant domain.Tenant
	// Plan is the plan the workspace started on, and Version the row's version once it is there.
	Plan                string
	ExternalReference   string
	Version             int
	OwnerAccountID      shared.ID
	OwnerRedemption     secret.Secret
	DefaultHubID        shared.ID
	ExampleCollectionID shared.ID
}

ProvisionedTenant is what the one answer carries - including the owner's redemption token, shown for the only time (T-18's "shown once").

type QueryTenants

type QueryTenants struct{ Reader TenantReader }

QueryTenants answers a page of the workspaces matching the filters.

func (QueryTenants) Descriptor

func (h QueryTenants) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (QueryTenants) Execute

Execute answers one page.

type QuotaChange

type QuotaChange struct {
	Quota string
	// Value is the new ceiling (0 = unlimited); nil clears the override.
	Value *int64
}

QuotaChange is one key's instruction: set a ceiling, or clear the override back to the mode's default. Absent keys are simply not in the list.

type ReadInstanceOverview

type ReadInstanceOverview struct {
	Writer InstanceWriter
	// Installation is the census, through the one function that answers it.
	Installation adminrepo.Installation
}

ReadInstanceOverview answers how big this installation is and how its workspaces stand.

func (ReadInstanceOverview) Descriptor

func (h ReadInstanceOverview) Descriptor() usecase.Descriptor

func (ReadInstanceOverview) Execute

Execute reads it.

type ReadInstanceSettings

type ReadInstanceSettings struct{ Writer InstanceWriter }

ReadInstanceSettings answers what the installation has decided.

func (ReadInstanceSettings) Descriptor

func (h ReadInstanceSettings) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ReadInstanceSettings) Execute

Execute reads the level.

type ReadPlan

type ReadPlan struct{ Writer PlanWriter }

ReadPlan answers one plan.

func (ReadPlan) Descriptor

func (h ReadPlan) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ReadPlan) Execute

func (h ReadPlan) Execute(ctx context.Context, actor appshared.ActorContext, name string) (PlanView, error)

Execute reads it.

type ReadPlatformSubscription

type ReadPlatformSubscription struct{ Writer PlatformWriter }

ReadPlatformSubscription answers one.

func (ReadPlatformSubscription) Descriptor

func (ReadPlatformSubscription) Execute

Execute reads it.

type ReadTenant

type ReadTenant struct{ Reader TenantReader }

ReadTenant answers one workspace.

func (ReadTenant) Descriptor

func (h ReadTenant) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ReadTenant) Execute

Execute reads one workspace.

type ReadUsage

type ReadUsage struct {
	Writer  InstanceWriter
	Ledger  adminrepo.UsageLedger
	Tenancy env.TenancyMode
}

ReadUsage answers each workspace's use per metric and day for billing (UC-INS-14 checks 4 and 8): only while the operator has switched metering on, a page of workspaces at a time.

func (ReadUsage) Descriptor

func (h ReadUsage) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ReadUsage) Execute

func (h ReadUsage) Execute(ctx context.Context, actor appshared.ActorContext, query UsageQuery) (UsagePage, error)

Execute reads one page.

type RecoverWorkspace

type RecoverWorkspace struct {
	Tenants    adminrepo.Tenants
	UnitOfWork persistence.UnitOfWork
	Resume     ResumeTenant
	Cancel     CancelTenantDeletion
}

RecoverWorkspace is the rest of the way back into a locked installation (UC-INS-03): run on the machine as `hubtask workspace resume|cancel-deletion --workspace <short name>`, it makes the same transition as ResumeTenant and CancelTenantDeletion - the same guards, the same trail entry, the same journal entry - for a workspace whose every operator is locked out by the very state it is in. A single-mode installation's only workspace suspended, or set for deletion, before the server refused both is that case (multi-tenancy.md §5).

The door is access to the machine, as for RestoreOperator: the actor it hands the two use cases holds `admin:tenants` for that one workspace and is recorded as the machine. No register check - the register's accounts are the ones the state locks out - and no route: it has no Descriptor, because a registered use case is reachable from outside, and this one mints its own actor.

func (RecoverWorkspace) CancelDeletionBySlug

func (h RecoverWorkspace) CancelDeletionBySlug(ctx context.Context, slug string) (domain.TenantStatus, error)

CancelDeletionBySlug calls off the pending deletion of the workspace a short name names, and answers the status the workspace is in afterwards - SUSPENDED where the request found it so.

func (RecoverWorkspace) ResumeBySlug

func (h RecoverWorkspace) ResumeBySlug(ctx context.Context, slug string) error

ResumeBySlug reactivates the suspended workspace a short name names. An active one is left as it is; one pending deletion is refused as the API refuses it - the cancel comes first.

type RedeliverPlatformSubscription

type RedeliverPlatformSubscription struct {
	Writer     PlatformWriter
	Deliveries adminrepo.PlatformDeliveries
	Jobs       PlatformJobs
}

RedeliverPlatformSubscription queues every dead letter of a subscription again, with the event identifier it always had and its attempts counting on (UC-INS-14 checks 5 and 6).

func (RedeliverPlatformSubscription) Descriptor

func (RedeliverPlatformSubscription) Execute

Execute queues them in bounded batches, each in a transaction of its own; a dead letter another redelivery took meanwhile is queued by that one alone.

type RedemptionTokens

type RedemptionTokens interface {
	SetRedemptionToken(
		ctx context.Context, accountID shared.ID, presented domain.Token,
		expiresAt, now time.Time,
	) (bool, error)
}

RedemptionTokens is the one-method slice provisioning needs of the sign-in accounts: storing the owner's way in.

type RegisterState

type RegisterState interface {
	State(ctx context.Context) (workspaces, operators int64, err error)
}

RegisterState answers how many workspaces and operators the installation has.

type RegisterWarnings

type RegisterWarnings struct {
	State       RegisterState
	Environment OperatorsFromEnvironment
	UnitOfWork  persistence.UnitOfWork
}

RegisterWarnings is the health report's view of who runs the installation (UC-INS-02 check 4, UC-INS-03 check 4), read live for each report:

  • an installation with more than one workspace and nobody in the register - counted as `is_operator` counts, so exactly where nobody can reach the control plane - names the command that restores an operator from the machine;
  • each HUBTASK_OPERATORS entry whose account does not exist yet;
  • and a register that could not be read says so, rather than reading as "fine" (P-11).

func (RegisterWarnings) Warnings

func (r RegisterWarnings) Warnings(ctx context.Context) []health.Warning

Warnings answers the warnings that hold now.

type RemoveInstanceAiProvider

type RemoveInstanceAiProvider struct{ Writer InstanceAiWriter }

RemoveInstanceAiProvider withdraws an offered model. Every workspace that chose it stops getting answers at its next call; its own configuration, if it has one, is untouched (ADR-0072 §5).

func (RemoveInstanceAiProvider) Descriptor

func (RemoveInstanceAiProvider) Execute

Execute removes it. An unknown model is not an error: removing it is somebody making sure.

type RemoveInstanceIdentityProvider

type RemoveInstanceIdentityProvider struct{ Writer InstanceProviderWriter }

RemoveInstanceIdentityProvider takes one away.

func (RemoveInstanceIdentityProvider) Descriptor

func (RemoveInstanceIdentityProvider) Execute

func (h RemoveInstanceIdentityProvider) Execute(
	ctx context.Context, actor appshared.ActorContext, id shared.ID, stepUpToken string,
) error

Execute removes it - only once its offer has ended, or where no workspace uses it (ADR-0077 §2). Removal deletes every connection between a person and the provider, which offering it again would not restore, so it comes after a withdrawal that announced itself; *Withdraw now* is the answer to a compromised provider. It is journalled, and the accounts it signed in keep their rows and their live sessions: what they lose is the way back, exactly as when a workspace removes its own.

type RemoveOperator

type RemoveOperator struct{ Writer InstanceWriter }

RemoveOperator takes one out.

func (RemoveOperator) Descriptor

func (h RemoveOperator) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (RemoveOperator) Execute

func (h RemoveOperator) Execute(
	ctx context.Context, actor appshared.ActorContext, accountID shared.ID,
) error

Execute removes, unless it would empty the register.

type RemovePlan

type RemovePlan struct{ Writer PlanWriter }

RemovePlan ends a plan no workspace is on. Its short name stays taken.

func (RemovePlan) Descriptor

func (h RemovePlan) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (RemovePlan) Execute

func (h RemovePlan) Execute(ctx context.Context, actor appshared.ActorContext, name string) error

Execute removes it.

type RequestTenantDeletion

type RequestTenantDeletion struct {
	Tenants     adminrepo.Tenants
	Journal     adminrepo.Journal
	Automations adminrepo.Automations
	// Holds are the workspace's legal holds in force: the request is refused while any stands
	// (data-protection.md §5).
	Holds  lifecyclerepo.LegalHolds
	Jobs   JobQueue
	StepUp stepup.Verifier
	// Register waives the step-up for a service account it names. Nil demands it of everybody.
	Register PlatformRegister
	Audit    audit.Sink
	// Platform tells a purchase platform the deletion was requested (UC-INS-14).
	Platform   platform.Recorder
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	IDs        clock.IDGenerator
	// Tenancy refuses the request in single mode (requireMulti).
	Tenancy env.TenancyMode
}

RequestTenantDeletion moves the workspace to PENDING_DELETION (§5): access blocked from the next request on, automations disabled visibly, the export machinery woken one last time, and the hard delete seeded as a job by this very write (multi-tenancy.md §2.1: nothing enumerates tenants, so there is no sweeper to find pending deletions - the request that creates the debt creates the job). It demands a step-up and the typed workspace name, the restore precedent: the two things a stolen admin token does not have. A purchase platform's service account in the register sends the typed name alone (UC-INS-08 check 7): a step-up is a person's proof, which a machine cannot give.

func (RequestTenantDeletion) Descriptor

func (h RequestTenantDeletion) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (RequestTenantDeletion) Execute

Execute schedules the deletion.

type RequestTenantDeletionCommand

type RequestTenantDeletionCommand struct {
	TenantID shared.ID
	// Confirmation is the workspace's display name, typed exactly - the restore precedent.
	Confirmation string
	// StepUpToken is the step-up proof (identity.md §16), consumed by this one request. A purchase
	// platform's service account sends none (waivesStepUp).
	StepUpToken string
}

RequestTenantDeletionCommand is the input, typed.

type RestoreOperator

type RestoreOperator struct{ Writer InstanceWriter }

RestoreOperator is the way back into an installation nobody can run (UC-INS-03): run on the machine as `hubtask operator add --workspace <short name> --email <address>`, with the server's own configuration and database connection.

No scope and no register check, and deliberately so: the door is access to the machine, which is what the person who could otherwise fix this with a database shell already has. That is also why it is not in the registry: nothing registered is reachable without a route, and this must have none (UC-INS-03 *Where it ends*).

func (RestoreOperator) Execute

func (h RestoreOperator) Execute(ctx context.Context, slug, email string) (RestoredOperator, error)

Execute adds the account an address names in a workspace, and records it as done from the machine. An address that matches no account is the one refusal the screen gives too, which says nothing about other workspaces.

type RestoredOperator

type RestoredOperator struct {
	AccountID shared.ID
	Added     bool
}

RestoredOperator says which account it was and whether it was added just now.

type ResumeTenant

type ResumeTenant struct{ LifecycleShift }

ResumeTenant is §5's one write back: the next request of the tenant's people works again.

func (ResumeTenant) Descriptor

func (h ResumeTenant) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (ResumeTenant) Execute

func (h ResumeTenant) Execute(
	ctx context.Context, actor appshared.ActorContext, tenantID shared.ID,
) error

Execute reactivates the workspace.

type RotatePlatformSubscriptionSecret

type RotatePlatformSubscriptionSecret struct{ Writer PlatformWriter }

RotatePlatformSubscriptionSecret issues a new signing secret, answered once; deliveries are signed with it from this moment.

func (RotatePlatformSubscriptionSecret) Descriptor

func (RotatePlatformSubscriptionSecret) Execute

Execute rotates it, the step-up first.

type SingleOperatorResolver

type SingleOperatorResolver interface {
	ResolveSingle(ctx context.Context, email string) (shared.ID, error)
}

SingleOperatorResolver finds an address in a single-mode installation's one workspace.

type SuspendTenant

type SuspendTenant struct{ LifecycleShift }

SuspendTenant flips the middleware: the tenant's people answer 403 tenant_suspended on their very next request, the data remains, the read export still works (multi-tenancy.md §5).

func (SuspendTenant) Descriptor

func (h SuspendTenant) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (SuspendTenant) Execute

func (h SuspendTenant) Execute(
	ctx context.Context, actor appshared.ActorContext, tenantID shared.ID,
) error

Execute suspends the workspace.

type SweepPlatformEvents

type SweepPlatformEvents struct {
	Events adminrepo.PlatformEvents
	// Deliveries are swept on the same clock: a settled delivery untouched for 30 days, its dead
	// letter included, which can no longer be redelivered once its event is gone. Nil sweeps none.
	Deliveries adminrepo.PlatformDeliveries
	Jobs       JobQueue
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	Tenancy    env.TenancyMode
}

SweepPlatformEvents removes the installation's events and their deliveries past their 30 days (data-catalog.md). An installation job: it runs under the system scope, which reaches the policy-free tables and no workspace's rows.

func (SweepPlatformEvents) Run

Run removes one pass's worth of expired events inside the job's transaction, and answers how many.

func (SweepPlatformEvents) Seed

Seed asks for the sweep once a worker starts. Single mode keeps no events and seeds nothing.

type TargetStore

type TargetStore interface {
	OpenTarget(ctx context.Context, tenantID, targetID shared.ID) (backupstorage.Store, error)
}

TargetStore opens a configured backup target - the seam the audit and DSR exports write through: an export needs somewhere to put bytes and has no business with a target's credentials.

type TellExportFinished

type TellExportFinished struct {
	Platform   platform.Recorder
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
}

TellExportFinished tells a purchase platform that a workspace's export has been written (UC-INS-14). The export's job runs outside a transaction and may run again after it wrote the archive, so the event is written in a transaction of its own and keyed by the export: once, however often the job runs.

func (TellExportFinished) Finished

func (t TellExportFinished) Finished(ctx context.Context, workspace, exportID shared.ID) error

Finished writes `export_finished` for the export, in the workspace's own scope.

type TenantExportArchivist

type TenantExportArchivist struct {
	Targets  TargetStore
	Rows     backuprepo.Export
	Objects  storage.ObjectStore
	Cipher   crypto.StreamCipher
	Snapshot persistence.Snapshot
	Clock    clock.Clock
	// SchemaVersion and ProductVersion go into the manifest: what an archive has to record is
	// the build that wrote it.
	SchemaVersion  string
	ProductVersion string
}

TenantExportArchivist writes the archive the job owes: the Hubtask archive of tenant-export.md, FULL, unencrypted, media and audit included, credentials redacted - through the same writer and the same row-level-security path a backup uses (T-20).

func (TenantExportArchivist) Write

Write produces one archive and answers its manifest.

type TenantReader

type TenantReader struct {
	Writer PlanWriter
	Finder adminrepo.TenantFinder
}

TenantReader reads workspaces for the control plane: one by its identifier, or a page of those matching a platform's filters.

type TestInstanceBackupTarget

type TestInstanceBackupTarget struct{ Writer InstanceBackupTargetWriter }

TestInstanceBackupTarget probes one.

func (TestInstanceBackupTarget) Descriptor

func (TestInstanceBackupTarget) Execute

Execute reads the target and its sealed credential in the installation's scope, probes outside any transaction, and writes down what the probe found.

type UpdatePlatformSubscription

type UpdatePlatformSubscription struct{ Writer PlatformWriter }

UpdatePlatformSubscription changes a subscription's target or pauses and resumes it. A new target demands a fresh step-up; pausing and resuming do not - they send less, or what was sent before.

func (UpdatePlatformSubscription) Descriptor

func (UpdatePlatformSubscription) Execute

Execute changes it, guarded on the version read or the one the caller names.

type UpdatePlatformSubscriptionCommand

type UpdatePlatformSubscriptionCommand struct {
	ID              shared.ID
	TargetURL       string
	State           string
	ExpectedVersion int
	StepUpToken     string
}

UpdatePlatformSubscriptionCommand is the change. Empty fields are left alone.

type UpdateTenant

type UpdateTenant struct {
	Writer  PlanWriter
	Tenants adminrepo.Tenants
	Finder  adminrepo.TenantFinder
	Audit   audit.Sink
}

UpdateTenant sets or clears a platform's reference on a workspace.

func (UpdateTenant) Descriptor

func (h UpdateTenant) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (UpdateTenant) Execute

Execute sets it. A workspace leaving can still have its reference cleared - it stays taken through the grace, and a platform provisioning that customer again clears it first - until its purge begins.

type UpdateTenantCommand

type UpdateTenantCommand struct {
	TenantID        shared.ID
	Reference       string
	ExpectedVersion *int
}

UpdateTenantCommand carries the reference; empty clears it.

type UpdateTenantQuotas

type UpdateTenantQuotas struct {
	Tenants    adminrepo.Tenants
	Store      quotarepo.Store
	Usage      quotarepo.Usage
	Audit      audit.Sink
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	Tenancy    env.TenancyMode
}

UpdateTenantQuotas is the operator's write on a workspace's §4 ceilings: partial by design, version-guarded, audited field by field.

func (UpdateTenantQuotas) Descriptor

func (h UpdateTenantQuotas) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (UpdateTenantQuotas) Execute

Execute applies the changes and answers the resolved standings.

type UpdateTenantQuotasCommand

type UpdateTenantQuotasCommand struct {
	TenantID shared.ID
	Changes  []QuotaChange
}

UpdateTenantQuotasCommand is the input, typed.

type UsageDay

type UsageDay struct {
	Day   time.Time
	Value int64
}

UsageDay is one metric's value on one day.

type UsageMetric

type UsageMetric struct {
	Name      string
	Aggregate string
	Day       string
}

UsageMetric is how one metric is counted and by which day (multi-tenancy.md §4.3).

func UsageMetrics

func UsageMetrics() []UsageMetric

UsageMetrics is the vocabulary in the contract's order: the quotas' own names.

type UsagePage

type UsagePage struct {
	Workspaces   []WorkspaceUsage
	Metrics      []UsageMetric
	FinalThrough time.Time
	Next         string
	More         bool
}

UsagePage is one read.

type UsageQuery

type UsageQuery struct {
	From, To time.Time
	Metric   string
	Tenant   shared.ID
	Cursor   string
	Size     int
}

UsageQuery is what a read asks.

type WithdrawCommand

type WithdrawCommand struct {
	ID           shared.ID
	At           time.Time
	ConfirmCount *int
	StepUpToken  string
}

WithdrawCommand is the input, typed. A zero At is the default notice; one that has come is now.

type WithdrawInstallationPreset

type WithdrawInstallationPreset struct{ Writer InstallationPresetWriter }

WithdrawInstallationPreset removes one. Hubs and collections made from it keep their settings.

func (WithdrawInstallationPreset) Descriptor

Descriptor is the catalogue entry.

func (WithdrawInstallationPreset) Execute

func (h WithdrawInstallationPreset) Execute(
	ctx context.Context, actor appshared.ActorContext, id shared.ID, expected *int,
) error

Execute withdraws it; the default is refused until another preset, or none, is the default.

type WithdrawInstanceIdentityProvider

type WithdrawInstanceIdentityProvider struct{ Writer InstanceProviderWriter }

WithdrawInstanceIdentityProvider announces the end of an offer, or ends it now.

func (WithdrawInstanceIdentityProvider) Descriptor

func (WithdrawInstanceIdentityProvider) Execute

Execute withdraws.

type WorkspaceUsage

type WorkspaceUsage struct {
	Tenant  shared.ID
	Purged  bool
	Metrics map[string][]UsageDay
}

WorkspaceUsage is one workspace's days, per metric.

type WriteInstanceSettings

type WriteInstanceSettings struct{ Writer InstanceWriter }

WriteInstanceSettings replaces the level whole.

func (WriteInstanceSettings) Descriptor

func (h WriteInstanceSettings) Descriptor() usecase.Descriptor

Descriptor is the catalogue entry.

func (WriteInstanceSettings) Execute

Execute writes the level on a person's behalf, made on the version `expected` (UC-INS-09 check 8). Zero means the caller named none, and the level is compared with the version read in the same transaction - so two saves at once still refuse the second, as the workspace's own does.

func (WriteInstanceSettings) ExecuteAsFile

Execute writes it.

A `PUT` rather than a merge, because a merge over eighteen switches that can each be absent has no way to say "unset this one" - and "the operator decided nothing here" is a value the resolver acts on, so it has to be expressible. ExecuteAsFile writes the level on behalf of the instance file, at start-up.

**The one caller that is not a person**, and the one that may write while a file enforces — because it *is* the file. It carries no actor: there is nobody to authorise, nothing has a session yet, and the journal records the source rather than a name. Everything else about it is the same write, including the entry in the journal, which is what makes a change that arrived from a repository as visible as one somebody clicked.

It compares no version: the file is the source and wins. In `enforce` every replica writes it at start, and a refusal here would stop a start over a race between two copies of the same file.

Jump to

Keyboard shortcuts

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