The complete, followable procedure for taking a partner from “devel has moved on” to “this release is ready to tag” — merge, semantic sweep, evidence-grade validation, release-ticket collection for the TjK and author outreach, release gates. Supersedes the verification-only devel-update-verification-recipe (now a stub).
Six phases, in order. 0 preconditions → 1 merge + semantic sweep → 2 evidence-grade validation → 3 collect release tickets (the TjK/outreach phase) → 4 release gates → 5 re-check devel before pushing.
Deliverable is never “it merged”. It is: proof the merged devel was live, an enumerated list of semantic breaks found and fixed, every remaining failure classified pre-existing vs merge-caused against a base-commit worktree, an explicit statement of which CI gates will be red and why they already were, and a ticket list with authors for the test report.
Tool: /Users/levander/coding/facekom/.claude/scripts/release_tickets.py (Phase 3).
The flow
graph TD
P0["Phase 0 — preconditions<br/>which repos? dead lines? one ticket?<br/>CHECK remote.origin.fetch"] --> P1
P1["Phase 1 — devel update<br/>worktree → chore/ branch → git merge origin/devel<br/>+ SEMANTIC sweep"] --> P2
P2["Phase 2 — validation<br/>Node 24 · base-commit worktree · isolated suites<br/>remote run in the product image"] --> P3
P3["Phase 3 — release tickets<br/>release_tickets.py → 2 buckets<br/>TjK entries + author outreach"] --> P4
P4["Phase 4 — release gates<br/>merge to customization/ · RELEASE.md · tag<br/>config sweep · smoke test · TjK"] --> P5
P5["Phase 5 — re-check devel<br/>right before push AND before release"]
P5 -.->|upstream moved| P1
Phase 0 — Preconditions
Answer these three before touching a repo.
0.1 Which repos does this partner actually live in?
Grep the partner name across the whole fleet — do not assume. Generali is vuer_oss + vuer_css only: zero references in portal_css, esign_oss, esign_css.
Watch for dead branch lines
A partner can have more than one customization/* branch and only one of them is alive.
Branch
Status
Evidence
customization/generali-atvilagitas
LIVE — the release line
tag generali-atvilagitas-1.9.11.18
customization/generali-kar
DEAD
last commit 2022-01-24, 1754 commits behind devel
Never update a dead line. Merging 1754 commits of devel into a branch nobody deploys is pure risk with zero payoff. Confirm liveness by the newest tag, not by the branch existing.
Name glue (YouTrack suffix ≠ repo name) lives in client-registry.
0.2 One ticket or two?
Both conventions are in use and both are acceptable — just decide up front:
Dedicated devel-update ticket — e.g. FKITDEV-9073Generali update 2026-07-15.
Folded into the release-prep ticket — Generali 1.9.11.19 ran the devel update on FKITDEV-9194“Generáli Release 1.9.11.19 előkészítés” itself.
The branch name carries the ticket, so the choice locks in at Phase 1. It also determines where TjK evidence gets attached.
0.3 Check remote.origin.fetch — before anything else
git -C <repo> config --get remote.origin.fetch
If it is anything other than +refs/heads/*:refs/remotes/origin/*, you are in a narrowed clone and git fetch origin devel will lie to you. See the refspec trap below. vuer_css and esign_css are narrowed; vuer_oss is not.
Sibling directory named <repo>-FKITDEV-NNNN. Never work in the main clone — it carries other people’s WIP and half-finished branches, and a devel update touches hundreds of files.
A GitHub ruleset rejects the legacy update/customization/<partner>/<date> form outright. Precedent: chore/FKITDEV-9073-generali-update-2026-07-15, chore/FKITDEV-9194-generali-update-2026-08-08. Commit messages are equally constrained — see techteamer-commit-message-ruleset.
1.3 Merge, never rebase
git fetch origin '+refs/heads/devel:refs/remotes/origin/devel'git merge origin/devel # a real merge commit
Merge commit, always. Rebasing a partner branch onto devel rewrites years of partner history.
The PR targets customization/<partner>. It never goes into devel. Devel is upstream of the partner branch, not downstream.
1.4 The semantic sweep — this is the actual work
A textually clean merge is not a working merge
Git reconciles text. devel deletes, renames and refactors core files while the customization/ tree keeps requiring them by path and calling them by name. Every one of those breaks merges conflict-free and fails at runtime.
Run these five checks, in this order.
(a) Deletions and renames first — the highest-risk class
Do this first because customization/ overrides bind to core by path and go stale silently.
vuer_css — caught exactly one: devel DELETEDserver/util/aiActHelper.ts (replaced by server/web/helper/getAiActData.js), still required at customization/server/web/routes/waiting-room.endpoint.js:5. Left as merged, the waiting-room route throws Cannot find module at request time.
vuer_oss — 0 deletions, 0 renames in the merge. That is not a shrug: it makes the entire bug class structurally impossible for this merge. Worth measuring precisely because it tells you how hard to look at everything else.
(b) Programmatically resolve every relative require/import under customization/
Do not grep by eye. Resolve all of them and report unresolvable ones.
Skip comments, or you drown in false positives
Generali’s vuer_oss had 704 relative requires under customization/. The only unresolved hits were 4 commented-out placeholder examples — proven inert by showing they are byte-identical on the base commit.
Extension rules matter: devel converts .js→.ts regularly and vuer repos require an explicit .ts (typescript-in-vuer-repos). An extensionless require that used to resolve will stop.
(c) Grep customization for identifiers devel REMOVED
Removals are the highest-risk change class, and they are invisible to git.
supportedSteps — removed by devel’s feat: [fkitdev-9119] — had 0 references in real source in either repo. Verify rather than assume in either direction.
Devel’s a6185aa41 fix: [fkitdev-8846] remove duplicated socket connections (#3130) replaced auth() with SocketService.getConnection('<page>.script') plus a “Missing layout socket connection provider” guard. Generali’s customization/ui/pages/gen-self-service-consent-{pep,ttny}/{*.script.js,*.ui.js} kept calling auth(...).
(d) Diff each customization/ override against its core counterpart
Look for drifted function signatures, renamed config keys, changed template variables. An override is a fork of a core file — it silently stops tracking the moment core moves (breakage-risks).
Fix direction
Mirror devel’s own core refactor inside the override, keeping the partner-specific identifiers and template paths (gen-*, cofidis-*, …). Do not revert core to suit the override.
(e) Diff the DEPENDENCY SETS — in both directions
The one class of break that neither git, nor lint, nor the test suite will show you
A removed dependency merges clean, lints clean, and dies at require-time on boot. git merge-tree reports the affected files as untouched, because the two sides never edit the same line: devel edits package.json, the partner’s require() sits in customization/. Full write-up: devel-dependency-removal-breaks-partner-customization.
depcheck cannot help. It runs on the branch it is invoked on, and partner customization/ code exists only on the partner branch — so it was never in the tree when devel decided the dependency was unused.
Do not diff package.json as text and move on. Compare the dependency sets of customization/<partner> and devel, and walk both differences.
# what did the merge REMOVE?git diff <merge>^1..<merge> -- package.json | grep '^-' | grep -v '^---'# for each removed package: does partner code still import it?git grep -n "require(['\"]<pkg>" -- customization/git grep -n "from ['\"]<pkg>" -- customization/# does it resolve ANYWAY? (present in node_modules, absent from package.json)node -e "console.log(require('<pkg>/package.json').version)"
Then decide per package. There are three outcomes, and only one of them is “restore the line”:
Situation
Correct action
devel removed it deliberately — security retirement, EOL package — and partner code imports it
Port the partner code off it. Restoring the dependency re-introduces precisely what devel retired.
devel removed it as merely unused in core, partner code imports it
Restore the version the partner declared pre-merge.
Partner-only dependency devel never had, still imported
KEEP it. Naively “taking devel’s package.json” at conflict resolution silently deletes it.
devel’s deliberate security removal — CIB’s own security/*.md had logged “Remove of request and request-promise-native scheduled” monthly since 2025-03 ⇒ ported to fetch (mTLS via undici, see vuer-oss-global-fetch-ignores-agent-mtls)
The third command is the one people skip, and “it still resolves” is not a pass. CIB’s multer failed require.resolve() outright — a real runtime break in customization/api/document-upload.js. Its uuid was worse: it resolved only transitively off devel’s hoisted uuid@14.0.1 against a declared ^10.0.0 — working by accident, at a major nobody chose, ready to break with zero partner-side changes the day the hoisting dependency moves. Undeclared-but-hoisted is unowned.
eslint gives you the quiet case for free
n/no-extraneous-require flags “you require something you did not declare”. Under --max-warnings 0 it fires on the transitive case at no cost — do not silence it.
Also check the merge for lint-config changes in the same pass: if devel migrated eslint, partner eslint-disable comments for now-disabled rules become “Unused eslint-disable directive”warnings, and --max-warnings 0 makes them failures — eslint9-flat-config-dead-disable-directives.
Phase 2 — Validation (evidence-grade)
2.1 Get Node right before you conclude anything
The repo default is too old, and engines lies
vuer_ossyarn install --frozen-lockfile hard-fails on Node 22 — geoip-lite requires >=24. package.jsonengines still says >=22.18.0, which is stale; CI pins Node 24. Jest also needs >=24.9 here (SLACOFI-14).
Locally: export PATH=/opt/homebrew/opt/node@24/bin:$PATH (24.18.0 used for Generali and CIB). A wrong-node failure tells you nothing and costs an hour.
Run the gate through its npm script — calling the tool directly drops flags
Invoking jest directly (a tempting way to dodge RTK’s yarn rewriting — see below) silently loses whatever the package.json script adds around it. On CIB (FKITDEV-9197), omitting --experimental-vm-modules manufactured 4 phantom failures in vuer_oss.
If you must bypass the script runner, copy the full flag list out of package.json first — and prefer rtk proxy yarn … over hand-rolling the invocation.
2.2 Classify every failure against a TRUE baseline
Never assert "pre-existing" without measuring it
Claiming a failure is pre-existing without a baseline run is the single easiest way to ship a regression on a partner branch.
git worktree add ../<repo>-base <pre-merge-partner-tip>cd ../<repo>-base && yarn install --frozen-lockfile # its OWN install
Do NOT symlink node_modules from the merged tree
The merge changes yarn.lock — 800 lines in the vuer_oss case. Symlinking the merged tree’s node_modules tests the base commit against the wrong dependency tree, and any conclusion drawn from it is void. That shortcut invalidated a first attempt on this very round. Pay the second yarn install.
It is worst exactly where you need the control most: when the merge changed dependencies, the symlink contaminates every suite that touches a removed one. On CIB (FKITDEV-9197) vuer-cv-service failed in the base worktree with Cannot find module 'request-promise-native' — a failure manufactured entirely by the shortcut, telling you nothing about the base commit.
Diff the failure sets, not the counts.
2.3 Run suspect suites isolated, and repeatedly
Full-suite vuer_oss runs showed a shifting failure set — test-order interference masks the signal. Isolated runs of the suspect suites were deterministic across 3 runs each. Take the isolated result as the truth, and say so in the write-up.
2.4 Real Generali numbers
Measured on fk-dev, inside the product image, Oracle Linux 9.7 / Node 24.12.0:
Tree
Result
Base bfe85a4e68
3 suites / 9 tests failed
Merged
2 suites / 3 tests failed
⇒ zero merge-caused failures; the merge NET-FIXES 6 tests. Survivors are converter and vuer-cv-service, both pre-existing and environmental — vuer-cv-service hardcodes the CI path /workspace/.... That is a far stronger statement than “3 failures, looks pre-existing”.
vuer_css on the same round: lint / build / depcheck PASS, test:unit120 suites / 1083 passed / 0 failed (base 115/1043/0 ⇒ devel’s 5 new suites all pass against Generali customization), improved-yarn-audit --min-severity critical0 vulnerabilities.
2.5 Remote validation on fk-dev, inside the product image
Unit tests that pass on macOS prove less than the same tests inside the image that ships. Run them on the fk-dev Tailscale VM (root@fk-dev, 100.91.108.61 — see dev-build-host) in a throwaway container off the real image (harbor.techteamer.com/facekom-devel/vuer_css:...) with the source mounted. No need to disturb the running stack or its DB.
Ship the tree with git archive, or:
COPYFILE_DISABLE=1 tar --exclude='._*' -czf /tmp/tree.tgz -C <worktree> .
macOS AppleDouble files become ~20 bogus failing suites
A plain tar on macOS emits ._* AppleDouble sidecars. They land in the tarball, jest picks them up as test files, and you get ~20 fabricated failing suites that look like merge damage. COPYFILE_DISABLE=1 + --exclude='._*', or just use git archive.
The product image sets NODE_ENV=dev — in-image results read GREENER than CI
jest defaults to NODE_ENV=test; the image runs as dev. Every failure that depends on which config file loads is therefore masked in-image and live in CI.
On CIB (FKITDEV-9197) portal-client.test.jspassed in-image and failed in CI for precisely this reason — a module-scope require('../../../config') that finds dev.json under dev and nothing under test.
In-image validation is a strong check on native build/lint and on anything platform-dependent. It is a weak check on config loading. Do not report an in-image pass as a CI prediction.
Three environments, three answers — for the same tree
macOS local, fk-dev in-image, and CI disagreed on CIB, structurally rather than flakily: macOS has no /etc/hostname (see Gotchas), the image pins NODE_ENV=dev, CI runs NODE_ENV=test.
Treat “it passes locally” and “it passes in-image” as weak evidence about CI. The in-image pass is the more seductive of the two, because it looks like production.
Access — what actually works (tested 2026-08-12):
ops and root are both permitted. The canonical string stays command ssh ops@fk-dev.taild4189d.ts.net (dev-build-host). levander, ubuntu, admin and facekom are refused with tailnet policy does not permit you to SSH as user "<u>".
Connect by name, not by IP.fk-dev and fk-dev.taild4189d.ts.net are in known_hosts; 100.91.108.61 is not, and the IP form fails with Host key verification failed.
Use throwaway containers off the real image with the source mounted — the running partner stack stays untouched.
ssh is aliased
ssh is aliased to _kaku_wrapped_ssh, which is not loaded non-interactively — use command ssh from scripts.
2.6 Work out what CI NEWLY turns on
Three separate questions:
Does the branch inherit new jobs? A legacy partner branch on the single lint-and-build job jumps to devel’s 6 jobs on its first devel merge (customization-branch-ci-pipeline-inheritance). Generali’s branches already carried the 6-job pipeline, so this did not fire. Cofidis’s did — it cost 5 failure clusters in FKITDEV-9059. CIB’s fired on all three repos, and on portal_css the inherited audit job took the branch from 25 CRITICAL advisories to 0 — the expansion is not always a tax (FKITDEV-9197).
Does the branch enter a scheduled-workflow MATRIX? Check the matrix, not the file’s presence. vuer_oss gained long-lived-branches.yaml, but it is schedule-only and its matrix lists devel / mbh / raiffeisen / kh / unicredit — Generali is unaffected.
What does the gate graph imply?build has needs: [lint, test, audit, sonar] — a red audit also blocks build.
A red gate hides everything downstream — "skipped" is not "passed"
On the first CIB vuer_css run (FKITDEV-9197) Build and SonarQube did not fail, they were skipped, because both needs: the red test job. Reading that run as “4 green, 1 red” overstates what is known by two whole jobs. Re-read the needs: graph before reporting a run, and re-run after every fix rather than assuming the untouched jobs are fine.
vuer_oss audit is RED on every partner PR — and it is not yours
sequelizeGHSA-v8fg-2rw7-q452 via sequelize@npm:@techteamer/sequelize6.32.2. Pre-existing: byte-identical resolution on the base commit, which fails the same gate. This is the FKITDEV-8279 fork problem. Say so explicitly in the PR body, because it also reds out build and will otherwise be read as merge damage.
2.7 Local gate checklist
Run all of these, per repo, and record the numbers:
yarn lint
yarn test:unit — against the base-commit worktree too
improved-yarn-audit --min-severity critical — this is the real CI gate, not full yarn audit; parse-error lines on its output are cosmetic (cve-2025-7783-form-data-via-request)
This is the phase that turns a green branch into a shippable release
The TjK needs one section per shipped ticket, and you cannot write those sections yourself — you need to reach out to the people who wrote the code. This phase produces both the section list and the contact list.
It resolves the latest release tag per repo from git ls-remote --tags (and fetches the tag explicitly — necessary in narrowed-refspec clones where the tag is not local), then emits two markdown tables with YouTrack links, GitHub commit links, GitHub PR links, state, assignee and commit author.
Partner-specific work. Always needs a TjK entry — this is what the partner paid for.
2
Came via devel but modified customization/
git log <tag>..HEAD --no-merges -- customization/ minus bucket 1
Core work that nonetheless altered partner behaviour. Usually needs an entry too — and is easy to miss entirely.
Bucket 2 is the whole reason the tool exists. Nobody reviewing a devel merge thinks of core commits as “release scope”, yet a core commit that touched customization/ changed the partner’s surface and has to be tested and reported.
3.2 Verified output — Generali 1.9.11.19
Baseline tag generali-atvilagitas-1.9.11.18.
Bucket 1 — landed directly on the customization branch
FKITDEV-9080 is Pending and shipping anyway. The code is merged and on the branch, so it goes out with the release regardless of what the ticket says. Surface that to the release owner rather than letting the ticket state imply it is out of scope.
3.3 Two things the tool deliberately does NOT do
Assignee ≠ commit author — contact the right human
Both columns are emitted for exactly this reason. FKITDEV-8567 is assigned to Ferenc Jurkiewicz but was authored by Szecsődi Imre. When you need “what does this actually do, and how do I test it”, the author usually knows and the assignee sometimes does not. Pick per ticket.
The headline changelog item can be invisible to the tool
The tool only sees tickets referenced in commit subjects of commits reachable from the tag. A core commit that never touched customization/ is in neither bucket — yet it can be the release’s headline feature.
For 1.9.11.19 that is exactly what happened: the headline is ASSGRALI-63 / FKITDEV-8887 (iOS Safari audio recovery, 6bdf66d16), a pure-core commit. Always cross-check the parent ASS* release ticket’s changelog against the tool’s output and merge the two lists by hand.
3.4 What to do with the output
Draft the TjK section list from bucket 1 + bucket 2 + the ASS* changelog.
Message each author/assignee for the functional description and expected behaviour of their ticket.
A green pipeline on a chore/... branch means the code compiles and the tests pass. It does not mean anything is releasable. Six gates:
The chore/... branch is MERGED into customization/<partner> via PR. The tag is cut from the customization branch, not from your working branch. Until the PR lands, there is nothing to tag.
customization/RELEASE.md changelog entry. Historically its own ticket — FKITDEV-9153 did this for Generali .18. Format varies per partner (Release Notes Format).
Tag <partner>-1.9.11.NN on both repos.
package.jsonversion stays 1.9.11
The .NN release counter lives only in the tag and the changelog. Do not bump package.json looking for it.
Breaking-change / config sweep. Run over the full range:
Real example from 1.9.11.19 — a silent config break browsers.showOldBrowserWarning → browsers.oldBrowserWarning.{show,delay}, and server/web/Template.js:123 now reads the new path. Any partner production config still on the old key silently loses the old-browser warning — no error, no log line, the feature just stops. docs/config/ was not updated to match, so the schema does not warn either (typescript-in-vuer-repos sibling gotcha: new config fields need a JSON-schema entry).
Devel restructured
Also new in this range but default-off (so safe, but worth listing in the changelog): sftp, dataCleanupCron, transferRoomCron, documentRecognitionVersion, roomExportFilesExtendedName.
Functional smoke test on a real deployment. Unit + lint + build in a container is not a smoke test. Boot the app and drive a real flow — see mjml-v5-esm-breaks-commonjs-email-templates for a green-CI/broken-runtime break that no gate above catches.
TjK written with evidence — [[tesztjegyzokonyv-generation-flow|/fk-tjk]], using the Phase 3 ticket list.
Phase 5 — Re-check devel immediately before pushing, and again before release
The hard lesson of this round
I diagnosed a devel regression (FKITDEV-9199 — dataset.socketToken never resolving, because the twigs emitted camelCase data-socketToken), wrote a reader-side fix and pushed it — while the upstream fix cda6c80b97 had already landed ~2 hours earlier, taking the opposite approach (kebab-casing the twigs). Merging mine on top would have re-broken it.
Always git ls-remote for the upstream fix right before pushing to a fast-moving core branch — not just before merging. Hours matter on devel.
Practical form:
git ls-remote origin refs/heads/devel # right before pushgit log --oneline origin/devel -20 | grep -iE '<the thing you fixed>'
Re-run the same check again before cutting the release — the window between “PR merged” and “tag pushed” is often days.
Gotchas
git log -S does NOT follow renames — and that decides the fix side
A pathspec + -S silently stops at the rename, and you get a FALSE history
On CIB (FKITDEV-9197) git log -S "status(400)" -- <post-rename path> returned nothing, which was read — and asserted to the user — as “400 never existed in this middleware”. It had existed, under the file’s old name, and the whole question “was this test ever right?” turned on it.
git log --follow -S '<string>' -- <path> # follows the renamegit log -L <start>,<end>:<file> # better — one line's ENTIRE history, across renames
This bites specifically in devel-update work, where a stale-looking test forces the choice fix the test vs fix the code, and that choice is made almost entirely from history. A false negative sends you to the wrong side. Devel merges are full of renames by construction.
The general rule — when a negative result is about to carry weight, change instrument
An empty git log -S, a still-failing hypothesis test, a refused SSH user — none of these are evidence of absence until you have checked that the instrument could have detected the thing. In devel-update work this decides whether you fix the test or fix the code (FKITDEV-9197 §14.1), so getting it wrong sends you to the wrong side of the change.
Distinct from “read the actual error text” below, which covers a failure being misread. This covers a null result being over-trusted — three worked instances, and how each was caught, in §15.
macOS cannot load vuer_css’s config.js under NODE_ENV=dev
config.js:43 does fs.readFileSync('/etc/hostname') on the dev / travisci path. macOS has no /etc/hostname → ENOENT, unless DEV_DOMAIN is set.
The real cost is the wrong conclusion, not the ENOENT
On CIB, an agent set NODE_ENV=dev to test a config-loading hypothesis, saw the suite still fail, concluded “NODE_ENV is not the variable”, and reattributed a genuine CI failure to a “nested worktree artefact”. The hypothesis was correct; the test of it was broken by an unrelated platform gap.
When a hypothesis test fails, read the actual error text. Never generalise from a failure you have not identified — a failed experiment only refutes the hypothesis if it failed for the reason you were testing. The move that resolved it was running the same tree in the Linux product image on fk-dev (Phase 2.5).
GitHub’s default PR title fails the branch-name ruleset
The branch name the rulesets require produces a PR title the rulesets reject:
Chore/fkitdev 9197 cib devel update ← GitHub's default from the branch
chore: [fkitdev-9197] merge devel into customization/cib ← conforming
It misses on both counts — no lowercase type: prefix, no bracketed lowercase ticket. This is structural, so make it a checklist item: open the PR, then immediately fix the title (techteamer-commit-message-ruleset).
gh pr edit infers the repo from cwd — always pass -R
In a three-repo round with worktrees in unusual places, cwd inference is a coin flip.
Narrowed fetch refspec
git fetch origin devel silently leaves origin/devel STALE
In vuer_css and esign_css the clone has a narrowedremote.origin.fetch (e.g. +refs/heads/customization/raiffeisen:…). git fetch origin devel therefore writes only FETCH_HEAD, never refs/remotes/origin/devel — while printing a cheerful success line and exiting 0. The following git merge origin/devel merges a stale tree, conflict-free, with zero warning.
This nearly shipped Generali 1.9.11.19 without its headline feature: the first css merge used devel @ 7d4f9956c8 (2026-07-28) against a live 3ace8872c3 (2026-08-07) — 6 commits dropped, including 6bdf66d16 = FKITDEV-8887 = ASSGRALI-63, the release’s main item. Full mechanism: narrowed-fetch-refspec-stale-devel-merge.
--force-with-lease fails with “stale info” in those same repos
Because git cannot infer the lease without a remote-tracking ref. Do not fall back to plain --force — keep the safety net by naming the expected sha:
$B:r is zsh’s remove-extension history modifier and eats the destination. Use fully literal refspec strings, or single-quote and interpolate outside the :.
Test files cannot be linted in vuer_css
The eslint config references a missing jest-formatting plugin and exits 2 on devel’s own test files. That is why yarn lint ignores test/*. Do not “fix” it as part of a devel update; do not read a lint pass as covering the test tree.
portal_css has the same residue: devel removed eslint-plugin-jest-formatting in 65ac214c feat: eslint 9 FKITDEV-6045 but eslint.config.mjs still references 'jest-formatting/padding-around-all': 'warn'. It is inert only because that block is scoped to files: ['test/*','test/**/*'] and yarn lint runs --ignore-pattern "test/*". Latent landmine if test linting is ever enabled.
An eslint-9 migration turns partner eslint-disable comments into build failures
Disabling a rule should make lint quieter. It does the opposite here. devel’s flat-config migration dropped compat.extends('standard','plugin:n/recommended','plugin:jest-formatting/strict') for n.configs['flat/recommended'] + js.configs.recommended and turned offno-empty / no-unused-vars / no-redeclare / no-useless-assignment. Every partner // eslint-disable-next-line <that rule> then reports “Unused eslint-disable directive” — a warning — and yarn lint --max-warnings 0 makes it red. CIB: 9 dead directives across 6 customization/ files (FKITDEV-9197).
test/tests/unit/translations.test.js:181 misuses it.each — the callback is passed as each()’s second argument and the returned function is discarded ⇒ zero tests registered
Never cite this suite as coverage. Its green is structural. Every wrongKeys / missingLanguage finding it computes is discarded — a probe found 42 invisible findings on CIB alone
The same file’s scan() takes relativeincludedDirectories while cwd is absolute ⇒ loading depends on process.cwd() at module-eval time
Actively flaky in CI — Directory not found: client/features on ~1 nightly run in 3, same commit, both runners, client/features being real and tracked. Does not reproduce on macOS
server/util/pdf.test.js › printImage › place sample PNG image does a byte-for-byte PDF comparison
Drifts by 3 bytes at random. A re-run with no content change goes green
Operational consequence: a single red run is not evidence of a regression, and a single green run is not evidence of its absence. Re-run, then classify against a base-commit worktree (Phase 2.2) and run suspect suites isolated and repeatedly (Phase 2.3).
If you are tempted to fix any of them — two traps
The it.each repair is a breaking change, not a cleanup: it converts those 42 findings into hard failures on CIB and an unknown number elsewhere. Budget the fallout, upstream, as its own ticket.
The obvious scan() one-liner silently breaks the suite.path.join(cwd, startPath) makes every returned path absolute, and three downstream consumers key off relative paths (require doubles the path, excludedFiles.includes stops matching, argMap[...] goes undefined). Keep the returns relative — the correct form is in the note.
portal_css has no test signal at all
yarn jest on pristine devel runs zero tests, and would still run zero if you wrote some — the custom test/lib/jest/test.sequencer.js has an empty CORE_TEST_ORDER and prepareTests filters with order.includes(...), and jest sorts before its “no tests found” check. CI is green only because the workflow appends --passWithNoTests. On this repo, validate with yarn lint + yarn build and do not claim a test gate — portal-css-jest-runs-zero-tests.
Portal versioning is a separate train
portal_css does not carry the vuer 1.9.11.NN numbers. CIB release 1.9.11.102 corresponds to portal tag cib-1.4.0.74. Resolve the Phase 3 baseline tag with the repo’s own prefix (cib-1.4.0.), not the release number quoted in the ticket.
No jsdom — jest runs in the node environment
All DOM behaviour in vuer_css is hand-mocked, so nothing exercises real HTML attribute parsing. This is exactly why the data-socketToken regression shipped green through 1083 passing tests.
To verify DOM-coupled behaviour, render for real
Render the actual twig with the real twig engine (autoescape: true, matching server/web/WebServer.js:257) and parse the output with a real HTML parser. Asserting against a hand-built mock object proves nothing about attribute casing, escaping, or serialization.
youtrack_guard hook blocks git operations
The facekom PreToolUse hook blocks git commit / git push / gh pr (and rm -rf) while /tmp/fk-ticket/.analyze-active exists, and it re-arms. When the user has authorized the action, clear it with a plain rm (not rm -rf, which the hook also blocks).
node_modules is not gitignored when it is a symlink
.gitignore has a trailing slash (node_modules/), which does not match a symlink. In any worktree where you symlinked or hand-placed deps, stage files explicitly — never git add ..
RTK mangles some git/jest invocations
Never trust RTK-rendered git output for a verification claim
RTK failures here are silent and confident, not error-shaped — you get a plausible wrong answer. Confirmed modes, all hit during real devel updates:
Invocation
What RTK does
git show <rev>:<path>
rewrites the <rev>:<path> argument
git show <ref>:<file> | grep
garbles output so real matches read as absent — nearly caused a wrong conflict resolution (FKITDEV-9197)
git log -1 right after committing a merge
shows the wrong commit (devel’s tip instead of the new merge commit)
diff -u
unreadable line-offset view plus a wrong exit code
git diff --no-index
renders as if the whole file were new
${PIPESTATUS[0]}
blanked
jest --verbose
per-test lines swallowed
find | wc -l, | grep -c
zeroed
Workarounds: rtk proxy <cmd>; git grep <pattern> <ref> -- <path> instead of git show \| grep; shasum -a 256 for same-vs-different; python3 subprocess.run([...]) with an argv list; python3 -c "import difflib…" to generate a diff; jest --json --outputFile=<f> when you need to assert on results.