Skip to content

fix(hooks): now-gate matched a bold NOW stamp nothing writes; correct the symlink comment; drop 835 lines of base64 - #2300

Merged
gHashTag merged 1 commit into
masterfrom
fix/now-gate-and-stale-now-artifacts
Aug 20, 2026
Merged

fix(hooks): now-gate matched a bold NOW stamp nothing writes; correct the symlink comment; drop 835 lines of base64#2300
gHashTag merged 1 commit into
masterfrom
fix/now-gate-and-stale-now-artifacts

Conversation

@gHashTag

@gHashTag gHashTag commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Closes #2299. Three small, independent defects found while analysing the docs/NOW.md bottleneck.

Everything below was verified against origin/master at 7e8de87b10bf2754c27500e107345b289f26d700, not against a working branch. That mattered: the local checkout is 2,400 commits behind and its .gitattributes still says merge=theirs, which would have produced a false finding for defect 2.

1. tri hooks now-gate required a format nothing produces

cli/tri/src/hooks.rs matched the bold stamp:

r"(?m)^\*\*Last updated:\*\*\s*(\d{4}-\d{2}-\d{2})"

The producer disagrees — cli/tri/src/nownote.rs writes the plain form:

format!("# NOW -- {title} ({date})\n\nLast updated: {date}\n\n## {title}{suffix}\n\n");

On master:

$ git show origin/master:docs/NOW.md | grep -c '^\*\*Last updated:\*\*'
0
$ git show origin/master:docs/NOW.md | grep -c '^Last updated:'
136

Zero bold against 136 plain, so the gate always fell to its None branch and bailed with "no line found". Fixed on the consumer side — changing 136 stamps to satisfy a regex would be backwards. ** stays optional so archived snapshots and root NOW.md, which still carry the old bold style, keep matching.

The tests were self-matching

Both existing now_gate tests write their own fixture in the bold form, then assert the regex reads it back. The fixture matched because the same commit authored both sides; neither test ever saw nownote.rs's output or the real document. Two added:

  • now_gate_accepts_the_plain_form_nownote_writes — pins the shape nownote.rs actually emits. Fails against the pre-fix pattern.
  • now_gate_agrees_with_the_live_gate_on_the_real_document — runs the gate against the real docs/NOW.md, deriving the expected date with the live gate's own rule (bootstrap/src/suite.rs takes the first line containing Last updated:). It asserts the two implementations agree. It deliberately does not assert freshness, so it cannot go red merely because the file is a day old, and it no-ops rather than failing outside a checkout.

This is dead code, and saying so changes the severity

Nothing invokes tri hooks now-gate or tri hooks pre-commit. The gate that actually runs at commit time is a separate implementation: .githooks/pre-commitscripts/tri check-nowt27c check-now (bootstrap/src/suite.rs), which matches on contains("Last updated:") and therefore accepts plain. Outside hooks.rs itself, the only mentions are two table rows in MIGRATION_AUDIT.md.

So no commit is blocked today. It is still worth fixing: MIGRATION_AUDIT.md presents tri hooks as the Rust port that replaces the shell gates, and it would reject every commit the moment anything wires it up.

2. .gitattributes described a symlink that does not exist

The comment read "The root file is a symlink, so git merges the link target, not the text".

$ git ls-tree origin/master NOW.md
100644 blob 4d8a8afdb238043b84692d55d6a356bcee85e9f2	NOW.md

100644 is a regular file; a symlink would be 120000. Root NOW.md is its own divergent document stamped Last updated: 2026-08-09 while docs/NOW.md is stamped today.

A second claim was backwards: "the rule above names the SYMLINK, not the file that actually conflicts". The pattern NOW.md has no slash, so git matches it by basename at any depth — it already covers docs/NOW.md. Verified:

$ git check-attr merge -- NOW.md docs/NOW.md sub/dir/NOW.md
NOW.md: merge: union
docs/NOW.md: merge: union
sub/dir/NOW.md: merge: union

Comments only — no rule changed. git check-attr merge returns union for the same three paths before and after this commit.

Whether root NOW.md should become a real symlink, be deleted, or keep its own content is deliberately not decided here. #2253 raised that and stays open for it; this PR fixes only the comment-accuracy half.

3. docs/NOW.md.master — 835 lines of committed base64

Landed in d063152ad, evidently a .master conflict side-file that was base64'd and committed. Verified junk before removing:

  • Decodes cleanly to 35,328 bytes of an obsolete NOW snapshot stamped **Last updated:** 2026-04-08 — note the bold form, which is the format defect 1's regex was written against.
  • git grep -n "NOW\.md\.master" origin/master returns zero hits tree-wide. No script, workflow, test or doc reads the path.

Being base64, it was unreadable in review, ungreppable and undiffable — inert bytes. Removed.

A number in the brief did not survive checking

The task I worked from said "137 entries". Measured on master it is 136 Last updated: stamps across 74 ^# NOW -- headers. Corrected in the issue, the commit message and the NOW entry rather than repeated. The load-bearing figure for the defect is the zero, not the 136.

Verification

$ git show --numstat HEAD
18	11	.gitattributes
59	2	cli/tri/src/hooks.rs
86	0	docs/NOW.md      <- pure prepend, 0 deleted
0	835	docs/NOW.md.master
  • docs/NOW.md: 0 removed lines — existing entries untouched.
  • No conflict markers in any changed file.
  • File list is exactly the four intended paths.
  • Both gate rules resolve to the same stamp on the new file (line 3, 2026-08-20); the old bold-only pattern still finds nothing there, which is the defect reproducing on the live document.

Compilation — not done locally, since confirmed by CI

I did not run cargo locally (out of scope for this change), so when this was opened the Rust edits were reviewed by reading only. CI has since compiled and run them. The cli-tri job reports 155 passed / 1 failed, and all five now_gate tests are green, including both new ones:

test hooks::tests::now_gate_accepts_the_plain_form_nownote_writes ... ok
test hooks::tests::now_gate_agrees_with_the_live_gate_on_the_real_document ... ok
test hooks::tests::now_gate_accepts_today_override ... ok
test hooks::tests::now_gate_rejects_stale_date ... ok

The liveness test passing is the meaningful one: it ran the gate against the real docs/NOW.md in a fresh checkout and the two implementations agreed.

The three red checks are pre-existing on master

build (cli-tri), coverage (seal-coverage) and withdrawn-live (withdrawn-live-gate) fail here, and they fail on master too — including at 7e8de87b1, the exact commit this branched from:

$ gh run list --branch master --workflow cli-tri.yml --limit 2
failure  2026-08-20T05:43:24Z  b6cb1ec69
failure  2026-08-20T04:27:09Z  7e8de87b1   <- this PR's base

None is caused by this change. The cli-tri failure is fpga::tests::test_smoke_gate_json_synthetic_verify_lean at cli/tri/src/fpga.rs:9977, which trips on [smoke-gate] SKIP: yosys not on PATH — unrelated to hooks.rs. All four required contexts (check-now-freshness, validate, check, check-linked-issue) pass.

One thing observed and deliberately not changed: now_gate computes "today" from Utc::now(), while the live t27c check-now uses the local clock. Those can disagree either side of midnight. Out of scope — flagged for whoever wires tri hooks up.

Three uncontested defects, each verified against origin/master at 7e8de87
before being touched.

1. `tri hooks now-gate` matched `^\*\*Last updated:\*\*` (bold), but the
   producer `cli/tri/src/nownote.rs` writes the plain form, and master's
   `docs/NOW.md` has 0 bold stamps against 136 plain. The gate always took
   its `None` branch and bailed on the only document it exists to check.
   The pattern now accepts either form.

   Both existing tests wrote their own bold fixture and asserted the regex
   read it back, so the producer/consumer split was invisible to them. Added
   one test pinning the plain shape `nownote.rs` emits, and one running the
   gate against the real `docs/NOW.md` with the expected date re-derived by
   the live gate's own rule -- an agreement check, not a freshness check.

   Note: nothing invokes `tri hooks now-gate` or `tri hooks pre-commit`
   today. The gate that runs is `scripts/tri check-now` -> `t27c check-now`
   in `bootstrap/src/suite.rs`, which uses `contains("Last updated:")` and
   accepts plain. So this is dead code, not a live block -- but
   `MIGRATION_AUDIT.md` advertises `tri hooks` as the replacement port, and
   it would reject every commit the moment it is wired up.

2. `.gitattributes` said root `NOW.md` "is a symlink". `git ls-tree` reports
   mode 100644 -- a regular file (a symlink is 120000). It is a divergent
   document stamped 2026-08-09. A second claim was backwards: the pattern
   `NOW.md` has no slash, so git matches it by basename at any depth and it
   already covers `docs/NOW.md`. Comments corrected; the `merge=union` rules
   are unchanged and `git check-attr merge` returns `union` for the same
   paths before and after. Whether root `NOW.md` should be a symlink, be
   deleted, or keep its content is left open -- see #2253.

3. `docs/NOW.md.master` was 835 lines of committed base64 from d063152.
   It decodes to 35,328 bytes of an obsolete NOW snapshot stamped
   2026-04-08, and `git grep "NOW\.md\.master"` finds zero references
   tree-wide. Unreadable in review, ungreppable, undiffable. Removed.

The 137 figure in the brief did not survive checking: master has 136
`Last updated:` stamps across 74 `^# NOW --` headers. The NOW entry is a
pure prepend -- `git diff --numstat` reports 86 added, 0 deleted.

Not verified: this crate was not compiled. Building it needs cargo, which
was out of scope for this change, so the Rust edits are reviewed by reading
only.

Closes #2299
@github-actions

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-08-20 05:43:07 UTC

Summary

Status Count
Total Open PRs 20
PRs with Failing Checks 5
PRs with All Checks Green 15
READY 7
FAILING 5
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=cbbfac87dff3 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag enabled auto-merge (squash) August 20, 2026 05:43
@gHashTag
gHashTag merged commit b6cb1ec into master Aug 20, 2026
22 of 27 checks passed
@gHashTag
gHashTag deleted the fix/now-gate-and-stale-now-artifacts branch August 20, 2026 05:46
gHashTag pushed a commit that referenced this pull request Aug 20, 2026
Entries were prepended to the single file docs/NOW.md, so every PR rewrote its
first line and all 18 open PRs are marked CONFLICTING by GitHub. Entries are now
one file per unit of work, docs/now/<YYYY-MM-DD>-<slug>.md: two PRs write two
different paths, so the shared line is gone rather than papered over.

merge=union is retired for both NOW.md and docs/NOW.md. Measured, not assumed:
git merge-tree from a worktree checked out at origin/master reports docs/NOW.md
clean for PRs GitHub simultaneously calls CONFLICTING, so the driver never ran
where the conflicts were reported. Where it did run its failure mode is silent
duplication: docs/NOW.md should hold one `Last updated:` line per entry heading
and holds it for every entry but one, `Wave Loop 421 close-out / Wave Loop 422
setup (2026-07-06)`, which carries no date line.

The gate asserts strictly more than before: presence (the diff must ADD an
entry, --diff-filter=A), freshness (filename date in the same
[yesterday .. tomorrow] UTC window), plus a new content assertion of one heading
and one bullet, which closes the vacuous-touch hole a whitespace edit used to
walk through. Freshness reads the filename, so there is no first-Last-updated
coupling and no line for two branches to duplicate.

Two dead consumers fixed in passing: tri hooks now-gate matched a bold
**Last updated:** label that occurs 0 times in docs/NOW.md -- every stamp there
is plain -- and so could never pass; suite.rs check_now_sync demanded today's
LOCAL date where CI allowed a UTC window, blocking work locally that CI would
accept.

docs/NOW.md is frozen as a historical archive with a pointer header. The
existing entries are NOT migrated and the orphaned entry is NOT repaired.

Rebased onto master after #2300 landed. Three notes on that resolution:

  - #2300 added two now_gate tests that pass a FILE. This change makes now_gate
    take a directory, so read_dir gives ENOTDIR and neither can survive. One of
    them, now_gate_agrees_with_the_live_gate_on_the_real_document, was the only
    test in the module that touched the real repository; dropping it for five
    temp-fixture tests would have been a net loss of liveness coverage. It is
    replaced by the directory analogue,
    now_gate_agrees_with_the_ci_gate_on_the_real_entries_directory, which runs
    the gate against the real docs/now/ and cross-checks the entry against the
    independent pattern and content rules in scripts/ci/now-sync-gate-diff.sh.
    It derives its expected date from the newest entry present rather than from
    Utc::now(), so it asserts agreement without asserting freshness and cannot
    go red merely because nobody wrote an entry today. Its one honest limit is
    recorded in the test: docs/now/ is created by this PR, so today it asserts
    against the directory this PR itself adds.

  - The FROZEN ARCHIVE banner is placed at line 1 explicitly. Under union it
    landed below master's newest entry, which read as though entry #1 were
    exempt from "do not add entries here".

  - Absolute counts in the prose (137 headings / 136 date lines) went stale
    within one wave -- master is at 138/137 now. They are restated as the
    invariant they were evidence for, in .gitattributes, in the banner, and in
    the now_gate doc comment.

Closes #2297
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

now-gate matches a bold format nothing writes; .gitattributes describes a symlink that is not one; 835 lines of base64 in docs/

2 participants