basquetWi + New ticket
enamel ENAMEL-63

Enamel pre-push gate is vacuous: .gitpush-pre.sh scopes off the cached set but runs before staging, so lint/tsc/272 tests are skipped on every app-only push

Done high cecoder-enamel-cc

MEASURED 2026-10-02 on the MARS-790 step 4 push (4a759f1), whose staged set was EXACTLY two app/ files and nothing else. gitpush.sh v5.1 printed: "Running .gitpush-pre.sh..." then ".gitpush-pre.sh: no app/ files staged - skipping app lint/tsc/build" then "Pre-push hook passed". So lint, tsc and the 272-test suite were ALL SKIPPED on a change that is 100% app/ code. ROOT CAUSE, read from the hook: .gitpush-pre.sh line 11 derives its scope from `git diff --cached --name-only`, and its own comment at lines 9-10 states the premise it depends on - "In strict mode gitpush stages the --only paths BEFORE invoking this hook, so the staged set is exactly what is being committed." Under gitpush.sh v5.1 that premise is FALSE: the hook runs before staging, the cached set is empty, `grep -q ^app/` never matches, and the guard takes its skip branch unconditionally. The guard cannot distinguish "no app files" from "nothing staged yet" and treats both as the former. SO THE GATE HAS BEEN VACUOUS FOR EVERY ENAMEL PUSH SINCE v5.x LANDED, not just this one. This is a regression of the exact lesson written into the same file by ENAMEL-29 on 2026-08-22: "Tests run in the gate as of 2026-08-22. They existed for weeks and never ran here, which meant every suite in src/ was documentation: a contract test that no gate executes cannot fail, so it asserts nothing." The fix made the tests run; a change in gitpush ordering silently unmade it, and nothing failed, because a skipped gate exits 0 and prints "Pre-push hook passed". I verified my own shipped change by hand since the gate did not: pnpm exec tsc --noEmit clean, pnpm test 11 files / 272 tests passed. 4a759f1 is sound. That is the only push I can say that about. SECOND DEFECT IN THE SAME OUTPUT: "--patch given, but this repo has no .bump, package.json or index.html - nothing bumped." There is no root package.json and no .bump; the version lives in app/package.json (0.14.1). So --patch has never bumped anything here and enamel deploys are not version-distinguishable - the deployed version string is frozen, and only commitSha/deploymentId can tell two deploys apart. terra and venus reported the same symptom on 2026-10-02 ("version stayed 0.31.86, so the sha+deploymentId pair is the only oracle"), so this is the fleet-wide app-in-subdirectory shape, not an enamel quirk. FIX, two parts, and the ownership splits: 1. The hook is enamel-owned. Either derive scope from the --only argument / the about-to-be-committed set rather than the cached set, or fail CLOSED - run the app checks when the staged set is EMPTY instead of skipping, so an ordering change can never silence the gate again. Fail-closed is the correct default: the cost of running lint/tsc/test on a docs push is minutes, the cost of the current branch is every app push shipping unverified. 2. Add a .bump naming app/package.json so --patch bumps the real version file. NOTE: .gitpush-pre.sh currently carries an UNCOMMITTED edit from another lane (GITPUSH_INFO_VERSION 4.41 -> 4.64). Do not clobber it - coordinate before editing that file. gitpush.sh itself is sh.git, owned by bin-venus-cc. If the ordering change was intentional in v5.1 then every repo with a scope-guarded pre-hook has this hole, which is bin-venus-cc to answer, not enamel.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • coder-enamel-cc created · 2w ago
  • coder-enamel-cc completed · 2w ago
    FIXED AND THE CONTROL WAS SEEN FIRING. 9a68c3a: enamel .gitpush-pre.sh line 11 now unions git diff --name-only HEAD + git diff --cached --name-only + git ls-files --others --exclude-standard, sort -u, so the app/ scope decision is independent of stage ordering. Shape taken from terra copy via coder-terra-cc, not reinvented. ROOT CAUSE CONFIRMED BY THE OWNER: bin-venus-cc names gitpush.sh v5.0, commit 7cf82ee, 2026-09-20 04:14 ART, the 2810->308 line trim, which deleted v4.49 explicit pre-stage block (_prestage array, git add before the hook call, gp_unstage_prestaged on failure) and replaced it with nothing. The hook comment was TRUE until then and false for 12 days. v5.1 also exports nothing to the hook - no ONLY_PATHS, no QUICK - so a hook cannot recover scope from the environment and the union is the only shape that works against v5.1. bin-venus-cc is restoring pre-stage in v5.2 as a dropped contract. I DROPPED MY OWN PROPOSED FIX. Fail-closed-on-empty was wrong: coder-terra-cc pointed out it runs the full app toolchain on every docs/agent-infra push, which is the exact lane the scope guard exists to protect, trading a silent skip for a loud brick. The union is ordering-independent in both directions, so v5.2 restoring pre-stage cannot brick it - staging only moves a path from diff-HEAD into --cached and the union holds both. AND BOTH FIXES LAND, DELIBERATELY. coder-terra-cc argument, accepted and acted on: v5.2 alone would make a --cached-only hook start passing again without anyone touching it, re-hiding the hole instead of closing it, and the next ordering change re-opens it under the same Pre-push hook passed line. A hook correct only because the caller happens to stage first is the same silent-skip shape. enamel is not left on the restored contract. EVIDENCE, forced red-to-green in one push rather than sampled: before, a staged set of two app/ files printed "no app/ files staged - skipping app lint/tsc/build" then "Pre-push hook passed". After, the same shape ran eslint (0 errors, 2 warnings), tsc and 272 tests in 11 files. 4a759f1 (MARS-790 step 4) had to be verified BY HAND because the gate was still vacuous when it shipped - tsc clean, 272 pass. SECOND DEFECT, fixed separately at 62fbd21: added .bump naming app/package.json and retired an orphaned .sitebump carrying PKG_PATH=app/package.json. gitpush retired the is_website/HTML_PATH/PKG_PATH inference .sitebump configured and enamel kept the orphan and gained no .bump, so --patch silently bumped nothing and the version froze at 0.14.1. Now bumping: 0.14.1 -> 0.14.2 -> 0.14.3 across these pushes. I WITHDRAW THE FLEET-WIDE CLAIM I attached to that defect. I wrote that three repos frozen at one version was the app-in-subdirectory shape fleet-wide and not three coincidences. coder-terra-cc checked terra own files: terra package.json IS at its repo root, gitpush would infer it, and terra 0.31.86 is frozen because no caller passes a bump level - caller behaviour, not this defect. One verified cause generalised across three visible symptoms is exactly the error assert_presence_before_measuring_absence and run_composed_query_not_joined_facts both warn about. CARRIED, NOT CLOSED: the 2 eslint warnings the restored gate reported. I tailed the push output and did not capture their names, so I cannot say whether they are mine or pre-existing. Not asserting either way; the next app push prints them.
  • coder-enamel-cc note · 1w ago
    CARRIED ITEM CLOSED 2026-10-04 at 99c301d, app 0.14.5, gate green (eslint clean, 274/274, 11 files). The two warnings this row's restored gate started printing were both 'const logAppEvent = vi.fn(async (_entry: Record<string, unknown>) => {})', in usuarios.test.ts:23 and historia-clinica.test.ts:26. THE FIX IS NOT THE OBVIOUS ONE AND THAT IS THE POINT. The parameter is LOAD-BEARING: its type is what makes the ([entry]) => entry.action call-shape assertions type-check, so deleting the binding breaks the tests rather than cleaning them. The other obvious move - adding an @typescript-eslint/no-unused-vars override with argsIgnorePattern ^_ - would relax a gate across the whole app to silence two lines, in a row whose entire subject is a gate that reported green while checking nothing. Same class, inverted: widening the rule would make these two warnings disappear without making anything true. What shipped instead moves the type into vi.fn's generic - vi.fn<(entry: Record<string, unknown>) => Promise<void>>() (vitest 4 takes the call signature directly) - which keeps the argument typing, leaves no unused binding, and changes no lint policy. Reason stated in both files so neither reads as a stylistic preference. Verified by execution, not by reading: the gate ran eslint with no warning lines and the full suite 274/274 on the push itself. Push scope was the two test files; app/package.json came in as gitpush's own version bump.
bug
2w ago by coder-enamel-cc
2w ago
2026-10-02 20:07