Documentation
¶
Overview ¶
Package store owns the SQLite mirror: schema, migrations, transactions, full-text index and the derived fields the source does not provide.
0.x promises three things, documented in specs/000-product/data-model.md: the issues_full view plus the RECIPES queries, gadak sql stdout, and views open --keys - semantics. Everything else in the schema is documented so you can read it, not promised so you can build on it.
Index ¶
- Constants
- Variables
- func AliasIssueKey(m map[string]any)
- func EnsureLocal(mirrorPath string) error
- func FTSCJKBigramColumn(title, body, comments string) string
- func FormatDuration(d time.Duration) string
- func IsBusy(err error) bool
- func LocalAttachReuses() uint64
- func LocalNewerSchemaWarnsSuppressed() uint64
- func LocalPath(mirrorPath string) string
- func MarshalWithIssueKeyAlias(issueKey string, v any) ([]byte, error)
- func Now() string
- func OpenReadOnly(path string) (*sql.DB, error)
- func ValidateRecipeName(name string) error
- func WithBusyHint(err error) error
- type APIUsageDay
- type APIUsageDelta
- type APIUsageSum
- type APIUsageSummary
- type ActorPerson
- type Attachment
- type AuthorComment
- type Batch
- type ChangeEntry
- type Comment
- type CommentsByAuthorResult
- type DB
- func (db *DB) APIUsage(ctx context.Context, days int) ([]APIUsageDay, error)
- func (db *DB) APIUsageSummary(ctx context.Context) (APIUsageSummary, error)
- func (db *DB) AbsorbDashboards(ctx context.Context, incoming []Dashboard) error
- func (db *DB) AbsorbRecents(ctx context.Context, kinds map[string][]string) error
- func (db *DB) AbsorbViews(ctx context.Context, incoming []SavedView) error
- func (db *DB) AddAPIUsage(ctx context.Context, day string, u APIUsageDelta) error
- func (db *DB) AppendSyncRun(ctx context.Context, sourceID string, r SyncRun) error
- func (db *DB) AttachmentBelongs(ctx context.Context, issueKey, attachmentID string) (bool, error)
- func (db *DB) AttachmentOrigin(ctx context.Context, issueKey, attachmentID string) (sourceID, contentURL string, err error)
- func (db *DB) Close() error
- func (db *DB) CommentsByAuthor(ctx context.Context, authorID string, limit int) (CommentsByAuthorResult, error)
- func (db *DB) ComputeFieldUsage(ctx context.Context, aliases []string) ([]FieldUsageRow, error)
- func (db *DB) ConfluenceSpaceWatermarks(ctx context.Context, sourceID string) (map[string]string, error)
- func (db *DB) Dashboard(ctx context.Context, id string) (Dashboard, error)
- func (db *DB) DashboardVersion(ctx context.Context) (int64, error)
- func (db *DB) Dashboards(ctx context.Context) ([]Dashboard, error)
- func (db *DB) DeleteDashboard(ctx context.Context, id string) error
- func (db *DB) DeleteItems(ctx context.Context, sourceID string, keys []string) (int, error)
- func (db *DB) DeleteRecipe(ctx context.Context, name string) error
- func (db *DB) DeleteSavedView(ctx context.Context, id string) error
- func (db *DB) DeletedKeysSince(ctx context.Context, since string) ([]string, error)
- func (db *DB) Detail(ctx context.Context, key string) (*Detail, error)
- func (db *DB) DistinctCount(ctx context.Context, table, column string) (int, error)
- func (db *DB) DropSourceMirror(ctx context.Context, sourceID string) error
- func (db *DB) EnrichmentsByKind(ctx context.Context, kind string) (map[string]json.RawMessage, error)
- func (db *DB) EnrichmentsFor(ctx context.Context, key string) (map[string]json.RawMessage, error)
- func (db *DB) ExternalID(ctx context.Context, key string) (string, error)
- func (db *DB) Favorites(ctx context.Context) ([]string, error)
- func (db *DB) Feed(ctx context.Context, opts FeedOpts) (FeedResult, error)
- func (db *DB) FieldUsage(ctx context.Context) ([]FieldUsageRow, error)
- func (db *DB) FreshenSyncClock(ctx context.Context) error
- func (db *DB) GroupQueryHits(ctx context.Context, query string) (map[string]string, error)
- func (db *DB) HasCustomFieldKeysInRaw(ctx context.Context) (bool, error)
- func (db *DB) HasCustomFieldKeysInRawSampled(ctx context.Context, limit int) (bool, error)
- func (db *DB) HasPageVersion(ctx context.Context, itemID string, number int) (bool, error)
- func (db *DB) HasSource(ctx context.Context, id string) (bool, error)
- func (db *DB) History(ctx context.Context, opts HistoryOpts) (HistoryPage, error)
- func (db *DB) ImportRecents(ctx context.Context, items []Recent) error
- func (db *DB) IssueCommentCount(ctx context.Context) (int, error)
- func (db *DB) IssueLites(ctx context.Context) ([]IssueLite, error)
- func (db *DB) IssueLitesByKeys(ctx context.Context, keys []string) ([]IssueLite, error)
- func (db *DB) IssueLitesSince(ctx context.Context, since string) ([]IssueLite, error)
- func (db *DB) IssueSyncState(ctx context.Context) (SyncState, error)
- func (db *DB) KeySource(ctx context.Context, key string) (string, error)
- func (db *DB) KeysBySource(ctx context.Context, sourceID string) ([]string, error)
- func (db *DB) MarkFeedRead(ctx context.Context, opts MarkFeedReadOpts) (MarkFeedReadResult, error)
- func (db *DB) PageDetail(ctx context.Context, key string) (*PageDetail, error)
- func (db *DB) PageLites(ctx context.Context) ([]PageLite, error)
- func (db *DB) PageStamps(ctx context.Context, sourceID, spaceKey string) (map[string]PageStamp, error)
- func (db *DB) PageVersions(ctx context.Context, itemID string) ([]PageVersion, error)
- func (db *DB) PoolStats() sql.DBStats
- func (db *DB) ProjectSource(ctx context.Context, projectKey string) (string, error)
- func (db *DB) PruneConfluenceSpaces(ctx context.Context, sourceID string, keepKeys []string) (int, error)
- func (db *DB) PruneLocalHistory(ctx context.Context) error
- func (db *DB) PurgeIssueIDsOutsideNamespace(ctx context.Context, sourceID, ns string) (int, error)
- func (db *DB) PurgePageIDsOutsideNamespace(ctx context.Context, sourceID, ns string) (int, error)
- func (db *DB) PutRecipe(ctx context.Context, name, query string) (Recipe, error)
- func (db *DB) PutSavedView(ctx context.Context, v SavedView) error
- func (db *DB) Query(query string, args ...any) (*sql.Rows, error)
- func (db *DB) QueryActorPeople(ctx context.Context) ([]ActorPerson, error)
- func (db *DB) QueryIssueActors(ctx context.Context) ([]IssueActor, error)
- func (db *DB) QueryRow(query string, args ...any) *sql.Row
- func (db *DB) ReadOnly() (*sql.DB, error)
- func (db *DB) RecentVisits(ctx context.Context, limit int) ([]RecentVisit, error)
- func (db *DB) Recents(ctx context.Context, kind string) ([]Recent, error)
- func (db *DB) Recipe(ctx context.Context, name string) (Recipe, error)
- func (db *DB) RecipeNames(ctx context.Context) ([]string, error)
- func (db *DB) Recipes(ctx context.Context) ([]Recipe, error)
- func (db *DB) RecomputeEpicKeys(ctx context.Context) error
- func (db *DB) RecordRecent(ctx context.Context, kind, value string) (Recent, error)
- func (db *DB) RecordSearch(ctx context.Context, query string, resultCount int, ...) (Search, error)
- func (db *DB) RecordSync(ctx context.Context, sourceID string, r SyncResult) error
- func (db *DB) RecordVisit(ctx context.Context, kind, key string) (Visit, error)
- func (db *DB) ReingestCustom(ctx context.Context, specs []fields.SpecIDs, bodyFieldIDs []string) (int, error)
- func (db *DB) ReplaceDevLinks(ctx context.Context, key string, update DevLinksUpdate) error
- func (db *DB) ReplaceFieldUsage(ctx context.Context, rows []FieldUsageRow) error
- func (db *DB) ReplacePageVersions(ctx context.Context, itemID string, vers []PageVersion) error
- func (db *DB) ReplaceProjectVersions(ctx context.Context, projectKey string, rows []VersionRow) error
- func (db *DB) ReplaceSourceQueries(ctx context.Context, sourceID string, queries []SourceQuery) error
- func (db *DB) ResetForNewOrigin(ctx context.Context, sourceIDs []string) (OriginReset, error)
- func (db *DB) SaveDashboard(ctx context.Context, name, config string) (Dashboard, error)
- func (db *DB) SavedViews(ctx context.Context) ([]SavedView, error)
- func (db *DB) ScanFieldFill(ctx context.Context, ...) error
- func (db *DB) SchemaAudit(ctx context.Context) (SchemaAuditResult, error)
- func (db *DB) SchemaVersion() int
- func (db *DB) Search(ctx context.Context, query string, limit int) (SearchResult, error)
- func (db *DB) SearchExplain(ctx context.Context, query string, limit int) (SearchResult, error)
- func (db *DB) SetFavorite(ctx context.Context, key string, on bool) error
- func (db *DB) SetLastNotifiedAt(ctx context.Context, sourceID, at string) error
- func (db *DB) SetSearchOpened(ctx context.Context, id int64, kind, key string) (Search, error)
- func (db *DB) SetSpaceWatermark(ctx context.Context, sourceID, key, watermark string) error
- func (db *DB) SetWatch(ctx context.Context, key string, on bool) error
- func (db *DB) SourceQueries(ctx context.Context, sourceID string) ([]SourceQuery, error)
- func (db *DB) StatusCategories(ctx context.Context) (map[string]string, error)
- func (db *DB) SyncRuns(ctx context.Context, sourceID string, limit int) ([]SyncRun, error)
- func (db *DB) SyncState(ctx context.Context, sourceID string) (SyncState, error)
- func (db *DB) TableCount(ctx context.Context, table string) (int, error)
- func (db *DB) UpsertIssues(ctx context.Context, b Batch) (int, error)
- func (db *DB) UpsertPages(ctx context.Context, records []PageRecord) (int, error)
- func (db *DB) UpsertSource(ctx context.Context, s Source) error
- func (db *DB) UpsertSpaces(ctx context.Context, sourceID string, rows []SpaceRow) error
- func (db *DB) UserCatalog(ctx context.Context) ([]UserAccount, error)
- func (db *DB) Watches(ctx context.Context) ([]string, error)
- func (db *DB) WriteBusyRetries() uint64
- type Dashboard
- type DeriveInput
- type Derived
- type Detail
- type DetailAttachment
- type DetailChange
- type DetailComment
- type DetailLink
- type DevLink
- type DevLinksUpdate
- type DurationsInput
- type FeedFocus
- type FeedIdentity
- type FeedItem
- type FeedOpts
- type FeedResult
- type FeedUnreadCounts
- type FieldUsageRow
- type HistoryItem
- type HistoryOpts
- type HistoryPage
- type Issue
- type IssueActor
- type IssueLite
- type IssueRecord
- type Item
- type ItemRef
- type Link
- type MarkFeedReadOpts
- type MarkFeedReadResult
- type OriginReset
- type Page
- type PageComment
- type PageDetail
- type PageLite
- type PageRecord
- type PageStamp
- type PageVersion
- type Recent
- type RecentVisit
- type Recipe
- type SavedView
- type SchemaAuditResult
- type SchemaTooNewError
- type Search
- type SearchExplain
- type SearchMatch
- type SearchResult
- type Source
- type SourceQuery
- type SpaceRow
- type Spans
- type SyncResult
- type SyncRun
- type SyncState
- type UserAccount
- type VersionRow
- type Visit
Constants ¶
const ( FeedWindowDays = 30 FeedDefaultLimit = 80 FeedMaxLimit = 200 )
Feed window and default page size match the client contract (web/src/lib/api.ts getFeed / FeedFocus).
const ( VisitKindIssue = "issue" VisitKindPage = "page" )
VisitKind is visits.kind / searches.opened_kind: "issue" or "page".
const ( CommentsByAuthorDefaultLimit = 50 CommentsByAuthorMaxLimit = 200 )
People-axis comment list defaults (GET people/{author_id}/comments/).
const BusyHolderHint = "another gadak process (app/serve/CLI) holds this profile's mirror; close it or retry"
BusyHolderHint is the user-facing clause appended to SQLITE_BUSY errors. One owner (GDK-754 / GDK-740): sync's death path and the mirror-stale re-read warning both go through WithBusyHint, so the sentence cannot drift. It does not scan other processes — doctor lists holders separately.
const CategoryDone = "done"
CategoryDone is the only status category with meaning to the derived rules. Every rule keys on the category, never on a status name: the internal tool this was extracted from matched "Reopened" / "다시 열림" and broke on any other site or account language (contracts/sync.md, "Localization hazard").
const CategoryInProgress = "inprogress"
CategoryInProgress pairs with CategoryDone for the lifecycle spans. Like every rule here it keys on the category, never a status name.
const LocalRetention = 180 * 24 * time.Hour
LocalRetention is how long raw visit/search events are kept. Counts older than this window are not rolled up — derive from whatever is still here.
const RecentCap = 10
RecentCap is the per-kind ceiling. Matches web/src/lib/recency.ts MAX.
Variables ¶
var ErrInvalidCursor = errors.New("invalid cursor")
ErrInvalidCursor means a pagination cursor did not parse. The server's history handler maps it to HTTP 400 by identity — errors.Is against this value, not message text (GDK-609) — so cursor parsing must return this sentinel and never a fresh errors.New with a similar sentence.
var ErrKeyAmbiguous = errors.New("this key is mirrored from more than one source — scope one side out (projects / linear.teamIds) before writing to it")
ErrKeyAmbiguous means one key exists under more than one source (a Jira project ENG and a Linear team key ENG both mint ENG-1). A write routed by that key could land on the wrong tracker while the UI shows the other row (GDK-400), so callers refuse instead of picking one.
var ErrNotFound = errors.New("not found")
ErrNotFound means a single-row lookup (Detail, etc.) found no matching key. Callers map it to 404 / "not in the mirror"; store code still uses sql.ErrNoRows internally and wraps at the package boundary.
Functions ¶
func AliasIssueKey ¶ added in v0.17.0
AliasIssueKey copies m["issue_key"] onto m["key"] so map-built JSON cannot emit one name without the other (GDK-255).
func EnsureLocal ¶ added in v0.13.0
EnsureLocal creates local.db next to the mirror if needed and migrates it. Failures are returned; callers of Open log them and still open the mirror.
func FTSCJKBigramColumn ¶ added in v0.17.0
FTSCJKBigramColumn is the items_fts.cjk_bigram value for one row: the CJK bigrams of the title, body and comment text, space-joined. Exported because internal/snapshot writes rows through the same shape — any writer that fills only the three scored columns leaves CJK mid-match silently empty rather than broken, which is this design's named trap (0009 §Consequences).
func FormatDuration ¶ added in v0.17.0
FormatDuration renders a span with its single largest unit — the scale the durations line reads at. Sub-minute spans keep seconds.
func IsBusy ¶ added in v0.17.1
IsBusy reports whether err is SQLITE_BUSY (5) or SQLITE_BUSY_SNAPSHOT (517). Match on the driver's Code(), never on prose (store.go sqliteBusy).
func LocalAttachReuses ¶ added in v0.16.0
func LocalAttachReuses() uint64
LocalAttachReuses is how many times attachLocalHook found schema `local` already present and skipped the failure log.
func LocalNewerSchemaWarnsSuppressed ¶ added in v0.16.0
func LocalNewerSchemaWarnsSuppressed() uint64
LocalNewerSchemaWarnsSuppressed is how many times migrateLocal skipped a repeat of the newer-schema notice for a path already warned.
func MarshalWithIssueKeyAlias ¶ added in v0.17.0
MarshalWithIssueKeyAlias encodes v then sets `"key"` equal to issueKey. Callers pass a named type alias of themselves so this is not recursive. Map-built JSON uses AliasIssueKey.
func Now ¶
func Now() string
Now returns UTC now in config.ISOMilli. The server hands this value to clients as the `delta` cursor; see that constant for why milliseconds are required.
func OpenReadOnly ¶ added in v0.13.0
OpenReadOnly opens the mirror with SQLite mode=ro and ATTACHes local.db (created empty if missing so SELECT local.visits never fails the connection).
func ValidateRecipeName ¶ added in v0.17.0
ValidateRecipeName is the recipes save NAME rule. Empty and anything outside recipeNameRe are refused.
func WithBusyHint ¶ added in v0.17.1
WithBusyHint wraps a SQLITE_BUSY error so Error() names the likely holder. Non-busy errors and nil pass through. Already-hinted values are unchanged.
Types ¶
type APIUsageDay ¶
type APIUsageDay struct {
Day string `json:"day"`
Requests int64 `json:"requests"`
Throttled int64 `json:"throttled"`
ServerErrors int64 `json:"server_errors"`
Retries int64 `json:"retries"`
WaitMS int64 `json:"wait_ms"`
LastThrottledAt *string `json:"last_throttled_at,omitempty"`
}
APIUsageDay is one row of daily accumulated outbound API volume.
type APIUsageDelta ¶
type APIUsageDelta struct {
Requests int64
Throttled int64
ServerErrors int64
Retries int64
WaitMS int64
LastThrottledAt string // RFC3339 UTC, empty if unknown this flush
}
APIUsageDelta is a non-negative increment to accumulate into one UTC day. Numbers only — this package must not import jira (layering).
type APIUsageSum ¶
type APIUsageSum struct {
Requests int64 `json:"requests"`
Throttled int64 `json:"throttled"`
ServerErrors int64 `json:"server_errors"`
Retries int64 `json:"retries"`
WaitMS int64 `json:"wait_ms"`
LastThrottledAt *string `json:"last_throttled_at,omitempty"`
}
APIUsageSum is a multi-day total (no day key).
type APIUsageSummary ¶
type APIUsageSummary struct {
Today APIUsageDay `json:"today"`
Last7Days APIUsageSum `json:"last_7_days"`
}
APIUsageSummary is the shape status --json and GET settings/ runtime expose: today's row plus a 7-day rollup of our outbound call volume.
type ActorPerson ¶ added in v0.17.1
type ActorPerson struct {
AssigneeName string
AssigneeEmail string
AssigneeID string
ReporterName string
ReporterEmail string
ReporterID string
}
ActorPerson is the assignee/reporter triple JQL people resolution needs — the narrow projection PeopleFromIssues consumes, without IssueLite's twenty-odd columns (GDK-748: loading full lites made every --jql path pay a whole-mirror row scan).
type Attachment ¶
type Attachment struct {
ID string
ExternalID string
Filename string
MimeType string
Size int64
Author string
AuthorID string
CreatedAt string
// URL is the origin content URL when the source does not share Jira's
// /attachment/content/{id} shape. Empty for Jira (the proxy builds it).
URL string
}
Attachment is metadata only. Bytes are proxied on demand, never mirrored.
type AuthorComment ¶
type AuthorComment struct {
Key string `json:"key"`
Kind string `json:"kind"` // "issue" | "page"
Title string `json:"title"`
Snippet string `json:"snippet"`
CreatedAt string `json:"created_at"`
}
AuthorComment is one row in GET people/{author_id}/comments/.
type Batch ¶
type Batch struct {
Categories map[string]string // status id -> status category
Priorities []string // priority display names, most urgent first
Records []IssueRecord
// Force rewrites rows whose updated_at is unchanged. Off, an unchanged row
// is skipped entirely, which is what keeps an incremental re-run from
// bumping sync_state.version.
Force bool
}
Batch is one page of sync output. Categories and Priorities come from the site's own metadata endpoints (which are not localized) and feed the derived field rules.
type ChangeEntry ¶
type ChangeEntry struct {
ID string
At string
Author string
AuthorID string
Field string // "status", "assignee", "priority", ...
FromValue string
FromID string
ToValue string
ToID string
}
ChangeEntry is one field change. Status entries carry ids because display names are localized per account.
type Comment ¶
type Comment struct {
ID string // "<source_id>:<comment_id>"
ExternalID string
Author string
AuthorID string
BodyADF json.RawMessage
BodyText string
CreatedAt string
UpdatedAt string
// VisibilityType/VisibilityValue are the origin restriction (Jira
// visibility.type/value). Empty means unrestricted. Linear and wiki
// comments have no such field and stay empty.
VisibilityType string
VisibilityValue string
// JsdPublic is JSM's jsdPublic marker. nil means the origin omitted
// the key (not a JSM project, or Jira did not send it); false is an
// internal comment. Absence and false are distinct.
JsdPublic *bool
}
Comment is stored flat: the source API exposes no thread parent.
type CommentsByAuthorResult ¶
type CommentsByAuthorResult struct {
// Author is the display name from the newest matching comment ("" when none).
Author string `json:"author"`
Total int `json:"total"`
Comments []AuthorComment `json:"comments"`
}
CommentsByAuthorResult is the response body for comments by author.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is a handle on the mirror. Safe for concurrent use; writes are serialized.
func Open ¶
Open opens or creates the mirror at path and migrates it forward. A database written by a newer gadak is refused rather than used.
The data directory is created (and existing owner-writable dirs tightened) to 0700; an owner-locked directory (0555) is left locked. The DB file and any -wal/-shm sidecars are set to 0600. Chmod failures are logged and ignored so unsupported filesystems (or Windows) still work.
func (*DB) APIUsage ¶
APIUsage returns up to the most recent days rows, newest day first. days <= 0 means all rows.
func (*DB) APIUsageSummary ¶
func (db *DB) APIUsageSummary(ctx context.Context) (APIUsageSummary, error)
APIUsageSummary builds today + last 7 days from the mirror. Missing days are zeros. last_throttled_at on the rollup is the latest timestamp in the window.
func (*DB) AbsorbDashboards ¶ added in v0.17.2
AbsorbDashboards merges incoming rows (an export file, another machine) into local.dashboards — AbsorbViews, for dashboards. Same rules: a name the server already owns wins (incoming row dropped), an id that collides gets a fresh one so an insert can never overwrite a stored row.
func (*DB) AbsorbRecents ¶ added in v0.16.0
AbsorbRecents merges a localStorage dump into local.recents. Existing server rows stay in front (they are already the owner); incoming values not already present fill the remainder, still capped at RecentCap per kind. Incoming slices are newest-first, same as recentOf().
func (*DB) AbsorbViews ¶ added in v0.17.0
AbsorbViews merges browser-local (localStorage) views into local.saved_views — the GDK-437 promotion day must not look like data loss. Same rules as AbsorbRecents: the server is already the owner, so an incoming view whose name exists there is dropped (server row wins), the rest are inserted. Reads order by name, so "server rows in front" reduces to that conflict rule. Kept ids stay stable — the client hides its absorbed rows by id — and an id that already exists gets a fresh one so an insert can never overwrite a server row (PutSavedView's upsert is deliberately not used here).
func (*DB) AddAPIUsage ¶
AddAPIUsage UPSERTs delta into the given UTC day (YYYY-MM-DD), adding to any existing counters. last_throttled_at keeps the latest non-empty timestamp.
func (*DB) AppendSyncRun ¶
AppendSyncRun stores one run and prunes the history to the newest 100.
func (*DB) AttachmentBelongs ¶ added in v0.14.1
AttachmentBelongs reports whether the mirror lists attachmentID on issueKey. The id is the Jira external id when that column is set, otherwise the store row id — the same rule handleDetail uses when it builds content URLs. One EXISTS query: serving bytes must not pay for Detail's comments/history/links/refs.
func (*DB) AttachmentOrigin ¶ added in v0.16.1
func (db *DB) AttachmentOrigin(ctx context.Context, issueKey, attachmentID string) (sourceID, contentURL string, err error)
AttachmentOrigin is the proxy's lookup: which source owns this attachment and, when stored, the origin content URL. Matches AttachmentBelongs' id rule (external_id when set, else store id).
func (*DB) CommentsByAuthor ¶
func (db *DB) CommentsByAuthor(ctx context.Context, authorID string, limit int) (CommentsByAuthorResult, error)
CommentsByAuthor returns comments for an exact author_id match, newest first. Missing author_id yields total 0 and an empty list (not an error). limit defaults to 50 and is capped at 200.
func (*DB) ComputeFieldUsage ¶
ComputeFieldUsage builds field_usage rows from issues.custom for the given aliases. total is the issue count per project; filled is how many issues have a non-empty value for that alias.
func (*DB) ConfluenceSpaceWatermarks ¶ added in v0.13.0
func (db *DB) ConfluenceSpaceWatermarks(ctx context.Context, sourceID string) (map[string]string, error)
ConfluenceSpaceWatermarks returns each space key's incremental floor for sourceID. A missing or NULL watermark is "". Callers treat empty as "not yet backfilled" and run a full fetch for that space.
func (*DB) Dashboard ¶ added in v0.17.2
Dashboard looks up one row by id. Unknown id is ErrNotFound.
func (*DB) DashboardVersion ¶ added in v0.17.2
DashboardVersion is the monotonic change counter for dashboard writes (0 before the first save). It moves on save and delete — the two events an open tab must reflect.
func (*DB) Dashboards ¶ added in v0.17.2
Dashboards lists every dashboard by name.
func (*DB) DeleteDashboard ¶ added in v0.17.2
DeleteDashboard removes one row by id and bumps the change counter. Unknown id is ErrNotFound.
func (*DB) DeleteItems ¶
DeleteItems removes items whose keys have left the source's scope and records a tombstone so `delta` can report the deletion to a client that missed it.
func (*DB) DeleteRecipe ¶ added in v0.17.0
DeleteRecipe removes one recipe. Unknown name is ErrNotFound.
func (*DB) DeletedKeysSince ¶
DeletedKeysSince lists keys tombstoned after the cursor. `delta` must report these: a missed deletion leaves a tombstone visible in the client forever.
func (*DB) Detail ¶
Detail assembles one issue. An unknown key returns ErrNotFound so the handler can answer 404 without importing database/sql.
func (*DB) DistinctCount ¶
DistinctCount returns COUNT(DISTINCT col) for a fixed known table.column used by doctor. Empty/NULL values are excluded.
func (*DB) DropSourceMirror ¶ added in v0.16.1
DropSourceMirror deletes everything one source mirrored: its items (and their cascaded children), FTS rows, spaces, tombstones, sync state and sync runs — so the next sync starts full against the new origin. Used when a standalone workspace converts to connected: a pre-namespace mirror holds `jira:N` / `confluence:N` rows the new site's upsert would silently overwrite on an id collision. The disposable cache is dropped whole rather than reconciled row by row, and the personal rows keyed into it go with it.
This is the per-source half. ResetForNewOrigin is the whole conversion and is what the surfaces call; this stays exported for a single-source drop.
func (*DB) EnrichmentsByKind ¶
func (db *DB) EnrichmentsByKind(ctx context.Context, kind string) (map[string]json.RawMessage, error)
EnrichmentsByKind returns every payload of one kind, keyed by issue key. The list and delta responses merge it in.
func (*DB) EnrichmentsFor ¶
EnrichmentsFor returns every kind attached to one issue, for the detail view.
func (*DB) ExternalID ¶ added in v0.17.0
ExternalID is the origin's own id for a mirrored issue key — what Jira's dev-status calls issueId (numeric on Cloud, issuetap's id standalone).
func (*DB) FieldUsage ¶
func (db *DB) FieldUsage(ctx context.Context) ([]FieldUsageRow, error)
FieldUsage returns every field_usage row.
func (*DB) FreshenSyncClock ¶
FreshenSyncClock stamps every sync timestamp as now. It exists for throwaway fixtures — `gadak demo` and the demo recordings — where the snapshot's real age would surface as a stale-sync warning about data that is deliberately frozen. Never call this on a mirror that syncs for real: it would hide a stalled sync.
func (*DB) GroupQueryHits ¶ added in v0.16.0
GroupQueryHits is the result of Config.GroupQuery. Keys not in the map fall through to groupRules / assignee. A present empty string means unclassified (stop).
func (*DB) HasCustomFieldKeysInRaw ¶
HasCustomFieldKeysInRaw reports whether any stored raw document contains a customfield_ key under fields. Used by `gadak fields --apply` to refuse when the mirror was never synced with custom fields. The substring probe runs in SQL and stops at the first hit — the previous Go-side parse of every raw document cost ~1.5 s of JSON unmarshal at 20k issues (measured 2026-08-23, GDK-749). On a corpus with no custom fields the probe still walks every document (~12 KB each), which is the price of an exact no.
func (*DB) HasCustomFieldKeysInRawSampled ¶ added in v0.17.1
HasCustomFieldKeysInRawSampled is the doctor-hint twin of the probe above: it looks at at most limit documents and reports whether any carried a customfield_ key. A miss is not proof of absence — the hint it feeds says "none seen", not "none exist" — but it keeps `gadak doctor` off the whole-mirror path (GDK-749: the exact probe measured 600-860 ms on a custom-field-free 20k mirror; fields --apply keeps the exact one).
func (*DB) HasPageVersion ¶ added in v0.16.0
HasPageVersion reports whether a stamp for this version number is already stored. Sync uses it as the incremental gate: if the incoming page version is already on disk, history cannot have grown and must not be refetched.
func (*DB) History ¶ added in v0.13.0
func (db *DB) History(ctx context.Context, opts HistoryOpts) (HistoryPage, error)
History returns visits and searches newest-first. Cursor is opaque.
func (*DB) ImportRecents ¶ added in v0.16.0
ImportRecents upserts export-file rows. File used_at wins for a (kind, value) pair; each kind is then trimmed to RecentCap newest. Local-only pairs stay if they still fit in the cap.
func (*DB) IssueCommentCount ¶ added in v0.17.0
IssueCommentCount returns SUM(issues.comment_count): the comment figure the settings runtime shows, which counts issue comments only. It is deliberately not TableCount(ctx, "comments") — page comments share the comments table (upsertPageRecord rewrites them through the same insertComment helper), so the raw table count would mix wiki comments into an issue figure and move the number the settings UI has always shown (GDK-610).
func (*DB) IssueLites ¶
IssueLites returns the whole mirror, which is what `bootstrap` sends.
func (*DB) IssueLitesByKeys ¶ added in v0.14.1
IssueLitesByKeys returns the IssueLite rows for keys, in the order asked. Missing and empty keys are skipped. Duplicate keys yield duplicate rows. The SQL IN-list is de-duplicated; callers that need FTS rank pass the ranked key list and get that order back.
func (*DB) IssueLitesSince ¶
IssueLitesSince returns rows written at or after the given cursor. Only a row whose content actually changed has a newer synced_at, so an idle poll returns none. The bound is inclusive on purpose: re-sending a row the client already has is a harmless upsert, while missing one leaves it stale forever.
func (*DB) IssueSyncState ¶ added in v0.17.0
IssueSyncState is the issue-origin freshness row status --json and gadak_status publish as top-level watermark / sync_count / last_error. Jira wins when it has activity so dual-source workspaces keep the historical shape; Linear wins when it is the only issue source that has run.
func (*DB) KeySource ¶ added in v0.16.1
KeySource returns the source_id owning key in the mirror, "" when the key is not mirrored, and ErrKeyAmbiguous when two sources both mint it — silently preferring one source sent writes to a tracker the screen was not showing (GDK-400).
func (*DB) KeysBySource ¶ added in v0.16.1
KeysBySource returns issue keys mirrored from one source, in key order.
func (*DB) MarkFeedRead ¶
func (db *DB) MarkFeedRead(ctx context.Context, opts MarkFeedReadOpts) (MarkFeedReadResult, error)
MarkFeedRead upserts read receipts and returns the refreshed unread counts.
func (*DB) PageDetail ¶
PageDetail assembles one page. An unknown key returns (nil, nil).
func (*DB) PageStamps ¶ added in v0.14.2
func (db *DB) PageStamps(ctx context.Context, sourceID, spaceKey string) (map[string]PageStamp, error)
PageStamps returns the version stamp of every mirrored page in one space, keyed by the source's own page id (items.external_id). It exists so an incremental sync can answer "do I already hold this page?" from the mirror instead of spending a body fetch to find out — a search hit already carries version.number and version.when.
The mirror is a disposable cache, so a missing or malformed row simply means "fetch it": callers must treat an absent key as unknown, never as unchanged.
func (*DB) PageVersions ¶ added in v0.16.0
PageVersions returns stamps for itemID in number order. Missing item is empty.
func (*DB) PoolStats ¶ added in v0.16.0
PoolStats reports the connection pool's state. A caller that has finished with a mirror can assert nothing is still checked out: a connection that outlives Close belongs to something still running, and under WAL it writes journal files back into a directory the caller may already have removed (GDK-270).
func (*DB) ProjectSource ¶ added in v0.17.0
ProjectSource is KeySource for a project key: the source_id owning issues.project_key in the mirror. Create has no issue key yet, so routing a Linear-team create uses this. Empty when the project is not mirrored; ErrKeyAmbiguous when Jira and Linear both mint it.
func (*DB) PruneConfluenceSpaces ¶ added in v0.13.0
func (db *DB) PruneConfluenceSpaces(ctx context.Context, sourceID string, keepKeys []string) (int, error)
PruneConfluenceSpaces deletes mirrored wiki pages (and their items, FTS rows, and cascaded children) whose space_key is not in keepKeys, then drops the matching spaces rows. keepKeys empty is a no-op so a zero-scope call cannot wipe the mirror. The returned count is the number of pages removed (space-row-only cleanup is not counted).
func (*DB) PruneLocalHistory ¶ added in v0.13.0
PruneLocalHistory deletes visit/search rows older than LocalRetention. Called from the same pass as tombstone expiry (DeleteItems).
func (*DB) PurgeIssueIDsOutsideNamespace ¶ added in v0.16.1
PurgeIssueIDsOutsideNamespace deletes one source's issue rows whose item id does not start with ns+":". Upgrade path for GDK-241: a standalone mirror written before ids were namespaced holds `jira:N` rows whose keys the next sync re-inserts as `standalone-jira:N` — same (source_id, key), new id — which UNIQUE(source_id, key) rejects. The rows re-mirror immediately under the new namespace, so no tombstones are written. Children go via ON DELETE CASCADE; items_fts is contentless and needs the explicit delete.
func (*DB) PurgePageIDsOutsideNamespace ¶ added in v0.16.1
PurgePageIDsOutsideNamespace is the wiki sibling of PurgeIssueIDsOutsideNamespace (GDK-344). A standalone page's key is its numeric external id, so a pre-namespace `confluence:N` row and the namespaced `standalone-confluence:N` insert share UNIQUE(source_id, key).
func (*DB) PutRecipe ¶ added in v0.17.0
PutRecipe inserts or overwrites a recipe. Same name keeps created_at and refreshes sql / updated_at. Name and SQL are validated here so every writer shares the rule.
func (*DB) PutSavedView ¶
PutSavedView inserts or replaces a view. The caller owns id generation.
func (*DB) Query ¶ added in v0.17.0
Query runs a multi-row read on the same held connection (GDK-635 parent hierarchy hint). Same rule as QueryRow: do not open a second handle.
func (*DB) QueryActorPeople ¶ added in v0.17.1
func (db *DB) QueryActorPeople(ctx context.Context) ([]ActorPerson, error)
QueryActorPeople streams the assignee/reporter columns across all issues. It reads six narrow columns instead of the full IssueLite row set; the JQL resolver deduplicates in memory (people count ≪ issue count).
func (*DB) QueryIssueActors ¶ added in v0.17.0
func (db *DB) QueryIssueActors(ctx context.Context) ([]IssueActor, error)
QueryIssueActors returns every (issue, actor) touch, unordered. The set is small — bounded by comments + changelog + dev_links rows — and the caller (buildView) only folds it into a map, so no LIMIT.
func (*DB) QueryRow ¶ added in v0.17.0
QueryRow runs a single-row read on the mirror. It exists so a caller that already holds this connection (the CLI's staleness warning, GDK-314) does not open a second one — writes still go through the store API only.
func (*DB) ReadOnly ¶ added in v0.17.2
ReadOnly opens this DB's mirror with OpenReadOnly — mode=ro, local.db attached — for executing untrusted SQL (dashboard datasources). The single purpose is that a datasource statement cannot take the mirror's write lock or mutate a row no matter what the config contains: the "writes pass through origin" invariant's integrity axis for arbitrary SQL.
func (*DB) RecentVisits ¶ added in v0.17.0
RecentVisits lists the distinct (kind, key) pairs newest-first, at most limit: docs/MIRROR.md's "recently viewed" recipe (MAX(viewed_at) GROUP BY key) as a typed accessor, both kinds at once. It carries History's epoch clause for the same reason History does: after a workspace changes origin, a retired pair names a key the new origin can mint (GDK-418), and this list exists precisely so its reader goes on to open those keys.
func (*DB) Recents ¶ added in v0.16.0
Recents returns recent-use rows newest-first. Empty kind lists every kind.
func (*DB) RecipeNames ¶ added in v0.17.0
RecipeNames is the saved-name list a missing-name error quotes.
func (*DB) RecomputeEpicKeys ¶ added in v0.17.1
RecomputeEpicKeys rewrites issues.epic_key for the whole table. Full sync calls this once after every page has been upserted; per-batch upserts only recompute the batch and its parent-chain dependents (GDK-755).
func (*DB) RecordRecent ¶ added in v0.16.0
RecordRecent puts value at the front of kind (de-dup, cap RecentCap). Empty kind or value is refused. Kind is an opaque string — same names the web helper already used (assignee, transition:<project>, create-type:<project>, …).
func (*DB) RecordSearch ¶ added in v0.13.0
func (db *DB) RecordSearch(ctx context.Context, query string, resultCount int, openedKind, openedKey string) (Search, error)
RecordSearch appends one search. openedKind/openedKey may both be empty, or both set to the item opened from this search.
func (*DB) RecordSync ¶
RecordSync stores the run's bookkeeping. It does not bump `version`: a run that changed no rows must leave the ETag alone. A successful run advances first_sync_at (once) and sync_count.
func (*DB) RecordVisit ¶ added in v0.13.0
RecordVisit appends one view. Same (kind, key) twice is two rows. The insert uses a dedicated local.db connection so a writer holding the mirror (BEGIN IMMEDIATE / _txlock=immediate) does not block recording (GDK-753). Reads that join visits to the mirror stay on the ATTACH path.
func (*DB) ReingestCustom ¶
func (db *DB) ReingestCustom(ctx context.Context, specs []fields.SpecIDs, bodyFieldIDs []string) (int, error)
ReingestCustom recomputes issues.custom and the FTS body from the stored raw JSON — no network. Returns the number of issues rewritten.
func (*DB) ReplaceDevLinks ¶ added in v0.17.0
ReplaceDevLinks swaps one issue's dev_links rows for a successful origin answer — the mirror-refresh half of a dev-link write-through (GDK-497). Never a source of truth: the same rows are rebuilt by any later sync. update is a complete pull-request answer (empty Links drains the PR rows — GDK-580); deployment/build rows are not part of a dev-status answer and survive, written only by `gadak dev deploy`/`dev build` (GDK-592).
func (*DB) ReplaceFieldUsage ¶
func (db *DB) ReplaceFieldUsage(ctx context.Context, rows []FieldUsageRow) error
ReplaceFieldUsage replaces the entire field_usage table.
func (*DB) ReplacePageVersions ¶ added in v0.16.0
ReplacePageVersions writes the complete stamp list for one item. The caller must have a successful full listing; an empty slice clears stored rows. Re-running with the same numbers is a no-op on cardinality (PK upsert).
func (*DB) ReplaceProjectVersions ¶ added in v0.17.0
func (db *DB) ReplaceProjectVersions(ctx context.Context, projectKey string, rows []VersionRow) error
ReplaceProjectVersions upserts one project's version catalog and deletes rows for that project whose id is no longer in the catalog. The catalog is the origin; this table is a cache (GDK-532). An empty list clears the project. Rows with an empty id are skipped (no join key).
func (*DB) ReplaceSourceQueries ¶ added in v0.13.0
func (db *DB) ReplaceSourceQueries(ctx context.Context, sourceID string, queries []SourceQuery) error
ReplaceSourceQueries swaps one source's named queries in a single write. An empty list clears that source (the account deleted every filter).
func (*DB) ResetForNewOrigin ¶ added in v0.17.0
ResetForNewOrigin is the one owner of "this workspace's origin is being replaced". Both conversion surfaces reach it through originbind.DropStandaloneProjection, so the decision cannot exist on the CLI path and not the HTTP one (GDK-247).
One transaction: a conversion that dropped the mirror but left the feed marks behind would be the same class of half-state this function exists to remove.
func (*DB) SaveDashboard ¶ added in v0.17.2
SaveDashboard inserts a dashboard, or updates the row that already owns name (keeping its id and created_at). The id is minted here so the CLI and the API cannot disagree about who owns id generation. Config is stored verbatim; validation of the document is internal/dashboards' job, at every writer that accepts one from a user.
func (*DB) ScanFieldFill ¶
func (db *DB) ScanFieldFill(ctx context.Context, fn func(projectKey string, fieldVals map[string]json.RawMessage) error) error
ScanFieldFill streams every issue's project key and the fields object from stored raw JSON. Rows with empty or unparseable raw are skipped (older rows). fn receives field id → raw value for custom and system fields present in raw.
func (*DB) SchemaAudit ¶ added in v0.17.0
func (db *DB) SchemaAudit(ctx context.Context) (SchemaAuditResult, error)
SchemaAudit compares the open mirror against a fresh :memory: database that ran this build's migrate() path. It issues only reads against the open file. It is a doctor entry point, not part of Open: running migrate() on :memory: on every command would be wasted work.
func (*DB) SchemaVersion ¶
SchemaVersion is the migration level this binary applied.
func (*DB) Search ¶
Search runs a key lookup (when the query looks like a key) then an FTS5 query over titles, bodies and comment text. Key hits are reserved at the front of Keys/Pages so FTS cannot drop them when filling limit. Bare terms are rewritten as quoted prefix queries (see ftsPrefixQuery). A query FTS5 cannot parse is retried as a literal phrase rather than surfaced as an error, because this is fed raw user input. limit applies to the combined result (key hits + FTS), with key hits taking slots first.
func (*DB) SearchExplain ¶ added in v0.15.0
SearchExplain is Search plus a per-hit reason list. The FTS query is the same; only the returned Explain slice is extra.
func (*DB) SetFavorite ¶
SetFavorite adds or removes a favorite issue key.
func (*DB) SetLastNotifiedAt ¶
SetLastNotifiedAt advances the OS-notification watermark. Never touches feed_reads.
func (*DB) SetSearchOpened ¶ added in v0.13.0
SetSearchOpened stamps the item opened from an existing search row.
func (*DB) SetSpaceWatermark ¶ added in v0.13.0
SetSpaceWatermark writes one space's incremental floor. An unknown key is inserted so a just-scoped space can be stamped after its first successful pass without a separate UpsertSpaces.
func (*DB) SourceQueries ¶ added in v0.13.0
SourceQueries returns one source's named queries, starred first then by name.
func (*DB) StatusCategories ¶ added in v0.17.0
StatusCategories is the mirror's status id -> category map: the cached origin catalog (status_catalog, written by every sync pass) overlaid on the reconstruction from issue rows that carry one — the same query loadDeriveContext runs for `issue --derive`, kept so a mirror migrated to the catalog but not yet re-synced still resolves what it can. Durations needs this to walk the changelog, which stores bare ids.
func (*DB) SyncState ¶
SyncState reads the state for one source. A source that has never synced returns a zero state, not an error.
func (*DB) TableCount ¶
TableCount returns SELECT COUNT(*) for a fixed known table name used by status and doctor. Only whitelisted table names are accepted.
func (*DB) UpsertIssues ¶
UpsertIssues writes one page of sync output in a single transaction and returns how many items it actually changed. Derived fields are recomputed from the batch's changelog, never carried over (contracts/sync.md invariant 5).
An item whose updated_at is unchanged is skipped whole — children, FTS and the version counter included — unless Batch.Force is set. That is what makes an incremental re-run over the watermark overlap window a no-op.
func (*DB) UpsertPages ¶
UpsertPages writes document records in a single transaction and returns how many items it actually changed. A page whose stored body, meta and comments match the incoming record is skipped (no version bump). Comment-only edits still bump because comments are part of the compare — that is the comments- only trap the old always-rewrite path existed to close.
func (*DB) UpsertSource ¶
UpsertSource records a connector instance. Credentials never come near it (Constitution Article 8).
func (*DB) UpsertSpaces ¶
UpsertSpaces writes wiki space rows (key → name/kind/homepage_id) for a source. Empty name/kind/homepage_id on conflict keeps the previous value so page-hit upserts (name only) do not wipe kind or homepage filled by a full space listing or per-space GET.
func (*DB) UserCatalog ¶ added in v0.17.0
func (db *DB) UserCatalog(ctx context.Context) ([]UserAccount, error)
UserCatalog reads the cached origin account catalog (GDK-590): every account sync has seen, with the origin's account_type spelling. Callers judge bots through the connector's one judgement function, never here — the store is source-neutral.
func (*DB) WriteBusyRetries ¶ added in v0.16.0
WriteBusyRetries is how many times write() has retried SQLITE_BUSY (5 or 517) on this handle. Same shape as PoolStats: a cheap accessor, no logs.
type Dashboard ¶ added in v0.17.2
type Dashboard struct {
ID string `json:"id"`
Name string `json:"name"`
Config json.RawMessage `json:"config"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
Dashboard is one row of local.dashboards (GDK-781). Config is the {html, datasources} document internal/dashboards owns; the store keeps it as raw bytes so a newer config shape round-trips through an older binary.
type DeriveInput ¶
type DeriveInput struct {
Changelog []ChangeEntry
Categories map[string]string // status id -> new | inprogress | done
CurrentCategory string // the issue's category right now
Priority string
Priorities []string // site priority names, most urgent first
Comments []Comment
Links []Link
}
DeriveInput is everything the derived-field rules need. Changelog entries carry only status ids, so the id -> category map has to come from the site's status list, which the connector supplies per batch.
type Derived ¶
type Derived struct {
StatusChangedAt *string
ResolvedAt *string
ReopenCount int
ReopenedAt *string
ReopenReason string
AssigneeChangedAt *string
CommentCount int
PriorityRank int
ClonedFrom string
}
Derived holds the columns gadak computes because the source does not provide them. Rules are documented in docs/DERIVE.md, and a test there requires every field of this struct to appear in that file.
func Derive ¶
func Derive(in DeriveInput) Derived
Derive computes every derived field in one pass over the issue's changelog. A status id missing from the category map counts as not-done, which can only ever miss a reopen — never invent one.
type Detail ¶
type Detail struct {
// JSON alias "key" is added by issueDoc / detailResponse / AliasIssueKey,
// not by MarshalJSON on this type: an anonymous Marshaler embed would
// replace the whole `gadak issue --json` object (GDK-255).
IssueKey string `json:"issue_key"`
DescriptionADF json.RawMessage `json:"description_adf"`
// DescriptionText is items.body_text. Linear (and any source that does not
// store ADF) lands markdown/plain here; surfaces fall back to it when
// DescriptionADF is empty. Never stuff markdown into DescriptionADF.
DescriptionText string `json:"description_text,omitempty"`
Comments []DetailComment `json:"comments"`
Attachments []DetailAttachment `json:"attachments"`
History []DetailChange `json:"history"`
LinkedIssues []DetailLink `json:"linked_issues"`
// RefPages are wiki pages this issue's body/comments mention (item_refs,
// target_kind=page). Only pages present in the mirror; empty omitted.
RefPages []PageLite `json:"ref_pages,omitempty"`
// BacklinkPages are wiki pages that mention this issue key. Empty omitted.
BacklinkPages []PageLite `json:"backlink_pages,omitempty"`
// Custom is the issue's full alias→value map. List rows strip body-role
// values (they can be document-sized); detail is where they surface.
Custom map[string]any `json:"-"`
// Created is items.created_at — the origin stamp Durations measures the
// wait span from. Internal like Custom: the wire already carries the
// lites' created_at, and the detail response exposes only the derived
// spans (wait_ms / progress_ms), not this raw input.
Created string `json:"-"`
// DevLinks are the development-panel links (GDK-497), newest first.
DevLinks []DevLink `json:"dev_links"`
}
Detail is everything the on-demand detail view needs, assembled from the mirror with no call to the source.
type DetailAttachment ¶
type DetailAttachment struct {
ID string `json:"id"`
ExternalID string `json:"external_id"`
Filename string `json:"filename"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
Author string `json:"author,omitempty"`
AuthorID string `json:"author_id,omitempty"`
CreatedAt string `json:"created_at"`
URL string `json:"url,omitempty"`
}
DetailAttachment is metadata only; the handler turns ExternalID into the content proxy path. URL is the origin content URL when stored (Linear); empty for Jira.
type DetailChange ¶
type DetailChange struct {
At string `json:"at"`
Author string `json:"author"`
AuthorID string `json:"author_id,omitempty"`
Field string `json:"field"`
FromValue string `json:"from_value"`
FromID string `json:"from_id"`
ToValue string `json:"to_value"`
ToID string `json:"to_id"`
}
DetailChange is one history row. The ids come along because display values are localized: a caller that wants the `from_category` / `to_category` the detail contract allows has to resolve them from an id, never from a name.
type DetailComment ¶
type DetailComment struct {
ID string `json:"id"`
ExternalID string `json:"external_id"`
Author string `json:"author"`
AuthorID string `json:"author_id"`
BodyADF json.RawMessage `json:"body_adf"`
Body string `json:"body"` // flattened; the client's fallback when ADF will not render
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
// VisibilityType/VisibilityValue are empty when the origin sent no
// restriction. JsdPublic is nil when the jsdPublic key was absent.
VisibilityType string `json:"visibility_type"`
VisibilityValue string `json:"visibility_value"`
JsdPublic *bool `json:"jsd_public"`
}
DetailComment is one comment as the detail panel renders it.
type DetailLink ¶
type DetailLink struct {
Key string `json:"key"`
Type string `json:"type"`
Direction string `json:"direction"`
Summary string `json:"summary"`
StatusCategory string `json:"status_category"`
}
DetailLink is an edge plus whatever the mirror knows about the far side. A target outside the mirror keeps an empty summary and category.
type DevLink ¶ added in v0.17.0
type DevLink struct {
Kind string // pullrequest | deployment | build (GDK-592)
ExternalID string
URL string
Title string
// Status is the stored form of the origin's OPEN|MERGED|DECLINED
// vocabulary (lowercase). Produced by jira.DevPRStatus.Stored();
// unknown origin tokens stay the lowercased payload.
Status string
// Author is the pull request's author (login). Actor/ActorName are who
// wrote the link (issuetap's X-Issuetap-Actor accountId and display
// name). Different axes — a bot links a human's PR — never merged
// (GDK-589). Branch is the PR head ref. All ” when the origin sent
// nothing (v33 columns are NOT NULL DEFAULT ”).
Author string
Actor string
ActorName string
Branch string
// Environment is a deployment row's target (production, staging, …)
// — kind data with its own v36 column, never a title slot. Empty on
// pull-request and build rows (GDK-592).
Environment string
UpdatedAt string
}
DevLink is one development-panel link (GDK-497): a pull request the origin associates with the issue. URL is the idempotent key per issue.
type DevLinksUpdate ¶ added in v0.17.0
type DevLinksUpdate struct {
Links []DevLink
}
DevLinksUpdate is a successful origin answer for one issue's development-panel links. The type exists only after a completed fetch (or a deliberate drain such as Cloud opt-out). Nil *DevLinksUpdate skips the rewrite; a non-nil value with empty Links drains. A fetch error cannot construct this value, so it cannot reach ReplaceDevLinks or the upsert rewrite (GDK-536 / GDK-580).
type DurationsInput ¶ added in v0.17.0
type DurationsInput struct {
Created string
Changelog []DetailChange
Categories map[string]string // status id -> new | inprogress | done
Now time.Time
}
DurationsInput is everything the two lifecycle spans need. Changelog entries carry status ids only, so the id -> category map comes with them; Now is an input so the computation stays deterministic for tests and the server (GDK-590) can reuse it verbatim.
type FeedFocus ¶
type FeedFocus string
FeedFocus is the focus tab the client sends: all | assignee | reporter | mention. "watched" is a reason, not a focus filter.
type FeedIdentity ¶
type FeedIdentity struct {
AccountID string
Email string
DisplayName string // TokenOwner / display name for author matching
}
FeedIdentity is the local user, used for relevance and self-action exclusion. AccountID comes from config (Jira /myself); Email/DisplayName fall back when the mirror still only has display names on some rows.
func FeedIdentityOf ¶ added in v0.17.2
func FeedIdentityOf(cfg *config.Config) FeedIdentity
FeedIdentityOf maps the configured credential onto the feed identity. The sync notifier and the HTTP feed handler both build this — one owner, not two copies that can drift when the credential shape grows (GDK-820). A nil config reads as the zero identity, which the notifier's degraded path already treats as "no focus".
type FeedItem ¶
type FeedItem struct {
ID int `json:"id"`
EventID string `json:"event_id"`
IssueKey string `json:"issue_key"`
Summary string `json:"summary"`
CurrentStatus string `json:"current_status"`
EventType string `json:"event_type"`
OccurredAt *string `json:"occurred_at"`
ActorName string `json:"actor_name"`
Payload map[string]any `json:"payload"`
Reasons []string `json:"reasons"`
ReadAt *string `json:"read_at"`
}
FeedItem is one activity row the client renders (web/src/lib/types.ts FeedItem).
func (FeedItem) MarshalJSON ¶ added in v0.17.0
MarshalJSON adds `key` as an alias of `issue_key` (GDK-255).
type FeedOpts ¶
type FeedOpts struct {
Focus FeedFocus
Limit int
Me FeedIdentity
// Now, when set, freezes the 30-day window for tests.
Now time.Time
}
FeedOpts configures a personal-feed query.
type FeedResult ¶
type FeedResult struct {
Items []FeedItem `json:"items"`
UnreadCounts FeedUnreadCounts `json:"unread_counts"`
}
FeedResult is GET feed/ response body (without the outer wrapper keys).
type FeedUnreadCounts ¶
type FeedUnreadCounts struct {
All int `json:"all"`
Assignee int `json:"assignee"`
Reporter int `json:"reporter"`
Mention int `json:"mention"`
}
FeedUnreadCounts is the badge counts per focus tab.
type FieldUsageRow ¶
FieldUsageRow is one (project, alias) fill statistic from field_usage.
type HistoryItem ¶ added in v0.13.0
type HistoryItem struct {
Type string `json:"type"` // visit | search
ID int64 `json:"id"`
Kind string `json:"kind,omitempty"`
Key string `json:"key,omitempty"`
Query string `json:"query,omitempty"`
ResultCount *int `json:"result_count,omitempty"`
OpenedKind *string `json:"opened_kind,omitempty"`
OpenedKey *string `json:"opened_key,omitempty"`
At string `json:"at"`
}
HistoryItem is one row of the mixed timeline (newest first).
type HistoryOpts ¶ added in v0.13.0
type HistoryOpts struct {
// Kind is "issue", "page", or "search". Empty means all three.
Kind string
Limit int
Cursor string
}
HistoryOpts filters the mixed visit+search timeline.
type HistoryPage ¶ added in v0.13.0
type HistoryPage struct {
Items []HistoryItem `json:"items"`
NextCursor string `json:"next_cursor,omitempty"`
}
HistoryPage is one cursor page of History.
type Issue ¶
type Issue struct {
ProjectKey string
IssueType string
IssueTypeID string
Status string
StatusID string
StatusCategory string // new | inprogress | done
Priority string
PriorityID string
Assignee string
AssigneeID string
AssigneeEmail string
Reporter string
ReporterID string
ReporterEmail string
ParentKey string
// HierarchyLevel is the source-neutral tree rank of this issue's type
// (e.g. epic=1, standard=0, sub-task=-1). Used to derive EpicKey.
HierarchyLevel int
Labels []string
Components []string
FixVersions []string
// FixVersionIDs is the same-order source ids for FixVersions (column
// fix_version_ids). Join the versions catalog on these, not on names —
// names rename. Empty slice stores "[]", same rule as FixVersions.
FixVersionIDs []string
AffectsVersions []string
EnvironmentText string
Duedate string
Resolution string
ResolutionID string
// SprintID/SprintName/SprintState are the one sprint projected from the
// origin's sprint array (active > future > closed, then larger id). Nil /
// empty when the site has no sprint field, the array is empty, or an
// element was not an object. Linear leaves them unset.
SprintID *int64
SprintName string
SprintState string
// SecurityLevelID/SecurityLevel are the origin issue security level.
// Empty when the origin sent no security object (unrestricted, or a
// source that has none — Linear). Id is the key; the name is
// display-only and localizes. nz() stores empty as NULL.
SecurityLevelID string
SecurityLevel string
DescriptionADF json.RawMessage
Custom map[string]any // mapped custom fields, keyed by config alias
Raw json.RawMessage
}
Issue is the tracker projection. `item_id`, `key`, `created_at`, `updated_at` are taken from the record's Item and every derived field is computed by the store, so a connector fills none of them.
type IssueActor ¶ added in v0.17.0
type IssueActor struct {
IssueKey string
SourceID string
ActorID string
ActorName string
Via string // "comment" | "changelog" | "dev_link"
}
IssueActor is one touch of one issue by one account: a comment, a changelog entry, or a development-panel link (GDK-590). The union is also the issue_actors view, so `gadak sql` and documented recipes see the same axis the server builds members from.
type IssueLite ¶
type IssueLite struct {
IssueKey string `json:"issue_key"`
Summary string `json:"summary"`
ProjectKey string `json:"project_key"`
IssueType string `json:"issue_type"`
IssueTypeID string `json:"issue_type_id"`
Status string `json:"status"`
StatusID string `json:"status_id"`
StatusCategory string `json:"status_category"`
Priority *string `json:"priority"`
// PriorityID is the stable Jira priority id (issues.priority_id). Empty
// on rows a sync has not rewritten since the column was added — clients
// fall back to the display name for those, the same way they do for a
// missing status_id / issue_type_id.
PriorityID string `json:"priority_id"`
PriorityRank int `json:"priority_rank"`
Assignee *string `json:"assignee"`
AssigneeID *string `json:"assignee_id"`
AssigneeEmail *string `json:"assignee_email"`
Reporter *string `json:"reporter"`
ReporterID *string `json:"reporter_id"`
ReporterEmail *string `json:"reporter_email"`
// EpicKey is the nearest hierarchy_level==1 ancestor (derived). Nil when
// none. Distinct from ParentKey, which is the direct parent only.
EpicKey *string `json:"epic_key"`
// ParentKey is the direct parent issue key (source field), not the epic.
ParentKey *string `json:"parent_key"`
// HierarchyLevel is issues.hierarchy_level as stored (epic 1, standard
// 0, sub-task −1). Projected, not recomputed.
HierarchyLevel int `json:"hierarchy_level"`
Labels []string `json:"labels"`
Components []string `json:"components"`
FixVersions []string `json:"fix_versions"`
Duedate *string `json:"duedate"`
Resolution *string `json:"resolution"`
// ResolutionID is the stable Jira resolution id (issues.resolution_id).
// Empty on rows a sync has not rewritten since the column was added —
// the same contract as priority_id. Unresolved issues also store ”.
ResolutionID string `json:"resolution_id"`
CreatedAt *string `json:"created_at"`
UpdatedAt *string `json:"updated_at"`
StatusChangedAt *string `json:"status_changed_at"`
ResolvedAt *string `json:"resolved_at"`
ReopenCount int `json:"reopen_count"`
ReopenedAt *string `json:"reopened_at"`
ReopenReason *string `json:"reopen_reason"`
ClonedFrom *string `json:"cloned_from"`
// SourceProject is ClonedFrom's project prefix, precomputed because the
// list filter groups by it.
SourceProject *string `json:"source_project"`
CommentCount int `json:"comment_count"`
// Custom holds the configured field aliases from issues.custom. The server
// spreads them into the response as top-level keys, which is where the client
// reads severity and friends.
Custom map[string]any `json:"custom,omitempty"`
// Source is items.source_id (jira / linear / …). Write pickers key on it
// so a Linear row does not consume a Jira catalog or credential.
Source string `json:"source,omitempty"`
// SprintID/SprintName/SprintState are the projected sprint (GDK-518).
// Nil when the origin had none, the site has no sprint field, or the
// row predates the next sync after v30.
SprintID *int64 `json:"sprint_id"`
SprintName *string `json:"sprint_name"`
SprintState *string `json:"sprint_state"`
// SecurityLevelID/SecurityLevel are the origin issue security level
// (v32). Nil when the issue is unrestricted, the origin sent no
// security object, or the row predates the next sync after v32.
// Id is the key (names localize); the name is display-only.
SecurityLevelID *string `json:"security_level_id"`
SecurityLevel *string `json:"security_level"`
}
IssueLite is the row shape the read API hydrates the client with. Field names are the ones web/src/lib/types.ts already parses (contracts/api.md, "IssueLite"): adding is safe, renaming is not.
func (IssueLite) MarshalJSON ¶ added in v0.17.0
MarshalJSON adds `key` as an alias of `issue_key` so JSON surfaces and SQL (`issues_full.key`) share a name (GDK-255). Derived at marshal time so a constructor cannot emit one without the other.
type IssueRecord ¶
type IssueRecord struct {
Item Item
Issue Issue
Comments []Comment
Attachments []Attachment
Changelog []ChangeEntry
Links []Link
// Users feeds the account catalog cache. Unlike the child lists above it
// merges rather than replaces: a row with an empty name or account_type
// keeps what the catalog already knows (some payloads carry less than the
// first one that mentioned the account).
Users []UserAccount
// DevLinks, when non-nil, is a complete origin answer (including
// empty). Nil means the origin was not observed and existing rows
// stay (GDK-536 / GDK-580).
DevLinks *DevLinksUpdate
}
IssueRecord is one item and everything hanging off it. Child lists are replaced wholesale on upsert, so a partial list would delete rows.
type Item ¶
type Item struct {
ID string // "<source_id>:<external_id>"
SourceID string
Kind string // "issue" in v0.1
ExternalID string
Key string // human-facing key, unique per source
Title string
BodyText string // flattened body, what FTS indexes
Author string
AuthorID string
URL string
CreatedAt string
UpdatedAt string
}
Item is the neutral spine row: anything with a title, body, author and timestamp fits it.
type ItemRef ¶
type ItemRef struct {
TargetKind string // "issue" | "page"
TargetKey string // issue key or page id (numeric string)
Via string // "url" | "text"
}
ItemRef is one outgoing reference from an item. Pure extraction returns these; the write path persists them into item_refs.
func ExtractIssueRefsFromPage ¶
ExtractIssueRefsFromPage finds issue keys in a page's ADF (URL paths) and plain body_text (bare keys filtered by knownProjects). Same key from both sources yields one row with via=url. knownProjects may be empty (no text hits).
func ExtractPageRefsFromIssue ¶
ExtractPageRefsFromIssue finds Confluence page IDs in an issue description (raw ADF + flattened text) and its comment bodies. Scanning ADF is what picks up link marks and inlineCards that PlainText drops — the same raw scan ExtractIssueRefsFromPage already does in the other direction. Both URL shapes use via=url. Duplicates collapse to one.
type MarkFeedReadOpts ¶
type MarkFeedReadOpts struct {
EventIDs []string
IssueKeys []string
All bool
Me FeedIdentity
Now time.Time
}
MarkFeedReadOpts is POST feed/read/ body semantics.
type MarkFeedReadResult ¶
type MarkFeedReadResult struct {
Updated int `json:"updated"`
UnreadCounts FeedUnreadCounts `json:"unread_counts"`
}
MarkFeedReadResult is POST feed/read/ response.
type OriginReset ¶ added in v0.17.0
type OriginReset struct {
// Removed is table → rows deleted. Only tables whose rows named the
// retired origin appear; the mirror's own cascade is not itemised.
Removed map[string]int `json:"removed"`
// RetiredHistory is visit and search rows still on disk but no longer in
// the timeline: they name keys the new origin would resolve to other
// issues. Readable with `gadak sql` (local.visits / local.searches).
RetiredHistory int `json:"retired_history"`
// OriginEpoch is the generation history is stamped with from now on.
OriginEpoch int `json:"origin_epoch"`
// SavedViews are authored views whose stored query mentions a project the
// retired origin owned. Kept — the user wrote them — and named here,
// because `project = STD` now means the new site's STD. A text match on
// the opaque config: the web owns that shape, so this over-reports rather
// than staying silent.
SavedViews []string `json:"saved_views_naming_retired_projects"`
}
OriginReset is what a conversion took, so a surface can say it instead of leaving the user to discover an empty feed. Keyed by table rather than by field so a rule added later reports itself without a struct change.
func (OriginReset) MarshalJSON ¶ added in v0.17.0
func (r OriginReset) MarshalJSON() ([]byte, error)
MarshalJSON keeps Removed non-nil so `--json` never emits a bare null for a field a caller iterates.
func (OriginReset) String ¶ added in v0.17.0
func (r OriginReset) String() string
String renders the reset for a human: one line, empty when nothing went.
type Page ¶
type Page struct {
SpaceKey string
ParentID string
Version int
Status string
// Labels are source-neutral tag names (JSON array column). Empty slice is
// stored as "[]", never NULL. Sync sorts alphabetically for determinism.
Labels []string
// BodyADF is the raw Atlas Document Format body for rendering. Empty when
// the mirror only has flattened body_text (pre-v10 rows).
BodyADF json.RawMessage
}
Page is the document projection (one row in the pages table). Field names match the projection columns, not any source API (same status as Issue).
type PageComment ¶
type PageComment struct {
Author string `json:"author"`
CreatedAt string `json:"created_at"`
BodyADF json.RawMessage `json:"body_adf"`
BodyText string `json:"body_text"`
}
PageComment is one comment on a page detail response.
type PageDetail ¶
type PageDetail struct {
PageLite
BodyADF json.RawMessage `json:"body_adf"`
// BodyText is BodyADF flattened by adf.PlainText — the same walker FTS
// indexes. Always present (empty when the body is empty) so a text client
// does not have to parse ADF.
BodyText string `json:"body_text"`
Comments []PageComment `json:"comments"`
// RefIssueKeys are issue keys this page's body mentions (item_refs). Only
// issues present in the mirror, sorted ascending. Empty omitted.
RefIssueKeys []string `json:"ref_issue_keys,omitempty"`
// BacklinkIssueKeys are issue keys that mention this page. Empty omitted.
BacklinkIssueKeys []string `json:"backlink_issue_keys,omitempty"`
}
PageDetail is PageLite plus the raw ADF body, its plain-text form, and comments.
type PageLite ¶
type PageLite struct {
Key string `json:"key"`
Title string `json:"title"`
SpaceKey string `json:"space_key"`
// SpaceName is the human-readable space title from the spaces table join.
// Empty when no spaces row is mirrored for this key (never omitted in JSON).
SpaceName string `json:"space_name"`
// SpaceHomepageID is the content id of the space root page (spaces.homepage_id).
// Empty when the space row is missing or homepage has not been learned yet.
SpaceHomepageID string `json:"space_homepage_id"`
ParentID string `json:"parent_id"`
Author string `json:"author"`
// AuthorID is items.author_id. Empty on legacy rows that only have a
// display name — clients group by this when present and fall back to Author.
AuthorID string `json:"author_id"`
UpdatedAt string `json:"updated_at"`
Version int `json:"version"`
URL string `json:"url"`
// Excerpt is a one-line body preview derived from body_adf (schema v15):
// whitespace-normalized, at most 200 runes. Empty when the body is empty.
Excerpt string `json:"excerpt"`
Labels []string `json:"labels"`
}
PageLite is the list/search row for a mirrored wiki page. Field names are snake_case JSON to match IssueLite and the read API contract.
type PageRecord ¶
PageRecord is one document item plus its projection and comments. Comments are replaced wholesale on upsert, matching IssueRecord.
type PageStamp ¶ added in v0.14.2
PageStamp is the mirror's record of one page's upstream identity: the source's version number and the lastModified the row was written from. Both together, never the number alone — a rollback can reuse a number.
type PageVersion ¶ added in v0.16.0
type PageVersion struct {
Number int
CreatedAt string
AuthorID string
AuthorName string
Message string
MinorEdit bool
}
PageVersion is one history stamp on a wiki page. Bodies are never stored — only the number, when, who, the editor's note, and the minor-edit flag. Field names match the page_versions columns, not the Confluence payload.
type Recent ¶ added in v0.16.0
type Recent struct {
Kind string `json:"kind"`
Value string `json:"value"`
UsedAt string `json:"used_at"`
}
Recent is one (kind, value) pair in local.recents. Unlike visits, the same pair is one row: recording it again only moves used_at to now.
type RecentVisit ¶ added in v0.17.0
type RecentVisit struct {
Kind string `json:"kind"`
Key string `json:"key"`
ViewedAt string `json:"viewed_at"`
}
RecentVisit is one (kind, key) pair folded to its newest visit — the row `gadak recents` prints. Unlike Visit there is no ID: dedup happened in SQL.
type Recipe ¶ added in v0.17.0
type Recipe struct {
Name string `json:"name"`
SQL string `json:"sql"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
Recipe is a named read-only SQL query in local.recipes (GDK-503).
type SavedView ¶
type SavedView struct {
ID string `json:"id"`
Name string `json:"name"`
Config json.RawMessage `json:"config"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
SavedView is a user's stored filter set. Personal state is the only thing in this database a user would miss, so it is also the only thing `gadak export` has to dump (Constitution Article 1). Since GDK-105 it lives in local.db, which survives `rm gadak.db`.
type SchemaAuditResult ¶ added in v0.17.0
type SchemaAuditResult struct {
Stamp int // PRAGMA user_version of the open file
Supported int // len(migrations) — the level this build writes
Missing []string // "table", "table.column", or a named index
Extra []string
}
SchemaAuditResult is the diagnosis of "does this file's sqlite_master match the schema this build migrates to?". Missing is the damage signal (expected tables/columns/views/indexes that are absent). Extra is informational leftover on the file.
func (SchemaAuditResult) OK ¶ added in v0.17.0
func (r SchemaAuditResult) OK() bool
OK reports whether the file is missing anything this build expects. Surplus objects do not fail the audit.
type SchemaTooNewError ¶ added in v0.17.0
type SchemaTooNewError struct {
Path string // the mirror file
Have int // schema version found in the file
Supported int // highest schema version this build migrates to
}
SchemaTooNewError means the file was migrated by a gadak that reads a later schema than this build does — the app and the CLI ship as separately versioned formulae, so one build opening the mirror once is enough to lock the other out of that workspace (GDK-498).
It is a type, not a string, because more than one surface has to say the same true thing about it: nothing is lost, since the mirror is a cache the origin can rebuild. Recognise it with errors.As rather than matching text.
func (*SchemaTooNewError) Error ¶ added in v0.17.0
func (e *SchemaTooNewError) Error() string
type Search ¶ added in v0.13.0
type Search struct {
ID int64 `json:"id"`
Query string `json:"query"`
SearchedAt string `json:"searched_at"`
ResultCount int `json:"result_count"`
OpenedKind *string `json:"opened_kind"`
OpenedKey *string `json:"opened_key"`
}
Search is one append-only search execution. OpenedKind/OpenedKey name the item opened from that search, when one was.
type SearchExplain ¶ added in v0.15.0
type SearchExplain struct {
Key string `json:"key"`
Reason string `json:"reason"`
Field string `json:"field,omitempty"`
Score *float64 `json:"score,omitempty"`
}
SearchExplain is why one returned hit sits where it does. Reason is "key-exact", "key-prefix", or "fts". Field and Score are set only for fts (winning column and bm25). Filled only by SearchExplain; Search leaves Explain nil so the normal path does not allocate it.
type SearchMatch ¶
SearchMatch says which FTS column matched and shows a plain-text snippet. Field is "title" | "body" | "comment". Snippet has no HTML or highlight markers — the client highlights against its own query string.
type SearchResult ¶
type SearchResult struct {
Keys []string `json:"keys"`
Pages []PageLite `json:"pages"`
Total int `json:"total"`
Matches map[string]SearchMatch `json:"matches"`
Explain []SearchExplain `json:"explain,omitempty"`
// ElapsedMS is the searchAll wall time in milliseconds. Search leaves it
// 0 (omitted from JSON); SearchExplain fills it so --explain can name
// the query cost without changing the default Search contract.
ElapsedMS float64 `json:"elapsed_ms,omitempty"`
// contains filtered or unexported fields
}
SearchResult is a kind-aware hit list. Keys are issue keys (key-lookup hits first, then FTS among issues); Pages are page hits in the same window. Total is the number of hits returned (keys + pages), matching the pre-R2 meaning of total = len(results after limit). Matches maps each returned issue or page key to the winning FTS column match when FTS contributed one.
type SourceQuery ¶ added in v0.13.0
type SourceQuery struct {
ID string `json:"id"`
SourceID string `json:"source_id"`
ExternalID string `json:"external_id"`
Name string `json:"name"`
QueryText string `json:"query_text"`
Config json.RawMessage `json:"config"`
Favourite bool `json:"favourite"`
Owner string `json:"owner"`
Applied []string `json:"applied"`
Unsupported []string `json:"unsupported"`
UpdatedAt string `json:"updated_at"`
}
SourceQuery is a named query mirrored from a connector (Jira saved filter). Jira is the record: ReplaceSourceQueries rewrites one source's rows.
type SpaceRow ¶
SpaceRow is one wiki space (key + human name + kind). Source-neutral: a connector maps its space listing or page-embedded space ref onto this. Kind is the source type string (e.g. "global", "personal"); empty is allowed when only a name is known from a page hit. HomepageID is the content id of the space root page when known; empty on page-hit upserts so they do not wipe a value filled by a space listing.
type Spans ¶ added in v0.17.0
type Spans struct {
Wait *time.Duration // created -> first entry into in-progress
Progress *time.Duration // latest entry into in-progress -> done, or Now while in progress
}
Spans holds the two lifecycle spans `gadak issue` shows. nil means the changelog cannot answer: never entered progress (Wait), or no in-progress entry to measure from (Progress).
func Durations ¶ added in v0.17.0
func Durations(in DurationsInput) Spans
Durations computes the spans from the changelog — the same walk Derive does, with two questions asked of it. Nothing is stored: data-model.md keeps time-in-status deliberately absent, so this stays a query-time computation (GDK-591).
Progress measures from the latest in-progress entry, not the first: a reopened issue that re-enters progress restarts the clock, which pairs with "still in progress means until now" — first-entry would report the whole history of a reopened issue as one uninterrupted run.
type SyncResult ¶
type SyncResult struct {
Watermark string // ignored when empty or not greater than the stored one
FullSync bool // stamps last_full_sync_at
Err error // recorded as last_error; nil clears it
// Locale records the origin locale this pass fetched display names
// under (GDK-597). Empty leaves the stored marker alone — an error
// path or a source that does not localize must not clobber it.
Locale string
}
SyncResult is what a finished sync run reports.
type SyncRun ¶
type SyncRun struct {
Kind string `json:"kind"` // full | incremental (+reconcile)
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
Fetched int `json:"fetched"`
Changed int `json:"changed"`
Deleted int `json:"deleted"`
Error string `json:"error,omitempty"`
}
SyncRun is one recorded sync pass. Only meaningful runs are stored: ones that changed something, were a full pass, or failed — the watch loop's no-op incrementals would otherwise bury the history in noise.
type SyncState ¶
type SyncState struct {
SourceID string `json:"source_id"`
Watermark string `json:"watermark"`
// SyncedAt is the last run that finished without an error — the only field
// here that means "the mirror is fresh". A watermark stalls on its own
// whenever the project is simply quiet.
SyncedAt *string `json:"synced_at"`
Version int64 `json:"version"`
LastFullSyncAt *string `json:"last_full_sync_at"`
LastError *string `json:"last_error"`
// SchemaVersion is the mirror's migration level (PRAGMA user_version).
// Diagnostic surfaces (status --json, MCP gadak_status, doctor) all
// publish this field. The sync_state.schema_version column can lag
// when Open migrates after the last sync wrote the row (GDK-526), so
// SyncState overwrites the scanned column with the live PRAGMA rather
// than introducing a second JSON name for the same fact.
SchemaVersion int `json:"schema_version"`
// SchemaVersionRow is the stored column. json omitted so it cannot
// collide with schema_version on the wire; doctor uses it to name a lag.
SchemaVersionRow int `json:"-"`
// FirstSyncAt is the first successful sync for this source (retention).
FirstSyncAt *string `json:"first_sync_at,omitempty"`
// SyncCount is the number of successful sync runs (retention).
SyncCount int64 `json:"sync_count"`
// LastNotifiedAt is the OS-notification watermark. Independent of
// feed_reads: delivering a desktop alert must not mark the feed read.
LastNotifiedAt *string `json:"last_notified_at,omitempty"`
// Locale is the origin locale the jira source's display names were
// fetched under (GDK-597). NULL (pre-v35 mirror) reads as "" = English.
Locale string `json:"locale,omitempty"`
}
SyncState is the per-source sync bookkeeping.
type UserAccount ¶ added in v0.17.0
UserAccount is one row of the origin's account catalog (GDK-590). The connector collects every user payload the sync already reads — assignee, reporter, creator, comment/changelog/attachment authors — and the store caches the union in the users table. AccountType keeps the origin's spelling ("agent", "app", "atlassian", …); source-neutral on purpose, the bot judgement on those values lives in the jira package.
type VersionRow ¶ added in v0.17.0
type VersionRow struct {
ID string
ProjectKey string
Name string
Released bool
Archived bool
ReleaseDate string
}
VersionRow is one row of the project version catalog (GDK-532). Id is the join key; names rename. Released/Archived are origin flags; ReleaseDate is a date-only string or empty.