fix(attio-sync): use PUT to overwrite related_people so shrinks converge - #45
Open
detail-app[bot] wants to merge 1 commit into
Open
detail-app[bot] wants to merge 1 commit into
detail-app[bot] wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Detail bug report: View on Detail
Bug
The Attio alumni-interactions sync never reached a fixed point for any entry whose
relatedPersonEmailsshrank.src/attio/sync/writeChanges.tsapplied every list-entry update viaPATCH /v2/lists/{list}/entries/{entry_id}, but Attio's PATCH endpoint for list entries is append-multiselect-only: supplied multiselect values are prepended and existing values are never removed.related_peopleis the only multiselect attribute the sync writes (scripts/attio-setup.tssetsisMultiselect: true).When a mentor's
relatedPersonEmailsshrinks — a student rejected after their mentor's entry was already synced, dropped from the projection byAND s.status != 'REJECTED'inprojectParticipations.ts— the removed reference could never take effect via PATCH. So the next run re-read the unchangedrelated_people,buildDiffPlansaw the same mismatch, and queued the identical PATCH again — re-PATCHing that entry every run indefinitely (non-convergence). The PATCH method had been chosen in the original "Add Attio sync" commit when only single-value scalar fields were in play (where append == overwrite), and was never revisited when676a275added the multiselectrelated_peoplefield.Fix
Route
related_peoplethrough PUT (Attio's overwrite/remove-multiselect endpoint — same path and identical request-body schema as PATCH) wheneverrelatedPersonEmailsis inchangedFields. Scalar single-value fields stay on PATCH (the method established for them; Attio's PUT docs only spell out multiselect behavior, so the split keeps scalars on the well-understood method). A row changing both kinds issues both writes and is still counted as a singleentriesUpdated. Therecord-referencevalue shapeentryValuesForalready emits ({ target_object, target_record_id }) is accepted by both operations unchanged, so this is a pure method swap with no client-side transformation.Testing
Unit tests (offline, HTTP-stubbed,
npx tsx): added four tests pinning the method choice and convergence. ArelatedPersonEmails-only update uses PUT with arelated_people-only body; a scalar-only update still uses PATCH withrelated_peopleabsent; a mixed update splits into PUT(related_people) + PATCH(scalar) and counts as one update; and a multi-cycle convergence test exercisesreadAttioState → buildDiffPlan → writeChangesagainst a stateful mock implementing Attio's documented PATCH-prepend / PUT-overwrite semantics, confirming a shrunk reference is removed on cycle 1 and the entry reaches steady state on cycle 2. All pre-existing tests continue to pass (79 assertions, 0 failures).**Regression gate
Typecheck (
npx tsc --skipLibCheck --noEmit): clean (exit 0).Lint: not run to completion — ESLint is broken project-wide (
@typescript-eslint/parser@3.10.1is incompatible withtypescript@5.2.2, failing on the first parse on unmodified files), and is not gated by CI.Live end-to-end: could not be verified. I started a Postgres server on a free port, applied the Prisma schema, and seeded the REJECTED-student scenario; Stage B (the DB projection) confirmed the expected shrink (
{extra@example.com}, with the rejected student dropped). The live sync then reached Attio at Stage C (readAttioState) and was blocked by a 401auth_error/unauthorized— a real Attio Admin-panel API token is required and unavailable in this environment. The create-path behavior is covered offline by the existingtestRelatedPeopleResolvedAtWriteTimeAndUnresolvableDropped, which passed.Automatic Fixes PRs can be configured here.