Skip to content

Import fidelity: preserve legacy GitHub ownership when copied body or labels mention another provider #143

Closed

Description

@unbraind

PR: #142
Reviewed head: a36af2a
Source:

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

  1. In a disposable real SDK tracker, create an open Issue titled Old imported title, with tag gh:example/repo#1, description GH issue #1: https://github.com/example/repo/issues/1, and body Documentation example: [jira]. Do not set any provider identity or the new marker.
  2. Supply upstream issue chore(deps): bump actions/setup-node from 6 to 7 #1 as closed/completed with title Upstream revised title to runImport, using the injectable fetch boundary. No live GitHub calls are needed.
  3. Repeat independently with an ordinary body and the copied label linear:documentation instead.

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_only for 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

  1. unbraind commented on Oct 18, 2026

    @unbraind
    OwnerAuthor

    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, and matchImportedIssue unchanged 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:

    • Missing github_link_only + ordinary copied body Documentation example: [jira]: linkOnly=true, via=provider-link
    • Missing github_link_only + copied label linear:documentation: same incorrect result
    • Ordinary legacy owner without those markers: linkOnly=false
    • Explicit github_link_only:false + copied body control: linkOnly=false
    • Genuine nonempty jira_id control: linkOnly=true

    The 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.

  2. unbraind commented on Oct 18, 2026

    @unbraind
    OwnerAuthor

    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.ts passes 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.

  3. unbraind commented on Oct 18, 2026

    @unbraind
    OwnerAuthor

    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.

  4. unbraind commented on Oct 18, 2026

    @unbraind
    OwnerAuthor

    Revalidated after integrating current main in pushed head 04bbf3603c30e6dd81efa2622e3aa0d086f845eb. node --test --test-name-pattern='legacy GitHub' test/import-fidelity.test.ts passes 10/10. Both exact isolated-identity npm run release:check and bun run release:check pass 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 was 762285d94e90e645927b90e8032292c191772b49; 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.

  5. unbraind commented on Oct 18, 2026

    @unbraind
    OwnerAuthor

    PR #148 is pushed at 7d4306774005b5542ab617926593252c6b319f80. node --test --test-name-pattern='provider-only descriptor|legacy GitHub' test/import-fidelity.test.ts passes 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:check and bun run release:check pass 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions