Repository navigation
Import fidelity: preserve legacy GitHub ownership when copied body or labels mention another provider #143
Description
PR: #142
Reviewed head: a36af2a
Source:
Lines 59 to 79 in a36af2a
| const declared = identities.size > 0 || (item.github_link_only !== false && item.tags?.some(tag => /^(linear|jira|ado):/i.test(tag))) || | |
| Object.entries(item).some(([key, value]) => /^(provider|source|provenance)$/i.test(key) && (typeof value === "string" | |
| ? /^(?:pm-)?(?:linear|jira|ado)$/i.test(value.trim()) | |
| : /"(?:provider|source|system)"\s*:\s*"(?:pm-)?(?:linear|jira|ado)"/i.test(JSON.stringify(value) ?? ""))); | |
| return Object.entries(item).filter(([key, value]) => identities.has(key) || | |
| (declared && /url|link|provenance|source|external/i.test(key)) || | |
| ((/description|provenance/i.test(key) || item.github_link_only !== false) && /description|body|provenance/i.test(key) && typeof value === "string" && | |
| /\[(linear|jira|ado)\]|\b(linear|jira|ado)_(id|key|url)=|Jira(?:\s+[A-Z][A-Z0-9]+-\d+)?:\s/i.test(value))) | |
| .map(([, value]) => typeof value === "string" ? value : JSON.stringify(value)).join("\n"); | |
| } | |
| /** Match GitHub locators or provider back-links before creation, refusing multiple owners. */ | |
| export function matchImportedIssue(items: readonly PmItem[], issue: GhIssue, tagged?: PmItem): ImportMatch { | |
| const issueUrl = locators(issue.html_url)[0]!; | |
| const bodyUrls = new Set(locators(issue.body ?? "")); | |
| const candidates = items.filter(item => item.id && (item.id === tagged?.id || | |
| Object.entries(item).some(([key, value]) => /url|link|provenance|source|external|description/i.test(key) && locators(value).includes(issueUrl)) || | |
| locators(providerProvenance(item)).some(url => bodyUrls.has(url)))); | |
| if (candidates.length > 1) throw new CommandError(`GitHub issue #${issue.number} has multiple existing owners; import plan is not idempotent. No item was written.`); | |
| const item = candidates[0]; | |
| const provider = item && (Boolean(providerProvenance(item)) || (item.github_link_only !== false && item.tags?.some(tag => /^(linear|jira|ado):/i.test(tag)))); |
Legacy GitHub-owned records have the existing gh:owner/repo#N provenance tag but no github_link_only field. The !== false guards treat a copied issue body containing an ordinary example such as [jira], or a copied label such as linear:documentation, as external ownership. Both import modes then preserve stale native title/status and persist github_link_only=true. Subsequent syncs remain link-only.
Reproduction
- In a disposable real SDK tracker, create an open Issue titled
Old imported title, with taggh:example/repo#1, descriptionGH issue #1: https://github.com/example/repo/issues/1, and bodyDocumentation example: [jira]. Do not set any provider identity or the new marker. - Supply upstream issue chore(deps): bump actions/setup-node from 6 to 7 #1 as closed/completed with title
Upstream revised titletorunImport, using the injectable fetch boundary. No live GitHub calls are needed. - Repeat independently with an ordinary body and the copied label
linear:documentationinstead.
Actual in both plain and atomic modes: the import reports one updated record, but the result is status=open, title=Old imported title, github_state=closed, github_link_only=true.
Control: remove the misleading body/label marker while retaining the same provenance, and both modes correctly update title/status and set github_link_only=false.
This does not mean genuine external-provider evidence should be ignored. Separate controls confirm nonempty jira_id, explicit provider=pm-jira with an external locator, and exporter description provenance remain link-only even with an existing GitHub tag and github_link_only=false.
Expected and acceptance criteria
- Existing GitHub import provenance must not silently change ownership solely because ordinary copied body/label text resembles provider markers and the new marker is absent
- Add real-SDK migration regressions without
github_link_onlyfor both plain and atomic imports, covering body and label cases; assert native title/status/lifetime reconcile and retries remain GitHub-owned - Keep genuine external identities/descriptors/exporter provenance authoritative and preserve those owners' content, status, release and lifecycle
- Distinguish copied GitHub data from real ownership evidence rather than suppressing all provider-looking text
The current negative ownership test explicitly supplies github_link_only:false, so it misses records produced by the previous release.
Scope and validation
This is reproduced on the unmerged PR head linked above, not a claim about a shipped incident. PR142 explicitly promises GitHub ownership of imported records and that body/label examples do not transfer ownership.
Independent build, build:test and 29/29 existing real-SDK import-fidelity fixtures passed. An extra synthetic reproduction confirms all four cases (plain/atomic × body/label), with two normal-import and three authoritative external-owner controls. Full 769-test release gates were not rerun locally. No live provider mutations were performed.
All-status issues and PR142 reviews were checked; no matching finding was present.
Activity
unbraind commentedon Oct 18, 2026
Rechecked after #142 merged: this issue remains reproducible on default-branch commit
ed0e3d6c869076486f68325696f001fbcf3e8660, rather than only the earlier unmerged PR head.Bounded new verification: I extracted
locators,providerProvenance, andmatchImportedIssueunchanged from the immutable source and executed them with synthetic objects under Node v24.19.0. No SDK, GitHub, or other provider operation ran in this probe.Results for an existing GitHub-tagged item:
github_link_only+ ordinary copied bodyDocumentation example: [jira]:linkOnly=true,via=provider-linkgithub_link_only+ copied labellinear:documentation: same incorrect resultlinkOnly=falsegithub_link_only:false+ copied body control:linkOnly=falsejira_idcontrol:linkOnly=trueThe downstream consequence is also still present in that commit: atomic planning produces a tag-only update; plain import bypasses native title/status reconciliation; finalization persists the link-only marker and skips native lifecycle/release refresh.
The canonical import-fidelity goal requires preserving source lifecycle while retaining genuine cross-provider ownership. These legacy GitHub-owned controls should remain eligible for source reconciliation.
Keep this issue as the owner. The migration acceptance remains: real-SDK plain and atomic tests with the marker absent must refresh native title/status/lifetime for both copied-text cases, remain GitHub-owned on retry, and preserve genuinely declared external owners.
Scope: the new evidence establishes the classifier behavior and traces its call sites on merged source. It does not rerun the issue's original full real-SDK reproductions or release gates, establish that this commit has been published/adopted, or claim an affected real tracker or production incident.
unbraind commentedon Oct 18, 2026
Fixed in PR #148 at
f94e4e5e7f06b5a3d443f082a1b4f4215c967534(pushed). Legacy GitHub provenance prevents copied body/label hints from transferring ownership, while real provider identities/descriptors/exporter provenance remain authoritative.node --test --test-name-pattern="legacy GitHub" test/import-fidelity.test.tspasses 10/10 real-SDK cases, including all four plain/atomic migrations with lifecycle and retry assertions plus six external-owner controls. Reverting only import-fidelity.ts fails all 4/4 migration regressions; restoring it passes. Full isolated npm/Bun gates and CI are in progress.unbraind commentedon Oct 18, 2026
Final delivery for PR #148 is pushed at
96a4b4bedea2943797a02510e7f4e7aad395b8f6. Real-SDK legacy ownership and external-owner controls: 10/10 pass; reverting only the fix fails all four migration regressions. Both isolated CI release commands pass 824/824 with zero failures/skips and exact 100% in all four coverage dimensions across 14 modules. Final-head Node 22/26, native Bun acceptance, CodeQL and Semgrep pass: https://github.com/unbraind/pm-github/actions/runs/39098874412 . Implementation and tests match the release-gated source; the temporary validation workflow was removed. No PR was merged.unbraind commentedon Oct 18, 2026
Revalidated after integrating current main in pushed head
04bbf3603c30e6dd81efa2622e3aa0d086f845eb.node --test --test-name-pattern='legacy GitHub' test/import-fidelity.test.tspasses 10/10. Both exact isolated-identitynpm run release:checkandbun run release:checkpass 836/836 with exact 100/100/100/100 coverage across all 14 authored modules: https://github.com/unbraind/pm-github/actions/runs/39105101037. The validated head was762285d94e90e645927b90e8032292c191772b49; final production code, tests, scripts, docs and dependency pins are byte-identical. Existing behavioral revert proof is preserved, and PR #148 retains its closing link. Final-head CI is tracked in the PR body; this comment does not close the issue.unbraind commentedon Oct 18, 2026
PR #148 is pushed at
7d4306774005b5542ab617926593252c6b319f80.node --test --test-name-pattern='provider-only descriptor|legacy GitHub' test/import-fidelity.test.tspasses 12/12. The CodeRabbit descriptor-only case is now covered without external_url or github_link_only; native ownership survives plain/atomic retries. Reverting only the new production predicate fails both added cases; restoring it passes.Both exact isolated
npm run release:checkandbun run release:checkpass 893/893 with zero failures/skips/cancellations/todos and exact 100/100/100/100 source coverage: https://github.com/unbraind/pm-github/actions/runs/39119457879 . Fresh-clone strict health passes. Final-head Node 22/26 CI, including native Bun acceptance, passes (https://github.com/unbraind/pm-github/actions/runs/39120403305); CodeQL and Semgrep pass; no merge or release was performed.