Entity paths
Most attribution tools work at the line level. Selvedge attributes things you actually search for. This page is the canonical reference for what an entity path looks like.
Conventions
Section titled “Conventions”users.email DB column — table.columnusers DB table — bare table namesrc/auth.py::login Function in a file — path::symbolsrc/auth.py File — repo-relative pathsrc/auth.py::Auth Class — same syntax as functionsapi/v1/users API route — slash-delimiteddeps/stripe Dependency — deps/<package>env/STRIPE_SECRET_KEY Environment var — env/<NAME>config/redis.conf Config file — config/<path>schema/foo.proto Schema file — schema/<path>You can extend the namespace freely. Agents are free to use whatever path makes the entity searchable; Selvedge only enforces uniqueness, not syntax.
entity_type is descriptive metadata — column / table / file / function /
class / endpoint / dependency / env_var / index / schema / config /
other. Unknown values coerce to "other". Queries don’t filter on entity_type
unless you ask them to.
Prefix matching
Section titled “Prefix matching”Searching users returns users, users.email, users.created_at, and any other
entity under the users. namespace.
The prefix is a literal string, not a regex or glob. users matches users (exact)
and anything starting with users followed by ., /, or :: — so usersettings
won’t accidentally match.
Wildcards in the underlying SQL LIKE queries (_, %, \) are properly escaped
since v0.3.0. So selvedge search "stripe_customer_id" matches the literal underscore,
not “any character” — same for % in column names like usage_pct.
Worked examples
Section titled “Worked examples”# All changes to a columnselvedge diff users.email
# All changes touching anything under the users tableselvedge diff users
# Last change to a specific column with full reasoningselvedge blame payments.amount
# All changes to a functionselvedge diff "src/billing.py::charge_card"
# All env-var changes in the last 30 daysselvedge history --entity env --since 30d
# Every change in a feature rolloutselvedge changeset add-stripe-billingThe selvedge log CLI uses click.Choice for change_type so typos are caught at
argument parsing. There’s no entity_type validation at parse time — it’s coerced — but
typos in change_type ("modifyed", "banana") are rejected with a clear error.
Empty paths
Section titled “Empty paths”entity_path="" is rejected by log_change and selvedge log since v0.3.0. Empty
paths used to insert orphan rows that broke prefix queries. Now they fail loudly.
Renames emit two events
Section titled “Renames emit two events”A rename of users.tier to users.subscription_tier emits:
- A
renameevent onusers.tier - A
createevent onusers.subscription_tier
So selvedge blame works under both names — the new one points at the create, the old
one points at the rename. Same pattern for SQL RENAME COLUMN and Alembic
op.rename_table. This is what makes selvedge import against a long Alembic history
actually produce a useful trail.
Why this is at the entity level, not the line level
Section titled “Why this is at the entity level, not the line level”The first question after git blame is usually “what’s the history of this column?”,
not “what’s the history of lines 40–48 of users.py?”. Lines move. Symbols move
between files. Columns get renamed. Selvedge follows the symbol through those moves —
git follows the line.
Selvedge isn’t trying to replace git blame; the two answer different questions. The
diff stays git’s job. The why-and-where-of-the-thing stays Selvedge’s.