DEBT.UNRECORDED — a debt the database carries and the account has never heard of
- Category: safety
- Level: 0
- Severity: info
- Stability: stable
- Suites: audit, deploy
The catalog shows a debt — a constraint added NOT VALID and never validated — and the committed
debt account does not mention it.
That is a definite statement about the file rather than a gap in the reading. The database was queried, the object is there, and the account is silent about it.
Why this could not happen until now, and why that was the problem
Debts used to be recorded by one thing: a static rule that reads migrations. It works, and it has a blind spot that is exactly the shape of the debts worth the most.
A NOT VALID constraint that was already in the database when this package arrived has no
migration to read. Nothing that reads migrations will ever find it. So it was reported on every
single run, identically, with:
- no
first_seen, so no age — and age is the entire reason the account exists, - no way to write down a reason for carrying it,
- no way to acknowledge it, so no way to stop it shouting.
A finding a team cannot act on is one they learn to scroll past, which is the outcome the debt account was built to prevent. The oldest debt a project carries was the only one with no date on it.
Recording it
php artisan sqlens:audit --debt=record
The default is --debt=check: compare, report, write nothing. An ordinary audit run still edits
nothing at all.
What the date means, and what it does not
first_seen on an entry recorded this way is the day this project first looked. It is not the
day the constraint was created — nothing available to this package knows that, and the statement
that created it ran in some earlier deploy, possibly before the package existed.
That is honest and it is useful: an age measured from the day you started counting is still an age, and it still separates a debt somebody took on last week from one nobody has touched in a year. But it is a different sentence from the one on the entry beside it, so the entry says which it is:
{
"kind": "not_valid_constraint",
"object": "public.orders_user_id_foreign",
"migration": "",
"origin": "catalog",
"first_seen": "2026-08-23"
}
origin: catalog and an empty migration are the same statement written twice, on purpose: a
reader sees it, and so does every consumer that reads the file — the reporters, the escalation path
and the get_debt_ledger tool can branch on a field and cannot read this page.
One constraint is one entry, from either direction
A constraint that both sides can see does not produce two rows. The identity is derived from the debt's kind and its object and deliberately not from the migration, because the migration is how we came to know about it rather than what it is. Before that was true the two sides could not agree by construction, each reported an age computed from half the picture, and reconciliation could not converge — each side wanted to remove the other's row.
What a recording run will not do
It will not remove a debt it could not have seen. An audit run reads the catalog and never the migrations, so every migration-origin entry is outside what it looked at, and it is carried through untouched rather than treated as settled. "I did not look" and "it is not there any more" are different answers, and only one of them is safe to write to a file.
Related
DEBT.STILL_OPEN— what this becomes once it is recordedDEBT.RESOLVED— the same entry once the catalog shows it settled