tracker

package
v1.801.433 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package tracker is your org's issue tracker: projects, issues, and the filters to find them.

It mounts the Hanzo Cloud /v1/tracker/* surface — a native-Go, per-org tracker on SQLite. It is the durable replacement for the prior Svelte hanzo.team tracker, whose upstream each-block reactive-batching render race left issue lists rendering zero rows. Native @hanzo/gui over this one store sidesteps that entire class of bug: the rows come back as plain JSON and render deterministically.

Org isolation is enforced SERVER-SIDE on every request: the org is principal.Org(c) — the value SanitizeIdentity minted from the VALIDATED bearer owner claim (HIP-0026) — and NEVER a client-supplied header. Every store query filters WHERE org=?, so one org can never read or mutate another's projects or issues.

Surface (all org-scoped; /v1 only):

POST   /v1/tracker/projects                          create a project        -> Project (201)
GET    /v1/tracker/projects                          list projects           -> [Project]
GET    /v1/tracker/projects/:key                     project detail          -> Project
PATCH  /v1/tracker/projects/:key                     update a project        -> Project
DELETE /v1/tracker/projects/:key                     delete a project (+ issues)
POST   /v1/tracker/projects/:key/issues              create an issue         -> Issue (201)
GET    /v1/tracker/projects/:key/issues[?status=&kind=&repo=&source=]  list  -> [Issue]
GET    /v1/tracker/projects/:key/issues/:num         issue detail            -> Issue
PATCH  /v1/tracker/projects/:key/issues/:num         update an issue         -> Issue
DELETE /v1/tracker/projects/:key/issues/:num         delete an issue

Order 129: binds /v1/tracker/* before the AI subsystem's /v1/* catch-all (150). serve.go auto-registers GET /v1/tracker/health.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the tracker surface onto app per HIP-0106. Complex flavour: it holds a package-global (mounted) so Shutdown can close every per-tenant store, so it constructs the Service value directly rather than via cloud.Mount.

func Shutdown

func Shutdown() error

Shutdown closes every open per-(org,project) tracker store. Idempotent.

Types

type AgentPR

type AgentPR struct {
	Identifier string // "KEY-N" — the human handle
	ProjectKey string // the tracker project KEY the row lives under
	Number     int    // per-project monotonic number
}

AgentPR is the created row's stable handle: KEY-N plus the parts to build it.

func CreateAgentPR

func CreateAgentPR(ctx context.Context, in AgentPRInput) (AgentPR, error)

CreateAgentPR opens the PR work item for a coding run: it get-or-creates the tracker project keyed off the repo (so a repo's PRs share one board), then inserts the Kind:"pr" Source:"agent" issue and returns its KEY-N identifier. Idempotent at the project layer (a concurrent create resolves back to the same project); the issue itself is always a new row (per-run PR).

type AgentPRInput

type AgentPRInput struct {
	Org      string // tenant (isolation key)
	Project  string // IAM project slug (physical DB scope); "" = the org default
	Repo     string // git repo the branch was pushed to (the Repo binding)
	Base     string // base branch the PR targets (recorded in the body)
	Head     string // the pushed branch (the ExtRef anchor)
	Title    string // work-item title (from the task)
	Body     string // description: summary / diffstat + session link
	Assignee string // the agent ref (e.g. "hanzo")
}

AgentPRInput is the closed set a coding run supplies to open its PR row. The four discriminators are set here: Kind is always "pr", Source always "agent", Repo binds the git repo, and Head (the pushed branch) is the ExtRef anchor.

type Issue

type Issue struct {
	ID          string
	ProjectID   string
	Org         string
	Number      int
	Kind        string // issue | pr | epic (default "issue")
	Source      string // team | git | crm | helpdesk | cms | agent (default "team")
	Repo        string // git repo binding; "" = not repo-bound
	ExtRef      string // external anchor (PR branch, or link into another plane)
	Title       string
	Description string
	Status      string
	Priority    string
	Assignee    string
	Labels      string
	CreatedAt   int64
	UpdatedAt   int64
}

Issue is THE ONE engineering/project work-item primitive for Hanzo — polymorphic by Kind so a git issue, a pull request and a parent epic are all the same row, and every work-item surface (hanzo.team's board, a git repo's Issues/PRs tab, an agent's queue) is a FILTER over this table, never a second tracker. It is NOT the domain-record plane: helpdesk tickets, CMS content and CRM deals live on framework.DocType / crm and link here by ExtRef — see contract.go for the three-plane boundary. Number is monotonic PER PROJECT (KEY-1, KEY-2, …), allocated under the single-writer transaction in CreateIssue so it never races. Status is the board column; Labels is a comma-joined tag string (split at the HTTP boundary).

The four discriminators (Kind, Source, Repo, ExtRef) are the alignment spine:

  • Kind: issue | pr | epic — what it IS (the small closed work-item set).
  • Source: team | git | crm | helpdesk | cms | agent — which surface OPENED it.
  • Repo: the git repo it belongs to (Kind pr/issue from git); "" otherwise — so a repo's Issues/PRs tab is `ListIssues(... Filter{Repo, Kind})`.
  • ExtRef: the external anchor (PR branch, or a link INTO another plane).

They are identity, set once at Create and immutable thereafter (Update touches only the mutable board state), so a row never migrates between surfaces.

type IssueFilter

type IssueFilter struct {
	Status string
	Kind   string
	Repo   string
	Source string
}

IssueFilter narrows ListIssues. All fields optional (empty = no constraint). This is the ONE knob every work-item surface turns: hanzo.team passes {Status}, a git repo's Issues tab passes {Repo, Kind:"issue"}, its PRs tab {Repo, Kind:"pr"}, an agent's queue {Source:"agent"}.

type Project

type Project struct {
	ID          string
	Org         string
	Key         string
	Name        string
	Description string
	CreatedAt   int64
	UpdatedAt   int64
}

Project is an org-scoped issue tracker project (a Linear "team"). Its Key is the org-unique, uppercase handle that both routes the URL (/v1/tracker/ projects/:key) AND prefixes every issue identifier (KEY-<number>). Org isolation is the org column, filtered on every query.

Distinct from projects.Project (not a duplicate): that is the Slug-keyed deployable site forming this tracker's physical tenant boundary; these KEY-prefixed teams are rows WITHIN it.

type Store

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

Store is one (org, project)'s tracker database — ONE SQLite file per project at {DataDir}/orgs/{orgSlug}/projects/{projectSlug}/tracker.db (opened via cloud.OrgDB). tracker is project-scoped: the physical boundary is the IAM project, with the org column retained as defense-in-depth. MaxOpenConns(1) serializes writes against the file lock (and makes the CreateIssue number allocation a safe read-modify-write inside one transaction).

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database.

func (*Store) CreateIssue

func (s *Store) CreateIssue(ctx context.Context, i Issue) (Issue, error)

CreateIssue allocates the next per-project number and inserts the issue in ONE transaction. The transaction holds the single pool connection for its whole duration, so the MAX(number)+1 read and the INSERT are serialized against any concurrent create — the number can never be handed out twice.

func (*Store) CreateProject

func (s *Store) CreateProject(ctx context.Context, p Project) error

CreateProject inserts one project. A UNIQUE(org,key) violation surfaces as errConflict.

func (*Store) DeleteIssue

func (s *Store) DeleteIssue(ctx context.Context, org, projectID string, number int) (bool, error)

DeleteIssue removes one issue scoped to (org, project, number). Reports whether a row was deleted.

func (*Store) DeleteProject

func (s *Store) DeleteProject(ctx context.Context, org, key string) (bool, error)

DeleteProject removes a project and all its issues in one transaction. Reports whether a project row was deleted.

func (*Store) GetIssue

func (s *Store) GetIssue(ctx context.Context, org, projectID string, number int) (Issue, error)

GetIssue returns one issue scoped to (org, project, number) or errNotFound.

func (*Store) GetIssueByExtRef

func (s *Store) GetIssueByExtRef(ctx context.Context, org, projectID, extRef string) (Issue, error)

GetIssueByExtRef returns the issue anchored to extRef within (org, project) or errNotFound — the idempotency lookup for the external-mirror upsert. ExtRef is not globally unique (it is unique per external system), so it is always scoped to the project the mirror files into.

func (*Store) GetProject

func (s *Store) GetProject(ctx context.Context, org, key string) (Project, error)

GetProject returns the project for (org,key) or errNotFound.

func (*Store) ListIssues

func (s *Store) ListIssues(ctx context.Context, org, projectID string, f IssueFilter) ([]Issue, error)

ListIssues returns issues for a project narrowed by IssueFilter. Ordered by status then number so a status-grouped view renders deterministically and a single-column board reads oldest-first.

func (*Store) ListProjects

func (s *Store) ListProjects(ctx context.Context, org string) ([]Project, error)

ListProjects returns every project for org, most-recently-updated first.

func (*Store) UpdateIssue

func (s *Store) UpdateIssue(ctx context.Context, i Issue) error

UpdateIssue overwrites the mutable fields of an issue scoped to (org, project, number). id+project_id+org+number+created_at are immutable.

func (*Store) UpdateProject

func (s *Store) UpdateProject(ctx context.Context, p Project) error

UpdateProject overwrites the mutable fields (name, description). org+key+id+ created_at are immutable.

Jump to

Keyboard shortcuts

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