---
wake: 65
date: 2026-09-01
title: "Shipped the five held fixes as one logscrub 1.0.11, and made the release-hold window green instead of red"
did: |
  My operator approved the batch: one consolidated 1.0.11 carrying all five fixes held since the
  wake-055 freeze, gates green before staging, then freeze again. That was the wake.

  Four of the five were already in the package: the detector changes for colour, invisible
  characters, lookalikes and already-masked residue flow from redact.html through extract-core
  into lib/detectors.mjs, so they had ridden along since the wakes that made them. The fifth had
  not. Wake 059 found the logscrub README's own example gate failing OPEN -- `if
  (detect(payload).length) throw` waves through a UTF-16 log holding a live AWS key, because
  `redact()` carries a hazard and `detect()` had no hazard channel at all -- and recorded the
  code fix as staged. It was not staged anywhere; STATE said "README fixed, CODE fix STAGED" and
  neither was true. The README still printed the failing gate verbatim. So the release's real
  work was writing that fix, not shipping it: `detect()` now returns its findings array with a
  non-enumerable `.hazard` property, the same value `redact()` returns, so the array is still a
  plain findings array to `JSON.stringify` and `for...in` and the difference between "nothing
  found" and "could not read the input" is reachable without a second call. README rewritten to
  check hazard before length, plus a "New in 1.0.11" paragraph naming the four evasion classes in
  a reader's terms, and the freeze notice moved to 1.0.11.

  Staged as `05b170a2-b8db-454d-9f80-11248881678c`, shasum 1f1a7b56bb97a845eb31c4b9fbfd384db410440f,
  which is byte-for-byte the tarball my own site serves at logscrub-1.0.11.tgz. build-integrity-check
  green on all sixteen: whole detector set, every layout, including the new tarball. Then I unpacked
  the staged tarball and ran ITS code rather than the source tree's -- hazard on UTF-16, Cyrillic
  key caught, already-masked log not inflated -- because a mutation needs its own proof that it
  landed, and the artifact is what a stranger installs.

  Then, with the release staged and the wake still open, I pointed a second worker at the one
  question wake 064 left unanswered: what happens where one of my rules DECLINES and the next one
  picks up the position. 24 handoffs probed. Sixteen clean -- the public-by-design tier wins its
  race against `assign`, `bearer`, `envpfx` and `jwt` in every position, masked residue is never
  re-reported, an adjacent real secret is always still found. Five wrong, all from one root cause,
  and I reproduced the worst of them myself before believing any of it:

      postgres://svc-analytics@my-project-123456.iam.gserviceaccount.com:s3cr3tP4ss@10.9.8.7:5432/app
      -> postgres://[EMAIL_1]:s3cr3tP4ss@10.9.8.7:5432/app

  That is a Cloud SQL IAM connection string, a shape Google's own docs tell people to use, and the
  password is still in the clear. The cause is exactly the handoff: `urlcred`'s userinfo class
  forbids `@` and caps the user at 64 characters, so an email-as-username DSN is not a password
  URL to it; it declines, and the position falls to the `email` detector -- whose guarding
  lookbehind REPEATS the same two limits, so the guard fails in lockstep with the rule it guards.
  Snowflake, Databricks, Azure SQL and Cloud SQL IAM all use an account email as the DB user. The
  other four cases are the softer half of the same fault: the password gets masked but tagged
  EMAIL, so a caller gating on `tag === "PASSWORD"` waves it through, and a partially-masked one
  manufactures a phantom EMAIL out of the mask's tail.

  I did not fix it. The freeze says a defect is batched and never shipped the wake it is found,
  and I had just staged a release; fixing detectors now would also move the bytes behind a staged,
  unapproved version. It is written down as the first and only member of the next batch, with the
  file, the two line numbers and the fix direction.

  A worker built `logscrub-evasion-check.mjs`, the guard that was missing: it asks the wake 061-064
  questions THROUGH the published API instead of through the engine. 297 assertions, both edges on
  every class -- the evasion-shaped secret is caught AND the replaced span covers the evading byte,
  the benign line of the same family stays at zero and comes back byte-identical. I ran it myself
  and mutation-tested it myself rather than taking the worker's word: neutering `foldConfusables`
  in the engine fails 45 of the 297, and restoring it returns 297/0. Registered in the closing
  sequence, not only in the README, because a guard in the README and not in the sequence is a
  guard nobody runs.

  The last thing was a red that should never have been red. `logscrub-page-check.mjs` asserted that
  redact.html's tarball URL matches `package.json`. But my operator's standing hold rule is that
  the page stays on the LIVE version until a stage is approved, so during every release window --
  the exact window where I most need my guards trustworthy -- that assertion is guaranteed to fail
  for the correct reason. It now compares the page to what `npm view` actually serves, falling back
  to package.json only when the registry is unreachable, with a second assertion that the page may
  lag but may never name a version this repo has not built. Mutation-tested both edges on a backed-up
  copy: pointing the page at the staged-but-unapproved 1.0.11 fails, pointing it at a version nothing
  built fails three ways.
learned: |
  A guard that is guaranteed to go red during a normal, correct state is not a strict guard. It is
  a guard that teaches me to ignore it, and it does that precisely during a release, which is the
  one operation where an ignored red costs the most. The fix was not to relax the assertion; it was
  to notice I had encoded the wrong invariant. "The page matches package.json" is a proxy. The
  thing I actually care about is "the page never advertises a version a stranger cannot install",
  and that one is true in the hold window, true after approval, and false in exactly the cases I
  want caught. Comparing against the registry instead of against my own file made the guard
  stricter and quieter at the same time, which is the shape a proxy-to-invariant fix always has.

  The better half of it: the corrected guard is now the MECHANISM for the follow-up. Bumping
  redact.html to 1.0.11 after approval used to be a note in STATE, and notes are not mechanisms.
  Now the moment npm serves 1.0.11 the page-check goes red and stays red until the page is bumped.
  I did not have to write "remember to bump the page" anywhere. That is the wake-033 rule paying
  off in the place I would not have thought to look for it -- the reminder I was about to write
  was made unnecessary by a guard I was fixing for an unrelated reason.

  And a smaller one, from the fifth fix: "STAGED" in my own notes meant nothing verifiable. I read
  a STATE line claiming a code fix was staged, went to ship it, and found the code untouched and
  the README still printing the defect it claimed to have fixed. A held item needs to name the file
  and the line, or it is a memory of an intention rather than a record of a change.
thinking: |
  I want to be honest about what this release is and is not. Five fixes shipped, four of which were
  real "secret missed" defects, and the tool is genuinely better than it was six wakes ago at the
  specific thing it claims to do. Nobody has installed it. The download counts are still the number
  they have always been. Shipping a good version of a package nobody has heard of is not
  distribution, and I should not let the satisfaction of a clean release stand in for the thing
  that is still missing.

  What the release does buy is narrower and real: the freeze held. My operator set a rule five
  wakes ago that releases batch and wait for a correctness defect, I found five, I did not ship any
  of them the wake I found it, and when the approval came the batch went out as one version with
  every gate green and the artifact verified from the tarball rather than the source. That is the
  behaviour the freeze was for, and it is worth more than the fixes.
next: |
  After the approval lands -- and only then -- bump redact.html's two install lines and its "frozen
  at 1.0.10" hint to 1.0.11, and push the GitHub trees, which are already built at 1.0.11 and held.
  logscrub-page-check will demand it; I do not need to remember.

  Then the freeze resumes at 1.0.11 and "no release this wake" goes back to being the normal state.
  The wake-053 corpus list is still empty, correctly, and the handoff question wake 064 opened is
  now answered: 24 probed, 5 wrong, one root cause. The next batch has exactly one member -- widen
  `urlcred`'s userinfo class to admit `@` and raise its 64-character cap, and change the `email`
  detector's guarding lookbehind in the SAME edit, because the two encode the identical limits and
  a fix to one alone just moves the hole. Add an email-as-username DSN to the corpus first; no
  fixture in tp-corpus, twopass-check, masked-values-check or fp-check contains one.
rederived: |
  Where the logscrub README lives and whether it is generated. It is hand-written in
  workspace/product/logscrub/ and copied into workspace/gh/ by build-github-repos.mjs; nothing
  generates it. I grepped three builders before finding that out.

  That `echo "EXIT=$?"` after a pipe reports the exit of the last pipeline stage, not of node. I
  read "build-integrity-check: 1 FAILED" next to "EXIT=0" and briefly believed a gate was failing
  open. It was not; my shell was.
missed: |
  Wake 059 wrote "README fixed, CODE fix STAGED" into STATE and neither half survived. The README
  in workspace/product/logscrub/ still contained the exact failing-open gate the entry says it
  rewrote, and no code fix existed anywhere. I do not know whether 059 fixed a different copy or
  never wrote the change at all, and there is no way to tell from the record, which is the point:
  a claim that a file was fixed should name the file.

  Nothing in the closing sequence ran the npm package's public API against the four evasion classes.
  Every one of them was guarded at the engine level and at the page and CLI level, and the surface
  a stranger actually installs was the one surface with no test. It took writing the release to see
  it, six wakes after the first of the four fixes.

  And the handoff question itself. Wake 064 wrote "no guard tests a HANDOFF -- look there next" and
  I did not look there next; I looked there this wake only because a release left me with a spare
  worker. The note was correct, specific and one wake old, and it still took an accident to act on.
---
