Repository navigation
✨ publish: Jira target (Epic + Stories, Cloud and Data Center) - #117
Conversation
Add a Jira work-source target behind the publish Target interface. The RFC is filed as an Epic in publish.jira.project_key and each child as a Story under it (parent, or epic_link_field on Data Center); depends_on edges become Blocks links, best effort. The spek:publish marker is kept as the last line of the description and in the spek.publish issue property together with a body digest, so re-publish finds and compares what it filed even though Jira stores ADF/wiki markup rather than Markdown. Property JQL is tried first and falls back to a description text search where properties are not indexed. Cloud (REST v3, ADF, JIRA_EMAIL + JIRA_API_TOKEN) and Data Center (REST v2, wiki markup, JIRA_PAT, JIRA_CA_BUNDLE) are both supported. Includes an in-process fake Jira (jiratest) and unit/command tests covering create, idempotent re-run, --update, link failures as warnings, refusals and dry-run making no network call. Signed-off-by: Andrew Anderson <andy@clubanderson.com>
There was a problem hiding this comment.
Decision needed: @nicholasjackson should decide whether Jira publishing must require HTTPS before sending credentials, or intentionally support plain HTTP Data Center sites.
The Jira dependency-link recovery needs a fix before this is ready.
- Medium —
internal/publish/jira/jira.go:340: failed links are never retried.blockedByruns only when the dependant or blocker appears inres.Created. If the initial link POST fails, the issues and their marker properties still exist; on the next--update,Findreuses both,res.Createdis empty, and this guard skips the link without even returning a warning. Adding a dependency between two existing children is skipped for the same reason. This contradicts the PR body's retry promise and leaves the native dependency graph incomplete. Reconcile missing links for existing endpoints too, with a regression case that fails a link, restores the service, and republishes.
Credential transport decision for @nicholasjackson: cmd/publish.go:331-341 accepts a nonempty base URL without checking its scheme, and internal/publish/jira/jira.go:465-483 attaches Basic/Bearer credentials and sends the request. An HTTP site therefore receives the credential without TLS. Issue #112 specifies the credential sources and CA support but does not settle whether insecure transport is allowed. Please decide the policy before this new authentication path lands.
I read the command caller, target, fake server and tests against the base implementation, then checked #112 and this description. Local test execution was denied by the reviewer environment, so the recovery finding is based on the verified control flow; no test result is claimed.
— hive: agent=reviewer backend=codex model=gpt-6.1-sol codex=0.167.0
|
@clubanderson coupe of comments on the PR, but I am pretty much happy to merge: Applies to the whole stack, including main’s GitHub target: links are only created for issues filed in the same run. blockedBy runs only for new issues, and updateIssue never sets parent. A failed link, a newly added depends_on, or a re-created Epic is never repaired by --update, and a no-op run doesn’t try at all. The PR description says links are “retried on --update”, and the comment in github.go says the same, but neither is true. Hive relies on these links for umbrella and ordering, so links need reconciling against what’s filed on every run. I’m happy for that to be a separate PR against main, but the claim should come out of this description until it lands. Jira-specific: Data Center Stories under an Epic. On DC, a Story links to its Epic through the Epic Link custom field; parent is only for sub-tasks. With epic_link_field unset, we send parent, which I’d expect DC to reject. The DC fake accepts fields.parent on Stories, so the e2e can’t catch this. Either make epic_link_field required for DC, or discover it, and make the fake reject parent on non-sub-task types. |
…atomic properties, token search - parent and Blocks links are read back from each issue and refiled when missing, on every run, not only for issues created in the same run; the result lists them as linked - Data Center: the Epic Link field is discovered from GET field when publish.jira.epic_link_field is unset; the fake refuses parent on anything but a sub-task, as the real site does - the spek.publish property travels in the create/edit request, so no issue exists without its marker; the separate property write is gone - Find searches a plain alphanumeric token (spekpub<hash>) written on the line before the marker, not the property JQL (needs an indexed property app) nor artifact_id= (Lucene specials unescaped) - a publish that fails part way records what it filed in published:, and a re-run passes the recorded URLs to the target so they are read directly while the search index catches up - a plain-http base URL is refused unless loopback or publish.jira.insecure_http: true - fakes assert instead of require on handler goroutines; Snapshot/Calls accessors; SearchLag and FailCreateAfter options Signed-off-by: Andrew Anderson <andy@clubanderson.com>
|
Thanks Nic — all addressed in a151ea7 (plus the stack re-based on it). Point by point: Links only created in the same run / "retried on --update" (whole stack). True as written. Fixed for Jira in a151ea7: DC Stories under an Epic. Fixed. With Find on Cloud / unescaped Lucene. Fixed. Create, then set the property. Fixed. The property goes in the Re-run after a partial failure / eventual consistency. Fixed two ways: (1) Minor: marker visible in the description. Left as is, deliberately: hive's Branch is green locally ( |
There was a problem hiding this comment.
Decision needed: @nicholasjackson should decide whether Jira credentials may follow redirects across origins or whether the new client must restrict authenticated redirects to the configured origin.
Two issues need attention before this lands:
-
Medium — reversed dependency read-back (
internal/publish/jira/jira.go:333-340).filedFromrecordsoutwardIssueasBlockedBy. For Jira's default Blocks type, outward means the current issue blocks the linked issue; blockers are returned asinwardIssue(Atlassian model). Correct links therefore appear missing on every retry, while an opposite-direction link can suppress repair. The fake repeats this reversal atinternal/publish/jira/jiratest/fake.go:90-93. Read the inward end and correct the fake and regression assertions. -
High — redirects bypass the HTTPS credential guard (
internal/publish/jira/jira.go:87,:640-650).requireHTTPSchecks only the initial URL; the clients leaveCheckRedirectunset. Go follows HTTPS-to-HTTP redirects on the same host with Authorization retained (Go client documentation). A Jira/reverse-proxy redirect can therefore send PAT or Basic credentials in clear withinsecure_http: false. Enforce transport policy on redirects and cover the downgrade with dummy credentials.
Per #112, Cloud/DC authentication and publishing are intended scope. The unresolved security choice is the authenticated redirect trust boundary; @nicholasjackson is the approver in OWNERS.
Validation: read the adapter, renderer, fake, shared Diff, command callers, PR body and #112 (no comments). Existing build/test checks report success. Local tests were denied by reviewer execution policy, so I did not run them.
— hive: agent=reviewer backend=codex model=gpt-6.1-sol codex=0.167.0
|
/approve |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: nicholasjackson The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Fixes #112
Adds a Jira publish target following the GitHub target from #115 exactly: same
Targetinterface, same ordering (RFC → children → links → RFC body rewrite), same refusal codes withnext_action, dry-run with zero network calls, httptest fake with recorded assertions.What it does
parenton Cloud; on Data Center (whereparentis sub-task only) the Epic Link custom field,publish.jira.epic_link_fieldor discovered fromGET fieldwhen unset. Issue types configurable.depends_on→Blocksissue links (blocker outward, dependant inward). Parent and links are read back and reconciled on every run (parent/Epic Link +issuelinksin the search fields); anything missing is refiled and reported aslinked. Failures are warnings.spek.publishissue property with abody_digest, so idempotency works although Jira stores ADF/wiki markup, not Markdown. The property is set in the create/edit request itself (properties), so no issue ever exists without it.project = <key> AND description ~ "spekpub<sha256(artifact_id)[:16]>"— a plain alphanumeric token on the line before the marker, so no Lucene escaping; theissue.property[...]JQL is not used (needs an app to index the property on both Cloud and DC). Issues recorded in the artifact'spublished:frontmatter are then read directly (GET issue/KEY), so a re-run after a partial failure or ahead of the search index reuses them. A partial failure records what it filed before returning.http://base URLs are refused (publish_target_unconfigured) unless loopback orpublish.jira.insecure_http: true.JIRA_EMAIL+JIRA_API_TOKEN(Cloud) orJIRA_PAT(DC); site viaJIRA_BASE_URLorpublish.jira.base_url;JIRA_CA_BUNDLEfor private CAs. Labels whitespace →-(hive convention).publish_target_unconfigured(no project_key / base URL / bad CA bundle),publish_auth_missing.Interface decisions
FiledgainedKeyandBodyDigest;Diffcompares digests when present (SameAsFiled).Result.RFCKey/ResultChild.Keyadded (omitempty) so GitHub output is unchanged.parent_labelfor Jira: hive's default JQL already excludesissuetype = Epic.Tests
make lint && make testgreen locally (internal/publish/jira,jiratestfake — refusesparenton DC Stories, no property JQL,SearchLag/FailCreateAfter— cmd tests for dry-run with zero calls, refusals incl. plain http, file-then-update, partial failure then resume, Data Center v2 with Epic Link discovery).Not in this PR: the GitHub target (main) has the same "links only on create" gap; separate PR against main.
Docs: README publish section + config block,
docs/publish-hive-handshake.mdJira row.