Skip to main content
← Back to list
01Issue
FeatureOpenSwamp ClubPublic
AssigneesNone

Relationships

↔ sibling #809

#1667 Finish the swamp_* attribute set on Attio users, and settle the two unprefixed ones (#809 follow-up)

Opened by keeb · 8/15/2026

Follow-up to #809. Separable from #1665 (which is about which operatives reach Attio); this one is about what we say about each of them, and applies at any scope.

#809 proposed six custom attributes and listed their final slug names as an open question, since a slug is a permanent contract once created:

swamp_username, swamp_role, swamp_tier, swamp_profile_url, swamp_signup_at, swamp_email_verified

What exists today

lib/infrastructure/attio-schema.ts declares, on users:

  • swamp_username — ✅ as proposed
  • role_in_collective — ⚠️ the swamp_role idea, without the swamp_ prefix
  • email_verified — ⚠️ the swamp_email_verified idea, without the prefix

And absent entirely: swamp_tier, swamp_profile_url, swamp_signup_at.

(On workspaces, swamp_slug is correctly prefixed; the rest of that object's custom attributes — plan, seats_in_use, trial_* — are likewise unprefixed.)

Why the prefix matters, and why this is not just tidying

#809's stated invariant was that the integration only ever creates swamp_* attributes, and the payoff was "a crisp, prefix-filterable blast radius — auditable, teardown-able, collision-proof". Two unprefixed attributes we created ourselves defeat exactly that: a human auditing the workspace cannot tell role_in_collective and email_verified apart from something Attio or a salesperson added.

The catch is that Attio cannot delete an attribute, only archive it, so a rename is not a rename — it is create-new + leave-the-old-one-populated-forever. That makes this a decision rather than a chore:

  1. Leave them. Accept two unprefixed attributes and correct the invariant's wording in the schema module and CLAUDE.md so it describes what is actually true.
  2. Add prefixed twins and migrate. swamp_role / swamp_email_verified alongside, repaint via a CRM_MAPPING_VERSION bump, and leave the originals archived and empty. Restores the clean blast radius at the cost of two dead attributes.

I lean (1) unless the audit story is load-bearing — the honest fix is to correct the claim rather than to churn a live workspace for it.

The three missing attributes

swamp_tier, swamp_profile_url and swamp_signup_at are additive, so they carry none of the above hazard. Notes before adding them:

  • swamp_tier moves. #809's own design put a "mapping change" behind a versioned re-backfill for exactly this reason. A tier is a good CRM signal (it says how invested someone is) but it changes under you, and the existing projection deliberately excludes anything that moves daily — see the "usage attributes" note at the top of lib/app/attio-desired-state.ts, which parks swamp_score / last_seen / active_days on the grounds that syncing raw movers is "a permanent write treadmill". Tier is coarse enough to be safe where score is not; quantize deliberately, do not sync XP.
  • swamp_profile_url is derivable from the username and costs nothing to keep current, but it duplicates a field that already moves when someone renames.
  • swamp_signup_at is immutable and free — the clearest win of the three.

Adding any of them means bumping CRM_MAPPING_VERSION in lib/repositories/crm-records.ts, or the payload change is invisible in production while every test passes. Provision first with swamp model @keeb/attio/objects method run provision (dry-run by default) — scripts/attio-provision-input.ts derives the checklist from the schema module, so the attributes created and the payload sent cannot drift.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/15/2026, 1:20:40 AM

No activity in this phase yet.

03Sludge Pulse
keeb linked related to #8098/15/2026, 1:20:59 AM

Sign in to post a ripple.