onboarding

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseInviteCode

func ParseInviteCode(raw string) (code, origin string, err error)

ParseInviteCode extracts the invite code, and the backend origin it names, from whatever form the member pasted.

Three forms are accepted: the bare code, the desktop deep link, and the join-page URL whose fragment carries the code (a fragment never reaches server logs). Only the https form knows where the Project lives, so only it returns an origin; the other two return an empty one, meaning "the backend the caller selected". This is what lets a member on a purely local profile paste a link and join a team Project on a server this Mac has never used, without being asked which server that was.

func ValidateAPIOrigin

func ValidateAPIOrigin(raw string) (string, error)

ValidateAPIOrigin canonicalizes a backend origin a member supplied.

The rule is exactly hosted.New's, because that is the client every later call goes through: an HTTPS origin, or loopback HTTP for a backend running on this Mac, with no path, query, fragment, or userinfo. Checking it here means the member is told at the field rather than by the first request that fails.

Types

type CredentialStore

type CredentialStore interface {
	Put(context.Context, string, string) error
	Get(context.Context, string) (string, error)
	Delete(context.Context, string) error
}

type Options

type Options struct {
	ConfigRoot, RepositoryRoot, ProjectLabel, DeviceLabel, AppVersion string
	// DisplayName is optional; empty means the member has not chosen one yet.
	DisplayName string
	// SkipInvite creates the Project without minting a one-use invite. A local
	// Project has no second member to hand one to, and offering a code is how a
	// screen implies that inviting somebody is the next step (ADR-072).
	SkipInvite bool
	// ProjectID reuses an identifier the caller already holds rather than being
	// issued a new one. Empty - which is every path today - means the server
	// issues one, and that is what an ordinary creation wants. It is set when a
	// Project is being re-created on a second backend and has to stay the same
	// Project: its identifier is salted into every repository fingerprint and
	// stamped on every event envelope the device has queued, so changing it
	// would orphan all of that evidence from the Project it describes.
	ProjectID string
}

type ResetOutcome

type ResetOutcome struct {
	Status            hosted.CredentialStatus
	BackendID         string
	APIBaseURL        string
	DeviceID          string
	ClearedWorkspaces int
	CredentialDeleted bool
}

ResetOutcome describes what a reset changed on this device.

func ResetAll

func ResetAll(ctx context.Context, configRoot string, force bool) ([]ResetOutcome, error)

ResetAll runs Reset for every backend on the profile, which is what a member asking to forget this Mac entirely means. It reports each backend's outcome and stops at the first refusal, so a working enrollment is never erased on the way to a broken one.

type Result

type Result struct {
	ProjectID, DeviceID, WorkspaceID, WorkstreamID string
	JoinCode, DashboardTicket                      string
}

type Service

type Service struct {
	Backend  config.Backend
	Client   func(token string) (API, error)
	Creds    CredentialStore
	Register func(context.Context, string, string, string, config.Workspace) error
	// Rebind moves an existing Project onto this flow's backend. Like Register
	// it is a seam: the desktop points it at the running service so a move takes
	// effect without a restart, and it falls back to editing the profile.
	Rebind func(ctx context.Context, configRoot, projectID, apiBaseURL, deviceID string) error
}

Service is one onboarding flow against one backend. The backend travels with the flow rather than with the profile: a Mac holds a device identity per backend (ADR-069, ADR-074), so "which server" and "which credential" are one question and are answered together.

func New

func New(backend config.Backend) Service

func (Service) Create

func (s Service) Create(ctx context.Context, options Options) (Result, error)

func (Service) CreateAdditional

func (s Service) CreateAdditional(ctx context.Context, options Options, deviceID, token string) (Result, error)

CreateAdditional creates another Project for a device that is already enrolled in the local profile. The existing credential is deliberately reused: one per-user service has one device identity across its Projects. Unlike first enrollment, a local registration failure must never revoke the shared device and strand its existing Projects.

func (Service) CreateOnNewBackend

func (s Service) CreateOnNewBackend(ctx context.Context, options Options) (Result, error)

CreateOnNewBackend creates a Project on this flow's backend, whether or not the profile has used that backend before.

A backend the profile has never seen gets a device identity minted for it; one it already has an identity for reuses that identity, because a second credential for the same server would strand the Projects the first one holds. Both cases are one member action - "add a Project on this server" - so the choice is made here rather than in each caller.

func (Service) CredentialState

func (s Service) CredentialState(ctx context.Context, configRoot string) (hosted.CredentialStatus, config.Config, error)

CredentialState reports whether the credential stored for this flow's backend is still accepted by it. A backend this profile has no device identity for is reported as OK because there is nothing that could have been rejected.

It is per backend, not per profile: one revoked team Project says nothing about the local Project beside it.

func (Service) Join

func (s Service) Join(ctx context.Context, options Options, joinCode string) (Result, error)

func (Service) JoinAdditional

func (s Service) JoinAdditional(ctx context.Context, options Options, deviceID, token, joinCode string) (Result, error)

JoinAdditional redeems an invite for a device that is already enrolled in the local profile, adding a second Project without a second device identity.

It is the join counterpart of CreateAdditional and shares its rule: the existing credential is reused, and a local registration failure must never revoke the shared device and strand the Projects this Mac already has. That is why it does not route through Join, whose rollback revokes the device it just created - here that device is the one holding every other Project.

func (Service) JoinOnNewBackend

func (s Service) JoinOnNewBackend(ctx context.Context, options Options, joinCode string) (Result, error)

JoinOnNewBackend redeems an invite on this flow's backend, minting a device identity for a backend the profile has never used and reusing the one it has otherwise. Joining a friend's team Project from a purely local profile is the first case, and it is the common one.

func (Service) Promote added in v0.1.2

func (s Service) Promote(ctx context.Context, options Options, projectID string) (Result, error)

Promote moves a Project that lives on one backend onto this flow's backend, keeping its identifier, its repositories, and its agent bindings.

This is how a local Project becomes a shared one. Nothing about the repository changes and nothing is re-enrolled: the Project is re-created on the new backend under the identifier it already has, this device takes an identity there, and the profile is repointed. The coordination record does not travel - the new backend learns the current state from the next scan - which is what keeps this a change of address rather than a migration.

It is written to be safe to run twice. Every step short of the rebind is either idempotent or recoverable, because the failure that matters here is the one that happens after the new Project exists.

func (Service) Reset

func (s Service) Reset(ctx context.Context, configRoot string, force bool) (ResetOutcome, error)

Reset forgets this device's identity on one backend so the member can enroll against it again. It deletes that backend's stored credential and removes the backend, its Projects, and the repositories registered to them. Every other backend on the profile is untouched, which is the whole point of binding Projects to backends: a revoked team Project must not take the local Project down with it.

It refuses unless that backend has actually rejected the credential. Being offline is not being locked out, and erasing a working enrollment cannot be undone. force exists for support and headless recovery, where the operator has already established that the enrollment is dead.

Jump to

Keyboard shortcuts

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