Skip to content

fix: dedup dependency auto-apply to prevent duplicate tool mints (Gap #10)#23

Open
dhruva-reddy wants to merge 2 commits intomainfrom
chore/gap-10-fix
Open

fix: dedup dependency auto-apply to prevent duplicate tool mints (Gap #10)#23
dhruva-reddy wants to merge 2 commits intomainfrom
chore/gap-10-fix

Conversation

@dhruva-reddy
Copy link
Copy Markdown
Contributor

Summary

Fixes Gap #10 — targeted assistant pushes were minting duplicate dashboard tools when bootstrap pull stored an existing dashboard tool under a name-slugged state key (e.g. end-call-67aea057) instead of the user's original local key (b2b-invoice-end-call). The exact-key lookup in ensureToolExists / ensureStructuredOutputExists missed and POSTed a fresh duplicate; each subsequent targeted push repeated the cycle, accumulating dashboard orphans.

What changed

src/dep-dedup.ts (new) — pure-logic helpers (slugify, extractBaseSlug, extractResourceName, findExistingResourceByName). Imports only ./types.ts so it's testable without booting config.ts's CLI parse.

src/push.tsensureToolExists and ensureStructuredOutputExists now run a name-based dedup check between the exact-key short-circuit and the create path:

  1. State-side: scan state for any key whose extractBaseSlug matches the local payload's slugified name (catches bootstrap-renamed keys).
  2. Dashboard-side: lazy-fetch the live /tool (or /structured-output) list once per push, cached on DependencyContext, and check for a remote resource with the same canonical name.

When >1 distinct UUID matches the same name (real on-dashboard duplicates from prior bug runs), pick the lex-smallest UUID for stable, deterministic adoption; warn naming the loser UUIDs; point at npm run cleanup. Never mint another duplicate.

Adoption flow handles two lifecycle traps surfaced in code review:

  • Orphan-deletion guard: after re-keying state to the adopted UUID, drop other state keys pointing at the same UUID and mark them touched, so a subsequent full push (--force) doesn't see them as "tracked but no local file" and DELETE the dashboard resource we just adopted. (Stack J / mergeScoped flushes the deletion.)
  • Drift-check + local-edit safety: adoption routes through applyTool / applyStructuredOutput (which take the PATCH path because existingUuid is now set), running the standard checkDriftForUpdate flow and pushing the local payload. Avoids recording a fake lastPushedHash that would silently drop locally-edited dependencies on the next push.

tests/dep-dedup.test.ts (new, 15 tests) — covers findExistingResourceByName for: state-only match, dashboard-only match, both-agree, ambiguous (state-vs-state and state-vs-dashboard), no-name payload, exact-key-excluded (caller's job), no-match, empty inputs, top-level-name-wins, remote payload using top-level name.

Test plan

  • npm run build clean (tsc --noEmit)
  • npm test 114/114 pass (was 99 before; +15 new dep-dedup tests + 0 regressions)
  • Pipeline followed: planner (inline) → implementer → test-writer → code-reviewer (2 blocking findings → fixed → re-implemented)
  • Smoke against a sandbox env with a state file containing a bootstrap-renamed tool entry — relies on unit coverage; integration harness would require a stub API server (documented in test-writer report as deliberately skipped)

Skipped / non-blocking notes from review

  • Dry-run dashboard-side dedup: getExistingRemoteTools returns [] in dry-run (no API call), so dry-run only exercises state-side dedup. Acceptable: dry-run is meant to be cheap; the state-side check covers the common case.
  • Lex-smallest UUID tiebreaker: deterministic but arbitrary. A future "UUID most-referenced by current assistants" rule would be more correct but requires a graph walk; deferred until ambiguity proves a real problem in practice.
  • slugify / extractBaseSlug deliberately duplicated between pull.ts and dep-dedup.ts so the new module stays config-free for testability — file header documents the rationale.

Improvements log

Will mark improvements.md §10 as [RESOLVED YYYY-MM-DD] (#<this-PR>) in a follow-up commit on this branch once the PR # is known.

…10)

Targeted assistant pushes minted duplicate dashboard tools when bootstrap
pull stored an existing dashboard tool under a name-slugged state key
(e.g. `end-call-67aea057`) instead of the user's original local key. The
exact-key lookup in `ensureToolExists` / `ensureStructuredOutputExists`
missed and POSTed a fresh duplicate. Each subsequent targeted push
repeated the cycle, accumulating dashboard orphans.

This adds a name-based dedup check between the exact-key short-circuit
and the create path, in two layers:

  1. State-side: scan state for any key whose `extractBaseSlug` matches
     the local payload's slugified name (handles bootstrap-renamed keys).
  2. Dashboard-side: lazy-fetch the live `/tool` (and `/structured-output`)
     list once per push and check for a remote resource with the same
     canonical name.

When >1 distinct UUID matches the same name (real on-dashboard duplicates
from prior bug runs), pick the lex-smallest UUID for stable adoption,
warn naming the loser UUIDs, and point at `npm run cleanup`. Never mint
another duplicate.

Adoption flow:
  - Re-key state to the adopted UUID under the local resourceId.
  - Drop other state keys pointing at the same UUID and mark them
    touched, so a subsequent full push doesn't orphan-delete the
    adopted dashboard resource (Stack J / mergeScoped flushes the
    deletion).
  - Route through `applyTool`/`applyStructuredOutput` so the local
    payload PATCHes the dashboard with the standard drift-check flow,
    instead of recording a fake `lastPushedHash` that would silently
    drop a locally-edited dependency.

Tests: 12 unit tests for `findExistingResourceByName` covering state-only,
dashboard-only, both-agree, ambiguous (state-vs-state, state-vs-dashboard),
no-name, exact-key-excluded, no-match. All 114 suites pass.

Refs: improvements.md §10
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