Documentation
¶
Overview ¶
Package ticket computes and applies ticket-source synchronization plans: comparing a requirement's rollup CANARY status against its owning non-flatfile source's remote status (proposing "transition" actions), and codifying flatfile-to-ticket promotion as paired "create_issue" + "remap" actions. Plan computation (ComputePlan) is pure and side-effect free; JiraClient (jira.go) is the only piece that talks to the network, and only when the CLI layer (pkg/cmds/ticket) is told to --apply. CANARY: REQ=CP-279; FEATURE="TicketSync"; ASPECT=Engine; STATUS=TESTED; TEST=TestCANARY_CBIN_306_ComputePlan_FlatfileCreateAndRemapPairing,TestCANARY_CBIN_306_ComputePlan_FlatfileNoNonFlatfileSource_NoAction,TestCANARY_CBIN_306_ComputePlan_JiraStatusMismatch_Transition,TestCANARY_CBIN_306_ComputePlan_MatchingStatus_NoAction,TestCANARY_CBIN_306_ComputePlan_EmptyRemoteStatus_AllTransitionsProposed,TestCANARY_CBIN_306_ComputePlan_StatusMapOverrideHonored,TestCANARY_CBIN_306_ComputePlan_RollupIsWorstOfTokens,TestCANARY_CBIN_306_ComputePlan_UnresolvedPrefixSkipped,TestCANARY_CBIN_306_ComputePlan_DeterministicOrdering,TestCANARY_CBIN_306_RollupStatus; UPDATED=2026-08-29 CANARY: REQ=ENG-3958; FEATURE="TicketDestination"; ASPECT=Engine; STATUS=TESTED; TEST=TestCANARY_ENG_3958_ComputePlan_CreateIssueStampsDestinationProject,TestCANARY_ENG_3958_ComputePlan_CreateIssueProjectEmptyWhenDestinationUnset; UPDATED=2026-08-29
Index ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultStatusMap = map[string]string{
"STUB": "To Do",
"IMPL": "In Progress",
"TESTED": "Done",
"BENCHED": "Done",
}
DefaultStatusMap is the CANARY-status -> remote-status-name mapping used when a source's StatusMap doesn't override a given status.
var ErrDecodeResponse = errors.New("decode response")
ErrDecodeResponse is the sentinel wrapped by a JSON decode failure on an otherwise-successful (2xx) response. The printed error string is always static (method + safePath + this reason) — the underlying json error's text is deliberately discarded because it can quote a fragment of the offending response body (for example an overflowing numeric literal), which must never reach a log line.
Functions ¶
func DoneStatuses ¶ added in v0.3.3
DoneStatuses returns the set of remote status names that a "done" CANARY status (TESTED or BENCHED) maps to across reg's non-flatfile sources, honoring each source's StatusMap and the DefaultStatusMap fallback. A transition whose target status is in this set advances an issue to done, so the apply layer gates it on passing evidence rather than flipping the issue blind.
func FetchRemoteStatus ¶
func FetchRemoteStatus(ctx context.Context, c *JiraClient, project string) (map[string]string, error)
FetchRemoteStatus pages through JIRA's token-based search endpoint (POST /rest/api/3/search/jql) for every issue in project, returning a map of issue key -> current status name. Used only under `--apply`, immediately before ComputePlan, so transition actions are proposed against real remote state.
The project key is validated against config.SourceKeyPattern before any request is made, and quoted inside the JQL (project = "KEY") so it can never break out of the clause. Pagination follows nextPageToken, capped at maxSearchPages.
func RollupStatus ¶
RollupStatus returns the worst (least advanced) status among tokens per STUB < IMPL < TESTED < BENCHED. Tokens with an unrecognized status are ignored. An empty slice, or a slice with no recognized statuses, returns "".
Types ¶
type Action ¶
type Action struct {
Type string `json:"type"` // create_issue | transition | remap
ReqID string `json:"req_id,omitempty"` // the CANARY requirement ID this action concerns
Issue string `json:"issue,omitempty"` // remote issue key; "" on a pending remap until applied
To string `json:"to,omitempty"` // JIRA status name (transition target)
Summary string `json:"summary,omitempty"` // for create_issue
Description string `json:"description,omitempty"`
Source string `json:"source"` // owning source's Name
Project string `json:"project,omitempty"` // create_issue only: the destination source's Project (empty if unset)
}
Action is one proposed (or, once applied, completed) ticket-sync operation.
func ComputePlan ¶
func ComputePlan(tokens []*storage.Token, reg *sources.Registry, remoteStatus map[string]string) ([]Action, error)
ComputePlan computes the deterministic, pure set of proposed sync actions.
tokens are grouped by ReqID; reg resolves each requirement's owning source; remoteStatus maps a non-flatfile issue key to its current remote status name (nil or empty means "unknown" — every eligible transition is then proposed, since no remote state contradicts it).
Rules:
- A requirement owned by a non-flatfile source gets a "transition" action when its rollup status (RollupStatus: worst of its tokens, STUB<IMPL<TESTED<BENCHED), mapped through the source's StatusMap (falling back to DefaultStatusMap), differs from remoteStatus[reqID].
- A requirement owned by a flatfile source gets a paired "create_issue"
- "remap" action, but only when the registry configures at least one non-flatfile source — otherwise there's nothing to promote it to. The create_issue's Summary is "<ReqID>: <primary feature>"; Description is a bounded feature/aspect/status/file list. The paired remap's Issue is left "" — the apply step fills in the created key.
- A requirement whose prefix resolves to no configured source is skipped.
Ordering is deterministic: requirements are visited in sorted ReqID order; a flatfile requirement's create_issue action is immediately followed by its paired remap action.
type JiraClient ¶
type JiraClient struct {
BaseURL string
Email string
Token string
// HTTPClient lets tests point the client at an httptest server; nil
// defaults to a client with a bounded 15s timeout.
HTTPClient *http.Client
// Sleep injects the backoff wait so tests can assert on (and skip) the
// retry delays; nil defaults to time.Sleep.
Sleep func(time.Duration)
}
JiraClient talks to the JIRA Cloud REST API (v3) using basic auth (email + API token). Every method is safe for concurrent use. CANARY: REQ=CP-279; FEATURE="TicketSync"; ASPECT=Engine; STATUS=TESTED; TEST=TestCANARY_CBIN_306_JiraClient_CreateIssue,TestCANARY_CBIN_306_JiraClient_CreateIssue_ErrorStatus,TestCANARY_CBIN_306_JiraClient_TransitionIssue_ResolvesIDByName,TestCANARY_CBIN_306_JiraClient_TransitionIssue_NoMatch,TestCANARY_CBIN_306_FetchRemoteStatus_Paged; UPDATED=2026-08-30 CANARY: REQ=CP-279; FEATURE="TicketSync"; ASPECT=Security; STATUS=TESTED; TEST=TestJiraSearchJQLPagination,TestJiraSearchInvalidProjectKey,TestJiraRetry429ThenSuccess,TestJiraNoRetryOn400,TestJiraBodyCap,TestJiraErrorRedaction,TestJiraStringRedactsToken,TestJiraTimeout15s,TestAuditF16; UPDATED=2026-08-30
func (*JiraClient) CreateIssue ¶
func (c *JiraClient) CreateIssue(ctx context.Context, project, issueType, summary, description string) (string, error)
CreateIssue creates a new issue of issueType in project with summary and description (wrapped as an ADF paragraph), returning the created issue's key (e.g. "CP-12").
func (*JiraClient) String ¶ added in v0.3.3
func (c *JiraClient) String() string
String renders the client with its Token redacted, so a stray %v (in a log line or wrapped error) can never spill the API token.
func (*JiraClient) TransitionIssue ¶
func (c *JiraClient) TransitionIssue(ctx context.Context, key, toStatusName string) error
TransitionIssue moves key to the transition whose target status name (transitions[].to.name) matches toStatusName case-insensitively.