workspace

package
v0.19.1 Latest Latest
Warning

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

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

Documentation

Overview

Package workspace mounts additional gadak profiles under /w/<name>/ and lists them at GET /api/v1/workspaces. Shared by `gadak serve` and the desktop app.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CreateHandler added in v0.19.0

func CreateHandler() http.HandlerFunc

CreateHandler answers POST /api/v1/workspaces by seeding a standalone workspace profile or registering a paired one (kind "paired" + offer). The standalone seed is the shared one: config write, default issue type, mirror fill. A fill that fails is logged, not failed (SeedStandalone contract — the workspace exists and writes work; the next sync fills). The paired path is createPaired's.

func ListHandler

func ListHandler() http.HandlerFunc

ListHandler answers GET /api/v1/workspaces with the primary profile first (active:true) and every named profile after. Credentials never appear.

func Prefix added in v0.15.0

func Prefix(profile, served string) string

Prefix is the path segment that selects `profile` on a server whose primary profile is `served`: "" when they are the same mirror, "/w/<name>" when the profile has to be reached as a workspace mount.

Callers join it ahead of the rest of the path, so the empty string is the correct answer for the primary profile and not a missing value.

func ProfileEq added in v0.15.0

func ProfileEq(got, want string) bool

ProfileEq reports whether two profile names mean the same mirror. The primary profile answers to both "" and "default" — the config layer stores it empty, humans and URLs write it out — so a comparison that misses this sends `gadak views open` to /w/default on the very server it is talking to.

func ProfileExists

func ProfileExists(name string) bool

ProfileExists reports whether name is a directory under profiles/ (disk is the source of truth for workspace mounts).

func RemoveHandler added in v0.19.0

func RemoveHandler(reg *Registry) http.HandlerFunc

RemoveHandler answers DELETE /api/v1/workspaces/{name}?yes=1&destroy_origin=1. Refusals map from Remove's typed errors; detail reuses the CLI refusal wording so both surfaces teach the same next move.

Types

type Entry

type Entry struct {
	// Handler is the concrete server handler, not http.Handler: this entry
	// owns its lifetime, and Close has to be able to stop the background sync
	// before the mirror it writes to is closed (GDK-270).
	Handler *server.Handler
	DB      *store.DB
	Cfg     *config.Config
	// contains filtered or unexported fields
}

Entry is one opened workspace mirror (handler + DB + config).

type InvalidNameError added in v0.19.0

type InvalidNameError struct {
	Name string
	Err  error
}

InvalidNameError wraps config.DirFor's refusal (separator, "..", ...). DirFor stays the single owner of name validation; this type only makes the refusal distinguishable from other failures for the HTTP mapper.

func (*InvalidNameError) Error added in v0.19.0

func (e *InvalidNameError) Error() string

func (*InvalidNameError) Unwrap added in v0.19.0

func (e *InvalidNameError) Unwrap() error

type KindUnreadableError added in v0.19.0

type KindUnreadableError struct {
	Name       string
	ConfigPath string
	Err        error
}

KindUnreadableError is a config.json that will not parse. An unreadable config is a refusal, not a guess: if this might be standalone, its persist is the only copy of that tracker and a plain --yes must not destroy it.

func (*KindUnreadableError) Error added in v0.19.0

func (e *KindUnreadableError) Error() string

func (*KindUnreadableError) Unwrap added in v0.19.0

func (e *KindUnreadableError) Unwrap() error

type ListEntry

type ListEntry struct {
	Name     string   `json:"name"`
	Site     string   `json:"site,omitempty"`
	Projects []string `json:"projects,omitempty"`
	Active   bool     `json:"active,omitempty"`
	Error    string   `json:"error,omitempty"`
}

ListEntry is one item in GET /api/v1/workspaces. Never carries Token or Email — only site URL and project keys for the picker.

type NeedsDestroyOriginError added in v0.19.0

type NeedsDestroyOriginError struct {
	Name    string
	Persist string
	CmdHint string
}

NeedsDestroyOriginError: standalone with an existing persist, but the caller did not opt into destroying the only copy of that tracker.

func (*NeedsDestroyOriginError) Error added in v0.19.0

func (e *NeedsDestroyOriginError) Error() string

type NeedsYesError added in v0.19.0

type NeedsYesError struct {
	Name       string
	CmdHint    string
	Standalone bool
}

NeedsYesError: the caller asked about removal without committing to it. Standalone picks the wording that says there is no origin data to protect (no persist exists); connected picks the one that says the origin keeps everything.

func (*NeedsYesError) Error added in v0.19.0

func (e *NeedsYesError) Error() string

type NotFoundError added in v0.19.0

type NotFoundError struct {
	Name        string
	ProfilesDir string
	CmdHint     string
}

NotFoundError is a name with no profile directory under profiles/.

func (*NotFoundError) Error added in v0.19.0

func (e *NotFoundError) Error() string

type Registry

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

Registry lazy-opens profile mirrors on first /w/<name>/ request and closes them when the process exits.

func New

func New() *Registry

New returns an empty workspace registry.

func (*Registry) Close

func (r *Registry) Close()

Close closes every opened workspace DB. Safe to call once at shutdown. In-flight constructors are waited on (they see closed and close their own object) so this never closes a half-built entry.

func (*Registry) EnsureOrigins added in v0.18.0

func (r *Registry) EnsureOrigins()

EnsureOrigins retries ownership for every opened entry. The rescan loop calls this so a failed bind heals within watchRescanInterval.

func (*Registry) EnsureWatch added in v0.13.0

func (r *Registry) EnsureWatch(name string) bool

EnsureWatch starts this profile's Watch if WatchAll has armed the owner, the profile now has a credential, and no loop is already running. Idempotent. LoadFor and store.Open run with r.mu released.

func (*Registry) EnsureWatches added in v0.13.0

func (r *Registry) EnsureWatches() []string

EnsureWatches is the single "credential appeared" scan: start a Watch for every non-primary profile that now has a credential and does not already have a loop. Returns the names that were started this call.

func (*Registry) Evict added in v0.19.0

func (r *Registry) Evict(name string)

Evict closes one opened workspace entry and forgets it, so a removal (DELETE /api/v1/workspaces/{name}) does not delete a profile directory out from under an open SQLite handle — the /w/<name>/ mount would keep serving a zombie mirror of files that no longer exist. No-op when the entry is not open. The next Get for the name reconstructs it lazily, so a refused removal costs one reopen, nothing else. Lock discipline matches Close: the map mutation is under mu, closeEntry (IO) runs after release.

func (*Registry) Get

func (r *Registry) Get(name string) (*Entry, error)

Get returns a cached workspace entry, opening the profile on first use.

func (*Registry) Handler

func (r *Registry) Handler(spa http.Handler, version string) http.HandlerFunc

Handler routes /w/<name>/… to the named profile's API, config, healthz, or SPA. Invalid names and unknown profiles answer 404. version is reported in healthz (same field as the primary /healthz).

func (*Registry) WatchAll

func (r *Registry) WatchAll(ctx context.Context, primary string, logf func(string)) []string

WatchAll arms the credential-appearance owner and starts a loop for every profile that already has a credential, skipping primary (its caller already watches that one). Returns the profile names that got a loop. Subsequent credentials (onboarding connect, or a later EnsureWatches / rescan) start through the same owner and cannot double-start. Log receives status lines; never pass secrets into it — only profile names are logged from here.

type RemoveResult added in v0.19.0

type RemoveResult struct {
	Removed         string
	Kind            string
	OriginDestroyed bool

	// The success lines below need more than the three fields above:
	// Dir is the removed directory, Persist the destroyed persist path
	// (empty when none), Standalone the pre-removal kind reading.
	Dir        string
	Persist    string
	Standalone bool

	// Advisory materials.
	PairedSelector string
	ClearedStored  bool
	WasActive      bool
}

RemoveResult is what a successful removal did. The CLI shell reassembles its output lines from these fields; the HTTP handler serialises them. The core itself prints nothing except the stored-default warning, which was stderr-bound before the extraction and stays that way (both surfaces want it on stderr, and a silent failure to clear a now-dangling default is worse than a duplicated line).

func Remove added in v0.19.0

func Remove(name string, yes, destroyOrigin bool, cmdHint string) (RemoveResult, error)

Remove deletes the named workspace profile. Refusals are typed so each surface (CLI text, HTTP JSON) can answer with its own shape without re-deriving the decision; every refusal's Error() is the CLI wording. cmdHint is the command spelling the "to proceed" lines name ("workspaces" or "profiles" for the CLI aliases, "workspaces" for HTTP).

func (RemoveResult) Advisories added in v0.19.0

func (res RemoveResult) Advisories() []string

Advisories returns the lines that are true for every successful removal, whatever the kind: the pairing hint for a workspace that paired with a home serve, the stored-default cleanup note, the was-active retarget hint, and the serve-staleness warning (there is no reliable detector for a running serve since the persist lock was removed, GDK-936). One owner of the wording — the CLI prints each line, the HTTP handler returns them as the JSON "advisories" array.

type RootRefusalError added in v0.19.0

type RootRefusalError struct {
	Name string
	Home string
}

RootRefusalError is the ""/"default" refusal: the unnamed workspace is the home directory itself, not a profile under it — removing "the default workspace" must not be rm -rf on the whole home by another door.

func (*RootRefusalError) Error added in v0.19.0

func (e *RootRefusalError) Error() string

Jump to

Keyboard shortcuts

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