v8.5.27
⚠️ Fix: the versioned cache could serve what it was told to forget
RedisVersioned's rules stream is the only record that an invalidation ever
happened, and it carried a TTL — which made it an eviction candidate like any
item. Evict it and XRANGE answers an empty array, indistinguishable from a
group that was never invalidated, so every item invalidated but not yet read
came back with its old value. Silently, for the whole group.
Reproduced under volatile-lru, the policy we run: 32 MB, one tagged item,
invalidated and not read again, and the stream was gone after 7 000 unrelated
writes while the item survived to be served stale. It is the same shape as the
RedisTagAwareAdapter tag Sets that bit us on allkeys-lru years ago, and the
reason we moved to volatile-lru — which protects Symfony's design and did not
protect this one.
The stream now carries no TTL, as Symfony's tag Sets do not, and PERSIST
strips the one an earlier version left behind, so an existing group repairs
itself on its next invalidation. Under volatile-* or noeviction the stream
is no longer in the eviction pool at all.
Under allkeys-* it can still be lost, and the read path answers for that
rather than trusting it. Every rule now records whether it opened the stream
(XADD NOMKSTREAM refuses to create one, so a nil reply says there was none),
and an item stamped older than an opening rule has seen rules the stream no
longer holds: a miss rather than a hit. The group recomputes once instead of
serving what it was told to forget.
Upgrading: run a volatile-* or noeviction eviction policy. No call site
changes. Two limits are worth knowing: an item written before its group's
first-ever invalidation carries no watermark to check and is served if the
stream is later lost, and the check is an ordering, so it cannot see a stream
lost and reborn inside the same millisecond its items were stamped in. Both
need the stream to go, which under the policies above means a DEL, a
FLUSHDB or a slot lost with a cluster node, not eviction.
Console sender: the untracked pass
Service\Console\Untracked finds the git or svn working copy at or above
BASE_DIR, asks it read-only (git --no-optional-locks ls-files --others --exclude-standard -z, never git status; svn status without -u, an
unversioned directory opened to its files) and builds the report ovos/console
takes at POST /api/v1/ingest/files with platform php.
An executable-shaped or server-config file is listed by path — urgent under a
web-reachable directory (console.files.web, default public), high elsewhere
— and everything else is counted per directory with an extension histogram and
never named. Every failure to ask is null, never a guess: no working copy,
proc_open closed, a non-zero exit, a run past the budget (stdout to a temp
file and a proc_get_status poll, because a Windows pipe blocks regardless of
the blocking flag).
Sender::untrackedReport() builds it (CLI only), Sender::reportFiles() posts
it and answers the console's code, and Sender::reportUntracked() does both for
a cron line. An empty answer still posts, which is how a finding goes GONE on
the console.
Console sender: the working copy also names what changed and what is gone
Beside the untracked list, git --no-optional-locks -c core.fileMode=false diff --no-renames --name-status -z HEAD — both answers are required, since a report
built from one would mark the other half's findings GONE. svn's M/A/R rows
are modified, !/D missing, a property-only change nothing.
A modified tracked file is always listed by path: its name is repository
content, not customer data. It carries the untracked tiers plus one of its own —
a browser script (.js, .html, .svg) under the web directory is high, the
card skimmer's shape. A missing tracked file is info, and the root area carries
foreign/modified/missing counters in the checksum pass's words.
core.fileMode=false, so a chmod is not a modification; the class docblock says
when a deploy should git update-index --refresh.