# Function-Level Identity *Every function has a name that outlives its name.* A function in a codebase is a thing. It has a purpose, a history, a set of callers, a set of tests that exercise it. Its name is a label we attach to it for convenience. When we rename it, we have not created a new function — we have changed the label on the same thing. Git does not believe this. To Git, `login()` and `authenticate()` are unrelated strings in unrelated lines. When you rename the function, Git records a deletion and an addition and hopes `--follow` will limp along. Cross-file renames are worse. Cross-commit-chain renames fall off a cliff. Aura believes the function is the thing. It assigns every function a stable identity derived from its content, not its name. That identity persists across renames, across file moves, across reformatting, across refactors that preserve the function's essence. > A function's identity is what it does, not what it is called. ## The content hash When Aura sees a function, it computes a content hash. The hash is derived from the function's structural signature: - The normalized AST of the body (whitespace, comments, and formatting trivia stripped). - The parameter list (as a structural shape — order, types, defaults — not the parameter names). - The return type or return-expression shape. - A normalization of bindings: local variable names are canonicalized, so `let x = 1` and `let y = 1` hash the same. What the hash deliberately ignores: - The function's own name. (Otherwise rename would change identity.) - The file it lives in. (Otherwise moving it would change identity.) - Formatting, indentation, trailing commas, comment placement. - Local variable names inside the body. - The order of sibling functions in the file. What the hash captures: the function's behavior at the structural level. Two functions with the same hash are guaranteed to have the same structural body. They may be named differently. They may live in different files. They are, for Aura's purposes, the same function. ## Identity is stable, not permanent A function's content hash changes when its body changes. That is the point — editing a function's behavior produces a new version of the same function, with a new body hash, linked to the old one by its stable *identity anchor*. Aura distinguishes two things: - **Content hash** — what the function's body currently is. Changes on every meaningful edit. - **Identity anchor** — the persistent "this is the same function" thread that links successive versions together across edits, renames, and moves. The identity anchor is what survives a rename. It is what `aura rewind` uses to find a prior version of a function. It is what the [shadow branch](/shadow-branches) records history against. It is what lets Aura tell you, six months later, that the function now called `validate_auth_token` is the same function that was once called `check_login`. ## How identity survives change A worked walkthrough. Start with: ```python def login(user, password): token = hash(user + password) return token ``` Step 1 — rename the function. ```python def authenticate(user, password): token = hash(user + password) return token ``` The body hash is unchanged. Aura recognizes this as the same function with a new name. History is preserved. All references to the old name are rewritten. Step 2 — reformat. ```python def authenticate(user, password): token = hash( user + password ) return token ``` No structural change. The AST is identical after normalization. Body hash unchanged. Identity unchanged. Step 3 — rename a local variable. ```python def authenticate(user, password): t = hash(user + password) return t ``` Body hash unchanged — local names are canonicalized. Identity unchanged. Step 4 — move to another file. ```python # moved from auth.py to security/tokens.py def authenticate(user, password): t = hash(user + password) return t ``` Body hash unchanged. Identity unchanged. Aura records a move operation and updates every import statement across the tree. Step 5 — change behavior. ```python def authenticate(user, password, algorithm="sha256"): t = hash(user + password, algorithm) return t ``` Now the signature changed and the body references a new argument. Body hash changes. Identity anchor is preserved — Aura sees this as the same function with a new version. History links the old body to the new body. Callers are surfaced for review because the signature changed. | Change | Body hash | Identity anchor | History | |---|---|---|---| | Rename function | unchanged | unchanged | preserved | | Reformat | unchanged | unchanged | preserved | | Rename local variable | unchanged | unchanged | preserved | | Move file | unchanged | unchanged | preserved | | Edit body | changes | unchanged | new version linked | | Change signature | changes | unchanged | new version linked, callers flagged | | Delete function | — | ended | retained in history | | Re-add deleted function (same body) | matches old | re-linked | continues prior history | ## Identity across deletion and re-addition If a function is deleted and later a function with the same structural body is added, Aura links them. History is continuous. This is not always what you want — sometimes two separate additions really are coincidentally similar — so Aura surfaces re-identification as an event you can inspect. The default is to preserve continuity. ## Why names alone cannot be identity Imagine naming as identity for a moment. What breaks: - Rename breaks history. - Two functions with the same name in different modules get mixed up. - A function moved to another file appears to be a new function. - The tool cannot distinguish "I changed the name" from "I deleted one and wrote another." Imagine file+line as identity. What breaks: - Any edit above the function shifts its line number. - Reformatting changes identity. - Moving the function breaks history. - Adding an import changes every function's line number in the file. Imagine AST body hash alone as identity. What breaks: - Two functions with identical bodies but different purposes (both return `42`) get unified. - Any edit changes identity, so history cannot follow edits. Aura combines body hash (for rename-proofing and dedup) with a persistent identity anchor (for history continuity across edits). Neither alone is enough. Together, they capture the intuition that a function is a thing with a life. ## Surfacing identity in the tool Aura exposes function identity in several places: - **`aura_prove`** asks about behaviors by name, but resolves to identity under the hood. A proof that "the user can authenticate via OAuth" still works if `authenticate` is renamed. - **`aura_rewind`** reverts a function to a prior version by identity anchor. It works even if the function has been renamed since the commit you are reverting to. - **Semantic review** (`aura_pr_review`) reports function changes by identity. A rename appears as "renamed," not "deleted + added." - **Cross-branch impact alerts** follow identity, so when a teammate modifies a function you depend on, the alert finds your usage even if either side renamed it. ## The practical payoff The payoff of function-level identity compounds silently. You notice it when: - You rename something and your review does not explode. - You move a module and nothing gets lost. - You `git blame` a line and Aura can tell you who wrote the logic, even though the line has been through three renames and a file move. - A teammate's refactor and your bug fix merge cleanly because Aura followed the function across the refactor. The things you do not notice are often worth more: the fears you never felt, the refactors you did not postpone, the history you did not lose. ## What identity does not promise Function-level identity is a structural claim, not a semantic one. It does not claim that two functions with the same body compute the same thing in all universes — a function named `compute` might depend on global state that changes its behavior. It claims that two versions linked by identity are the same code artifact across history. Reasoning about behavior is the job of tests, the type checker, and [aura_prove](/intent-tracking). Identity also does not try to paper over genuinely new functions. When you write a new function, it has a new identity. When you delete a function, its identity ends — though its history is retained. The goal is honest continuity, not clever deduplication. ## A note on other kinds of nodes Functions are the primary unit of identity in Aura because they are the primary unit of thought in code. Other nodes — types, constants, methods, classes — also get identity, by the same principles: structural content for matching, persistent anchors for history. The mechanisms generalize. Functions are just where the payoff is largest and the examples are clearest. ## Related Identity is the substrate for [rename-proof identity](/rename-proof-identity) as a user-facing feature. The hashes live in the [content-addressed logic](/content-addressed-logic) graph. [AST merge](/ast-merge) uses identity to match nodes across branches.