forked from molecule-ai/molecule-core
OrgHandler.Import was non-idempotent — every call INSERTed a fresh row for every workspace in the tree, regardless of whether matching workspaces already existed. Calling /org/import twice with the same template duplicated the entire tree. This was the bigger leak source than TeamHandler.Expand (deleted in PR #2856). tenant-hongming accumulated 72 distinct child workspaces in 4 days entirely from repeated org-template spawns of the same template — the (tier × runtime) matrix in the audit data was the template's static shape, multiplied by spawn count. Fix: route through a new lookupExistingChild helper before INSERT. Skip-if-exists semantics by default: - Match on (parent_id, name) using `IS NOT DISTINCT FROM` so NULL parents (root workspaces) are included. - Ignore status='removed' rows so collapsed teams or deleted workspaces don't block re-import. - Recursion still runs on the existing id so partial-match templates (parent exists, some children missing) backfill correctly instead of either no-op'ing the whole subtree or duplicating the existing children. - Result entries for skipped nodes carry skipped:true so callers (canvas Import preflight modal) can surface "5 of 7 already existed, 2 created." The recursion that walked ws.Children is extracted into recurseChildrenForImport so both the create-path and the skip-path share one implementation — no duplicated grid math, no two paths to keep in sync. Note: replace_if_exists semantics (re-roll: stop+delete old, create new) are deferred. Skip-if-exists alone closes the leak; re-roll is a later UX decision for the canvas Import preflight modal. Tests: - 4 sqlmock cases on lookupExistingChild: not-found, found, nil-parent (the IS NOT DISTINCT FROM NULL trick), DB-error propagates (must fail fast — silent fallback to INSERT is the failure mode the helper exists to prevent). - 1 source-level AST gate (per memory feedback_behavior_based_ast_gates.md): pins that h.lookupExistingChild( appears BEFORE INSERT INTO workspaces in org_import.go. If a future refactor reintroduces the un-checked INSERT, the gate fails. Verified load-bearing by removing the call — build fails (helper symbol gone). go vet ./... clean. go test ./internal/handlers/ -count 1 — all green (4.2s, no regression on existing OrgImport / Provision / Team tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| cmd | ||
| internal | ||
| migrations | ||
| pkg/provisionhook | ||
| .ci-force | ||
| .gitignore | ||
| .golangci.yaml | ||
| Dockerfile | ||
| Dockerfile.tenant | ||
| entrypoint-tenant.sh | ||
| go.mod | ||
| go.sum | ||