Documentation
¶
Overview ¶
Package origin is the single owner of "this workspace's origin clients".
Production code constructs a *jira.Client only through Client (the workspace's origin) or Connected (a candidate site/email/token, used when verifying a credential the user just typed). The wiki client is Wiki — the same split (standalone in-process handler vs connected site). jira.New stays exported for tests that stand up httptest servers; a gate test fails if production files grow a new direct call.
Index ¶
- Constants
- Variables
- func Client(cfg *config.Config) (*jira.Client, error)
- func Close() error
- func CloseStandalone(cfg *config.Config) error
- func Connected(site, email, token string) *jira.Client
- func CreatesVersionsByName(w Writer) bool
- func DefaultConfluenceConfig() *config.ConfluenceConfig
- func Describe(cfg *config.Config) (kind, origin string)
- func FoldPairedError(cfg *config.Config, err error) error
- func ForgetLive()
- func FormatLinkType(t jira.IssueLinkType) string
- func FormatLinkTypes(list []jira.IssueLinkType) string
- func HasRemoteLinks(cfg *config.Config, w any) bool
- func InProcessAuthB64() string
- func IsInProcess(cfg *config.Config) bool
- func IsPairingFailure(err error) bool
- func LegacyYAMLPath(dir string) string
- func Linear(cfg *config.Config) (*linear.Client, error)
- func LiveServeFor(profile string) (serveaddr.Record, bool)
- func OpenHolder(persist string) int
- func OwnerStatus(cfg *config.Config) string
- func PairedStatus(cfg *config.Config) (*pairing.Remote, error)
- func PersistPath(dir string) string
- func ResetInProcess()
- func ResolveCreateSource(ctx context.Context, cfg *config.Config, db *store.DB, project string) (string, error)
- func ResolveLinkType(token string, catalog []jira.IssueLinkType) (lt jira.IssueLinkType, inwardDescription bool, err error)
- func SessionsConstructed() uint64
- func SetInProcess(cfg *config.Config, v bool)
- func StandaloneHandler(cfg *config.Config) (http.Handler, error)
- func SyncFetchesRemoteLinks(cfg *config.Config, w any) bool
- func TransportIsEmbedded(rt http.RoundTripper) bool
- func TransportIsServe(rt http.RoundTripper) bool
- func VerifyPaired(ctx context.Context, endpoint, token string) (jira.User, error)
- func Wiki(cfg *config.Config) (*confluence.Client, error)
- type Attachment
- type Comment
- type CommentVisibility
- type CreateFieldCatalog
- type CreateFieldMeta
- type CreateMetaIssueType
- type CreateMetaProject
- type FieldMeta
- type GadakProbe
- type IssueLink
- type IssueLinkType
- type IssueLinker
- type LinkTypeHit
- type MediaRef
- type NamedID
- type PairingError
- type RemoteLink
- type RemoteLinker
- type Transition
- type User
- type Version
- type VersionCatalog
- type Writer
Constants ¶
const DefaultProjectKey = "STD"
DefaultProjectKey is the project seeded into a new standalone origin so create/createmeta have somewhere to file. Issuetap also creates a project on first write if a caller names another key.
const DefaultSpaceKey = "LOC"
DefaultSpaceKey is the wiki space seeded into a new standalone origin so page create has somewhere to file. Short and obviously local — not a display name, and not a site-specific key.
const LegacyYAMLRel = "origin/issuetap.yaml"
LegacyYAMLRel is the pre-SQLite persist path. When PersistRel is absent and this file exists, NewEmbedded seeds from it as FixturePath once. The YAML is left in place as a rollback asset.
const PersistRel = "origin/issuetap.db"
PersistRel is the workspace-relative path of the issuetap write-through SQLite state file (WAL). The file is the origin; gadak.db remains a disposable cache filled by sync.
const ProbePath = "/api/v1/issues/sync/progress/"
ProbePath and probeTimeout are how port_fallback and views open decide whether a loopback port is a gadak UI serve (X-Gadak / X-Gadak-Profile). They are not used to route origin writes. A leftover serve-origin.json from a previous version is ignored (GDK-936).
const RESTPrefix = "/api/v1/origin"
RESTPrefix is the serve passthrough root. A client request to /rest/api/3/issue is sent to <serve> + RESTPrefix + /rest/api/3/issue. Paired remote clients reach this machine's origin through this prefix.
Variables ¶
var ( ErrUnsupported = errors.New("this origin does not support that") ErrNoVersionCatalog = unsupported("linear: project versions are not supported on this origin") ErrNoIssueLinks = unsupported("linear: issue links are not supported on this origin") ErrNoCreateFields = unsupported("linear: create-time field metadata is not supported on this origin") ErrNoMediaRef = unsupported("linear: inline comment media is not supported; the file is attached to the issue") ErrNoRemoteLinks = unsupported("this origin does not support remote issue links — they need a standalone or paired workspace") )
These strings were the linearWriter stubs' Error() values. Callers that type-assert a missing face must return the same text (GDK-641).
ErrUnsupported is the cause of that whole class: a permanent "this origin cannot do that", distinct from a transport failure. failJira branches on it. The four ErrNo* wrap it so errors.Is(err, ErrUnsupported) is true while errors.Is(err, ErrNoIssueLinks) (and the other three) keep matching and Error() stays the original sentence (GDK-685).
var ErrNoIssueTypes = unsupported("linear: this origin has no issue types")
ErrNoIssueTypes is the edit --type refusal (GDK-962): Linear has no issue types, so asking to change one is refused, never silently dropped — the same class as create --type being dropped before GDK-741. The CLI refuses before fetching any catalog (a Linear type catalog cannot exist), and linearWriter.UpdateFields refuses the issuetype field itself so any other surface sending it inherits the same sentence.
var ErrWorkspaceFrozen = errors.New("this workspace is frozen — no requests leave for the origin; unfreeze with `gadak config set frozen false`")
ErrWorkspaceFrozen: frozen means no request leaves for the origin — pulls and writes alike (GDK-507 decision). The gate lives here, on the client mint, so every surface (CLI verbs, REST writes, per-issue resync, the api escape hatch, page writes) refuses in one place instead of each carrying its own check. A scrubbed demo fixture with a live credential must neither re-pollute its mirror nor create real issues on the origin.
var LinearEndpoint string
LinearEndpoint, when non-empty, is the GraphQL URL Linear() installs on the client. Tests point it at httptest; production leaves it empty so New keeps linear.Endpoint. This is not a config.json field — an install URL must not become a persisted setting.
Functions ¶
func Client ¶
Client is the single owner of "this workspace's Jira client". A connected workspace gets the same jira.New(site, email, token) as before. A standalone workspace embeds issuetap over the persist SQLite file (WAL). A paired remote workspace talks to the home serve's RESTPrefix passthrough. BaseURL stays empty so stored browse links are /browse/KEY rather than a fake https origin a person might click.
func Close ¶
func Close() error
Close checkpoints every live standalone origin (WAL) and drops the sessions. Safe to call more than once. The process owner (cmd/gadak main) calls this on the way out. Writes commit before ACK; Close is a checkpoint, not a debounce flush.
In-flight constructors are waited on (they publish, then this snapshots) so Close never closes a half-built Embedded. There is no permanent closed flag: Client after Close must open a new session (persist is the origin; the process is allowed to come back).
func CloseStandalone ¶ added in v0.18.0
CloseStandalone checkpoints and drops the live session for cfg's persist. Waits an in-flight constructor for the same key. No-op when nothing is live. Callers that marked SetInProcess unmark it themselves — this only owns the session.
func Connected ¶
Connected builds a client for an explicit site/email/token — verifying a credential the user just typed, not "this workspace's origin".
func CreatesVersionsByName ¶ added in v0.17.0
CreatesVersionsByName reports whether w mints a project version from a fixVersions add {"name": token}. True for issuetap (standalone in-process, routed serve, paired home). False for Cloud Jira and Linear (GDK-678).
func DefaultConfluenceConfig ¶
func DefaultConfluenceConfig() *config.ConfluenceConfig
DefaultConfluenceConfig is what initStandalone writes so the wiki sync pass is on and scoped to the seeded space. Presence of the block is the on switch (internal/sync/confluence.go).
func Describe ¶
Describe answers doctor: which kind of workspace, and where the origin is. Connected reports "jira" — "jira+linear" when the Linear source is on (no hostname or key either way; doctor is safe to paste). Standalone reports the persist path.
func FoldPairedError ¶ added in v0.17.0
FoldPairedError is the single owner of the remote-device pairing sentence. On a paired workspace, 401 and dial failures become a PairingError whose Error() is the first line a person should see. Other workspaces pass err through.
func ForgetLive ¶
func ForgetLive()
ForgetLive drops cached embedded sessions without closing them. Tests use this to simulate a second process: the serve handler still holds the graph, but Client no longer finds it in this process.
func FormatLinkType ¶ added in v0.17.0
func FormatLinkType(t jira.IssueLinkType) string
FormatLinkType names one type the way both the CLI listing and the HTTP error body name it.
func FormatLinkTypes ¶ added in v0.17.0
func FormatLinkTypes(list []jira.IssueLinkType) string
FormatLinkTypes renders a catalog for an error message or a listing.
func HasRemoteLinks ¶ added in v0.19.2
HasRemoteLinks reports an issuetap-backed origin — standalone (in-process) or paired (the home serve's passthrough). Single owner of the write gate: the CLI verbs ask here.
func InProcessAuthB64 ¶ added in v0.17.0
func InProcessAuthB64() string
InProcessAuthB64 is the base64 payload of the in-process Basic credential the local CLI presents to the origin passthrough. Exported so the server's pairing gate can rewrite a *validated* Bearer into the exact Authorization shape the embedded issuetap graph has always seen: the gate authenticates the caller, then speaks to the origin as the in-process user it always did (GDK-433).
func IsInProcess ¶ added in v0.18.0
IsInProcess reports whether this process owns cfg's persist.
func IsPairingFailure ¶ added in v0.17.0
IsPairingFailure reports a pairing/dial/auth failure that create must not relabel as a missing --project flag.
func LegacyYAMLPath ¶ added in v0.17.1
LegacyYAMLPath is the absolute pre-SQLite persist path inside a profile directory. Empty dir yields empty path.
func Linear ¶ added in v0.16.1
Linear is the single owner of "this workspace's Linear client" — the same role Wiki plays for Confluence (GDK-258: a third source beside the Jira client, never a facade behind its Transport). There is no standalone variant: issuetap has no Linear surface, and the block carries its own credential rather than the Atlassian one.
func LiveServeFor ¶ added in v0.19.0
LiveServeFor returns the first live UI serve advertising profile, found through the home-root run directory (serveaddr) and the identity probe — not leftover serve-origin.json, which GDK-936 made meaningless.
This is the single owner of that discovery walk (GDK-987): until it, two copies of the same serveaddr.List → ProbeGadakOnPort loop lived in originbind.RefuseIfOpen (standalone→connected conversion refusal) and pairflow.AdvertisedEndpoint (the pairing endpoint default), and a guard added to one copy would silently miss the other. Consumers keep their own framing of the record: RefuseIfOpen wraps PID/Addr in WorkspaceOpenError, AdvertisedEndpoint upgrades Addr to a URL.
func OpenHolder ¶ added in v0.18.1
OpenHolder reports the PID of another live process holding this persist, or 0. This process's own mark is not a holder — the caller is asking whether it must stand aside for someone else.
A marker naming a dead PID is removed here rather than reported: the alternative is a workspace that refuses conversion forever after one crash, with nothing on screen explaining why.
func OwnerStatus ¶
OwnerStatus is the doctor line for a standalone workspace. There is no exclusive persist owner after GDK-936 (WAL); leftover serve-origin.json is ignored. Empty when the workspace is not standalone.
func PairedStatus ¶ added in v0.17.0
PairedStatus is the single owner of "is this workspace paired with a remote gadak serve?". status, doctor, profiles, and pairing list read this instead of opening remote-origin.json themselves. Standalone is excluded: the same file on the home machine only carries the local pairing-gate token (`_home`), not a remote origin.
func PersistPath ¶
PersistPath is the absolute issuetap SQLite state path inside a profile directory.
func ResetInProcess ¶ added in v0.18.0
func ResetInProcess()
ResetInProcess clears every ownership mark. Tests only — production unmarks per workspace (Runtime.Close / closeEntry).
func ResolveCreateSource ¶ added in v0.17.2
func ResolveCreateSource(ctx context.Context, cfg *config.Config, db *store.DB, project string) (string, error)
ResolveCreateSource picks the origin a create files to — the create-side sibling of WriterFor's per-key routing. A project the mirror already knows as Linear routes there (same idea as store.KeySource; create has no issue key yet, so the project key is the only routing fact); a Linear-only workspace (no Atlassian credential) always routes to Linear, even before the first team is mirrored. CLI withCreateSession and REST createWriter both call this — one routing rule, not two copies that can drift (GDK-820).
func ResolveLinkType ¶ added in v0.17.0
func ResolveLinkType(token string, catalog []jira.IssueLinkType) (lt jira.IssueLinkType, inwardDescription bool, err error)
ResolveLinkType matches token against the catalog. An all-digit token is a type id and uses the outward description convention. Otherwise the type name and its outward description are outward; a match on the inward description reports inwardDescription. The caller assigns POST issue ends because Jira's issue response displays type.inward for outwardIssue and type.outward for inwardIssue.
func SessionsConstructed ¶
func SessionsConstructed() uint64
SessionsConstructed is how many times constructStandalone ran. Tests use a delta to prove a live session was reused.
func SetInProcess ¶
SetInProcess marks/unmarks this process as the persist owner of cfg's workspace. Keyed by persist path — the same key as live — so owning one workspace's persist does not disable routing for any other (STD-3).
func StandaloneHandler ¶
StandaloneHandler is the in-process issuetap HTTP surface for this workspace. The serve RESTPrefix passthrough uses it so a paired remote client lands on the same origin the UI already holds. Always embeds.
func SyncFetchesRemoteLinks ¶ added in v0.19.2
SyncFetchesRemoteLinks is deliberately narrower than HasRemoteLinks: the remote-link read is one request per issue, which is free in-process and one tailnet round-trip per issue on a paired workspace — a full sync of a thousand-issue mirror would pay a thousand of them for a feature most issues do not use. So a paired workspace's rows arrive through the write -through path (`gadak ref` refreshes the issue it touched) and a sync leaves them alone (nil preserves). Widen this only with a protocol that answers for many issues at once.
func TransportIsEmbedded ¶
func TransportIsEmbedded(rt http.RoundTripper) bool
TransportIsEmbedded reports whether c talks to an in-process issuetap handler. Tests use this instead of naming the unexported type.
func TransportIsServe ¶
func TransportIsServe(rt http.RoundTripper) bool
TransportIsServe reports whether c talks to a live serve passthrough.
func VerifyPaired ¶ added in v0.17.0
VerifyPaired proves a pairing offer before anything is saved (GDK-433 verify-before-save): one GET /rest/api/3/myself over the exact transport the paired workspace would use. A serve that answers 401 surfaces as jira.ErrAuth; an unreachable or broken endpoint surfaces as a transport error — the caller tells those apart without retrying. The offer string itself never enters an error. Deliberately no actor header (GDK-586): verification must not provision an agent identity on an origin the workspace does not exist on yet.
Types ¶
type Attachment ¶ added in v0.17.0
type Attachment = jira.Attachment
type CommentVisibility ¶ added in v0.17.0
type CommentVisibility = jira.CommentVisibility
type CreateFieldCatalog ¶ added in v0.17.0
type CreateFieldCatalog interface {
CreateFields(ctx context.Context, projectIDOrKey, issueTypeID string) ([]CreateFieldMeta, error)
}
CreateFieldCatalog is what this project+type requires and accepts at create time — the create-side sibling of EditMeta (GDK-254). An origin that cannot answer is missing the face; callers degrade, they do not block. Linear: AsCreateFieldCatalog returns ErrNoCreateFields.
func AsCreateFieldCatalog ¶ added in v0.17.0
func AsCreateFieldCatalog(w Writer) (CreateFieldCatalog, error)
AsCreateFieldCatalog returns w as CreateFieldCatalog, or ErrNoCreateFields.
type CreateFieldMeta ¶ added in v0.17.2
type CreateFieldMeta = jira.CreateFieldMeta
type CreateMetaIssueType ¶ added in v0.17.0
type CreateMetaIssueType = jira.CreateMetaIssueType
type CreateMetaProject ¶ added in v0.17.0
type CreateMetaProject = jira.CreateMetaProject
type GadakProbe ¶ added in v0.16.1
GadakProbe classifies a loopback GET to the progress endpoint. Exported so cmd/gadak's port fallback and views open use this single copy (GDK-423). Guards: 700ms context, no Origin header, X-Gadak required, profile from X-Gadak-Profile.
func ProbeGadakOnPort ¶ added in v0.16.1
func ProbeGadakOnPort(port string, timeout time.Duration) GadakProbe
type IssueLinkType ¶ added in v0.17.2
type IssueLinkType = jira.IssueLinkType
type IssueLinker ¶ added in v0.17.0
type IssueLinker interface {
IssueLinkTypes(ctx context.Context) ([]IssueLinkType, error)
LinkIssues(ctx context.Context, typeID, outwardKey, inwardKey string) error
// IssueLinks is the live projection with link ids (the mirror carries
// none), and DeleteIssueLink takes one of those ids back (GDK-1205).
IssueLinks(ctx context.Context, key string) ([]IssueLink, error)
DeleteIssueLink(ctx context.Context, id string) error
}
IssueLinker is GET /rest/api/3/issueLinkType plus POST /rest/api/3/issueLink. Names and inward/outward descriptions can be renamed; writes send the id. Linear has relations but no counterpart in this adapter yet (GDK-19): AsIssueLinker returns ErrNoIssueLinks.
func AsIssueLinker ¶ added in v0.17.0
func AsIssueLinker(w Writer) (IssueLinker, error)
AsIssueLinker returns w as IssueLinker, or ErrNoIssueLinks.
type LinkTypeHit ¶ added in v0.17.0
type LinkTypeHit struct {
Type jira.IssueLinkType
InwardDescription bool
}
LinkTypeHit is one catalog entry that matched a token and whether it is the type's inward description.
type MediaRef ¶ added in v0.17.0
type MediaRef interface {
MediaRef(ctx context.Context, attachmentID string) (mediaID, filename string, err error)
}
MediaRef resolves an attachment id to the media UUID Jira needs in an ADF comment node plus the filename. Linear has no counterpart; AsMediaRef returns ErrNoMediaRef and the comment path already degrades on that error.
func AsMediaRef ¶ added in v0.17.0
AsMediaRef returns w as MediaRef, or ErrNoMediaRef.
type PairingError ¶ added in v0.17.0
type PairingError struct {
// contains filtered or unexported fields
}
PairingError is the remote-device first line for a failed home-serve round trip: cause and next action, no REST method/path.
func (*PairingError) Error ¶ added in v0.17.0
func (e *PairingError) Error() string
type RemoteLink ¶ added in v0.19.2
type RemoteLink = jira.RemoteLink
type RemoteLinker ¶ added in v0.19.2
type RemoteLinker interface {
RemoteLinks(ctx context.Context, key string) ([]RemoteLink, error)
SetRemoteLink(ctx context.Context, key string, rl RemoteLink) error
DeleteRemoteLink(ctx context.Context, key, id string) error
}
RemoteLinker is Jira's remote issue links (GDK-1032): a pointer at something outside this tracker. gadak writes them only on an issuetap-backed origin (standalone / paired) — a Cloud site would show them to the whole team, and a cross-workspace pointer is personal.
func AsRemoteLinker ¶ added in v0.19.2
func AsRemoteLinker(cfg *config.Config, w Writer) (RemoteLinker, error)
AsRemoteLinker returns w as RemoteLinker when this workspace's origin is issuetap-backed, else ErrNoRemoteLinks. The face test alone would not do: a connected Cloud site is the same *jira.Client type and would answer the route, publishing a personal cross-workspace pointer to the whole team.
type Transition ¶ added in v0.17.0
type Transition = jira.Transition
type VersionCatalog ¶ added in v0.17.0
type VersionCatalog interface {
ProjectVersions(ctx context.Context, projectKey string) ([]Version, error)
CreatesVersionsByName() bool
}
VersionCatalog is GET /rest/api/3/project/{key}/versions. Linear has no counterpart (GDK-516): AsVersionCatalog returns ErrNoVersionCatalog.
CreatesVersionsByName is the issuetap mint-by-name capability (GDK-678): a fixVersions add {"name": token} creates the version when it is missing from the catalog. Cloud Jira is false — unknown names 400, and creating a version is a separate project-admin permission. Same shape as sync.OSNotifier.Supported: a boolean on the face that already owns the verb, not a workspace-kind string.
func AsVersionCatalog ¶ added in v0.17.0
func AsVersionCatalog(w Writer) (VersionCatalog, error)
AsVersionCatalog returns w as VersionCatalog, or ErrNoVersionCatalog.
type Writer ¶ added in v0.16.1
type Writer interface {
CreateMeta(ctx context.Context, projects []string) ([]CreateMetaProject, error)
CreateIssue(ctx context.Context, fields map[string]any) (string, error)
EditMeta(ctx context.Context, key string) (map[string]FieldMeta, error)
UpdateFields(ctx context.Context, key string, fields map[string]any) error
EditIssue(ctx context.Context, key string, fields, update map[string]any) error
Transitions(ctx context.Context, key string) ([]Transition, error)
Transition(ctx context.Context, key, transitionID string, fields map[string]any, comment json.RawMessage) error
AddComment(ctx context.Context, key string, adf json.RawMessage, visibility *CommentVisibility, internal bool) (Comment, error)
SetAssignee(ctx context.Context, key, accountID string) error
SearchUsers(ctx context.Context, query string) ([]User, error)
PriorityCatalog(ctx context.Context) ([]NamedID, error)
Upload(ctx context.Context, key, filename string, file io.Reader) ([]Attachment, error)
}
Writer is the write surface of an origin: the verbs every origin implements and the server and CLI call when writing through. Jira (connected) and standalone (issuetap speaks the Jira API) satisfy it via jiraWriter wrapping *jira.Client; a Linear adapter implements the same verbs over GraphQL (GDK-358). The method vocabulary is Jira-shaped (EditMeta, Transitions) so existing callers keep working; the types are origin DTOs (GDK-665), not internal/jira names.
Capability negotiation stays EditMeta/CreateMeta: an origin that cannot edit a field omits it there, and the existing UI already turns fields on and off from that answer. An unsupported verb returns an honest error, never a silent no-op.
Optional faces (VersionCatalog, IssueLinker, CreateFieldCatalog, MediaRef) are not part of Writer. Callers type-assert via As*; a missing face returns the matching ErrNo* string, never a silent no-op (GDK-641).
func WriterFor ¶ added in v0.16.1
WriterFor picks the write path for one issue's source — the caller reads it from the mirror (store.KeySource), because a key's shape cannot tell a Linear "MID-5" from a Jira "MID-5". Jira and standalone rows share the Jira client (wrapped); "linear" routes to the GraphQL adapter (GDK-361). An empty source (a key the mirror does not know yet, or a create) routes to the default origin.