offboard

package
v0.0.7 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package offboard answers one question in one operation: "this person left, are they out?" (Task 20261).

Before this, the answer was no. An enterprise hub let an identity hold five independent credential surfaces, and disabling the account at the IdP severed none of them on its own:

  • dashboard sessions, which outlive an IdP disablement until the absolute TTL because a refresh that returns no id_token cannot narrow claims;
  • API tokens, which carry an owner binding that nothing consulted on departure, so a departed user's PAT worked until its ExpiresAt;
  • glasses links, the same rows with a different Kind, whose own source conceded that an IdP dropping someone from a group "is not noticed until the link expires";
  • secret leases held by their running tasks, which keep real credentials materialised inside a sandbox;
  • the tasks themselves, still executing on the fleet in their name.

The documented remedy was a hand-written deny binding, which stops the account *acting* but ends nothing it already holds.

Those are what a person can use. What they keep here — personal broker secrets and the grants spending them, grant requests still open, a Claude Code login home on every hub member — is credentials.go (Task 20400): destroyed by default, kept under a legal hold.

Why one package

The CLI and the dashboard both need this, and they need it to mean exactly the same thing. Two implementations of "who counts as this person" is the failure mode that matters: the one that under-matches reports success while leaving a credential live. So resolution, planning and execution live here, and cmd/ and pkg/ui are thin.

Ordering

Severing runs before stopping. Revoking credentials first means a task that notices its lease vanishing and retries cannot re-authenticate; stopping the task first would leave a window where its still-valid session could start another one. The durable credentials go in a single transaction (see statedb.OffboardIdentity) so there is no partially-offboarded state; leases, the stored credentials, tasks and Claude logins cannot join that transaction — they are process memory, the broker, other databases and other hub members' filesystems — and so are applied after it, in that order, each reporting its own failures rather than rolling back a severing that already succeeded. Claude logins come last so a task of theirs is already told to stop before the directory it writes to is removed.

What it will not do

Projects are reported, never deleted. A departing user's projects usually hold the team's work, and an offboarding command that silently removed them would be a data-loss tool wearing a security label. The operator gets the list and reassigns.

Index

Constants

View Source
const ClaudeHomesEndpoint = "claude_homes"

ClaudeHomesEndpoint is the member-row endpoint naming where a hub member keeps its Claude homes, so a tree a member left behind can be found on its host. A path, read by processes on the same machine.

Variables

This section is empty.

Functions

func OwnerKeysOf added in v0.0.6

func OwnerKeysOf(ids []DeniedIdentity) []string

OwnerKeysOf returns every key of every identity, sorted.

Types

type ClaudeHomeRef added in v0.0.5

type ClaudeHomeRef struct {
	// Member names the hub process keeping this copy: a cluster member id, or
	// "" for the process running the offboarding.
	Member string `json:"member,omitempty"`
	// OwnerKey is the spelling of the identity the directory is keyed by.
	OwnerKey string `json:"owner_key"`
	// Dir is the directory on that member's filesystem.
	Dir string `json:"dir"`
	// Exists, Credential and Login are what was found there: the directory,
	// a non-empty credential file in it, and a `claude auth login` in flight
	// for this spelling on that member.
	Exists     bool `json:"exists"`
	Credential bool `json:"credential"`
	Login      bool `json:"login,omitempty"`
}

ClaudeHomeRef is one copy of a Claude Code login the hub keeps for the identity: the private CLAUDE_CONFIG_DIR one hub process resolves for one spelling of it.

One identity can have several. Each spelling of the person — their email, and "sub:<subject>" from a time the IdP withheld it — keys its own directory, and each hub process resolves the directory under its own config root, so a cluster whose members run under different users or container filesystems keeps one copy per member.

type ClaudeHomeResult added in v0.0.5

type ClaudeHomeResult struct {
	ClaudeHomeRef
	// LoginCancelled reports that a login in flight was killed.
	LoginCancelled bool `json:"login_cancelled,omitempty"`
	// LoggedOut reports that `claude auth logout` ran and succeeded, which
	// revokes the session at Anthropic and not only on this disk.
	LoggedOut bool `json:"logged_out,omitempty"`
	// Logout says why logout did not run, or how it failed. Best-effort: the
	// directory is removed either way.
	Logout string `json:"logout,omitempty"`
	// Removed reports that the directory is gone.
	Removed bool `json:"removed,omitempty"`
	// Kept reports that a legal hold left the directory in place.
	Kept bool `json:"kept,omitempty"`
	// Error is what failed when the copy could not be removed.
	Error string `json:"error,omitempty"`
}

ClaudeHomeResult is what the run did to one copy.

type ClaudeHomes added in v0.0.5

type ClaudeHomes interface {
	// Inspect finds every copy of these owner keys' logins it can reach, and
	// names the hub processes it could not.
	Inspect(ownerKeys []string) ([]ClaudeHomeRef, []ClaudeUnreached, error)
	// Sever cancels the logins in flight and, unless keep is set, logs each
	// copy out and removes it.
	Sever(ownerKeys []string, keep bool, actor, reason string) ([]ClaudeHomeResult, []ClaudeUnreached, error)
}

ClaudeHomes is the Claude Code login surface. A run with none skips it and the plan says logins were not checked.

type ClaudeTree added in v0.0.6

type ClaudeTree struct {
	// Member names the hub member that keeps it, "" for this process's own.
	Member string
	Root   string
}

ClaudeTree is one hub process's Claude home root on this host.

func ClaudeTreesOnThisHost added in v0.0.6

func ClaudeTreesOnThisHost(db *statedb.DB, own string) ([]ClaudeTree, error)

ClaudeTreesOnThisHost lists the Claude home roots hub members on this host recorded in their member rows, besides own — the tree of the process asking — without duplicates. A member on another host keeps a tree this one cannot see.

type ClaudeUnreached added in v0.0.5

type ClaudeUnreached struct {
	Member string `json:"member"`
	Detail string `json:"detail"`
}

ClaudeUnreached is a hub process whose copies the run could not reach: a member that did not answer, or one that stopped and left its tree behind. A copy nobody reached may still hold a live refresh token, so it is reported rather than assumed absent.

type Credentials added in v0.0.5

type Credentials struct {
	Secrets  []PersonalSecretRef `json:"secrets"`
	Grants   []PersonalGrantRef  `json:"grants"`
	Requests []GrantRequestRef   `json:"requests"`
	// Claude lists every copy of a Claude login found for the identity,
	// on every hub process the run could reach.
	Claude []ClaudeHomeRef `json:"claude"`
	// ClaudeUnreached lists the hub processes the run could not ask.
	ClaudeUnreached []ClaudeUnreached `json:"claude_unreached,omitempty"`

	// Keep reports a legal hold: the secrets and the Claude homes listed
	// above are kept, everything else is severed.
	Keep bool `json:"keep"`

	// SecretsError, RequestsError and ClaudeError say why a part of this
	// footprint could not be read. A run with one set reports it as a
	// failure: what could not be listed could not be destroyed.
	SecretsError  string `json:"secrets_error,omitempty"`
	RequestsError string `json:"requests_error,omitempty"`
	ClaudeError   string `json:"claude_error,omitempty"`
}

Credentials is the identity's stored footprint: what the run destroys, or under a legal hold keeps.

func (Credentials) ClaudeCopies added in v0.0.5

func (c Credentials) ClaudeCopies() int

ClaudeCopies counts the copies that hold anything: a directory, or a login in flight.

func (Credentials) Empty added in v0.0.5

func (c Credentials) Empty() bool

Empty reports whether there is nothing stored to destroy or keep.

type DeniedIdentity added in v0.0.6

type DeniedIdentity struct {
	// Keys are the owner keys: lowercased emails and "sub:<subject>".
	Keys []string
	// Denies names the bindings that deny them, as claim=value.
	Denies []string
}

DeniedIdentity is one person the control plane denies hub-wide, with every owner-key spelling their Claude home may be kept under.

func DeniedIdentities added in v0.0.6

func DeniedIdentities(db *statedb.DB) (denied []DeniedIdentity, held []string, err error)

DeniedIdentities lists the identities whose Claude homes no member should keep: denied hub-wide by a binding other than an in-force SCIM suspension's, and under no legal hold. Held reports, by owner key, the denied identities a hold keeps.

type DenyRef

type DenyRef struct {
	ID    string `json:"id"`
	Claim string `json:"claim"`
	Value string `json:"value"`
}

DenyRef is a deny binding the run will write.

type Failure

type Failure struct {
	Surface string `json:"surface"`
	Detail  string `json:"detail"`
}

Failure records a surface that could not be severed, so a partial run is legible instead of being reported as a success.

type GrantRequestRef added in v0.0.5

type GrantRequestRef struct {
	ID          string    `json:"id"`
	RequestedBy string    `json:"requested_by"`
	SecretName  string    `json:"secret_name,omitempty"`
	Kind        string    `json:"kind,omitempty"`
	Subject     string    `json:"subject"`
	CreatedAt   time.Time `json:"created_at,omitempty"`
}

GrantRequestRef is one self-service grant request the identity filed that is still waiting for a decision.

type HoldRef added in v0.0.5

type HoldRef struct {
	Identity string    `json:"identity"`
	Reason   string    `json:"reason"`
	PlacedAt time.Time `json:"placed_at"`
	PlacedBy string    `json:"placed_by,omitempty"`
}

HoldRef is a legal hold an offboarding found placed on its identity.

type LeaseRef

type LeaseRef struct {
	ID         string    `json:"id"`
	ExecutorID string    `json:"executor_id,omitempty"`
	ProjectID  string    `json:"project_id,omitempty"`
	Kinds      []string  `json:"kinds,omitempty"`
	ExpiresAt  time.Time `json:"expires_at,omitempty"`
	// GrantIDs are the grants whose material the lease carries, so a lease on
	// somebody else's project holding this identity's personal credential can
	// be named in the plan (Task 20400).
	GrantIDs []string `json:"grant_ids,omitempty"`
}

LeaseRef is one live secret lease held on the departing user's behalf.

type Leases

type Leases interface {
	// LiveLeases reports every lease the broker would still honour.
	LiveLeases() []LeaseRef
	// Release ends one lease and destroys any credentials minted under it.
	Release(id string)
}

Leases is the secret-broker surface offboarding needs. A hub with no broker passes nil, and leases are simply not part of the run.

type LocalClaude added in v0.0.5

type LocalClaude struct {
	// Manager holds this process's in-flight logins. Nil — a shell has none.
	Manager *claudecodeauth.Manager
	// Member labels the copies this process finds: its cluster member id, or
	// "" when it is the process running the offboarding.
	Member string
	// Root is where the homes are. Empty — production — is
	// claudecodeauth.HomeRootPath, from this process's own environment.
	// Set by tests that run two hub members in one process, where the
	// environment is shared and the trees must not be.
	Root string
	// OperatorShell marks the process as an operator's shell on the hub host —
	// `cloop hub user offboard` — rather than a hub acting on a request. The
	// host-execution policy governs what a hub runs on a request's behalf; a
	// person at a shell runs what they choose, and the logout revokes the
	// session at Anthropic, so a shell runs it whatever the policy — the same
	// on every host, rather than depending on which directory's config the
	// CLI happened to load. Never set by a hub.
	OperatorShell bool
	// OnLoginCancelled runs after a login in flight was killed, so a cluster
	// member can give up its claim on it.
	OnLoginCancelled func(ownerKey string)
	// Logout runs `claude auth logout` against one directory. Nil is
	// claudecodeauth.Logout; tests substitute it, because the real one talks
	// to Anthropic.
	Logout func(ctx context.Context, configDir string) error
	// LogoutTimeout bounds one logout. Zero is defaultLogoutTimeout.
	LogoutTimeout time.Duration
}

LocalClaude is the Claude login surface of one process: the homes under its own config directory, and the logins its Manager has in flight. A hub's cluster surface is one of these for itself plus every other member's answer; the CLI's is one of these for its own tree plus what members report over the bus.

func (LocalClaude) Inspect added in v0.0.5

func (c LocalClaude) Inspect(ownerKeys []string) ([]ClaudeHomeRef, []ClaudeUnreached, error)

Inspect implements ClaudeHomes for this process.

func (LocalClaude) Sever added in v0.0.5

func (c LocalClaude) Sever(ownerKeys []string, keep bool, actor, reason string) ([]ClaudeHomeResult, []ClaudeUnreached, error)

Sever implements ClaudeHomes for this process.

The order is the one that leaves nothing to come back: the login in flight first, because a `claude auth login` that completes after the directory is gone would write a fresh credential into a new one; then logout, while the credential it revokes still exists; then the directory.

type MembershipRef added in v0.0.5

type MembershipRef struct {
	Path string `json:"path"`
	Role string `json:"role"`
	// Identity is the spelling the grant was recorded under. Worth keeping: a
	// grant found under "sub:…" is the one an email-only sweep would miss.
	Identity string `json:"identity"`
}

MembershipRef is one project the identity was admitted to without owning it (Task 20366). The opposite disposition from ProjectRef: an owned project is reported and left alone, because deleting somebody's work is not offboarding, while a membership is removed outright — it is access to a project that belongs to someone else, and ending that access is the point.

type Options

type Options struct {
	// DB is the hub's control-plane database. Required.
	DB *statedb.DB

	// Identity is the email or subject the operator typed. Required, unless
	// Target is set.
	Identity string

	// Target is an identity its caller already resolved (Task 20415): a SCIM
	// directory, which knows the person by the claim it matches on and must
	// not be widened past an ambiguous match by the expansion an operator's
	// input gets. When set, Identity is only the label the trail records.
	Target *Target

	// Reason is recorded on every audit event. Required: an offboarding with
	// no stated cause is not reviewable, and the reviewability is the point.
	Reason string

	// Actor is who is doing this — an operator's email, or an OS user for CLI
	// runs.
	Actor string

	// Via distinguishes "cli" from "ui" in the trail.
	Via string

	// DryRun resolves and reports without writing anything.
	DryRun bool

	// Leases, Tasks and Projects are optional. A nil collaborator means that
	// surface is skipped, and the report says so rather than implying it was
	// clean.
	Leases   Leases
	Tasks    Tasks
	Projects Projects

	// Sessions is the live session authority. Strongly preferred where one
	// exists — see the interface doc.
	Sessions Sessions

	// Secrets and Claude reach what the identity keeps on the hub (Task
	// 20400). Optional like the others: nil means that part is not checked,
	// and the plan says so.
	Secrets Secrets
	Claude  ClaudeHomes

	// KeepCredentials is a legal hold. Access is severed exactly as without
	// it — grants revoked, requests withdrawn, logins in flight cancelled —
	// but the personal secrets and the Claude homes are kept as they are, and
	// the plan and the trail say what was kept.
	KeepCredentials bool

	// Now is the clock, swappable in tests.
	Now func() time.Time
}

Options configures a run.

type PersonalGrantRef added in v0.0.5

type PersonalGrantRef struct {
	ID         string    `json:"id"`
	SecretID   string    `json:"secret_id"`
	SecretName string    `json:"secret_name,omitempty"`
	Subject    string    `json:"subject"`
	ExpiresAt  time.Time `json:"expires_at,omitempty"`
}

PersonalGrantRef is one live grant over a personal secret of the identity.

Listed with its subject because a grant to a project somebody else owns is how a colleague's work loses the credential, and an operator approving the run should see that before it happens rather than hear about it afterwards.

type PersonalSecretRef added in v0.0.5

type PersonalSecretRef struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	Kind      string    `json:"kind"`
	Owner     string    `json:"owner,omitempty"`
	CreatedAt time.Time `json:"created_at,omitempty"`
}

PersonalSecretRef is one secret the identity owns personally.

type Plan

type Plan struct {
	Target   Target       `json:"target"`
	Sessions []SessionRef `json:"sessions"`
	Tokens   []TokenRef   `json:"tokens"`
	Glasses  []TokenRef   `json:"glasses"`
	Denies   []DenyRef    `json:"denies"`
	Leases   []LeaseRef   `json:"leases"`
	Tasks    []TaskRef    `json:"tasks"`
	Projects []ProjectRef `json:"projects"`

	// Memberships are projects this identity can reach without owning them
	// (Task 20366). Unlike Projects these are removed, in the credential
	// transaction.
	Memberships []MembershipRef `json:"memberships"`

	// Credentials is what the identity keeps on the hub — personal secrets,
	// the grants over them, pending requests, Claude logins (Task 20400).
	// Destroyed after the transaction, or kept under a legal hold.
	Credentials Credentials `json:"credentials"`

	// Hold is the legal hold placed on this identity, when there is one
	// (Task 20415): it keeps the stored credentials whatever the run was
	// asked, so an offboarding nobody ran by hand — the identity provider
	// deleting the account — honours it too.
	Hold *HoldRef `json:"hold,omitempty"`

	// Warnings name things the run cannot be sure about — most importantly
	// tokens whose owner binding did not decode, which cannot be proven *not*
	// to belong to this person.
	Warnings []string `json:"warnings,omitempty"`
}

Plan is the blast radius: everything a run would touch, resolved before anything is written. It is what --dry-run prints.

func BuildPlan

func BuildPlan(o Options) (Plan, error)

BuildPlan resolves the identity and collects every surface it holds.

func (Plan) Empty

func (p Plan) Empty() bool

Empty reports whether there is nothing to sever. A plan with only a deny binding is not empty: denying an identity that currently holds nothing is a legitimate pre-emptive offboarding.

type ProjectRef

type ProjectRef struct {
	Name  string `json:"name"`
	Path  string `json:"path"`
	Owner string `json:"owner,omitempty"`
}

ProjectRef is a project owned by the departing user. Reported, never deleted.

type Projects

type Projects interface {
	// Owned reports projects whose owner is any of the given owner keys.
	Owned(ownerKeys []string) ([]ProjectRef, error)
}

Projects enumerates project ownership.

func RegistryProjects

func RegistryProjects(entries []multiui.ProjectEntry) Projects

RegistryProjects reports ownership from an already-loaded project list.

It takes entries rather than calling multiui.Load itself because Load misses the hub's own WorkDir and anything passed via --projects; the dashboard assembles the full set separately, and an offboarding that swept only the registry file would under-report which projects need reassigning.

type Report

type Report struct {
	Plan
	DryRun bool      `json:"dry_run"`
	Actor  string    `json:"actor,omitempty"`
	Reason string    `json:"reason,omitempty"`
	At     time.Time `json:"at"`

	SessionsRevoked []string  `json:"sessions_revoked,omitempty"`
	TokensRevoked   []string  `json:"tokens_revoked,omitempty"`
	GlassesRevoked  []string  `json:"glasses_revoked,omitempty"`
	DeniesWritten   []string  `json:"denies_written,omitempty"`
	LeasesReleased  []string  `json:"leases_released,omitempty"`
	TasksStopped    []TaskRef `json:"tasks_stopped,omitempty"`

	// MembershipsRevoked are the project memberships actually removed, in
	// the same transaction as the credentials (Task 20366).
	MembershipsRevoked []MembershipRef `json:"memberships_revoked,omitempty"`

	// What became of the stored credentials (Task 20400). Under a legal hold
	// SecretsKept replaces SecretsDeleted, and each Claude copy reports Kept.
	GrantsRevoked     []PersonalGrantRef  `json:"grants_revoked,omitempty"`
	SecretsDeleted    []PersonalSecretRef `json:"secrets_deleted,omitempty"`
	SecretsKept       []PersonalSecretRef `json:"secrets_kept,omitempty"`
	RequestsWithdrawn []GrantRequestRef   `json:"requests_withdrawn,omitempty"`
	ClaudeResults     []ClaudeHomeResult  `json:"claude_results,omitempty"`

	// Failures is non-empty when a surface could not be severed. The run does
	// not abort on one: a lease that will not release is not a reason to leave
	// the sessions live. The caller decides what a partial run means, and both
	// front ends report it as an error with the detail attached.
	Failures []Failure `json:"failures,omitempty"`
}

Report is the outcome of a run. A dry run returns the Plan with DryRun set and every "-ed" field empty.

func Run

func Run(o Options) (Report, error)

Run performs the offboarding, or reports what one would do when DryRun is set.

func (Report) OK

func (r Report) OK() bool

OK reports whether every surface was severed.

type Secrets added in v0.0.5

type Secrets interface {
	// Owned reports the personal secrets owned by any of these owner keys,
	// and every live grant over them.
	Owned(ownerKeys []string) ([]PersonalSecretRef, []PersonalGrantRef, error)
	// Pending reports the requests still awaiting a decision that were filed
	// by any of these identities.
	Pending(requesters []string) ([]GrantRequestRef, error)
	// RevokeGrant revokes one grant.
	RevokeGrant(id, actor, reason string) error
	// DeleteSecret destroys one secret: its sealed material, and every grant
	// still pointing at it.
	DeleteSecret(id, actor, reason string) error
	// WithdrawRequest withdraws one pending request on behalf of its departed
	// requester, reporting false when it had already been decided.
	WithdrawRequest(id, actor, reason string) (bool, error)
}

Secrets is the secret-store surface: the identity's personal secrets, the grants spending them, and the requests it filed. A run with none skips these and the plan says they were not checked.

func BrokerSecrets added in v0.0.5

func BrokerSecrets(b *secretbroker.Broker) Secrets

BrokerSecrets adapts a secret broker.

It reads with the broker's unscoped listings — offboarding is one of the callers ListSecrets stays unscoped for — and writes only through the broker, so every revocation, deletion and withdrawal is audited by the code that audits them everywhere else, and a deleted secret leaves the tombstone a later refusal names it from.

func StoreSecrets added in v0.0.5

func StoreSecrets(db *statedb.DB) (Secrets, error)

StoreSecrets builds the secret surface over a control-plane database.

The broker it builds holds no key (secretbroker.WithoutKey). Destroying a credential removes rows and never reads one, so an offboarding works from a shell that does not carry CLOOP_SECRET_KEY — the emergency the CLI exists for — and the hub uses the same construction, so both front ends sever the same rows the same way. Its audit rows go to the control plane, where every secret action is homed.

type SessionRef

type SessionRef struct {
	ID        string    `json:"id"`
	Subject   string    `json:"subject,omitempty"`
	Email     string    `json:"email,omitempty"`
	IP        string    `json:"ip,omitempty"`
	IssuedAt  time.Time `json:"issued_at,omitempty"`
	ExpiresAt time.Time `json:"expires_at,omitempty"`
	// Kind is browser or cli: a terminal signed in with `cloop login` holds a
	// session like a browser does, and is severed with it (Task 20412).
	Kind string `json:"kind,omitempty"`
	// Username and OID are the preferred_username and oid claims the session
	// signed in with (Task 20415).
	Username string `json:"username,omitempty"`
	OID      string `json:"oid,omitempty"`
}

SessionRef is one dashboard session that will be, or was, ended.

type Sessions

type Sessions interface {
	// List reports every live session.
	List() ([]SessionRef, error)
	// Revoke ends one session and drops it from any in-process cache.
	// It reports whether a session was actually ended.
	Revoke(id, actor, reason string) (bool, error)
}

Sessions is the running hub's session authority — its authenticator.

Supplying it matters more than it looks. A hub whose session store is not durable (no sealing key, or no control-plane database) keeps sessions in process memory, where the control-plane table this package otherwise reads is simply empty. An offboarding driven off that table would report "0 sessions" and leave the person signed in — the worst possible answer to "are they out?". Going through the authenticator is also what drops the session from its 30-second read cache, which deleting the row underneath it does not.

A caller with no running hub (the CLI, on a host whose hub may be down) passes nil and the control-plane table is used instead, which is correct there: there is no process holding memory sessions to ask.

type Target

type Target struct {
	// Input is what the operator typed, preserved for the audit trail.
	Input string `json:"input"`

	// Key is the canonical owner key: lowercased email, or "sub:"+Subject.
	// This is what project ownership rows are recorded under.
	Key string `json:"key"`

	// Subjects and Emails are every identifier found to belong to this person.
	// Emails are lowercased; subjects are opaque and compared exactly.
	//
	// Always serialised, even when empty: a front end rendering "resolved to"
	// needs to distinguish "no subject" from a field that was omitted, and
	// omitempty would make an identity this hub has never seen indistinguishable
	// from a decoding bug.
	Subjects []string `json:"subjects"`
	Emails   []string `json:"emails"`

	// Usernames and OIDs are preferred_username and Entra oid values that
	// address this person too (Task 20415). Only a SCIM directory matching on
	// one of those claims puts them here: they are what the identity provider
	// deactivated, and a deny binding on them refuses a sign-in that carries
	// neither a known subject nor a known email. Lowercased.
	Usernames []string `json:"usernames,omitempty"`
	OIDs      []string `json:"oids,omitempty"`
}

Target is the resolved identity: every identifier that turned out to address the same person.

Resolution matters because the operator types one thing — usually an email — and the surfaces are keyed inconsistently. Sessions carry a subject and an email; a token's owner may carry only a subject. Matching literally on what was typed would leave the subject-keyed token behind, which is precisely the credential an operator would never think to look for.

func ResolveTarget added in v0.0.5

func ResolveTarget(db *statedb.DB, seeds []string) (Target, error)

ResolveTarget expands seeds — emails, and subjects spelt "sub:<subject>" — into every identifier that addresses the same person, as an operator's input is expanded (Task 20415). A SCIM directory calls it with the identifiers the identity provider vouched for, and checks the result for an ambiguity before acting on it.

func (Target) Label

func (t Target) Label() string

Label is the friendliest name for this identity.

func (Target) OwnerKeys

func (t Target) OwnerKeys() []string

OwnerKeys returns every owner-key spelling this identity may be stored under.

type TaskRef

type TaskRef struct {
	ProjectPath string `json:"project_path"`
	ProjectName string `json:"project_name,omitempty"`
	ID          int    `json:"id"`
	Title       string `json:"title,omitempty"`
	Status      string `json:"status,omitempty"`

	// Attempt names the execution observed, so a kill filed here cannot land on
	// a later attempt of the same task (Task 20203). Not serialised: it is an
	// internal fencing token, not something an operator reads.
	Attempt string `json:"-"`
}

TaskRef is one in-flight task running in a project this identity owns.

type Tasks

type Tasks interface {
	// Running reports in-flight tasks for one project path.
	Running(projectPath string) ([]TaskRef, error)
	// Stop requests that the named execution be killed. The ref carries the
	// attempt token, so the request cannot land on a later attempt.
	Stop(t TaskRef, actor, reason string) error
}

Tasks is the per-project execution surface. A hub that cannot reach project databases passes nil.

func LocalTasks

func LocalTasks() Tasks

LocalTasks reads and aborts tasks through each project's own state database, which is where a project's plan lives — the hub's control plane does not hold other projects' tasks.

type TokenRef

type TokenRef struct {
	ID        string    `json:"id"`
	Name      string    `json:"name,omitempty"`
	Prefix    string    `json:"prefix,omitempty"`
	Kind      string    `json:"kind,omitempty"`
	Owner     string    `json:"owner,omitempty"`
	Roles     []string  `json:"roles,omitempty"`
	ExpiresAt time.Time `json:"expires_at,omitempty"`
}

TokenRef is one API token or glasses link.

Jump to

Keyboard shortcuts

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