Skip to content

fix(tasks): a sourceRef reopen preserves the completed run's notes and reports a differing title - #1748

Merged
lilyshen0722 merged 1 commit into
mainfrom
fix/task-134-reopen-preserves-notes
Sep 19, 2026
Merged

lilyshen0722 merged 1 commit into
mainfrom
fix/task-134-reopen-preserves-notes

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

What happened

commonly_create_task with a sourceRef that matches an already-done row in the pod reopens that row in place instead of creating anything — sourceRef is an idempotency key with a unique partial index behind it, and re-filing a bug when its source PR becomes active again is a deliberate use.

Two things that branch did silently, both hit live today on TASK-133 while filing a follow-up row:

  1. The submitted title was discarded and never compared. Nothing in tasksApi.ts:253-275 read title. The response returned the old row's title with no field marking the difference.
  2. notes was replaced wholesale with one sentence. TASK-133's notes held the full completion writeup — the shipped PR, the patch-id verification against the gated head, the mutation proofs. The row's updates history survived, which is the only reason it was recoverable.

Both are invisible to a caller that trusts the success shape: the response was { alreadyExists: false, reopened: true, task: <the pre-existing row> }. reopened: true is truthful; alreadyExists: false reads as "nothing existed before" on a call whose only effect was to mutate a pre-existing row.

The change

  • The previous notes are pushed into the append-only updates history before the reopen note replaces them, so nothing is lost and notes stays one sentence.
  • A submitted title that differs from the row's is reported in both notes and the history. The row still keeps its own title — applying it would be a rename of a different row, which is not what a follow-up filing means.
  • When the submitted title is identical, no extra history is written (no conservation noise).
  • alreadyExists/reopened are left as wire values, documented in a comment at the branch.

Verification

backend/__tests__/service/tasks.source-ref-idempotency.test.js7/7 on Node 22, two new cases on top of the five existing ones.

Mutation proof, each run alone against the real route:

  • preservation block removed → the new case red
  • titleDiffers forced false → the new case red
  • title report dropped from notes while the history keeps it → the new case red
  • preservation made unconditional (if (true)) → the no-noise case red, so that assertion is non-vacuous rather than passing because the fixture is empty

routes/tasksApi.ts eslint: 0 errors. tsc: no errors in either touched file.

Not in scope (needs a human ruling, noted on TASK-134)

Whether a differing title should reopen at all, or whether that is a genuinely new task the sourceRef lookup was never meant to swallow. The change is correct under either answer.

Related

AX audit entry 59 lands with #1742 (audit stream, so the two do not conflict on the same file), which is where the tool-vs-behaviour mismatch is recorded.

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gate at e72ab052744ba78af33fbe03a385ecda190160b6: passes, nothing blocking.

tasks.source-ref-idempotency.test.js 7/7 on Node 22. Each mutation changes one line (git diff --numstat 1+1-):

  • preserve inverted (if (!previousNotes), so it archives only when there is nothing to archive): 2 red
  • titleDiffers = !!title (any submitted title counts as differing): 1 red
  • titleDiffers = false: 1 red
  • previousNotes = '' (nothing to move): 1 red
  • drop .trim(): green. A whitespace-only notes would then be archived as a blank "Previous notes preserved" entry. Cosmetic, unpinned, not worth a row on its own.

A note on method, since you raised it: my first attempt at the preserve mutation was if (false), which printed Tests: 0 total — ts-jest refusing unreachable code, not the suite failing. Same trap you caught and refused to count. The inversion above compiles and fails behaviourally, so it measures the guard.

Two things I checked beyond the diff:

  • existing.updates.push(...) is safe on a legacy row: updates is an array field in Task.ts, so mongoose hydrates it to [] rather than undefined.
  • Ordering is the load-bearing part and it is correct: the archive push happens before existing.notes is reassigned, so it captures the completed run's writeup and not the reopen sentence. The previousNotes = '' mutation is what pins that.

@samxu01
samxu01 force-pushed the fix/task-134-reopen-preserves-notes branch 2 times, most recently from 91c51c6 to 490201d Compare September 18, 2026 13:25

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-gate at 490201dcf513539efb2f270e15db0e0094be6b5a: carries; still passes.

Rebase-only confirmed by measurement, not by the claim: patch-id 6a27db39 identical to my e72ab052 gate, 1 commit over main, and exactly the two files (tasksApi.ts + tasks.source-ref-idempotency.test.js). The head that briefly carried the stray integrations commit is gone — this head is clean.

All the findings from that gate stand: preserve inverted 2 red, titleDiffers forced either way 1 red each, previousNotes = '' 1 red, and the .trim() still unpinned (cosmetic).

@samxu01
samxu01 force-pushed the fix/task-134-reopen-preserves-notes branch from 490201d to 168c8f1 Compare September 19, 2026 11:01

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carry re-gate at 168c8f11: patch-id 6a27db39 unchanged from the e72ab052 gate, 1 commit, the same two files. Rebase only; the preserve/titleDiffers findings stand.

…d reports a differing title

A create carrying a sourceRef that matches a DONE row reopens that row in
place (the ref is an idempotency key, backed by a unique partial index).
Two things it did silently: it discarded the submitted title without
comparing it, and it overwrote `notes` — the field this pod treats as the
durable record — with a single sentence, destroying a completed run's
writeup. Hit live on TASK-133 today (AX entry 59).

The previous notes now move into the append-only updates history before the
reopen note replaces them, and a submitted title that differs from the
row's is reported in both `notes` and the history instead of vanishing.
When nothing differs, no extra history is written.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant