Help us shape the future of Go! Take the 2026 Go Developer Survey

setup

package
v0.15.1 Latest Latest
Warning

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

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

Documentation

Overview

Package setup is `basecamp connect setup`'s half of the connector (plan step 16): connect.json — its schema, where it lives, how it is written and how it is refused — and the checks that can run before the connector does.

connect.json

connect.json is the only local authority for which Basecamp projects may drive the agent and for who may drive it. It is local policy: nothing read from Basecamp serves a project or widens trust. Its admission part (trust and projects) is exactly what admission.ParsePolicy reads; the rest belongs to dispatch.

No directory is associated with a project. The connector runs where it was started, and a task that needs a clone or a directory of its own is the agent's business to make.

Because the file is the trust anchor, a copy another user could have written is not one to act on. Save writes it owner-only (0600, in 0700 directories) and atomically; Load refuses a file or directory another user owns or can write, a symlink, and anything that is not a regular file. A policy the connector would misread is refused at both ends.

What is not here

The agent's credential. Setup runs on a profile that already holds it (connected with `basecamp auth agent connect`, or a bot user logged in with `basecamp auth login --expect-identity`), and never stores, replaces or removes one. connect.json records only the identity that credential proved, so a later run can refuse a profile that has since been pointed at someone else.

Index

Constants

View Source
const (
	StatusPass = "pass"
	StatusFail = "fail"
	StatusWarn = "warn"
	StatusSkip = "skip"
)

Check statuses, the doctor command's vocabulary.

View Source
const (
	// KindAgent is an Agent person holding a client_credentials profile, the
	// connection ceremony's result.
	KindAgent = "agent"
	// KindBotUser is a person login (a device-flow bot user) pinned by
	// --expect-identity: version 1's path.
	KindBotUser = "bot_user"
)

Credential kinds recorded for the agent.

View Source
const AckContentMax = 16

AckContentMax is the longest boost Basecamp takes, counted in runes as `basecamp boost create` counts it.

View Source
const DefaultAckContent = "👀"

DefaultAckContent is the boost the connector acknowledges with unless connect.json says otherwise.

View Source
const FileName = "connect.json"

FileName is connect.json's name inside its profile directory.

View Source
const MaxTicketLifetime = time.Hour

MaxTicketLifetime bounds a ticket's reported lifetime. The server issues about two minutes; a lifetime past an hour is not one the contract allows.

View Source
const PersonableAgent = "Agent"

PersonableAgent is the personable type Basecamp gives an Agent person.

View Source
const Version = 1

Version is the connect.json schema version this package writes and reads.

Variables

View Source
var ErrLockHeld = errors.New("the lock is held by another process")

ErrLockHeld reports a lock another process already holds.

View Source
var ErrLockUnavailable = errors.New("this host cannot lock the connector's policy")

ErrLockUnavailable reports a host that cannot take the setup lock at all: a filesystem without flock, a lock file this user does not own. Setup's guarantee is the lock, so it refuses rather than run without one.

View Source
var ErrMalformedTicket = errors.New("the stream ticket response carries no ticket, no URL, or no lifetime within bounds")

ErrMalformedTicket reports a mint that answered without a ticket or URL.

View Source
var ErrNotPrivate = errors.New("connect.json is not private to this user")

ErrNotPrivate reports a connect.json, or a directory holding it, that someone other than this user could have written.

View Source
var ErrSetupRunning = errors.New("another command holds this profile's connector policy")

ErrSetupRunning reports the per-profile policy lock held by somebody else. Three callers take it, and which one is holding decides whether a wait is worth anything: `connect setup` across its whole load-change-save, network checks included; `connect redispatch`, a one-shot operator command, across one read and one ledger write; and the running connector, for the moment it authorizes a launch. See the block above LockWait.

View Source
var LockWait = 10 * time.Second

Who takes this lock, and for how long. Two holders, and a third form for the one caller that must never wait:

  • `connect setup` (Lock) holds it across its whole load, change and save, network checks included. Seconds, and on a slow or unreachable account longer than that.
  • `connect redispatch` (Lock) holds it across one file read and one ledger transaction, then lets go before it re-runs a prerequisite against Basecamp. Milliseconds. It is a one-shot operator command, not the connector.
  • the running connector (TryLock) takes it once per launch, across one file read and one ledger transaction, and never waits: a pass that cannot take it dispatches nothing and asks again on its next tick.

Setup is the only one that holds it across a network call. Other locks are taken UNDER this one and never the other way about: the ledger's SQLite locks on a redispatch and on a launch, and the credential key's lock on setup's final write. That one order is what keeps this from deadlocking, and a fourth caller that waits on this while holding something a current holder waits on is how it stops being one. There is no such caller today.

LockWait bounds Lock's wait for a holder to finish. The connector takes this lock too now, and what it does under it is one file read and one SQLite transaction — bounded by the ledger's own five-second busy retry — so a setup that refused the instant a dispatcher pass overlapped it would fail for no reason a person could act on. Ten seconds is that worst case with room, and a setup that waits it out reports a real holder.

A variable so tests can shorten it.

Functions

func CheckPrivateDir

func CheckPrivateDir(dir string) error

CheckPrivateDir holds a directory to the rules a private file's directory must meet — every ancestor this user's own and unwritable by anyone else, and the directory itself private — without opening anything inside it.

It exists for a second open of a file this process already holds: the file must not be opened again (POSIX drops a process's locks on any close of it), but the path it is reached through can still be vetted.

func CheckPrivateFile

func CheckPrivateFile(path string) error

CheckPrivateFile holds an existing file to EnsurePrivateFile's rules without creating anything: every directory on the way must be this user's alone, the file must not be a symlink, and — inspected through the open descriptor — it must be this user's own file that nobody else can read. A missing file, or a missing directory, is an error satisfying errors.Is(err, os.ErrNotExist).

func EnsurePrivateDir

func EnsurePrivateDir(dir string) error

EnsurePrivateDir creates dir owner-only when it is missing, and refuses it when dir — or any directory above it — could be changed by someone else.

It is the same check connect.json gets, exported for the state the running connector keeps beside it: the instance lock and the ledger live under the same config root, and a directory another local user can write is a directory where a lock file can be swapped for one that excludes nobody.

Only the last component is created. Everything above it must already exist and already be trustworthy, which is what makes the refusal meaningful.

func EnsurePrivateFile

func EnsurePrivateFile(path string) error

EnsurePrivateFile creates path owner-only when it is missing, and refuses it when it is not this user's own regular file reached through directories only this user can change.

It is stricter than the check connect.json gets in one way: a file another user can merely READ is refused too. It exists for the connector's ledger, which holds the account's feed positions — resumable tokens, so credentials — and there a readable file is already the leak.

What is validated is the descriptor, not the name: the file is opened without following symlinks and inspected through that open file, so the thing checked is the thing the caller will go on to use. Creation is exclusive for the same reason — O_CREAT|O_EXCL refuses a symlink outright rather than following it somewhere else.

func ErrorText

func ErrorText(err error) string

ErrorText is the one way setup turns a read's error into text, for every check and every refusal. An HTTP answer is reduced to its status: the SDK builds an error's message from the response body, which the server controls, and a ticket mint's body can carry the ticket and the feed URL. Only an error that never reached a server keeps its text, reduced to one line.

func Lock

func Lock(ctx context.Context, path string) (unlock func(), err error)

Lock takes the per-profile setup lock beside connect.json, so a load, a change and a save cannot interleave with another holder's. It waits up to LockWait, or until ctx ends, and then reports ErrSetupRunning or ctx's own error.

The wait honors ctx, and a wait that ends takes nothing: a person who pressed Ctrl-C during contention is not made to sit out the bound, and a command whose context has expired does not go on to acquire a lock and act under it (Copilot on #771). What a caller that cannot get the lock gets is a refusal, never a fall-through: "the policy could not be checked" is not permission to change it.

The wait is for the connector's hold, which is milliseconds. It cannot tell one holder from another, though, so a second `connect setup` whose predecessor finishes inside LockWait is now queued rather than refused — two setups run one after the other where they used to be one setup and one refusal. That is a real change, and the price of not failing a setup that happens to overlap a dispatcher pass. Nothing is written twice by it: each one loads, changes and saves under the lock in turn, which is what the lock is for. A second setup whose predecessor is still going after LockWait is still refused.

func Path

func Path(configDir, profile string) (string, error)

Path is where a profile's connect.json lives: under the CLI's config directory, one directory per profile, since a connector runs as one profile's agent.

func Save

func Save(held auth.HeldCredential, path string, f File) error

Save validates f and writes it to path atomically, owner-only, while the caller holds the lock on the credential the file is about to name. The proof is auth.HeldCredential, and auth.Held is what reads it: a value this package could fabricate — a struct embedding the interface — is not one auth made, and is refused.

The proof must name this file's own profile: a lock held on another profile's credential is not a lock on this one. The proof says the lock was held when Save checked it. What happens after that is the write's own: one atomic rename, so a connect.json exists whole or not at all.

held is only proof; what is written comes from f, which the caller has already checked against the credential (see File.VerifyAgent).

func SetOperator added in v0.13.0

func SetOperator(t *admission.Trust, id int64, requested bool) error

SetOperator records the operator. When the allowlist then names nobody besides them, the assignment opt-in has nobody left to cover. Kept from an earlier run, it is dropped: the operator assigns without one, and keeping it would make the file refuse to validate over a change setup itself made. Asked for in this run (requested), it is refused, so the person learns it does nothing rather than finding it quietly off.

func TryLock

func TryLock(path string) (unlock func(), err error)

TryLock takes the lock if it is free and reports ErrSetupRunning if it is not, without waiting. It is the dispatcher's form: a pass that cannot take the lock authorizes nothing and tries again on its next tick, which costs one tick of latency. Waiting instead would park the dispatcher behind a `connect setup`'s network checks, and a dispatcher that blocks on a file another process writes is its own hazard. It takes no context because it does not wait: there is nothing to cancel.

func TryLockPrivate

func TryLockPrivate(path string) (unlock func() error, err error)

TryLockPrivate takes an exclusive lock on path without waiting, having first established that the lock is a lock: the directory holding it is this user's alone, and the file itself is this user's own regular file rather than a symlink or something another user left there. A lock on a file two processes can be pointed at different inodes excludes nobody.

It reports ErrLockHeld when another process holds it, which is an answer rather than a failure. Every other error means the lock could not be established, and the caller must refuse rather than carry on unlocked.

func UsableTicket

func UsableTicket(t *basecamp.StreamTicket) bool

UsableTicket reports whether a minted ticket is one the connector could dial: a credential, a URL, and a lifetime above zero and at most MaxTicketLifetime.

func ValidClass

func ValidClass(class string) bool

ValidClass reports whether a class is one connect.json can carry: it is written onto every stdout pointer line, so it stays a short plain token.

Types

type AckBoost added in v0.14.0

type AckBoost struct {
	Off     bool   `json:"off,omitempty"`
	Content string `json:"content,omitempty"`
}

AckBoost is the connector's acknowledgement of a request: a boost on the request's recording with Content, given before the request is handed off, or none at all when Off, which leaves acknowledging to the session.

func (AckBoost) Validate added in v0.14.0

func (a AckBoost) Validate() error

Validate refuses an acknowledgement the connector could not give: one both off and with content, or a boost Basecamp would refuse.

type Agent

type Agent struct {
	// PersonID is the agent's Person id in AccountID. Admission takes the
	// agent's id from the profile's verified identity at run time; this is
	// what that identity is checked against.
	PersonID int64 `json:"person_id"`
	// Kind is KindAgent or KindBotUser.
	Kind string `json:"kind"`
	// IdentityID is the bot user's account-independent identity, the value
	// --expect-identity pins. Zero for an Agent person, which has none.
	IdentityID int64 `json:"identity_id,omitempty"`
}

Agent is the identity the agent profile authenticated as at setup.

type Changes

type Changes struct {
	// Trust is the trust mode, "" to keep the file's.
	Trust admission.TrustMode
	// Allow replaces the allowlist when non-empty: the people trusted as
	// operators besides the operator. It implies allowlist mode unless the
	// same run asks for project mode, which admits the project's members as
	// participants beside them.
	Allow []int64
	// AllowAssignments turns the allowlist's assignment opt-in on or off.
	// Nil keeps the file's, except in a run that passes Allow, which
	// restates the opt-in with the list it covers: off unless set here.
	// SetOperator drops a kept one that is left covering nobody.
	AllowAssignments *bool

	// AllowAgents adds agents allowed to wake this one by @mentioning it;
	// DisallowAgents removes them. The list is kept across runs, like the
	// served projects.
	AllowAgents    []int64
	DisallowAgents []int64
	// AgentThreadCap and AgentDailyCap set the agent loop caps; nil keeps
	// the file's. Zero restores the default.
	AgentThreadCap *int
	AgentDailyCap  *int

	// AllowInPing lets people reach the agent in one Ping alone, as
	// participants there; DisallowInPing takes them out. Kept across runs,
	// like the allowed agents.
	AllowInPing    []PingPerson
	DisallowInPing []PingPerson

	// Serve are the project (bucket) ids to serve. A project already served
	// keeps its class and watch_completions.
	Serve []int64
	// Classes sets a served project's class; an empty class clears it.
	Classes map[int64]string
	// WatchCompletions turns watch_completions on (true) or off (false) for
	// a served project.
	WatchCompletions map[int64]bool
	// Remove stops serving projects.
	Remove []int64

	// AckBoost sets how the connector acknowledges a request; nil keeps the
	// file's.
	AckBoost *AckBoost
}

Changes is what one setup run asks to change. A zero field leaves the file's value alone, so setup can be run again to serve another project without restating everything else.

type Check

type Check struct {
	Name    string `json:"name"`
	Status  string `json:"status"`
	Message string `json:"message"`
	Hint    string `json:"hint,omitempty"`
}

Check is one setup check's result.

func ProjectChecks

func ProjectChecks(ctx context.Context, r Reader, f File, firstSetup bool) []Check

ProjectChecks reads each served project the way admission will, as the agent: the project, and its people (project trust mode's membership read).

firstSetup says there is no connect.json yet. Serving no project is two different questions depending on it, and answering both with one rule left the operator no way to turn the agent off through the interface that turned it on: an explicit --unserve of the last project produced a valid empty file that the readiness check then refused to write (Copilot on #765). A first setup that serves nothing has not been set up, and is refused. Withdrawing the last project is a thing an operator may mean — it is how you stop the agent working anywhere without editing the trust anchor by hand — so it is written, and warned about.

func ScopeCheck

func ScopeCheck(oauthType, granted string) Check

ScopeCheck classifies the scope a credential was granted, as stored from the server's answer. Only "full" lets the agent reply and acknowledge. Launchpad grants carry no scope and are full access. Anything else — read, empty on a credential that should carry one, or a value this CLI does not know — is insufficient, never assumed full.

func TicketCheck

func TicketCheck(ctx context.Context, r Reader, kind string) Check

TicketCheck mints a stream ticket as the agent.

func VerifyPings added in v0.14.0

func VerifyPings(ctx context.Context, r Reader, pings, recorded map[int64]admission.PingTrust, agentID int64) []Check

VerifyPings checks everyone connect.json will let into one Ping, as the agent sees that Ping, since admission reads it as the agent: the agent is in it, so is the person, and the person is neither the agent, an Agent, nor a client of the account. One read per Ping.

A person recorded there already who cannot be confirmed now (the Ping cannot be read, or they have left it) is a warning, as for the allowlist: a later setup run should not fail over an entry it was not asked to change. An Agent or a client fails whether recorded or not: no agent authorizes, and clients are what project trust refuses too.

func VerifyTrust

func VerifyTrust(ctx context.Context, people Reader, t Trust, agentID int64) []Check

VerifyTrust is the one check of the trust anchor every setup goes through before connect.json is written. A failed result means nothing may be written. The properties, each checked here:

  1. The operator and every allowlisted id are Person ids (positive).
  2. None of them is the agent itself: the agent's own id never authorizes.
  3. Each was verified as a person in the agent's account: the operator by their own profile's credential, or else by reading them; every allowlisted id by reading it. A read that is refused or fails refuses the id, unless connect.json already records that same person in that same role, which was verified when it was written.
  4. None of them reads back as an Agent person: agents never authorize.
  5. None of them reads back as a client of the account.
  6. A read answers for the id that was asked for.

people reads persons in the account; pass a reader built on the operator's own credential when there is one, since Basecamp refuses person reads to an Agent identity today.

type Duration

type Duration time.Duration

Duration is a time.Duration written as Go's duration string ("45m0s").

func (Duration) MarshalJSON

func (d Duration) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*Duration) UnmarshalJSON

func (d *Duration) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler.

type File

type File struct {
	Version int `json:"version"`

	// Profile is the CLI profile holding the agent's credential.
	Profile string `json:"profile"`
	// AccountID is the account the agent belongs to.
	AccountID string `json:"account_id"`
	// Agent is who the profile's credential proved to be when setup ran.
	Agent Agent `json:"agent"`

	// Trust and Projects are admission's, in admission's own types, so the
	// two cannot drift. Projects is the list of Basecamp projects this agent
	// serves, keyed by project id.
	Trust    admission.Trust             `json:"trust"`
	Projects map[int64]admission.Project `json:"projects"`
	// Pings lets people reach the agent in one Ping alone, as participants
	// there (--allow-in-ping), keyed by the Ping's id. Absent when it lets
	// nobody in, so a file that uses none still opens in a CLI that predates
	// the key.
	Pings map[int64]admission.PingTrust `json:"pings,omitempty"`
	// AckBoost is how the connector acknowledges a request someone made of
	// the agent (--ack-boost, --no-ack-boost). Absent means the default, a
	// DefaultAckContent boost, and setup writes it only when it is not, so a
	// file that keeps the default still opens in a CLI that predates the
	// key.
	AckBoost *AckBoost `json:"ack_boost,omitempty"`

	// The Legacy fields are the settings of a connector that ran its own
	// workers: which driver and coding agent, how many at once, for how
	// long, and dangerous mode. The connector no longer starts workers
	// (the person's own session does the work), so nothing reads these.
	// They are fields for the same reason LegacyWorktrees is: Parse refuses
	// an unknown key, and a connect.json written before has them. Parse
	// zeroes them, and omitempty keeps them out of anything written from
	// here.
	LegacyDriver      string `json:"driver,omitempty"`
	LegacyWorker      string `json:"worker,omitempty"`
	LegacyConcurrency int    `json:"concurrency,omitempty"`
	LegacyDeadline    string `json:"deadline,omitempty"`
	LegacyDangerous   bool   `json:"dangerous,omitempty"`

	// LegacyWorktrees is the --worktrees setting of a connector that gave
	// each task a git worktree of its own. Worktrees are gone: a task runs
	// where the connector was started, and nothing here reads this. It is
	// still a field
	// because Parse refuses an unknown key, and a connect.json written
	// before they went has "worktrees" in it — accepting it is what lets
	// that file still open. Parse zeroes it, and omitempty keeps it out of
	// everything written from here, so the next `connect setup` writes the
	// key away for good.
	LegacyWorktrees bool `json:"worktrees,omitempty"`
}

File is connect.json.

func Apply

func Apply(f File, ch Changes) (File, error)

Apply returns f with ch applied. f is not modified. Everything that can be refused without the network is refused here; the result still has to pass Validate, which Save runs.

func Load

func Load(path string) (File, error)

Load reads and validates the connect.json at path. A file that does not exist is reported as os.ErrNotExist (use errors.Is). A file this user does not solely control is refused before a byte of it is believed.

func New

func New(profile string) File

New is an empty connect.json for a profile, with the spec's defaults.

func Parse

func Parse(data []byte) (File, error)

Parse decodes connect.json strictly. It refuses what encoding/json would quietly accept: an unknown key (a misspelled "watch_completion" ignored is a project the operator believes is driven and is not), a key given twice (only the last would count, so the file would not say what it reads as), and anything after the object.

func (File) Ack added in v0.14.0

func (f File) Ack() AckBoost

Ack is the acknowledgement this file asks for, the default when it names none.

func (File) Policy

func (f File) Policy(agentID int64) (admission.Policy, error)

Policy is the admission policy this file yields for the agent, obtained the way the connector obtains it: by serializing the file and parsing it with admission.ParsePolicy, then validating. A file whose policy does not validate is one admission would refuse, so setup refuses to write it.

func (File) Validate

func (f File) Validate() error

Validate refuses a file the connector could misread. It covers everything Policy does and the dispatch settings besides.

func (File) VerifyAgent

func (f File) VerifyAgent(kind string, personID, identityID int64) error

VerifyAgent refuses a connect.json that does not describe the identity a profile's credential authenticates as. It is the check that makes a credential replaced behind setup's back harmless: whoever acts on connect.json — setup before it writes, the connector before it runs — proves the file and the credential name one agent, and refuses otherwise.

type Person

type Person struct {
	ID             int64
	Name           string
	PersonableType string
	Client         bool
}

Person is the part of a Basecamp person the checks look at.

type Ping added in v0.14.0

type Ping struct {
	ID     int64
	People []Person
}

Ping is the part of a Basecamp Ping the checks look at. People are the others in it, as the reader sees it.

type PingPerson added in v0.14.0

type PingPerson struct {
	Ping   int64
	Person int64
}

PingPerson is a person in one Ping.

type Reader

type Reader interface {
	// Me is the person the credential authenticates as.
	Me(ctx context.Context) (Person, error)
	// Person reads someone else in the account.
	Person(ctx context.Context, id int64) (Person, error)
	// MintStreamTicket mints a stream ticket and throws it away. The ticket
	// is a bearer credential: an implementation must neither return, log nor
	// keep it.
	MintStreamTicket(ctx context.Context) error
	// Project reads a project.
	Project(ctx context.Context, id int64) error
	// ProjectPeople reads a project's people.
	ProjectPeople(ctx context.Context, id int64) error
	// Ping reads a Ping the reader is in.
	Ping(ctx context.Context, id int64) (Ping, error)
}

Reader is what the checks read, as the agent, in the agent's account.

type Report

type Report struct {
	Path          string
	Profile       string
	AccountID     string
	AgentPersonID int64
	AgentKind     string
	OperatorID    int64
	TrustMode     string
	// Projects is how many Basecamp projects the file serves.
	Projects int
	Written  bool
	// contains filtered or unexported fields
}

Report is the outcome of one setup run: what was written and every check. Whether the connector is ready is never stored: it is derived from the checks every time it is asked for, so no code path can report ready while a check has failed or before any check ran.

func (*Report) Add

func (r *Report) Add(checks ...Check)

Add records checks, in order.

func (*Report) Checks

func (r *Report) Checks() []Check

Checks returns the checks recorded so far.

func (*Report) Failed

func (r *Report) Failed() []Check

Failed returns the checks that failed.

func (*Report) MarshalJSON

func (r *Report) MarshalJSON() ([]byte, error)

MarshalJSON writes the report with ready derived, never stored.

func (*Report) Ready

func (r *Report) Ready() bool

Ready reports whether the connector could run as set up: connect.json was written, at least one check ran, and none failed. A check with a status outside the known ones counts as failed.

type SDKReader

type SDKReader struct {
	Client *basecamp.AccountClient
}

SDKReader is Reader over an SDK account client. Build the client without request hooks or a debug logger: they log request URLs, and a feed URL can carry a position or a ticket.

func (SDKReader) Me

func (r SDKReader) Me(ctx context.Context) (Person, error)

Me implements Reader.

func (SDKReader) MintStreamTicket

func (r SDKReader) MintStreamTicket(ctx context.Context) error

MintStreamTicket implements Reader through the SDK's stream-ticket operation, and accepts exactly what the connector runtime's minter accepts (eventfeed's liveMinter refuses a ticket with no credential, no URL or no positive lifetime), with the lifetime also bounded above. The ticket is dropped on return.

func (SDKReader) Person

func (r SDKReader) Person(ctx context.Context, id int64) (Person, error)

Person implements Reader.

func (SDKReader) Ping added in v0.14.0

func (r SDKReader) Ping(ctx context.Context, id int64) (Ping, error)

Ping implements Reader.

func (SDKReader) Project

func (r SDKReader) Project(ctx context.Context, id int64) error

Project implements Reader.

func (SDKReader) ProjectPeople

func (r SDKReader) ProjectPeople(ctx context.Context, id int64) error

ProjectPeople implements Reader.

type Trust

type Trust struct {
	// Operator is the operator. When OperatorProfile is set, it is the person
	// that profile's own credential read back, which proves who they are.
	Operator        Person
	OperatorProfile string
	// OperatorIsOwner says Operator is the person the agent works for, as
	// Basecamp named them in the agent's own profile. Basecamp's word proves
	// who they are, as a profile's own credential does, so they are not read
	// again: a new agent may share no project with them yet.
	OperatorIsOwner bool
	// Allowlist is every allowlisted Person id the file will hold.
	Allowlist []int64
	// Agents is every agent the file will allow to mention this one. Each
	// must read back as an Agent person: the reverse of the allowlist.
	Agents []int64
	// Recorded is the trust connect.json already holds for this same agent
	// and account. It is taken as the owner's own record — the file is
	// private to them, and setup verified it if setup wrote it — so a person
	// in it who cannot be re-read now is a warning, not a refusal. A read
	// that answers still decides. The zero value records nobody.
	Recorded admission.Trust
}

Trust is who connect.json says may drive the agent, as setup is about to write it, and how each person in it came to be named.

Jump to

Keyboard shortcuts

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