postgres

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: 79 Imported by: 0

Documentation

Overview

Package postgres is the persistence adapter. It is the only package that holds the database driver (ADR-0010, enforced by depguard and by test/architecture): everything else reaches the database through the unit of work, which is where the tenant boundary is set.

Index

Constants

View Source
const (
	// SubjectAmbiguous is a full deletion whose moment matches more than one removed account.
	SubjectAmbiguous = "SUBJECT_AMBIGUOUS"
	// SubjectUnresolved is a full deletion whose moment matches no removed account.
	SubjectUnresolved = "SUBJECT_UNRESOLVED"
)
View Source
const ChangeChannel = "hubtask_change"

ChangeChannel is the `LISTEN` channel the change log's trigger announces on (db/migrations/0022_change_notify.sql). A constant on both sides of a name that has to agree, and the reason it is here rather than in a configuration file: a channel nobody is listening on is a stream that never wakes up, and nothing would say so.

View Source
const DefaultExportBatch = 500

DefaultExportBatch is the page size a run uses unless it is told otherwise.

View Source
const DefaultJournalBatch = 1000

DefaultJournalBatch is the page size a restore reads the journal in.

View Source
const DefaultRetentionBatch = 1000

DefaultRetentionBatch is the thousand objects per transaction data-retention.md §5 asks for.

View Source
const JobChannel = "hubtask_job"

JobChannel is the `LISTEN` channel the queue's trigger announces on (db/migrations/0048_job_notify.sql). A constant on both sides of a name that has to agree, for ChangeChannel's reason: a channel nobody listens on is a queue that never wakes early, and nothing would say so.

View Source
const RecoveredErasuresTimeout = RecoveredHoldsTimeout

RecoveredErasuresTimeout bounds the whole read of the temporary cluster (rule 7).

View Source
const RecoveredHoldsTimeout = 2 * time.Minute

RecoveredHoldsTimeout bounds one read of a temporary cluster: connecting, and the statement.

Variables

This section is empty.

Functions

func Diagnostics

func Diagnostics(err error) []slog.Attr

Diagnostics names a database failure in attributes a log line may carry: the SQLSTATE, and the constraint, table and column the server reported. Never the message or the detail - both quote the row, and a row is user content (rule 10, ADR-0017).

It exists for the failure nobody can read: a job that fails with `postgres.query_failed` and nothing else. The code says a statement failed and the log cannot say which; the constraint name does.

func FromContext

func FromContext(ctx context.Context) (pgx.Tx, error)

FromContext returns the transaction to a repository. The error is deliberate: a repository called outside a unit of work is a programming error, and it must not fall back to the pool.

func ImportableTables

func ImportableTables() []string

ImportableTables answers the tables this build can write a restore into. It is what the test beside the archive's entity list compares against, so that an entity added to the archive without a statement here fails the gate rather than a restore.

func IsNoRows

func IsNoRows(err error) bool

ErrNoRows lets the application layer recognise an empty result without importing the driver.

func NewPool

func NewPool(ctx context.Context, cfg env.Config, role env.Role) (*pgxpool.Pool, error)

NewPool opens the connection pool for one role.

The role decides the query budget: short on the interactive path, long for background work (engineering-guidelines.md §4). It is applied as a connection parameter rather than per query, so a statement that runs away is cut off by PostgreSQL even when the calling code forgot its own deadline.

func ReadRecoveredHolds

func ReadRecoveredHolds(
	ctx context.Context, dsn string, tenantID shared.ID, timeout time.Duration,
) ([]domain.LegalHold, error)

ReadRecoveredHolds answers one workspace's legal holds, released or not, as another database holds them: the temporary cluster the operator recovered to the end of the WAL archive (backup-restore.md §8.5 step 5).

Through the transaction wrapper under the workspace's scope, like every read of the installation's own database (rule 3), and with the workspace named in the statement as well, because the role the operator connects with may bypass row level security.

func RegisterSchemaTypes

func RegisterSchemaTypes(ctx context.Context, conn *pgx.Conn) error

RegisterSchemaTypes teaches one connection the types above.

It is exported because a type map belongs to a connection, so every pool has to install it, and the integration tests open their own pools against the container rather than going through NewPool. Wiring it in as AfterConnect is what keeps that from being something a caller has to remember.

A database that has not been migrated yet carries none of these types. LoadTypes then finds nothing, returns nothing, and registers nothing rather than failing - which is what lets the migrator open a pool against an empty database.

Types

type AccessTokenRepository

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

AccessTokenRepository looks up personal access tokens.

It is the only place that knows how a token becomes a hash, because the pepper is a secret of this layer (security.md §8) - which is also why the port takes the presented token whole rather than a hash the application layer would have had to compute.

func NewAccessTokenRepository

func NewAccessTokenRepository(hasher security.TokenHasher) AccessTokenRepository

func (AccessTokenRepository) Find

Find reads one token. A token of another tenant is not found rather than forbidden, because row level security makes it invisible before any comparison could be made (multi-tenancy.md §2).

func (AccessTokenRepository) FindByToken

FindByToken reads the credential inside the caller's transaction, which is what bounds it to a tenant: the query itself carries no tenant condition, because row level security applies one that no query can forget (ADR-0010). A token quoting the wrong tenant therefore finds nothing.

func (AccessTokenRepository) Insert

func (r AccessTokenRepository) Insert(
	ctx context.Context, token identity.AccessToken, presented identity.Token,
) error

Insert writes the minted row. The hash is computed here and nowhere else - the application layer handed over the presented token whole, precisely so that it never held the stored value.

func (AccessTokenRepository) ListForAccount

func (r AccessTokenRepository) ListForAccount(
	ctx context.Context, accountID shared.ID,
) ([]identity.AccessToken, error)

ListForAccount answers one account's own credentials, newest first.

func (AccessTokenRepository) Revoke

func (r AccessTokenRepository) Revoke(
	ctx context.Context, tokenID shared.ID, at time.Time,
) (bool, error)

Revoke stamps the row and reports whether it changed anything. Nothing changed means it was already revoked, which is not an error - and the moment it was first pulled is kept, because that is the one an auditor asks about.

func (AccessTokenRepository) TouchLastUsed

func (r AccessTokenRepository) TouchLastUsed(ctx context.Context, tokenID shared.ID, at time.Time) error

TouchLastUsed writes the bookkeeping column. The row is reachable only within the tenant's own transaction, so no tenant condition is needed here either.

type AccountRepository

type AccountRepository struct{}

AccountRepository is the account table.

No method takes a tenant. Row level security bounds every statement to the tenant of the running transaction, which is what makes an account of another tenant *not found* rather than forbidden - anything else confirms that it exists (ADR-0010, multi-tenancy.md §2).

func NewAccountRepository

func NewAccountRepository() AccountRepository

func (AccountRepository) AcceptInvitation

func (r AccountRepository) AcceptInvitation(
	ctx context.Context, accountID shared.ID, now time.Time,
) (bool, error)

AcceptInvitation activates an invited account whose invitation has not run out.

func (AccountRepository) Find

func (r AccountRepository) Find(ctx context.Context, accountID shared.ID) (identity.Account, error)

func (AccountRepository) FindByEmail

func (r AccountRepository) FindByEmail(ctx context.Context, email string) (identity.Account, error)

func (AccountRepository) FindBySignInName

func (r AccountRepository) FindBySignInName(ctx context.Context, signInName string) (identity.Account, error)

FindBySignInName answers the account a folded sign-in name names (ADR-0074).

func (AccountRepository) HasManaged

func (r AccountRepository) HasManaged(ctx context.Context) (bool, error)

HasManaged answers whether the workspace has a managed account that can still sign in.

func (AccountRepository) Insert

func (r AccountRepository) Insert(ctx context.Context, account identity.Account) error

func (AccountRepository) InsertManaged

func (r AccountRepository) InsertManaged(
	ctx context.Context, account identity.Account, passwordHash string, at time.Time,
) error

func (AccountRepository) IssueStartPassword

func (r AccountRepository) IssueStartPassword(
	ctx context.Context, accountID shared.ID, passwordHash string, at time.Time,
) (bool, error)

InsertManaged writes a managed account with its start password's hash and the moment it was drawn, which is also the start password's mark. IssueStartPassword writes a new start password for a managed account; false is "not managed".

func (AccountRepository) ListOfKind

func (r AccountRepository) ListOfKind(
	ctx context.Context, kind identity.AccountKind,
) ([]identity.Account, error)

ListOfKind answers the tenant's accounts of one kind, newest first. Its one caller is the service account listing: a person is found by name or address, and a machine is found in the list of machines.

func (AccountRepository) Rename

func (r AccountRepository) Rename(ctx context.Context, account identity.Account, at time.Time) error

Rename writes the display name alone.

func (AccountRepository) Restricted

func (r AccountRepository) Restricted(
	ctx context.Context, accountIDs []shared.ID,
) (map[shared.ID]bool, error)

Restricted answers which of the accounts named may not be processed automatically.

One round trip for a whole pool rather than one per candidate: the caller is a draw over a policy's candidates, and a question per candidate would make an assignment policy's cost grow with the size of the team.

func (AccountRepository) SetAvatar

func (r AccountRepository) SetAvatar(
	ctx context.Context, accountID, mediaID shared.ID, at time.Time,
) error

SetAvatar writes the picture alone; the zero ID clears it.

func (AccountRepository) SpendPending

func (r AccountRepository) SpendPending(ctx context.Context, accountID shared.ID, at time.Time) (int, error)

SpendPending spends every unfinished sign-in step of the account.

func (AccountRepository) UpdatePreferences

func (r AccountRepository) UpdatePreferences(
	ctx context.Context, account identity.Account, at time.Time,
) error

type ActivityRepository

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

The item history, written and read.

Nothing here names a tenant. The transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010) - which is why the cross-tenant tests in test/integration are what prove this file correct rather than a reading of it.

One type for both halves of the port, unlike the trash, because there is nothing to keep apart: the write is one insert and the read is one select over the same table, and a second type would only mean a second thing for the composition root to build.

func NewActivityRepository

func NewActivityRepository(cursors security.CursorCodec) ActivityRepository

func (ActivityRepository) List

List returns one page of one item's history, newest first.

func (ActivityRepository) Record

func (r ActivityRepository) Record(ctx context.Context, entry domain.Entry) error

Record appends one entry to an item's history, inside the caller's transaction.

type AddressRepository

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

AddressRepository keeps a managed account's address waiting for its mailbox (ADR-0074 §6) on an ADDRESS row of auth_pending. The token is hashed under the pending credential's purpose label, the reset link's, because it is read the same way.

func NewAddressRepository

func NewAddressRepository(pendingHasher security.PendingTokenHasher) AddressRepository

NewAddressRepository builds it with the pending credential's hasher.

func (AddressRepository) Confirm

func (r AddressRepository) Confirm(
	ctx context.Context, accountID shared.ID, address string, at time.Time,
) (bool, error)

Confirm makes the address the account's while it is still managed.

func (AddressRepository) Consume

func (r AddressRepository) Consume(ctx context.Context, requestID shared.ID, at time.Time) (bool, error)

Consume spends the request once.

func (AddressRepository) FindByToken

func (r AddressRepository) FindByToken(
	ctx context.Context, token identity.Token,
) (repository.AddressLookup, error)

FindByToken answers the request a presented link names.

func (AddressRepository) Insert

func (r AddressRepository) Insert(
	ctx context.Context, request identity.PendingCredential, address string,
) error

Insert writes a request with its address and no token yet.

func (AddressRepository) LatestRequest

func (r AddressRepository) LatestRequest(ctx context.Context, accountID shared.ID) (time.Time, error)

LatestRequest answers the newest request's moment, or the zero time.

func (AddressRepository) LockAccount

func (r AddressRepository) LockAccount(ctx context.Context, accountID shared.ID) (identity.Account, error)

LockAccount reads the account and holds its row for the transaction.

func (AddressRepository) Mint

func (r AddressRepository) Mint(
	ctx context.Context, requestID shared.ID, presented identity.Token, now time.Time,
) (repository.AddressMail, error)

Mint stores the mail's token hash and answers where the mail goes.

func (AddressRepository) Supersede

func (r AddressRepository) Supersede(ctx context.Context, accountID shared.ID, at time.Time) error

Supersede spends the account's unspent requests.

type AdminTenantRepository

type AdminTenantRepository struct{}

AdminTenantRepository is the control plane's view of the tenant row.

The listing is the one deliberate exception to "no statement reaches beyond its transaction's tenant": it goes through the SECURITY DEFINER enumerator migration 0067 pins down, under the installation scope. Everything else here is bounded the way every repository is.

func NewAdminTenantRepository

func NewAdminTenantRepository() AdminTenantRepository

func (AdminTenantRepository) CancelDeletion

func (AdminTenantRepository) CancelDeletion(
	ctx context.Context, now time.Time,
) (identity.TenantStatus, bool, error)

CancelDeletion moves a pending tenant back, guarded on the purge's mark in the statement.

func (AdminTenantRepository) ClosePassword

func (AdminTenantRepository) ClosePassword(ctx context.Context, due, now time.Time) (bool, error)

ClosePassword ends the opening on the transaction's own tenant - any, or only one due by `due`.

func (AdminTenantRepository) Find

Find answers the transaction's own tenant row.

func (AdminTenantRepository) Insert

Insert writes the row inside the new tenant's own scope; the identifier comes from current_tenant_id(), so the row and the transaction cannot disagree.

func (AdminTenantRepository) List

List reads through admin_tenants(), the one legitimate enumerator (multi-tenancy.md §2.1).

func (AdminTenantRepository) OpenPassword

func (AdminTenantRepository) OpenPassword(
	ctx context.Context, opening identity.PasswordOpening, now time.Time,
) (bool, error)

OpenPassword writes an operator's opening on the transaction's own tenant (ADR-0078 §3).

func (AdminTenantRepository) RequestDeletion

func (AdminTenantRepository) RequestDeletion(
	ctx context.Context, purgeAfter, now time.Time,
) (bool, error)

RequestDeletion moves either living status to PENDING_DELETION and stamps the grace deadline.

func (AdminTenantRepository) SetPlan

func (AdminTenantRepository) SetPlan(ctx context.Context, planID shared.ID, version int, now time.Time) (bool, error)

SetPlan puts the transaction's own tenant on a plan, or none.

func (AdminTenantRepository) SetReference

func (AdminTenantRepository) SetReference(ctx context.Context, reference string, version int, now time.Time) (bool, error)

SetReference sets a platform's reference on the transaction's own tenant, or clears it.

func (AdminTenantRepository) SetStatus

func (AdminTenantRepository) SetStatus(
	ctx context.Context, from, to identity.TenantStatus, now time.Time,
) (bool, error)

SetStatus moves one guarded edge on the transaction's own tenant.

type AiChoiceRepository

type AiChoiceRepository struct{}

AiChoiceRepository is the workspace's choice of AI source, under row level security.

func NewAiChoiceRepository

func NewAiChoiceRepository() AiChoiceRepository

func (AiChoiceRepository) Find

func (AiChoiceRepository) Save

type AiProviderRepository

type AiProviderRepository struct{}

The workspace's AI provider. No method takes a tenant: row level security bounds every statement, and the workspace is the row's key (ADR-0010).

func NewAiProviderRepository

func NewAiProviderRepository() AiProviderRepository

func (AiProviderRepository) Delete

func (AiProviderRepository) Find

func (AiProviderRepository) FindWithKey

func (AiProviderRepository) RewrapKey

func (AiProviderRepository) RewrapKey(
	ctx context.Context, sealed crypto.Sealed, expectedKeyID string,
) (bool, error)

func (AiProviderRepository) Upsert

func (AiProviderRepository) UpsertKeepingKey

func (AiProviderRepository) UpsertKeepingKey(
	ctx context.Context, configured integration.AiProvider, now time.Time,
) (integration.AiProvider, error)

type AuditPartitionRepository

type AuditPartitionRepository struct{}

AuditPartitionRepository keeps the trail's partitions conforming (audit.md §3).

Its own type beside the trail rather than a method on it: this is maintenance of the table, not a read of the evidence, and a repository that could do both would put a DDL statement one call away from the request path.

func NewAuditPartitionRepository

func NewAuditPartitionRepository() AuditPartitionRepository

func (AuditPartitionRepository) DropAged

func (r AuditPartitionRepository) DropAged(ctx context.Context, defaultDays int) ([]repository.TrailDrop, error)

DropAged lets the trail's months past their period go, whole (0152_audit_partition_drop.sql): the function decides, holds and the default partition included, and answers what it did.

func (AuditPartitionRepository) Ensure

func (r AuditPartitionRepository) Ensure(ctx context.Context, month time.Time) (string, error)

Ensure runs the duty for one month.

The work is in the database rather than here, and deliberately: creating a partition of `audit_log`, revoking on it and giving it its policy are the owner's rights, and the application role holds none of them. What this calls is a SECURITY DEFINER function whose only parameter is a date (0043_audit_partition_duty.sql) - which is also why there is no statement built here.

type AuditSink

type AuditSink struct {
	IDs clock.IDGenerator
	// Fingerprints keys the fingerprints of sensitive values (audit.md §4). A sink without one
	// records such a field as "changed" alone; the server always builds it with one (Keyed).
	Fingerprints port.Fingerprinter
}

AuditSink appends to the trail, inside the caller's transaction.

The application role may only INSERT and SELECT on audit_log, a trigger refuses an UPDATE or a DELETE, and the hash chain makes a rewrite detectable - three levels, because one is not enough (audit.md §3). This type is responsible for the third; the chain itself is computed in infrastructure/audit, which needs no database in order to be tested.

project-structure.md sketches this file as infrastructure/audit/PostgresAuditSink.go. It lives here instead, because rule 3 keeps the driver inside this package: a sink elsewhere would either hold pgx itself - which the architecture test refuses - or need a second set of type conversions exported to it.

func NewAuditSink

func NewAuditSink(ids clock.IDGenerator) AuditSink

func (AuditSink) Append

func (s AuditSink) Append(ctx context.Context, entry port.Entry) error

Append writes one entry and extends the tenant's chain.

The chain is serialised per tenant with a transaction-scoped advisory lock. Two transactions writing for the same tenant would otherwise read the same tail and produce two entries with the same sequence number and the same predecessor - a chain that forks, which is indistinguishable from one that was tampered with. Per tenant rather than globally, so one busy workspace does not serialise the installation; and transaction-scoped, so a process that dies releases it.

func (AuditSink) Keyed

func (s AuditSink) Keyed(fingerprints port.Fingerprinter) AuditSink

Keyed is the sink with the fingerprinter it keys sensitive values with.

type AuditTrailRepository

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

The reading half of the trail (audit.md §5).

Its own type beside AuditSink rather than a second method on it, and that is the same decision the port makes: the sink is a dependency of every use case that writes anything, and a sink that could also read would put the whole trail one method call away from code that has no business reading it.

Nothing here names a tenant. The transaction the caller opened decided that, and row level security applies it to every statement (ADR-0010) - the statement repeats the condition all the same, because `audit_time_idx` and `audit_id_uq` both begin with `tenant_id` and a query without it would be a sequential scan over every partition.

func NewAuditTrailRepository

func NewAuditTrailRepository(cursors security.CursorCodec) AuditTrailRepository

func (AuditTrailRepository) ChainEnd

ChainEnd answers the tail of this tenant's chain, the same read the sink continues from.

func (AuditTrailRepository) HashAt

func (r AuditTrailRepository) HashAt(ctx context.Context, seq int64) ([]byte, error)

HashAt answers the stored hash at one sequence number.

func (AuditTrailRepository) LatestAnchor

func (r AuditTrailRepository) LatestAnchor(ctx context.Context) (repository.Anchor, error)

LatestAnchor answers the last chain end this tenant exported outside the database.

func (AuditTrailRepository) Query

Query answers one page of the trail, newest first.

func (AuditTrailRepository) Record

Record writes one anchor. A second anchor of the same sequence number is refused by the primary key and answered as a conflict, which the job reads as "nothing new to anchor".

func (AuditTrailRepository) Walk

func (r AuditTrailRepository) Walk(
	ctx context.Context, period repository.Period, yield func(repository.Record) error,
) error

Walk hands over every entry of a period, oldest first.

It pages on the same keyset the list descends by, so a walk that runs for minutes while entries are being appended can neither repeat an entry nor skip one. Entries appended *during* the walk are outside its period or after its cursor, which is the same answer a snapshot would give and costs no open transaction.

type AutoAssignPolicyRepository

type AutoAssignPolicyRepository struct{}

AutoAssignPolicyRepository stores the assignment policy per scope.

The candidates and the rotation's state travel as JSONB, in the shape the column has carried since 0001_init: `[{"kind": "ACCOUNT", "id": "…"}]` and `{"cursor": 0}`. The shapes are named here once, beside the only code that reads or writes them.

func (AutoAssignPolicyRepository) Delete

func (r AutoAssignPolicyRepository) Delete(
	ctx context.Context, scope work.AutoAssignScope, scopeID shared.ID,
) error

Delete removes the scope's policy. Idempotent: removing what is not there is the state the caller asked for.

func (AutoAssignPolicyRepository) FindForScope

FindForScope reads the policy at one scope, or ErrNotFound when none is configured there.

func (AutoAssignPolicyRepository) Lock

Lock reads the same row and holds it for the rest of the transaction (the rotation's concurrency control - see the port).

func (AutoAssignPolicyRepository) SaveState

SaveState persists the advanced rotation, under the lock Lock took.

func (AutoAssignPolicyRepository) Upsert

Upsert writes the whole definition, creating or replacing the scope's row.

type AutomationInboundRepository

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

AutomationInboundRepository is the address an INBOUND_WEBHOOK rule answers on.

Its own type rather than two more methods on the rule repository, for CalendarFeedRepository's reason: it is the only place that knows how a presented token becomes a hash, and the pepper is a secret of this layer (security.md §8). Everything else about a rule is read through a repository that holds no key at all.

func (AutomationInboundRepository) FindByToken

FindByToken answers the rule an address opens.

The lookup runs in the tenant the token names inside itself, which the caller set as the transaction's scope. The hash covers the whole presented string, so a token rewritten to quote another tenant hashes to something no row carries.

func (AutomationInboundRepository) SetToken

func (r AutomationInboundRepository) SetToken(
	ctx context.Context, ruleID shared.ID, token integration.InboundToken, at time.Time,
) (bool, error)

SetToken mints or rotates the address. The token itself is not stored, is not logged, and does not leave the call it was answered in.

type AutomationReferenceRepository

type AutomationReferenceRepository struct{}

AutomationReferenceRepository answers the check's one question (ADR-0060): does something a rule names still exist. Seven statements for seven kinds, each under the tenant context the transaction wrapper set - so another workspace's object answers "no", which is what SG-3 asks of a lookup by identifier.

func NewAutomationReferenceRepository

func NewAutomationReferenceRepository() AutomationReferenceRepository

NewAutomationReferenceRepository returns the resolver.

func (AutomationReferenceRepository) Exists

Exists answers for one kind. A kind this resolver does not know is an internal error rather than "no": answering false would make every parameter of an unclassified kind a finding, which is the check inventing things wrong with a rule.

type AutomationRuleRepository

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

AutomationRuleRepository stores the rules.

The four jsonb columns are serialised here rather than in the domain: JSON is a wire format and the domain does not serialise itself (project-structure.md §3). What travels is the validated aggregate, so a document in the column is one this build could produce - the shapes are read back defensively all the same, because the row outlives the release that wrote it.

func NewAutomationRuleRepository

func NewAutomationRuleRepository(cursors security.CursorCodec) AutomationRuleRepository

func (AutomationRuleRepository) ClaimDue

func (r AutomationRuleRepository) ClaimDue(
	ctx context.Context, at time.Time, limit int,
) ([]domain.Occurrence, error)

func (AutomationRuleRepository) Delete

func (r AutomationRuleRepository) Delete(
	ctx context.Context, id shared.ID, at time.Time,
) (bool, error)

func (AutomationRuleRepository) DisableBroken

func (r AutomationRuleRepository) DisableBroken(
	ctx context.Context, id shared.ID, at time.Time,
) (bool, error)

DisableBroken switches a rule off because the check found it cannot run (ADR-0060).

func (AutomationRuleRepository) Due

func (r AutomationRuleRepository) Due(
	ctx context.Context, at time.Time, limit int,
) ([]domain.Rule, error)

automationRuleFrom reads a row back.

Defensively, although every document in the column was written by a validated aggregate: the row outlives the release that wrote it, and a shape this build cannot read has to be an error a log names rather than a zero value that quietly changes what a rule does. Due answers this tenant's rules whose moment has come.

The tenant is the transaction's, never a parameter: the pass is opened under one tenant's scope by that tenant's own poller, and nothing may enumerate tenants (rule 3, multi-tenancy.md §2.1).

func (AutomationRuleRepository) Find

func (AutomationRuleRepository) Forget

func (r AutomationRuleRepository) Forget(ctx context.Context, ruleID, itemID shared.ID) error

func (AutomationRuleRepository) ForgetItem

func (r AutomationRuleRepository) ForgetItem(ctx context.Context, itemID shared.ID) error

func (AutomationRuleRepository) Insert

func (AutomationRuleRepository) List

func (AutomationRuleRepository) NextDue

NextDue answers the earliest moment this tenant owes anything, and the zero time when it owes nothing - which is what lets the poller finish rather than spin.

func (AutomationRuleRepository) NextOccurrence

func (r AutomationRuleRepository) NextOccurrence(ctx context.Context) (time.Time, error)

func (AutomationRuleRepository) RecordCheck

func (r AutomationRuleRepository) RecordCheck(
	ctx context.Context, id shared.ID, findings []domain.Finding, at time.Time,
) error

RecordCheck writes the findings and the moment, and nothing else (ADR-0060).

func (AutomationRuleRepository) RewrapActions

func (r AutomationRuleRepository) RewrapActions(
	ctx context.Context, id shared.ID, actions []domain.Action, expectedVersion int,
) (bool, error)

func (AutomationRuleRepository) SealedNotUnder

func (r AutomationRuleRepository) SealedNotUnder(
	ctx context.Context, keyID string,
) ([]domain.Rule, error)

func (AutomationRuleRepository) SetEnabled

func (r AutomationRuleRepository) SetEnabled(
	ctx context.Context, id shared.ID, enabled bool, expectedVersion int, at time.Time,
) error

func (AutomationRuleRepository) SetNextRun

func (r AutomationRuleRepository) SetNextRun(
	ctx context.Context, id shared.ID, at time.Time,
) error

SetNextRun moves one rule on to its next moment, or to none.

The version is deliberately untouched: nobody read this rule in order to advance it, and bumping it would make every occurrence look like an edit to a client holding an optimistic lock.

func (AutomationRuleRepository) Update

func (r AutomationRuleRepository) Update(
	ctx context.Context, rule domain.Rule, expectedVersion int,
) error

func (AutomationRuleRepository) Upsert

func (r AutomationRuleRepository) Upsert(
	ctx context.Context, occurrence domain.Occurrence,
) error

type AutomationRunRepository

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

AutomationRunRepository is the log of what the rules have done, the failure counter, and the per-event lookup the dispatcher makes.

Three ports on one type because they are one table pair, and three ports rather than one because the callers differ: a subscriber inside the dispatcher's transaction has no business writing a rule, and the writer that manages rules has no business switching one off behind a person's back.

func NewAutomationRunRepository

func NewAutomationRunRepository(cursors security.CursorCodec) AutomationRunRepository

func (AutomationRunRepository) Bump

func (r AutomationRunRepository) Bump(
	ctx context.Context, ruleID shared.ID, at time.Time,
) (int, error)

func (AutomationRunRepository) ByTriggerKind

func (r AutomationRunRepository) ByTriggerKind(
	ctx context.Context, kind domain.TriggerKind,
) ([]domain.Rule, error)

ByTriggerKind answers this tenant's enabled rules of one kind, which is what a producer that is not the event dispatcher asks - the relative-date producer does.

func (AutomationRunRepository) Clear

func (r AutomationRunRepository) Clear(ctx context.Context, ruleID shared.ID, at time.Time) error

func (AutomationRunRepository) CountSince

func (r AutomationRunRepository) CountSince(
	ctx context.Context, ruleID shared.ID, since time.Time,
) (int, error)

func (AutomationRunRepository) Disable

func (r AutomationRunRepository) Disable(
	ctx context.Context, ruleID shared.ID, threshold int, at time.Time,
) (bool, error)

func (AutomationRunRepository) Find

func (AutomationRunRepository) Finish

func (AutomationRunRepository) ForEventType

func (r AutomationRunRepository) ForEventType(
	ctx context.Context, eventType event.Type,
) ([]domain.Rule, error)

func (AutomationRunRepository) List

func (AutomationRunRepository) Start

type AutomationSwitch

type AutomationSwitch struct{}

AutomationSwitch throws §5's one switch: every enabled rule of the transaction's tenant, off.

func NewAutomationSwitch

func NewAutomationSwitch() AutomationSwitch

func (AutomationSwitch) DisableAll

func (AutomationSwitch) DisableAll(ctx context.Context, now time.Time) (int, error)

DisableAll is bounded by row level security to the tenant of the transaction.

type BackupExportRepository

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

BackupExportRepository reads a tenant out, one page at a time, for the archive writer.

Paged on each entity's own key rather than on OFFSET, and the difference is not performance. A backup runs inside a REPEATABLE READ snapshot for minutes; an OFFSET re-counts the rows it already skipped on every page, and an OFFSET over a set that another statement is free to reorder can repeat a row or drop one. A key cursor can do neither, and it is also what lets a run that died resume where it stopped.

func NewBackupExportRepository

func NewBackupExportRepository(batch int) BackupExportRepository

func (BackupExportRepository) MediaLocation

func (r BackupExportRepository) MediaLocation(
	ctx context.Context, checksum string,
) (repository.MediaLocation, error)

MediaLocation answers where one medium's bytes lie, by the checksum the archive addresses it with.

func (BackupExportRepository) Rows

func (r BackupExportRepository) Rows(
	ctx context.Context, table string, since time.Time, yield func(repository.Row) error,
) error

Rows hands over one table's rows, oldest change first.

func (BackupExportRepository) Tombstones

func (r BackupExportRepository) Tombstones(
	ctx context.Context, table string, since time.Time, yield func(repository.Tombstone) error,
) error

Tombstones hands over one table's deletion markers after an instant.

type BackupImportRepository

type BackupImportRepository struct{}

BackupImportRepository writes a tenant back in, one row at a time (backup-restore.md §8).

The mirror of BackupExportRepository, and deliberately its opposite in shape: the export pages because it reads a whole tenant, and the import does not because it decides each row - against the conflict rule, against the deletion journal - and a batch would lose which of thirty rows it had skipped.

The row travels as one jsonb value and is unpacked by `jsonb_populate_record` against the table's own row type. That is what keeps rule 9 true here: no byte of an archive ever becomes SQL text, not even a column name. A field the archive carries and the table no longer has is dropped by the unpacking, and a column the archive predates arrives NULL - which is what makes an archive from an older schema restorable at all.

func NewBackupImportRepository

func NewBackupImportRepository() BackupImportRepository

func (BackupImportRepository) Clear

func (r BackupImportRepository) Clear(ctx context.Context, table string) (int, error)

Clear empties one table within the tenant.

func (BackupImportRepository) Holds

func (r BackupImportRepository) Holds(
	ctx context.Context, table string, data map[string]any,
) (bool, error)

Holds reports whether the tenant already has the row this data identifies.

func (BackupImportRepository) Write

func (r BackupImportRepository) Write(
	ctx context.Context, table string, data map[string]any, overwrite bool,
) (bool, error)

Write inserts the row, or replaces it when overwrite is true.

type BackupRunRepository

type BackupRunRepository struct{}

BackupRunRepository stores what happened.

func NewBackupRunRepository

func NewBackupRunRepository() BackupRunRepository

func (BackupRunRepository) Find

Find answers one run.

func (BackupRunRepository) Finish

func (r BackupRunRepository) Finish(ctx context.Context, outcome domain.Outcome) error

Finish records how a run ended and what it left behind.

func (BackupRunRepository) LastSuccessPerTarget

func (r BackupRunRepository) LastSuccessPerTarget(
	ctx context.Context,
) (map[shared.ID]time.Time, error)

LastSuccessPerTarget is the number alert A-12 watches.

func (BackupRunRepository) LatestSuccessful

func (r BackupRunRepository) LatestSuccessful(
	ctx context.Context, targetID shared.ID,
) (domain.Run, error)

LatestSuccessful is the archive an incremental continues.

func (BackupRunRepository) MarkExpired

func (r BackupRunRepository) MarkExpired(ctx context.Context, id shared.ID) error

MarkExpired moves a run whose archive has been deleted to EXPIRED.

func (BackupRunRepository) RecordVerification

func (r BackupRunRepository) RecordVerification(
	ctx context.Context, id shared.ID, at time.Time, ok bool,
) error

RecordVerification writes down what `:verify` found.

func (BackupRunRepository) SetExpiry

func (r BackupRunRepository) SetExpiry(
	ctx context.Context, id shared.ID, expiresAt time.Time,
) error

SetExpiry records when the generation plan expects an archive to go.

func (BackupRunRepository) Start

func (r BackupRunRepository) Start(ctx context.Context, run domain.Run) (bool, error)

Start writes the run and answers whether it got the target.

The lock §5 asks for is the statement, not a check this method ran a moment earlier: the insert carries `WHERE NOT EXISTS (... status = 'RUNNING')`, so a second run at the same target writes nothing and finds out by getting no row back. A check followed by an insert would have a gap between them wide enough for exactly the thing it is meant to prevent.

type BackupScheduleRepository

type BackupScheduleRepository struct{}

BackupScheduleRepository stores what runs when.

`next_run_at` is a stored decision rather than a rule expanded on every read. Expanding an RRULE costs a library call per schedule, and a poller that did it on every wake-up would do it for every schedule that is not due; the value is written by the pass that last ran, which is the shape the reminders already use.

func NewBackupScheduleRepository

func NewBackupScheduleRepository() BackupScheduleRepository

func (BackupScheduleRepository) Delete

Delete removes one schedule. What it produced stays: the archives at the target, and the runs in the log.

func (BackupScheduleRepository) Due

func (r BackupScheduleRepository) Due(
	ctx context.Context, now time.Time, batch int,
) ([]domain.Schedule, error)

Due answers the schedules whose moment has come.

func (BackupScheduleRepository) Find

Find answers one schedule.

func (BackupScheduleRepository) ForTarget

func (r BackupScheduleRepository) ForTarget(
	ctx context.Context, targetID shared.ID,
) ([]domain.Schedule, error)

ForTarget answers the schedules that name a target - what a refused deletion names.

func (BackupScheduleRepository) Insert

func (r BackupScheduleRepository) Insert(
	ctx context.Context, schedule domain.Schedule, nextRunAt time.Time,
) error

Insert writes a schedule with the moment it is next due already decided.

func (BackupScheduleRepository) List

List answers the schedules visible in the caller's scope.

func (BackupScheduleRepository) NextDue

NextDue is the earliest moment anything in scope is owed.

func (BackupScheduleRepository) SetNextRun

func (r BackupScheduleRepository) SetNextRun(
	ctx context.Context, id shared.ID, nextRunAt time.Time,
) error

SetNextRun records when the schedule is next owed.

func (BackupScheduleRepository) Update

func (r BackupScheduleRepository) Update(
	ctx context.Context, schedule domain.Schedule, nextRunAt time.Time, expectedVersion int,
) (bool, error)

Update writes a changed schedule and the moment it is next owed.

The target and the scope are not among the columns the statement writes, for the reason the contract gives: a schedule that moved either would be a different schedule under an old identifier, and the archives already at the target would disagree with it.

type BackupTargetRepository

type BackupTargetRepository struct{}

BackupTargetRepository stores the targets a tenant has configured.

The credential is read by one method, and that method reads nothing else. It is the same separation the statements make and for the same reason: what a credential must never do is travel alongside something on its way to a response, and the way to make that structural rather than remembered is for the rows that go to a response never to contain one.

func NewBackupTargetRepository

func NewBackupTargetRepository() BackupTargetRepository

func (BackupTargetRepository) Coverage

Coverage is what the installation's health surface asks.

func (BackupTargetRepository) Credential

func (r BackupTargetRepository) Credential(
	ctx context.Context, id shared.ID,
) (crypto.Sealed, error)

Credential answers the sealed credential, still sealed. This repository never learns what is inside it: the key lives in the encryptor, and that is what makes a database dump worth nothing on its own (security.md §8).

func (BackupTargetRepository) Delete

func (r BackupTargetRepository) Delete(ctx context.Context, id shared.ID) (bool, error)

Delete removes the target and its sealed credential from the workspace's configuration.

**Nothing at the target is touched.** `backup-restore.md`'s rule that Hubtask never deletes a file it did not write applies at least as strongly to the files it did write: an archive is what somebody restores from after the row that described where it lies is gone.

func (BackupTargetRepository) Find

Find answers one target, without its credential.

func (BackupTargetRepository) Insert

func (r BackupTargetRepository) Insert(
	ctx context.Context, target domain.Target, credential crypto.Sealed,
) error

Insert writes the target and the sealed credential beside it.

func (BackupTargetRepository) List

List answers the tenant's targets, by name and without a credential among them.

func (BackupTargetRepository) RecordTest

func (r BackupTargetRepository) RecordTest(
	ctx context.Context, id shared.ID, at time.Time, ok bool, code string,
) error

RecordTest writes down what the probe found.

func (BackupTargetRepository) Rewrap

func (r BackupTargetRepository) Rewrap(
	ctx context.Context, id shared.ID, sealed crypto.Sealed, expectedKeyID string,
) (bool, error)

func (BackupTargetRepository) SealedNotUnder

func (r BackupTargetRepository) SealedNotUnder(
	ctx context.Context, keyID string,
) ([]repository.SealedCredential, error)

type BucketRepository

type BucketRepository struct{}

BucketRepository stores the columns of a collection's board.

Nothing here names a tenant. The transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010) - which is why the cross-tenant tests in test/integration are the ones that prove this file correct, not a unit test with a fake.

func NewBucketRepository

func NewBucketRepository() BucketRepository

func (BucketRepository) Find

Find returns the bucket as it is stored, a deleted one included.

func (BucketRepository) FirstOther

func (r BucketRepository) FirstOther(
	ctx context.Context, collectionID, excludedID shared.ID,
) (work.Bucket, error)

FirstOther returns the collection's leftmost remaining bucket, ignoring one.

func (BucketRepository) Insert

func (r BucketRepository) Insert(ctx context.Context, bucket work.Bucket) error

Insert writes the bucket.

The name check is the unique index rather than a query before the insert: a check followed by an insert is two statements with a gap, and two requests arriving inside that gap both pass the check.

func (BucketRepository) LastOrderKey

func (r BucketRepository) LastOrderKey(
	ctx context.Context, collectionID shared.ID,
) (string, error)

LastOrderKey returns the highest rank on the board, or the empty string when it is empty.

func (BucketRepository) List

func (r BucketRepository) List(
	ctx context.Context, collectionID shared.ID,
) ([]work.Bucket, error)

List returns a collection's board, left to right.

func (BucketRepository) MoveItems

func (r BucketRepository) MoveItems(
	ctx context.Context, sourceID, targetID shared.ID, at time.Time,
) (int, error)

MoveItems moves every item out of one bucket and into another.

func (BucketRepository) Neighbours

func (r BucketRepository) Neighbours(
	ctx context.Context, collectionID, beforeID, movingID shared.ID,
) (string, string, error)

Neighbours returns the ranks either side of a position on one board.

func (BucketRepository) SetAttributes

func (r BucketRepository) SetAttributes(
	ctx context.Context, bucket work.Bucket, expectedVersion int,
) error

SetAttributes writes a bucket's own fields.

func (BucketRepository) SetDeleted

func (r BucketRepository) SetDeleted(
	ctx context.Context, bucket work.Bucket, expectedVersion int,
) error

SetDeleted writes the bucket's deletion stamp.

func (BucketRepository) SetOrderKey

func (r BucketRepository) SetOrderKey(
	ctx context.Context, bucket work.Bucket, expectedVersion int,
) error

SetOrderKey writes a new rank for one bucket.

type CalendarFeedRepository

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

CalendarFeedRepository stores the subscriptions and finds the one a token names.

It is the only place that knows how a feed token becomes a hash, for the reason the access token repository is: the pepper is a secret of this layer (security.md §8), which is why the port takes the presented token whole rather than a value the application layer would have had to compute.

func NewCalendarFeedRepository

func NewCalendarFeedRepository(hasher security.FeedTokenHasher) CalendarFeedRepository

func (CalendarFeedRepository) Find

Find returns one feed, revoked or not.

func (CalendarFeedRepository) FindByToken

FindByToken is the public route's whole lookup, and it runs inside the caller's transaction - which is what bounds it to a tenant, because the query carries no tenant condition and row level security applies one no query can forget (ADR-0010). A token quoting a tenant it does not belong to therefore finds nothing.

func (CalendarFeedRepository) Insert

Insert writes the feed and the hash of the token minted beside it. The token itself is not stored, is not logged, and does not leave the call it was answered in.

func (CalendarFeedRepository) ListForAccount

func (r CalendarFeedRepository) ListForAccount(
	ctx context.Context, accountID shared.ID,
) ([]domain.CalendarFeed, error)

ListForAccount returns one person's own feeds, newest first.

func (CalendarFeedRepository) Revoke

func (r CalendarFeedRepository) Revoke(
	ctx context.Context, id shared.ID, at time.Time,
) (bool, error)

Revoke stamps the feed and says whether it changed anything. The statement's own `revoked_at IS NULL` decides that, so a second revocation costs one round trip and no read.

type CallSealingRepository

type CallSealingRepository struct{}

CallSealingRepository is the re-seal's reach into the job table (ADR-0045): the HTTP calls still waiting to be made or retried, whose header secret lives in the job's payload. Its own type rather than a method on the queue's repository, because the queue reads payloads it does not understand and this reads exactly one field of one kind.

The job table has no row level security, so the statements name the transaction's workspace themselves.

func NewCallSealingRepository

func NewCallSealingRepository() CallSealingRepository

func (CallSealingRepository) RewrapCall

func (CallSealingRepository) RewrapCall(
	ctx context.Context, jobID shared.ID, sealed domain.SealedSecret, expectedKeyID string,
) (bool, error)

func (CallSealingRepository) SealedNotUnder

func (CallSealingRepository) SealedNotUnder(
	ctx context.Context, keyID string,
) ([]repository.PendingCall, error)

type CapabilityProfileRepository

type CapabilityProfileRepository struct{}

CapabilityProfileRepository reads the profile in force per item type.

func NewCapabilityProfileRepository

func NewCapabilityProfileRepository() CapabilityProfileRepository

func (CapabilityProfileRepository) List

List returns one profile per item type. Which one - the tenant's override or the system default - is decided by the query; what is visible at all is decided by row level security and by the scope the caller opened (ADR-0010).

func (CapabilityProfileRepository) ListSystem

ListSystem returns the defaults, ignoring whatever this tenant has overridden. They bound what a narrowing may do, and the hierarchy reads the topology off them for that reason.

type ChangeListener

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

ChangeListener is the wake-up for the change stream: one connection per process holding a `LISTEN`, and an in-process fan-out from it (ADR-0007).

Why this holds a connection, and why that is not a hole in rule 3

The reasoning is the Leader's, and it is worth writing out again because it is the second exception in this package rather than a precedent being widened. Rule 3 exists because the transaction wrapper is the only place that sets the tenant context, so a query reaching the database another way would run without one. Nothing here is such a query: the only statement is `LISTEN hubtask_change`, it touches no table, and no other code can reach this connection - it is unexported and this type offers no way to run anything on it. What arrives back is a tenant identifier and nothing else; every read of the log still goes through the wrapper, under row level security, exactly as a poll would.

It has to be a held connection because `LISTEN` belongs to a session. A pooled connection handed back after the statement would take the subscription with it.

One connection of the API role's pool is therefore occupied for as long as the process serves streams. That is the cost, and it is why the pool is never sized at one.

func NewChangeListener

func NewChangeListener(pool *pgxpool.Pool) *ChangeListener

func (*ChangeListener) Connected

func (l *ChangeListener) Connected() bool

Connected reports whether a listening connection is currently held. What the health report reads: a process without one still serves streams, and serves them at the poll interval.

func (*ChangeListener) Run

func (l *ChangeListener) Run(ctx context.Context)

Run holds the listening connection for as long as the context lives, reconnecting when it drops.

Started through SafeGo by the composition root like every other loop (rule 5). It returns when the context is cancelled and not before: a listener that gave up after a failure would leave every stream on the process at its poll interval, which is a degradation nothing reports.

func (*ChangeListener) Subscribe

func (l *ChangeListener) Subscribe(tenantID shared.ID) (<-chan struct{}, func())

Subscribe asks to be woken when the workspace changes, and returns the channel and the way to stop.

The channel has room for one, and a send that would block is dropped. That is the correct semantics for a doorbell: a subscriber with an unread wake-up already knows there is something to read, and a second one tells it nothing new. It is also what stops a slow reader from blocking the listening loop, which is the one goroutine every stream on this process depends on.

The cancel is idempotent and must be called, or the waiter outlives the connection it belongs to.

type ChangeLog

type ChangeLog struct{}

ChangeLog records state deltas for synchronising clients (offline-sync.md §10).

func NewChangeLog

func NewChangeLog() ChangeLog

func (ChangeLog) After

func (c ChangeLog) After(
	ctx context.Context, after int64, batch int,
) ([]changelog.Recorded, error)

After returns up to batch entries past the cursor, oldest first.

Read inside the caller's transaction like every repository method here, so the tenant comes from the transaction rather than from a parameter and row level security compares it (ADR-0010). A stream that passed a tenant would be a stream that could be asked for somebody else's.

func (ChangeLog) Latest

func (c ChangeLog) Latest(ctx context.Context) (int64, error)

Latest is where the log stands now. Zero for a workspace nothing has happened in.

func (ChangeLog) Of

func (c ChangeLog) Of(ctx context.Context, entity string, id shared.ID) (map[string]shared.HLC, error)

Of answers the server's clock per field of one entity (repository.FieldClocks).

func (ChangeLog) Record

func (c ChangeLog) Record(ctx context.Context, change changelog.Change) error

Record writes one change inside the caller's transaction.

The sequence number is assigned by the database. It is the cursor a client pages on, so it has to be gapless and monotonic per tenant; a value chosen in the application would leave a hole wherever a transaction rolled back, and a client would then wait for a change that is never coming.

type CommentRepository

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

CommentRepository stores the discussion beside the entries.

func NewCommentRepository

func NewCommentRepository(cursors security.CursorCodec) CommentRepository

func (CommentRepository) Find

Find returns the comment, tombstone or not.

func (CommentRepository) Insert

func (r CommentRepository) Insert(ctx context.Context, comment work.Comment) error

Insert writes a new comment.

func (CommentRepository) List

List returns one page of one entry's comments, oldest first.

func (CommentRepository) SetBody

func (r CommentRepository) SetBody(
	ctx context.Context, comment work.Comment, expectedVersion int,
) error

SetBody writes the rewritten text under the optimistic lock. A tombstone is never matched (see the statement), so an edit racing a deletion comes back as a version conflict - the same answer as any other row that moved on, which is all the caller can act on either way.

func (CommentRepository) SetDeleted

func (r CommentRepository) SetDeleted(
	ctx context.Context, comment work.Comment, expectedVersion int, keepText bool,
) error

SetDeleted writes the tombstone under the optimistic lock, keeping the stored text when a legal hold covers the comment.

type Consumption

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

Consumption is the record of what a subscriber has already seen (ADR-0007). It lives here rather than beside the subscribers because the deduplication has to be in the same transaction as the reaction - two places would be two commits, and the gap between them is exactly the duplicate the record exists to prevent.

func NewConsumption

func NewConsumption(now clock.Clock) Consumption

func (Consumption) Claim

func (c Consumption) Claim(ctx context.Context, consumer string, eventID shared.ID) (bool, error)

Claim records that consumer is about to handle eventID and reports whether it is the first to do so. The insert is the question: an insert that changed nothing means somebody already asked.

type ContainerRepository

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

ContainerRepository stores hubs and collections.

Nothing here names a tenant. The transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010) - which is why the cross-tenant tests in test/integration are the ones that prove this file correct, not a unit test with a fake.

func NewContainerRepository

func NewContainerRepository(cursors security.CursorCodec) ContainerRepository

func (ContainerRepository) Find

Find returns the container as it is stored, including a trashed or an archived one. What that state means is the domain's decision (I-C2, I-C3); a query that filtered here would turn "it is in the trash" into "it does not exist".

func (ContainerRepository) InOrder

InOrder answers the collections across the hubs, in the tree's order.

The cursor carries the whole sort - the hub's rank, the hub, the collection's rank - and the collection as its identifier, so a boundary between two hubs is as exact as one inside a hub.

func (ContainerRepository) Insert

func (r ContainerRepository) Insert(ctx context.Context, container work.Container) (work.Container, error)

Insert writes the container.

The name check is the unique index rather than a query before the insert. A check followed by an insert is two statements with a gap, and two requests arriving inside that gap both pass the check - which is how duplicates appear on exactly the busy levels where they matter.

func (ContainerRepository) LastOrderKey

func (r ContainerRepository) LastOrderKey(ctx context.Context, parentID shared.ID) (string, error)

LastOrderKey returns the highest rank at one level, or the empty string when the level is empty.

func (ContainerRepository) List

List returns one page of one level, in the containers' manual order.

func (ContainerRepository) Neighbours

func (r ContainerRepository) Neighbours(
	ctx context.Context, parentID, beforeID, movingID shared.ID,
) (string, string, error)

func (ContainerRepository) RestoreBatch

RestoreBatch takes one container deletion back out of the trash, whole.

func (ContainerRepository) SetArchived

func (r ContainerRepository) SetArchived(
	ctx context.Context, container work.Container, expectedVersion int,
) error

SetArchived writes the container's own archive stamp, set or cleared.

func (ContainerRepository) SetAttributes

func (r ContainerRepository) SetAttributes(
	ctx context.Context, container work.Container, expectedVersion int,
) error

SetAttributes writes the container's own descriptive fields.

The name check is the unique index rather than a query before the update, for the reason Insert relies on it: a check followed by a write is two statements with a gap, and two renames arriving inside that gap both pass the check.

func (ContainerRepository) SetPlacement

func (r ContainerRepository) SetPlacement(
	ctx context.Context, container work.Container, expectedVersion int,
) error

SetPlacement writes where a collection sits and how it ranks there.

func (ContainerRepository) SetPolicies

func (r ContainerRepository) SetPolicies(
	ctx context.Context, container work.Container, expectedVersion int,
) error

SetPolicies writes one key of the policies document and leaves the others alone.

func (ContainerRepository) SetPrivate

func (r ContainerRepository) SetPrivate(
	ctx context.Context, container work.Container, expectedVersion int,
) error

SetPrivate writes whether a hub is private.

func (ContainerRepository) SetRank

func (r ContainerRepository) SetRank(
	ctx context.Context, container work.Container, expectedVersion int,
) error

Neighbours returns the ranks either side of a position at one container level. SetRank writes the rank alone, which is what a reorder changes and all it may change.

It touches no parent, which is what lets it rank a hub: `SetPlacement` needs one and a hub has none, so the identifier it would pass is the zero value and `uuidOf` refuses it before the statement is reached.

func (ContainerRepository) TrashSubtree

TrashSubtree puts a hub or a collection and everything under it into the trash under one batch.

Three statements for a hub and two for a collection, in one method because they must not be separable: a hub in the trash whose collections are still live is a tree that does not describe itself, and a collection in the trash whose items are not is a deletion no restore could reverse.

type CustomFieldRepository

type CustomFieldRepository struct{}

CustomFieldRepository stores the definitions a workspace adds to its entries.

The values are not here: they are a jsonb document on the entry, written through ItemRepository.SetCustomFields. This is the vocabulary, and keeping the two apart is what stops a read of an entry from reaching a statement written for the definitions.

Nothing here names a tenant: the transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010).

func NewCustomFieldRepository

func NewCustomFieldRepository() CustomFieldRepository

func (CustomFieldRepository) Find

Find returns the definition as it is stored, a deleted one included.

func (CustomFieldRepository) FindInScope

func (r CustomFieldRepository) FindInScope(
	ctx context.Context, collectionID shared.ID, key string,
) (work.CustomFieldDefinition, error)

FindInScope returns the live definition one collection sees under a key.

func (CustomFieldRepository) Insert

Insert writes a new definition.

func (CustomFieldRepository) ListInScope

func (r CustomFieldRepository) ListInScope(
	ctx context.Context, collectionID shared.ID,
) ([]work.CustomFieldDefinition, error)

ListInScope returns every live definition in force for one collection.

func (CustomFieldRepository) SetAttributes

func (r CustomFieldRepository) SetAttributes(
	ctx context.Context, definition work.CustomFieldDefinition, expectedVersion int,
) error

SetAttributes writes what an edit may change.

func (CustomFieldRepository) SetDeleted

func (r CustomFieldRepository) SetDeleted(
	ctx context.Context, definition work.CustomFieldDefinition, expectedVersion int,
) error

SetDeleted marks the definition out of use.

type DeletedTemplateRepository

type DeletedTemplateRepository struct{}

DeletedTemplateRepository reads and removes the templates a legal hold kept after their deletion (data-retention.md §4). Its own type rather than methods on TemplateRepository: the retention pass takes this half, and a port that carried both would let it write a template.

func (DeletedTemplateRepository) Deleted

Deleted returns one page of the deleted templates after the identifier given.

func (DeletedTemplateRepository) Remove

func (DeletedTemplateRepository) Remove(ctx context.Context, ids []shared.ID) ([]shared.ID, error)

Remove deletes the named templates that are still deleted and answers which went.

type DeletionJournalRepository

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

DeletionJournalRepository reads the deletion journal (backup-restore.md §7).

The reading half of the journal's promise: objects deleted between the archive point and the restore do not come back.

func NewDeletionJournalRepository

func NewDeletionJournalRepository(batch int) DeletionJournalRepository

func (DeletionJournalRepository) DeletedSince

func (r DeletionJournalRepository) DeletedSince(
	ctx context.Context, since time.Time, yield func(repository.Deletion) error,
) error

DeletedSince hands over the deletions recorded after an instant, oldest first.

type DeviceRepository

type DeviceRepository struct{}

DeviceRepository is the statements behind the devices that synchronise. Every one runs inside the caller's transaction and therefore under `SET LOCAL app.tenant_id` (rule 3).

func NewDeviceRepository

func NewDeviceRepository() DeviceRepository

func (DeviceRepository) CountExpired

func (r DeviceRepository) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

CountExpired reports what is due, bounded by the ceiling.

func (DeviceRepository) DeleteExpired

func (r DeviceRepository) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpired removes one batch of devices silent past the cutoff, revoking the session each last synchronised under first (the DEVICE data kind, offline-sync.md §6).

func (DeviceRepository) Find

Find answers one device of this workspace.

func (DeviceRepository) ForAccount

func (r DeviceRepository) ForAccount(ctx context.Context, accountID shared.ID) ([]domain.Device, error)

ForAccount lists one account's devices, most recently seen first.

func (DeviceRepository) Forget

func (r DeviceRepository) Forget(
	ctx context.Context, id, accountID shared.ID, now time.Time,
) (domain.Device, bool, error)

Forget marks the device blocked and answers it as it was.

func (DeviceRepository) Touch

func (r DeviceRepository) Touch(ctx context.Context, contact domain.Contact) (domain.Device, error)

Touch registers or records a contact, and refuses with the code that says why when it cannot.

type DispatchedEvents

type DispatchedEvents struct{}

DispatchedEvents is the outbox as the retention engine sees it: what is due, and one batch of it removed (ADR-0007's second countermeasure).

A type of its own rather than two more methods on Outbox, because the two have nothing to do with each other beyond the table. Outbox is the write path and the dispatcher; this is the sweep, and a retention engine that could reach Claim or Append would be an engine that could publish an event.

func NewDispatchedEvents

func NewDispatchedEvents() DispatchedEvents

func (DispatchedEvents) CountExpired

func (DispatchedEvents) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

CountExpired reports how many rows are due, counted no higher than the ceiling.

func (DispatchedEvents) DeleteExpired

func (DispatchedEvents) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpired removes one batch. The guard that a row nobody has consumed is never due lives in the query, not here - see db/queries/Outbox.sql.

func (DispatchedEvents) DeleteExpiredConsumption

func (DispatchedEvents) DeleteExpiredConsumption(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpiredConsumption removes one batch of consumption records. Called by the same sweep and bounded by the same period as the events themselves - see db/queries/Outbox.sql.

type EmbeddingRepository

type EmbeddingRepository struct{}

Semantic search's store (ADR-0050). No method takes a tenant: row level security bounds every statement (ADR-0010).

**The one repository in this package whose SQL is not generated**, and ADR-0050 decision 5 says why: sqlc reads `db/migrations`, and `item_embedding` is created inside a `DO` block because pgvector is detected rather than demanded - so it is invisible to the generator. That is a consequence of the capability being conditional, not a shortcut.

What rule 9 still holds here, and what the statements below are written to keep: every value is **bound**, and nothing a caller sent is ever concatenated into SQL text. The one string built at run time is the vector literal, which is assembled from float32s this process produced - never from anything that arrived in a request.

func NewEmbeddingRepository

func NewEmbeddingRepository() EmbeddingRepository

func (EmbeddingRepository) CountMissing

func (EmbeddingRepository) CountMissing(ctx context.Context, ceiling int) (int, error)

CountMissing reports how many entries have no vector at all.

func (EmbeddingRepository) Forget

func (EmbeddingRepository) Forget(ctx context.Context, creators []shared.ID) (int, error)

Forget removes the vectors of every entry the named people created.

func (EmbeddingRepository) ForgetAssigned

func (EmbeddingRepository) ForgetAssigned(ctx context.Context, assignees []shared.ID) (int, error)

ForgetAssigned removes the vectors of every entry the named people are on.

func (EmbeddingRepository) Near

func (EmbeddingRepository) Near(
	ctx context.Context, itemID shared.ID, floor float64, limit int,
) (repository.Nearby, error)

Near answers the entries closest to one entry, nearest first.

Two statements rather than one, and the first is the cheap one: an entry the embedding pass has not reached has no model, and asking for its neighbours would be asking the index to compare nothing. It is also what tells a proposal which model to record.

func (EmbeddingRepository) NearVector

func (EmbeddingRepository) NearVector(
	ctx context.Context, vector []float32, model string, floor float64, limit int,
) ([]repository.Neighbour, error)

NearVector answers the entries closest to a vector of no entry's (repository.Embeddings).

func (EmbeddingRepository) OtherSpaceRemains

func (EmbeddingRepository) OtherSpaceRemains(ctx context.Context, space string) (bool, error)

OtherSpaceRemains reports whether vectors of another model than space are still stored.

func (EmbeddingRepository) Owed

func (EmbeddingRepository) Owed(
	ctx context.Context, model string, batch int, skip, skipAssigned []shared.ID,
) ([]repository.OwedEmbedding, error)

Owed answers a batch of entries whose vectors are missing or stale.

Staleness is decided in the database rather than in Go, and that is what the `digest_of` function is for: the alternative is reading every entry's text into the process to fingerprint it, which is the whole table crossing the wire to find the handful of rows that moved.

func (EmbeddingRepository) Store

Store writes one entry's vector.

type EmergencyAccessRepository

type EmergencyAccessRepository struct{}

EmergencyAccessRepository keeps the emergency accesses to private hubs (ADR-0073 §4). Row level security bounds every statement to the transaction's tenant (ADR-0010).

func NewEmergencyAccessRepository

func NewEmergencyAccessRepository() EmergencyAccessRepository

func (EmergencyAccessRepository) Find

Find answers one access with its hub's name.

func (EmergencyAccessRepository) Insert

Insert writes a new access.

func (EmergencyAccessRepository) List

List answers a hub's accesses, newest first.

func (EmergencyAccessRepository) Members

func (r EmergencyAccessRepository) Members(ctx context.Context, hubID shared.ID) ([]shared.ID, error)

Members answers the hub's members.

func (EmergencyAccessRepository) NoticesFor

func (r EmergencyAccessRepository) NoticesFor(
	ctx context.Context, recipientID shared.ID, since time.Time,
) ([]work.EmergencyAccess, error)

NoticesFor answers the accesses the recipient was told of since the moment.

func (EmergencyAccessRepository) Running

func (r EmergencyAccessRepository) Running(
	ctx context.Context, hubID, openedBy shared.ID, now time.Time,
) (work.EmergencyAccess, bool, error)

Running answers the account's running access to the hub.

type EpochRepository

type EpochRepository struct{}

EpochRepository is the workspace's synchronisation epoch, a column of the tenant row.

func NewEpochRepository

func NewEpochRepository() EpochRepository

func (EpochRepository) Advance

func (EpochRepository) Advance(ctx context.Context) (int64, error)

func (EpochRepository) Current

func (EpochRepository) Current(ctx context.Context) (int64, error)

type ExternalAccountRepository

type ExternalAccountRepository struct{}

ExternalAccountRepository is the link between a provider's subject and an account.

`account_identity` rather than `account.external_subject`, which holds one subject per account and cannot say which provider vouched for it. The column stays where it is - a rolling update still reads it - and nothing here writes it, which is why an account's links live in the table and the column is a contract step for a later migration.

Its own type rather than a method on AccountRepository, for the reason every slice here has one: the sign-in flow needs two statements about a table nothing else touches, and a repository that could write a link from anywhere is one that eventually does.

func NewExternalAccountRepository

func NewExternalAccountRepository() ExternalAccountRepository

func (ExternalAccountRepository) CountWithoutIdentityAt

func (ExternalAccountRepository) CountWithoutIdentityAt(
	ctx context.Context, providerIDs []shared.ID,
) (int, error)

CountWithoutIdentityAt counts the workspace's active people no provider in the list signs in (ADR-0078 §1). The providers travel as one array parameter (rule 9).

func (ExternalAccountRepository) FindBySubject

func (ExternalAccountRepository) FindBySubject(
	ctx context.Context, providerID shared.ID, subject string,
) (identity.Account, error)

func (ExternalAccountRepository) HasIdentity

func (ExternalAccountRepository) HasIdentity(ctx context.Context, accountID shared.ID) (bool, error)

HasIdentity answers whether the account already signs in through some provider - a credential of its own, which ADR-0071's addendum does not let another provider's word override.

func (ExternalAccountRepository) HoldersOf

func (ExternalAccountRepository) HoldersOf(ctx context.Context, providerID shared.ID) ([]shared.ID, error)

HoldersOf answers the accounts of this workspace connected to the provider.

func (ExternalAccountRepository) IdentitiesOf

func (ExternalAccountRepository) IdentitiesOf(
	ctx context.Context, accountID shared.ID,
) ([]repository.Connection, error)

ProvidersOf answers the providers the account is connected to (ADR-0075 §2). IdentitiesOf answers the account's connections in this workspace, oldest first.

func (ExternalAccountRepository) LinkSubject

func (ExternalAccountRepository) LinkSubject(
	ctx context.Context, providerID, accountID shared.ID, subject string, now time.Time,
) (bool, error)

func (ExternalAccountRepository) ProvidersOf

func (ExternalAccountRepository) ProvidersOf(ctx context.Context, accountID shared.ID) ([]shared.ID, error)

func (ExternalAccountRepository) UnlinkAll

func (ExternalAccountRepository) UnlinkAll(ctx context.Context, accountID shared.ID) (int, error)

UnlinkAll drops every provider identity of the account (ADR-0078 §1).

type FigureRepository

type FigureRepository struct{}

FigureRepository counts collections' figures (db/queries/Figures.sql). One statement per kind on the page, never one per collection; row level security keeps each inside the transaction's workspace.

func NewFigureRepository

func NewFigureRepository() FigureRepository

func (FigureRepository) Count

func (r FigureRepository) Count(
	ctx context.Context, requests []work.FigureRequest, window work.FigureWindow,
) (map[shared.ID]work.FigureCounts, error)

Count answers what each request's kind needs.

type GroupRepository

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

GroupRepository is the group table and its member links.

func NewGroupRepository

func NewGroupRepository(cursors security.CursorCodec) GroupRepository

func (GroupRepository) AddMember

func (r GroupRepository) AddMember(ctx context.Context, groupID, accountID shared.ID) error

func (GroupRepository) Delete

func (r GroupRepository) Delete(ctx context.Context, groupID shared.ID) error

func (GroupRepository) Find

func (r GroupRepository) Find(ctx context.Context, groupID shared.ID) (identity.Group, error)

func (GroupRepository) Insert

func (r GroupRepository) Insert(ctx context.Context, group identity.Group) error

func (GroupRepository) List

func (GroupRepository) Lock

func (r GroupRepository) Lock(ctx context.Context, groupID shared.ID) error

Lock takes the group's row for the rest of the transaction.

func (GroupRepository) Members

func (r GroupRepository) Members(ctx context.Context, groupID shared.ID) ([]shared.ID, error)

func (GroupRepository) RemoveMember

func (r GroupRepository) RemoveMember(ctx context.Context, groupID, accountID shared.ID) error

func (GroupRepository) Update

func (r GroupRepository) Update(ctx context.Context, group identity.Group, expectedVersion int) error

type HubLockRepository

type HubLockRepository struct{}

HubLockRepository takes a hub's row for the writes that must not interleave on a private hub.

func NewHubLockRepository

func NewHubLockRepository() HubLockRepository

func (HubLockRepository) GroupHoldsRoleUnder

func (r HubLockRepository) GroupHoldsRoleUnder(ctx context.Context, hubID shared.ID) (bool, error)

GroupHoldsRoleUnder answers whether a group holds a role on the hub or anywhere below it.

func (HubLockRepository) LockHubOf

func (r HubLockRepository) LockHubOf(ctx context.Context, id shared.ID) (repository.Hub, bool, error)

LockHubOf locks and answers the hub the identifier sits under. The tenant is the transaction's: row level security hides another tenant's rows, which then answer as not found (ADR-0010).

type IdempotencyStore

type IdempotencyStore struct{}

IdempotencyStore keeps the answers of completed attempts in idempotency_key.

No method names a tenant. The reservation takes its tenant from current_tenant_id(), which is the value row level security compares against, so a row cannot be written into the wrong tenant even by a caller that wanted to (ADR-0010).

func NewIdempotencyStore

func NewIdempotencyStore() IdempotencyStore

func (IdempotencyStore) Complete

func (s IdempotencyStore) Complete(ctx context.Context, key repository.Key, status int, body []byte) error

Complete stores the answer. A body that is not JSON is stored as no body at all: the column is jsonb, and this API answers JSON - anything else is a bug worth not compounding by failing the request that produced it.

func (IdempotencyStore) CountExpired

func (s IdempotencyStore) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

CountExpired answers how many keys are due, up to the ceiling.

func (IdempotencyStore) DeleteExpired

func (s IdempotencyStore) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpired removes one batch of keys older than the cutoff (the IDEMPOTENCY_KEY data kind, data-retention.md §3). The tenant is the transaction's, through row level security.

func (IdempotencyStore) Release

func (s IdempotencyStore) Release(ctx context.Context, key repository.Key) error

Release lets a reservation go. The engine calls it for an action that claimed its key and then failed, in the same transaction: the claim and the failure commit together, so a replay of the run finds the key free and performs the action the first run never did.

func (IdempotencyStore) Reserve

func (s IdempotencyStore) Reserve(
	ctx context.Context,
	key repository.Key,
	requestHash []byte,
) (repository.Record, bool, error)

Reserve claims the key, and reads the existing record only when the claim was lost. The common path - a key nobody has used - is one round trip.

type IdentityProviderRepository

type IdentityProviderRepository struct{}

func NewIdentityProviderRepository

func NewIdentityProviderRepository() IdentityProviderRepository

func (IdentityProviderRepository) Count

func (IdentityProviderRepository) Delete

func (IdentityProviderRepository) Find

func (IdentityProviderRepository) FindWithSecret

func (IdentityProviderRepository) Insert

func (IdentityProviderRepository) List

func (IdentityProviderRepository) ListSealed

func (IdentityProviderRepository) Reconfigure

Reconfigure writes everything but the switch, which the statement's COALESCE keeps as the row holds it (ADR-0076 §5).

func (IdentityProviderRepository) RewrapSecret

func (IdentityProviderRepository) RewrapSecret(
	ctx context.Context, providerID shared.ID, sealed crypto.Sealed, expectedKeyID string,
) (bool, error)

func (IdentityProviderRepository) SetWithdrawal

func (IdentityProviderRepository) SetWithdrawal(
	ctx context.Context, providerID shared.ID, at, now time.Time,
) (identity.IdentityProvider, bool, error)

SetWithdrawal sets or clears when an installation's offer ends (ADR-0076 §2).

func (IdentityProviderRepository) Update

type ImportRunRepository

type ImportRunRepository struct{}

ImportRunRepository stores what an import asked and did. The shape is the restore run's: the applier writes the same report and the same progress into it, because the applying is the restore's.

func NewImportRunRepository

func NewImportRunRepository() ImportRunRepository

func (ImportRunRepository) Claim

func (r ImportRunRepository) Claim(ctx context.Context, id shared.ID, at time.Time) (bool, error)

Claim moves the run to RUNNING; a run already RUNNING claims itself again.

func (ImportRunRepository) Find

Find answers one run.

func (ImportRunRepository) Finish

func (r ImportRunRepository) Finish(ctx context.Context, outcome domain.Outcome) error

Finish records how the run ended.

func (ImportRunRepository) Insert

func (r ImportRunRepository) Insert(ctx context.Context, run domain.Run) error

Insert writes the accepted run. The tenant is current_tenant_id(): the transaction's.

func (ImportRunRepository) RecordProgress

func (r ImportRunRepository) RecordProgress(
	ctx context.Context, id shared.ID, report backup.Report, progress map[string]int,
) error

RecordProgress writes how far the applier got, and the report so far.

type InstallationPresetRepository

type InstallationPresetRepository struct{}

InstallationPresetRepository keeps the installation's presets (db/queries/InstallationPreset.sql). Their settings are a layer's settings document (StructureDocument.go), as a saved preset's are.

func NewInstallationPresetRepository

func NewInstallationPresetRepository() InstallationPresetRepository

func (InstallationPresetRepository) ClearDefault

func (InstallationPresetRepository) ClearDefault(ctx context.Context, except, by shared.ID, at time.Time) error

ClearDefault makes every preset but one no default.

func (InstallationPresetRepository) Count

Count answers how many there are.

func (InstallationPresetRepository) Default

Default answers the default preset, where there is one.

func (InstallationPresetRepository) Delete

func (InstallationPresetRepository) Delete(ctx context.Context, id shared.ID, expected *int) error

Delete removes a preset.

func (InstallationPresetRepository) Find

Find answers one.

func (InstallationPresetRepository) Insert

Insert stores a new preset.

func (InstallationPresetRepository) List

List answers every preset by name.

func (InstallationPresetRepository) Update

func (InstallationPresetRepository) Update(
	ctx context.Context, preset work.InstallationPreset, expected *int, by shared.ID, at time.Time,
) error

Update writes a preset whole.

type InstallationRepository

type InstallationRepository struct{}

InstallationRepository answers the census (migration 0105).

Its own type rather than a method on one of the others, for the reason every slice here has one: the census reaches through a SECURITY DEFINER function that can answer five integers and nothing else, and a repository that could ask it from anywhere is one that eventually grows a sixth question.

func NewInstallationRepository

func NewInstallationRepository() InstallationRepository

func (InstallationRepository) Census

type InstanceJournal

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

InstanceJournal writes the installation's own evidence (audit.md §6). The table carries no row-level-security policy, so the write lands inside whatever transaction the act runs in - including the one that ends the tenant it names.

func NewInstanceJournal

func NewInstanceJournal(cursors security.CursorCodec) InstanceJournal

NewInstanceJournal takes the codec the read needs; a writer-only wiring may leave it zero, and only `Page` would notice.

func (InstanceJournal) Page

Page walks the journal backwards, newest first.

Keyed on the moment and the identifier together, which is the same keyset every other listing here walks: two entries can share a moment - a provisioning writes one while a suspension writes another - and an offset would then skip or repeat one.

No tenant anywhere in it, deliberately: the journal is the installation's own record and its rows are about workspaces that are usually gone, which is the reason the table exists.

func (InstanceJournal) Record

Record appends one entry.

type InstanceSettingRepository

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

InstanceSettingRepository is the installation's own level (ADR-0070 §2).

**This file is the only thing that knows how a switch is spelled in a row.** The keys are dotted and the values are JSON, because the set grows with every feature that has an installation-wide default and a column per switch would be a migration per preference; on the other side of this seam the level is the typed value the domain resolves, and nothing above here parses anything. It is `settingsDocument`'s discipline applied one level up.

func FromFile

func FromFile(path string, enforced bool) InstanceSettingRepository

FromFile is the repository that knows a file is in play.

`seed` leaves this a plain database repository once the seeding is done — the file wrote the values once and the API owns them from then on, which is what "schreibt sie beim ersten Start und lässt sie danach in Ruhe" means. `enforce` keeps both facts, because both are answers a reader needs: where the values came from, and why a save is refused.

func NewInstanceSettingRepository

func NewInstanceSettingRepository() InstanceSettingRepository

func (InstanceSettingRepository) Read

Read answers the whole level.

func (InstanceSettingRepository) Write

func (r InstanceSettingRepository) Write(
	ctx context.Context, level repository.InstanceLevel, expected int, by shared.ID, at time.Time,
) (bool, error)

Write replaces the level whole, while it is still at `expected`.

The version moves first: its statement takes the row lock, so a second writer on the same version waits here, before it has written a single switch, and is then refused.

type ItemLabelRepository

type ItemLabelRepository struct{}

ItemLabelRepository stores which items carry which labels, and the tags that decide it after an offline merge.

Two tables behind one type. `item_label` is the membership every read goes through, and `set_element` is the OR-set tag that survives a merge (offline-sync.md §4.2). They are written together, in one method each, because writing one without the other is the failure: membership with no tag merges as last writer wins and loses a concurrent change, a tag with no membership is invisible to every read.

func NewItemLabelRepository

func NewItemLabelRepository() ItemLabelRepository

func (ItemLabelRepository) Add

func (r ItemLabelRepository) Add(
	ctx context.Context, itemID, labelID shared.ID, tag shared.HLC,
) error

Add puts a label on an item and records the addition's tag.

func (ItemLabelRepository) Elements

func (r ItemLabelRepository) Elements(
	ctx context.Context, itemID shared.ID,
) ([]work.SetElement, error)

Elements returns every tag of one item's label set.

func (ItemLabelRepository) List

func (r ItemLabelRepository) List(ctx context.Context, itemID shared.ID) ([]shared.ID, error)

List returns the labels an item carries, deleted labels left out.

func (ItemLabelRepository) ListFor

func (r ItemLabelRepository) ListFor(
	ctx context.Context, itemIDs []shared.ID,
) (map[shared.ID][]shared.ID, error)

ListFor returns the labels a page of entries carries, keyed by entry.

func (ItemLabelRepository) Remove

func (r ItemLabelRepository) Remove(
	ctx context.Context, itemID, labelID shared.ID, tag shared.HLC,
) (bool, error)

Remove takes a label off an item, records the removal's tag, and reports whether the item carried it at all.

type ItemMemberRepository

type ItemMemberRepository struct{}

ItemMemberRepository stores which accounts an entry carries, and the tags that decide it after a merge.

Two tables behind one type, exactly as ItemLabelRepository holds them. `item_member` is the membership every read goes through, and `set_element` is the OR-set tag that survives a merge (offline-sync.md §4.2). They are written together, in one method each, because writing one without the other is the failure: membership with no tag merges as last writer wins and loses a concurrent change, a tag with no membership is invisible to every read.

The set the tags are written under is `members` rather than `labels`, which is the whole of the difference between this type and that one - the statements behind both are the same three, and `set_name` is a parameter of them for exactly this second caller.

func NewItemMemberRepository

func NewItemMemberRepository() ItemMemberRepository

func (ItemMemberRepository) Add

func (r ItemMemberRepository) Add(
	ctx context.Context, itemID, accountID shared.ID, tag shared.HLC,
) error

Add puts an account on an entry and records the addition's tag.

func (ItemMemberRepository) Elements

func (r ItemMemberRepository) Elements(
	ctx context.Context, itemID shared.ID,
) ([]work.SetElement, error)

Elements returns every tag of one entry's member set.

func (ItemMemberRepository) List

func (r ItemMemberRepository) List(ctx context.Context, itemID shared.ID) ([]shared.ID, error)

List returns the accounts an entry carries.

func (ItemMemberRepository) Remove

func (r ItemMemberRepository) Remove(
	ctx context.Context, itemID, accountID shared.ID, tag shared.HLC,
) (bool, error)

Remove takes an account off an entry, records the removal's tag, and reports whether the entry carried it at all.

type ItemRepository

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

ItemRepository stores tasks, work packages and activities - one table for all three, because they are one aggregate (ADR-0006).

Nothing here names a tenant. The transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010) - which is why the cross-tenant tests in test/integration are what prove this file correct, not a unit test with a fake.

func NewItemRepository

func NewItemRepository(cursors security.CursorCodec) ItemRepository

func (ItemRepository) ChildCompletion

func (r ItemRepository) ChildCompletion(ctx context.Context, parentID shared.ID) (work.ChildCompletion, error)

ChildCompletion counts one item's children and how many are done.

func (ItemRepository) ClaimDueSoon

func (r ItemRepository) ClaimDueSoon(
	ctx context.Context, now, threshold time.Time, limit int,
) ([]repository.DueAnnouncement, error)

ClaimDueSoon takes the entries whose deadline has come within the lead and stamps them as announced, in one statement.

func (ItemRepository) ClaimOverdue

func (r ItemRepository) ClaimOverdue(
	ctx context.Context, now time.Time, limit int,
) ([]repository.DueAnnouncement, error)

ClaimOverdue takes the entries whose deadline has passed with the work not done.

func (ItemRepository) CountOpenByAssignee

func (r ItemRepository) CountOpenByAssignee(
	ctx context.Context, accounts []shared.ID,
) (map[shared.ID]int, error)

CountOpenByAssignee counts the open entries each of the given accounts carries, tenant-wide. What "open" means - and why an ancestor's archive is not consulted - is stated at the query.

func (ItemRepository) EntriesBelow

func (r ItemRepository) EntriesBelow(ctx context.Context, ids []shared.ID) ([]repository.EntriesBelow, error)

EntriesBelow answers the live entries under each entry among ids, in one recursive statement. Another tenant's entries are absent, by the predicate and row level security.

func (ItemRepository) Find

Find returns the item as it is stored, including a trashed or an archived one (I-W4).

func (ItemRepository) FindByCalendarUID

func (r ItemRepository) FindByCalendarUID(ctx context.Context, uid string) (work.WorkItem, error)

FindByCalendarUID returns the item a calendar client's UID names.

Not found is the whole of what a miss says, exactly as in Find: the partial unique index is per tenant and the transaction's tenant is the only one the statement can see, so an address another workspace's client chose is one this workspace does not hold.

func (ItemRepository) FindMany

func (r ItemRepository) FindMany(ctx context.Context, ids []shared.ID) ([]work.WorkItem, error)

FindMany returns the entries among ids as Find returns each; an identifier this tenant does not hold is absent from the answer, for the reason Find answers not found for it.

func (ItemRepository) Flatten

func (r ItemRepository) Flatten(ctx context.Context, flattening repository.Flattening) error

Flatten makes an entry a top-level entry of another type, and moves the paths of the trashed entries still under it along.

func (ItemRepository) Insert

func (r ItemRepository) Insert(ctx context.Context, item work.WorkItem) (work.WorkItem, error)

Insert writes the item.

One uniqueness is translated and one deliberately is not. The identity: a client that minted its own (a CalDAV client's address) and presents one the workspace already holds has to hear "taken" rather than "database error", because it is the one thing it can act on - look the entry up and update it. The title is not: two items in one collection may share one. A shopping list with "milk" on it twice is a list somebody wrote that way, and a container's name is the thing that has to be unique because it is how a person navigates.

func (ItemRepository) InsertCopy

func (r ItemRepository) InsertCopy(ctx context.Context, duplicate repository.Copy) error

InsertCopy writes an entry that carries another one's description.

func (ItemRepository) LastOrderKey

func (r ItemRepository) LastOrderKey(ctx context.Context, collectionID, parentID shared.ID) (string, error)

LastOrderKey returns the highest rank among a new item's siblings, or the empty string when it would be the first.

func (ItemRepository) List

List returns one page of one level of one collection, in the items' manual order.

Unlike Find, this filters: a trashed item is not part of a level, and an archived one only when the caller says so. Find answers "what is this item", where the lifecycle state is the answer; this answers "what is in here", where it is not.

func (ItemRepository) MoveSubtree

func (r ItemRepository) MoveSubtree(
	ctx context.Context, move repository.Move,
) (int, []work.DroppedReference, error)

MoveSubtree rewrites the item's placement and its subtree's paths, and returns the subtree's size.

Two statements in the order that matters. The placement carries the optimistic lock, so it runs first and a stale version stops the move before any path is rewritten. The subtree rewrite then cannot fail on a version, which is why it is safe to have no lock of its own: it is bounded by the path prefix of a row this transaction has already claimed.

The moved item is written by the first statement and excluded from the second, so one move moves one version per row. Written the other way round - both statements matching the moved item - a single move would bump its version twice, which is an artefact of the split rather than anything a caller asked for.

func (ItemRepository) Neighbours

func (r ItemRepository) Neighbours(
	ctx context.Context, level repository.Level, beforeID, movingID shared.ID,
) (string, string, error)

Neighbours returns the ranks either side of a position at one level.

func (ItemRepository) NextDueAnnouncement

func (r ItemRepository) NextDueAnnouncement(
	ctx context.Context, lead time.Duration,
) (*time.Time, error)

NextDueAnnouncement answers when the tenant next owes an announcement.

func (ItemRepository) Query

Query answers the query language: a filter over an anchored scope, sorted, paged, grouped.

func (ItemRepository) RecordOrigin

func (r ItemRepository) RecordOrigin(ctx context.Context, itemID, entryID shared.ID) (bool, error)

RecordOrigin writes the provenance a conversion records: which jumble entry the item came from. Set exactly once - the guard is in the statement - and false reports that another conversion, or nothing, already owns the row.

func (ItemRepository) RestoreBatch

func (r ItemRepository) RestoreBatch(ctx context.Context, restore repository.ItemTrash) (int, error)

RestoreBatch takes one deletion back out of the trash, whole.

The batch rather than the subtree: a younger deletion inside the same subtree is a decision somebody else made and stays in the trash. It reaches the items of a trashed collection too - they carry the container's batch, so a container's restore ends here for its entries.

func (ItemRepository) Search

Search answers the full text search: one page of entries in the order the database ranked them.

It sits beside the query language's half rather than among the generated statements for the same reason that one does: the statement is compiled from a validated request, and it runs on the transaction the unit of work opened. What is different is the walk - a search is ordered by a rank the query computes rather than by a column, so the cursor carries that rank.

func (ItemRepository) SetArchived

func (r ItemRepository) SetArchived(
	ctx context.Context, item work.WorkItem, expectedVersion int,
) error

SetArchived writes an item's archive stamp, set or cleared.

func (ItemRepository) SetAssignee

func (r ItemRepository) SetAssignee(ctx context.Context, item work.WorkItem, expectedVersion int) error

SetAssignee writes the one person the entry is on, set or cleared, or reports a version conflict.

The whole item is passed and the identifier read off it, as everywhere else in this adapter: the decision about what the row should say has already been taken, and an adapter handed the account alone would have to be trusted to put it on the right row.

func (ItemRepository) SetAttributes

func (r ItemRepository) SetAttributes(ctx context.Context, item work.WorkItem, expectedVersion int) error

SetAttributes writes an item's own fields: the title, and the notes where the type carries them.

One statement writing both columns, whichever of them moved. What the row should say was decided by the use case, and re-deciding it here from a list of changed fields would put that rule in the layer that is not allowed to hold one (ADR-0005).

func (ItemRepository) SetCompletion

func (r ItemRepository) SetCompletion(ctx context.Context, item work.WorkItem, expectedVersion int) error

SetCompletion writes the completion, against the version the caller decided on.

func (ItemRepository) SetCover

func (r ItemRepository) SetCover(ctx context.Context, item work.WorkItem, expectedVersion int) error

SetCover writes the cover, set or cleared, or reports a version conflict. The consistency of the three columns is the table's CHECK (migration 0013); this only spells the domain's value out into them.

func (ItemRepository) SetCustomField

func (r ItemRepository) SetCustomField(
	ctx context.Context, item work.WorkItem, key string, definitionID shared.ID,
	expectedVersion int,
) error

SetCustomField writes one key of the entry's custom field document.

One key rather than the whole document: the row may hold values whose definitions were deleted - hidden from every read, but kept - and a write that replaced the document with what a read answered would erase them. The value travels as jsonb; nil (the key absent from the wanted state) removes it, which is the one spelling "cleared" has.

func (ItemRepository) SetDueDate

func (r ItemRepository) SetDueDate(ctx context.Context, item work.WorkItem, expectedVersion int) error

SetDueDate writes the due trio, set or cleared whole, or reports a version conflict.

The whole item is passed and the trio read off it, as SetAssignee does and for the same reason: the decision about what the row should say has already been taken, and the three columns travel together because none of them means anything alone.

func (ItemRepository) SetOrderKey

func (r ItemRepository) SetOrderKey(ctx context.Context, item work.WorkItem, expectedVersion int) error

SetOrderKey writes a new rank and the column for one item.

func (ItemRepository) ShapesIn

func (r ItemRepository) ShapesIn(
	ctx context.Context, collectionIDs []shared.ID, limit int,
) ([]repository.EntryShape, error)

ShapesIn answers up to limit live entries of the collections, with the capabilities each holds a value for. Another tenant's collection is absent, by the predicate and row level security.

func (ItemRepository) Subtree

func (r ItemRepository) Subtree(
	ctx context.Context, item work.WorkItem, limit int,
) ([]work.WorkItem, error)

Subtree reads everything below one entry, parents before their children.

One row beyond the caller's limit is asked for, so that the caller can tell a subtree that fits from one that does not: the statement answering exactly `limit` rows says nothing about whether there is a row after them.

func (ItemRepository) TrashSubtree

func (r ItemRepository) TrashSubtree(ctx context.Context, trash repository.ItemTrash) (int, error)

TrashSubtree puts an item and everything under it into the trash under one batch.

Two statements and not one: the item's own row carries the optimistic lock the caller read against, and its descendants do not - they are not rows anybody read, and refusing the deletion because a child changed while the request was in flight would make deleting a busy subtree a matter of luck. The item is excluded from the subtree statement so that one deletion moves its version once.

func (ItemRepository) TypesOf

func (r ItemRepository) TypesOf(ctx context.Context, ids []shared.ID) (map[shared.ID]work.ItemType, error)

TypesOf answers the type of each entry among ids that this tenant holds.

type ItemTallyRepository

type ItemTallyRepository struct{}

ItemTallyRepository counts what a page of entries holds - parts, comments, attachments - in one statement (TallyItems in db/queries/Structure.sql). It reads within the caller's unit of work, as every repository here does, so the tenant's row-level security applies to the counts too.

func NewItemTallyRepository

func NewItemTallyRepository() ItemTallyRepository

func (ItemTallyRepository) TallyFor

func (r ItemTallyRepository) TallyFor(
	ctx context.Context, itemIDs []shared.ID,
) (map[shared.ID]repository.ItemTally, error)

TallyFor answers every entry asked about that the reader can see.

type JobListener

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

JobListener is the wake-up for the worker: one connection per process holding a `LISTEN`, and a single doorbell out of it (ADR-0007's first countermeasure).

Why this is a second listener rather than a second channel on the first

The two live in different processes. `ChangeListener` runs where streams are served - the API role - and this runs where jobs are executed - the worker role. A single listener would mean the API process holding a subscription for notifications nobody in it is waiting for, and the worker process holding one for streams it does not serve. Two small listeners, each in the role that needs it, is what the role split is for.

Why holding a connection is not a hole in rule 3

ChangeListener's reasoning, unchanged: the only statement is `LISTEN hubtask_job`, it touches no table, and nothing else can reach this connection - it is unexported and this type offers no way to run anything on it. What arrives back is a doorbell with an empty payload. Every claim still goes through the transaction wrapper.

What it is not

It is not the delivery guarantee. A missed notification costs latency and nothing else: the runner's poll is the fallback, and it is what a process whose listener has never connected falls back to. That is why this can be optional at the composition root and why a failure here is a warning rather than an error.

func NewJobListener

func NewJobListener(pool *pgxpool.Pool) *JobListener

func (*JobListener) Connected

func (l *JobListener) Connected() bool

Connected reports whether a listening connection is currently held. What the health report reads: a worker without one still runs every job, and runs them at the poll interval.

func (*JobListener) Run

func (l *JobListener) Run(ctx context.Context)

Run holds the listening connection for as long as the context lives, reconnecting when it drops.

Started through SafeGo by the composition root like every other loop (rule 5). It returns when the context is cancelled and not before: a listener that gave up after one failure would leave the worker at its poll interval for the rest of the process's life, which is a degradation nothing else reports.

func (*JobListener) Woken

func (l *JobListener) Woken() <-chan struct{}

Woken is the channel the runner selects on beside its poll timer.

type JobRepository

type JobRepository struct{}

JobRepository is the caller's side of the job table.

It reads the same rows as Queue and shares none of its statements, because the two ask different questions: the queue claims work across tenants and this one answers for exactly one. The tenant is taken from the transaction the unit of work opened, never from an argument - `job` is the one table without row level security (db/migrations/0001_init.sql), so the condition that a policy applies everywhere else is applied here, in one place, and a method that forgot it would be reading across the boundary.

func NewJobRepository

func NewJobRepository() JobRepository

func (JobRepository) AskerOf

func (r JobRepository) AskerOf(ctx context.Context, id shared.ID) (repository.Asker, error)

AskerOf answers who asked a job and about what (repository.Jobs).

func (JobRepository) Cancel

func (r JobRepository) Cancel(
	ctx context.Context, id shared.ID, now time.Time,
) (domain.Job, error)

Cancel stops a queued or running job, in one statement that carries both conditions.

The state condition is in the statement rather than only in the caller's check, because the caller's check ran a moment earlier: a job that succeeded in between must not read back as cancelled. No lease is named - the caller holds none, and a pass that is under way finds at its next write that the row is no longer RUNNING under its lease and rolls back.

func (JobRepository) Find

func (r JobRepository) Find(ctx context.Context, id shared.ID) (domain.Job, error)

Find answers the job the caller's tenant owns.

type JumbleIntakeRepository

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

JumbleIntakeRepository is the tenant's one webhook address.

The hasher is the intake's own purpose over the installation secret, so a rule's inbound token presented here matches nothing and a stored hash can never be replayed as a credential of another kind (security.md §5).

func NewJumbleIntakeRepository

func NewJumbleIntakeRepository(hasher security.InboundTokenHasher) JumbleIntakeRepository

func (JumbleIntakeRepository) RotatedAt

func (r JumbleIntakeRepository) RotatedAt(ctx context.Context) (time.Time, error)

func (JumbleIntakeRepository) SetToken

func (JumbleIntakeRepository) VerifyToken

func (r JumbleIntakeRepository) VerifyToken(
	ctx context.Context, token integration.InboundToken,
) (bool, error)

type JumbleRepository

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

JumbleRepository is the inbox's table. Like every repository it is called inside a unit of work and never opens a transaction of its own; row level security bounds every statement to the tenant of the running transaction (ADR-0010).

func NewJumbleRepository

func NewJumbleRepository(cursors security.CursorCodec) JumbleRepository

func (JumbleRepository) CountExpired

func (r JumbleRepository) CountExpired(
	ctx context.Context, cutoff time.Time, ceiling int,
) (int, error)

CountExpired reports how many entries are due, counted no higher than ceiling.

func (JumbleRepository) DeleteExpired

func (r JumbleRepository) DeleteExpired(
	ctx context.Context, cutoff time.Time, batch int,
) (int, error)

DeleteExpired removes one batch of entries that arrived before the cutoff and were never converted (data-retention.md §3).

Which entries those are lives in the statement rather than in a parameter: an entry that became a work item is that item's provenance, and no period a tenant configures may reach it.

func (JumbleRepository) Find

func (JumbleRepository) Insert

func (r JumbleRepository) Insert(ctx context.Context, entry domain.Entry) error

func (JumbleRepository) List

func (JumbleRepository) Settle

func (r JumbleRepository) Settle(ctx context.Context, entry domain.Entry) (bool, error)

type KeptCommentTextRepository

type KeptCommentTextRepository struct{}

KeptCommentTextRepository reads and clears the text a legal hold kept on deleted comments (data-retention.md §4). Its own type rather than methods on CommentRepository: the retention pass takes this half, and a port that carried both would let it write a comment's body.

func (KeptCommentTextRepository) Clear

Clear removes the kept text of the comments named.

func (KeptCommentTextRepository) Kept

Kept returns one page of the deleted comments whose text is kept, after the identifier given.

type KudosRepository

type KudosRepository struct{}

KudosRepository keeps kudos (db/queries/Kudos.sql). Every read is one person's own side.

func NewKudosRepository

func NewKudosRepository() KudosRepository

func (KudosRepository) Give

func (KudosRepository) Give(ctx context.Context, kudos work.Kudos) (bool, error)

Give records the kudos; kudos already given keep their moment.

func (KudosRepository) Given

func (KudosRepository) Given(ctx context.Context, itemID, givenBy, givenTo shared.ID) (bool, error)

Given reports whether the giver gave kudos to this completion.

func (KudosRepository) Received

func (KudosRepository) Received(ctx context.Context, itemID, givenTo shared.ID) ([]work.Kudos, error)

Received answers the kudos the person received on the entry, oldest first.

func (KudosRepository) TakeBack

func (KudosRepository) TakeBack(ctx context.Context, itemID, givenBy, givenTo shared.ID) (bool, error)

TakeBack removes the giver's kudos on this completion; none given is no error.

type LabelRepository

type LabelRepository struct{}

LabelRepository stores a collection's vocabulary.

Nothing here names a tenant: the transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010).

func NewLabelRepository

func NewLabelRepository() LabelRepository

func (LabelRepository) Find

func (r LabelRepository) Find(ctx context.Context, id shared.ID) (work.Label, error)

Find returns the label as it is stored, a deleted one included.

func (LabelRepository) Insert

func (r LabelRepository) Insert(ctx context.Context, label work.Label) error

Insert writes the label. The name check is the unique index rather than a query before the insert, for the reason BucketRepository.Insert relies on it.

func (LabelRepository) List

func (r LabelRepository) List(ctx context.Context, collectionID shared.ID) ([]work.Label, error)

List returns a collection's labels by name.

func (LabelRepository) ListWithDeleted

func (r LabelRepository) ListWithDeleted(
	ctx context.Context, collectionID shared.ID,
) ([]work.Label, error)

ListWithDeleted returns a collection's labels by name, deleted ones included.

func (LabelRepository) SetAttributes

func (r LabelRepository) SetAttributes(
	ctx context.Context, label work.Label, expectedVersion int,
) error

SetAttributes writes a label's own fields.

func (LabelRepository) SetDeleted

func (r LabelRepository) SetDeleted(
	ctx context.Context, label work.Label, expectedVersion int,
) error

SetDeleted writes the label's deletion stamp.

func (LabelRepository) SetRestored

func (r LabelRepository) SetRestored(
	ctx context.Context, label work.Label, expectedVersion int,
) error

SetRestored clears the label's deletion stamp.

type Leader

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

Leader is leadership as a PostgreSQL advisory lock held on one connection.

Why this holds a connection, and why that is not a hole in rule 3

Rule 3 says every query goes through the transaction wrapper, because that wrapper is the only place that sets the tenant context - a query that got to the database another way would run without a tenant and read what it must not. Nothing that happens here is such a query: the two statements are pg_try_advisory_lock and pg_advisory_unlock, they take a number, they touch no table, and no other code can reach this connection - it is unexported and this type offers no way to run anything on it.

It has to be a held connection because a session-level advisory lock belongs to the session that took it. A transaction-scoped lock would be released at the next commit, which would mean leadership for the length of one tick and a race for it after every one; a lease in a table would mean waiting out an expiry after a crash. A lock on a held connection is neither: when the process dies, the operating system closes the socket, PostgreSQL releases the lock, and the standby has it on its next tick (observability-reliability.md §9).

One connection of the role's pool is therefore occupied for as long as the process is the leader. That is the cost, and it is why a scheduler runs with more than one connection.

func NewLeader

func NewLeader(pool *pgxpool.Pool, key LockKey) *Leader

func (*Leader) Acquire

func (l *Leader) Acquire(ctx context.Context) (bool, error)

Acquire tries to become the leader. Being turned down is the normal state of a standby, not an error: it returns false and the caller tries again on its next tick.

func (*Leader) Confirm

func (l *Leader) Confirm(ctx context.Context) (bool, error)

Confirm reports whether leadership is still held.

It asks the connection rather than trusting the field, because the failure this guards against is invisible from inside the process: a cut network, a database that restarted, a connection killed by an administrator. The lock went with it, another instance has taken over, and a former leader that keeps working is the double execution the lock exists to prevent.

func (*Leader) Release

func (l *Leader) Release(ctx context.Context) error

Release gives leadership up, so that a standby can take over at once rather than after the connection has timed out. A graceful shutdown calls it; a crash does not need to.

type LegalHoldRepository

type LegalHoldRepository struct{}

LegalHoldRepository places and lifts the instructions that override every deletion.

Its own type rather than four more methods on LifecycleRepository, and for the same reason the ports are apart: the deletion paths take the reading half, and a repository that carried both would let a purge reach the statement that lifts the hold stopping it.

func NewLegalHoldRepository

func NewLegalHoldRepository() LegalHoldRepository

func (LegalHoldRepository) Find

Find answers one hold, released or not.

func (LegalHoldRepository) List

func (r LegalHoldRepository) List(
	ctx context.Context, includeReleased bool,
) ([]domain.LegalHold, error)

List answers the tenant's holds, newest first.

func (LegalHoldRepository) Lock

Lock takes the exclusive hold lock for the rest of the transaction.

func (LegalHoldRepository) Place

Place writes a legal hold.

func (LegalHoldRepository) Release

func (r LegalHoldRepository) Release(
	ctx context.Context, hold domain.LegalHold,
) (bool, error)

Release lifts one, and answers false for a hold that was already lifted.

func (LegalHoldRepository) TargetExists

func (r LegalHoldRepository) TargetExists(
	ctx context.Context, scope domain.HoldScope, id shared.ID,
) (bool, error)

TargetExists answers whether what a hold names is in this workspace.

func (LegalHoldRepository) WorkspaceLeaving

func (r LegalHoldRepository) WorkspaceLeaving(ctx context.Context) (bool, error)

WorkspaceLeaving answers whether the transaction's workspace is pending deletion.

type LifecycleRepository

type LifecycleRepository struct{}

LifecycleRepository is the end of data's life: the instructions not to delete, and the record of what was deleted anyway (ADR-0020).

Nothing here names a tenant. The transaction the caller opened decided that, and row level security applies it to every statement below - which matters more here than anywhere else, because a legal hold read across the wrong boundary would not be a wrong answer but somebody else's obligation ignored.

func NewLifecycleRepository

func NewLifecycleRepository() LifecycleRepository

func (LifecycleRepository) Active

Active returns the holds in force for this tenant.

func (LifecycleRepository) Containers

func (r LifecycleRepository) Containers(
	ctx context.Context, cutoff, due time.Time, skip, batchSize int,
) ([]repository.ExpiredContainer, error)

Containers returns the hubs and collections whose time in the trash is up, those removable by due first, collections first within each group.

func (LifecycleRepository) Contributors

func (r LifecycleRepository) Contributors(
	ctx context.Context, itemIDs []shared.ID,
) (map[shared.ID][]shared.ID, error)

Contributors answers, per entry, whose own data goes with it.

func (LifecycleRepository) Ensure

func (r LifecycleRepository) Ensure(ctx context.Context, policies []domain.Policy) error

Ensure writes the documented defaults for a tenant that has none.

func (LifecycleRepository) Find

Find returns the period in force for one kind.

func (LifecycleRepository) Finish

func (r LifecycleRepository) Finish(
	ctx context.Context, id shared.ID, result repository.RunResult,
) error

Finish closes the log entry with what the run did.

func (LifecycleRepository) Items

func (r LifecycleRepository) Items(
	ctx context.Context, cutoff, due time.Time, skip, batchSize int,
) ([]repository.ExpiredItem, error)

Items returns the entries whose time in the trash is up, those removable by due first, deepest first within each group.

func (LifecycleRepository) Record

func (r LifecycleRepository) Record(
	ctx context.Context, removals []domain.Removal, deletedAt, purgeAfter time.Time,
) error

Record writes a journal entry and a tombstone for each removal.

Two statements per table rather than one per row: a retention run removes in batches of a thousand, and a statement per row would make the transaction as long as the batch. The removals are grouped by table here rather than at every call site, so that a purge of a hub - containers and entries in one act - stays a single call.

func (LifecycleRepository) Start

func (r LifecycleRepository) Start(
	ctx context.Context, id shared.ID, kind domain.DataKind, startedAt time.Time,
) error

Start opens the log entry for one retention run.

type LockKey

type LockKey int64

LockKey identifies what leadership is being held over.

Advisory locks share one namespace per database, so the numbers are chosen here rather than derived from a name: two leaders whose names happened to hash to the same number would look like "the other one is always the leader", and nothing in a log would say why.

const SchedulerLock LockKey = 8_010_001

SchedulerLock is the lock the scheduler role competes for (ADR-0008). The value is arbitrary but fixed - changing it while a cluster is running would let an old pod and a new pod both believe they are the leader, which is the one state this mechanism exists to rule out.

type MailIntakeRepository

type MailIntakeRepository struct{}

MailIntakeRepository is a workspace's mail intake (ADR-0094): settings, receipts, and the lookups that route a message.

func NewMailIntakeRepository

func NewMailIntakeRepository() MailIntakeRepository

NewMailIntakeRepository builds the repository.

func (MailIntakeRepository) CollectionByHandle

func (MailIntakeRepository) CollectionByHandle(ctx context.Context, handle string) (shared.ID, error)

func (MailIntakeRepository) CountExpired

func (MailIntakeRepository) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

func (MailIntakeRepository) CountFrom

func (MailIntakeRepository) CountFrom(ctx context.Context, sender string, since time.Time) (int, error)

func (MailIntakeRepository) DeleteExpired

func (MailIntakeRepository) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

func (MailIntakeRepository) Find

func (MailIntakeRepository) ItemByKey

func (MailIntakeRepository) ItemByKey(ctx context.Context, key domain.Key) (shared.ID, error)

func (MailIntakeRepository) KeyOf

func (MailIntakeRepository) KeyOf(ctx context.Context, itemID shared.ID) (domain.Key, error)

func (MailIntakeRepository) Recent

func (MailIntakeRepository) Recent(ctx context.Context, limit int) ([]domain.Receipt, error)

func (MailIntakeRepository) Record

func (MailIntakeRepository) Record(ctx context.Context, receipt domain.Receipt) (bool, error)

func (MailIntakeRepository) Save

func (MailIntakeRepository) Save(
	ctx context.Context, settings domain.Settings, by shared.ID, at time.Time,
) error

func (MailIntakeRepository) Seen

func (MailIntakeRepository) Seen(ctx context.Context, messageID string) (bool, error)

func (MailIntakeRepository) SetHandle

func (MailIntakeRepository) SetHandle(ctx context.Context, collectionID shared.ID, handle string) error

func (MailIntakeRepository) WorkspaceNamed

func (MailIntakeRepository) WorkspaceNamed(ctx context.Context, name string) (shared.ID, error)

type MailboxAuthorizationRepository

type MailboxAuthorizationRepository struct{}

MailboxAuthorizationRepository is the OAuth connections waiting for a provider's answer.

func NewMailboxAuthorizationRepository

func NewMailboxAuthorizationRepository() MailboxAuthorizationRepository

NewMailboxAuthorizationRepository builds the repository.

func (MailboxAuthorizationRepository) DeleteExpired

func (MailboxAuthorizationRepository) DeleteExpired(ctx context.Context, now time.Time) (int, error)

func (MailboxAuthorizationRepository) Insert

func (MailboxAuthorizationRepository) Insert(ctx context.Context, pending domain.PendingAuthorization, stateHash []byte, verifier crypto.Sealed, clientSecret *crypto.Sealed) error

func (MailboxAuthorizationRepository) Take

type MailboxRepository

type MailboxRepository struct{}

MailboxRepository is a workspace's connected mailboxes (ADR-0094).

func NewMailboxRepository

func NewMailboxRepository() MailboxRepository

NewMailboxRepository builds the repository.

func (MailboxRepository) Count

func (MailboxRepository) Count(ctx context.Context) (int, error)

func (MailboxRepository) Delete

func (MailboxRepository) Delete(ctx context.Context, id shared.ID) error

func (MailboxRepository) Find

func (MailboxRepository) Insert

func (MailboxRepository) Insert(ctx context.Context, box domain.Mailbox, sealed crypto.Sealed, by shared.ID) error

func (MailboxRepository) List

func (MailboxRepository) RecordCheck

func (MailboxRepository) RecordCheck(ctx context.Context, box domain.Mailbox) error

func (MailboxRepository) Reseal

func (MailboxRepository) Reseal(ctx context.Context, id shared.ID, sealed crypto.Sealed) error

type MediaRepository

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

MediaRepository stores the media records and the attachment links.

func NewMediaRepository

func NewMediaRepository(cursors security.CursorCodec) MediaRepository

func (MediaRepository) Add

func (r MediaRepository) Add(
	ctx context.Context, itemID, mediaID shared.ID, tag shared.HLC,
) (bool, error)

Add links the object to the item and records the addition's tag.

func (MediaRepository) AdjustRefCount

func (r MediaRepository) AdjustRefCount(ctx context.Context, id shared.ID, delta int) error

AdjustRefCount moves the counter, floored at zero.

func (MediaRepository) Elements

func (r MediaRepository) Elements(
	ctx context.Context, itemID shared.ID,
) ([]work.SetElement, error)

Elements returns every tag of one item's attachment set.

func (MediaRepository) Find

Find returns the object, marked or not.

func (MediaRepository) Insert

func (r MediaRepository) Insert(ctx context.Context, object media.Object) error

Insert stages a new object.

func (MediaRepository) ListForItem

func (r MediaRepository) ListForItem(
	ctx context.Context, itemID shared.ID, page repository.Page,
) (mediarepo.ObjectPage, error)

ListForItem returns one page of an item's attachments as media objects.

func (MediaRepository) MarkDeleted

func (r MediaRepository) MarkDeleted(ctx context.Context, id shared.ID, at time.Time) (bool, error)

MarkDeleted marks an unreferenced, live object.

func (MediaRepository) MarkOrphans

func (r MediaRepository) MarkOrphans(
	ctx context.Context, now time.Time, before mediarepo.Thresholds,
) (int, error)

MarkOrphans marks what nothing references and has not for long enough.

func (MediaRepository) MediaIDs

func (r MediaRepository) MediaIDs(ctx context.Context, itemID shared.ID) ([]shared.ID, error)

MediaIDs returns the identifiers an item carries.

func (MediaRepository) Recount

func (r MediaRepository) Recount(ctx context.Context, now time.Time) error

Recount makes every live counter what the references say, and stamps what has reached zero.

func (MediaRepository) ReferencingItems

func (r MediaRepository) ReferencingItems(
	ctx context.Context, mediaID shared.ID,
) ([]mediarepo.ItemRef, error)

ReferencingItems returns the items the object serves.

func (MediaRepository) Remove

func (r MediaRepository) Remove(
	ctx context.Context, itemID, mediaID shared.ID, tag shared.HLC,
) (bool, error)

Remove unlinks and records the removal's tag.

func (MediaRepository) RemoveRows

func (r MediaRepository) RemoveRows(ctx context.Context, ids []shared.ID) (int, error)

RemoveRows deletes the records for good.

func (MediaRepository) Seal

func (r MediaRepository) Seal(ctx context.Context, object media.Object) error

Seal writes the confirmation.

func (MediaRepository) ServesAvatar

func (r MediaRepository) ServesAvatar(ctx context.Context, mediaID shared.ID) (bool, error)

ServesAvatar answers whether some account shows the object as its picture.

func (MediaRepository) TakeOrphans

func (r MediaRepository) TakeOrphans(
	ctx context.Context, markedBefore time.Time, batch int,
) ([]mediarepo.Orphan, error)

TakeOrphans returns marked rows whose grace ended.

type MembershipGrantRepository

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

MembershipGrantRepository is the write half of the membership table, and the one read that lists what was written at a scope.

func NewMembershipGrantRepository

func NewMembershipGrantRepository(cursors security.CursorCodec) MembershipGrantRepository

func (MembershipGrantRepository) Find

func (r MembershipGrantRepository) Find(ctx context.Context, membershipID shared.ID) (identity.Grant, error)

func (MembershipGrantRepository) Grant

func (MembershipGrantRepository) ListAt

func (MembershipGrantRepository) LockOwners

func (r MembershipGrantRepository) LockOwners(ctx context.Context) error

func (MembershipGrantRepository) OfGroup

func (r MembershipGrantRepository) OfGroup(ctx context.Context, groupID shared.ID) ([]identity.Grant, error)

func (MembershipGrantRepository) OwnerPaths

func (MembershipGrantRepository) Revoke

func (r MembershipGrantRepository) Revoke(ctx context.Context, membershipID shared.ID) (bool, error)

type MembershipRepository

type MembershipRepository struct{}

MembershipRepository reads what an account holds.

func NewMembershipRepository

func NewMembershipRepository() MembershipRepository

func (MembershipRepository) Administrators

func (r MembershipRepository) Administrators(
	ctx context.Context, path []identity.Scope,
) ([]shared.ID, error)

Administrators answers who administers anywhere on the path.

func (MembershipRepository) Along

func (r MembershipRepository) Along(
	ctx context.Context, accountID shared.ID, path []identity.Scope,
) ([]identity.Membership, error)

Along returns the memberships that could apply to the path, held directly or through a group.

The tenant is not a parameter: row level security bounds the query to the tenant of the running transaction, so a membership from another tenant cannot reach the resolution even if its identifier were guessed correctly (ADR-0010).

func (MembershipRepository) HeldAnywhere

func (r MembershipRepository) HeldAnywhere(ctx context.Context, accountID shared.ID) ([]identity.Role, error)

HeldAnywhere answers each role the account holds anywhere in the transaction's tenant, once.

func (MembershipRepository) HoldsAny

func (r MembershipRepository) HoldsAny(ctx context.Context, accountID shared.ID) (bool, error)

HoldsAny answers whether the account holds a role anywhere in the transaction's tenant.

func (MembershipRepository) HubsOf

func (r MembershipRepository) HubsOf(
	ctx context.Context, ids []shared.ID,
) (map[shared.ID]repository.Hub, error)

HubsOf answers the hub each named container or entry sits under.

The tenant is not a parameter: row level security bounds the query to the tenant of the running transaction, so another tenant's container answers nothing even when its identifier is known (ADR-0010).

func (MembershipRepository) HubsOpenedFor

func (r MembershipRepository) HubsOpenedFor(
	ctx context.Context, accountID shared.ID, hubIDs []shared.ID, now time.Time,
) ([]shared.ID, error)

HubsOpenedFor answers, of the named hubs, those the account holds a running emergency access to.

func (MembershipRepository) SharedItemsIn

func (r MembershipRepository) SharedItemsIn(
	ctx context.Context, accountID, collectionID shared.ID,
) ([]shared.ID, error)

SharedItemsIn returns the entries of one collection the account holds a membership on, directly or through a group.

The tenant is not a parameter: row level security bounds the query to the tenant of the running transaction, so an entry of another tenant cannot reach the answer even if its identifier were guessed correctly (ADR-0010).

type MfaRepository

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

MfaRepository maintains the second factor: the sealed enrolment, the recovery codes, the pending credential, and the tenant's enforcement switch. It is the only place that knows how a pending token or a recovery code becomes a hash, AccessTokenRepository's reasoning. No method takes a tenant - row level security bounds every statement (ADR-0010).

func NewMfaRepository

func NewMfaRepository(
	pendingHasher security.PendingTokenHasher,
	recoveryHasher security.RecoveryCodeHasher,
) MfaRepository

func (MfaRepository) Burn

func (r MfaRepository) Burn(
	ctx context.Context, accountID shared.ID, presented string, now time.Time,
) (bool, error)

func (MfaRepository) Confirm

func (r MfaRepository) Confirm(
	ctx context.Context, accountID shared.ID, step int64, now time.Time,
) (bool, error)

func (MfaRepository) Consume

func (r MfaRepository) Consume(
	ctx context.Context, credentialID shared.ID, at time.Time,
) (bool, error)

func (MfaRepository) Disable

func (r MfaRepository) Disable(ctx context.Context, accountID shared.ID) (bool, error)

func (MfaRepository) DiscardUnbacked

func (r MfaRepository) DiscardUnbacked(ctx context.Context, accountID shared.ID) error

func (MfaRepository) Find

func (r MfaRepository) Find(
	ctx context.Context, accountID shared.ID,
) (repository.MfaEnrollment, error)

func (MfaRepository) FindArrivalByToken

func (r MfaRepository) FindArrivalByToken(
	ctx context.Context, token identity.Token,
) (repository.PendingLookup, error)

FindArrivalByToken answers a TERMS credential that carries a provider's newcomer (migration 0142). Row level security keeps it to the token's workspace.

func (MfaRepository) FindByID

func (r MfaRepository) FindByID(
	ctx context.Context, credentialID shared.ID,
) (repository.PendingLookup, error)

FindByID answers the credential a provider flow remembered (ADR-0078 §1): the CONNECT link it started from. Row level security keeps it to the transaction's workspace, so another workspace's identifier answers ErrNotFound as an unknown one does.

func (MfaRepository) FindByToken

func (r MfaRepository) FindByToken(
	ctx context.Context, token identity.Token,
) (repository.PendingLookup, error)

func (MfaRepository) Insert

func (r MfaRepository) Insert(
	ctx context.Context, credential identity.PendingCredential, presented identity.Token,
) error

func (MfaRepository) RecordStep

func (r MfaRepository) RecordStep(
	ctx context.Context, accountID shared.ID, step int64, now time.Time,
) (bool, error)

func (MfaRepository) Remaining

func (r MfaRepository) Remaining(ctx context.Context, accountID shared.ID) (int, error)

func (MfaRepository) Replace

func (r MfaRepository) Replace(
	ctx context.Context, accountID shared.ID, ids []shared.ID, presented []string, now time.Time,
) error

func (MfaRepository) ReplacementsSealedNotUnder

func (r MfaRepository) ReplacementsSealedNotUnder(
	ctx context.Context, keyID string,
) ([]repository.MfaEnrollment, error)

ReplacementsSealedNotUnder answers the waiting replacements to re-seal.

func (MfaRepository) RequireAdminTotp

func (r MfaRepository) RequireAdminTotp(ctx context.Context) (bool, error)

RequireAdminTotp reads the enforcement switch off the tenant's settings document. The document is the adapter's shape: the application layer asks the question and never sees the JSON.

func (MfaRepository) Rewrap

func (r MfaRepository) Rewrap(
	ctx context.Context, accountID shared.ID, sealed cryptoport.Sealed, expectedKeyID string,
) (bool, error)

func (MfaRepository) RewrapReplacement

func (r MfaRepository) RewrapReplacement(
	ctx context.Context, accountID shared.ID, sealed cryptoport.Sealed, expectedKeyID string,
) (bool, error)

RewrapReplacement writes a replacement's moved wrapping, guarded like Rewrap.

func (MfaRepository) SealedNotUnder

func (r MfaRepository) SealedNotUnder(
	ctx context.Context, keyID string,
) ([]repository.MfaEnrollment, error)

func (MfaRepository) StartReplacement

func (r MfaRepository) StartReplacement(
	ctx context.Context, accountID shared.ID, sealed cryptoport.Sealed,
	sessionID shared.ID, expiresAt, now time.Time,
) (bool, error)

StartReplacement puts a new secret beside an armed one.

func (MfaRepository) Supersede

func (r MfaRepository) Supersede(
	ctx context.Context, accountID shared.ID, purpose identity.PendingPurpose, at time.Time,
) (int, error)

func (MfaRepository) SwapReplacement

func (r MfaRepository) SwapReplacement(
	ctx context.Context, accountID, sessionID shared.ID, expected cryptoport.Sealed,
	step int64, now time.Time,
) (bool, error)

SwapReplacement arms the replacement in one statement.

func (MfaRepository) Upsert

func (r MfaRepository) Upsert(
	ctx context.Context, accountID shared.ID, sealed cryptoport.Sealed, now time.Time,
) (bool, error)

type MilestoneRepository

type MilestoneRepository struct{}

MilestoneRepository records and reads when a collection or hub was last finished (db/queries/Milestones.sql).

func NewMilestoneRepository

func NewMilestoneRepository() MilestoneRepository

func (MilestoneRepository) LockHub

func (MilestoneRepository) LockHub(ctx context.Context, hubID shared.ID) error

LockHub takes the hub's milestone lock for the rest of the transaction.

func (MilestoneRepository) Of

func (MilestoneRepository) Of(ctx context.Context, containerIDs []shared.ID) (map[shared.ID]time.Time, error)

Of answers the milestones of the given containers.

func (MilestoneRepository) RecordCollection

func (MilestoneRepository) RecordCollection(ctx context.Context, collectionID shared.ID, at time.Time) (bool, error)

RecordCollection records the collection's milestone when nothing in it is open.

func (MilestoneRepository) RecordHub

func (MilestoneRepository) RecordHub(ctx context.Context, hubID shared.ID, at time.Time) (bool, error)

RecordHub records the hub's milestone when nothing in any of its collections is open.

type NaturalOrderingRepository

type NaturalOrderingRepository struct{}

NaturalOrderingRepository answers whether names sort under the ICU root collation.

Beside the two above and for the same reason: a read of what this installation *is*.

func NewNaturalOrderingRepository

func NewNaturalOrderingRepository() NaturalOrderingRepository

func (NaturalOrderingRepository) Available

func (r NaturalOrderingRepository) Available(ctx context.Context) (bool, error)

Available reports whether hubtask_name is an ICU collation.

type NotificationPreferenceRepository

type NotificationPreferenceRepository struct{}

NotificationPreferenceRepository stores what people have said about being told.

A type of its own rather than more methods on the one above, because both interfaces need a Find and a Save and one type cannot carry two of each. The split is honest anyway: a record and a preference are different tables with different lifetimes, and the delivery reads one of them without ever writing the other.

func NewNotificationPreferenceRepository

func NewNotificationPreferenceRepository() NotificationPreferenceRepository

func (NotificationPreferenceRepository) Find

func (r NotificationPreferenceRepository) Find(
	ctx context.Context, accountID shared.ID, category domain.Category, channel domain.Channel,
) (domain.Preference, error)

Find returns one preference, or ErrNotFound where the account has said nothing.

func (NotificationPreferenceRepository) ListForAccount

func (r NotificationPreferenceRepository) ListForAccount(
	ctx context.Context, accountID shared.ID,
) ([]domain.Preference, error)

func (NotificationPreferenceRepository) Save

Save writes a preference, replacing whatever the account said before.

type NotificationRepository

type NotificationRepository struct{}

NotificationRepository stores the notification records and the preferences.

func NewNotificationRepository

func NewNotificationRepository() NotificationRepository

func (NotificationRepository) CountExpired

func (r NotificationRepository) CountExpired(
	ctx context.Context, cutoff time.Time, ceiling int,
) (int, error)

CountExpired reports how many records are due, counted no higher than ceiling.

func (NotificationRepository) DeleteExpired

func (r NotificationRepository) DeleteExpired(
	ctx context.Context, cutoff time.Time, batch int,
) (int, error)

DeleteExpired removes one batch of records written before the cutoff.

func (NotificationRepository) Find

Find returns the record.

func (NotificationRepository) Insert

func (r NotificationRepository) Insert(
	ctx context.Context, record domain.Notification,
) (bool, error)

Insert writes a record, reporting whether this call was the one that wrote it.

func (NotificationRepository) Save

Save writes back what happened to a record.

type OauthClientRepository

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

func NewOauthClientRepository

func NewOauthClientRepository(secretHasher security.OauthClientSecretHasher) OauthClientRepository

func (OauthClientRepository) Delete

func (r OauthClientRepository) Delete(ctx context.Context, clientID shared.ID) (bool, error)

func (OauthClientRepository) Find

func (OauthClientRepository) Insert

func (r OauthClientRepository) Insert(
	ctx context.Context, client identity.OauthClient, presented identity.Token,
) error

func (OauthClientRepository) List

func (OauthClientRepository) SecretMatches

func (r OauthClientRepository) SecretMatches(
	ctx context.Context, clientID shared.ID, presented secret.Secret,
) (bool, error)

SecretMatches compares in constant time, in the one layer that holds the pepper. A public client answers false however right the guess: it has nothing to match.

type OauthCodeRepository

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

func NewOauthCodeRepository

func NewOauthCodeRepository(codeHasher security.OauthCodeHasher) OauthCodeRepository

func (OauthCodeRepository) Consume

func (r OauthCodeRepository) Consume(
	ctx context.Context, presented identity.Token, now time.Time,
) (identity.OauthCode, bool, error)

func (OauthCodeRepository) Insert

func (r OauthCodeRepository) Insert(
	ctx context.Context, code identity.OauthCode, presented identity.Token,
) error

type OauthGrantRepository

type OauthGrantRepository struct{}

func NewOauthGrantRepository

func NewOauthGrantRepository() OauthGrantRepository

func (OauthGrantRepository) Find

func (OauthGrantRepository) ListForAccount

func (r OauthGrantRepository) ListForAccount(
	ctx context.Context, accountID shared.ID,
) ([]repository.GrantListing, error)

func (OauthGrantRepository) Revoke

func (r OauthGrantRepository) Revoke(
	ctx context.Context, grantID, accountID shared.ID, at time.Time,
) (bool, error)

func (OauthGrantRepository) RevokeSessions

func (r OauthGrantRepository) RevokeSessions(
	ctx context.Context, grantID shared.ID, at time.Time,
) (int, error)

func (OauthGrantRepository) Upsert

type OfferedAiRepository

type OfferedAiRepository struct{}

OfferedAiRepository is the models the installation offers (ADR-0072 §1). No method takes a tenant: the read policy admits these rows everywhere and the write policy only in the installation's own scope, so which transaction a call runs in decides what it may do.

func NewOfferedAiRepository

func NewOfferedAiRepository() OfferedAiRepository

func (OfferedAiRepository) Delete

func (OfferedAiRepository) Delete(ctx context.Context, id shared.ID) (bool, error)

func (OfferedAiRepository) Find

func (OfferedAiRepository) FindWithKey

func (OfferedAiRepository) Insert

func (OfferedAiRepository) List

func (OfferedAiRepository) ListSealed

ListSealed answers every offered model's wrapped key, for the installation's re-seal.

func (OfferedAiRepository) RewrapKey

func (OfferedAiRepository) RewrapKey(
	ctx context.Context, providerID shared.ID, sealed crypto.Sealed, expectedKeyID string,
) (bool, error)

RewrapKey puts a moved key back where the row still names the key it was read under.

func (OfferedAiRepository) Update

func (OfferedAiRepository) UpdateKeepingKey

func (OfferedAiRepository) UpdateKeepingKey(
	ctx context.Context, offered integration.AiProvider, now time.Time,
) (integration.AiProvider, error)

type OidcFlowRepository

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

OidcFlowRepository is the only place that knows how a presented state becomes a hash, OauthCodeRepository's reasoning.

func NewOidcFlowRepository

func NewOidcFlowRepository(stateHasher security.OidcFlowHasher) OidcFlowRepository

func (OidcFlowRepository) Consume

func (r OidcFlowRepository) Consume(
	ctx context.Context, presented identity.Token, now time.Time,
) (identity.OidcFlow, bool, error)

func (OidcFlowRepository) ConsumeForConnect

func (r OidcFlowRepository) ConsumeForConnect(
	ctx context.Context, presented identity.Token, sessionID shared.ID, now time.Time,
) (identity.OidcFlow, bool, error)

ConsumeForConnect burns a connection's flow, and only one bound to this session (ADR-0078 §1).

func (OidcFlowRepository) ConsumeForStepUp

func (r OidcFlowRepository) ConsumeForStepUp(
	ctx context.Context, presented identity.Token, sessionID shared.ID, now time.Time,
) (identity.OidcFlow, bool, error)

ConsumeForStepUp burns a step-up's flow, and only one bound to this session (ADR-0075 §2).

func (OidcFlowRepository) Insert

func (r OidcFlowRepository) Insert(
	ctx context.Context, flow identity.OidcFlow, presented identity.Token,
) error

func (OidcFlowRepository) Live

func (r OidcFlowRepository) Live(ctx context.Context, presented identity.Token, now time.Time) (bool, error)

Live reads whether the state names a flow still live, any kind, and spends nothing.

type OperatorRepository

type OperatorRepository struct{}

OperatorRepository is the register of ADR-0070 §1, reached through its narrow functions.

Not through the table: `operator` carries no row-level policy and no grant to this role, for the reason migration 0101 gives. The functions are `resolve_tenant`'s discipline applied to a table - a boolean, a listing the control plane alone reaches, and two writes that keep the register's one invariant in the statement rather than in a read somebody could race.

func NewOperatorRepository

func NewOperatorRepository() OperatorRepository

func (OperatorRepository) Dismiss

func (OperatorRepository) Dismiss(
	ctx context.Context, accountID shared.ID,
) (repository.DismissOutcome, error)

Dismiss takes one out, unless it is the last.

func (OperatorRepository) Enlist

func (OperatorRepository) Enlist(
	ctx context.Context, accountID, by shared.ID,
) (repository.EnlistOutcome, error)

Enlist puts an active account in, and the adder too where the register was empty.

func (OperatorRepository) Holds

func (OperatorRepository) Holds(ctx context.Context, accountID shared.ID) (bool, error)

Holds is the check, asked when the scope is minted and when it is exercised.

func (OperatorRepository) RegisteredServiceAccount

func (OperatorRepository) RegisteredServiceAccount(ctx context.Context, accountID shared.ID) (bool, error)

RegisteredServiceAccount answers whether the register holds the account's own row as a service account's; the empty register's owner and a registered person are not.

func (OperatorRepository) Resolve

func (OperatorRepository) Resolve(ctx context.Context, slug, email string) (shared.ID, error)

Resolve answers the account an address names inside one workspace.

func (OperatorRepository) ResolveSingle

func (OperatorRepository) ResolveSingle(ctx context.Context, email string) (shared.ID, error)

ResolveSingle answers the account an address names in a single-mode installation's one workspace, and zero where there is not exactly one workspace or the address matches nothing (migration 0128).

func (OperatorRepository) Roster

Roster answers the register with names, and nothing to a caller the register does not name.

func (OperatorRepository) State

func (OperatorRepository) State(ctx context.Context) (workspaces, operators int64, err error)

State answers how many workspaces there are and how many operators, counted as `is_operator` counts (migration 0128).

type Outbox

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

Outbox is both halves of the table (ADR-0007): the write path leaves a row in the transaction of the change it describes, and the dispatcher takes it from here afterwards.

That split is the point of the pattern - the write path owes nothing but a row, so an unreachable webhook target cannot fail a user's request - but the two halves are one adapter because they are one table, and a claim that disagreed with the insert about what a row means would be a bug nobody could see from either side alone.

func NewOutbox

func NewOutbox(jobs queue.Queue) Outbox

func (Outbox) Append

func (o Outbox) Append(ctx context.Context, envelope event.Envelope) error

Append writes the event.

The tenant is not a parameter: it comes from current_tenant_id(), the same value row level security compares against, so an event cannot be written into another tenant's stream even deliberately.

func (Outbox) Claim

func (o Outbox) Claim(ctx context.Context, limit int) ([]event.Envelope, error)

Claim takes up to limit undelivered events of the running tenant and holds them for the length of the transaction.

The tenant is not a parameter here either: row level security decides which rows exist, so a dispatcher opened for tenant A cannot claim tenant B's events even by asking for them.

func (Outbox) CountPending

func (o Outbox) CountPending(ctx context.Context) (int, error)

CountPending is how the dispatcher decides between another round now and going back to sleep.

func (Outbox) FindEvent

func (o Outbox) FindEvent(ctx context.Context, eventID shared.ID) (event.Envelope, error)

envelopeFrom rebuilds an event from its row.

It builds the struct rather than going through event.NewEnvelope, and that is deliberate. The constructor refuses an event type it does not know, which is right on the way in - but on the way out it would mean that during a rolling update the old pod dead-letters the events the new pod writes (ADR-0003, expand/contract). What is in the table was validated when it was written; reading it back is not the place to have second thoughts. FindEvent reads one event as it was written. The webhook deliverer renders its body from this rather than from a copy in the job payload, so a retry two days later sends what the first attempt would have.

func (Outbox) MarkDispatched

func (o Outbox) MarkDispatched(ctx context.Context, ids []shared.ID, at time.Time) error

MarkDispatched records the delivery, in the same transaction as the delivery itself.

func (Outbox) Poll

func (o Outbox) Poll(
	ctx context.Context, eventType event.Type, after outbox.Position, horizon time.Time, limit int,
) ([]event.Envelope, error)

Poll answers one type's events after a position, for an external trigger.

No tenant parameter and no tenant predicate, like every other read here: row level security has already narrowed the table to the transaction's tenant, and a poller reading another workspace's stream is therefore not a thing this method could be asked to do.

The batch is bounded here as well as by the caller. The limit arrives from a query parameter, and a value the specification happens to allow today is not a value this adapter wants to learn from a request tomorrow.

type PasskeyChallengeRepository

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

PasskeyChallengeRepository keeps a passkey registration's challenge (migration 0161) where the pending credentials live, hashed with their pepper; its own statements bind it to its purpose, its session and its account.

func NewPasskeyChallengeRepository

func NewPasskeyChallengeRepository(pendingHasher security.PendingTokenHasher) PasskeyChallengeRepository

NewPasskeyChallengeRepository builds it with the pending credentials' hasher.

func (PasskeyChallengeRepository) Consume

func (r PasskeyChallengeRepository) Consume(
	ctx context.Context, presented identity.Token, sessionID, accountID shared.ID, now time.Time,
) (identity.PasskeyChallenge, bool, error)

func (PasskeyChallengeRepository) Issue

type PasskeyProofRepository

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

PasskeyProofRepository keeps the challenges a known account answers (migration 0176) where the pending credentials live, hashed with their pepper.

func NewPasskeyProofRepository

func NewPasskeyProofRepository(pendingHasher security.PendingTokenHasher) PasskeyProofRepository

NewPasskeyProofRepository builds it with the pending credentials' hasher.

func (PasskeyProofRepository) Issue

func (PasskeyProofRepository) Spend

func (r PasskeyProofRepository) Spend(
	ctx context.Context, purpose identity.PendingPurpose, presented identity.Token,
	accountID, sessionID shared.ID, now time.Time,
) (identity.PasskeyProofChallenge, bool, error)

type PasskeyRepository

type PasskeyRepository struct{}

PasskeyRepository keeps the WebAuthn credentials (identity.md §23, migration 0161).

func NewPasskeyRepository

func NewPasskeyRepository() PasskeyRepository

NewPasskeyRepository builds it.

func (PasskeyRepository) CountOf

func (PasskeyRepository) CountOf(ctx context.Context, accountID shared.ID) (int, error)

func (PasskeyRepository) Insert

func (PasskeyRepository) ListOf

func (PasskeyRepository) ListOf(
	ctx context.Context, accountID shared.ID,
) ([]identity.PasskeyCredential, error)

func (PasskeyRepository) LockAccount

func (PasskeyRepository) LockAccount(ctx context.Context, accountID shared.ID) error

func (PasskeyRepository) Remove

func (PasskeyRepository) Remove(ctx context.Context, accountID, id shared.ID) (bool, error)

func (PasskeyRepository) Rename

func (PasskeyRepository) Rename(
	ctx context.Context, accountID, id shared.ID, name string,
) (identity.PasskeyCredential, error)

type PasskeySignInRepository

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

PasskeySignInRepository keeps a passkey sign-in's challenge where the pending credentials live, hashed with their pepper, and reads and marks the credential a browser names (migration 0174).

func NewPasskeySignInRepository

func NewPasskeySignInRepository(pendingHasher security.PendingTokenHasher) PasskeySignInRepository

NewPasskeySignInRepository builds it with the pending credentials' hasher.

func (PasskeySignInRepository) HolderOf

func (PasskeySignInRepository) HolderOf(
	ctx context.Context, credentialID []byte,
) (repository.PasskeyHolder, error)

func (PasskeySignInRepository) IssueChallenge

func (r PasskeySignInRepository) IssueChallenge(
	ctx context.Context, challenge identity.PendingCredential, presented identity.Token,
) error

func (PasskeySignInRepository) MarkUsed

func (PasskeySignInRepository) MarkUsed(
	ctx context.Context, id shared.ID, signCount uint32, backedUp bool, at time.Time,
) (bool, error)

func (PasskeySignInRepository) SpendChallenge

func (r PasskeySignInRepository) SpendChallenge(
	ctx context.Context, presented identity.Token, now time.Time,
) (bool, error)

type PasswordRepository

type PasswordRepository struct{}

PasswordRepository is the store side of a password's life (ADR-0068 §5): the hash with the moment it was written, and the few hashes before it.

No method names a tenant: row level security has bound the transaction to one, and that is what makes another workspace's account invisible rather than forbidden (ADR-0010).

func NewPasswordRepository

func NewPasswordRepository() PasswordRepository

func (PasswordRepository) Append

func (PasswordRepository) Append(
	ctx context.Context, entryID, accountID shared.ID, hash string, at time.Time,
) error

Append records a hash that has just stopped being current.

func (PasswordRepository) FindByEmailForPassword

func (PasswordRepository) FindByEmailForPassword(
	ctx context.Context, email string,
) (repository.PasswordAccount, error)

FindByEmailForPassword answers the account an address names.

func (PasswordRepository) FindForPassword

func (PasswordRepository) FindForPassword(
	ctx context.Context, accountID shared.ID,
) (repository.PasswordAccount, error)

FindForPassword answers one account with its hash and the moment.

func (PasswordRepository) Recent

func (PasswordRepository) Recent(
	ctx context.Context, accountID shared.ID, count int,
) ([]secret.Secret, error)

Recent answers the newest hashes, newest first.

func (PasswordRepository) SetPassword

func (PasswordRepository) SetPassword(
	ctx context.Context, accountID shared.ID, hash string, at time.Time,
) (bool, error)

SetPassword writes the hash and the moment together.

func (PasswordRepository) Trim

func (PasswordRepository) Trim(ctx context.Context, accountID shared.ID, keep int) error

Trim drops everything past the newest `keep`.

type PausedAutomationRules

type PausedAutomationRules struct{}

PausedAutomationRules is the rules a workspace's deletion request switched off, for the cancel that switches them back on (multi-tenancy.md §5).

func NewPausedAutomationRules

func NewPausedAutomationRules() PausedAutomationRules

func (PausedAutomationRules) List

List answers the marked rules; the tenant is the transaction's.

func (PausedAutomationRules) Resume

func (PausedAutomationRules) Resume(
	ctx context.Context, id shared.ID, next, at time.Time,
) (bool, error)

Resume switches one marked rule back on with its next moment.

type PlanRepository

type PlanRepository struct{}

PlanRepository is the installation's plans (multi-tenancy.md §4.2).

A plan's rows are the installation level's own key shape, read and written through the level's one mapping (`applyRow`, `rowsOf`), so a key means the same at both levels and a key added to one is added to both. What only a plan has - the features it leaves out - is `entitlement.<feature>`.

func NewPlanRepository

func NewPlanRepository() PlanRepository

NewPlanRepository answers the repository.

func (PlanRepository) Ever

func (PlanRepository) Ever(ctx context.Context) (bool, error)

Ever reports whether any plan was ever created, removed ones included.

func (PlanRepository) Find

func (PlanRepository) Find(ctx context.Context, name string) (plan.Plan, error)

Find answers one plan not removed by its short name.

func (PlanRepository) Hold

func (PlanRepository) Hold(ctx context.Context, name string) (shared.ID, error)

Hold answers a plan's identifier and holds it against a removal until the transaction ends.

func (PlanRepository) Insert

func (PlanRepository) Insert(ctx context.Context, created plan.Plan, by shared.ID) error

Insert writes a new plan with its values.

func (PlanRepository) LeftOut

LeftOut answers what the transaction's own workspace's plan leaves out, empty where it is on none (admin.Entitlements).

func (PlanRepository) List

func (PlanRepository) List(ctx context.Context) ([]plan.Plan, error)

List answers every plan not removed, oldest first, with what each decides.

func (PlanRepository) OfWorkspace

func (PlanRepository) OfWorkspace(ctx context.Context) (plan.Plan, bool, error)

OfWorkspace answers the transaction's own workspace's plan, and false where it is on none.

func (PlanRepository) PlanAi

PlanAi answers the transaction's workspace's plan's AI layer, the zero layer on no plan.

func (PlanRepository) Remove

func (PlanRepository) Remove(
	ctx context.Context, planID shared.ID, by shared.ID, now time.Time,
) (repository.RemoveOutcome, error)

Remove ends a plan unless a workspace is on it.

func (PlanRepository) Replace

func (PlanRepository) Replace(
	ctx context.Context, changed plan.Plan, expected *int, by shared.ID, now time.Time,
) (bool, error)

Replace writes a plan's display name and values whole.

func (PlanRepository) Workspaces

func (PlanRepository) Workspaces(ctx context.Context) (map[string]int64, error)

Workspaces answers how many workspaces are on each plan.

type PlatformDeliveryRepository

type PlatformDeliveryRepository struct{}

PlatformDeliveryRepository keeps the deliveries of the installation's subscriptions (repository.PlatformDeliveries).

func NewPlatformDeliveryRepository

func NewPlatformDeliveryRepository() PlatformDeliveryRepository

NewPlatformDeliveryRepository builds the repository.

func (PlatformDeliveryRepository) DeadLetters

DeadLetters counts the installation's dead letters.

func (PlatformDeliveryRepository) Find

Find answers one delivery.

func (PlatformDeliveryRepository) Insert

Insert queues a delivery once.

func (PlatformDeliveryRepository) Page

func (PlatformDeliveryRepository) Page(
	ctx context.Context, subscriptionID shared.ID, status string, before shared.ID, size int,
) ([]integration.PlatformDelivery, error)

Page answers a subscription's deliveries, newest first.

func (PlatformDeliveryRepository) Record

Record writes one attempt's outcome.

func (PlatformDeliveryRepository) Requeue

func (PlatformDeliveryRepository) Requeue(ctx context.Context, subscriptionID shared.ID, now time.Time, batch int) ([]shared.ID, error)

Requeue returns a batch of dead letters to pending.

func (PlatformDeliveryRepository) Sweep

func (PlatformDeliveryRepository) Sweep(ctx context.Context, cutoff time.Time, batch int) (int, error)

Sweep removes a batch of settled deliveries.

type PlatformEventRepository

type PlatformEventRepository struct{}

PlatformEventRepository keeps the installation's events (repository.PlatformEvents).

func NewPlatformEventRepository

func NewPlatformEventRepository() PlatformEventRepository

NewPlatformEventRepository builds the repository.

func (PlatformEventRepository) After

After reads the events numbered after `after`.

func (PlatformEventRepository) Append

func (PlatformEventRepository) Append(ctx context.Context, envelope event.Envelope, dedupeKey string) (bool, error)

Append numbers the event under the installation's lock and writes it.

func (PlatformEventRepository) FindEvent

FindEvent answers one event by its id, for its delivery; ErrNotFound once it is swept.

func (PlatformEventRepository) ForgetReference

func (PlatformEventRepository) ForgetReference(ctx context.Context) error

ForgetReference removes the reference from the transaction's workspace's events.

func (PlatformEventRepository) Sweep

func (PlatformEventRepository) Sweep(ctx context.Context, cutoff time.Time, batch int) (int, error)

Sweep removes a batch of expired events.

type PlatformSubscriptionRepository

type PlatformSubscriptionRepository struct{}

PlatformSubscriptionRepository keeps the installation's subscriptions (repository.PlatformSubscriptions).

func NewPlatformSubscriptionRepository

func NewPlatformSubscriptionRepository() PlatformSubscriptionRepository

NewPlatformSubscriptionRepository builds the repository.

func (PlatformSubscriptionRepository) Active

Active answers the subscriptions an event is sent to.

func (PlatformSubscriptionRepository) Delete

Delete removes one, its deliveries with it.

func (PlatformSubscriptionRepository) Find

Find answers one subscription with its sealed secret.

func (PlatformSubscriptionRepository) Insert

Insert writes a new subscription.

func (PlatformSubscriptionRepository) List

List answers every subscription.

func (PlatformSubscriptionRepository) MarkFromFile

MarkFromFile records that an enforcing file decides the subscription.

func (PlatformSubscriptionRepository) Outcome

func (PlatformSubscriptionRepository) Outcome(ctx context.Context, id shared.ID, deliveredAt time.Time, lastError string) error

Outcome records the last attempt for the screen.

func (PlatformSubscriptionRepository) Rotate

Rotate replaces the sealed secret.

func (PlatformSubscriptionRepository) Update

func (PlatformSubscriptionRepository) Update(
	ctx context.Context, subscription integration.PlatformSubscription, expectedVersion int, now time.Time,
) (bool, error)

Update writes the target and the state, guarded on the version.

type PoolStats

type PoolStats struct {
	Total        int32
	Acquired     int32
	Idle         int32
	Max          int32
	AcquireCount int64
	// AcquireWait is the total time callers spent waiting for a free connection. Rising means
	// the pool is too small, or something is holding connections too long.
	AcquireWait time.Duration
}

PoolStats is what the health report and the metrics need, without handing either of them the pool itself.

func Stats

func Stats(pool *pgxpool.Pool) PoolStats

type PresetRepository

type PresetRepository struct{}

PresetRepository keeps the saved presets (db/queries/Preset.sql). Their settings are stored in the document a layer's settings key holds (StructureDocument.go), so a preset and a layer read and write one shape.

func NewPresetRepository

func NewPresetRepository() PresetRepository

func (PresetRepository) Count

func (PresetRepository) Count(ctx context.Context, origin work.PresetOrigin, ownerID shared.ID) (int, error)

Count answers how many presets of the origin there are.

func (PresetRepository) Delete

func (PresetRepository) Delete(ctx context.Context, id shared.ID, expectedVersion int) error

Delete removes the preset.

func (PresetRepository) Find

Find answers one saved preset. Another tenant's is not found (multi-tenancy.md §2).

func (PresetRepository) Insert

func (PresetRepository) Insert(ctx context.Context, preset work.Preset, at time.Time) error

Insert stores a new saved preset.

func (PresetRepository) ListOffered

func (PresetRepository) ListOffered(ctx context.Context, ownerID shared.ID) ([]work.Preset, error)

ListOffered answers the workspace's presets and the owner's own.

func (PresetRepository) Update

func (PresetRepository) Update(ctx context.Context, preset work.Preset, expectedVersion int) error

Update writes the preset's name and values.

type PrivacyRepository

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

Data subject rights, stored (data-protection.md §4).

Nothing here names a tenant: the transaction the caller opened decided that, and row level security applies it to every statement (ADR-0010). That holds for the installation-wide case as well - what crosses the boundary is a list of tenant identifiers, and the collection that follows opens one ordinary transaction per tenant.

func NewPrivacyRepository

func NewPrivacyRepository(cursors security.CursorCodec) PrivacyRepository

func (PrivacyRepository) Anonymise

func (r PrivacyRepository) Anonymise(
	ctx context.Context, accountID shared.ID, marker string, at time.Time,
) (bool, error)

Anonymise keeps the row and takes everything of the person's out of it.

func (PrivacyRepository) Assign

func (r PrivacyRepository) Assign(
	ctx context.Context, actorID shared.ID, pseudonym, reason string, at time.Time,
) error

Assign maps one actor to one pseudonym.

func (PrivacyRepository) AutomationsRunningAs

func (r PrivacyRepository) AutomationsRunningAs(
	ctx context.Context, accountID shared.ID,
) (int, error)

AutomationsRunningAs counts the rules that act as the person.

func (PrivacyRepository) BlockKept

func (r PrivacyRepository) BlockKept(
	ctx context.Context, requestID shared.ID, code string, params map[string]string,
) error

BlockKept records why the rest of a case could not be erased.

func (PrivacyRepository) CompleteIfOpen

func (r PrivacyRepository) CompleteIfOpen(
	ctx context.Context, id shared.ID, mode domain.ErasureMode, completedAt time.Time, subject shared.ID,
) (bool, error)

CompleteIfOpen completes a case at its first completion's moment while it is still open.

func (PrivacyRepository) Contributions

func (r PrivacyRepository) Contributions(
	ctx context.Context, accountID shared.ID,
) ([]repository.Contribution, error)

Contributions answers the person's rows on entries, and where each entry is.

func (PrivacyRepository) CountIntake

func (r PrivacyRepository) CountIntake(ctx context.Context, accountID shared.ID) (int, error)

CountIntake answers how much of the intake carries the person's address or names them.

func (PrivacyRepository) Deadlines

func (r PrivacyRepository) Deadlines(
	ctx context.Context, now time.Time,
) (repository.Deadlines, error)

Deadlines is the reading behind alert A-19.

func (PrivacyRepository) Delete

func (r PrivacyRepository) Delete(ctx context.Context, accountID shared.ID) (bool, error)

Delete removes the account and lets the cascades take the rest.

func (PrivacyRepository) DeleteComments

func (r PrivacyRepository) DeleteComments(
	ctx context.Context, accountID shared.ID, ids []shared.ID,
) (int, error)

DeleteComments removes the person's comments named.

func (PrivacyRepository) DiscardAiDrafts

func (r PrivacyRepository) DiscardAiDrafts(ctx context.Context, accountID shared.ID) (int, error)

DiscardAiDrafts removes what AI drafted for the person alone, and the words they asked with.

func (PrivacyRepository) DiscardIntake

func (r PrivacyRepository) DiscardIntake(ctx context.Context, accountID shared.ID) (int, error)

DiscardIntake removes what the person sent in by mail or captured into the jumble.

func (PrivacyRepository) DiscardKudos

func (r PrivacyRepository) DiscardKudos(ctx context.Context, accountID shared.ID) (int, error)

DiscardKudos removes the kudos the person gave and received.

func (PrivacyRepository) DiscardMedium

func (r PrivacyRepository) DiscardMedium(ctx context.Context, mediaID shared.ID) error

DiscardMedium removes one medium's row.

func (PrivacyRepository) DiscardNotifications

func (r PrivacyRepository) DiscardNotifications(
	ctx context.Context, accountID shared.ID,
) (int, error)

DiscardNotifications removes what was sent to them and what was about to be.

func (PrivacyRepository) Extend

func (r PrivacyRepository) Extend(
	ctx context.Context, request domain.Request, now time.Time,
) (bool, error)

Extend writes an extension, guarded on the conditions the domain checked.

func (PrivacyRepository) Find

Find answers one case.

func (PrivacyRepository) For

func (r PrivacyRepository) For(
	ctx context.Context, actorIDs []shared.ID,
) (map[shared.ID]string, error)

For answers the pseudonyms of the actors named.

func (PrivacyRepository) Insert

func (r PrivacyRepository) Insert(ctx context.Context, request domain.Request) error

Insert records a new case.

func (PrivacyRepository) InsertAsItWas

func (r PrivacyRepository) InsertAsItWas(ctx context.Context, request domain.Request) (bool, error)

InsertAsItWas writes an erasure case a point-in-time recovery rewound away, open, as it stood at the end of the archive.

func (PrivacyRepository) KeepsAccount

func (r PrivacyRepository) KeepsAccount(ctx context.Context, accountID shared.ID) (bool, error)

KeepsAccount reports whether a case still keeps this account because of a hold.

func (PrivacyRepository) KeptOf

func (r PrivacyRepository) KeptOf(
	ctx context.Context, requestIDs []shared.ID,
) (map[shared.ID][]domain.Kept, error)

KeptOf answers the parts of each case named.

func (PrivacyRepository) KudosOf

func (r PrivacyRepository) KudosOf(ctx context.Context, accountID shared.ID, yield func(work.Kudos) error) error

KudosOf hands the kudos the person gave and received to yield, a page at a time.

func (PrivacyRepository) Latest

func (r PrivacyRepository) Latest(
	ctx context.Context, accountID shared.ID, purpose string,
) (domain.Consent, error)

Latest answers the most recent record for an account and a purpose.

func (PrivacyRepository) List

List answers one page of the cases, soonest deadline first.

func (PrivacyRepository) Lock

func (r PrivacyRepository) Lock(ctx context.Context, id shared.ID) (bool, error)

Lock takes the case's row for the rest of the transaction.

func (PrivacyRepository) LockStatus

LockStatus answers an account's status with its row locked. Another workspace's account is hidden by row level security and answers as not found.

func (PrivacyRepository) Notice

func (r PrivacyRepository) Notice(ctx context.Context, accountID shared.ID, at time.Time) error

Notice records an acknowledgement.

func (PrivacyRepository) NoticedAt

func (r PrivacyRepository) NoticedAt(ctx context.Context, accountID shared.ID) (time.Time, error)

NoticedAt answers when a person last acknowledged that their objection stopped taking effect.

func (PrivacyRepository) OrphanedMedia

func (r PrivacyRepository) OrphanedMedia(
	ctx context.Context, accountID shared.ID,
) ([]repository.Medium, error)

OrphanedMedia answers the uploads nothing points at any more.

func (PrivacyRepository) PendingKept

func (r PrivacyRepository) PendingKept(
	ctx context.Context, holdID shared.ID,
) ([]repository.PendingKept, error)

PendingKept answers the cases something is still kept for.

func (PrivacyRepository) Record

func (r PrivacyRepository) Record(ctx context.Context, consent domain.Consent) error

Record writes one consent record.

func (PrivacyRepository) RecordKept

func (r PrivacyRepository) RecordKept(
	ctx context.Context, requestID shared.ID, kept []domain.Kept, at time.Time,
) error

RecordKept makes the case's pending parts exactly these.

func (PrivacyRepository) ReleaseAssignmentsOn

func (r PrivacyRepository) ReleaseAssignmentsOn(
	ctx context.Context, accountID shared.ID, itemIDs []shared.ID, at time.Time,
) (int, error)

ReleaseAssignmentsOn hands the entries named back to nobody.

func (PrivacyRepository) ReleaseIntake

func (r PrivacyRepository) ReleaseIntake(ctx context.Context, accountID shared.ID) (int, error)

ReleaseIntake keeps the text and takes the person's address and account off it.

func (PrivacyRepository) RestrictedPeople

func (r PrivacyRepository) RestrictedPeople(ctx context.Context) ([]domain.RestrictedPerson, error)

RestrictedPeople answers whose processing is restricted.

func (PrivacyRepository) RevokeCredentials

func (r PrivacyRepository) RevokeCredentials(
	ctx context.Context, accountID shared.ID, at time.Time,
) (int, error)

RevokeCredentials removes every token, feed, device and passkey of the person, and ends their open invitation.

func (PrivacyRepository) Save

func (r PrivacyRepository) Save(ctx context.Context, request domain.Request) (bool, error)

Save writes a case back after the domain has moved it.

func (PrivacyRepository) SetStatus

func (r PrivacyRepository) SetStatus(
	ctx context.Context, id shared.ID, status string, at time.Time,
) (bool, error)

SetStatus writes an account's status.

func (PrivacyRepository) Standing

func (r PrivacyRepository) Standing(ctx context.Context, purpose string) ([]repository.Objector, error)

Standing answers every person whose newest record for a purpose is a withdrawal.

func (PrivacyRepository) SubjectExists

func (r PrivacyRepository) SubjectExists(ctx context.Context, accountID shared.ID) (bool, error)

SubjectExists answers whether the workspace has the account.

func (PrivacyRepository) Tenants

func (r PrivacyRepository) Tenants(ctx context.Context, email string) ([]shared.ID, error)

Tenants answers the workspaces in which one address is a member.

The one cross-tenant read in this adapter, and it is a function call rather than a query: it answers identifiers and nothing else, and `SET LOCAL app.tenant_id` is not relaxed for it (db/migrations/0044_privacy_requests.sql).

func (PrivacyRepository) Withdraw

func (r PrivacyRepository) Withdraw(
	ctx context.Context, accountID shared.ID, purpose string, at time.Time,
) (int, error)

Withdraw ends every standing consent of an account for a purpose.

type PrivateHubRepository

type PrivateHubRepository struct{}

PrivateHubRepository answers what an administrator may know of private hubs, and how many people the workspace has.

func NewPrivateHubRepository

func NewPrivateHubRepository() PrivateHubRepository

func (PrivateHubRepository) CountPeople

func (r PrivateHubRepository) CountPeople(ctx context.Context) (int, error)

CountPeople counts the persons of the transaction's tenant that can still act.

func (PrivateHubRepository) HeldBy

func (r PrivateHubRepository) HeldBy(ctx context.Context, accountID shared.ID) ([]shared.ID, error)

HeldBy answers the private hubs the account is a member of.

func (PrivateHubRepository) ListPrivateHubs

ListPrivateHubs lists the private hubs of the transaction's tenant; row level security bounds it (ADR-0010).

func (PrivateHubRepository) Lock

func (r PrivateHubRepository) Lock(ctx context.Context, hubIDs []shared.ID) error

Lock takes the hubs' rows for the rest of the transaction.

func (PrivateHubRepository) Orphaned

func (r PrivateHubRepository) Orphaned(ctx context.Context, named []shared.ID) ([]shared.ID, error)

Orphaned answers the private hubs without a member among those the named rows sit under; nil names every private hub of the transaction's tenant.

func (PrivateHubRepository) WorkspaceOwners

func (r PrivateHubRepository) WorkspaceOwners(ctx context.Context) ([]shared.ID, error)

WorkspaceOwners answers the persons holding OWNER on the workspace itself.

type Probe

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

Probe reports whether PostgreSQL can serve queries. It is the only mandatory dependency (ADR-0003): when it is down the process is not ready, and no amount of degradation helps.

What it does not do is check anything else. /healthz never touches a dependency at all - otherwise a database outage takes down every pod at once instead of leaving them running and unready (engineering-guidelines.md §5).

func NewProbe

func NewProbe(pool *pgxpool.Pool) *Probe

func (*Probe) Check

func (p *Probe) Check(ctx context.Context) health.Result

func (*Probe) Name

func (p *Probe) Name() string

func (*Probe) Required

func (p *Probe) Required() bool

type Queue

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

Queue is the job table behind the queue port (ADR-0008).

It is the one repository that reads across tenants, because it has to: a worker claims the next job before it can know whose it is. That is why the table carries no row level security and why every statement here states its own conditions - see db/queries/Job.sql. The tenant boundary is not weakened by it: the job row names a tenant, and the transaction that runs the job is opened under that tenant, so the work itself is as bounded as any request.

func NewQueue

func NewQueue(ids clock.IDGenerator, now clock.Clock) Queue

func (Queue) Claim

func (q Queue) Claim(ctx context.Context, lease queue.Lease) ([]queue.Job, error)

Claim takes the next batch and holds it until the lease expires.

func (Queue) Complete

func (q Queue) Complete(ctx context.Context, job queue.Job, outcome string) error

Complete finishes a job for good, in the transaction that carries its effect.

func (Queue) Depth

func (q Queue) Depth(ctx context.Context) ([]queue.Depth, error)

Depth reports the backlog per kind across every tenant.

func (Queue) Enqueue

func (q Queue) Enqueue(ctx context.Context, request queue.Request) (shared.ID, error)

Enqueue adds a job, or leaves the one that is already scheduled alone.

func (Queue) Fail

func (q Queue) Fail(ctx context.Context, failure queue.Failure) error

Fail records an attempt that did not work: back to the queue, to the dead letter, or - for a refusal the job met - finished as FAILED with the refusal's parameters.

It runs in a transaction of its own, because the handler's has just been rolled back - which is also why the failure has to be written from a code path that shares nothing with the failure itself.

func (Queue) Hold

func (q Queue) Hold(ctx context.Context, job queue.Job) error

Hold takes the row lock on the job for the rest of the caller's transaction (queue.Queue.Hold).

A missing row is the fence answering: the lease expired and somebody else has the job, so this pass has nothing to hold and nothing to write - the same answer Complete gives, so the caller rolls back rather than working on.

func (Queue) PurgeFinished

func (q Queue) PurgeFinished(ctx context.Context, before time.Time, batch int) (map[string]int, error)

PurgeFinished deletes up to batch finished jobs that ended before the moment, oldest first, and answers how many went in each state.

func (Queue) Repeat

func (q Queue) Repeat(ctx context.Context, job queue.Job, runAt time.Time) error

Repeat sends a poller round back to the queue for its next one.

func (Queue) Report

func (q Queue) Report(ctx context.Context, job queue.Job, fraction float64) error

Report writes how far along a long job is.

Fenced on the lease like every statement a handler runs, and silent when the fence does not hold: a worker that lost its job writes nothing, and that is not a failure to hand back. The number is for whoever is watching, and the clamp is in the statement rather than here because a fraction outside [0,1] on a progress bar is a rendering bug in every client at once.

func (Queue) ScheduledBacklog

func (q Queue) ScheduledBacklog(ctx context.Context, kind queue.Kind) (int, error)

ScheduledBacklog counts the jobs of one kind waiting for a future moment - the retry ladders parked on the queue, which is what hubtask_webhook_retry_backlog reads (observability-reliability.md §4).

type QuotaReachedRepository

type QuotaReachedRepository struct{}

QuotaReachedRepository keeps the marks of limits reached (repository.Reached).

func NewQuotaReachedRepository

func NewQuotaReachedRepository() QuotaReachedRepository

NewQuotaReachedRepository builds the repository.

func (QuotaReachedRepository) Clear

Clear removes the quota's mark, reading first so that a create finding none writes nothing.

func (QuotaReachedRepository) Mark

func (QuotaReachedRepository) Mark(ctx context.Context, quota, period string, at time.Time) (bool, error)

Mark marks the quota at its period, forgetting its other periods.

type QuotaRepository

type QuotaRepository struct{}

QuotaRepository is the surface of the limits (multi-tenancy.md §4): the overrides in the tenant's settings document, the live counts, and the billing ledger. One type for the three ports - they are three angles on one subject, and every method is bounded by the transaction it runs in.

func NewQuotaRepository

func NewQuotaRepository() QuotaRepository

func (QuotaRepository) Accounts

func (QuotaRepository) Accounts(ctx context.Context) (int64, error)

Accounts counts the people the workspace holds (multi-tenancy.md §4.2).

func (QuotaRepository) Add

func (QuotaRepository) Add(ctx context.Context, metric string, at time.Time, amount int64) error

Add increases one metric's daily tally in the billing ledger.

func (QuotaRepository) AutomationRunsSince

func (QuotaRepository) AutomationRunsSince(ctx context.Context, since time.Time) (int64, error)

AutomationRunsSince counts the runs that actually ran since the instant.

func (QuotaRepository) InstanceLocks

func (QuotaRepository) InstanceLocks(ctx context.Context) (map[string]bool, error)

InstanceLocks answers the quotas the installation locked, from the same rows' lock origin.

func (QuotaRepository) InstanceOverrides

func (QuotaRepository) InstanceOverrides(ctx context.Context) (repository.Overrides, error)

InstanceOverrides answers what the installation set as its defaults.

One row per quota rather than one document, because `instance_setting` is a key/value table with a lock per key and that is what the whole instance level is: the operator may lock a ceiling, and a document would have nowhere to put the lock.

A malformed row is skipped rather than failing the read, `Overrides`' reasoning exactly: a value this build cannot parse is the operator's to repair, and refusing every request until then would be the worse outcome.

func (QuotaRepository) InvitationOpen

func (QuotaRepository) InvitationOpen(ctx context.Context, account string) (bool, error)

InvitationOpen answers whether the people count holds the invitation.

func (QuotaRepository) Items

func (QuotaRepository) Items(ctx context.Context) (int64, error)

Items counts the workspace's work items, trash included.

func (QuotaRepository) LiveExports

func (QuotaRepository) LiveExports(ctx context.Context) (int64, error)

LiveExports counts the pending and running export jobs.

func (QuotaRepository) LockPeople

func (QuotaRepository) LockPeople(ctx context.Context) error

LockPeople serialises the creations of people in the transaction's workspace until it ends.

func (QuotaRepository) MediaBytes

func (QuotaRepository) MediaBytes(ctx context.Context) (int64, error)

MediaBytes sums the stored objects' sizes.

func (QuotaRepository) MeteredSince

func (QuotaRepository) MeteredSince(
	ctx context.Context, metric string, since time.Time,
) (int64, error)

MeteredSince sums one metered thing since the instant.

From the billing ledger, which every other count here refuses to read - the port says why: what it sums has no row of its own, so the tally is not a lagging copy of something countable but the only record there is. Generic in the metric, so the vocabulary stays the application layer's.

func (QuotaRepository) Overrides

Overrides answers what the transaction's tenant configured.

func (QuotaRepository) PlanLimits

PlanLimits answers the transaction's workspace's plan's ceilings and locks, through the plan read the sign-in rule uses. The plan's map is reassembled by the same document as the installation's rows, so a name this build does not know is dropped in both.

func (QuotaRepository) SetOverrides

func (QuotaRepository) SetOverrides(
	ctx context.Context, overrides repository.Overrides, expectedVersion int, now time.Time,
) (bool, error)

SetOverrides replaces the quotas key, guarded on the row version.

func (QuotaRepository) SetOwnAiBudget

func (QuotaRepository) SetOwnAiBudget(ctx context.Context, tokens *int64, now time.Time) error

SetOwnAiBudget writes the workspace's own AI budget beside the quotas key; nil removes it.

func (QuotaRepository) TimeZone

func (QuotaRepository) TimeZone(ctx context.Context) (string, error)

TimeZone answers the workspace's zone, the one its AI budget's day ends in.

func (QuotaRepository) WebhookTargets

func (QuotaRepository) WebhookTargets(ctx context.Context) (int64, error)

WebhookTargets counts the subscriptions.

type RecoveredErasure

type RecoveredErasure struct {
	Case domain.Request
	// Unresolved says why the case's subject could not be named: a full deletion whose case no
	// longer names the account, and for which the trail and the journal name none or more than one.
	// Empty when the subject is named, or when the case was about an address nobody held.
	Unresolved string
}

RecoveredErasure is one erasure case as the temporary cluster holds it, and whom it was about.

func ReadRecoveredErasures

func ReadRecoveredErasures(
	ctx context.Context, dsn string, tenantID shared.ID, recoveryPoint time.Time, timeout time.Duration,
) ([]RecoveredErasure, error)

ReadRecoveredErasures answers one workspace's erasure cases completed after the recovery point, as another database holds them: the temporary cluster the operator recovered to the end of the WAL archive (backup-restore.md §8.5 step 5). Read through the transaction wrapper under the workspace's scope, with the workspace named in the statement too (ReadRecoveredHolds' reason).

type RecurrenceRepository

type RecurrenceRepository struct{}

RecurrenceRepository stores the series beside the entries.

Two rows move together here, which is why the pointer's write lives in this adapter rather than in the application: a rule and the entry that points at it are one state, and a caller that had to remember both would eventually not.

func NewRecurrenceRepository

func NewRecurrenceRepository() RecurrenceRepository

func (RecurrenceRepository) Advance

func (r RecurrenceRepository) Advance(
	ctx context.Context, rule work.RecurrenceRule, at time.Time,
) (bool, error)

Advance moves the watermark under the compare-and-set, and reports whether this caller moved it.

The rule carries the watermark it was read with, which is what the statement compares against: the caller passes the rule it decided from rather than a value it might have derived twice.

func (RecurrenceRepository) AttachOccurrence

func (r RecurrenceRepository) AttachOccurrence(
	ctx context.Context, occurrenceID, ruleID, sourceID shared.ID,
) error

Attach points an entry at a series - the materialisation's one write outside the rule itself.

func (RecurrenceRepository) ClaimToMaterialize

func (r RecurrenceRepository) ClaimToMaterialize(
	ctx context.Context, now time.Time, limit int,
) ([]work.RecurrenceRule, error)

ClaimToMaterialize takes the series whose window may owe something, locking the rows.

func (RecurrenceRepository) Delete

func (r RecurrenceRepository) Delete(
	ctx context.Context, rule work.RecurrenceRule, expectedVersion int,
) error

Delete removes the series and clears the entry's pointer.

func (RecurrenceRepository) FindForItem

func (r RecurrenceRepository) FindForItem(
	ctx context.Context, itemID shared.ID,
) (work.RecurrenceRule, error)

FindForItem returns the entry's series.

func (RecurrenceRepository) Insert

Insert writes the series and points the entry at it.

func (RecurrenceRepository) LatestCompletion

func (r RecurrenceRepository) LatestCompletion(
	ctx context.Context, ruleID shared.ID,
) (*time.Time, error)

LatestCompletion answers when the series was last completed.

func (RecurrenceRepository) OpenOccurrences

func (r RecurrenceRepository) OpenOccurrences(
	ctx context.Context, ruleID shared.ID,
) (int, error)

OpenOccurrences counts what a series still has open.

func (RecurrenceRepository) Update

func (r RecurrenceRepository) Update(
	ctx context.Context, rule work.RecurrenceRule, expectedVersion int,
) error

Update writes the whole document under the optimistic lock.

type RefreshTokenRepository

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

RefreshTokenRepository maintains the rotating chains. A type of its own because both ports insert and find, and one receiver cannot answer two spellings of the same verb.

func (RefreshTokenRepository) FindByToken

func (RefreshTokenRepository) Insert

func (r RefreshTokenRepository) Insert(
	ctx context.Context, token identity.RefreshToken, presented identity.Token,
) error

Insert writes a freshly minted link of a session's chain. The hash is computed here and nowhere else.

func (RefreshTokenRepository) Rotate

func (r RefreshTokenRepository) Rotate(ctx context.Context, tokenID shared.ID, at time.Time) (bool, error)

type ReminderRepository

type ReminderRepository struct{}

ReminderRepository stores the reminders beside the entries.

No cursor codec, unlike the comment store: what one entry may carry is bounded where reminders are written, so the list is one answer rather than a page.

func NewReminderRepository

func NewReminderRepository() ReminderRepository

func (ReminderRepository) ClaimDue

func (r ReminderRepository) ClaimDue(
	ctx context.Context, now time.Time, limit int,
) ([]work.Reminder, error)

ClaimDue takes what is due, locking the rows for the caller's transaction.

func (ReminderRepository) CountForItem

func (r ReminderRepository) CountForItem(ctx context.Context, itemID shared.ID) (int, error)

CountForItem returns how many reminders the entry carries.

func (ReminderRepository) Delete

func (r ReminderRepository) Delete(
	ctx context.Context, id shared.ID, expectedVersion int,
) error

Delete removes the row under the optimistic lock.

func (ReminderRepository) Find

Find returns the reminder.

func (ReminderRepository) Insert

func (r ReminderRepository) Insert(ctx context.Context, reminder work.Reminder) error

Insert writes a new reminder.

func (ReminderRepository) ListForItem

func (r ReminderRepository) ListForItem(
	ctx context.Context, itemID shared.ID,
) ([]work.Reminder, error)

ListForItem returns one entry's reminders, oldest first.

func (ReminderRepository) ListPendingForItem

func (r ReminderRepository) ListPendingForItem(
	ctx context.Context, itemID shared.ID,
) ([]work.Reminder, error)

ListPendingForItem returns the entry's reminders that are still waiting.

func (ReminderRepository) NextMoment

func (r ReminderRepository) NextMoment(ctx context.Context) (*time.Time, error)

NextMoment answers when the tenant next owes a reminder.

func (ReminderRepository) Reschedule

func (r ReminderRepository) Reschedule(ctx context.Context, reminder work.Reminder) error

Reschedule writes a recomputed moment alone.

No version and no conflict: the row was not edited, and a reminder that has fired in the meantime is simply not matched - which is the right outcome rather than an error, because a due date moving is not an instruction about a reminder that already went out.

func (ReminderRepository) Settle

func (r ReminderRepository) Settle(
	ctx context.Context, id shared.ID, state work.ReminderState,
) (bool, error)

Settle writes the guarded transition and reports whether this caller made it.

func (ReminderRepository) Update

func (r ReminderRepository) Update(
	ctx context.Context, reminder work.Reminder, expectedVersion int,
) error

Update writes the edited fields under the optimistic lock. A reminder that is no longer pending is never matched, so an edit racing a firing comes back as a version conflict - the same answer as any other row that moved on, which is all the caller can act on either way.

type RestoreRunRepository

type RestoreRunRepository struct{}

RestoreRunRepository stores what a restore did.

func NewRestoreRunRepository

func NewRestoreRunRepository() RestoreRunRepository

func (RestoreRunRepository) Claim

func (r RestoreRunRepository) Claim(ctx context.Context, id shared.ID, at time.Time) (bool, error)

Claim moves the run to RUNNING and answers whether it got the tenant.

func (RestoreRunRepository) Find

Find answers one restore.

func (RestoreRunRepository) Finish

Finish records how a restore ended.

func (RestoreRunRepository) InProgress

func (r RestoreRunRepository) InProgress(ctx context.Context) (bool, error)

InProgress reports whether this tenant already has a restore going.

func (RestoreRunRepository) Insert

func (r RestoreRunRepository) Insert(ctx context.Context, restore domain.Restore) error

Insert writes the accepted restore.

The tenant of the row is `current_tenant_id()` - the tenant that asked - and the tenant being restored *into* is a column of its own. They differ only for NEW_TENANT, whose target did not exist when the restore was asked for, and filing the row under that one would make it invisible to the person holding the `result_url` (migration 0034).

func (RestoreRunRepository) RecordProgress

func (r RestoreRunRepository) RecordProgress(
	ctx context.Context, id shared.ID, report domain.Report, progress map[string]int,
) error

RecordProgress writes how far the run has got, and the report so far.

func (RestoreRunRepository) RecordSafetyCopy

func (r RestoreRunRepository) RecordSafetyCopy(ctx context.Context, id, backupRunID shared.ID) error

RecordSafetyCopy writes down the backup taken before a destructive mode.

type RetentionCursorRepository

type RetentionCursorRepository struct{}

RetentionCursorRepository keeps where each read of the rule sweep stopped (data-retention.md §5), under the tenant's row security like every tenant table.

func NewRetentionCursorRepository

func NewRetentionCursorRepository() RetentionCursorRepository

func (RetentionCursorRepository) Clear

Clear forgets the reader's place.

func (RetentionCursorRepository) Find

Find answers the reader's place, and false when it has none.

func (RetentionCursorRepository) Set

Set records the reader's place, replacing the one it had.

type RetentionMarkingRepository

type RetentionMarkingRepository struct{}

RetentionMarkingRepository is the two phases of data-retention.md §5 against the entries.

One statement per anchor rather than one with the column as a parameter. The anchor is a value of the domain's closed set and never a byte of a request, which is rule 9 applied to the one place where it is tempting to bend: a period runs from a column, and a column name assembled from anything a caller sent would be the one string concatenation the rule exists to forbid.

func NewRetentionMarkingRepository

func NewRetentionMarkingRepository() RetentionMarkingRepository

func (RetentionMarkingRepository) Archive

func (r RetentionMarkingRepository) Archive(
	ctx context.Context, ids []shared.ID, at time.Time,
) (int, error)

Archive is the act of an ARCHIVE stage.

func (RetentionMarkingRepository) Block

func (r RetentionMarkingRepository) Block(
	ctx context.Context, ids []shared.ID, ruleID shared.ID,
	action domain.Action, reason string,
) (int, error)

Block records what a rule would do and what is stopping it.

func (RetentionMarkingRepository) Clear

func (r RetentionMarkingRepository) Clear(
	ctx context.Context, ids []shared.ID, keepRule bool, now time.Time,
) (int, error)

Clear takes entries out of the running period.

func (RetentionMarkingRepository) CountDue

func (r RetentionMarkingRepository) CountDue(
	ctx context.Context, anchor domain.Anchor, scope domain.Scope, cutoff time.Time,
) (int, error)

CountDue is how many entries in a rule's scope are past its cutoff.

func (RetentionMarkingRepository) CountScope

func (r RetentionMarkingRepository) CountScope(
	ctx context.Context, scope domain.Scope,
) (int, error)

CountScope is the denominator of the five-per-cent switch.

func (RetentionMarkingRepository) Due

func (r RetentionMarkingRepository) Due(
	ctx context.Context, anchor domain.Anchor, cutoff time.Time, after repository.Position, batch int,
) ([]repository.Candidate, error)

Due answers entries whose anchor lies before the cutoff and which are not marked yet.

func (RetentionMarkingRepository) DueInChain

func (r RetentionMarkingRepository) DueInChain(
	ctx context.Context, anchor domain.Anchor, ruleID shared.ID, cutoff time.Time,
	after repository.Position, batch int,
) ([]repository.Candidate, error)

DueInChain is the same question restricted to what a rule's own first stage acted on.

func (RetentionMarkingRepository) Mark

func (r RetentionMarkingRepository) Mark(
	ctx context.Context, ids []shared.ID, ruleID shared.ID,
	action domain.Action, effectiveAt time.Time,
) (int, error)

Mark writes what is coming, when, and under which rule.

func (RetentionMarkingRepository) MarkedDue

func (r RetentionMarkingRepository) MarkedDue(
	ctx context.Context, now time.Time, after repository.Position, batch int,
) ([]repository.Candidate, error)

MarkedDue answers the entries whose grace period has run out.

func (RetentionMarkingRepository) Marking

Marking answers one entry's marking.

func (RetentionMarkingRepository) RetainedDescendants

func (r RetentionMarkingRepository) RetainedDescendants(
	ctx context.Context, ids, going []shared.ID,
) (map[shared.ID]int, error)

RetainedDescendants is §4.6: how many entries below each of these are not going in this pass.

func (RetentionMarkingRepository) Trash

func (r RetentionMarkingRepository) Trash(
	ctx context.Context, ids []shared.ID, batchID shared.ID, at time.Time,
) (int, error)

Trash is the act of a TRASH stage.

type RetentionRuleRepository

type RetentionRuleRepository struct{}

RetentionRuleRepository stores the rule model of data-retention.md §2.

func NewRetentionRuleRepository

func NewRetentionRuleRepository() RetentionRuleRepository

func (RetentionRuleRepository) CarryOver

func (r RetentionRuleRepository) CarryOver(
	ctx context.Context, id shared.ID, kind domain.DataKind, now time.Time,
) error

CarryOver writes the old table's period as a tenant-wide rule for a tenant that has none.

func (RetentionRuleRepository) ClearFailures

func (r RetentionRuleRepository) ClearFailures(ctx context.Context, ruleIDs []shared.ID) error

ClearFailures takes the last failure off the rules named.

func (RetentionRuleRepository) ClearMarks

func (r RetentionRuleRepository) ClearMarks(
	ctx context.Context, ruleID shared.ID, now time.Time,
) (int, error)

ClearMarks takes every entry counting down under a rule out of its period.

func (RetentionRuleRepository) Delete

func (r RetentionRuleRepository) Delete(ctx context.Context, id shared.ID) (bool, error)

Delete withdraws a rule.

func (RetentionRuleRepository) Find

Find answers one rule.

func (RetentionRuleRepository) Insert

Insert writes a rule.

func (RetentionRuleRepository) List

List answers every rule the tenant has, narrowest scope first.

func (RetentionRuleRepository) RecordFailure

func (r RetentionRuleRepository) RecordFailure(
	ctx context.Context, ruleID shared.ID, failure domain.RuleFailure,
) error

RecordFailure sets the rule's last failure.

func (RetentionRuleRepository) Update

func (r RetentionRuleRepository) Update(
	ctx context.Context, rule domain.Rule, expectedVersion int, now time.Time,
) (bool, error)

Update writes a corrected rule, guarded on the version the caller read.

The kind and the scope are not among the columns the statement writes: the unique index over the pair is what makes "the rule for this kind at this level" a thing one can name at all.

type SavedViewRepository

type SavedViewRepository struct{}

SavedViewRepository stores the bookmark shelf. The query documents pass through it as they were sent - validated by the application, interpreted by nobody here.

Nothing here names a tenant: the transaction the caller opened decided that, and row level security applies it to every statement below (ADR-0010).

func NewSavedViewRepository

func NewSavedViewRepository() SavedViewRepository

func (SavedViewRepository) Delete

func (r SavedViewRepository) Delete(
	ctx context.Context, saved view.SavedView, expectedVersion int,
) error

Delete removes the view, or reports a version conflict.

func (SavedViewRepository) Find

Find answers one view.

func (SavedViewRepository) Insert

func (r SavedViewRepository) Insert(ctx context.Context, saved view.SavedView) error

Insert writes a new view.

func (SavedViewRepository) ListOwned

func (r SavedViewRepository) ListOwned(
	ctx context.Context, ownerID shared.ID,
) ([]view.SavedView, error)

ListOwned answers the account's own views.

func (SavedViewRepository) ListReachable

func (r SavedViewRepository) ListReachable(
	ctx context.Context, ownerID shared.ID, scopeIDs []shared.ID,
) ([]view.SavedView, error)

ListReachable answers the account's own views plus what is shared into the given scopes.

func (SavedViewRepository) SetAttributes

func (r SavedViewRepository) SetAttributes(
	ctx context.Context, saved view.SavedView, expectedVersion int,
) error

SetAttributes writes the view's own fields whole, or reports a version conflict.

func (SavedViewRepository) SetSharing

func (r SavedViewRepository) SetSharing(
	ctx context.Context, saved view.SavedView, expectedVersion int,
) error

SetSharing writes who sees the view, or reports a version conflict.

type SchemaLedger

type SchemaLedger struct {
	UnitOfWork persistence.UnitOfWork
}

SchemaLedger reads which migrations the database has applied, for the readiness probe (deployment.md §5). The ledger itself is closed to the application role, so it is read through applied_schema_versions() (migration 0150), which answers the version numbers and nothing else.

func (SchemaLedger) Applied

func (l SchemaLedger) Applied(ctx context.Context) (applied []int64, older bool, err error)

Applied answers the applied versions. older is true when the database predates the reader - the function is not there - which means it lacks at least the migration that brought it.

type SealingRepository

type SealingRepository struct{}

SealingRepository is the census a rotation ends on (ADR-0045): one statement over the places a sealed value lives, bounded to the transaction's tenant by row level security like every other statement here, and one more over the installation's own rows.

func NewSealingRepository

func NewSealingRepository() SealingRepository

func (SealingRepository) Count

func (SealingRepository) CountInstallation

func (SealingRepository) CountInstallation(ctx context.Context) (repository.Counts, error)

type SearchIndexRepository

type SearchIndexRepository struct{}

SearchIndexRepository is the adapter for the search's bookkeeping.

func NewSearchIndexRepository

func NewSearchIndexRepository() SearchIndexRepository

func (SearchIndexRepository) Rebuild

func (r SearchIndexRepository) Rebuild(ctx context.Context, batch int) (int64, error)

Rebuild rewrites one batch of stale rows.

func (SearchIndexRepository) Stale

Stale counts the workspace's rows whose document would be built differently today.

type SemanticSearchRepository

type SemanticSearchRepository struct{}

SemanticSearchRepository answers whether this installation can search by meaning.

Beside the text languages and for the same reason: both are reads of what this installation *is*.

func NewSemanticSearchRepository

func NewSemanticSearchRepository() SemanticSearchRepository

func (SemanticSearchRepository) Available

func (r SemanticSearchRepository) Available(ctx context.Context) (bool, error)

Available reports whether the embedding store exists.

type SessionRepository

type SessionRepository struct{}

SessionRepository maintains the sign-in rows and their refresh chains.

It is the only place that knows how a refresh token becomes a hash, AccessTokenRepository's reasoning: the pepper is a secret of this layer, which is why the ports take the presented credential whole. No method takes a tenant - row level security bounds every statement to the tenant of the running transaction (ADR-0010).

func NewSessionRepository

func NewSessionRepository() SessionRepository

func (SessionRepository) CountExpired

func (r SessionRepository) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

CountExpired reports what is due, bounded by the ceiling.

func (SessionRepository) DeleteExpired

func (r SessionRepository) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpired removes one batch of sessions that are over (the SESSION data kind). The query's own guard only ever matches a session that has run out or was revoked - ending sign-ins is revocation's job, and the sweep only forgets.

func (SessionRepository) Elevate

func (r SessionRepository) Elevate(
	ctx context.Context, sessionID, accountID shared.ID, until, now time.Time,
) (bool, error)

Elevate raises one live session of the account for a bounded while (ADR-0070 §4).

func (SessionRepository) ElevatedUntil

func (r SessionRepository) ElevatedUntil(
	ctx context.Context, sessionID, accountID shared.ID,
) (time.Time, error)

ElevatedUntil answers when the account's live session stops carrying the control plane's scope.

func (SessionRepository) Extend

func (r SessionRepository) Extend(ctx context.Context, sessionID shared.ID, expiresAt time.Time) error

func (SessionRepository) FindForAuth

func (r SessionRepository) FindForAuth(
	ctx context.Context, sessionID shared.ID,
) (repository.SessionCredential, error)

func (SessionRepository) ForAccount

func (r SessionRepository) ForAccount(
	ctx context.Context, accountID shared.ID, now time.Time,
) (repository.AccountSessions, error)

func (SessionRepository) Insert

func (r SessionRepository) Insert(ctx context.Context, session identity.Session) error

func (SessionRepository) Revoke

func (r SessionRepository) Revoke(
	ctx context.Context, sessionID, accountID shared.ID, at time.Time,
) (bool, error)

func (SessionRepository) RevokeAll

func (r SessionRepository) RevokeAll(
	ctx context.Context, accountID shared.ID, at time.Time,
) (int, error)

func (SessionRepository) RevokeOthers

func (r SessionRepository) RevokeOthers(
	ctx context.Context, accountID, keep shared.ID, at time.Time,
) (int, error)

RevokeOthers ends every live session of the account but the one named. A zero `keep` spares nothing: it becomes NULL, never an empty identifier a comparison would silently match nothing with.

func (SessionRepository) TouchLastSeen

func (r SessionRepository) TouchLastSeen(ctx context.Context, sessionID shared.ID, at time.Time) error

type SetupCodeRepository

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

SetupCodeRepository holds the first start's codes (migration 0128) through their three narrow functions; the table has no policy and no grant to the application role. A code is stored only as its hash under the setup code's own purpose label.

func NewSetupCodeRepository

func NewSetupCodeRepository(hasher security.InboundTokenHasher) SetupCodeRepository

func (SetupCodeRepository) Claim

Claim spends a code inside the caller's transaction.

func (SetupCodeRepository) Issue

func (r SetupCodeRepository) Issue(
	ctx context.Context, id shared.ID, code identity.SetupCode, issuedAt, expiresAt time.Time,
) (bool, error)

Issue stores the code's hash, on an empty installation only.

func (SetupCodeRepository) Unset

Unset reports whether the installation has no workspace and nobody in its register.

type SignInRepository

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

SignInRepository is the account surface of the sign-in flow: the credential check's read, the attempt ledger, the invitation redeemed, and the tenant resolution (multi-tenancy.md §3).

func NewSignInRepository

func NewSignInRepository(
	redemptionHasher security.RedemptionTokenHasher,
	attemptHasher security.AuthAttemptHasher,
) SignInRepository

func (SignInRepository) ActivateFirstOwner

func (r SignInRepository) ActivateFirstOwner(
	ctx context.Context, accountID shared.ID, passwordHash string, now time.Time,
) (bool, error)

ActivateFirstOwner sets the first start's owner's password and activates the account (UC-INS-01). False where the account is not an invited one without a redemption token.

func (SignInRepository) Clear

func (r SignInRepository) Clear(ctx context.Context, subject string) error

func (SignInRepository) Fail

func (r SignInRepository) Fail(ctx context.Context, subject string, at time.Time) (int, error)

Fail adds one failure atomically and answers the count after it.

func (SignInRepository) Find

func (SignInRepository) FindByRedemptionToken

func (r SignInRepository) FindByRedemptionToken(
	ctx context.Context, token identity.Token,
) (repository.RedemptionAccount, error)

func (SignInRepository) FindForSignIn

func (r SignInRepository) FindForSignIn(
	ctx context.Context, email string,
) (repository.SignInAccount, error)

func (SignInRepository) PasswordHashOf

func (r SignInRepository) PasswordHashOf(
	ctx context.Context, accountID shared.ID,
) (secret.Secret, error)

PasswordHashOf answers one account's stored hash, wrapped so no way of printing it yields anything (rule 10).

func (SignInRepository) Record

func (r SignInRepository) Record(
	ctx context.Context, subject string, attempt repository.AuthAttempt,
) error

func (SignInRepository) RecordTermsAgreement

func (r SignInRepository) RecordTermsAgreement(
	ctx context.Context, accountID shared.ID, agreement identity.TermsAgreement,
) (bool, error)

RecordTermsAgreement replaces the account's agreement to the terms of use.

func (SignInRepository) Redeem

func (r SignInRepository) Redeem(
	ctx context.Context, accountID shared.ID, passwordHash string, now time.Time,
) (bool, error)

func (SignInRepository) RehashPassword

func (r SignInRepository) RehashPassword(
	ctx context.Context, accountID shared.ID, current, replacement string, at time.Time,
) (bool, error)

RehashPassword replaces the stored hash only while it is still the one the sign-in verified - the compare-and-set that keeps a password changed in between from being overwritten.

func (SignInRepository) Resolve

func (r SignInRepository) Resolve(ctx context.Context, slug string) (shared.ID, error)

Resolve maps a slug - or, empty, the single-mode installation's only row - to its tenant. It runs through the narrow SECURITY DEFINER function of migration 0063 inside an installation-scoped read-only transaction: the caller has no tenant yet, which is the whole point.

func (SignInRepository) SetRedemptionToken

func (r SignInRepository) SetRedemptionToken(
	ctx context.Context, accountID shared.ID, presented identity.Token,
	expiresAt, now time.Time,
) (bool, error)

func (SignInRepository) TermsAgreementOf

func (r SignInRepository) TermsAgreementOf(
	ctx context.Context, accountID shared.ID,
) (identity.TermsAgreement, error)

TermsAgreementOf answers the account's latest agreement to the terms of use (UC-ID-19).

type SnapshotRepository

type SnapshotRepository struct{}

SnapshotRepository reads the current state for an initial synchronisation: every kind the change log records, in pages by identifier, live rows only.

Every statement runs inside the caller's transaction and therefore under `SET LOCAL app.tenant_id` (rule 3): row level security is what keeps one workspace's walk out of another's rows, and the explicit tenant predicate beside it is the index's, not the boundary's.

func NewSnapshotRepository

func NewSnapshotRepository() SnapshotRepository

func (SnapshotRepository) Buckets

func (SnapshotRepository) Buckets(
	ctx context.Context, after shared.ID, batch int,
) ([]work.Bucket, error)

Buckets pages the live columns.

func (SnapshotRepository) Comments

func (SnapshotRepository) Comments(
	ctx context.Context, after shared.ID, batch int,
) ([]repository.InCollection[work.Comment], error)

Comments pages the live comments of live entries.

func (SnapshotRepository) Containers

func (SnapshotRepository) Containers(
	ctx context.Context, after shared.ID, batch int,
) ([]work.Container, error)

Containers pages the live hubs and collections.

func (SnapshotRepository) Items

func (SnapshotRepository) Items(
	ctx context.Context, after shared.ID, batch int,
) ([]work.WorkItem, error)

Items pages the live entries, archived ones included.

func (SnapshotRepository) Labels

func (SnapshotRepository) Labels(
	ctx context.Context, after shared.ID, batch int,
) ([]work.Label, error)

Labels pages the live labels.

func (SnapshotRepository) Recurrences

func (SnapshotRepository) Recurrences(
	ctx context.Context, after shared.ID, batch int,
) ([]repository.InCollection[work.RecurrenceRule], error)

Recurrences pages the recurrence rules of live entries.

func (SnapshotRepository) Reminders

func (SnapshotRepository) Reminders(
	ctx context.Context, after shared.ID, batch int,
) ([]repository.InCollection[work.Reminder], error)

Reminders pages the reminders of live entries.

func (SnapshotRepository) SetElements

SetElements pages every tag row of every live entry, in the order of the row's key.

func (SnapshotRepository) Templates

func (SnapshotRepository) Templates(
	ctx context.Context, after shared.ID, batch int,
) ([]work.Template, error)

Templates pages the live templates.

type StarterRepository

type StarterRepository struct{}

StarterRepository keeps whether the workspace's first start was answered (db/queries/Starter.sql).

func NewStarterRepository

func NewStarterRepository() StarterRepository

func (StarterRepository) Close

Close records the answer.

func (StarterRepository) Pending

func (StarterRepository) Pending(ctx context.Context) (bool, error)

Pending reports whether the question is still to be asked.

type StepUpRepository

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

StepUpRepository maintains the step-up proof on the session rows. A type of its own for RefreshTokenRepository's reason: it hashes a different credential under a different label.

func NewStepUpRepository

func NewStepUpRepository(hasher security.StepUpTokenHasher) StepUpRepository

func (StepUpRepository) Consume

func (r StepUpRepository) Consume(
	ctx context.Context, presented identity.Token, accountID shared.ID,
	cutoff, now time.Time,
) (identity.StepUpMethod, bool, error)

func (StepUpRepository) Record

func (r StepUpRepository) Record(
	ctx context.Context, sessionID, accountID shared.ID,
	presented identity.Token, method identity.StepUpMethod, at time.Time,
) (bool, error)

type StreamPartitionRepository

type StreamPartitionRepository struct{}

StreamPartitionRepository is the monthly duty of the partitioned streams, the audit partition repository's shape: two narrow SECURITY DEFINER acts, nothing else.

func NewStreamPartitionRepository

func NewStreamPartitionRepository() StreamPartitionRepository

func (StreamPartitionRepository) DropAged

func (StreamPartitionRepository) DropAged(
	ctx context.Context, table string, defaultDays int,
) ([]repository.Dropped, error)

DropAged lets go what has wholly aged out.

func (StreamPartitionRepository) Ensure

func (StreamPartitionRepository) Ensure(
	ctx context.Context, table string, month time.Time,
) (string, error)

Ensure creates or repairs one month's partition.

type StructureRepository

type StructureRepository struct{}

StructureRepository stores the structure layers of hubs, collections and the workspace. No statement names a tenant: row level security binds each to the transaction's, so another workspace's container is not found and its settings are not there.

func NewStructureRepository

func NewStructureRepository() StructureRepository

func (StructureRepository) Heirs

func (r StructureRepository) Heirs(ctx context.Context, hubID shared.ID, ceiling int) ([]work.Container, error)

Heirs answers the live containers inheriting a hub's layer, or the workspace's.

func (StructureRepository) HoldContainer

func (r StructureRepository) HoldContainer(ctx context.Context, id shared.ID) error

HoldContainer holds the row FOR SHARE. A container another workspace owns is not found, like a missing one.

func (StructureRepository) SetContainerStructure

func (r StructureRepository) SetContainerStructure(
	ctx context.Context, container work.Container, expectedVersion int,
) error

SetContainerStructure writes the container's own layer and, on a collection, its own completion.

func (StructureRepository) SetWorkspace

func (r StructureRepository) SetWorkspace(
	ctx context.Context, workspace work.WorkspaceStructure, expectedVersion int, at time.Time,
) error

SetWorkspace writes the workspace's layer under the tenant row's version.

func (StructureRepository) Workspace

Workspace answers the workspace's layer and the tenant row's version, held as asked.

type SuggestionRepository

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

What AI proposed. No method takes a tenant: row level security bounds every statement (ADR-0010).

func NewSuggestionRepository

func NewSuggestionRepository(cursors security.CursorCodec) SuggestionRepository

func (SuggestionRepository) CountExpired

func (r SuggestionRepository) CountExpired(
	ctx context.Context, cutoff time.Time, ceiling int,
) (int, error)

func (SuggestionRepository) Decide

func (r SuggestionRepository) Decide(
	ctx context.Context, decided domain.Suggestion, expectedVersion int,
) (bool, error)

func (SuggestionRepository) Delete

func (r SuggestionRepository) Delete(ctx context.Context, id shared.ID) error

Delete removes a request the job has read. A request already gone is not an error: the job that reads it may run twice.

func (SuggestionRepository) DeleteExpired

func (r SuggestionRepository) DeleteExpired(
	ctx context.Context, cutoff time.Time, batch int,
) (int, error)

func (SuggestionRepository) Find

func (SuggestionRepository) Get

Get answers one request, or an error wrapping shared.ErrNotFound.

func (SuggestionRepository) List

func (SuggestionRepository) Narrow

func (r SuggestionRepository) Narrow(
	ctx context.Context, narrowed domain.Suggestion, expectedVersion int,
) (bool, error)

func (SuggestionRepository) Put

Put holds what a person asked a template to be drafted from, for the job that will read it.

func (SuggestionRepository) Record

func (r SuggestionRepository) Record(ctx context.Context, proposal domain.Suggestion) error

type SyncLogSweeper

type SyncLogSweeper struct{}

SyncLogSweeper removes the synchronisation's records past the offline window (data-retention.md §3, the SYNC_LOG kind): operation log rows and tombstones, one batch of each per pass, the device sweep's shape. The change log is not its business - its months fall as partitions (drop_stream_partition).

func NewSyncLogSweeper

func NewSyncLogSweeper() SyncLogSweeper

func (SyncLogSweeper) CountExpired

func (SyncLogSweeper) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

CountExpired answers how many records are due, up to the ceiling for each of the two tables.

func (SyncLogSweeper) DeleteExpired

func (SyncLogSweeper) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpired removes up to batch operation log rows and up to batch tombstones older than the cutoff, and reports how many rows went. A tombstone goes only when its own purge date has passed too: the date is the deletion plus the window as it stood, and the later of the two is what a device was promised.

type SyncOpLog

type SyncOpLog struct{}

SyncOpLog is the operation log's two statements: what a push did with each op_id, kept for the offline window. Both run inside the caller's transaction (rule 3).

func NewSyncOpLog

func NewSyncOpLog() SyncOpLog

func (SyncOpLog) Find

Find answers a processed operation's record, and false for one this workspace has not seen.

func (SyncOpLog) Record

func (SyncOpLog) Record(ctx context.Context, record repository.OpRecord) error

Record writes one processed operation; a repeat that raced this one is left standing.

type TemplateRepository

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

TemplateRepository stores the trees somebody wrote down to stamp out again.

func NewTemplateRepository

func NewTemplateRepository(cursors security.CursorCodec) TemplateRepository

func (TemplateRepository) Find

Find returns the template, deleted or not.

func (TemplateRepository) Insert

func (r TemplateRepository) Insert(ctx context.Context, template work.Template) error

Insert writes a new template.

func (TemplateRepository) ListInScopes

func (r TemplateRepository) ListInScopes(
	ctx context.Context, scopeIDs []shared.ID, page repository.Page,
) (repository.TemplatePage, error)

ListInScopes returns one page of what is reachable from a container's path.

func (TemplateRepository) Remove

func (r TemplateRepository) Remove(
	ctx context.Context, template work.Template, expectedVersion int,
) error

Remove deletes the template for good under the optimistic lock.

func (TemplateRepository) SetDeleted

func (r TemplateRepository) SetDeleted(
	ctx context.Context, template work.Template, expectedVersion int,
) error

SetDeleted writes the soft deletion.

func (TemplateRepository) Update

func (r TemplateRepository) Update(
	ctx context.Context, template work.Template, expectedVersion int,
) error

Update writes the whole document under the optimistic lock. A deleted template is never matched: a change racing a deletion loses to it.

type TenantHostRepository

type TenantHostRepository struct{}

TenantHostRepository is the hosts a workspace answers at (migration 0104).

No method takes a tenant: row level security bounds every statement and the workspace is the transaction's (ADR-0010). Nothing here finds a workspace *by* host: that lookup has to cross the boundary, the way `resolve_tenant` does.

func NewTenantHostRepository

func NewTenantHostRepository() TenantHostRepository

func (TenantHostRepository) Insert

func (TenantHostRepository) List

type TenantPurge

type TenantPurge struct{}

TenantPurge is the hard delete's surface (multi-tenancy.md §5, the final phase).

func NewTenantPurge

func NewTenantPurge() TenantPurge

func (TenantPurge) ArchiveUsage

func (TenantPurge) ArchiveUsage(ctx context.Context) (int64, error)

ArchiveUsage moves the workspace's counts to the installation's ledger and closes them on the purge day.

func (TenantPurge) DeleteIdempotency

func (TenantPurge) DeleteIdempotency(ctx context.Context) (int, error)

DeleteIdempotency clears the replay guards.

func (TenantPurge) DeleteJobs

func (TenantPurge) DeleteJobs(ctx context.Context, keep shared.ID) (int, error)

DeleteJobs removes the tenant's queue rows except the one running this purge.

func (TenantPurge) DeleteOutbox

func (TenantPurge) DeleteOutbox(ctx context.Context, batch int) (int, error)

DeleteOutbox removes one batch of the tenant's outbox.

func (TenantPurge) DropStructure

func (TenantPurge) DropStructure(ctx context.Context) (int64, error)

DropStructure fells the structure in dependency order; see db/queries/Admin.sql for why the cascade cannot be left to do it.

func (TenantPurge) Footprint

Footprint counts the stores before the fall.

func (TenantPurge) HardDelete

func (TenantPurge) HardDelete(ctx context.Context, now time.Time) (bool, error)

HardDelete removes the tenant row itself.

func (TenantPurge) MarkStarted

func (TenantPurge) MarkStarted(ctx context.Context, now time.Time) (bool, error)

MarkStarted writes the purge's mark on the tenant row before the first byte goes.

func (TenantPurge) PurgeTrail

func (TenantPurge) PurgeTrail(ctx context.Context) (int64, error)

PurgeTrail removes the tenant's audit trail through migration 0067's narrow act.

func (TenantPurge) StorageKeys

func (TenantPurge) StorageKeys(ctx context.Context, after string, batch int) ([]string, error)

StorageKeys pages through the tenant's object keys.

type TenantQueryRepository

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

TenantQueryRepository is the control plane's filtered, paged read of the workspaces.

func NewTenantQueryRepository

func NewTenantQueryRepository(cursors security.CursorCodec) TenantQueryRepository

NewTenantQueryRepository answers the repository; the cursors are signed with the installation's secret.

func (TenantQueryRepository) Query

Query answers one page of the matching workspaces, oldest first.

type TextLanguageRepository

type TextLanguageRepository struct{}

TextLanguageRepository reads which languages this installation can index.

Beside the capability profiles because it answers the same endpoint and nothing else, and because both are reads of what this installation *is* rather than of what a tenant has: the mapping is in the database (hubtask_text_languages), and the join against pg_ts_config inside it is what makes the answer this binary's rather than this product's.

func NewTextLanguageRepository

func NewTextLanguageRepository() TextLanguageRepository

func (TextLanguageRepository) List

List returns the language tags, in the order the function answers them.

type TombstoneRepository

type TombstoneRepository struct{}

TombstoneRepository answers whether an entity has been purged (offline-sync.md §7).

func NewTombstoneRepository

func NewTombstoneRepository() TombstoneRepository

func (TombstoneRepository) Holds

func (TombstoneRepository) Holds(ctx context.Context, entity string, id shared.ID) (bool, error)

Holds reports a purged entity.

type TrashRepository

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

TrashRepository reads what is in the trash and finally removes it.

func NewTrashRepository

func NewTrashRepository(cursors security.CursorCodec) TrashRepository

func (TrashRepository) BatchMembers

func (r TrashRepository) BatchMembers(
	ctx context.Context, batchIDs []shared.ID,
) ([]repository.TrashMember, error)

BatchMembers returns every row still in the trash that one of these deletions took.

func (TrashRepository) KeptBelow

func (r TrashRepository) KeptBelow(
	ctx context.Context, ids, going []shared.ID,
) (map[shared.ID]int, error)

KeptBelow answers how many rows below each of these are not going in this pass.

func (TrashRepository) List

List returns one page of the trash, newest deletion first.

func (TrashRepository) PurgeContainers

func (r TrashRepository) PurgeContainers(ctx context.Context, ids []shared.ID) (int, error)

PurgeContainers removes hubs and collections for good, by identifier. Collections before the hubs that hold them - `container.parent_id` is ON DELETE RESTRICT, and a hub whose collections are still there refuses to go.

func (TrashRepository) PurgeItems

func (r TrashRepository) PurgeItems(ctx context.Context, ids []shared.ID) (int, error)

PurgeItems removes items for good, by identifier.

func (TrashRepository) SubtreeIDs

func (r TrashRepository) SubtreeIDs(ctx context.Context, prefix string) ([]shared.ID, error)

SubtreeIDs returns every identifier in one item's subtree, the item included.

type UnitOfWork

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

UnitOfWork is the only place in the system that sets the tenant context (rule 3, ADR-0010). Everything else reaches the database through a transaction opened here.

SET LOCAL rather than SET: the setting is bound to the transaction and disappears with it, so a connection returned to the pool carries no tenant with it. That is also what makes the arrangement safe behind pgbouncer in transaction pooling mode (multi-tenancy.md §2.1).

func NewUnitOfWork

func NewUnitOfWork(pool *pgxpool.Pool) *UnitOfWork

func (*UnitOfWork) ScopeFromContext

func (u *UnitOfWork) ScopeFromContext(ctx context.Context) (persistence.Scope, bool)

ScopeFromContext reports the scope the running transaction was opened with. A repository uses it to state what it is operating under; the value is put there by this package, never by a caller.

func (*UnitOfWork) Within

func (u *UnitOfWork) Within(ctx context.Context, scope persistence.Scope, fn func(context.Context) error) error

Within runs fn in a read-write transaction bound to the scope.

func (*UnitOfWork) WithinIndependent

func (u *UnitOfWork) WithinIndependent(
	ctx context.Context, scope persistence.Scope, fn func(context.Context) error,
) error

WithinIndependent runs fn in a read-write transaction of its own, beside any the context carries.

func (*UnitOfWork) WithinReadOnly

func (u *UnitOfWork) WithinReadOnly(ctx context.Context, scope persistence.Scope, fn func(context.Context) error) error

WithinReadOnly runs fn in a read-only transaction. PostgreSQL enforces the read-only part, so a write that slipped into a query path fails loudly rather than quietly succeeding.

func (*UnitOfWork) WithinSnapshot

func (u *UnitOfWork) WithinSnapshot(
	ctx context.Context, scope persistence.Scope, fn func(context.Context, time.Time) error,
) error

WithinSnapshot runs fn against one consistent point in time (backup-restore.md §5).

REPEATABLE READ and read-only together. The isolation level is what makes the archive one point in time rather than a mixture; read-only is what lets PostgreSQL take the cheaper snapshot and what stops an export ever writing. A long-running snapshot holds back vacuum for its duration, which is the cost of a consistent backup and the reason §5 puts the run on a bulkhead pool of its own rather than on the API path.

func (*UnitOfWork) WithinWritable

func (u *UnitOfWork) WithinWritable(ctx context.Context, scope persistence.Scope, fn func(context.Context) error) error

WithinWritable joins a running read-write transaction, and opens one of its own beside a running read-only one (persistence.Writable). Beside it means on another connection: the context handed on carries neither the read's transaction nor its scope, so the write commits when fn returns whatever the read does afterwards.

type UsageRepository

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

UsageRepository reads the use for the control plane (multi-tenancy.md §4.3), through `admin_usage()`, which answers nothing inside a workspace's scope. Its cursor is the installation's signed one, as every list's (api-guidelines.md §4).

func NewUsageRepository

func NewUsageRepository(cursors security.CursorCodec) UsageRepository

NewUsageRepository answers the repository.

func (UsageRepository) CursorAfter

func (r UsageRepository) CursorAfter(last shared.ID) string

CursorAfter is the cursor of a page whose last workspace was last.

func (UsageRepository) Read

Read answers the rows of at most query.Size workspaces.

func (UsageRepository) SweepUsage

func (UsageRepository) SweepUsage(ctx context.Context, before time.Time) (int64, error)

SweepUsage removes the counts older than the day, through `sweep_usage()`, which answers 0 inside a workspace's scope.

type VoiceRepository

type VoiceRepository struct{}

VoiceRepository is a workspace's voice settings (ADR-0093).

func NewVoiceRepository

func NewVoiceRepository() VoiceRepository

NewVoiceRepository builds the repository.

func (VoiceRepository) Find

func (VoiceRepository) Save

func (VoiceRepository) Save(
	ctx context.Context, settings domain.Settings, by shared.ID, at time.Time,
) error

type WebhookDeliveryRepository

type WebhookDeliveryRepository struct{}

WebhookDeliveryRepository is the log of what was sent and what became of it.

func NewWebhookDeliveryRepository

func NewWebhookDeliveryRepository() WebhookDeliveryRepository

func (WebhookDeliveryRepository) CountExpired

func (r WebhookDeliveryRepository) CountExpired(ctx context.Context, cutoff time.Time, ceiling int) (int, error)

CountExpired reports how many delivery attempts are due, counted no higher than ceiling.

func (WebhookDeliveryRepository) DeleteExpired

func (r WebhookDeliveryRepository) DeleteExpired(ctx context.Context, cutoff time.Time, batch int) (int, error)

DeleteExpired removes one batch of delivery attempts older than the cutoff, in the tenant the transaction is bound to (data-retention.md §3). Which ones lives in the statement: a pending attempt a living job still names is kept.

func (WebhookDeliveryRepository) Find

func (WebhookDeliveryRepository) FindPendingOfPush

func (WebhookDeliveryRepository) FindPendingOfPush(
	ctx context.Context, subscriptionID shared.ID, key domain.CollapseKey,
) (domain.WebhookDelivery, bool, error)

func (WebhookDeliveryRepository) Insert

func (WebhookDeliveryRepository) List

func (WebhookDeliveryRepository) RecordOutcome

func (WebhookDeliveryRepository) Repoint

func (WebhookDeliveryRepository) Repoint(ctx context.Context, deliveryID, eventID shared.ID) (bool, error)

type WebhookSubscriptionRepository

type WebhookSubscriptionRepository struct{}

WebhookSubscriptionRepository stores the standing requests external systems make.

It holds no key and does no sealing: the ciphertext arrives already sealed and leaves as it came. That is the same division the access token's hash uses in the other direction - the layer that owns the secret material is the one that owns the operation on it, and this one owns the row.

func NewWebhookSubscriptionRepository

func NewWebhookSubscriptionRepository() WebhookSubscriptionRepository

func (WebhookSubscriptionRepository) Delete

func (WebhookSubscriptionRepository) Delete(
	ctx context.Context, subscriptionID shared.ID,
) (bool, error)

func (WebhookSubscriptionRepository) Find

func (WebhookSubscriptionRepository) Insert

func (WebhookSubscriptionRepository) List

func (WebhookSubscriptionRepository) Rewrap

func (r WebhookSubscriptionRepository) Rewrap(
	ctx context.Context, id shared.ID, secret, previous repository.SealedSecret, expectedVersion int,
) (bool, error)

func (WebhookSubscriptionRepository) Rotate

func (WebhookSubscriptionRepository) Rotate(
	ctx context.Context, subscriptionID shared.ID, secret repository.SealedSecret,
	previousUntil time.Time, expectedVersion int,
) (bool, error)

func (WebhookSubscriptionRepository) SealedNotUnder

func (WebhookSubscriptionRepository) Update

func (WebhookSubscriptionRepository) Update(
	ctx context.Context, subscription domain.WebhookSubscription, expectedVersion int,
) (bool, error)

func (WebhookSubscriptionRepository) WantingEvent

type WorkspaceRepository

type WorkspaceRepository struct{}

WorkspaceRepository answers what the tenant is called (backup-restore.md §8.3 step 3).

Its own type rather than a method on the restore log, because it reads a different table for a different reason - and because a repository that could read the tenant row while also writing restore runs is a repository somebody will eventually use to read the tenant row for something else.

func NewWorkspaceRepository

func NewWorkspaceRepository() WorkspaceRepository

func (WorkspaceRepository) Name

Name answers the display name of the tenant the transaction is bound to.

type WorkspaceSettingsRepository

type WorkspaceSettingsRepository struct{}

WorkspaceSettingsRepository is the tenant's own row as the people inside it read and change it. Neither statement names a tenant: row level security has bound the transaction to one, and that is what makes another workspace invisible rather than forbidden.

Named for what it serves rather than for the row, because `WorkspaceRepository` is taken by the restore's one-column reader and two types with one name would have to live in two packages to be told apart.

func NewWorkspaceSettingsRepository

func NewWorkspaceSettingsRepository() WorkspaceSettingsRepository

func (WorkspaceSettingsRepository) Find

Find answers the workspace the transaction is bound to.

func (WorkspaceSettingsRepository) Update

func (WorkspaceSettingsRepository) Update(
	ctx context.Context, changed identity.Workspace, expectedVersion int, now time.Time,
) (bool, error)

Update writes the three columns and merges the modelled settings keys, guarded on the version.

Source Files

Directories

Path Synopsis
Package query turns a validated query into the statement that answers it.
Package query turns a validated query into the statement that answers it.

Jump to

Keyboard shortcuts

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