Skip to main content

DEPLOY.DRIFT.UNCOMPARED — One object type could not be read, so it was never compared

  • Category: safety
  • Severity: none — the finding is undetermined, and a severity would put a number on a comparison that never happened
  • Level: 0
  • Downtime class: online — reporting a gap changes nothing and takes no lock
  • Stability: stable
  • Suites: deploy
  • Applies to: PostgreSQL, MySQL

What it reports

One object type, on one side of the comparison, that could not be read — so that slice of the schema was not compared at all.

sqlens.deploy DEPLOY.DRIFT.UNCOMPARED
The index objects of the live side could not be read (permission denied for
pg_index), so no index was compared — this is not the same as finding no drift
in them.

It is reported once per (type, side). The two sides are named separately on purpose: a type the live database withholds and a type the shadow replay could not produce are different problems with different fixes, and one finding covering both would be actionable for neither.

Why this is a finding rather than a log line

Because it is the only one of the four drift ids that can be true while the report shows nothing else at all — and every line of that report is accurate.

A run that could not read indexes finds no index drift. It prints zero unexpected, zero missing, zero divergent, and exits clean. Nothing about it looks wrong, nothing is red, and the sentence a reader takes away — "the schema matches the migrations" — is one the run never established.

That is the silent green this package refuses everywhere, and it is worse here than most: drift is the check people run precisely because they suspect the schema of holding something the files do not describe. An answer of "no" that means "I did not look" is the most expensive wrong answer this command can give.

So the comparison keeps going — the rest of it is still worth having — and it leaves this behind. DriftReport::isConclusive() is false for as long as one of these exists, and the run reports undetermined rather than pass.

What causes it

  • A privilege the reading role does not hold. The commonest cause on a hardened production database, and the one most likely to be permanent.
  • A managed database that withholds a catalog. Not fixable by granting anything; the provider does not expose it.
  • A catalog read that hit its own time budget on a very large schema.
  • The shadow replay could not produce the type at all — the expected side of the pair. A migration that failed inside the throwaway database leaves this, and it means the expectation side is incomplete rather than that the live database is.

The reason the reading gave travels in the message, so the finding names which of these it is.

What to do about it

Read the side first. live and expected fail for unrelated reasons:

  • live — the role reading the target database lacks something. Grant it, or accept the gap (below). On PostgreSQL the deploy reading role is described under what predeploy needs to be allowed to do.
  • expected — the shadow replay is incomplete, which is a problem with the migration run rather than with the database being examined. A sqlens:lint --roundtrip run against the same migration state is the faster way to see what failed.

Accepting it deliberately is legitimate, and this id exists so you can. A managed instance that withholds one catalog produces this finding on every run forever; baselining it records the gap in a file somebody reviews instead of leaving it to be re-read as noise every time.

Because it is its own id, the entry that accepts it accepts only the gap. The three answers stay armed on that same object type, and real drift in it still reports the moment the reading starts working again. Muting drift with a shared id would have cost exactly that.

What it does not claim

  • It never says how many objects went uncompared. That number is precisely what the failed reading would have told us. A finding claiming it would be inventing the thing it exists to report the absence of.
  • It is not a statement that the object type is fine. It is the statement that nothing was said about it.
  • It does not stop the run. The other object types are still compared, and their findings are as good as they would have been. Only the conclusiveness of the whole is lost.
  • A run carrying it has not established that the schema matches, however empty the other three classes came back. That is the entire content of the finding.
  • It writes nothing and locks nothing. Like every other reading here, the database being examined is only ever read.