For Agents

Living index of themes for the FaceKom KYC platform (vuer_oss / vuer_css / vuer_cv). Each H2 is a topic; bullets are wikilinks to related notes. Updated by obsidian-documenter when documenting work. Read by historian at bootstrap. Topics kept alphabetical.

Authentication / AD + LDAP

  • FKITDEV-9252the failure mode of passport-activedirectory: a transport error is reported via this.error(), which passport routes to the error handler and which ABORTS the strategy chain ⇒ a reset LDAPS connection 400s the login (“Something broke into 400 pieces!”, 47 fails vs 28 successes over 9 days) and the configured second AD server is never tried. Amplifier: one AD login opens several LDAP connections — user search, bind, and with useMemberOfProperty: false also the group-membership query — so any single reset is fatal. Fix = catch → retry → failover (vuer_oss PR #8143, 5e24c3667d, approved, not merged to devel as of 2026-09-07), LIVE-VERIFIED 2026-09-07: an always-resetting server 1 takes 5 resets + 4 retries → fail('ldap_unavailable'), the chain moves to server 2 and authenticates the user there (previously the chain aborted and server 2 was never tried). The retry split is decided by HOW the connection dies, NOT by which phase — an earlier phase-based claim (search → retries, bind → no retry) is REFUTED by experiment, do not repeat it. Observed in both phases: RSTError code:'ECONNRESET' (string) ⇒ retries 4× then failover; graceful FINConnectionError code:80 (number) ⇒ NO retry, immediate failover; hang ⇒ TimeoutError code:80; closed port ⇒ ECONNREFUSED. Mechanism: ldapjs emits both the raw socket error and a ConnectionError, and activedirectory2’s client.on('error') fires first with the raw one while its callbackInvoked once-guard swallows the ConnectionError — so RST wins the race, and a clean FIN produces no socket error at all. ⇒ the case that most deserves the retry (FIN: idle timeout, F5/HAProxy drain, DC restarting cleanly) is precisely the one excluded — a policy question, not a defect. AD_CONNECTION_ERROR_NAMES is load-bearing: ConnectionError+TimeoutError carry a numeric code:80 so the string-code list can never match them. No unit test can see any of this (authenticate = jest.fn()). A reusable LDAP fault-injection fixture now EXISTS (BER-parsing TCP proxy, socket.resetAndDestroy(), triggers selectable by connection + op ordinal) — but UNTRACKED in a scratch worktree, see the note. Also verified sound and worth reusing: passport 0.7.0 does Object.create(prototype) per attempt (lib/middleware/authenticate.js:195) ⇒ overriding this.error/fail/success inside authenticate is per-request, not shared state; and passport-activedirectory’s main is the rollup build index.js, NOT src/strategy.js (they differ — always read index.js)

Build / probe-build workflow (UBI10)

  • FKITDEV-8252 — podman + libkrun on macOS Apple Silicon for emulated linux/amd64 builds; needs 8 GiB RAM minimum (4 GiB OOMs on gcc-c++ family installs), 6 CPUs sufficient; iteration counts as complexity proxy (portal_css 3, vuer_css 3, vuer_oss 5, janus 7, vuer_cv 7); subagent Bash allowlist is more restrictive than main-session shell — plan Phase B with this in mind

CI / build gates

  • FKITDEV-8279the audit gate is red on vuer_oss devel itself, not just on customization branches (contrast ci-github-branch-audit-chronically-red): improved-yarn-audit --min-severity critical returns Found 1 vulnerabilities (sequelize, CRITICAL) exit 1 on devel and Found 0 exit 0 on the fix branch. Two structural details worth reusing: the yarn audit step directly above the gate swallows its exit code, so only the improved-yarn-audit line gates; and --min-severity critical means a HIGH advisory shows in the output for months without failing anything (GHSA-6457 was visible from 2026-03-11 and never blocked). Also a depcheck trap for any patch-package adoption: postinstall-postinstall has no bin and is never imported ⇒ depcheck reports it unused (exit 255) while patch-package itself is resolved correctly via depcheck’s bin special from the postinstall script — fix by adding both to .depcheckrc.json ignores. depcheck is warn-only and not in needs:, but a PR whose headline is “the red CI job goes green” must not open with a newly red check

  • k6-e2e-harness-vuer-ossan entire test suite with zero CI coverage: vuer_oss test/tests/k6/ is not linted (yarn lint ignores test/*), not typechecked (its tsconfig.json is separate from the root one, which excludes test/; there is no typecheck npm script), and not run by any workflow. A k6 file can be type-broken or reference a deleted page object and no gate notices. Manual gate: tsc -p test/tests/k6/tsconfig.json with @types/k6 installed. Same root cause as typescript-in-vuer-repos’s “no typecheck job in CI”

  • vuer-build-never-pushesthere is no publish gate on the legacy path at all: vuer_build/build.sh has no docker push / no docker login, so a “successful build” on the legacy path leaves nothing in Harbor. The modern vuer-release CI does publish (release-tool publish push + HARBOR_USER/HARBOR_SECRET). How legacy images reach the registry is an open question. Also: -l/--list-partners is broken (cd partners/ vs real partner/) and builds fail without an operator-supplied base/<svc>/github.key

  • depcheck-false-positive-minified-bundledepcheck can report a CI-only false positive because it fails OPEN on unparseable files. vuer_css CI flagged @emotion/is-prop-valid as unused while the identical pinned depcheck@1.4.7 + lockfile was clean locally and in a Linux container. The only reference is a literal require("@emotion/is-prop-valid") in a try/catch at web/sdk/web-sdk.js:205, and web/sdk is not in ignore-patterns — CI’s depcheck evidently can’t parse the 865 KB minified vendor bundle (OOM/timeout on the self-hosted runner) and then treats the file as containing no requires. Do NOT remove the dependency — it flips depcheck to reporting it missing. Fix = add to ignores in .depcheckrc.json. Generalized triage rule: a depcheck diff between CI and local with identical version+lockfile points at a parse failure on a big minified/vendored file, not a real dependency change. First hit on FKITDEV-8387; job introduced by FKITDEV-8239

  • FKITDEV-8239SonarCloud “Security Rating on New Code” gate fix (2026-06-23): after the depcheck PRs opened, the gate FAILED on 4/5 PRs because the new workflow lines tripped SonarCloud’s GHA supply-chain rules — npx (on-demand install; confirmed sole driver on vuer_css), yarn install (lifecycle scripts), unpinned actions/*@v6 (use full commit SHA); the existing jobs use the same patterns but are grandfathered as old code, only the PR’s new lines are gated. Profiles differ per repo (vuer_css=npx only; portal_css=all three). Fix (3 options considered): appended .github/** to sonar.exclusions in each sonar-project.properties (already present in sonar.coverage.exclusions); pushed solo-author andras.lederer (vuer_oss c2b5cfcb3f, vuer_css 93c9d976e, portal_css 7ee62cbd, esign_oss 4e96053, esign_css 9f9b862). gh pr checks: initially 4/5 green (vuer_css #3076, portal_css #703, esign_oss #353, esign_css #253); vuer_oss #8001 was red but NOT sonar/NOT this change — pre-existing self-service-room-archive Unit Tests failure (2 tests, async-leak; 3512 pass/2 fail) that also failed on devel, sonar/build skipped behind the test job. RESOLVED 2026-06-23: colleague merged the test fix to vuer_oss devel in PR #8003 (commit cfdc116543); merged origin/devel into the depcheck branch (clean, no conflicts — devel only touched CODEOWNERS + the test file; merge commit 3717e30b91 solo-author) and #8001 re-ran fully green. All 5 FKITDEV-8239 PRs now green and ready to merge (#8001/#3076/#703/#353/#253). Reusable gotcha → SonarCloud “Security Rating on New Code” can fail on new CI workflow lines

  • FKITDEV-8239adversarial deep-review verdict (2026-06-23): NO bugs, ship as-is. actionlint v1.7.12 clean (exit 0) on all 5 workflows (full Actions schema + expression validation); all 5 diffs purely additive vs origin/devel (31 insertions/0 deletions); ${{ env.NODE_VERSION }}=“24” resolves in all 5; bare npx -y depcheck@1.4.7 auto-discovers .depcheckrc.json; the 5 job blocks byte-identical except the intended install spelling (yarn --frozen-lockfile vuer_oss vs yarn install --frozen-lockfile ×4); depcheck warn-only (exit 255 absorbed by continue-on-error), unused_devDeps=[]; extra finding — portal_css has a genuine missing devDependency istanbul-lib-coverage. Merge-time op rule: do NOT add the Unused Dependencies status check to branch-protection required checks or it stops being warn-only

  • FKITDEV-8239warn-only depcheck CI job across all 5 repos (vuer_oss/vuer_css/portal_css/esign_oss/esign_css); continue-on-error: true, not in any needs: graph → never blocks a PR; tool pinned npx -y depcheck@1.4.7 (correct on both CI and macOS npm v10); .depcheckrc.json ignores build/lint/test tooling + dynamically-loaded runtime deps (pg/pg-hstore via Sequelize dialect, postcss via build pipeline) + vendored asset paths; Yarn Constraints NOT implemented (Yarn Classic v1 — Berry-only feature); candidates surfaced: vuer_oss soap/umzug, vuer_css add, portal_css lodash/tmp/tough-cookie, esign_oss ajv/fast-xml-parser/inquirer/jsdom/protobufjs/umzug, esign_css license-checker/postcss; portal_css over-suppression bug found+fixed (those 3 were genuinely unused — adding to ignores hid the signal the ticket exists to surface); all LOCAL/uncommitted on chore/FKITDEV-8239-depcheck-ci

  • FKITDEV-8887reading SonarCloud PR issues without a Sonar token (reusable, vuer_css projectKey vuer-css, org techteamer): SonarCloud posts every issue as a GitHub check-run annotation on the PR head commit (gh pr view <n> --json statusCheckRollup,headRefOid → find “SonarCloud Code Analysis” + head SHA → gh api repos/o/r/commits/<sha>/check-runs for the run id → gh api repos/o/r/check-runs/<id>/annotations); sonarqubecloud bot also leaves a PR summary comment. Gate gotchas: a passing Quality Gate ≠ zero issues (gate checks only new-code threshold metrics — coverage/duplication/rating — so it’s GREEN with many code-smell “New issues”); no analyzed devel baseline (only pull-request.yaml runs Sonar) → Sonar attributes pre-existing smells in a touched file to the PR, confirm authorship with gh pr diff <n>; annotation_level failure = issue severity, not a gate failure. Full recipe in 10. Verified gotchas

  • SonarCloud “Security Rating on New Code” can fail on new CI workflow linesadding NEW lines to a SonarCloud-scanned GitHub Actions workflow can fail the “Security Rating on New Code” gate via GHA supply-chain rules (npx on-demand install, yarn install/npm install lifecycle scripts, unpinned actions/*@vN SHAs) — even when the rest of the CI already uses those same patterns (grandfathered as old code; only the PR’s new lines are gated). Profiles differ per repo. Resolutions: (a) exclude .github/** via sonar.exclusions (TechTeamer repos already exclude it from sonar.coverage.exclusions); (b) run CLI tools via a pinned devDependency instead of npx + pin actions to full commit SHAs. The issue/rule REST APIs need auth for these private projects, but the per-PR findings are readable via GitHub check-run annotations (gh api repos/<repo>/check-runs/<id>/annotations — full recipe in FKITDEV-8887). First hit on FKITDEV-8239

  • ci-github-branch-audit-chronically-red — the “Github CI - Branch” workflow (.github/workflows/audit.yaml:33 in vuer_oss) is chronically RED on customization branches and is NOT a regression: improved-yarn-audit --min-severity critical --exclude <GHSAs> exits 4 on any critical advisory in a transitive dep not on the --exclude allowlist (as of 2026-06-04: twig>locutus, @techteamer/timestamp>…>basic-ftp, @kafkajs/confluent-schema-registry>protobufjs, request>form-data). Base branch customization/raiffeisen has failed it on every push since ≥April 2026; team merges through it and clears it by appending triaged GHSAs to --exclude (security acceptance) or remediating. Triage rule: commit didn’t touch package.json/yarn.lock + base already red ⇒ not your change. The real per-change gates are lint (yarn lint, --max-warnings 0, ignores customization/test/*) and unit tests (yarn jest <file>)

  • customization-branch-ci-pipeline-inheritancesystemic, will recur: legacy partner branches ran a single CI job (lint-and-build, old .github/workflows/pull-request.yaml blob ec0a1244); devel’s current workflow (blob dda79403) runs lint / test / audit / depcheck / sonar / build. So the first devel→customization merge makes four jobs run on that branch for the very first time, surfacing years of latent breakage in one PR. On cofidis (FKITDEV-9059): 5 failure clusters, only 2 merge-introduced, the rest dating 2017–2024. Two structural amplifiers: partners fork core source files in place (no override layer, e.g. server/service/FlowLiveUpdateService.js → forked file violates the core unit test), and there is no customization-aware unit-test layer (jest.config-unit.js matches only test/tests/unit/**, no per-partner test dir) so fixing a partner-specific test failure means diverging a shared core test file that then conflicts on every subsequent devel merge — a recurring tax, not a one-off

  • customization-branch-ci-pipeline-inheritancethird confirmed instance, and the one that breaks the “it’s always a tax” framing: on CIB (FKITDEV-9197) the inherited pipeline was a SECURITY WIN. Fired on all three repos — vuer_oss/vuer_css carried the legacy lint-and-build blob ec0a1244, byte-identical to the pre-merge Cofidis branch (⇒ the legacy workflow is one shared frozen artefact, so “is this branch still legacy?” is answerable with a single git rev-parse <branch>:.github/workflows/pull-request.yaml), and portal_css had no pull-request.yaml at all — zero CI on the branch until the merge. Measured against a base worktree at bf6dfbf8: eslint exit 1 (15 problems) → exit 0, audit 25 CRITICAL → 0. CIB’s portal had been shipping 25 critical advisories unmeasured (tar via semantic-release>npm, handlebars, twig>locutus ×2, browserify>shell-quote) precisely because the branch carried no audit gate — the same merge that switched the gate on also satisfied it. Either direction, the honest PR statement needs a measured base worktree: “these N gates are new on this branch; here is what they said before the merge and after” — do not report a newly-green gate as your achievement or a newly-red one as your damage. Running tally: Cofidis fired (5 clusters), Generali did not (already on 6 jobs), CIB fired ×3

  • cve-2025-7783-form-data-via-requestAudit-job debugging trap: ERROR: Unable to parse yarn audit output: SyntaxError … and Node 24’s DEP0169 url.parse() DeprecationWarning are cosmetic red herringsimproved-yarn-audit merges child stdout+stderr into one NDJSON stream so the deprecation warning corrupts lines, and the tool silently skips unparseable ones. Identical errors appear on green devel runs (proof: vuer_oss PR #8062, job 87891787317, Found 0 vulnerabilities, conclusion success). The exit code comes solely from the genuine advisory count — don’t chase the parse error, find the real advisory

  • nusz-devel-update-2026-06-16-lint-merge-fix — concrete case of the lint gate catching a bad merge: yarn lint (eslint . --max-warnings 0 --ignore-pattern "test/*") failed the NÚSZ devel-update merge with 1 n/no-missing-require error (cron.js:46, extensionless require of a module devel had renamed .js.ts); merge correctly held back uncommitted until fixed

  • FKITDEV-7973-sequelize-pool-fixblind spot in that same script: yarn lint passes --ignore-pattern "test/*", so test files are NOT linted by yarn lint or by the CI lint gate. Any change under test/ (e.g. the pool-config mirror test/lib/utils/db.utils.js) sails through untested for style/errors. The ESLint flat config does define a test/** block, so explicit invocation works — lint touched test files by hand with ./node_modules/.bin/eslint <paths> --max-warnings 0

  • FKITDEV-8981move PR checks off ubuntu-latest → self-hosted [self-hosted, node] across 7 repos’ single PR-check workflow .github/workflows/pull-request.yaml: 33 identical runs-on edits (vuer_oss/vuer_css/esign_oss/portal_css/mq 5 each, esign_css/janus-api 4 — they omit the test job; jobs ∈ {lint,test,audit,sonar,build}). Repo resolution: @techteamer/mqTechTeamer/mq default master; janus_apiTechTeamer/janus-api (hyphen) master (TechTeamer/janus_api does not exist); css/oss base origin/devel, mq+janus-api origin/master. Scope: portal_css pr-title-lint.yaml already removed on devel (PR FKITDEV-8976) and push-triggered release-caller.yaml (reusable node-semantic-release.yaml@master, no runs-on) is out of scope ⇒ only pull-request.yaml — re-scope against post-fetch devel. Job NAMES unchanged ⇒ branch-protection required-status-checks stay valid. Worktrees <repo>-FKITDEV-8981 on chore/FKITDEV-8981-self-hosted-runners; verified (numstat 5/5/5/4/5/5/4, zero ubuntu-latest residue, YAML parses) but NOT committed/pushed, NO PRs. Hard dependency/risk: inert + dangerous without online node-labelled (+ implicit self-hosted) runners carrying git + Node/yarn (setup-node@v6 cache: yarn) + SonarSource/sonarqube-scan-action@v6 (sonar = likeliest self-hosted gotcha); if none online at merge, every PR check queues forever and ALL PRs in these repos block. “Build-green ≠ runs” — true validation needs a live PR hitting a node runner. Precedent: vuer-release autobuild.yml already on [self-hosted, docker]. Commit-msg style chore: [fkitdev-8981] run PR checks on self-hosted runners

  • FKITDEV-8533SonarCloud “Maintainability Rating on New Code” gate FAILED on vuer_oss PR #8013 (the Janus CVO un-gate fix, branch fix/FKITDEV-8533-videoorient-ungate; rated C, then D after a refactor) — and it is pre-existing-debt mis-attribution, NOT the fix (2026-06-29). All 20 flagged issues are pre-existing (git blame 2018→Jan 2026; authors Jordán/Bence/jurki/kzsolt/Makkai; SonarCloud issue keys e.g. AZ8A1W4d…/AZ8A1W6o… identical before & after a PR refactor); decisive control = the co-modified server/db/model/customer.js carries zero flags, proving the diff is innocent — the gate counts legacy smells in any touched file, most likely because devel has no SonarCloud baseline analysis (project vuer-oss is private; New Code config unconfirmable without a token). Gotcha: any PR touching videochat.js/RoomTransportSession.js/SelfServiceTransportSession.js/VuerCVListenerSession.js (legacy optional-chain/.find/async smells) re-trips this → waive (mark issues Accept / admin-merge) or fix the baseline (Project Settings → New Code → Reference branch = devel, ensure devel is analyzed); do NOT bloat a targeted PR by “fixing” the unrelated debt — esp. the 2 [failure]-severity VuerCVListenerSession.js items (async-in-constructor + await-non-Promise, behavioural CV refactors). Decision (user, 2026-06-29): waive as pre-existing debt. Fix final state: helper server/transport/videoOrientExt.js refactored into Customer.prototype.videoOrientExtEnabled() called X.customer?.videoOrientExtEnabled() ?? true at the 4 sites; commit 1815f693fe (amended over d27d4cc990), solo-author, force-with-lease pushed; device test still pending

  • mjml-v5-esm-breaks-commonjs-email-templates“green CI, broken runtime”: unit tests never boot EmailService, so the MJML-v5 ESM regression on FKITDEV-9059 cofidis (ReferenceError: require is not defined at EmailService.init; the letter type never registers so its email never sends) passed all CI and only failed when the service actually started on fk-dev. Same class as FKITDEV-8981’s “build-green ≠ runs” and the FKITDEV-9059 innerHTML / web/-404 bugs; detection is runtime-only (deploy + restart, watch letter-registration errors: 3/boot → 0 after the fix)

  • FKITDEV-9194the pipeline-inheritance trap is not universal — check, don’t assume: both Generali branches already carried devel’s 6-job pipeline, so the Cofidis-style expansion was a no-op (css came out fully green). Also check scheduled workflows separately: vuer_oss gained long-lived-branches.yaml, but its branch matrix lists only devel/mbh/raiffeisen/kh/unicredit ⇒ the file being present on a branch does not mean the branch is in the matrix. And build has needs: [lint, test, audit, sonar]a red audit also blocks build (Generali oss: pre-existing critical sequelize GHSA-v8fg-2rw7-q452 via @techteamer/sequelize 6.32.2, FKITDEV-8279)

  • eslint9-flat-config-dead-disable-directivesa rule going from “on” to “off” upstream can turn a partner branch red, which is the opposite of the intuition. devel’s eslint-9 flat-config migration turned off no-empty/no-unused-vars/no-redeclare/no-useless-assignment, so every partner // eslint-disable-next-line <that rule> now reports “Unused eslint-disable directive” — a warning — and yarn lint --max-warnings 0 promotes it to a failure. CIB (FKITDEV-9197, portal_css): 9 dead directives across 6 customization/ files. eslint --fix does NOT remove them — it blanks each to a whitespace-only line, so trusting --fix leaves stray blank lines and unexplained whitespace churn in the diff. Sibling finding in the same config: 'jest-formatting/padding-around-all': 'warn' survives although 65ac214c feat: eslint 9 FKITDEV-6045 removed the plugin — inert only because that block is files: ['test/*','test/**/*'] and lint runs --ignore-pattern "test/*"; the portal_css twin of the vuer_css “test files cannot be linted” residue, and it detonates if test linting is ever enabled

  • portal-css-jest-runs-zero-testsa CI “test” job that is green because it executed nothing: portal_css’s yarn jest finds zero tests on pristine devel and would still find zero if you wrote some — empty test/tests/ plus a custom sequencer (test/lib/jest/test.sequencer.js, wired via testSequencer) whose CORE_TEST_ORDER is an empty array used as an allow-list in prepareTests (order.includes(relativePath)), and jest runs sequencer.sort() before the “no tests found” check. The jest script omits --passWithNoTests (bare runs are hard-red); the workflow appends it, which is the entire reason CI is green. ⇒ on this repo lint + build are the only gates carrying signal — never cite a portal_css test pass as validation evidence

  • customization-branch-ci-pipeline-inheritance“SKIPPED” IS NOT “PASSED”: a red gate hides every job downstream of it. On CIB’s first vuer_css run (FKITDEV-9197 #3152) Build and SonarQube were skipped, not failed, because both needs: the red test job — reading that as “4 green, 1 red” overstates what is known by two entire jobs. Same shape permanently on vuer_oss, where build needs: audit and audit is always red on the FKITDEV-8279 fork (npm advisory 1114318 “Sequelize: SQL Injection (Oracle DB)”) ⇒ Build never actually runs on any vuer_oss partner PR. Re-read the needs: graph before reporting a run, and re-run after every fix rather than assuming untouched jobs are fine. Also separate code-fix failures from human-override ones: on vuer_oss the audit and the SonarCloud quality gate (an 11-month devel merge graded as “New Code” — 334 core commits arriving as if freshly authored) are both overrides, and note the split that confuses everyone — the workflow’s own SonarQube job passed while the SonarCloud status check is red. Never “fix” either on a partner branch; that is how branches acquire divergence they pay for on every future merge. Final CIB tally: portal_css 7/7 green first run, vuer_css 6/6 after 2 fixes, vuer_oss green-but-blocked — 3 fixes total, an order of magnitude under Cofidis’s 5 clusters, because CIB had zero partner commits since its last release. The expansion reveals AGE, not activity: the headline failure was a test red since 2024-12-03 on a branch with no commits at all

  • vuer-oss-unit-tests-green-is-weak-evidencethe test job on vuer_oss is a weaker gate than its green implies, devel-wide: translations.test.js registers zero tests (misused it.each — the returned function is discarded), its scan() is actively flaky in the nightly long-lived-branches workflow (Directory not found: client/features on ~1 run in 3, same commit, both runners), and pdf.test.js’s byte-for-byte PDF comparison drifts by 3 bytes. ⇒ one red run is not evidence of a regression, one green run is not evidence of its absence. Full detail under Dev / testing workflow

  • FKITDEV-9197the product image sets NODE_ENV=dev while jest defaults to NODE_ENV=test, so in-image validation reads GREENER than CI. Phase 2.5 ran all three CIB repos inside harbor.techteamer.com/facekom-devel/{vuer_oss,vuer_css,portal_css}:2026.1.UBI9.1-20220315 (Node v24.12.0, throwaway containers on fk-dev, source via git archive + mount, running NÚSZ stack untouched) — install/lint/build green in all three, yet portal-client.test.js passed in-image and failed in CI purely because of the env difference. ⇒ in-image is a strong check on native build/lint and platform gaps, a weak one on config loading. More generally: three environments gave three different results for the same tree (macOS local / in-image / CI), structurally not flakily — macOS has no /etc/hostname, the image pins dev, CI runs test. Treat “passes locally” and “passes in-image” as weak evidence about CI

Container health / images (dev-box)

  • fk-dev-partner-branch-deploy-runbook — the third instance of the same uid-1000-vs-root-path failure class on this stack (after the esign nginx-PID bug and the janus record-dir bug): host ops=uid 1001 but the app runs as techteamer=uid 1000, so a blanket chown -R ops:ops /workspace/vuer_oss makes log4js/streamroller throw EACCES: permission denied, open 'logs/server.log' and supervisord restart-loops the program. Triage signal: supervisorctl status shows uptime 0:00:00 with a climbing PID. Fix: chown -R 1000:1000 <repo>/logs after any tree-wide chown. Explicitly corrects a circulating claim that ownership “moved to 1001 and the uid-1000 guidance is stale” — WRONG, and it cost a restart loop on 2026-08-18. The two uids coexist: tree 1001:1002 (host ops) AND process/logs/ 1000 (techteamer = host ubuntu). Provenance traced: not doc rot but an agent artefact — a historian subagent observed the tree live as 1001:1002 (correct) and over-generalised it into “this contradicts FKITDEV-9059, ownership has since changed”, which then travelled across an agent hand-off. FKITDEV-9059.md:78 was right the whole time and is vindicated, as is release-pipeline-automation-spec. The reusable lesson is the mechanism: an agent promoting a partial live observation into “the docs are stale” — treat any such hand-off as suspect until it accounts for BOTH uids
  • fk-dev-deploy-smoke-runbookthe janus container was supervisord-FATAL since fk-dev’s first boot (2026-06-30) and had never had a confirmed green videochat; root cause = janus_websockets can’t create the wss vhost (in-image libwebsockets built without working TLS). Fixed 2026-08-13 by switching the oss→Janus hop to plain ws (browser never touches Janus — see WebRTC topic). Gotcha: janus.transport.websockets.jcfg is a bind mount → edit in-place inside the container or the inode-swap is ignored
  • dev-box-esign-container-startup-failures-2026-06-01esign_css/esign_oss came up unhealthy because nginx (non-root techteamer uid 1000) couldn’t write its PID: baked nginx.conf line 6 pid /run/nginx.pid; but /run is root:root[emerg] open("/run/nginx.pid") failed (13: Permission denied) → nginx FATAL → unhealthy (app/redis/cron all RUNNING; image regression, not the InstaCash update); images esign_{css,oss}:2024.4.1-20240614 were rebuilt ~2025-12-08 w/ nginx 1.28 (tag date misleading); ephemeral fix sed PID → /tmp/nginx.pid + supervisorctl restart nginx (lost on recreate, /etc/nginx not bind-mounted) → durable fix = bake the PID path into the images. Healthcheck /usr/local/bin/supervisor-health-check.sh: unhealthy if any supervisord prog ≠ RUNNING or uptime 0:00:[0-5][0-9] (< 60s anti-flap) → ANY supervisorctl restart = ~60s unhealthy then auto-recovers (interval 60s, retries 3, start-period 60s)

Container migration / UBI10

  • FKITDEV-8252 — fleet-wide UBI8/UBI9 → UBI10 migration ahead of RHEL 9 EOL; Phase A.6.1 DONE (all 5 base images probe-build green; 2 new commits f38c8e2 + b984006; branch 17 ahead of origin/main, not pushed); Phase A.6.2 (remaining 3 base/* likely under common/*), Phase B (vuer-release 62 Dockerfiles), Phase C (vuer_docker PR #203) still open; vuer_cv now in-scope (5.93 GB UBI10 base added)
  • FKITDEV-8252RUNTIME fixes (build-green ≠ runs): four ubi10-minimal startup gotchas masking each other (supervisor 4.2.5 pkg_resources on Py3.12 → pin 4.3.0; supervisord logfile hidden by /var/log bind-mount → log to root; rabbitmq needs /bin/sumicrodnf install util-linux; erlang .erlang.cookie eacces — supervisord drops HOME → environment=HOME="/var/lib/rabbitmq"); plus removed wrong USER $DOCKER_USER from vuer_css/portal_css (must run supervisord as ROOT); all 3 rabbitmq images boot healthy; pushed solo-author across vuer_docker/vuer_build/vuer-release

Cron jobs / data retention

  • SLARAFIPI-84the “nothing prunes Activity/FlowActivity” finding is a NOT-FINDING and must be quoted as one. Activity.destroy, cascade deletes and crypto-shredding were all searched and all empty — that is no deletion path found, not no deletion happens, and it is the same fallacy that produced the wrong “the rejected value is not in the DB” answer (we verified where the table is written instead of where the value goes). Compounding it: config.js:103 springCloudConfigServer can override any key at boot, so features.archive = false at the tag proves nothing about their runtime. Retention promises to Raiffeisen are therefore scoped, not absolute.
  • fk-dev-nusz-deploy-and-8959-verificationFKITDEV-8959 TC-8959-02 verified PASS on fk-dev (2026-07-03): the NÚSZ image-deletion cron RemoveAttachmentDataCronJob (thin wrapper over CustomRemoveOldDataCronService.removeAttachmentData()) → getOldImageAttachments (type LIKE 'image/%' AND isArchived=false AND createdAt<cutoff, batched, excludes the file blob) → removeOldAttachments key-guard (const encryption = encryptionId ? getEncryption() : null; if (encryption?.key) encryptBuffer(empty,{key}) else { file=Buffer.from(''); encryptionId=null; skippedNoKey++ }; isArchived=true; save). Proven the key-offline path: BEFORE {isArchived:false,encryptionId:3,file_bytes:9}{processed:1,archived:1,skippedNoKey:1,errored:0} → AFTER {isArchived:true,encryptionId:null,file_bytes:0} (blanked + archived instead of the old The key options property is required… throw). Retention still OPEN: fk-dev expiryDays=7 (dev.json) / 28 (docker.json) vs ASSNUSZ-117’s 7 — confirm with NÚSZ (one-line config)
  • FKITDEV-8959 — RCA of why NÚSZ image deletion never ran: removeAttachmentData() was gated behind a hanging removeVideoData() step in a strictly-sequential cron (ran only ~17/49 nights), and the old getWhere selected a 1-day band (createdAt ∈ [now-8d, now-7d)) with no catch-up so a skipped night was permanent. Fix = own RemoveAttachmentDataCronJob (decoupled) + self-healing createdAt < cutoff image-scoped query + the key-guard above; verified on fk-dev per the note above
  • nusz-1.9.11.48-test-runbook — test plan for the 1.9.11.48 payload’s remove-old-data cron (ASSNUSZ-76 / FKITDEV-9150): productized bin/remove-old-video-files.js -f/-t must run to completion without wedging on ffmpeg’s Overwrite? [y/N] (root cause = missing -y) and strip the video track → audio-only webm; removeOldRoomData config (videos 7d / audios 730d / attachments 7d, batched) + the 7d-vs-28d retention question still open; disk reclaim / backlog scale flagged prod-only
  • janus-memory-leak-rcathe AutoCloseRoomsCronJob question, answered 2026-09-07. Full chain verified in source: customization/cron/AutoCloseRoomsCronJob.js (*/5 * * * *) → queueClient.roomCron.autoClose (cron.js:139 publisher) → queue-room-cronqueue_server/RoomCron (server.js:509 consumer) → RoomService.autoClose (RoomService.js:447) → videochat.close()roomTransport.destroy()closeJanus()janus.destroy(). The chain WORKS and IS a genuine mitigation for the abandoned-call janus leak, because transports leave TransportPool.sessions only via destroySession()/rpcDestroy() — nothing removes them on socket death — so the stranded transport is still in the pool for the cron to find. Three constraints: (a) double-gated on roomAutoCloseHours, which has NO DEFAULT (docs: “Nincs alapértelmezett érték”) ⇒ unset = no-op, so partners without it have nothing; (b) it structurally cannot clean the VuerCVListenerSession path — SelfServiceTransportSession.terminate() has zero references to vuerCVListenerSession; (c) it is createdAt-based, not last-activity ⇒ it cannot be tuned aggressively without killing live long calls. Separately: CloseExpiredSelfServiceRoomsCronJob is self-service only and does NOT cover operator videochat rooms
  • SLARAFIPI-84nothing prunes Activity or FlowActivity in vuer_oss (verified at tag raiffeisen-1.9.11.100): no Activity.destroy anywhere in server/, customization/ or bin/; features.archive is false in docker.json so ArchiveCronJob is never registered; and even enabled it only moves attachment bytes (AttachmentArchiveService nulls file, sets isArchived) and never touches Task.data, Activity or FlowActivity. ⇒ a retroactive recovery of rejected face-comparison scores back to June is viable. Two reading traps: the isArchived field in a room export is not a column — it is computed at serialization in server/web/helper/TechnicalLog.js, and null merely means the activity has no attachmentId; isDataAccessible: true means the customer’s encryption key still exists (not crypto-shredded). Caveat: verified in the released config only — Spring Cloud Config Server can override any key at boot, so confirm against the live runtime
  • SLARAFIPI-84CORRECTS the retention picture for the Raiffeisen recovery, and retires the crypto-shred worry entirely (2026-09-08). The three sources a rejected face-comparison score can be recovered from — selfService:attachment, selfService:cvTask:log, selfService:v2:config:state — are NOT members of ENCRYPTED_ACTIVITIES (server/db/model/activity.js:152-163), so activity.content returns plaintext JSON and deleting a customer key cannot make them unreadable; the earlier “room/customer deletion crypto-shreds the key” clause is REFUTED for these types. “Auto delete customers” selects only customers with no room and no self-service room (CustomCustomerDeleteService.js:25-31) and deletes no rows at all (CustomerDeleteService.js:27-72) — their own log says The auto deletion of 0 customers has been completed. Archive / flow-clear / delete-rooms / room-bulk-delete are not registered in the partner’s UAT vuer_cron.log (six jobs run, none of them these) ⇒ this is now a runtime observation from their log, not the weaker repo not-finding. The only content-destroying paths are manual: bin/db/truncate, bin/db/migrate-data, the admin flow-delete route (not registered; clearFlow throws) and socket flow:reset, gated on a per-proto resetable FlowScope row that is false by default. Still scope the promise: the Cron manager UI can start a job live, and features.flowClear / features.archive / deleteRoomCronJob.active are all overridable from config/local.json

Crypto policy / GPG SHA1

  • FKITDEV-8252decision revised: original per-key rpmkeys --import --allow-sha1-signatures plan did not survive UBI10 reality (flag disappears after microdnf -y update strips it from rpm-libs; DEFAULT:SHA1 sub-policy doesn’t exist — no SHA1.pmod ships); now using update-crypto-policies --set LEGACY in all 7 SHA1-key-importing build stages across portal_css, vuer_css, vuer_oss, janus (×2), vuer_cv; order matters: install crypto-policies-scripts from UBI10 BaseOS BEFORE COPY-ing the CentOS Stream 10 repo

CSP / log noise

  • ASSICASH-71 — InstaCash CSS log noise: WebServer.js setupCSPReportViolation() writes every report unthrottled; amplifies any hosts.portal / portal.url config drift

Customization branches

  • FKITDEV-9252CORRECTION 2026-09-07 to the “no customization-aware unit-test layer” claim: vuer_oss devel commit b704916a01 (fkqa-356) adds an explicit testMatch for customization/test/tests/unit/**, so current devel does run partner unit tests. Measurable tell: devel 426 suites vs 347 on a branch cut before it. The long-unsourced “79 never-run customization tests” figure is now sourcedfind customization -path "*/test/tests/unit/*" -name "*.test.js"79. ⇒ the claim survives only for branches predating b704916a01, and the merge that pulls it in will start executing those 79 files for the first time; customization-branch-ci-pipeline-inheritance §2 carries the callout

  • SLARAFIPI-84the base file can be self-consistent and still be the wrong answer. Our “the rejection boundary is probable (0.6)” claim came from reading server/service/SelfServiceCheckerService.js:36-38 alone, where it is TRUE; customization/listeners/self-service-v2.js:114-126 overwrites a different rung (perfect := raiffeisen.customerPortrait.threshold = 0.55) and never mentions probable at all, so searching for “the boundary” in the base finds a coherent ladder and stops. Rule: after reading a base file, grep customization/ for the same setting name before treating the base as authoritative.

  • ASSICASH-71customization/instacash (Express 4, HEAD b0a4a37a, deployed) vs devel (Express 5, PR 689 fixes); next core sync needs to carry route-array fix

  • FKITDEV-8787customization/raiffeisen overrides on SelfServiceRoomService.js and SelfServiceV2Service.js; PRDEBUG instrumentation gated by raiffeisen.debug.phantomRoomLog

  • FKITDEV-8533customization/generali-atvilagitas is the base branch for the Generali videoOrientExt tablet fix (PR #7893)

  • FKITDEV-8788customization/raiffeisen ocr.engine selection (warp-first VuerCVOCRRecognition vs no-warp VuerCVMRZDetector) + recognition recipe customization/cv/instruction.index are the suspected override surface for the HU-eID-back MRZ crop bug

  • instacash-update-2026-05-27-status — Periodic devel→instacash sync: branch name update/customization/instacash-2026-05-27 across all three repos (esign_css/vuer_oss/vuer_css); date-only convention; per-repo conflict topology captured

  • esign-css-instacash-orphan-history — esign_css customization/instacash is a single squashed orphan commit with no merge base; structurally distinct from conventional long-running customization branches

  • ci-github-branch-audit-chronically-red — every customization branch inherits a chronically RED “Github CI - Branch” audit gate (improved-yarn-audit exit 4 on un-excluded critical advisories); customization/raiffeisen has been red on it since ≥April 2026 — not a per-branch regression

  • customization-branch-ci-pipeline-inheritance — partner branches fork core source files in place (no override layer) and the repos have no customization-aware unit-test layer, so a partner-specific test failure can only be fixed by diverging a shared core test file — which then conflicts on every subsequent devel merge. Both amplifiers surface at once on a branch’s first devel-merge, when devel’s 6-job pipeline replaces the legacy single lint-and-build job

  • cve-2025-7783-form-data-via-requestCVE-2025-7783 / GHSA-fjxv-7rqg-78g4, critical form-data@2.3.3 (unsafe random multipart boundary, patched >=2.5.4) via EOL request@2.88.2 which hard-pins form-data: ~2.3.2. Absent from devel (its Audit job is green) — present only on the customization line, because request is live partner code: vuer_oss customization/api/sms/SmsCofidis.js, vuer_css customization/server/web/api/{login,register,partner-register}.endpoint.js. Confirmed red on customization/cofidis (#3100/#8040), customization/kh (#3098), customization/raiffeisen (#8055) ⇒ blocks every partner branch adopting the new pipeline. Fix matching house style (repos already pin csurf/cookie, twig/minimatch, ts-jest/handlebars): add "request/form-data": "^2.5.6" to resolutions in package.json + yarn install. Long-term correct fix = drop EOL request (4–5 call sites) = separate ticket

  • FKITDEV-8947customization/unicredit: migrate UniCreditApiService off request-promise-native → fetch; the service does mTLS (cert/key/ca/passphrase from portal.api), so the migration must use undici.fetch+Agent (vuer-oss-global-fetch-ignores-agent-mtls) — Node’s global fetch ignores agent:. Working tree also has a stray }w at ApiService.js:267

  • mjml-v5-esm-breaks-commonjs-email-templates — cofidis forks its own email/letter templates (customization/email/*/*.letter.data.js); the FKITDEV-8727 MJML v4→v5 (ESM-only) upgrade merged via FKITDEV-9059 broke the ones that mix a top-level import with require() (require is not defined at EmailService.init). Per-partner blast radius — every partner forks its own letter files, so the same upgrade can break each on its next devel-merge. Fix 52a0843a1e (require→import in all 6, only e-mail-invite had actually crashed) is unpushed on chore/FKITDEV-9059-cofidis-update-2026-07-13-fixes

  • FKITDEV-9194Generali branch topology: customization lives in vuer_oss + vuer_css only (zero generali refs in portal_css/esign_oss/esign_css); the live line is customization/generali-atvilagitas (tag generali-atvilagitas-1.9.11.18) and customization/generali-kar is DEAD — last commit 2022-01-24, 1754 commits behind devel, never merge devel into it

  • customization-branches — dead-lines table + Generali repo-footprint callout added 2026-08-08

  • FKITDEV-9197CIB branch topology: customization/cib lives in three repos — vuer_oss, vuer_css and portal_css — on two different version trains (cib-1.9.11.NN for the vuer pair, cib-1.4.0.NN for the portal). As of 2026-08-11 nothing had been committed directly to any of the three since the last release: each branch tip was the release commit (cib-1.9.11.101 2026-05-29, cib-1.4.0.74 2026-04-21), which is what makes 1.9.11.102 a pure core-update release with an empty Phase-3 bucket 1. CIB is not an Oracle partner (no db.options override, no oracledb ⇒ Postgres), so FKITDEV-8279’s Oracle-dialect swap risk does not reach it

Customer data encryption

  • sms-verification-code-dev-testingcustomer.data is an encrypted-at-rest TEXT column (serviceContainer.service.cryptos.data = DataCryptoService, keyed per-row by customer.key), but the Sequelize model’s data get accessor auto-decrypts on read (customer.js:23-51, _getDecrypted :316-318, isEncrypted() = !!this.key) — so reading any portal-data field via the model (e.g. getVerificationCode()videochatToken) returns plaintext, no manual crypto; by contrast smslogs.messageBody/phoneNo are separately encrypted and NOT plaintext-readable
  • fk-dev-nusz-deploy-and-8959-verificationthe encryption.key resolution chain (needed to exercise crypto-dependent code standalone): encryption.key is a Sequelize getter (server/db/model/encryption.js) → serviceContainer.service.cryptos.data.getActualKey(key, customerId) (DataCryptoService) → for a null/absent key it calls serviceContainer.service.customerKeyStorage.getKey(customerId). A standalone harness must therefore init cryptos.{media,data,attachment} + CryptoService + customerKeyStorage; stubbing customerKeyStorage.getKey() => null faithfully models “customer key offline / inaccessible” — the real trigger for the FKITDEV-8959 image-deletion bug. Proven by TC-8959-02: a null-key image is blanked + archived + encryptionId=null (not thrown)

Database / Sequelize connection pool

  • FKITDEV-server-session-too-lowRCA: server/db/sequelize.js only set pool.max: 100 with no lifecycle settings (idle/acquire/evict); × 7 supervisor processes = up to 700 connections against a PG default max_connections of 100–200 ⇒ SequelizeConnectionError: remaining connection slots are reserved for roles with the SUPERUSER attribute, first surfacing in the “Audit log forward” cron (cron is the first to starve). Assignee guidance (Bence László): don’t just bump max — turn on options.logging + options.benchmark and measure (idle too long? acquire too short?) before writing new numbers. Triage SQL: SHOW max_connections; + SELECT usename,state,count(*) FROM pg_stat_activity GROUP BY 1,2;. Escalation if pool tuning is insufficient: look for multiple new Sequelize instances, then PgBouncer
  • FKITDEV-7973-sequelize-pool-fix — the fix (max:100→10, +min/idle/acquire lifecycle settings, plus a deep-merge of db.options.pool so individual keys are overridable instead of the old shallow Object.assign) is implemented but NOT mergedvuer_oss PR #7852 is mergeable_state: blocked on two standing CHANGES_REQUESTED reviews. Review consensus: max: 10 is settled (Pocok256 initially wanted it raised — “anno a 100 is kevés volt” — but conceded after bencevarga666 argued the 7 processes must stay below the DB max_connections and cited the PG wiki preference for few connections; CLI/tooling opening their own connections means 7 already understates the count). Both open code asks ACTED ON 2026-07-27 in commit c89124e29c (PR head moved 7da69cba85c89124e2, solo-author): min: 2→**min: 0** and the evict: 10000 override deleted so Sequelize’s default 1s evict sweep applies — one change, since min: 0 is only safe because the sweep is fast; max/idle/acquire untouched. Two-file rule: server/db/sequelize.js has a mirror in test/lib/utils/db.utils.js — the test harness duplicates the pool config, so both must always move together. Still blocked: both CHANGES_REQUESTED reviews stand and no re-review was requested / no PR comment posted, so nothing prompts a second look; PR body still says TÖLTSD KI; branch ~58 commits behind devel. Floated-but-undecided: per-process pool configs. Blocking gotcha for the “measure first” step: logging is hardcoded after the config spread in server/db/sequelize.js, so db.options.logging is non-overridable — you cannot turn on query logging/benchmark from config

Dependency forks / vendored patches

  • FKITDEV-8279retiring the @techteamer/sequelize fork (upstream 6.32.1 + 3 patches, published 2023-10-13, v6 branch frozen since, ~2.7 yrs abandoned) for upstream sequelize@6.37.8 + patch-package, on branch fix/FKITDEV-8279-sequelize-upstream-6-37-8PUSHED 2026-08-31 @ 7664b784d0 (11 ahead / 16 behind devel), still no PR. The three patches are all load-bearing and were carried verbatim (fidelity mandate, byte-verified against 6.32.2 from the Yarn cache): (1) Oracle reserved words KEY/PASSWORD/TYPE/VALUE (112→116) — without it every Oracle partner running quoteIdentifiers: false breaks on the first query across 30 columns in 27 core models; (2) MSSQL null-BLOB attrTypes threading (otp, unicredit-srb are live on MSSQL, so it is NOT dead weight); (3) Model.sync() index comparison by fields not name, which runs outside the options.alter branch on every sync() and the Oracle partners boot syncOnStart: true. Four defences, four distinct failure modes: patch-package --error-on-fail (patch stops applying → hard install error), 30 unit tests (hunk loses its behaviour; 9 go red on bare upstream; the 7 originally-dark hunks were each proven load-bearing by reversing them one at a time), assertVendorPatches() at server/db/sequelize.js:35 (installed with --ignore-scripts or patches/ missing from the image → boot refuses), and CI’s yarn --frozen-lockfile making the unit suite itself a CI-level assertion that the patch applied. Gotchas that generalise to any patch-package use: it must be a runtime dependency where production installs run yarn install --production; a missing patches/ dir is a silent no-op even under --error-on-fail; yarn install --check-files is required once the patched package goes missing from node_modules; a version-stamped patch filename under a caret range only warns if the resolve moves. The guard is dialect-scoped (oracle+mssql) so it covers 2 of 3 patches. Unguarded attrTypes dereferences were carried over deliberately — fork-identical and in production at all five partners, so unreachable today, but the hazard is now ours
  • FKITDEV-82792026-08-31 evidence upgrade, and three corrections. Validated on Oracle 19c EE 19.3.0.0.0 (the engine partners actually run, not just 23ai), plus PG 16.14 / MySQL 8.4.11 / SQL Server 2019+2022 with the partner-pinned drivers read off the partner branches (oracledb 5.5.0 thick + Instant Client 23.26.2.0.0 for bb/mkb-instant, 6.10.0 for kh, tedious 14.2.0 for otp/unicredit-srb). 19c replay = 25+128 migrations, 0 errored, 0 skipped, 62 tables / 204 indexes, index inventory row-identical to 23ai. ORA-00904 reproduces in both directions on 19c ⇒ patch 1 is load-bearing on the real engine. Patch 3 is inert over a repo-built schema but NOT cosmetic — reversing the hunk over ONE renamed index makes Model.sync() abort with ORA-01408, a failed startup for syncOnStart: true partners; “not exercised” ≠ “not needed”. Oracle’s 30-char identifier limit does NOT apply on 19c (19 index names >30, max 58, zero ORA-00972) — stop using it as an argument. Engine inversion: 19c emits a bare ORA-00942 with no object name, 23ai interpolates it, so devel’s original whitelist regex already matches on 19c ⇒ commit 7664b784d0 is 21c+ forward-compatibility, not a live-defect repair. New findings: (A) the vendored patch has a latent MSSQL TypeError on raw sequelize.query(sql, {bind:[...]}) (proven by controlled inverse; zero of 21 raw call sites use bind:) — prevented at source by a no-restricted-syntax ESLint ban (47142d9156) rather than by patching the hunk; (B) oracledb@5.5.0 is broken on Node 24 (util.isDate removed in Node 23) independently of sequelize, and bb/mkb-instant pin ^5.5.0 while engines.node: ">=22.18.0" admits Node 24 (fix: 6.10.0 thick, 4/4 green) — separate ticket; (C) bin/db/migrate-rdbms.js cannot migrate an Oracle partner--url= makes sequelize-cli skip the config file and drop quoteIdentifiers:false, and sync() at :89-90 repeats it, so fixing the four --url= sites is not enough; the naive “merge db.options” fix is a trap (folds createdAtcreatedat on Postgres, tested) — pre-existing, separate ticket; (D) 170 DDL failures silently swallowed by the migration tooling (31/0/139 across 104 of 128 migrations), identical on both engines ⇒ a tooling property, own ticket. Still gating: no partner USER_INDEXES dump, patch 3 unproven outside a synthetic case, 109 of 128 migrations were no-ops in the replay ordering, partner data + customization migrations untested. fk-dev IS reachable — as ops@fk-dev.taild4189d.ts.net; the old “tailnet policy refuses SSH” note is retracted. Szabó Márton ran the earlier customization/bb test and is the likeliest route to a bb index dump — the question about his run’s effective config was never asked; FKITDEV-1208 names mkb-instant, not bb, as the org’s validation target
  • FKITDEV-9022 — the adjacent policy question for TechTeamer’s ~20 forks/libs: publishing them to the private npm.facekom.net registry; FKITDEV-8662 “Forkolt dependency-k karbantartása” is the inventory ticket both feed

Dev / testing workflow

  • FKITDEV-9252npx jest -c jest.config-unit.js is NOT vuer_oss’s test runner and invents three failures that do not exist: it omits --experimental-vm-modules, so the ESM-only stack-trace package cannot load and jest reports bogus SyntaxError: Unexpected token 'export' from logger/helpers, logger/syslog-client, logger/papertrail. Always validate with yarn test:unit; distrust any review figure that did not come from it. The rest of the round is a template for “review someone else’s fix”: Node v24.18.0 (= CI’s pinned NODE_VERSION: 24), two worktrees each with its OWN yarn install --frozen-lockfile (a stashed baseline has produced a wrong count here before — FKITDEV-8279), then compare the failing SET, not the countweb-server-auth.test.js 37/37 vs baseline 21/21 (+16), full suite 3 suites / 4 tests failing on both sides ⇒ no regressions, yarn lint 0 on both. Standing survivors: converter (ffmpeg mergeFiles), vuer-cv-service padDetection ×2 (hardcoded /workspace/ container path), self-service-v2 photoCandidate. A green suite is still weak evidence — the fix’s own failover path is stubbed out by authenticate = jest.fn() and the rewritten _setupLocalRouting message/audit block has zero coverage

  • FKITDEV-9252a reusable LDAP fault-injection fixture now EXISTS (the vault previously recorded that none did): a BER-parsing TCP proxy at vuer_oss-FKITDEV-9252/scratch-9252/ldap-fault-proxy.js fronting a live glauth, with triggers accept/acceptHang/bind/search/bindResponse/finBind/finSearch/hangBind/hangSearch selectable by connection and op ordinal, RST via socket.resetAndDestroy(); driver test/tests/unit/zz-ad-live-failover.test.js, output scratch-9252/full-run-output.txt. UNTRACKED in a scratch worktree ⇒ it dies with the worktree — move it somewhere tracked to keep it. It is the first real tier-2 asset for facekom-test-tiers. It also refuted a static-review finding, which is the general lesson: observe the wire, do not infer error shapes from library source. Limits to respect: ECONNABORTED/EPIPE never reached; glauth returns [] for getGroupMembershipForUser so useMemberOfProperty: false cannot succeed on it (all runs used true); the chain driver re-implements passport’s fail→next / error→abort / success→done semantics rather than using passport’s middleware. Wire fact worth keeping: one AD login opens TWO TCP connections — conn#1 technical bind + search, conn#2 technical bind again (createClient(null, null, this) inherits bindDN/bindCredentials) then the user bind

  • FKITDEV-8931a testing-scope trap worth generalising: a fix gated on one socketLabel looks broken everywhere else. The silent re-auth fires only for default.layout; kiosk.layout and videochat still hard-reload by design. Because customization/mkb-instant is kiosk-heavy, the obvious pages to click are exactly the ones out of scope — a tester lands on a reload and reports a failed fix. Correct target https://css-fk-dev.taild4189d.ts.net/mbh-services. Also: verify the change in the bundle as served over HTTPS, not the file on disk

  • fk-dev-partner-branch-deploy-runbookswapping fk-dev to a different PARTNER (2026-08-18), the step the branch-swap runbooks omit: a partner is branch + the read-only per-service config/local.json + a vuer_oss_<partner> database + operator users + css yarn build. Highest-value gotchas for any agent on this box: the uid 1001 (host ops) vs 1000 (container techteamer) split → a tree-wide chown breaks logging into a restart loop (EACCES … logs/server.log), always re-fix logs/ to 1000; yarn install must run as root in-container, and never pipe yarn to tail (it masks the exit code — the shell reported 0 while yarn had errored); the mounted local.json is per service, not per partner, and survives git checkout so it silently carries the previous tenant’s db.url+flow.flows; client-side vuer_css changes are invisible until yarn build (output is gitignored web/{js,css,branding,polyfills,libs/pdfjs/wasm}); and supervisorctl uptime 0:00:00 with a climbing pid means restart loop, not a fresh restart. The order is the dangerous part: db.syncOnStart: true makes every boot run migrate→sync→migrate, so a restart with the new code and the old db.url migrates the previous partner’s database (autorestart=true ⇒ a crash is enough) → DB + config before checkout. And local.json is inode-bound (single-file bind mount): rewrite with cat >, never sed -i/mv/git checkout --. Verify with the socket.io handshake, a grep of the bundle as served over HTTPS, and a check that referenced assets return 200 rather than only the HTML. Two named false alarms: a stale web/branding/layouts directory mtime (the .branding.css files inside carry the real time) and mkb-instant’s verify-password-test.js (rejects only the literal incorrectAccordingToPasswordPolicy, not a login blocker — unlike cofidis’s verifypassword.js)

  • facekom-test-tiersFaceKom release testing splits into three tiers and the MIDDLE one has no tooling at all. Measured against nusz-1.9.11.48-test-runbook: ~60% of a real runbook cannot be driven by a browser, which is exactly what the k6/Playwright framing assumes. Tier 1 static/hermetic (needs nothing — lint, unit, config schema, the clean-merge-broken-product detectors); tier 2 booted non-browser (app running, no browser — flow registration at boot §3.1, RPC, cron CLI completion §1.1, emails actually sending §4.5, report/xlsx exports, DB assertions) = NONE; tier 3 browser (full stack + media) = 12/45 k6 files ported. So “12/45 = 27% done” is a ratio against tier 3 only. Unreachable even in principle: iOS Safari mid-call backgrounding (§4.3, Safari-gated, iPadOS reports desktop UA) and the runbook’s own “cannot be validated in UAT” list (real disk reclaim, prod load profile ~40–50% CPU / ~940 Mbit/s / ~1 TB backlog, customer-key-offline deletion branch). Two hard tier-3 limits: k6 has no file upload at all ⇒ the attachment/S3 storage+export flow (§4.4 — a P1 on the FKITDEV-7665 BREAKING conflict surface) is unportable; and k6.yml passes only CI_DOMAIN, never K6_BROWSER_ARGS ⇒ no fake camera/mic, no --disable-web-security on the docker path — close to fatal for a video-identification product. The payoff: a tier-2 harness that boots the app to assert against it IS option (a) of the k6 seeding blocker (“Node pre-seed step reusing helper.ts, handing k6 a fixtures JSON via open()) — it already has helper.ts loaded and a live DB handle, so emitting fixtures is nearly free ⇒ building tier 2 unblocks the 30 stalled k6 tests as a side effect, which argues for tier 2 before finishing the port

  • FKITDEV-8279 — three vuer_oss gotchas that cost time on any branch, not just this one: core devel needs Node >= 24 (geoip-lite@2.0.3 declares engines.node >= 24.0.0, so yarn install fails on Node 22.22.3) even though package.json still says ">=22.18.0" — CI already pins NODE_VERSION: "24"; jest here takes --testPathPatterns (plural), the singular form was removed; and a genuine origin/devel baseline must be built with git archive + devel’s own lockfile, not by stashing — that is what corrected the pre-existing unit-failure count from 4 to 5 (devel 347 suites/3673 tests/5 failed vs branch 348/3703/5, identical failing sets). At least two of those five (SelfServiceV2Service › photoCandidate, AppointmentService › checkFreeAppointments) are date-dependent, so the expected-failure set drifts day to day

  • k6-e2e-harness-vuer-ossthe k6 browser-test harness in vuer_oss test/tests/k6/ (landed by FKITDEV-9041): vuer_docker k6.yml runs stock grafana/k6:master-with-browser, mounts test/tests/k6/e2e, run /e2e/${K6_TEST_FILE:-all.ts}, env CI_DOMAIN only ⇒ docker compose -f k6.yml up. Three things it does NOT give you: (1) no CI verification at all — separate test/tests/k6/tsconfig.json, root tsconfig excludes test/, yarn lint ignores test/*, no typecheck script ⇒ typecheck by hand tsc -p test/tests/k6/tsconfig.json; (2) no orderingplaywright.config.ts chains 14 projects via dependencies while k6 scenarios without startTime all fire at t=0 concurrently (shared-iterations defaults maxDuration: 10m); OpenHoursSetup is load-bearing (setOpenHours('00:00','24:00') × 7 days, else the vuer_css customer flow never renders its callback button); note that within a Playwright project a failing test fails only itself — only downstream projects skip; (3) no fixture seeding — 30 of 45 e2e tests boot the real Node app in-process via test/tests/support/helper.ts (server/db/sequelize, models, UserService, FlowService, acl), and k6 has its own JS VM (4 options weighed, undecided). Also: k6.yml passes no K6_BROWSER_ARGS ⇒ no fake camera/mic, no --disable-web-security on the docker path; suite runs NODE_ENV=dev and can erase the DB

  • playwright-to-k6-translation-recipehow to port a Playwright test to k6/browser, and what cannot be ported (limits verified vs @types/k6 2.0.1). Mechanical: page objects ~1:1, @playwright/testk6/browser (type Locator/type Page), vuerUrls.oss()oss() from lib/env.ts, drop readonly, explicit .ts on relative imports, describe/test()/beforeEach collapse into one export default async function () with beforeEach inlined per case, expect(v).toBe(y)check(v, {'<verbatim test() name>': …}), no test.step(), no storageState (every file calls loginOperator(page)). Two waiting rules that bite: k6 does NOT auto-wait on navigation → Promise.all([page.waitForNavigation(), locator.click()]), but do NOT add it when the click is already followed by page.waitForURL(...) (the second waiter misses a completed navigation and hangs); and Playwright’s expect() auto-retries while k6’s check() does not → anything that relied on polling needs a waiting locator (visible()/hidden()). Hard limits: newContext() throws if one is already open (browser/index.d.ts:754-766) ⇒ 1-to-1 Browser↔BrowserContext, a second actor must be a second page, and every test MUST close its context or all subsequent tests die; no file upload (no FileChooser/waitForEvent('filechooser')/setInputFiles; page.waitForEvent() takes only 'console'|'request'|'response'), no page.coverage.*, no page.route(), no Node built-ins/require('server/**'), no :has-text() (use locator('button',{hasText:'…'})). Faithful-porting caveat: vacuous assertions in the originals (hasEmailError()/hasRightsError() = !!page.locator(...); await-less expect().toHaveText() in open-hours.test.ts) were reproduced as-is on purpose

  • FKITDEV-9200 — the Playwright→k6 migration itself: 45-test inventory, 12 files / 93-of-96 cases ported on chore/FKITDEV-9200-e2e-k6 (uncommitted), 3 file-upload cases dropped, staticendpoints.test.ts deliberately not ported (coverage-only), all.ts restructured to one sequential scenario maxDuration: '1h'

  • dev-build-hostwhere to build/test: the fk-dev Tailscale VM (command ssh ops@fk-dev.taild4189d.ts.net). The old ssh Facekom box is decommissioned (offline since ~2026-06-27) — every older note saying “build/test on ssh Facekom” now means fk-dev. Still true: native builds on the remote host, never emulated on the Mac; use command ssh (the kaku alias shadows plain ssh non-interactively)

  • fk-dev-deploy-smoke-runbookthe practical “swap a component onto a branch and smoke it” runbook for fk-dev (verified 2026-08-13). Deploy = explicit-refspec fetch → reset/clean/checkout (vuer_css git ops via docker exec -u 0:0 because its tree is root-owned and ops has no sudo) → yarn install --frozen-lockfile in-container → npx sequelize-cli db:migratesupervisorctl restart; verify HTTP from the Mac. OpenHours is load-bearing for any videochat/operator-handoff smoke — table openhourstandards (plural), default calendar = calendarName IS NULL; open fully with update openhourstandards set "from"='00:00',"to"='24:00',"isOpen"=true where "calendarName" is null run via the app’s own require('./server/db/sequelize.js') (reads creds from node-config → no raw credentials, and printing db.password gets blocked by the safety classifier), then supervisorctl restart vuer_oss to clear the open-hours cache. e2e helper test/tests/open-hours.setup.ts / seed test/seed/seed.js getOpenHours(null) insert mon–sun 00:00–24:00 as reference

  • instacash-external-api-esign-headless-test-2026-06-01 — how to exercise the InstaCash external API headlessly with bin/instacash-cli.js from the vuer_oss container: SSH via command ssh to the dev host (now fk-dev, dev-build-host); start-server (detached) mocks the bank /auth+/status on 8189; post-application [rt|nrt] [mkb_szemelyi_kolcson|mkb_mszh|mbh_mfl] returns {customerId, customerProfileUrl, inviteUrl}; post-contract <id> [pdf] drives the ic-contract flow → EsignRPC → esign. Caveat: every cmd except start-server boots the full vuer_oss service in-process (needs healthy stack + instacash.external.apiKey Bearer + allowSelfSignedCerts); and the eSign signature itself can’t be automated (interactive video-ID + auth + sign at the inviteUrl)

  • dev-box-esign-container-startup-failures-2026-06-01 — debugging the esign_css/esign_oss unhealthy containers on the dev box (observed on the old lederera box, now decommissioned — same triage applies on fk-dev, dev-build-host): how to triage (docker exec <c> supervisorctl status → nginx FATAL while app/redis/cron RUNNING = the PID-permission bug); the ~60s-unhealthy-after-any-restart healthcheck anti-flap (don’t chase it); post-dep-major-bump lesson — re-yarn install the running container (/workspace/<svc>/node_modules is host-bind-mounted, a baked image install goes stale → RedisStore is not a constructor etc.); command ssh Facekom to reach the box from Claude’s shell (kaku shadows plain ssh), login shell (bash -l) so docker is on PATH; chalk ^5 ESM-only broke yarn trans (bin/test/trans-check.js:6) → dynamic import('chalk')

  • dev-box-cv-photo-processing-failures — “error during photo processing” / “CV server is down” on the lederera dev box has two compounding causes: vuer_cv container stopped (docker start vuer_cv, ~2 min to healthy; nginx 502→404 on loopback curl) and hairpin NAT (vuer_oss host-net /etc/hosts maps *-lederera → own LAN IP 192.168.1.93; remap → 127.0.0.1); the hosts edit is wiped on every docker restart vuer_oss (Docker-regenerated bind mount) so re-apply after any restart, via truncate+write not sed -i

  • sms-verification-code-dev-testing — get/force the SMS (and email) verification code when the customer phone is fake: test.security.tempTokenSms (truthy → fixed code every send; conventional value 123456 in all test/testconfigs/*.json; tempTokenEmail: "mailToken" for email) is read by the customer:verification:sendSms hook (customization/listeners/sms-verification.js); dev box (lederera/NODE_ENV=dev) does NOT ship it — add to config/local.json + restart (node-config caches at startup) + resend (old random code won’t match); alt recovery = read customer.getVerificationCode() via model (auto-decrypts); matchTokens (ContactValidationService.js:16-20) = case-insensitive exact match

  • esign-css-customization-branches — eSign standard dev/test method: test through VÜER CSS with requestFakeCustomer = true and ?esign=1&token=…

  • instacash-esign-dev-box-deploy — testing an InstaCash eSign release on the dev box (now fk-dev — the ssh Facekom box it was written against is decommissioned, see dev-build-host; code bind-mounted from /workspace): the box defaults to Raiffeisen so you must align the whole partner chain to InstaCash (esign_oss/css → tag instacash-1.3.0.11, vuer_oss/css → tag instacash-1.9.11.50 since the eSign ticket pins no vuer version, pdfservice stays main/2.0.12 partner-agnostic). Per repo: git stash WIP → git fetch --tags (clones predate the tag) → git checkout <tag> → rebuild in-container docker exec <c> sh -c 'cd /workspace/<repo> && yarn install && yarn build'supervisorctl restart all. Verify supervisord RUNNING + RabbitMQ connection established + Web server is listening + esign_css :10183 HTTP 200. Gotchas: a “dirty” vuer_css = untracked .claude/ dir; old log ERRORs may be historical from the prior run (check timestamps)

  • fk-dev-nusz-deploy-and-8959-verificationdeploy a branch to the fk-dev GCP dev-mirror VM (tailnet taild4189d.ts.net; NOT the offline on-prem ssh Facekom box) by bind-mount swap, no image rebuild: command ssh ops@fk-dev.taild4189d.ts.net (Tailscale SSH, no keypair; kaku shadows sshcommand ssh/command scp); the box has NO GitHub key so ssh-add ~/.ssh/id_ed25519 + command ssh -A to forward yours; then on /workspace/vuer_oss: git fetch origin <branch> + checkoutdocker exec vuer_oss sh -c 'cd /workspace/vuer_oss && yarn install && yarn build'docker exec vuer_oss supervisorctl restart all; verify supervisorctl RUNNING + Web server is listening on 10081 + operator UI (https://oss-fk-dev.taild4189d.ts.net) HTTP 302. postgresql peer-auth blocks psql -U postgres (use app Sequelize); nginx_proxy crash-loops but sidecars bypass it; restore the box’s original bd8923d69f (InstaCash) when NÚSZ testing done. Also documents a reusable standalone cron test-harness pattern (Node script in bin/process-settings bootstrap + logger Proxy + service/crypto stubs + sequelize authenticate + raw-SQL seed → call the REAL service methods → SELECT before/after; deliver via command scp+docker cp+docker exec+rm)

  • mailtrap-sandbox-inbox-dev-email — FaceKom dev email is not broken: config/dev.json email.transport.SMTP bakes a Mailtrap Sandbox inbox (host smtp.mailtrap.io, port 2525, user 643414e4c00185), so registration/verification mail is delivered into the original-dev/shared inbox you can’t see (symptom reads as “emails don’t send to Mailtrap”). Route to your inbox by overriding hostsandbox.smtp.mailtrap.io + auth.{user,pass} in the bind-mounted vuer_oss-local.json getconfig local layer (config/docker.json is never loaded under NODE_ENV=dev; Sandbox creds don’t auth the legacy smtp.mailtrap.io). Distinguish Sandbox (sandbox.smtp.mailtrap.io, per-inbox user/pass ~14 hex, catches mail) from Email Sending / live (live.smtp.mailtrap.io, api + 32-char token, delivers for real — wrong for testing). Verify with nodemailer.verify() first (require nodemailer by absolute path /workspace/vuer_oss/node_modules/nodemailer). Sibling to sms-verification-code-dev-testing (invisible-delivery-channel dev testing)

  • jest30-ignore-optional-native-resolveryarn test:unit dies on a fresh vuer_css install (root cause corrected 2026-07-31): .yarnrc sets --install.ignore-optional true; Jest 30’s jest-resolve@30.4.1unrs-resolver@1.12.2 ships its platform binding (@unrs/resolver-binding-darwin-arm64 / -linux-x64-gnu) as an optionalDependency, so it’s skipped → require('unrs-resolver') = “Cannot find native binding”Resolver.findNodeModule() returns null for EVERY module (verified for ts-jest, jest-circus, lodash, jest-resolve itself). Jest blames whichever module the config names first ⇒ the misleading Validation Error: Module ts-jest in the transform option was not found, and the jest-circus/build/runner.js not found follow-on is the same single bug, not a second one. Diagnostic: node -e "const R=require('jest-resolve').default; for (const m of ['lodash','jest-resolve']) console.log(m, R.findNodeModule(m,{basedir:process.cwd()}))" — all null ⇒ resolver/native-binding, not a per-package problem. Scope narrowed 2026-07-31: macOS/arm64 LOCAL DEV ONLY — CI is GREEN (pull-request.yaml installs yarn install --frozen-lockfile under the same .yarnrc; all 7 checks incl. Unit Tests pass on the FKITDEV-8887 head). Also NOT caused by --ignore-scripts (re-tested with scripts enabled — binding still absent; the failure is at fetch time). Fix = local node_modules patch, NOT a repo change: npm pack @unrs/resolver-binding-darwin-arm64@1.12.2 → extract into node_modules/@unrs/. The earlier “drop/scope ignore-optional on devel” recommendation is withdrawn — unjustified when CI is unaffected. Why Linux runners resolve the binding is an open question, not an assertion. Green with the binding + stock deps (yarn.lock/package.json untouched): 119 suites / 1051 passed / 47 skipped / 0 failures. RETRACTED: the “ts-jest@29.4.11 is an empty publish, pin 29.4.10” claim is FALSE — that dist-less directory (with the stray npm-view.err) was a poisoned local Yarn v6 cache entry (~/Library/Caches/Yarn/v6/npm-ts-jest-…-integrity/); yarn cache clean ts-jest + reinstall restores dist/ and stock 29.4.11 passes the whole suite. Do not pin ts-jest. Reusable lessons: a Jest “module not found” names a victim, not a culprit; ignore-optional is unsafe once any dep uses napi-rs per-platform bindings (esbuild/swc/rollup/lightningcss/sharp); a Yarn cache entry can be silently corrupt and is reused forever until cleaned, and a stray CI log (npm-view.err) inside an installed package is a cache-artifact tell, not proof of a bad publish; a local-only repro is not a repo bug — check CI before proposing a repo-wide config change.

  • devel-update-verification-recipethe base-commit-worktree technique, the only honest way to say “pre-existing”: git worktree add ../<repo>-base <pre-merge-tip> + symlink node_modules from the merged tree, run the same suites on both, diff the failure sets not the counts. Generali vuer_oss: base 10 failed vs merged 4 ⇒ zero merge-caused and the merge net-fixes sms-report-service. Corollary: run suspect suites isolated and repeatedly — full-suite runs showed a shifting failure set from test-order interference (isolated runs were deterministic across 3 repeats)

  • raiffeisen-1.9.11.100a git worktree’s node_modules can be silently out of sync with the branch it is checked out on. Re-running the FKITDEV-8787 suite in a vuer_css worktree failed on a missing @babel/plugin-proposal-class-properties — switching a worktree onto a release branch does not trigger a re-install, so you can be running another branch’s dependency tree against this branch’s code. Fix: yarn install --frozen-lockfile in the worktree (not the main checkout). Add it to the checklist alongside the base-commit-worktree technique above — both concern trusting a worktree’s environment. Same session also hit two RTK mangling cases (see ## RTK / tooling gotchas)

  • portal-css-jest-runs-zero-testsbefore you cite a test run as evidence, confirm it ran anything. In portal_css yarn jest executes zero tests and CI is green purely on --passWithNoTests appended by the workflow; the custom testSequencer filters every file through an empty allow-list and jest sorts before its “no tests found” check, so adding tests changes nothing. Diagnostic that isolates the sequencer from discovery/config: re-run with --testSequencer=@jest/test-sequencer — if the test suddenly runs, the sequencer is the blocker. Also blocks .ts tests structurally: no @types/jest, test/ outside tsconfig.json include, no types:["jest"]

  • vuer-oss-unit-tests-green-is-weak-evidencethe vuer_oss twin of the above, found on FKITDEV-9197: one shared suite registers ZERO tests and two others flake in CI — all three on devel, so EVERY partner branch is affected. (1) test/tests/unit/translations.test.js:181 misuses it.eachit.each(table) returns a function that must be invoked with (name, fn), but the callback is passed as each()’s second argument and the return value discardedno test is ever registered, only its it.skip(...) calls; verified in node and from CI output on a passing run (345 passed of 347 total / 3515 passed of 3672). Every wrongKeys/missingLanguage finding is therefore discarded for every partner — a probe found 42 invisible findings on CIB alone (flow_task_name/flow_task_instructions/flow_input_optionundefined in customization/flow/cib-*.flow.trans.js). Repairing it is a BREAKING CHANGE (those 42 become hard failures) ⇒ deliberate upstream ticket, never a drive-by. “Translations green” ≠ “translations checked” — FKITDEV-9197’s acaa597d83 stopped the suite throwing at import (real, and why the job runs) but bought no coverage. (2) the same file’s scan() takes relative includedDirectories while cwd is absolute ⇒ loading depends on process.cwd() at module-eval time, and it is actively flaky in production CI: nightly “Checks for Long-lived branches” on customization/mbh 2026-08-11 went pass (20:13) / fail Directory not found: client/features (20:29) / pass (21:10) on the same commit, with both runners producing both outcomes; client/features is real and tracked (215 files); does not reproduce on macOS. devel bfd1311aab (PR #8000) fixed only the require half ⇒ the flake predates and survives it. Root cause of the cwd perturbation is UNPROVEN (no process.chdir in repo code; cross-spawn’s resolveCommand.js:18 is a plausible leak window, not demonstrated). The obvious one-liner BREAKS it silently: path.join(cwd, startPath) makes returns absolute and kills three relative-path consumers (doubled require path, excludedFiles.includes stops matching, argMap[...] undefined) — and path.join concatenates, it does not resolve a leading /; that’s path.resolve; correct fix …scan(path.join(cwd,startPath), […]).map((f) => path.relative(cwd, f)). (3) server/util/pdf.test.js › printImage › place sample PNG image byte-compares a PDF and drifts by 3 bytes; re-run with no content change goes green ⇒ vuer_oss’s “Unit Tests green” was not first-attempt. Operational rule: one red run is not evidence of a regression, one green run is not evidence of its absence — re-run, then classify against a base-commit worktree and run suspect suites isolated and repeatedly

  • FKITDEV-9197when a hypothesis test fails, READ THE ACTUAL ERROR TEXT — a failed experiment only refutes the hypothesis if it failed for the reason you were testing. Concrete: macOS cannot load vuer_css’s config.js with NODE_ENV=dev unless DEV_DOMAIN is setconfig.js:43 does fs.readFileSync('/etc/hostname') on the dev/travisci path and macOS has no /etc/hostname → ENOENT. 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 misattributed a genuine CI failure to a ‘nested worktree artefact’. The hypothesis was right; the platform broke the experiment. The distinguishing move was running the same tree in the Linux product image on fk-dev. Companion to the NODE_ENV=dev masking entry under CI / build gates — the same variable misleads in both directions. AND IT HAPPENED THREE TIMES IN ONE TICKET, always the same failure mode — generalising from an experiment whose failure was never identified: four SSH users refused → “only root works” (ops never tested); NODE_ENV=dev still failed → “NODE_ENV isn’t the variable” (it failed on a missing /etc/hostname, an unrelated cause never read); git log -S returned empty → “400 never existed” (the pathspec didn’t follow the rename). Each was caught only because something INDEPENDENT contradicted it — a retest, a Linux container, a git log -L — and none would have been caught by looking harder at the original evidence. ⇒ the reusable rule is not “be more careful” but “when a negative result is about to carry weight, change instrument”: a null result tells you about your instrument at least as often as it tells you about the world

  • FKITDEV-9197a lazy require() is only safe if you can name who already loaded the module. Fixing portal-client.test.js meant moving a module-scope require('../../../config') into the method — and config.js is not side-effect-free (spring cloud config bootstrap, CORS default mutation, module-scope config.loaded). The argument that made it safe was enumerated, not assumed: config is already required at boot by server/web/WebServer.js:13, server/web/routes.js:2, server/bootstrap/connection/rabbitmq.js:2, customization/server/service/CIBSSOService.js:2 + four customization/listeners/*, and the sole caller of getPortalRedirectUrl is an HTTP route (customization/server/web/routes/device-change-redirect.endpoint.js:34) which is necessarily post-boot ⇒ require-cache hit on the same object, never a first load. Rejected alternatives and why: forking the test (blob is byte-identical on devel/Generali/CIB ⇒ permanent merge conflict), config/test.json (a deployed-repo change to satisfy a test runner), NODE_ENV in jest.config-unit.js (currently identical to devel’s; diverging costs the same as forking). Residual risk recorded honestly: getPortalRedirectUrl has zero test coverage, so the fix rests on an argument rather than an assertion

  • vuer-browser-e2e-real-call-gotchasseven silent traps between you and a scripted REAL vuer call (2026-09-07). The big one: Content-Security-Policy: upgrade-insecure-requests + Secure cookies mean vuer CANNOT be driven over plain HTTP — the browser rewrites every subresource to https, hits the non-TLS nginx, every stylesheet/script dies ERR_SSL_PROTOCOL_ERROR, and you get a bare skeleton whose form is never wired; and curl ignores CSP, so curl-over-http always looks fine ⇒ curl is not a valid smoke test for anything browser-driven. Plus: WebServerAuth.js:821 builds the post-login redirect from config, not the request (https://${config.get('hosts.oss')}${redirectTo}) so a non-standard port must live in hosts; the server-side gate GET /api/pre-check answers unknown for HeadlessChrome, not_compatible for Chrome/120, compatible for Chrome/141 ⇒ Playwright needs full chromium (channel: 'chromium', not the headless shell) plus a realistic UA; operator and customer need separate browser contexts (cookies are not port-scoped ⇒ one context leaks the operator session onto the customer origin); the submit handler attaches only after /api/pre-check resolves, so an early click falls through to a native GET form submit (?_csrf=…&lastName=…, page just reloads); [data-action="take-call"] lives inside a <template> so 0 hits means no customer row rendered, not “hidden”; firstName is letters-only ^[a-zA-Z…]{2,25}$. Built for the janus-memory-leak-rca measurement; test code on chore/FKITDEV-9239-e2e-janus-memleak (vuer_oss + vuer_docker)

  • verification-failure-modesthe four ways we answered a partner wrong, and the round-2 additions (2026-09-08) that are all silent-exit-0 failures. Modes 1–3: verified in the place we expected the answer (customization overlay changed a different rung); the record was stale; a correct fact scoped to a different question (the classifier-vs-flow trap — consulting the right-looking note increased confidence in the wrong answer). Round 2 adds: (1) a note that says “sent” is not evidence of postingSLARAFIPI-84 carried “This is the text that was sent” above a reply that was never posted; fetch the thread and count the comments, and label vault drafts UNPOSTED in the heading. (2) A lens can refute the wrong branch — STATIC “proved” _isSameFace fails CLOSED by quoting handler :322-324 (the missing-source branch) while the fail-open is the missing-target branch :338-367; ADVERSARIAL “proved” the config threshold never reaches the comparison by reading migrateConfigState as a mere change detector, overlooking the Setting-row write at SelfServiceCheckerService.js:1360-1364re-read the cited lines before accepting a refutation of a refutation. (3) Agent notifications truncate silently4 of 6 reports arrived cut mid-sentence with no marker ⇒ every agent writes its full report to a file and reports the path. (4) extract skips attachments with exit 0 (skipped-type) — it dropped the .tgz server log and the .xlsx export, the two files that decided the investigation

Devel update sync workflow

  • instacash-update-2026-05-27-status — Branch naming convention: update/customization/instacash-<date> (date-only, no ticket prefix), applied identically across all three repos (esign_css, vuer_oss, vuer_css); per-repo conflict surfaces vary significantly (esign_css blocked structurally, vuer_oss self-service-v2 listener high-risk, vuer_css modal a11y trio + customizations.js routes high-risk); using a separate worktree to keep parallel feature WIP branches untouched is the pattern (e.g. ~/coding/facekom/vuer_css-instacash-update to protect bugfix/FKITDEV-8787)

  • esign-css-instacash-orphan-history — When the target branch is structurally orphan, the standard git merge devel halts; git rev-list --count A..B is meaningless without a merge base — three viable workflow options to choose between before resuming

  • FKITDEV-8817 — Parallel-strategy sequencing: don’t block the sync on an unmerged adjacent PR; let the second devel→update-branch merge pull it in once the PR lands on devel

  • nusz-devel-update-2026-06-16-lint-merge-fix — NÚSZ update/customization/nusz-2026-06-16 merge of origin/devel failed validation at lint, left mid-merge (uncommitted). Reusable merge gotcha: devel renamed FFmpegService.js.ts; nusz tip had added an extensionless require('./server/service/FFmpegService') to cron.js — conflict-free merge kept both, so it no longer resolves under n/no-missing-require. cron.js:46 was the lone straggler (all other callers already on explicit .ts). Lesson: after a cross-side .js.ts rename merge, grep for extensionless require()s of the renamed modules

  • customization-branch-ci-pipeline-inheritancebudget for the CI pipeline expansion on a partner’s first devel-merge: 1 job (lint-and-build) → 6 (lint/test/audit/depcheck/sonar/build), i.e. 4 jobs run on the branch for the first time ever and most red is pre-existing partner breakage newly enforced, not merge-introduced. Triage every failure by origin (git blame/git log the offending line) before choosing a fix side (partner file vs shared core test). Characterised on FKITDEV-9059 (Cofidis, 2026-07-20)

  • FKITDEV-9059 — Cofidis devel update (vuer_css PR #3100 @ 4dd8a927a, 105 files; vuer_oss PR #8040 @ ad8a318a22, 586 files), both mergeable_state: blocked; full 8-row failure inventory with root cause / origin / fix-side per failure

  • mjml-v5-esm-breaks-commonjs-email-templatesan ESM-only major upgrade of a shared loader, merged via devel → customization/*, silently breaks CommonJS customization files. The FKITDEV-9059 cofidis merge pulled in FKITDEV-8727 MJML v4→v5 (ESM-only, c9602a519e) → EmailService.js await import(...)s letter templates; Node 22 then parses any customization/email/*/*.letter.data.js that mixes a top-level import with require() as ESM → ReferenceError: require is not defined at EmailService.init. Scope trap: NOT “41 of 49 files contain require(” — only the 6 mixed files break, and 5 had a createRequire shim so only e-mail-invite crashed at boot. Reusable rule: after any ESM-loader migration, grep customization/ for mixed import+require. The ESM cousin of the nusz-devel-update-2026-06-16-lint-merge-fix .js.ts rename gotcha

  • FKITDEV-9194Generali round 2026-08-08 (release 1.9.11.19 prep): branch chore/FKITDEV-9194-generali-update-2026-08-08 off customization/generali-atvilagitas in both repos (precedent chore/FKITDEV-9073-generali-update-2026-07-15); vuer_oss df01e922ce / vuer_css e3c8996d4, 0 behind live devel, not pushed. Two semantic breaks hidden by a conflict-free merge: devel deleted server/util/aiActHelper.ts (→ server/web/helper/getAiActData.js) still required by customization/server/web/routes/waiting-room.endpoint.js:5; and a6185aa41 [fkitdev-8846] remove duplicated socket connections swapped auth()SocketService.getConnection('<page>.script') while the gen-self-service-consent-{pep,ttny} overrides kept calling auth(). Fix pattern both times = mirror devel’s own core refactor inside the override, keep the gen-* identifiers

  • devel-update-and-release-flowSTART HERE: the end-to-end procedure, extracted 2026-08-11 and superseding the verification-only recipe. Phase 0 preconditions (which repos the partner is actually in; dead lines like customization/generali-kar, 1754 behind — never update them; one ticket or two; check remote.origin.fetch first). Phase 1 isolated worktree <repo>-FKITDEV-NNNN, ruleset-enforced chore/FKITDEV-NNNN-<partner>-devel-update branch, real merge (never rebase) back into customization/<partner> (never devel), then the semantic sweep: deletions/renames first (--diff-filter=D/R — vuer_css caught the deleted aiActHelper.ts; vuer_oss had 0/0, making the class structurally impossible), resolve every relative require under customization/ skipping comments, grep for removed identifiers, diff each override against its core counterpart. Phase 2 validation: Node 24 (yarn install --frozen-lockfile hard-fails on Node 22geoip-lite needs >=24; engines: >=22.18.0 is stale), classify against a base-commit worktree with its OWN yarn install (never symlink node_modules — the merge moved yarn.lock 800 lines and that shortcut voided a first attempt), isolated + repeated suite runs, remote run inside the product image on fk-dev (COPYFILE_DISABLE=1 tar --exclude='._*' or git archive, else macOS AppleDouble files become ~20 bogus failing suites), and which CI jobs and scheduled matrices the branch newly enters. Phase 3 = the new headline, see Release management. Phase 4 release gates. Phase 5 re-check devel immediately before pushing

  • devel-update-verification-recipe — superseded stub → devel-update-and-release-flow; kept only so old inbound links resolve

  • narrowed-fetch-refspec-stale-devel-mergestep 0 of any devel update: in a narrowed clone git fetch origin devel silently leaves origin/devel stale and the merge is conflict-free and wrong

  • devel-dependency-removal-breaks-partner-customizationNEW break class (Phase 1.4 check (e)): devel deleting a dependency as “unused” is only unused in CORE. depcheck runs on the branch it is invoked on, and partner customization/ code lives only on the partner branch — so it is not in the tree when the removal decision is made, the removal merges conflict-free, and nothing in CI can tell you until the partner merge. Canonical case (portal_css / CIB, FKITDEV-9197): devel’s 7a42894a chore(FKQA-304): remove unused libs (#675) dropped multer (customization/api/document-upload.jsmulter.memoryStorage(); after the merge NOT RESOLVABLE = real runtime break) and uuid (customization/api/submit-login.js + submit-registration.jsuuid.v4(); resolved only transitively via a hoisted uuid@14.0.1 against the declared ^10.0.0). The transitive half is the worse one — it works today, at a major nobody chose, and breaks with zero partner-side changes the day the hoisting dependency moves; “it still resolves” is not a pass, undeclared-but-hoisted is unowned. Fix = restore "multer": "1.4.5-lts.2" + "uuid": "^10.0.0". Check = diff package.json for removed deps → git grep each under customization/node -e "require('<pkg>/package.json')" to catch the hoisted ones (the step people skip). eslint n/no-extraneous-require is the free detector for the quiet case under --max-warnings 0 — do not silence it. Same family as the .js.ts (nusz-devel-update-2026-06-16-lint-merge-fix) and ESM-loader (mjml-v5-esm-breaks-commonjs-email-templates) traps: git reconciles text, the two sides never touch the same line. IT CUTS BOTH WAYS — three outcomes, only one of which is “restore the line” (all three occurred in the same CIB round): (1) devel removed it deliberately (security/EOL retirement) ⇒ PORT the partner code off it, restoring re-introduces exactly what devel retired — vuer_oss request+request-promise-native, whose removal CIB’s own security/*.md had logged as scheduled monthly since 2025-03, ported to fetch; (2) removed as merely unused-in-core ⇒ restore the partner’s declared version (portal_css multer/uuid); (3) partner-only dep devel never hadKEEP it — vuer_oss clamscan/soap/xml-formatter/short-uuid/uuid/zod, vuer_css node-fetch/passport/passport-oauth2 (the CIB corporate-portal SSO path). Row 3 has no detector whatsoever — it is not a merge artefact but a conflict-resolution mistake (taking devel’s package.json wholesale), so the check needs a fourth step diffing the partner’s dependency set against devel’s for keys devel lacks. And git diff -- package.json cannot tell row 1 from row 2 — only the removal commit’s reason can. Signature of the whole class: clean · clean · dead (merge clean, lint clean, dies at require-time on boot)

  • FKITDEV-9197CIB round 2026-08-11, release 1.9.11.102, all THREE repos (vuer_oss e191f3f53f, vuer_css 680a6256c, portal_css b1a4bc94), branch chore/FKITDEV-9197-cib-devel-update, Node v24.18.0. Pushed 2026-08-12, three PRs open (vuer_oss #8126 · vuer_css #3152 · portal_css #712) — see the CI half at the end of this entry. It is a PURE CORE-UPDATE release: nothing was committed directly to customization/cib since the last one — in all three repos the branch tip WAS the release commit (cib-1.9.11.101 2026-05-29, cib-1.4.0.74 2026-04-21) ⇒ Phase 3 bucket 1 is empty, and the only partner-requested ticket (ASSCIB-161 = FKITDEV-8887, iOS Safari audio) was already on devel (6bdf66d16) and arrives via the merge — the same commit that is Generali .19’s headline (ASSGRALI-63) and invisible to release_tickets.py in both releases. 11 months of drift = 334 commits / 212 tickets (inventory + authors at /Users/levander/coding/facekom/out/FKITDEV-9197-cib-1.9.11.102-tickets.md; Makkai 44, Horváth 37, Szecsődi 16, Szekeres 15). Portal is a SEPARATE version train — resolve baselines with the repo’s own cib-1.4.0. prefix, never the vuer number in the ticket. Gates: oss lint 0 / build 0 / depcheck clean / audit 1 CRITICAL (the FKITDEV-8279 fork, expected, and build needs: audit ⇒ CI build blocked) / test 3546 pass, 3 fail all pre-existing against real controls (translations.test.js is CIB-only and fails identically on the pre-merge tip; converter/vuer-cv-service/pdf fail on clean devel) + import sweep 1219 specifiers / 703 files / 0 unresolved; css lint 0 / audit 0 critical / 1066 pass, 2 fail pre-existing, 53 conflicts resolved, 722 relative imports + 302 twig include targets all resolve; portal all green but zero tests exist structurally. CIB is NOT an Oracle partner (no db.options override, no oracledb ⇒ Postgres) so FKITDEV-8279’s Oracle behavioural-swap risk does not apply to it. Prior art was buggy: two abandoned unmerged branches (update/customization/cib-2025-11-12, …-2025-12-08 tip 4df6666d4f) had already ported 4 files to fetch but silently dropped agentOptions, killing Infocert mutual TLS (6 InfocertRestAPI sites — compiles, lints, sends no client cert) and their multipart port cannot run (formData.getHeaders() doesn’t exist on web FormData, fs.createReadStream can’t be appended) ⇒ mine abandoned branches for the FILE LIST, never for the diff. Open for a human: whether portal_css ships in .102 at all (ASSCIB-166’s Komponensek lists only oss+css), undici as a new direct dep, the site-wide page-focus-visible = cib-green-700 branding call with no CIB precedent, the now-removed system-check browser-incompatibility guard (devel’s FKITDEV-6036 deleted it deliberately; unsupported browsers now render an error report instead of hard-failing), an ungated CV v2→v3 swap in self-service-v2.js against CIB’s identificationLimits.compat: true, REST ports unit-proven but not endpoint-proven, and CIB’s TjK shape never verified (priorShape: null). CI half (2026-08-12): portal_css 7/7 green first run, vuer_css 6/6 after two fixes, vuer_oss green except the two standing human-override reds (audit = the FKITDEV-8279 fork, npm advisory 1114318; SonarCloud quality gate = an 11-month merge graded as “New Code”) with Build skipped via needs: audit. “Skipped” ≠ “passed” — vuer_css’s Build+SonarQube were skipped, not failed, because both needs: the red test. Fixes: f434bf9db (sso-login-endpoint.test.js asserted 400 vs the middleware’s 401 — right for ~6 weeks in 2024, red for 20 months, unnoticed because the branch had no test job; c3256bc10 swept all three exits to 401 INVALID_AUTH_CREDENTIALS and renamed the file, updating only the test’s require), 83e4adbd2 (portal-client.test.js died at import — CIB-only module-scope require('../../../config') and no config/test.json under jest’s NODE_ENV=test; fix = lazy require inside the method, safe only because config is already loaded at boot and the sole caller is an HTTP route), acaa597d83 (translations.test.js calls unregistered callbacks with zero arguments; register the portal infoText/helpText/placeholder arg maps, no source change). Two methodology corrections, both of which produced confident wrong answers: git log -S does not follow renames (use --follow or git log -L <a>,<b>:<file>), and macOS cannot load vuer_css config.js with NODE_ENV=dev (config.js:43 reads /etc/hostname) — which caused a genuine CI failure to be misattributed to a “nested worktree artefact”. Open: getPortalRedirectUrl has zero coverage, the green SSO suite only exercises the dev-mock branch (mockSsoServer true in dev.json, false in docker.json), and the 401-on-missing-credentials contract with CIB’s portal client is undocumented

  • squash-merge-erases-partner-devel-ancestrySquash-merging a develcustomization/<partner> PR silently destroys the merge record. The squash commit has ONE parent (the partner tip), so devel stops being an ancestor and the NEXT devel update re-presents the whole already-merged span as new changes with heavy conflicts. Failure is deferred by a full release cycle. Detect with git cat-file -p <sha> | grep -c '^parent ' (2 = merge, 1 = squash/rebase) or git merge-base --is-ancestor origin/devel origin/customization/<partner>; via API gh api repos/TechTeamer/<repo>/commits/<sha> --jq '.parents[].sha'. Content is never at risk — the squash tree is byte-identical to the PR head tree, verify with .commit.tree.sha on both. Mitigations, in order: (1) always use Create a merge commit for sync PRs, (2) after the fact git merge -s ours <squashed devel sha> to restore ancestry with no tree change (needs a customization/** commit satisfying techteamer-commit-message-ruleset, and a human decision), (3) absorb the conflicts next round. First confirmed instance: cib-1.9.11.102 / FKITDEV-9197, 334 commits / ~11 months collapsed in both vuer_oss and vuer_css

Device detection

  • FKITDEV-8533 — server-side UA parsing cannot detect a modern iPad: iPadOS 13+ Safari sends a Macintosh desktop UA, ua-parser-js v1 returns device.type === undefined; customer.isTablet() (device.type === 'tablet') is a strict logical subset of customer.isMobile() ('mobile' OR 'tablet') so it adds no detection power; customer.userAgent is the only client signal the server has (no Sec-CH-UA hints); reliable detection = client-side navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent). Resolution avoided the detection problem entirely (2026-06-23): rather than detect the iPad, gate on the easy-and-reliable negative Customer.isNativeApp() (userAgent.startsWith('mobile/'), single prefix for iOS+Android native SDK) — browsers (incl. spoofing iPads) all fall on the enable side.

Face comparison

  • face-comparison-persistence-pathsREAD FIRST; corrects the two notes below. Itself CORRECTED 2026-09-07 from SLARAFIPI-84 — §3 and all customization/ line numbers were re-derived from the tag; the note is now current. Which paths actually write a faceComparisons row at tag raiffeisen-1.9.11.100. ONE writer (FaceRecognitionService.createFaceComparisonModelFaceComparison.create:152), 3 call sites not 4 (self-service-v1 is not a distinct site): core FlowService.handleTaskRecognitionOptions:2918-2968 (from submitTaskPhoto:2905), liveness-v2-only SelfServiceV2Service.js:1416:1431, videochat:close hook :41. status is ALWAYS 'success' — nothing ever writes 'failed', so every upstream guard failure produces no row at all and failed comparisons are structurally unrepresentable (⇒ “the export is missing the failures” is a category error). The compareFaceWith question, in its corrected form: liveness dispatches by step.type into THREE handlers (server/queue/rpc_server/SelfServiceV2.js:208-219) and only liveness-check-v2handleLivenessCheckV2:2114saveLivenessCheckV2Messages:1349 persists — but for myra that is not the binding constraint, because the proto’s BOTH liveness steps (v1 order 6, v2 order 7) declare no recognitionOptions at all. Ask “is recognitionOptions.compareFaceWith in the proto?” BEFORE “which handler runs?” — and the fix is a proto change, not a flow migration or core work. 3 ways a match happens with NO row: Raiffeisen liveness descriptors pushed into a CV LivenessTask (match computed inside vuer_cv — real, but not the reason there is no row); myra _isSameFace:309-380 (in-memory, flow-control only, and its result is then discarded by the photoFinalize submit gate); any silent guard skip. imageCategory is NOT a setting — a per-recognition copy of the task’s screenshotCategory (writes at RecognitionService.js:235,363 + SelfServiceV2Service.js:1405; FaceRecognitionService.js:194 is a read); myra sets screenshotCategory:'customer-portrait' on both emrtd (proto :81-98) and customer-portrait (:99-122) ⇒ rows stored mislabelled customer-portrait ↔ customer-portrait, so reports keyed on imageCategory cannot tell the sides apart. Fail-open, now VERIFIED reachable: _isSameFace max-reduces distance from identity 0, the best value in a distance metric ⇒ zero valid targets → 0 <= perfectCHECK_SUCCESS, i.e. missing data reads as a perfect match, silently (_logCVError:376 only fires if (!success) and is behind raiffeisen.debug.cv, default off). Contrast: the no-source-face branch fails closed, and the core path fails safe. Both former UNVERIFIED rows are now settled: the NFC question is YES via three paths (webSDK step-filtering, null eMRTD attachmentId, non-success recognition), and perfect = 0.55 IS a repo factconfig/docker.json:59-61 at the tag, applied at listeners/self-service-v2.js:124
  • face-comparison-data-verdict-threshold-modelcanonical model note (FKITDEV-8827 design): faceComparisons rows key by EITHER roomId (videochat/operator) OR selfServiceRoomId (self-service-v2), no mutual-exclusivity constraint (models.js:298-309, model/faceComparison.js); euclideanDistance = cosine distance 0–2 (misnamed). PION = videochat NOT self-service — protos declare videochat, comparisons from videochat:close hook (faceRecognitionHooks.js:26-44) keyed by roomId, gated by config faceRecognition.comparisonPairs (FaceRecognitionService.js:7,20) → self-service-only query returns zero PION rows. Verdict DERIVED by getFaceComparisonResult (SelfServiceCheckerService.js:132-154): SUCCESS≤perfect / PROBABLE≤probable (the match tier collapses, default match:null) / FAILURE>probable, operators <=, defaults {perfect:0.5,match:null,probable:0.6}. Threshold sourcing differs by link type: self-service per-room selfService:v2:config:state (oldest [0], ASC) REPLACES → global Setting key faceComparison (persisted by SettingsService.init()); videochat/operator rows have NO per-room path and are NOT verdict-classified at runtime (room.endpoint.js:144 raw distance only). No createdAt index → date-range exports full-scan; report-bin pattern + latent bug in raiffeisen-selfservice-failed-reports.js:106 (prepareReportData abstract, masked by active:false cron); Postgres but MySQL-portable (LOWER() LIKE not ILIKE)
  • face-comparison-different-face-db-query — face-comparison results are persisted: faceComparisons table (server/db/model/faceComparison.js:18-37) stores status ∈ {created,failed,success} + euclideanDistance (FLOAT nullable, actually cosine distance 0–2 despite the name); euclideanDistance written unconditionally by FaceRecognitionService.createFaceComparisonModel() regardless of threshold; different_face is not stored — it’s the CHECK_FAILURE read-time verdict from SelfServiceCheckerService.getFaceComparisonResult() (:132-153) when distance exceeds all thresholds; thresholds resolve per-room (selfService:v2:config:state activity log) → global Setting key faceComparison → code default probable:0.6; 4 call sites — liveness-V2 (SelfServiceV2Service.js:1390) gated by task.options.recognitionOptions.compareFaceWith (base V2 proto doesn’t set it), portrait/ID-doc (server/flow/FlowService.js:2943), videochat-close hook, V1; faceComparisons has no step column — portrait vs liveness only via joined FaceRecognition.imageCategory; queryable with one read-only SQL, no release
  • FKITDEV-8827the export SHIPPED: vuer_oss PR #7971 (raiffeisen-facecomparison-export.js + FaceComparisonExportService) APPROVED by m3szi and MERGED 2026-08-10 (merge commit 297aa2fac1f6…) into the 1.9.11.100 release branch — base retargeted off the older .95 release branch; not on customization/raiffeisen or devel, and raiffeisen-1.9.11.100 is not tagged. The read-time-verdict model is now proven end-to-end, not just by reading code: an untracked .dev-e2e/run-e2e.sh harness (throwaway postgres:17-alpine on :5544 via OrbStack) produced a real dated CSV in which a different_face row exports with status=success — i.e. the persisted status and the derived verdict genuinely disagree, exactly as face-comparison-data-verdict-threshold-model predicts. Unit suite RaiffeisenFaceComparisonExportService.test.js 23/23. Release hub raiffeisen-1.9.11.100
  • SLARAFIPI-84the myra customer-portrait comparison is computed TWICE and only the second one persists — which is why the export can contain nothing but accepted rows. Verified at tag raiffeisen-1.9.11.100 (worktree HEAD was 6 commits behind the tag, on exactly these files — always git show <tag>:<path>). Stage 1: the myra handler’s _isSameFace computes the cosine score itself and never writes faceComparisons. Stage 2: FlowService.submitTaskPhotohandleTaskRecognitionOptions, the only writer — unreachable for a rejected photo, because a mismatch sets recognitionValid=falseactions.submitEnabled=actions.recognitionValidSelfServiceV2Service.photoFinalize throws 'Submit was not enabled for this photo candidate!' (a server-side gate). ⇒ every different_face case is structurally absent from the export, and the observed distance ceiling (max 0.5395, zero rows ≥ 0.55 across 807 rows) is a consequence, not a coincidence. No bypass: the ungated screenshot-save RPC needs task.data.attachmentIds, only ever set by liveness/action-task paths; test.selfService.recognition.submitEnabled is inert (no test key in either config at the tag). The rejected score IS in the DB, in four places, only one of them debug-gated: Activity selfService:attachment → content.recognitionDetails.face_compare, Flow.tasks[].data.candidates[].recognitionDetails.face_compare (most reliable — not subject to the final-state race), FlowActivity flow:task:data:change, and Activity selfService:cvTask:log / CVTask:failed (gated on raiffeisen.debug.cv). Payload is {compareAttachmentIds, score, success}the field is score, NOT distance ("distance" appears zero times in a room export), and no threshold is in the payload. Recovery caveats: createActivityLog drops the row once the room is final and is called unawaited with fail() right behind it ⇒ the last rejection’s activity is the most at-risk; and _isSameFace only runs after the CV result is already acceptable, so sharpness/geometry rejects yield no score at all
  • face-comparison-distance-thresholdsSCOPED 2026-09-08: the perfect/probable/different_face ladder is the EXPORT CLASSIFIER’s semantics, not a flow-control fact. The myra (Raiffeisen) self-service flow does not use the ladderconst success = faceComparisonResult === CHECK_SUCCESS (myra-self-service-v2-phase-1.flow.handler.js:366-367 @ raiffeisen-1.9.11.100) ⇒ anything short of SUCCESS rejects the photo, so for that flow perfect (0.55) IS the accept/reject boundary and the 0.55–0.6 probable band can never be populated by it (partner data: max distance 0.5394541 over 807 rows, zero rows in [0.55, 0.6)). The note’s §4 “The 0.55 trap — perfect is NOT the accept/reject boundary” is true of the classifier and is the textbook Mode 3 failure: reading it made us more confident in the wrong answer to Raiffeisen. Both §4 and the TL;DR row are now scoped in place, not removed. Rule: before answering a partner threshold question, check what their handler does with a non-SUCCESS verdict

Express 5 migration

  • ASSICASH-71 — PR #666 (closed unmerged) → PR #670 (merged) for _router → router and /password-recovery/:token?/:lang? array rewrite; PR #689 follow-up
  • instacash-update-2026-05-27-statusNo Express 5 risk in vuer_css side of this update wave (contrary to a prior assumption rooted in ASSICASH-71’s vuer_css customization/instacash Express-4-on-devel-Express-5 case); vuer_css server-side merged clean (routes.js, WebServer.js, package.json, yarn.lock)

eSign / esign_css

  • instacash-external-api-esign-headless-test-2026-06-01 — the InstaCash post-contract <customerId> [pdf] external-API call (via bin/instacash-cli.js) is how the eSign contract leg is exercised headlessly: POST /external/contract → creates the “IC - Szerződés ajánlat feltöltése” (ic-contract) flow → EsignRPCClient → esign service. But the signature itself is interactive (video-ID + mock /auth + SMS 123456 + sign in the eSign UI) — the CLI proves the EsignRPC plumbing only, it cannot complete a signature. Requires the esign containers to be healthy first (see the nginx-PID fix note)
  • dev-box-esign-container-startup-failures-2026-06-01esign_css + esign_oss dev-box containers (esign_{css,oss}:2024.4.1-20240614, rebuilt ~2025-12-08, nginx 1.28, run as non-root techteamer uid 1000) come up unhealthy because nginx can’t write pid /run/nginx.pid; (root-owned /run) — image regression, fix belongs in the esign images/Dockerfiles (/etc/nginx not bind-mounted); RedisStore is not a constructor in server/web/web-server.js:24 is a red herring (code is correct connect-redis v9; only an old/stale node_modules bites); chalk ^5 ESM broke bin/test/trans-check.js yarn trans
  • esign-css-customization-branches — Customization branch fleet (only customization/instacash active, all others archived); standard dev/test method (test through VÜER CSS with requestFakeCustomer = true and ?esign=1&token=…)
  • esign-css-instacash-orphan-historycustomization/instacash is a single squashed orphan commit (b7cee2f, 2025-11-20, release 1.3.0.10); zero shared history with devel; 95-path delta; previous releases likely built via squash-rebuild + force-push pattern (confirmation needed)
  • FKITDEV-8817 — esign_css jQuery 2.2.4 vulnerability (CVE-2020-11023 / CVE-2019-11358); fix on bugfix/FKITDEV-8817-jquery-update pushed (b021019); dead-code path (auth.layout.twig) — repoint to existing /libs/jquery/jquery-3.7.1.min.js; PR not yet opened
  • esign — Electronic Signature System overview
  • instacash-esign-1.3.0.11release composition for InstaCash eSign 1.3.0.11 (esign_oss + esign_css only; tag instacash-1.3.0.11, 2026-06-08; Harbor instacash-esign-{oss,css}:1.3.0.11-20260608); changelog = devel update + FKITDEV-8817 vuln fixes (jQuery CVE-2020-11023 / CVE-2019-11358 on esign_css PR #250 + HSTS/nginx/WAF hardening); ASSICASH-92 release, ASSICASH-93 TESZT / ASSICASH-96 PROD (approved 2026-06-26 from 1.3.0.8); no DB migration / no breaking change, rollback = redeploy 1.3.0.8
  • instacash-esign-dev-box-deploydev-box recipe to test an InstaCash eSign release (run it on fk-dev; the old ssh Facekom box is decommissioned — dev-build-host): the box defaults to Raiffeisen so align the WHOLE chain to InstaCash (esign_oss/css → instacash-1.3.0.11, vuer_oss/css → instacash-1.9.11.50, pdfservice stays main/2.0.12); per repo git stashfetch --tags → checkout tag → in-container yarn install && yarn buildsupervisorctl restart all; verify supervisord RUNNING + “RabbitMQ connection established” + “Web server is listening” + esign_css :10183 HTTP 200

GCP dev-box mirror / Tailscale

  • fk-dev-partner-branch-deploy-runbookthe partner-swap half of fk-dev, verified 2026-08-18 (companion to fk-dev-deploy-smoke-runbook, which owns the smoke half). Deploying a partner is not just a branch checkout: it is branch + config/local.json rewrite + a vuer_oss_<partner> DB + operator users + (css) yarn build. Four traps, each a real failure: (1) uid mismatch — host ops=1001, in-container techteamer=1000 (host ubuntu); chown -R ops:ops /workspace/vuer_oss → restart loop with EACCES … open 'logs/server.log', always chown -R 1000:1000 <repo>/logs after; (2) yarn install as 1001 fails EACCES on pre-existing node_modules, run as root in-container, and piping yarn to tail hides its exit code (shell said 0, yarn had errored) → redirect + echo $?; (3) stale partner configconfig/local.json is bind-mounted read-only from /workspace/vuer_docker/tailscale/config/vuer_<svc>-local.json, one file per SERVICE not per partner, and survives branch switches, so it still held CIB’s db.url + a 30-entry flow.flows; you can’t delete it either because dev.json ships hosts empty and janus wss://localhost:8989 vs the box’s real ws://localhost:8188keep hosts + webrtc.janusServers, drop only partner keys. Also: SSH as root works (reconciles the ops-only claim; vuer/dev/techteamer newly confirmed refused), per-service tailnet names are sidecars not SSH hosts (port 22 refused), repos ops-owned under root → -c safe.directory=/workspace/<repo> on every git call, box has no GitHub creds (agent-forward via ssh-add --apple-load-keychain + command ssh -A), host has no node/yarn, db.syncOnStart: true auto-migrates a brand-new DB (no manual sync), psql needs -h localhost + PGPASSWORD=dev -U dev, bin/db/create_user takes only ONE role → convention is a direct update users set rights='["admin","supervisor","operator"]' (column is rights, a JSON string; no role/type column). (4) db.syncOnStart orderingserver/bootstrap/connection/db.js runs migrate→sync→migrate on every boot, so booting with the new partner’s code and the old db.url silently migrates the wrong DB, and supervisor autorestart=true means a crash in the checkout window suffices → create the DB + rewrite local.json before anything restarts (verified clean: vuer_oss_cib 158 migrations, 5 CIB-only, zero mkb leakage). Also: local.json is a single-file bind mount so docker binds the inodesed -i/mv/git checkout -- swap it and the container silently reads the OLD file (burned two prior sessions), rewrite with cat > only; existing automation /workspace/vuer_docker/bin/vuer.sh has the right stop-before-checkout order but no fetch, a stale branch list (customization/mkb not mkb-instant), no db.url handling, and builds AFTER starting (vuer.sh db init seeds admin/operator with password = username); ops has no sudo; verification must grep the bundle as served over HTTPS and assert referenced assets 200, not just the HTML. Box left on vuer_css fix/FKITDEV-8931-socket-test + vuer_oss customization/mkb-instant / vuer_oss_mkb_instant, prior CIB state in /workspace/_restore/; FKITDEV-8931 fires only for socketLabel default.layoutkiosk.layout/videochat still hard-reload (out of scope), and mkb-instant is kiosk-heavy, so test on https://css-fk-dev.taild4189d.ts.net/mbh-services
  • dev-build-hostCANONICAL host reference (2026-07-01): the on-prem box ssh Facekom (= HostName localhost + ProxyJump FKJumpBoxroot@lederera-447-fk-hardver) is DECOMMISSIONED — Tailscale shows it offline since ~2026-06-27 — and must never be used again. All native builds / tests / deploys now run on the fk-dev Tailscale VM (100.91.108.61, command ssh ops@fk-dev.taild4189d.ts.net, Tailscale SSH, no keypair). Unchanged: build native on the remote host, never emulated on the Mac (qemu SIGSEGV exit 139 / overlay-FS I/O exit 125 / repo-metalink 503s) — only the host moved. Agent gotcha: the user’s shell aliases ssh/scp to a _kaku_wrapped_ssh function that is NOT loaded in a non-interactive shell (_kaku_wrapped_ssh: command not found) → use command ssh / command scp. Other tailnet peers are per-service sidecars on fk-dev, not build hosts: oss-fk-dev (100.91.55.42), css-fk-dev, portal-fk-dev, esign-oss-/esign-css-/esign-api-fk-dev, css-sdk-demo-fk-dev
  • fk-dev-nusz-deploy-and-8959-verificationfk-dev is now OPERATIONAL (2026-07-03): the 8b per-service Tailscale-sidecar overlay is pushed + wired + running (closes the “push gated on user go” item) — sidecars oss-/css-/esign-*/portal-fk-dev up, vuer_oss operator UI at https://oss-fk-dev.taild4189d.ts.net (HTTP 302; Express :10081 inside), source bind-mounted /workspace/<repo> + supervisord per container. First real use = deploying the two NÚSZ fixes (FKITDEV-8747 + FKITDEV-8959 on customization/nusz tip d426cc6ae1) and verifying FKITDEV-8959 TC-8959-02. Operational gotchas: command ssh ops@fk-dev.taild4189d.ts.net (Tailscale SSH; kaku shadows ssh); box has no GitHub key (agent-forward with command ssh -A); postgresql peer-auth (no psql -U postgres); nginx_proxy crash-loops (bypassed). Still open: janus/WebRTC media over the tailnet, CV (non-GPU VM). Full deploy runbook + verification in the note; provisioning/overlay design in tailscale-gcp-dev-box-migration
  • fk-dev-deploy-smoke-runbookgeneral-purpose fk-dev deploy + smoke runbook (verified 2026-08-13, NÚSZ 1.9.11.48 smoke): the sibling to the NÚSZ/8959 note but partner-agnostic and carrying facts the others omit. SSH: ops is the ONLY tailnet-permitted user (levander/lederera/facekom/ubuntu/dev/deploy/admin all refused); command ssh is mandatory and _kaku_wrapped_ssh injects no user/identity (red herring — it only sets TERM+IdentitiesOnly). GitHub fetch needs command ssh -A + ssh-add ~/.ssh/id_ed25519 (that key auths GitHub as wowjeeez); empty agent → Permission denied (publickey). Stack: node 24.12 / yarn 1.22.22 live inside containers as root (uid 0), host has neither. vuer_css working tree is root-owned so its git ops (reset/clean/checkout) must run docker exec -u 0:0 vuer_css sh -lc '… safe.directory … ' (ops has no sudo). Always fetch with the explicit +refs/heads/<b>:refs/remotes/origin/<b> refspec (narrowed refspecs go stale). Migrations: npx sequelize-cli db:migrate (also auto-migrates on boot). Verify HTTP from the Mac (curl -sk https://{oss,css}-fk-dev.taild4189d.ts.net) — fk-dev can’t resolve its own sidecar MagicDNS non-interactively. supervisor programs listed per service. NOTE the SSH-user discrepancy with dev-build-host (a 2026-08-12 tip there says root also works; the 2026-08-13 smoke saw only ops)
  • tailscale-gcp-dev-box-migrationIN PROGRESS (decisions landed + VM provisioned 2026-06-30 via babylon #facekom_dev): mirror the FaceKom dev box on a GCP VM reachable over tailnet taild4189d.ts.net, replacing DuckDNS / public IP. Decision 1 — hostnames = Tailscale MagicDNS (<name>.taild4189d.ts.net), NOT facekomdev.net subdomains → public-DNS Phase 2a RETIRED. Decision 2 — routing = 8b multi-tailscaled sidecar per service (NOT port-based 8a): each of 9 services gets its own userspace tailscaled sidecar (env TS_AUTHKEY+TS_HOSTNAME, ~30 MB idle), own MagicDNS name, own tailscale cert; no app URL rework IFF sidecars named <prefix>-fk-dev to preserve <prefix>-<DEV_DOMAIN> with DEV_DOMAIN=fk-dev.taild4189d.ts.net (flagged to verify; pattern proven on pmv2-zurich). This reverses the note’s original host-level/sidecar-rejected recommendation. VM fk-dev provisioned by deploy (levandor-infra terraform module "vm"for_each=var.vms; pmv2 14 prod containers untouched): e2-standard-4 (4 vCPU/16 GB), 100 GB pd-balanced, europe-west6-a, VPC fk-dev-net/10.2.0.0/24, SA fk-dev-sa, tailnet IP 100.91.108.61, MagicDNS fk-dev.taild4189d.ts.net, ACL tag:cloud; public IP 34.158.19.122 egress-only, firewall denies all inbound except DERP, no public SSH; Docker 29.6.1 + OTel collector; ssh ops@fk-dev. Open (Andras/user): CV/GPU scope (VM non-GPU), toggle “HTTPS Certificates” ON in Tailscale admin (required before tailscale cert), whether deploy makes a FaceKom Artifact Registry namespace. 8b overlay IMPLEMENTED 2026-06-30 (BUILT + compose config-validated, unpushed, no commit on vuer_docker branch tailscale): tailscale.yml ships 8 userspace tailscale/tailscale:stable sidecars (network_mode host, TS_USERSPACE=true, --advertise-tags=tag:cloud, per-svc TS_HOSTNAME+TS_SERVE_CONFIG, DRY YAML anchors, named ts-state-* vols) + 5 app-svc stubs; tailscale/serve/*.json (8: oss/css/css-sdk-demo/esign-oss/esign-api/esign-css/portal/library, each HTTPS ${TS_CERT_DOMAIN}:443http://127.0.0.1:<port>, ports 20080/30080/30081/20180/20181/30180/30380/50080); tailscale/config/*-local.json (5: vuer_oss/vuer_css/esign_oss/esign_css/portal_css); tailscale/README.md; .gitignore+=tailscale/tailscale.env. KEY CORRECTION — “no app URL rework IFF naming preserved” was FALSE: apps compute separator = DEV_DOMAIN.endsWith('facekomdev.net') ? '-' : '.', so a tailnet DEV_DOMAIN flips to . → invalid dotted names like oss.fk-dev.taild4189d.ts.net (NOT MagicDNS-resolvable, NOT the sidecar name). Fix = zero app-repo edits, entirely in vuer_docker: host derivation guarded if (!config.X) + getconfig deep-merges config/local.json last (both verified empirically) → bind-mount a per-app config/local.json at /workspace/<app>/config/local.json setting hosts.* explicitly to <prefix>-fk-dev.taild4189d.ts.net (vuer_oss hosts.cv=null, +esign portal.url). Validated docker compose -f dev.yml -f vuer-oss.yml -f vuer-css.yml -f esign-oss.yml -f esign-css.yml -f portal-css.yml -f facekom-library.yml -f tailscale.yml config -q → EXIT 0. Networking: each sidecar host-net + userspace tailscaled, tailscale serve127.0.0.1:<app-port>, per-svc MagicDNS name + cert; nginx_proxy no longer the access path (harmless). Open (Andras/user): CV/GPU scope (VM non-GPU; hosts.cv=null), toggle “HTTPS Certificates” ON in Tailscale admin (required before cert issuance), whether deploy makes a FaceKom Artifact Registry namespace (none added — uses existing). Next (on user go): push → deploy wires onto fk-dev. Caveat: vuer_oss hosts.api=api-fk-dev has no sidecar but api- is only used by customization /external/createCustomerToken hostname-gating, not base dev flow. (Original source-verified analysis preserved in the note: dev.yml services network_mode: "host", nginx_proxy routes by subdomain PREFIX with domain-wildcard server_name, single self-signed cert, apps build URLs from DEV_DOMAIN; only literal duckdns = README.md:17.)

Generali

  • FKITDEV-9252“Bejelentkezés során jelentkező hiba” (parent ASSGRALI-67), a review request rather than a bug: Varga Vencel wrote the AD/LDAP failover fix and asked for a test round before merge. The fix targets devel, so it reaches Generali only via a devel update (FKITDEV-9194) — and needs no partner-side change, because Generali’s customization/ui/pages/login/login.trans.js override is an intentional no-op, so the new ldap_unavailable string surfaces as-is (checked specifically: a partner translation override is exactly where a new user-visible string normally goes missing). Test round complete 2026-09-07: no regressions on the unit suite plus a live fault-injection round that confirms the headline promise — first server retried 4× then abandoned, chain fails over, user authenticated on server 2
  • devel-update-and-release-flow — the Generali round generalised into the reusable flow: vuer_oss + vuer_css only, live line customization/generali-atvilagitas vs DEAD customization/generali-kar (2022-01-24, 1754 behind), fk-dev numbers base bfe85a4e68 9 failing tests → merged 3 (net −6, survivors converter + vuer-cv-service environmental — the latter hardcodes CI path /workspace/...), and the 1.9.11.19 release-ticket collection
  • FKITDEV-9194Generali devel update / release 1.9.11.19 prep (2026-08-08, parent ASSGRALI-72 whose changelog carried TODO: core update változtatások). Branch topology, the stale-devel near-miss, two semantic breaks, full local validation, and the pre-existing red audit gate. First vault coverage of the Generali devel-update line — nothing existed for FKITDEV-9073 either
  • ASSGRALI-63“Hang megszűnése videóhívásban Apple eszközváltás / iPhone feloldás során”; delivered by FKITDEV-8887 commit 6bdf66d16 and is the headline changelog item of release 1.9.11.19 — the commit the stale-devel merge almost dropped
  • FKITDEV-8533 — Generali videoOrientExt tablet fix (PR #7893, Changes Requested)
  • client-registry — Generali name glue: YouTrack GRALI (SLA/ASS) / GRALIA (CR/BUG) → build project generali-atvilagitas

Giro / girinfo

  • FKITDEV-8581CORRECTION 2026-08-05: the waiting task IS a genuine blocking GIRO gate (verified by direct code read on customization/raiffeisen @ 9fd6813dd2). The earlier draft reply — “elvileg meg nem érkező GIRO mellett is el lehetett jutni ‘Sikeres’-ig” — is RETRACTED; reporter Bihari Péter was essentially right. identificationStatus='verified' is written unconditionally (…flow.handler.js:240) but is unreachable without the gate: finish() (SelfServiceV2Service.js:408) needs serviceProgress==='wrapup' and has one caller (:549); waiting advances only from customization/listeners/self-service-v2.js:1655 (accepts resolved|rejected|cancelled, parks on falsy compareCustomerData()) or GirinfoService.js:90 (strictly resolved, fails the room on !acceptable); skip() throws (:843-846). ⇒ no girinfo = room PARKS on waiting and expires/fails. TERMINOLOGY TRAP (customization/portal/PortalData.trans.js): verified=“Ellenőrzés sikeres” (:277) vs finished=“Sikeres” (:286, later post-e-sign state) — two different enum values, which is the whole apparent contradiction. The 18 rooms (2025.07.02–2025.11.26): girinfo DID arrive; the logikai adategyezés is what never ran — pre-f830fd8e5a the gate advanced without calling compareCustomerData() (it sat on customer-portrait, usually running before GIRO returned); fixed by f830fd8e5a (FKITDEV-7667, m3szi), prod 2025-11-28, no new cases. waiting exists since proto v8 (94d3ee2df6, FKITDEV-1615, 2023-10-27) — the gap was the missing call, not a missing step. Three residual holes: (1) eMRTD fail-OPEN by constructionGirinfoService.js:339-343 returns true without comparing unless the eMRTD result is CHECK_SUCCESS, deliberate + unit-tested (GirinfoService.test.js:136-142), plus :335 skipCustomerDataComparisonCheck (true in config/dev.json); (2) gate ≠ room-page tile, OPPOSITE defaults — gate skips postal-code+city (:278-279, default true), tile checks them (self-service-checker.js:107,110, default false) and recomputes at read time ⇒ “Ellenőrzés sikeres” + “Logikai adategyezés: sikertelen” is a LEGITIMATE pairing; (3) GirinfoService.js:28 swallows the save error at debug and :69 is missing an await on findByPk (bare Promise always truthy ⇒ null-check never fires) ⇒ tile shows “Kérés folyamatban” while already resolved“Sikeres + Girinfo folyamatban” queries give FALSE POSITIVES
  • FKITDEV-8581GiroProcess.handleTask() in customization/server/backgroundProcess/giro.process.js (Raiffeisen, customization/raiffeisen branch only; renamed from giroService.process.js during the “Raiffeisen PIon project clean-up”) now splits the catch-block logging into “No response from girinfo service” (RequestError/ETIMEDOUT|ESOCKETTIMEDOUT|ECONNREFUSED|ECONNRESET|ENOTFOUND|EAI_AGAIN) vs “Bad response from girinfo service” (non-200/StatusCodeError/save failure, incl. statusCode); both add elapsedMs/requestTimeout/code. Log-only — retry (this.retry) unchanged. A heavier “mark bg-process/portal state after final no-response retry” option was DEFERRED (ties to the ambiguous-portal-state RCA — box “request in progress” vs dashboard “data arrived” vs logical-match “not available”). Original RCA fix f830fd8e5a shipped 2025-11-28; YouTrack still Pending

Git / orphan branches

  • esign-css-instacash-orphan-history — An orphan branch (customization/instacash in esign_css) breaks the conventional toolset: git merge halts on refusing to merge unrelated histories, git rev-list --count A..B returns numbers without meaning (no merge base), cherry-pick is fragile (different ancestor than the patch was authored against); the three operational responses are --allow-unrelated-histories (one-shot, recoverable), rebase-replay (matches historical pattern), or cherry-pick-delta-forward (cleanest narrative)

Git / commit + branch rulesets

  • techteamer-commit-message-ruleset — TechTeamer vuer repos enforce a repository ruleset on commit metadata: a push is rejected unless every commit subject matches ^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(!)?(\([^)]+\))?: [^\n]{1,100}. Two traps: a default git revert message (Revert "…") is rejected — use the revert: type (git revert -n <sha> + git commit -m 'revert: …'); and the subject after the type(scope): prefix is capped at 100 characters. A single bad subject anywhere in the pushed range rejects the whole push and the fix is a history rewrite — check the full range, not just HEAD. Same rulesets also enforce ticket-prefixed branch names (chore/FKITDEV-NNNN-…). Hit on FKITDEV-8387; branch-naming half first seen on nusz-1.9.11.47

  • techteamer-commit-message-rulesettrap 3: the branch name the rulesets REQUIRE produces a PR title the rulesets REJECT. GitHub derives the default PR title from the branch, so chore/FKITDEV-9197-cib-devel-update becomes Chore/fkitdev 9197 cib devel update — failing on both counts (no lowercase type: prefix; no bracketed lowercase ticket). Conforming form: chore: [fkitdev-9197] merge devel into customization/cib. This is structural, not occasional ⇒ checklist item: open the PR, then immediately fix the title (three needed it on FKITDEV-9197). Also gh pr edit resolves the repo from cwd — always pass -R org/repo, especially in multi-repo rounds with worktrees nested inside a repo

  • FKITDEV-9197git log -S does NOT follow renames, and in devel-update work that gives you a FALSE HISTORY. git log -S "status(400)" -- <post-rename path> returned nothing and was asserted as “400 never existed in this middleware”; the string had lived in the file under its old name and -S with a pathspec stops at the rename. Use git log --follow -S '<string>' -- <path>, or better git log -L <start>,<end>:<file> for a single line’s entire history across renames. Load-bearing because “was this test ever right?” is what decides whether you fix the test or the code, and devel merges are full of renames by construction

  • release-cut-mechanicsthe “team override-merges partner PRs” belief is imprecise: devel-update PRs land on customization/<partner> by SQUASH merge, not bypass. Read from the vuer_oss rulesets (all 4 bypass_actors: NONE): a preserving --merge drags bracket-less devel commits onto customization/** and the per-commit “Merge commit rules” pattern (bracketed lowercase ticket \[[a-z]+-\d+\]) rejects them with no bypass — so merge-commit style is structurally impossible. gh pr merge <n> -R <repo> --squash --admin collapses to ONE commit whose subject = the PR title, so the title must be compliant BEFORE merge (chore: [fkitdev-9217] nusz devel update); --admin skips only the RED status checks (Audit/Unused-Deps/Unit-Tests), never the message ruleset. Changelog = direct compliant commit (no require-PR rule on customization/<partner>) via gh api -X PUT …/contents/customization/RELEASE.MD; source tags via gh api …/git/refs (don’t build). Claude Code auto-mode classifier blocks --admin merges (user must run via !) but not gh api writes.

  • FKITDEV-8279refs/heads/devel falls under vuer_oss ruleset #1, the same bracketed-lowercase-ticket rule as customization/** (^(build|chore|…)(!)?(\(…\))?: \[[a-z]+-\d+\] …). Conventional-commit subjects satisfy ruleset #2 but not #1, so a branch of ordinary fix:/chore: commits must land as a SQUASH — and since the squash subject is the PR title, the title itself must read fix: [fkitdev-8279] …, bracketed and lowercase. Same shape as the customization/** squash rule under release-cut-mechanics, but for devel

Git / remote-tracking refs

  • narrowed-fetch-refspec-stale-devel-mergesome FaceKom clones have a narrowed remote.origin.fetch (vuer_css: +refs/heads/customization/raiffeisen:… — one branch; vuer_oss has the full +refs/heads/* and is fine). There, git fetch origin devel (bare branch name, no destination) writes only FETCH_HEAD, leaves refs/remotes/origin/devel untouched, prints success and exits 0 ⇒ a following git merge origin/devel merges a stale tree, conflict-free, with zero warning. Near-miss FKITDEV-9194: merged devel @ 7d4f9956c8 (Jul 28) vs live 3ace8872c3 (Aug 7), dropping 6 commits incl. 6bdf66d16 (FKITDEV-8887 / ASSGRALI-63) — the headline item of the release being prepared. Always git fetch origin '+refs/heads/<b>:refs/remotes/origin/<b>' then assert git rev-parse origin/<b> == git ls-remote origin refs/heads/<b>. Same narrowing is why --force-with-lease needs the explicit =<branch>:<oldsha> form (FKITDEV-9022)

HTTP client / fetch / mTLS

  • vuer-oss-global-fetch-ignores-agent-mtlsverified (Node v22.22.3 / bundled undici 6.24.1): every fetch() in vuer_oss is Node’s global fetch (no undici/node-fetch dep), which IGNORES the node-fetch-style agent: (honors only dispatcher) → the getHttpsAgent() idiom is a SILENT NO-OP (harmless only because cv.rejectUnauthorized defaults true); agent-based client-cert mTLS / rejectUnauthorized:false is dropped. FIX = undici’s own fetch + Agent (dispatcher: new Agent({ connect: { cert, key, ca, passphrase, rejectUnauthorized } })). CROSS-VERSION TRAP: standalone-undici 8.5.0 Agent into global fetch (undici 6.x) → UND_ERR_INVALID_ARG: invalid onRequestStart method; Facekom runs Node 22 and 24 (different bundled undici majors) so don’t pin standalone-undici to the bundled version. Surfaced scoping FKITDEV-8947 (UniCredit mTLS migration)
  • FKITDEV-8947 — migrate UniCredit customization/server/service/UniCredit/ApiService.js (customization/unicredit, UniCreditApiService) off request-promise-native → fetch; uses mTLS (cert/key/ca/passphrase from portal.api) so the naive agent: migration silently breaks TLS — must use undici.fetch+Agent per the gotcha note
  • FKITDEV-9197the first round to IMPLEMENT and PROVE the pattern rather than scope it (CIB, forced because devel retired request+request-promise-native out from under the partner branch): undici ^6.28.0 declared as a new DIRECT vuer_oss dependency (already transitive via cheerio, so a declaration not new weight; flagged reversible via node:https if the new direct dep is unwelcome), verified 11/11 against a real client-certificate HTTPS server incl. confirmed mTLS (clientCN: "client" observed server-side). The naive port’s failure mode is SILENCE — abandoned prior-art branches had ported 4 files to fetch while dropping agentOptions, which is how InfocertRestAPI passes {pfx, passphrase} at 6 sites: that port compiles, lints, and sends no client certificate, and nothing fails until the far end rejects the handshake. Their multipart port could not run at all (formData.getHeaders() does not exist on the web FormData; fs.createReadStream cannot be appended to one) — working shape is fs.openAsBlob() with fetch setting its own boundary

InstaCash

  • instacash-external-api-esign-headless-test-2026-06-01headless test path for the InstaCash external API + eSign via branch-only bin/instacash-cli.js (in the vuer_oss container): start-server mocks the bank /auth+/status (8189); every other cmd (post-application/get-invite/get-customer/post-contract/get-/revoke-contract) boots the full vuer_oss in-process and HTTPS-calls ${hosts.oss}/external/... (NOT a thin HTTP client). post-contract → ic-contract flow → EsignRPC → esign. The actual eSign signature is interactive and cannot be driven headless (manual video-ID + auth + sign at the inviteUrl). 2026-06-01 run: customerId 28, post-contract → HTTP 400 {"error":"Contract flow is in progress"} on a not-yet-identified application — open question vs developer-guide-hu.md (contract upload after identification)
  • dev-box-esign-container-startup-failures-2026-06-01 — surfaced while testing the InstaCash 2026-05-27 devel-update on the dev box: esign_css/esign_oss unhealthy due to the nginx non-root PID image regression (NOT the update code); also the post-dep-bump re-yarn install lesson (RedisStore is not a constructor if node_modules is stale) and a chalk ^5 ESM fix for yarn trans
  • dev-box-cv-photo-processing-failures — discovered testing the nrt self-service identification flow on the dev box: photo step fails (“error during photo processing”) when vuer_cv is stopped or the hairpin-NAT /etc/hosts fix is missing; recipe-oriented runbook (check vuer_cv running + hairpin remap, re-apply after restart)
  • sms-verification-code-dev-testing — discovered testing the NRT self-service identification flow on the dev box: how to get past SMS 2FA when the customer phone is fake (test.security.tempTokenSms fixed code + restart + resend, or read customer.getVerificationCode() via the model)
  • ASSICASH-71 — PROD vuer_css local.json portal.url UAT misconfig (FKITSYS-9486 fix 2026-01-06); pending log-volume confirmation; portal_css hosts.portal parallel risk
  • instacash-update-2026-05-27-status — 2026-05-27 devel→instacash sync wave across esign_css/vuer_oss/vuer_css; high-risk surface: vuer_oss customization/listeners/self-service-v2.js (FKITDEV-7518 id-card + newIdFormatAcceptance), vuer_css customization/customizations.js route reconciliation + modal a11y trio; 50 oss / 57 css commits in, esign_css blocked on orphan history; vuer_css server-side clean (no Express 5 risk here)
  • esign-css-instacash-orphan-history — InstaCash side of esign_css is structurally distinct: single squashed orphan, intentional MBH/MKB asset retention (55 devel-deleted-instacash-keeps), three workflow options for resyncing
  • FKITDEV-8817 — esign_css jQuery vuln fix on bugfix/FKITDEV-8817-jquery-update (commit b021019, pushed, PR not yet opened); flows into InstaCash via second-merge of 2026-05-27 sync once landed on devel
  • youtrack-tesztjegyzokonyv-attachment-recipe — InstaCash (ICASH) historically had NO release/eSign tesztjegyzőkönyv in YouTrack (the first was generated 2026-06-26 — see below; still not attached). In YouTrack today: ASSICASH-65 (FaceKom 1.9.11.50) / ASSICASH-92 (eSign 1.3.0.11) carry only build .logs; install tickets ASSICASH-66/62/67/93 (TESZT) / -96 (PROD) only screenshots. The only attached instacash “teszt jegyzőkönyv” PDFs are OLD compliance/DR docs: BUGICASH-460 (BCP teszt 2023-06) + ISSFK-338 (SaaS DR teszt 2021).
  • instacash-esign-1.3.0.11 — InstaCash eSign 1.3.0.11 release composition (esign_oss + esign_css; tag instacash-1.3.0.11; ASSICASH-92 release / ASSICASH-93 TESZT / ASSICASH-96 PROD approved 2026-06-26 from 1.3.0.8; FKITDEV-8817 jQuery CVEs + hardening; no migration, rollback = redeploy 1.3.0.8). First InstaCash release to get a TJK — generated via fk-tjk (instacash added to partners.json: display “InstaCash”, ASSICASH; 5 test cases → ~/Downloads/tesztjegyzokonyv_instacash_1.3.0.11.docx), not yet attached.
  • instacash-esign-dev-box-deploy — recipe to test InstaCash eSign on the dev box (fk-dev; the ssh Facekom box is decommissioned — dev-build-host): align the whole chain to InstaCash (esign → instacash-1.3.0.11, vuer → instacash-1.9.11.50, pdfservice stays main/2.0.12), git stash/fetch --tags/checkout per repo, rebuild in-container, supervisorctl restart all; verify supervisord RUNNING + the two startup log lines + esign_css :10183 200; gotchas: untracked .claude/ looks “dirty”, old log ERRORs may be historical (check timestamps)

iOS Safari / audio recovery

  • FKITDEV-8887root cause: iOS Safari suspends WebRTC audio on AVAudioSession interruption (lock→unlock / background); the remote <video> is paused and never resumed because there is no visibilitychange/pageshow/focus handler; Socket.IO connectionStateRecovery (30 s) masks brief backgrounds (no reconnect→reload), so only short SMS-code reads produce silence that persists. Fix (UNCOMMITTED, branch fix/FKITDEV-8887-ios-audio-resume): InterruptionRecovery controller wired via visibilitychange/pageshow/focus calls VideoFeed.ensurePlaying() (remote <video>.play() when paused) + VideoChatService.recoverAudioIfNeeded() (LocalMediaService.startLocalMedia({audio:true,video:false}) + replaceTrack). Critical gotcha: SenderPeer.pc is a Peer (WildEmitter) wrapper, NOT an RTCPeerConnection — the real connection is Peer.pc; calling this.pc.getSenders() on SenderPeer is always undefined → mic recovery must proxy through Peer.replaceAudioTrack(). Gate: isSafari (covers iPhone+iPad), not isIOS (iPadOS 13+ sends desktop UA → isIOS=false). 119 suites / 0 failures / lint clean.

  • FKITDEV-8887QA acceptance protocol (device repro): the only definitive acceptance gate (unit tests can’t confirm an iOS-runtime bug). Run baseline on origin/devel first (must stay silent) → then validate fix on fix/FKITDEV-8887-ios-audio-resume: iPhone Safari join → 2FA → lock ~10s to read SMS → unlock, expect audio both directions in ~1s. Evidence via allowlisted webrtclog: interruption:resumesenderPeer:audioRecovered {swapped:true} ({swapped:false}/:error = capture failure). Matrix: iPhone built-in+AirPods, iPad (Macintosh UA, validates non-isIOS gating), >30s background (crosses connectionStateRecovery), non-default mic survival (validates LocalMediaService.startLocalMedia path), Android/desktop regression, both directions. Version-risk settlement: pull userAgent for rooms 10071/10091 (ASSGRALI-63) + 2281 (ASSCIB-161/FKITDEV-8895) — iOS ≥16 weakens only the mic-interruption premise; playback + socket-mask hold regardless.

  • FKITDEV-8887polish pass 2026-06-15: mic recovery rerouted from raw getUserMediaLocalMediaService.startLocalMedia({ audio: true, video: false }) (respects saved device); localMedia wired in videochat.script.js; WebRTC test globals extracted to test/tests/unit/_helpers/webrtc-test-globals.js (must run at module top level, before describe + before require of SUT — config reads document.body.getAttribute at require-time); videochat.services.test.js updated to mock svc.localMedia.startLocalMedia + new undefined-guard case.

  • FKITDEV-8887SonarCloud cleanup 2026-06-18 (PR #3066): Quality Gate PASSED but 18 “New issues” (all code smells; 0 bugs/vulns; 89.2% new-code cov). Fixed the 13 in the PR’s new code (11× prefer-optional-chaining, 1× prefer-globalThis windowglobalThis, 1× cognitive-complexity recoverAudioIfNeeded 19→≤15 via _safeLog/_isAudioTrackDead/_applyRecoveredAudioTrack helpers — behavior-preserving, 119 suites/1045 tests + adversarial APPROVE). Left the 5 PRE-EXISTING promptUpload/validationResult smells (videochat.script.js ~L424–448, untouched per gh pr diff 3066) for a separate chore. See ## CI / build gates for the gh-check-run-annotations technique used to read the issues tokenless.

  • FKITDEV-8887devel catch-up merge for PR #3066 (2026-07-30): branch was 23 commits behind origin/devel; only yarn.lock conflicted (package.json auto-merged: devel resolutions + the branch’s browserify/shell-quote: ">=1.7.3"). Resolved by house precedent regenerate, don’t hand-mergegit checkout origin/devel -- yarn.lock + yarn install --ignore-scripts --non-interactive (Yarn Classic 1.22.22) — then verify by diffing the regenerated lock against the target branch: exactly 4 lines differ (shell-quote 1.9.0→1.10.0, key shell-quote@>=1.7.3, shell-quote@^1.6.1) ⇒ audit fix preserved, zero collateral churn; branch then differs from devel by exactly the PR’s 15 files. yarn lint clean; merge staged, NOT committed (analyze gate). Unit suite was initially blocked locally by jest30-ignore-optional-native-resolver (misdiagnosed on the day as a broken ts-jest publish — since fully retracted, it was a corrupt local Yarn cache); after adding the missing native resolver binding to node_modules it runs green against stock deps: 119 suites / 1051 passed / 47 skipped / 0 failures, with yarn.lock + package.json untouched. CI was green throughout (all 7 checks incl. Unit Tests) — the blocker was macOS-local only.

  • FKITDEV-9194where 6bdf66d16 ships: the iOS-Safari audio-recovery fix is the headline changelog item of Generali release 1.9.11.19 (ASSGRALI-63 / ASSGRALI-72) — and was silently dropped by the first vuer_css devel merge of the prep round because a narrowed fetch refspec left origin/devel 10 days stale (narrowed-fetch-refspec-stale-devel-merge). Caught before push

Networking / dev-box (hairpin NAT)

  • tailscale-gcp-dev-box-migrationDuckDNS → Tailscale GCP dev-box mirror over tailnet taild4189d.ts.net. IN PROGRESS (2026-06-30): MagicDNS hostnames + 8b per-service multi-tailscaled sidecar routing; VM fk-dev provisioned (tailnet 100.91.108.61). 8b overlay now BUILT + compose config-validated on vuer_docker branch tailscale (unpushed); key correction — “no app URL change” was FALSE (apps switch -. separator off tailnet DEV_DOMAIN), fixed entirely in vuer_docker via bind-mounted per-app config/local.json. See the ## GCP dev-box mirror / Tailscale topic for full detail
  • dev-build-hostSSH to the dev box (from Claude’s shell): command ssh ops@fk-dev.taild4189d.ts.net (Tailscale SSH, no keypair, no jump box), with a login shell (command ssh … bash -l -s <<'EOF' … EOF) so docker is on PATH. command ssh/command scp are mandatory — the user’s kaku alias (_kaku_wrapped_ssh) shadows plain ssh and is absent in non-interactive shells. The old two-hop topology (Facekom = lederera@localhost via ProxyJump FKJumpBoxroot@lederera-447-fk-hardver, jump box = bare Alpine, no docker) is DEAD — recorded for history in 5. SSH access to the dev box from Claude Code’s shell
  • dev-box-cv-photo-processing-failures — host-network containers (e.g. vuer_oss) reset at the TLS handshake when connecting to the box’s own public/LAN IP (192.168.1.93) — hairpin NAT; the container’s /etc/hosts maps all *-lederera.facekomdev.net to that LAN IP by default, so inter-service HTTPS (CV ping cv-lederera, bin/instacash-clioss-lederera/external/...) fails with read ECONNRESET / “Connection reset by peer”; fix = remap those names → 127.0.0.1 (loopback hits the same nginx, no hairpin); /etc/hosts is a Docker-regenerated single-file bind mount → edit wiped on every container start, re-apply after any restart via truncate+write (> /etc/hosts / base64 -d > /etc/hosts), NOT sed -i (fails Device or resource busy)
  • fk-dev-nusz-deploy-and-8959-verificationSSH topology of the new fk-dev GCP dev-mirror VM (distinct from the on-prem box’s ProxyJump topology above): reach it at command ssh ops@fk-dev.taild4189d.ts.net — user ops, Tailscale SSH (no keypair), tailnet taild4189d.ts.net / 100.91.108.61; the same kaku wrapper shadows plain ssh/scp in Claude’s shell → use command ssh / command scp. The box has no GitHub deploy key → forward the Mac’s id_ed25519 (ssh-add + command ssh -A) for git fetch. Per-service Tailscale sidecars (oss-/css-/esign-*/portal-fk-dev) are the access path; nginx_proxy crash-loops but is bypassed

NPM registry / package publishing

  • FKITDEV-9022 — publish 7 standalone @techteamer/* library repos (xlsx, timestamp_service, mq, video-processor, archiver-zip-encrypted, janus-api, acl — all on master, NOT the vuer monorepo) to the private registry https://npm.facekom.net/, so the GitHub source can later be made private. Mechanic (only org precedent = TechTeamer/amqplib-asyncapi-template, which declares publishConfig:{registry:"https://npm.facekom.net",access:"restricted"}): per-repo add that publishConfig to package.json + one .github/workflows/publish.yaml (push-to-master; secrets.FACEKOM_NPM_TOKEN~/.npmrc; npm view <name>@<version> --registry … guard = idempotent, publishes only new versions). publishConfig.registry only moves the publish target — the default registry stays npmjs, so installs/consumers are unaffected; no package renames. Decision (user, 2026-07-08): keep @techteamer/*, do NOT rename to @facekom/*timestamp_service is the sole special-case rename: @techteamer/timestamp-service + drop "private": true (both block publish). Correction: the “janus-sdk publish.yaml reference impl” was WRONG — TechTeamer/janus-sdk = 404; only the amqplib template references the registry. mq special case (release.config.mjs/semantic-release honors publishConfig.registry, but no release workflow wired yet). Phase 2 = take repos private / stop public publish — must come AFTER consumers (vuer_oss/vuer_css/portal_css, Yarn-Classic v1 + offline mirror) are repointed, or their CI/Docker installs break. Blocked on VPN: registry docs, CI token, org secret FACEKOM_NPM_TOKEN, real publish test. Status 2026-08-04: @techteamer/acl@2.0.2 PUBLISHED (first package on the registry, e2e-proven) and ALL 7 repos committed + PUSHED on chore/FKITDEV-9022-npm-facekom-publish (acl e16e1bd, janus-api 182dfc9, mq e044ce9, video-processor 39ef73d, xlsx c473103, archiver-zip-encrypted 67c012e, timestamp_service aff10e3), single-line message chore: [fkitdev-9022] publish to npm.facekom.net, solo-author, no auto-PR (compare links only). Auth solved by the htpasswd bot account techteamer-ci (own username lands in real_groups ⇒ no GitHub App needed for CI; mint tokens with --auth-type=legacy / basic-auth PUT, 90-day JWTs). ⚠️ Gotcha: fresh clones under facekom-v2-clones inherit the GLOBAL git identity (andras.lederer@alpiq.com) — first push carried the wrong author, fixed via --amend --reset-author + force-push; narrowed-refspec clones need the explicit --force-with-lease=<branch>:<oldsha> (plain form fails “stale info”). Remaining: runner-egress confirmation, PR review/merge, Phase 2

OCR / MRZ

  • FKITDEV-8788 — HU eID back side (MRZ TD1, classId 5, HUN-BO-06001_BACK_PO): FULL image scores MRZ valid_score:100 but the warped/cropped image of the same capture scores 2; prod rejects on the crop because getMrzRecognitionAttempts (SelfServiceV2Service.js:673-712) can feed the warped-document attempt to getMrzCheckResult (SelfServiceCheckerService.js:219-246); two CV paths — warp-first VuerCVOCRRecognition.js:45-99 (/api/v2/document-warp/api/v1/mrz on crop) vs no-warp MRZDetectionApi.js:22-38; validScoreValidator (:48-63) forces score 0 when detections.length !== 1; valid_score/mrzData only persist when ocr.useCVMRZData=true; CV 4.9.0 fix likely only validated on FULL image (triage open)

Oracle Instant Client

  • FKITDEV-8252 — Oracle has not published OL10 yum repos (404 across yum.oracle.com/repo/OracleLinux/OL10/); decision to use OL9 .el9 instantclient RPMs on UBI10 base for kh and bb partner Dockerfiles (Option 1 ship-it); memo at /Users/levander/coding/facekom/FKITDEV-8252-oracle-ol10-memo.md awaiting Bence sign-off and partner-contract escalation

Package renames / repo drift (UBI10)

  • FKITDEV-8252 — already-applied renames: pcre-devel→pcre2-devel, zlib-devel→zlib-ng-compat-devel, redis→valkey (with compat symlinks), coturn .el8 pin dropped → plain EPEL10 4.10.0-1.el10_3, rabbitmq /el/10/ empty → fallback /el/9/ 3.13.7 .el8.noarch, shadow-utils for groupadd/useradd, x86_64→$basearch in OL10 repos; A.6.1 additions: libopusopus/opus-devel, libmicrohttpd lives in EPEL10 not BaseOS, gzip missing from UBI10 minimal, GitHub archive URL strips v prefix (cd ${VAR#v}), git-lfs install --system must run before clone; on-probe-build watchlist for Phase B: ffmpeg-devel, libogg-devel, libconfig-devel, gtk-doc, jansson-devel, pkgconf, gengetopt, libsrtp2

Phantom room

  • FKITDEV-8787 — Raiffeisen Myra mobile self-service rooms with vestigial duplicates; SDK-local Already authorized / Already has some kind of room guards; OSS V2 SelfServiceV2Service.start() silently resumes any non-closed room; partial-unique-index gap
  • FKITDEV-8787FIX (2026-06-02, vuer_css fix/FKITDEV-8787-...): server-side self-heal in selfService:v2:start — if stale selfServiceRoomData, call OSS getRemainingSeconds(roomId) and delete only when < 1 (room positively expired); abort handler now clears state too. Design learning: FAIL CLOSED on ambiguity — first cut deleted state on ANY RPC error (fail-open), which would let a transient error on a LIVE room spawn a 2nd live room (the very duplicate bug, inverted); verified OSS returns 0 for timed-out rooms so the catch only sees truly-absent rooms → preserve state + log error there. RULE: for a duplicate/phantom-room-prevention guard, “RPC threw” ≠ “resource dead”
  • FKITDEV-8787RETRACTION (2026-08-11): both error strings are SERVER-SIDE in vuer_css, NOT in the FaceKom mobile SDK. EXCEPTIONS.ALREADY_AUTHORIZED = Already authorized and EXCEPTIONS.ALREADY_HAS_ROOM = Already has some kind of room are defined at server/socket/events/selfservice-v2.js:30-32, and the "data": {"error": …} envelope the partner pasted (which read like a mobile-SDK log) is createEndpoint’s own wire log via reportWireClientResponse — our own vuer_css log all along. Reusable lesson: on this platform “the server” is TWO repos — the original git grep correctly showed the strings are absent from vuer_oss, but “absent from OSS” was generalised to “absent from the server”, skipping the socket layer in the other repo. Knock-ons: the “mobile Restart must call clearSession()” recommendation was never the primary fix, and nothing was ever genuinely blocked on the Raiffeisen mobile team. Only half the bug is fixed: the shipped self-heal deliberately preserves customerId/authorization (test preserves customerId after abort (only roomData is cleared)), so a re-register/auth on the same socket can still throw Already authorized — that string in a future report is a different unaddressed path, not a regression of the ALREADY_HAS_ROOM fix. Shipped via vuer_css PR #3064, MERGED 2026-08-10 into the 1.9.11.100 release branch (selfservice-v2.js:303-322 self-heal, :575 abort clear) after #3050 and #3051 were both CLOSED UNMERGEDraiffeisen-1.9.11.100
  • raiffeisen-1.9.11.100-tjk-sectionshow the half-fix is worded to the customer. The Hungarian SLARAFIPI-60 TjK section states the self-heal only clears a stale room reference when the room is provably dead (remainingSeconds a valid number < 1), keeps state on any error/NaN/malformed response (fail-closed), and lists the two mobile scenarios (A expire-then-restart, B abort-then-restart) that only arm on socket reuse — closing and reopening the app opens a new socket and proves nothing. Server-side check: docker logs -f vuer_css | grep -i "self-heal". Its notes list records that Already authorized remains unaddressed by design

portal_css

  • portal_css — slim portal sister of vuer_css: registration, login, SCA, password recovery, JWT handoff (no Janus, no waiting-room)
  • ASSICASH-71hosts.portal config feeds CSP connect-src and PortalService.js:48 password-recovery email URL; empty default is a silent foot-gun
  • FKITDEV-8239 — first repo in the dependency-reduction initiative to get a depcheck CI job; branch chore/FKITDEV-8239-depcheck-ci (based on origin/devel), not yet committed
  • FKITDEV-9197the first documented portal_css devel update (CIB, release 1.9.11.102 ↔ portal tag cib-1.4.0.74 — the portal is on its own version train). 11 months of drift; two merge-caused breaks + two pre-existing devel defects. Two structural facts about this repo confirmed by that round: customization/cib had no .github/workflows/pull-request.yaml at all (so the branch had never been linted or audited by CI — the merge took it from 25 CRITICAL advisories to 0), and zero tests exist structurallytest/tests/ holds only a 0-byte .gitkeep. ⇒ green ≠ covered here, and multer is the proof: an unresolvable require in a live endpoint (customization/api/document-upload.js) with a fully green test job. Whether portal_css ships in CIB .102 at all is still open — ASSCIB-166’s Komponensek field lists only vuer_oss + vuer_css
  • portal-css-jest-runs-zero-teststhe repo has no unit-test signal: empty test/tests/ plus a custom sequencer with an empty CORE_TEST_ORDER allow-list that discards every file (and jest sorts before its “no tests found” check, so writing tests does not help). CI green only via --passWithNoTests in the workflow. Answers §17’s “which areas are actually exercised?” — none
  • devel-dependency-removal-breaks-partner-customization — devel’s 7a42894a “remove unused libs” dropped multer + uuid, both required by CIB customization/api/*; the FKITDEV-8239 depcheck job that motivates such commits is structurally blind to partner branches
  • eslint9-flat-config-dead-disable-directives — the eslint-9 flat-config migration in this repo (65ac214c feat: eslint 9 FKITDEV-6045) turns partner eslint-disable comments into --max-warnings 0 failures, and leaves a dead jest-formatting/padding-around-all rule reference behind

Queues / RabbitMQ (app-side)

  • vuer-oss-optional-queue-connectionvuer_oss supports multiple named MQ connections through @techteamer/mq’s ConnectionPool (named-map config shape); server.ts:465, background.ts:197 and bin/attachment.js:52 all read config.get('esign.queueConnection') then connectionPool.hasConnection(). So the optional-queue ECONNREFUSED swallow in server.ts (from FKITDEV-3191) is NOT dead code — it covers a supported partner setup where an external eSign RabbitMQ may be down at boot (without it, supervisord crash-loops a healthy service). It looks unreachable because no in-repo config sets optional — partner runtime configs live outside the repo tree. An attempt to delete it as dead code was reverted. Rule: when auditing “unused” config keys, grep the readers, not the setters
  • FKITDEV-9305-rca@techteamer/mq v7.2.0 RPCClient.initialize() asserts ONLY its reply queue (mq/src/RPCClient.ts:174), never its target — unlike QueueClient (mq/src/QueueClient.ts:34) ⇒ an RPC client’s target queue exists only while its worker runs, and ch.checkQueue() (amqplib passive queue.declare) against a missing one raises a channel-level not_found that kills the channel. vuer_oss/server/diagnostic.js runDiagnosticTick() then (a) swallows it so vuer_oss logs nothing while the broker spams every 5 s (diagnostic.rpcRoundTripIntervalMs, config/docker.json:736, started unconditionally at server.js:606), and (b) keeps using the dead channel, silently reporting {messageCount:0, consumerCount:0} for every queue iterated after it ⇒ QUEUE COUNT WARNING can never fire for background-recognition, rpc-esign:external, cronManager, rpc-xml-report, rpc-xlsx-report, rpc-transport-css, and any further missing queue is invisible. Also: an unreachable RPC target costs rpcTimeoutMs = 10000 per call (config/docker.json:688), and IntegrityCheckServices calls it in try AND finally ⇒ ~20 s and a finally-throw that overrides return false

Raiffeisen

  • face-comparison-data-verdict-threshold-model — FKITDEV-8827 (PION) face-comparison export design: PION is a videochat flow (comparisons keyed by roomId, produced by videochat:close hook, gated by faceRecognition.comparisonPairs), so the tool is a general both-paths export (raiffeisen-facecomparison-export.js + FaceComparisonExportService), not self-service-only; videochat rows get a report-computed verdict (no runtime classification) carrying raw distance + applied thresholds for audit; spec at .worktrees/vuer_oss-FKITDEV-8827/docs/superpowers/specs/2026-06-03-raiffeisen-facecomparison-export-design.md
  • FKITDEV-8581SLARAFIPI-53 correction 2026-08-05: retracted the claim that a room could reach a successful state without girinfo — the waiting task is a blocking GIRO gate, so no-girinfo rooms park and expire. The real 2025 defect was the logikai adategyezés never running (girinfo did arrive), fixed by f830fd8e5a (FKITDEV-7667, m3szi). Terminology trap that caused the dispute: verified=“Ellenőrzés sikeres” ≠ finished=“Sikeres” (post-e-sign). Reporter Bihari Péter. Corrected reply drafted at /Users/levander/coding/facekom/SLARAFIPI-53-reply-2.md; PR #7939 needs re-scoping (its giro-not-resolved case is redundant, and it inherits the eMRTD fail-open)
  • FKITDEV-8581 — girinfo no-response observability change on customization/raiffeisen: GiroProcess.handleTask() catch block split into distinct “no response” (timeout/network) vs “bad response” (non-200/save) logger.errors with elapsedMs/requestTimeout/code/statusCode; log-only (retry unchanged); heavier portal-state-marking option DEFERRED; SLARAFIPI-53 root cause = ambiguous portal states; original fix f830fd8e5a shipped 2025-11-28 but ticket still Pending
  • FKITDEV-8787 — Myra mobile KYC; customization/raiffeisen overrides; resolveExternalToken() reuses customer.id per offerId (mechanism for csökevény szoba); flow handler myra-self-service-v2-phase-1; m3szi owns prior fix (FKITDEV-7667 / SLARAFIPI-53)
  • FKITDEV-8788 — Raiffeisen PION HU-eID-back MRZ crop bug (SLARAFIPI-61, marked “Solved” prematurely on a CV 4.9.0 full-image test); CV/ML owner Zsolt Mészáros, coordinator Bence László, reporter Bihari Péter; mitigation ships on customization/raiffeisen
  • youtrack-tesztjegyzokonyv-attachment-recipe — Raiffeisen is the only client with an eSign-tied test record: BUGRAFIPI-512 “eSign 1.3.0.22 release” → …Raiffeisen esign - Tesztjegyzőkönyv - 22 Facekom Release v1.1.2.pdf (2024-10-24; 2/2 dev cases passed, executed Nagy Balázs 2024-10-16, components vuer_css/vuer_oss/esign_queue) + sibling BUGRAFIPI-516 (esign bizalmi szolgáltatás DR recovery TJK). The recent Tesztelési jegyzőkönyv PDFs (ASSRAFIPI-117 r1.9.11.94, -113 r93, -102 r92, 2026) are FaceKom/VUER OSS+CSS releases, NOT eSign. BUGRAFIPI-514 (FaceKom 1.9.11.61) has both .pdf + .docx variants.
  • raiffeisen-1.9.11.100first Raiffeisen release hub in the vault (ASSRAFIPI-135 / FKITDEV-9156). Release branch chore/FKITDEV-9156-raiffeisen-release-1.9.11.100 in BOTH repos (css 95b3e734…, oss 350d3e62…) cut from tag raiffeisen-1.9.11.99; .100 is NOT tagged ⇒ nothing publishable. Raiffeisen’s delivery route is release-branch-first: the customization/raiffeisen-targeted PRs 3051 were closed unmerged and both payloads landed via release-branch PRs (vuer_oss #7971 FKITDEV-8827, vuer_css #3064 FKITDEV-8787, merged 2026-08-10 nine minutes apart), with #7971’s base retargeted off the stale .95 release branch. ⚠️ Consequence: neither fix is on customization/raiffeisen or devel, so a branch cut off the partner branch silently loses both — “is it merged?” ≠ “is it on the partner branch?“. Changelog = 9 partner-side rows (ASSRAFIPI-124 CV doc-recognition v2→v3, -119, -18, CRRAFIPI-106, ASSRAFIPI-92 ImageId log, SLARAFIPI-59 girinfo, -60, -62 logikai adategyezés, CRRAFIPI-115 Liveness 2) of which only 2 have a traced FKITDEV+PR. All four YouTrack tickets stale (both FKITDEVs Pending/Review Needed; SLARAFIPI-60 + ASSRAFIPI-119 Blocked/Release needed) ⇒ they keep resurfacing as unshipped release scope. Bence Varga’s 2026-07-30 SLARAFIPI-60 question “melyik release-be tud ez belekerülni?” is still unanswered — answer is .100
  • raiffeisen-1.9.11.100-tjk-sections — the customer-facing Hungarian TjK sections for that release: ASSRAFIPI-119 (face-comparison export — why different_face is a read-time verdict, the raiffeisen-facecomparison-export.js CLI surface, 23/23 units + a real export CSV) and SLARAFIPI-60 (phantom-room self-heal — fail-CLOSED on an ambiguous getRemainingSeconds, 76/76 units incl. start → abort → start, the two mobile A/B scenarios that still need a real Myra client). Open items list flags the .100 draft’s own defects and the ALREADY_HAS_ROOM-only scope of the SLARAFIPI-60 fix
  • face-comparison-persistence-pathswhy Raiffeisen face-comparison data is shaped the way it is — CORRECTED 2026-09-07 (authority: SLARAFIPI-84). The old “myra runs liveness-check-v1, so compareFaceWith is inert there” framing is superseded: at tag raiffeisen-1.9.11.100 the myra phase-1 proto (version 16, taskCount 9) carries BOTH liveness steps — liveness-check-v1 at order 6 (:123-133) and liveness-check-v2 at order 7 (:134-145, screenshotCategory:'liveness-reference-face') — and exactly one survives per session via onBeforeCreateTaskslivenessCheckCompatibility(envData) against settings minAndroidVersionForLivenessCheckV2 (‘3.0.0’) / minIosVersionForLivenessCheckV2 (‘2.0.0’), which only runs in the envData.supportedSteps.length === 0 branch (otherwise the client’s declared supportedSteps decides). The operative reason neither branch persists is that NEITHER task has recognitionOptions at all while the v2 writer is gated on task.options?.recognitionOptions?.compareFaceWiththe fix is a customization PROTO change (add recognitionOptions.compareFaceWith to the already-present v2 task), NOT a flow migration and NOT core-handler work — cheaper than this note originally claimed. liveness-check-v2 only exists since 1c05206ccb (2026-08-17, FKITDEV-8595 / #7820, shipped in 1.9.11.100; absent from proto v12/v14/v15), and an export can never tell you which branch a session ran, because a v2 session produces no row either. Liveness distance is dropped by mergeRecognitions (copies imageId/status/score/attachmentId only) and survives solely via DebugLivenessTask.createLog:105 as Activity selfService:cvTask:log / CVTask:liveness, gated on raiffeisen.debug.cv || raiffeisen.debug.liveness (DebugLivenessTask.js:13-14) ⇒ full historical liveness coverage cannot be promised. Still true and unchanged: both sides of the persisted eMRTD↔selfie row are mislabelled customer-portrait; _isSameFace never sees per-room thresholds (passes no flow ⇒ falls back to the global Setting); and it carries the fail-open — now verified reachable via three paths and undisclosed to the customer. Raiffeisen’s perfect is in the repo after all: config/docker.json:59-61 = 0.55
  • SLARAFIPI-84Bihari Péter refuted two of our claims on 2026-09-04 and was right on both. (1) 0.55 IS the rejection boundary: raiffeisen.customerPortrait.threshold=0.55 in config/docker.json at tag raiffeisen-1.9.11.100 is applied to perfect, and the flow accepts only CHECK_SUCCESS (<= perfect) — so the 0.55–0.6 probable band exists in the export’s classifier but can never appear in it. Our earlier “the boundary is probable 0.6” was wrong. (2) Room 11651 was NOT a recognition failure: his own selfserviceroom-export-11651.zip shows five candidates on 2026-06-01 13:00:34–13:02:35 — 4 failed sharpness (37 and 33 vs min 40), #2 failed the geometry gate, and #3 and #5 RAN the comparison, scoring 0.7396 and 0.6507 with FACE_MISSMATCH:true (both selfie↔eMRTD chip; attachment 148700 is md5-identical to api/emrtd-photo/11651/8631/face, 240×320 PNG vs 1920×1080 JPEGs). Numeric scores ⇒ descriptors existed on both sides ⇒ pure threshold rejection. Then no-more-photo-candidate-allowed. The room export contains zero faceComparison rows and no faceRecognitions collection at all. Two threshold subtleties to remember: _isSameFace passes no flow to getFaceComparisonResult, so it always falls back to the live global setting while the export’s Threshold perfect column reads the room’s oldest selfService:v2:config:state — different sources; and migrateConfigState only back-fills missing keys, never overwrites, so an old room keeps an old threshold forever. Neither bit here (all 807 rows: perfect 0.55, probable 0.6, single distinct values). Delivered file profile: 807 rows, all myra-self-service-v2-phase-1 / self-service, Room ID null on all 807, distances 0.1311–0.5395, 11 rows in [0.50,0.55), status+verdict success on all, image-category pair ('customer-portrait','customer-portrait') ×807, room ids 11646–12847 with 395 (~33%) absent including 11651
  • SLARAFIPI-84ROUND 2 (2026-09-08): the reply we recorded as SENT was never posted, and 5 of our 9 posted claims regressed. The thread has 4 comments, the last being Bihari Péter’s 2026-09-04 refutation; ticket State = Blocked. All 19 claims were re-derived at ref raiffeisen-1.9.11.100 = 2352f5117f from a blob-hash-verified git archive (the vuer_oss-rel100 worktree is an ancestor of the tag by 6 commits), across four lenses and all 8 attachments. Decisive customer-checkable proof that the missing rows were never created rather than filtered: the delivered xlsx’s Face comparison ID column runs 9559 → 10365 with zero gaps (807 ids / 807 rows), and room 11651’s two comparisons (13:01:47 / 13:02:36) would have to sit between id 9559 (room 11646, 09:41:29) and 9560 (room 11652, 13:04:19). New runtime facts: raiffeisen.debug.cv and .liveness are true at the tag (config/docker.json:13-15) — nothing we need is gated off; springCloudConfigServer is configured in NEITHER docker.json NOR dev.json ⇒ the “second override path” caveat is retracted for this partner and config/local.json is the only in-repo mechanism; their effective config is docker.json + a host local.json, proved by a Sales Funnel cron no repo config enables; NODE_ENV was set (the csv failure itself proves it); DebugLivenessTask is v1-only (self-service-v2.js:1208,:1269) so v2 sessions log nothing; and the delivery path is UNVERIFIABLE from the repo (no Dockerfile in vuer_oss) ⇒ never repeat “Ehhez nem kell release”. Room 11651 precision fixes: window 13:00:34.680–13:02:36.286 (createdAtPrecise), sharpness 37/33 from cvTask:log … messages.details.sharpness, 148702 failed face detection too, comparison ran on 2 of 5 attempts. Export-side fix on fix/SLARAFIPI-84-facecomparison-export-rejected (worktree vuer_oss-SLARAFIPI-84, off the tag): 34/34 green, eslint clean, UNCOMMITTED. Round-2 Hungarian draft written but UNPOSTED — user decides. Quarantined from the thread: the _isSameFace missing-target fail-open (:338-367), the unguarded liveness-v2 writer, test.selfService.recognition.submitEnabled, and the release-state risk

Reports / SL export

  • FKITDEV-8639SL discrepancy between report UI and Excel export (NÚSZ). Two SL formulas coexist BY DESIGN: naive per-period round (CallsReportService.js:669-670) vs volume-weighted overall (:558-559, Simpson’s-paradox gap). PR #7862 (543f293c38, tag nusz-1.9.11.45) made the Excel report self-consistent/auditable (Sum value on every row) but did NOT make UI per-period == Excel weighted-overall; team standardised on the weighted overall as the headline SL. Per nusz-1.9.11.47 (ASSNUSZ-58 UAT), the client STILL reported the SL discrepancy after the fix (“Az SL eltérés itt is jelentkezett”) while the daily-stat discrepancy resolved. OPEN PRODUCT DECISION: UI == Excel exactly requires picking ONE formula everywhere — a product call, not a further bug fix; likely the live topic. Bug C (ReportsService.js:63 truelocale swallow) is DISTINCT from FKITDEV-8747 (forward locale through RPC queue boundary + empty SL when no calls, PR #7929, new for 1.9.11.48) — two locale bugs at two layers. Also Bug B (Excel column misalignment), Bug D (counting gap waiting_calls > calls + exits).
  • FKITDEV-8747the follow-up fix that shipped (PR #7929, squash 5099b8ad8b, merged 2026-05-28, nusz 1.9.11.48; parent ASSNUSZ-58). NOT a call-count bug — the raw counting logic is unchanged; all changed files are CORE (video-calls report / daily statistics), surfaced by NÚSZ. Three squashed fixes: (1) empty-period SL → null not 0 (CallsReportService.js ~L562: SL = calls>0 ? round((calls-lateAnswers)/calls*100) : null) on per-bucket and Sum/aggregate; client reportCalls.js renders null- (was always + '%', so 0% showed); (2) Sum-column SL aligned to the per-bucket rule — that mismatch was the reported “eltérés”; (3) locale plumbed end-to-end through the RPC queue (rpc_client/rpc_server Reports.js) + the new reporterDownload.process.js BackgroundProcess (a boolean true was passed instead of the locale string) → xlsx exports now use the user’s UI language, reviving the FKITDEV-8639 locale fix that was dead on the download path. Test cases: /Users/levander/coding/facekom/FKITDEV-8959-8747-test-cases.md (also covers FKITDEV-8959).
  • cib-reports-advanced-is-export-onlyCIB’s /reports-advanced renders a filter form and NOTHING else: no chart, no on-screen table — the xlsx export is the page’s only output. Source-verified in vuer_oss/customization/ui/pages/reports-advanced/: reports-advanced.template.twig renders only reports-advanced.form.twig (report type, time range, day/week/month/year pickers, prev/next, export button), and reports-advanced.script.js handleDownload() always sends download:'true' with a single success path window.location = /download/${res} — there is no branch that renders results into the page. Consequence: ASSCIB-166’s “a riport számai, a diagram adatai, valamint a XLSX export” names three places the FKITDEV-9230 async-filter bug surfaced in the computed data, all of which land in the workbook — NOT three UI surfaces; “a diagram adatai” is series data feeding a chart sheet in the exported file. So FKITDEV-9230 test evidence can only ever be the spreadsheet, never an application screenshot, and any request to “show the numbers on screen” for CIB is new feature work, not a fix
  • SLARAFIPI-84raiffeisen-facecomparison-export.js -c throws Unsupported report export format: csv purely because of a MISSING CONFIG KEY, and config/dev.json is a red herring. Under NODE_ENV=docker (set on all 7 supervisor programs) dev.json is never read; config/docker.json has a reporting block but no enabledExportFormats (keys present: debugger, extraFilters, lateAnswerTime, maxDateRange, sessionBasedCallsReportCalculation, videoCalls) ⇒ ReportsService falls back to ['xlsx']. A csv exporter genuinely exists; enabledCustomExports is also missing so comment_csv is unavailable too. Only fix path is config/local.json — env vars cannot do it, because getconfig only substitutes $VAR where the JSON already contains a placeholder and docker.json has none: {"reporting":{"enabledExportFormats":["xlsx","csv"]}} + restart. ⚠️ An EMPTY or malformed config/local.json kills the process at startup (SyntaxError in json file located at: … → process.exit(2)) — the file must be absent or valid JSON, and merged into rather than overwritten if it already exists (it is gitignored). Also: the bin writes with {mode: 0o600} and has no chmod; fs.writeFile applies mode only at creation, so an existing file keeps its old mode — and 0o640 is the right ask over 644 because -n/--withname decrypts customer names

Security / dependency CVEs

  • FKITDEV-8279two sequelize advisories against the @techteamer/sequelize fork (6.32.2): GHSA-v8fg-2rw7-q452 / CVE-2026-69240, SQL injection in the Oracle dialect only, critical CVSS 9.8, < 6.37.4, published 2026-08-03; and GHSA-6457-6jrx-69cr / CVE-2026-30951, SQLi via JSON cast type, high 7.5, <= 6.37.7, fixed only in 6.37.8 (source-confirmed in the fork, not just version-range inference: lib/sql-string.js:60-64 returns the attacker string unescaped for TO_TIMESTAMP/TO_DATE prefixes). The critical one hits exactly the Oracle partners (bb, kh, mkb-instant). TWO RETRACTIONS — do not repeat: the fork was never invisible to yarn audit (devel declares it through an npm alias, so Yarn v1 audits it under the key sequelize and submits 6.32.2 — both advisories were reported all along; the @techteamer/sequelize@6.32.2 → {} advisory-API result is real but is not what devel does), and no CVSS 9.8 sat unnoticed for two years — what sat for two years is the fork being abandoned, which is why there was no route to a fix. The checkable claim to use instead: improved-yarn-audit --min-severity critical is red on devel today (Found 1 vulnerabilities, exit 1) and green on the fix branch (exit 0); the gate lives at .github/workflows/pull-request.yaml:109-119 and is in the merge gate (needs: [lint, test, audit, sonar]), the yarn audit line above it deliberately swallows its exit code, and there is no .improved-yarn-audit-ignore in the repo. Generalisable lesson: an npm advisory query against the published package name says nothing about what your lockfile is audited as — run the audit on a genuine install of the branch in question
  • cve-2025-7783-form-data-via-requestCVE-2025-7783 / GHSA-fjxv-7rqg-78g4: critical form-data@2.3.3 (unsafe random multipart boundary; patched >=2.5.4) pulled in by EOL request@2.88.2 whose form-data: ~2.3.2 hard-pin will never move. Not on devel — customization-line only, because request is live partner code (vuer_oss customization/api/sms/SmsCofidis.js; vuer_css customization/server/web/api/{login,register,partner-register}.endpoint.js). Red on cofidis/kh/raiffeisen branches. Interim fix = resolutions override "request/form-data": "^2.5.6" + yarn install (house-style precedent: csurf/cookie, twig/minimatch, ts-jest/handlebars); real fix = drop request, separate ticket
  • security-audit — consolidated FaceKom vulnerability findings across vuer_oss / vuer_css / vuer_cv / esign / pdfservice / nyilvantarto-scraper (2 CRITICAL, 6 HIGH, 5 MEDIUM, 6 LOW + positive findings)
  • FKITDEV-9197a partner branch with NO CI workflow ships unmeasured criticals indefinitely: portal_css customization/cib carried no .github/workflows/pull-request.yaml at all, so nobody had ever audited it — 25 CRITICAL advisories (tar via semantic-release>npm, handlebars, twig>locutus ×2, browserify>shell-quote) measured on a base worktree at bf6dfbf8, all 25 cleared by the devel merge. Also the reverse security direction on the same round: devel’s removal of request + request-promise-native from vuer_oss was a deliberate retirement of the EOL client behind cve-2025-7783-form-data-via-request — CIB’s own security/*.md had logged it as scheduled monthly since 2025-03 — so the partner fix is to port the code off it, never to restore the dependency (devel-dependency-removal-breaks-partner-customization)

Self-service v2

  • face-comparison-data-verdict-threshold-model — self-service liveness-v2 face comparison (SelfServiceV2Service.js:1418) is gated by task.options.recognitionOptions.compareFaceWith (base V2 proto doesn’t set it → no row); self-service rows key by selfServiceRoomId, get per-room thresholds from the oldest selfService:v2:config:state activity (getActivityLog ASC [0], REPLACES the set) falling back to global Setting; verdict mirrors runtime getFaceComparisonResult (unlike videochat rows which have no runtime verdict)
  • dev-box-cv-photo-processing-failuresSelfServiceV2Service.photoCandidate (:1043) calls submitTaskRecognition unconditionallyFlowService.submitTaskRecognitionRecognitionService.runRecognitionsCVRecipe (server/cv/CVRecipe.js:88 throws “Recipe missing no CV Service!” when CV health-check marked the host down); there is no dev flag that skips face-detection recognition (selfService.ui.disabledChecks only covers girinfo/emrtd/kau), so an unreachable CV service hard-fails the nrt photo step
  • sms-verification-code-dev-testing — dev/testing: the self-service 2FA SMS code is set by the customer:verification:sendSms hook; force it with test.security.tempTokenSms (+restart+resend) or read customer.getVerificationCode() (stored as videochatToken)
  • FKITDEV-8787SelfServiceV2Service.start() silently resumes; _findOpenRoomForCustomer race; status enum ['waiting','incall','left','closed','deleted','archived'] — only last three treated as not-open; V1 throw at SelfServiceRoomService.js:217 swallowed by SelfServiceActions.js:27-34
  • FKITDEV-8787socket-layer fix (vuer_css server/socket/events/selfservice-v2.js): selfService:v2:start self-heals stale selfServiceRoomData via OSS getRemainingSeconds (delete iff < 1), selfService:v2:abort clears state after the OSS abort RPC; tests use REAL server/auth.js predicates via jest.requireActual (hand-rolled fakes had diverged — dropped the isAuthorized/customerId conjunct + collapsed the hasAnyRoom roomData branch)
  • FKITDEV-8581task-completion reachability rules (core, reusable beyond Raiffeisen): finish() (SelfServiceV2Service.js:408) returns early unless serviceProgress === 'wrapup' and has exactly one caller (:549), so a flow’s onFinished() is reachable only after the last task; skip() (:843-846) throws 'Current step is required!' unless task.options.step.required === false (strict inequality — a step proto with no required key is therefore NOT skippable). ⚠️ Bypass surfaces that call finishCurrentTask() with NO step-type guard: server/queue/rpc_server/AiActRPCServer.js:20 (registered at server.js:397, queue rpc-ai-act) and the isRecording-gated selfService:flow:finish at server/transport/session/SelfServiceTransportSession.js:290-300 — contrast GirinfoService.js:88, which does guard currentStep?.type !== 'waiting'. A late/duplicate RPC can therefore advance a room past a gating task (hypothesis, not reproduced). Raiffeisen flow proto is at v15 (ce08873962, FKITDEV-9081 AI Act, 2026-07-20)
  • FKITDEV-8788getMrzRecognitionAttempts (SelfServiceV2Service.js:673-712) selects between full-photo attempt (mrzTask.data.attachmentId) and warped-document attempt (candidate.document.attachmentId); getMrzCheckResult (SelfServiceCheckerService.js:219-246) turns recognitionAttempts[0].mrz.valid into accept/reject — fallback-to-full here is a candidate mitigation
  • FKITDEV-8787the socket-layer guards are in vuer_css, and only ONE of the two got fixed. server/socket/events/selfservice-v2.js:30-32 defines both EXCEPTIONS.ALREADY_AUTHORIZED and EXCEPTIONS.ALREADY_HAS_ROOM (an earlier note wrongly attributed them to the mobile SDK). The merged fix (PR #3064, 2026-08-10, :303-322 self-heal + :575 abort clear) addresses only ALREADY_HAS_ROOM — it clears client.sessionData.selfServiceRoomData but deliberately preserves customerId/authorization, pinned by the test preserves customerId after abort (only roomData is cleared). So selfService:v2:register on a socket that already authorized still throws Already authorized, by design. Whoever picks up the remaining half: clearing auth on abort is a much larger behavioural change, which is why it was scoped out — raiffeisen-1.9.11.100
  • face-comparison-persistence-pathsliveness step types are NOT interchangeable, but the type is NOT always what decides. task.options.step.type dispatches at server/queue/rpc_server/SelfServiceV2.js:208-219 into three handlers: liveness-checkhandleLivenessCheck:1199, liveness-check-v1handleLivenessCheckV1:1459 (→ mergeRecognitions:1310), liveness-check-v2handleLivenessCheckV2:2114 (→ saveLivenessCheckV2Messages:1349). Only the v2 handler contains face-comparison persistence, so recognitionOptions.compareFaceWith is read only on v2 and is silently inert on the other two. Corrects the “liveness-v2 is gated by compareFaceWith” line in face-comparison-data-verdict-threshold-model, which omitted that the gate presupposes the v2 handler. Refined 2026-09-07 (SLARAFIPI-84): a proto can carry BOTH liveness steps and pick one per session — myra does, filtering in onBeforeCreateTasks via livenessCheckCompatibility(envData) only when envData.supportedSteps.length === 0 — so “which handler runs” is a per-session runtime property you cannot read off the proto, and when neither task declares recognitionOptions (myra’s case) the handler question is moot: no branch persists. Check the proto for recognitionOptions first. Also: the core FlowService.handleTaskRecognitionOptions:2918-2968 path has five distinct guard failures that all produce no row, and only two of them log anything
  • SLARAFIPI-84CORRECTS the “myra runs liveness-check-v1” framing. At tag raiffeisen-1.9.11.100 the myra proto carries BOTH liveness steps and onBeforeCreateTasks picks one per session via livenessCheckCompatibility(envData) against settings minAndroidVersionForLivenessCheckV2 (default '3.0.0') / minIosVersionForLivenessCheckV2 ('2.0.0') — but that filter only runs when envData.supportedSteps.length === 0, otherwise the client-declared supportedSteps decides. Decisive: NEITHER liveness task has recognitionOptions in the proto, and v2 persistence is gated on task.options?.recognitionOptions?.compareFaceWithneither v1 nor v2 writes a liveness faceComparison for myra, and the cheap fix is a proto change (add recognitionOptions.compareFaceWith to the v2 task), not core code. liveness-check-v2 was only added 2026-08-17 by 1c05206ccb (“feat: [fkitdev-8595] raiffeisen liveness v2 (#7820)”), verified absent from proto v12/v14/v15 — the customer’s window ends 2026-08-26, so a tail of sessions could have been v2 and the export cannot tell (⇒ do not claim “every session used v1”). The liveness distance never reaches task.datamergeRecognitions copies imageId/status/score/attachmentId and drops distance; it lands in the DB only via DebugLivenessTask.createLog (Activity selfService:cvTask:log, type CVTask:liveness) behind raiffeisen.debug.cv || raiffeisen.debug.livenessfull retroactive liveness coverage cannot be promised

Sockets / reconnect + auth

  • FKITDEV-8931MKB DÁP reload bug: mobile backgrounding drops the socket and the old vuer_css client answered the reconnect with window.location.reload() — fatal because DÁP login REQUIRES backgrounding (switch to the DÁP app), so the reload fired on the happy path. Fix = silent auth(socketLabel) re-auth logging [socket] re-authenticated after reconnect, falling back to reload only on failure, plus a connection.on('connect')onConnectionRestored() that clears the 2s disconnectTimeout + reloadCountdownInterval and hides both snackbars. Gated on SILENT_REAUTH_LABELS = ['default.layout']kiosk.layout and videochat still hard-reload by design, so kiosk-heavy customization/mkb-instant pages are the WRONG test target (use mbh-services, which extends default.layout). BROWSER-TESTED 2026-08-18 — short drop passes (~1.4s → silent re-auth, no reload, snackbars hidden), but a long outage does NOT: the fix only cancels a pre-existing reload countdown, never removes it, so a 40s outage hard-reloaded the page 4× against a 502. onConnectionDisconnected() fires 2s after disconnect and counts down at 1s intervals to window.location.reload(); the length is hideDelay: 10 + random(50) (snackbar-container.twig:6) ⇒ randomized 10–60s per page load, effective tolerance ≈12–62s ⇒ the DÁP flow still breaks if the user lingers in the DÁP app, and it reproduces INTERMITTENTLY — the profile of a bug closed as “works for me”. Open question for vencelvarga: suppress or extend the countdown for SILENT_REAUTH_LABELS pages rather than merely cancelling it. Three config values corrected by the test (live data-socketioSettings beats the dev.json reading): transports is ["websocket","polling"] (polling IS available, not websocket-only), reconnectionAttempts is 20 not 5, and connectionStateRecovery NEVER ENGAGES (socket.recovered === false on every reconnect, incl. a 1.4s one; fresh socket id each time) → do not test against the 30s cliff, it is not the boundary. vuer_css only, PR #3153 approved/7-of-7/mergeable. Do NOT cherry-pick the FKITDEV-9199 kebab fix cda6c80b97 (PR #3146) onto the branch — it is internally consistent under the older data-socketToken + case-insensitive getAttribute convention, and kebab twigs without the dataset.socketToken readers break it
  • FKITDEV-9194 — the socket-token attribute convention and the superseded fix/FKITDEV-9194-socket-token-attribute-read branch that would re-break the bug if merged on top
  • FKITDEV-8787 — stale socket selfServiceRoomData surviving an in-app Restart; fail-closed self-heal

Supervisor / process config

  • unversioned-partner-supervisor-overlaysRELEASE BLOCKER pattern: partner supervisor overlays are NOT version-pinned to the app. vuer_build/partner/<client>/vuer_oss/Dockerfile does FROM harbor…/vuer_oss:${VUER_VERSION}… (version-pinned app image) then COPY supervisor_vuer_oss_docker.conf (unversioned, taken from main at build time) → conf and app version are decoupled, so a rebuild of an old release tag gets the old app with today’s conf. Consequences of changing a command= line: (a) older release tags stop rebuilding (MODULE_NOT_FOUND → supervisord crash-loop), (b) ~94 origin/customization/* branches still carry the old spelling so each partner breaks on its next build until it merges devel (time-staggered, looks like a random partner regression). Not fixable by merge ordering — it’s a versioning mismatch. Mitigations: hold the vuer_build/vuer-release merges until the first release tag containing the change is cut; or delete redundant overlay confs so partners inherit the base image’s symlink to the app’s own conf (decision rule in FKITDEV-8354-mvm-supervisor-config-dedup); or make overlays release-aware. Merge hazards: customization/kh edits both confs (conflicts), customization/nusz edits server.js+cron.js (rename+modify). Surfaced by FKITDEV-8387
  • FKITDEV-8354-mvm-supervisor-config-deduphow supervisor configs reach /etc/supervisor/conf.d/ in vuer-release component images (reusable): TWO paths — (a) base install/configure-app.sh:16-21 symlinks the source package’s supervisor*.conf, (b) the partner component Dockerfile COPYs a partner override on top (last-write-wins by filename); supervisord.conf includes files = …/conf.d/*.conf. Decision rule: a partner override is a deletable duplicate only if byte-identical to the repo’s conf, else it’s intentional customization. Applied to PR #28 (MVM): vuer_css override = byte-identical ⇒ deleted; vuer_oss override = kept (supervisor-stdout eventlistener logging stdout_events_enabled=true/stdout_logfile=NONE, 8 programs, omits [program:vuer_oss_storage] — consistent across all vuer-release partners: equilor/nusz/polgaribank-facekom/unicredit/unicredit-srb/mvm). The eventlistener apparatus (supervisor_stdout.py+supervisor_stdout_eventlistener.conf+pip install supervisor-stdout) lives ONLY in vuer-release and is the consumer of the supervisor-stdout plugin restored in PR #28 round-1 (07db225)
  • FKITDEV-8252supervisord runtime gotchas on ubi10-minimal (build-green ≠ runs): supervisor 4.2.5 crashes on Py3.12 (pkg_resources gone) → pin 4.3.0; supervisord logfile path hidden by a /var/log bind-mount → log to /var/log root; supervisord (PID1) does NOT propagate a program’s HOME nor does USER set it (erlang .erlang.cookie eacces → set environment=HOME=… per program); must run supervisord as ROOT (removed wrong USER $DOCKER_USER from vuer_css/portal_css)
  • entrypoint-rename-blast-radius — renaming any vuer entrypoint (server.js/cron.js/background.js/…) is a 4-repo coordinated release, not a single-repo edit: 95 supervisor conf files hard-code command=node <entry>.js75 in vuer_build/partner/* (35 partners), 11 in vuer-release/projects/*/components/*, 2 in vuer_docker/workspace/devtools/files/, only 7 in the three code repos (vuer_oss/vuer_css/portal_css). Two SILENT traps beyond the confs: vuer_oss/server/logger.js:102–114 picks the log4js channel by sniffing process.argv[1].endsWith('server.js')/etc. (rename → every process logs to the unknown channel, no crash), and an 8th entrypoint soap_server.js exists ONLY on bb/kh customization branches (…/{bb,kh}/vuer_oss/supervisor_vuer_oss_docker.conf:137, not on devel). A missed conf = supervisord crash-loop (exit 2, restart 1–2 s; a March-2026 attempt did exactly this). Motivates FKITDEV-8387’s .ts-module + one-line .js-shim approach (keeps every conf/logger.js/.nycrc filename literal untouched)
  • FKITDEV-9305-rcaa missing [program:] block in a partner overlay is a silent production defect, and the overlay mechanism is TWO SEQUENCED IMAGE BUILDS, not layer ordering. The base image links the canonical conf into conf.d/ (vuer-release: install/configure-app.sh:16-19 from base/components/vuer_oss/Dockerfile:106; legacy vuer_build: base/vuer_oss/Dockerfile:241), is tagged and pushed to harbor, and the partner image is built FROM that sealed base and COPYs its own file to the same path (projects/mkb-instant/components/vuer_oss/Dockerfile:1-2COPY :18; legacy partner/mkb-instant/vuer_oss/Dockerfile:6COPY :31, plus sed -i "/user=/d" … at :25) — the COPY structurally cannot lose. Legacy partner selection comes from the git tag (build.sh:365 strips mkb-instant-1.9.11.67mkb-instant, then sources settings.cfg). MKB’s overlay carries 7 programs vs canonical 9 (vuer_integration_log line 50, vuer_oss_storage line 155) ⇒ RabbitMQ not_found spam every 5 s plus six queues silently zeroed in monitoring, running continuously since the .54 tag of 2025-07-01 (~14 months). (Device-integrity breakage is a conditional extra — gated behind integrityCheck.*.enable, which is absent from MKB’s committed docker config.) Fleet scope: 32 of 35 omit vuer_integration_log; only granit, mvm, vkta keep it — undocumented, unlike the deliberate vuer_oss_storage convention, and MVM keeps integration-log while dropping storage ⇒ drift, not design. DO NOT copy the mbh delete-the-overlay fix (FKITDEV-8362, projects/mbh conf deleted in #35 / d7ebb37): MKB’s overlay is not “canonical minus 2 blocks” — every functional directive in the 7 shared blocks is identical (command, directory, environment, process_name, numprocs, umask, priority, autostart, autorestart, startsecs, exitcodes, stopsignal, stopwaitsecs), and the only deltas are dropped user=techteamer and file-logging → stdout_events_enabled=true / stderr_events_enabled=true / stdout_logfile=NONE / stderr_logfile=NONE, i.e. the supervisor-stdout eventlistener wiring that feeds OpenShift log collection — deleting it would break MKB’s logging. Correct fix = add one block in the overlay’s own logging style, in vuer-release (MKB migrated 2026-07-08, 4bf534e), not legacy vuer_build. Detection trick: that same wiring produces the INFO <program> | … prefixes, so grepping a customer’s log export for INFO vuer_integration_log | proves the worker’s presence/absence without shell access

Test reports / Tesztelési jegyzőkönyv (TjK)

  • tesztjegyzokonyv-generation-flowSTART HERE for “how do I make a TjK”. The /fk-tjk process: zero-context Phases 0–7, the newdoc + append two-step, evidence regeneration, and the Phase 6 tester runbook. Release trains are per partner — never carry a version in from conversation.
  • tesztjegyzokonyv-partner-release-document-structure — the document spec (release-doc shape): skeleton, Bevezetés block, per-ticket sections, the three evidence forms, and the closing telepítésre ajánlott paragraph that is the formal pass/fail statement to the bank — now reproduced VERBATIM in the note (<Partner> twice; keep the double space, the megvalósítható("…") spacing and the a fejlesztői, tesztek comma). Single source since 2026-08-11 — the on-disk docs/tjk-raiffeisen-document-structure.md was folded in and retired.
  • tesztjegyzokonyv-primer-prompt — the copy-paste primer: /fk-tjk <partner> for the loaded command, plus a verbatim Hungarian standalone prompt for a fresh session / another agent / another tool (zero-context rule, ~/Downloads base by NAME, regenerate evidence, two-step render, runbook, partner-side ids only). Was docs/tjk-primer-prompt.md on disk until 2026-08-11.
  • raiffeisen-1.9.11.100-tjk-sections — a worked example of finished output: the Hungarian ASSRAFIPI-119 + SLARAFIPI-60 sections with their evidence dumps, plus the open-items list. Carries the scope caveat that belongs in the section text: the SLARAFIPI-60 fix covers ALREADY_HAS_ROOM only, Already authorized is deliberately unfixed.
  • devel-update-and-release-flow → Phase 3where the TjK’s section list comes from. release_tickets.py emits the two ticket buckets (direct-on-customization vs via-devel-touching-customization/) with assignee and commit author, so you know who to ask for the functional description of each section. Remember to convert to partner-side ticket ids before drafting — FKITDEV-* never appears in a customer document.
  • youtrack-tesztjegyzokonyv-attachment-recipe — finding/downloading existing TjKs from YouTrack; the Phase 1 fallback when ~/Downloads has no TjK.
  • nusz-1.9.11.48-test-runbook — a real Phase 6 output: prioritized cases, arming conditions, and what UAT explicitly cannot prove.
  • Cross-cutting trap: the TOC is a static Word field. Rebasing one partner’s TjK onto another leaks the source’s ticket ids (ASSRAFIPI-124/SLARAFIPI-59 reached a NÚSZ TOC) — invisible in the body. append_release_sections.py newdoc clears the cache and refuses to write on a surviving id; a human must still refresh the TOC before PDF export.

TypeScript

  • k6-e2e-harness-vuer-osstest/tests/k6/tsconfig.json is a second, independent tsconfig (types:["k6"], strict, allowImportingTsExtensions, noEmit) that the root tsconfig knows nothing about (root does not include test/). Consequence for porting: relative imports inside test/tests/k6/ carry an explicit .ts extension — same rule as the runtime one in typescript-in-vuer-repos, but here it comes from allowImportingTsExtensions rather than Node’s resolver. Nothing typechecks it; run tsc -p test/tests/k6/tsconfig.json by hand
  • FKITDEV-8387SUPERSEDES the shim plan: the entrypoint migration landed as a DIRECT RENAME (2026-07-22). After reviewer feedback on vuer_oss PR #8059 the .js-shim strategy was dropped — entrypoints are real .ts files invoked as command=node server.ts, no shims: vuer_oss 7 entrypoints, vuer_css + portal_css 1 each, 75 supervisor confs in vuer_build, 12 in vuer-release; all three code PRs CI-green 8/8. Two gotchas worth remembering: the soap_server.js suffix trap ('soap_server.js'.endsWith('server.js') === true, so bb/kh’s SOAP entrypoint was silently inheriting the vuer log4js channel; the rename dropped it to unknown with no error → endsWith('server.ts') || endsWith('soap_server.js'); lesson: suffix-matching entrypoint dispatch is fragile under rename), and the still-open release blocker in unversioned-partner-supervisor-overlays
  • typescript-in-vuer-reposCORRECTION (2026-07-22): the unflagged type-stripping floor is Node 22.18, NOT 22.6 — 22.6 required --experimental-strip-types, and no supervisor command= passes node flags. Verified empirically: node:22.6 on a .ts entrypoint → SyntaxError: Missing initializer in const declaration (parsing TS as JS); node:22.18 runs it. Version surface: engines >=22.18.0 in all three repos, images/CI (vuer_build, vuer_docker) install 24.x, but vuer-release pins a floating NODE_VERSION: 22 — above the floor today, not pinned there
  • typescript-in-vuer-reposhow TypeScript actually works in vuer_oss/vuer_css/portal_css (established by FKITDEV-8246 “TS Magic”, vuer_oss PR #7645 55035572bb; foundation for the FKITDEV-8251 epic): NO build step (tsconfig noEmit:true+erasableSyntaxOnly:true, nothing runs tsc, no typecheck job in CI in any of the three repos — TS is editor/ESLint-only, type errors don’t fail CI), Node ≥ 22.18 strips types natively at runtime, files stay CommonJS (no "type":"module" anywhere), a cross-module require of a .ts module needs an explicit .ts extension (require('../util/magic.ts'); extensionless → MODULE_NOT_FOUND), root-level *.ts is SILENTLY UNLINTED (ESLint TS block files:['server/**/*.ts','customization/**/*.ts','client/**/*.ts'] → a root server.ts gets “File ignored because no matching configuration was supplied”; tsconfig include has the same blind spot), Jest transforms .ts via @swc/jest (vuer_oss) / ts-jest (vuer_css, portal_css), @typescript-eslint/no-explicit-any is an ERROR (never fix a type error with any), and require('node:module').stripTypeScriptTypes(src) cheaply asserts a file is erasable-syntax clean
  • nyc-cannot-load-typescriptGOTCHA: nyc (v18) cannot load .ts at all — it hijacks the .ts extension handler (append-transformdefault-require-extensions/js.js) and compiles TypeScript as raw JavaScript → SyntaxError: Unexpected token ':'; neither --extension=.ts nor --include '**/*.ts' helps (Node’s runtime type-stripping is bypassed by nyc’s require hook). Consequence: vuer_oss/supervisor_vuer_oss_e2e_test.conf runs npx nyc node <entry>.js for all 7 entrypoints and server.js already requires six .ts services at boot (server.js:48,83,99,108,109,110) → the conf has been broken since FKITDEV-8246 “TS Magic” landed (Jan 2026); nothing outside the git index references it (no CI job, no Docker repo) so it went unnoticed. Fix = swap nycc8 (V8 coverage, no require hook) or retire the conf; warrants its own YouTrack ticket. First hit on FKITDEV-8387
  • FKITDEV-8387 — Task 6 implementation, vuer_css server.js shim: second repo in the multi-repo .ts-shim rollout, single entrypoint. vuer_css/server/logger.js uses a static log channel list, unlike vuer_oss’s process.argv[1]-sniffing logger — no logger change needed, a repo-to-repo gotcha worth checking per repo. 4 files (eslint.config.mjs/tsconfig.json glob widen, server.ts <void> Promise-type fix, bin/server/server.task.js watch list); tsc/eslint/yarn lint exit 0, 115/115 suites (1064 tests) pass; commit b6513dc0 on chore/FKITDEV-8387-ts-entrypoints, local-only

Validation / log analysis

  • ASSICASH-71 — 2026-05-18 validation: PROD + 2 UAT log pulls (~1.1M lines total) confirm CSP-channel flood is gone; FKITSYS-9486 holding (0 ohp-uat.mbhbank.hu refs in PROD); both UATs silent for 12-19 months; status moved to validated

vuer_cv

  • dev-box-cv-photo-processing-failures — CV runs in its own container vuer_cv (image harbor.techteamer.com/facekom-devel/vuer_cv:4.6.2.DEV-...); when stopped, nginx returns 502 for https://cv-lederera.facekomdev.net and vuer_oss logs CV server is down; docker start vuer_cv boots it under supervisord (nginx/redis/CV proc/~10 workers) to Up (healthy) in ~2 min (loopback curl flips 502→404); but it can still be unreachable from vuer_oss due to hairpin NAT (see topic above)
  • FKITDEV-8252in scope for FKITDEV-8252 (Q3 resolved by execution); new base/vuer_cv/Dockerfile UBI10 base, probe-builds green at 5.93 GB (iter 7); needs EPEL10 for libmicrohttpd, git-lfs install --system before clone, ENV_VERSION=8 matching config/docker.json requiredEnvVersion; size-reduction (multi-stage drop of git-lfs/gcc-c++/python3-devel) flagged as follow-up; cleanup microdnf remove --allowerasing cascade through git-core deps worth a sanity audit

WebRTC / video orientation

  • fk-dev-deploy-smoke-runbookJanus / media-server fix (verified 2026-08-13): browser “media server connection errored” = the janus container was supervisord-FATAL since first boot because janus_websockets couldn’t create the wss vhost (in-image libwebsockets built without working TLS, even after adding certs). Key architecture fact that dissolves it: the browser NEVER connects to Janus — signaling is server-to-server (browser → vuer_css Socket.IO(wss) → RabbitMQ → vuer_oss → Janus over PLAIN ws, @techteamer/janus-api isomorphic-ws), media flows browser↔operator via TURN/coturn (turnserver.facekomtest.net). So plain ws://localhost:8188 is correct for the oss→Janus hop; no wss needed. Fix = enable ws in janus.transport.websockets.jcfg in-place inside the container (it’s a bind mount; host sed -i changes the inode and is ignored) + repoint vuer_oss/config/dev.json webrtc.janusServers.janus urlws://localhost:8188 / adminUrlws://localhost:7188 (keep adminSecret: janusoverlord). vuer_oss is network_mode: host so localhost (not janus:). Media still needs TURN reachable
  • fk-dev-deploy-smoke-runbookVideochat smoke gotchas AFTER signaling is fixed (verified 2026-08-13, first confirmed GREEN video on fk-dev — NÚSZ 1.9.11.48, flow “Sikeres”, both operator+customer video rendered): (1) a live call won’t record with mkdir (/workspace/records/<roomId>/) error: 13 (Permission denied) — the janus PROCESS runs as non-root (techtea…) but /workspace/records was root:root 0755; fix docker exec -u 0:0 janus … chmod -R 0777 /workspace/records (dir = webrtc.janusConfig.recordDirectory); (2) “Taking a while to load” = slow ICE gathering, NOT a hang[WARN] Waiting for candidates-done callback…, the call DOES connect (DTLS handshake completed follows), optional speedup full_trickle=true and/or drop Janus’s own STUN in janus.jcfg; TURN turnserver.facekomtest.net:3478 reachable, a raw TCP connect returning ECONNRESET is NORMAL (TURN isn’t plain TCP) — port-open is the signal; (3) resetting oss operator passwords — login is BY USERNAME (WebServerAuth.js, POST /login, plaintext, no 2FA), bcrypt worker is plain bcryptjs (no pepper) so bcrypt.hash(pw,10) verifies; update users set "password"=<hash>,"passwordExpiry"=<future> where username in (…); table uses isEnabled (not isActive) + passwordExpiry (rejected if < now); success = 302 → / + vuersid cookie; browser autofill of a stale saved password is a common false alarm
  • FKITDEV-8533videoOrientExt (the urn:3gpp:video-orientation RTP header extension) lets the receiver correct rotated video; gated at 4 sites — server/cv/VuerCVListenerSession.js, server/socket/events/videochat.js, server/transport/session/RoomTransportSession.js, server/transport/session/SelfServiceTransportSession.js (keep in sync). RESOLVED 2026-06-23 via “Option A — un-gate CVO for browsers” (branch fix/FKITDEV-8533-videoorient-ungate, commit d27d4cc990): restored the gate’s 2017 intent — shared helper server/transport/videoOrientExt.js videoOrientExtEnabled(customer) = !customer.isNativeApp() (null→true) enables CVO for ALL browsers, disables it ONLY for the native SDK (userAgent.startsWith('mobile/')). Supersedes the iPad-detection PRs 7945 (no detection needed — the iPad is a browser). The customer’s own ID photo is captured from the LOCAL preview, which CVO can’t touch → client-canvas rotation (PR #3043) is orthogonal and still needed.
  • FKITDEV-8887 — iOS Safari audio interruption recovery; InterruptionRecovery controller + VideoFeed.ensurePlaying() + Peer.replaceAudioTrack() proxy chain; see also ## iOS Safari / audio recovery topic for full detail
  • FKITDEV-9305-rcaCannot connect to STUN/TURN servers is always a relay-allocation problem, can never be caused by RabbitMQ, and vuer_oss CANNOT tell you which kind. First: the compat test is served by vuer_oss, not vuer_css — the customer stack frame ConnectionCheck.startChecking matches vuer_oss/client/features/system-check/check-steps/connection-check/connection-check.js (throw in startChecking()); the vuer_css twin throws from checkConnection(), so any vuer_css citation for this symptom is the wrong repo. Route: vuer_oss/server/web/routes/compat-test.endpoint.js, registered server/web/routes.js:109; the ICE list is server-rendered into the pageno queue, no RPC. THE TRAP: iceTest.js’s AUTH_FAILED = 2 (:44) and NOT_REACHABLE = 3 (:45) are DEAD CONSTANTS — defined and read (isAuthFailed() :74, isUnreachable() :78) but setResultCode() is only ever called with DONE (:136, :153) and CONNECTION_TIMED_OUT (:172), and there is no onicecandidateerror handlercredentials rejected, TURN unreachable and no iceServers configured all collapse into one message (the working classifier exists only in the vuer_css twin). Worse, this.result.ice is assigned (connection-check.js:66) but never emitted or reported anywhere in vuer_oss (vuer_css emits a 'report' event), and the pass/fail logic if (isTimedOut() || isAuthFailed() || isUnreachable() || !(hasRelay || hasReflex)) passed = false; else if (hasRelay) passed = true leaves srflx-only (STUN works, relay doesn’t) in NEITHER branchpassed stays undefined → falsy → fails silently; TIMEOUT_PERIOD = 60000 (iceTest.js:41). ⇒ diagnosis must happen outside the product: chrome://webrtc-internals, a Trickle-ICE test against the same TURN URL+creds, or coturn’s own logs. Three indistinguishable causes: TURN secret mismatch (coturn static-auth-secretwebrtc.turn.secret; REST creds username=<unixExpiry>:<name>, password=base64(HMAC-SHA1(secret, username)); also clock skew past webrtc.turn.validityInSec), UDP 3478 / TLS 5349 blocked or coturn down, and filterByJanusServer() leaving iceServers empty (identical error, no server-side log)
  • janus-memory-leak-rcaTHE JANUS MEMORY LEAK IS A vuer_oss BUG, NOT AN UPSTREAM JANUS BUG (root-caused 2026-09-02, four years of tickets closed on the wrong premise). Mechanism: janus-api/src/Janus.js:407-425 keepAlive() reschedules itself with a setTimeout closure capturing this, so a “leaked” Janus object is immortal AND actively pinging — V8 can’t collect it, its websocket stays open, and janus therefore never times the session out (keepAliveIntervalMs: 30000 vs janus’s 60 s session_timeout default = 2× margin; session_timeout is set in NO FaceKom config — checked vuer_docker, vuer_build, vuer-release). MEASURED on fk-dev: janus destroys sessions in UNDER 5 SECONDS when the websocket dies (40 opened, sockets killed with no destroy/keepalive, count went 44 → 4 within 5 s) ⇒ a crash or disconnect leaks NOTHING; the only way to strand a session is a live pinging JS object. Janus.destroy() is CORRECT (idempotent; clears keepalive on success, rejection and the 5 s-timeout path) — the bug is always “destroy was never called”, never “destroy is broken”. Leak paths ranked: (1) server/cv/VuerCVListenerSession.js:62 leaks on EVERY invocation incl. the success path — no destroy()/close() on the class and _janus.destroy() exists nowhere in either repo; owner SelfServiceTransportSession.terminate() (server/transport/session/SelfServiceTransportSession.js:392-404) closes only this.janus, but the listener is a second independent session (costs 1 session + 2 handles + recorder (record: true at :150) + up to 2 PeerConnections); (2) RoomTransportSession leaks on any call not ended via VideoChatService.close() (server/service/VideoChatService.js:201-233) — operator closing the tab emits videochat:leavehandleVideoChatLeave() writes an activity row and notifies CSS but never closes the room or touches the transport; vuer_css terminate() is return Promise.resolve() (a no-op), TransportPool.sessions is a Map with no TTL/sweep, and no base-install cron reclaims videochat rooms (only the AutoCloseRoomsCronJob customization, gated on roomAutoCloseHours); (3) RoomInspector.connect() failure strands a session with ZERO referencesclient.roomInspector = inspector is assigned after the await (server/socket/events/videochat.js:348-357) so the disconnect handler can’t see it, triggered normally by findRoom() throwing “Video room not found” for rooms nobody published into yet; (4) VuerCVSession registers teardown at line 87, AFTER _connectJanus() at line 58 — plus CVTask (server/cv/CVTask.js:6-28) never arms its timeout (this.timeout undefined when super() runs) for LivenessTask/LivenessV2Task/ActionTask/DocumentTask/HoloV2Task/SpeechTask/PadTask/MRZTask; (5) concurrent videochat:senderPeer:init orphans handles (not sessions) — VideoRoomPublisherJanusPlugin doesn’t override hangup() unlike the listener
  • janus-memory-leak-rcaSECONDARY FaceKom defect: videoroom rooms are NEVER destroyed. janus-api’s videoroom plugins send only join/start/rtp_forward/stop_rtp_forward/edit/list/create/configure/listparticipantsno destroy; vuer_oss sends none either, and RoomTransportSession.closeJanus() ends at janus.destroy() = a SESSION destroy. One janus room per vuer Room DB id (id: this.getRoomId()), record: true. Present since janus-api’s first commit, 2018-01-12 — an 8-year-old design gap, not a regression (which is why no upgrade or bisect ever found it). MEASURED ~6.0 kB retained per abandoned room — too small to dominate (100 MB/day would need ~17–21k rooms/day) — but the compounding bug is not small: findRoom() pulls the ENTIRE room list and linear-scans by description on every publisher connect AND every inspector connect, MEASURED 483 bytes/room, linear across 6 points: 3.0 KB @ 6 rooms → 969 KB / 69 ms @ 2006 rooms, extrapolating to ~9.7 MB per call setup at 20k rooms. Destroying rooms works (2000 destroyed, list back to baseline) but RSS does not return to the OS (glibc arena) ⇒ destroy CAPS growth, does not reclaim. Also MEASURED: 400 signalling-only sessions+handles ≈ 31.7 kB each. ⚠️ The per-stranded-session ~0.5–1.5 MB is INFERRED, not measured (real ICE/DTLS/SRTP/recorder state couldn’t be allocated from a script) — confirm with a prod census before quoting it
  • janus-memory-leak-rcaUPSTREAM/FORK FACTS + THE DEBUNK. The TechTeamer/janus-gateway fork changes ZERO lines of janus C source*.c, *.h, src/, configure.ac, Makefile.am are byte-identical to upstream; it is a pure CI/packaging wrapper (.travis.yml, test/check_janus.sh, npm demo tooling). Commit→version from configure.ac AC_INIT: b8bebd94=0.13.4, 08f25c9b=1.2.4 (actually a pre-release 1.2.4-dev snapshot, upstream master @ bad60d70 2024-08-02, git describe = v1.2.3-11-gbad60d70), cc0fdca8=1.4.1 (exactly upstream v1.4.1), af80f7ef=0.16.1. DEBUNK: upstream issue #3408 — cited in ASSRAFIPI-38 since 2024 as the relevant leak — DOES NOT APPLY: closed 2024-10-23, root cause was the Lua plugin, fixed by PR #3409 whose entire diff is g_thread_unref(g_thread_self()) in janus_lua.c and janus_duktape.c, and both plugins are excluded by --disable-all-plugins. Upgrading 1.2.4-dev → v1.4.1 gains exactly ONE unconditionally-applicable leak fix: 4419baf0 (2024-12-10), janus_recorder_free() never freed recorder->description; four others are conditional and don’t apply (SVC, dummy publishers, RTP forwarders, remote publishers). 4fc066ff (videoroom subscriber refcount leak in slow_link) is gated on slowlink_threshold > 0 and slowlink_threshold appears in NO FaceKom config ⇒ default 0 ⇒ disabled (caveat: Raiffeisen ships its own .jcfg via its own Dockerfile layer — confirm on their box). No OPEN upstream leak issues affect videoroom+websockets; 14 upstream commits exist after v1.4.1, none a leak fix. Build flags --enable-post-processing --disable-data-channels --disable-all-plugins --enable-plugin-echotest --enable-plugin-videoroom --disable-all-transports --enable-websocketsthe legacy vuer_build/base/janus/Dockerfile ALSO passes --enable-rest (HTTP transport compiled) while the newer vuer-release base does not. Latent build bug in the vuer-release base janus Dockerfile: copies libwebsockets.so.19 but symlinks libwebsockets.solibwebsockets.so.16, which does not exist (dangling; harmless at runtime via SONAME). Pinned deps aging: libnice 0.1.17 (2020), libsrtp 2.5.0, libwebsockets 4.3.2
  • janus-memory-leak-rcaCROSS-PARTNER + RELEASE BLOCKER. It is per-CALL, not per-unit-time — two independent confirmations: fk-dev janus idle 16 days = 20 MB RSS, and DÁP Grafana shows RSS FLAT across the whole weekend then stepping up during business hours. This finally answers the question ASSRAFIPI-38 asked in 2024 and never got answered. Raiffeisen (SLARAFIPI-83/FKITDEV-9239): janus 0.13.4, AWS t3.large, maxmemory 7168 MB, ~126 MB/day (longest clean run 03/05→04/28, 2%→97%), ~8 weeks to exhaustion, mitigation = manual restart only. DÁP/NISZ (DAP-1141): janus 1.2.4, Kubernetes, 4 pods, 8 GiB limit, OOMKilled after 13 days, ~0.7 GiB/day/pod; DAP-1141 was closed on “the upgrade process has started”, never verified. Cofidis: still on 0.13.4; 1 GB (2022) → 2.7 GB (2024-06) → 5 GB+ (2024-11); container once “Up 8 months”. UniCredit: build def says 1.4.1 but the running image on 2026-08-26 was janus:1.2.4.1-20220513build definition ≠ deployed reality. DÁP on the NEWER janus leaks ~5× FASTER than Raiffeisen on the older one — different workloads so not a clean comparison, but the opposite of what “newer is fixed” predicts. No partner has EVER had a post-upgrade memory measurement; “upgrade will fix it” has closed multiple tickets over 4 years and has never once been verified. Fleet build defs: 1.4.1 = cib, generali-atvilagitas, instacash, magnet, microsec, nusz, raiffeisen; 0.16.1 = barion, fundamenta, granit, kh, mbh, mkb-instant, szerencsejatek; 0.13.4 = cofidis alone. ⚠️ RELEASE BLOCKER: janus is absent from COMPONENT_LIST in ALL TEN Raiffeisen release manifests (vuer-release/projects/raiffeisen/release/1..10/release.json — every one is [vuer_oss, vuer_css]); the JANUS_VERSION_COMMIT field that flips 08f25c9bcc0fdca8 at rel6 (1.9.11.94) is an attribute ON the vuer_oss component (what it is built against), NOT a janus imageno janus image has ever been built or shipped to Raiffeisen (confirmed by Szabó Márton in FKITDEV-9239). Janus must be added to the build for .101 or nothing changes. Keep SEPARATE from FKITDEV-9193 (a distinct, confirmed Node.js leak in StorageService._getInFlightCache/stream(), likely driving FKITSYS-9902 — UniCredit, OSS alone at 17.6 GB). Read-only diagnostic scripts at /Users/levander/coding/facekom/out/janus-memory/ (janus-probe.js census via the already-enabled Admin API wss://janus:7989, adminSecret janusoverlord, JanusAdmin already in janus-api with listSessions/listHandles/handleInfo; plus roomleak.js, sessleak.js)
  • janus-memory-leak-rcaMEASURED ON REAL CALLS 2026-09-07 — and it CORRECTS the hypothesis: BOTH arms leak. Isolated full stack (vuerleak2, /workspace/_leak2/ on fk-dev: postgres + rabbitmq + janus + vuer_oss + vuer_css, own network/DB/TLS proxy), real browser calls with real media (two <video> per side, 640×480, advancing currentTime), janus RSS + Admin API census per cycle, 10 cycles/arm. ABANDONED: sessions_delta 10 / rooms_delta 10 / rss_delta_kb 10064~0.8–1.0 MB per call (reproduced: 999.2 kB/cycle), each call permanently costing 1 session + 1 handle + 1 populated videoroom. CLEAN (operator ends properly): sessions_delta 0 — sessions/handles ARE reclaimed — but rooms_delta 10, rss_delta_kb 6872~453 kB per properly ended call, because the videoroom is never destroyed. ⇒ “a cleanly ended call is fine” is WRONG, and the inferred 0.5–1.5 MB/session is SUPERSEDED. In the clean arm the RSS may be partly allocator retention — the room count is the hard evidence. Permanence verified long after the run: 9 sessions alive, 10 rooms (9 populated), 10 DB rooms stuck incall
  • janus-memory-leak-rcaSIDE-DEFECT: one abandoned call takes the OPERATOR OUT OF SERVICE. An abandoned call leaves Room.status='incall' forever — handleVideoChatLeave only writes an activity row, only videochat:close closes a room, and no cron covers operator videochat rooms (CloseExpiredSelfServiceRoomsCronJob is self-service only) ⇒ videochat:createRoom then refuses with operator_in_open_room and that operator can take no further calls until someone closes it by hand. This is an availability bug that has probably been misfiled for years as “operator can’t receive calls”
  • janus-memory-leak-rcaSIDE-DEFECT: multi-role users silently cannot answer calls. videoChat.receiveCall is granted only to operator in config/roles.json, but WebServerAuth.js:934 sets req.session.role = req.user.getMainRole() and getMainRole() (server/db/model/user.js:187) returns the first acl.roleList entry the user has ⇒ admin. Fails silently three layers deep: no right ⇒ waitinglist.script.js:113 never calls receiveAvailable(true).can-receive-call never added ⇒ WaitingList.styl:16 keeps .customer-item-actions at display:none. Fix: POST /api/role-switch with document.body.dataset.csrftoken (what default.layout.js:48 does). Symptom→cause shortcut: “sees the customer, no accept button” = role, not perms/CSS/sockets

RTK / tooling gotchas

  • SLARAFIPI-84CORRECTION 2026-09-07: the stale worktree produced wrong CITATIONS, never a wrong FACT — and an earlier revision of that note wrongly implied it caused both refuted answers. Re-read at both refs via subprocess.run(["/usr/bin/git",…,"show",…]): raiffeisen.customerPortrait.threshold is 0.55 at 350d3e626b AND at tag raiffeisen-1.9.11.100, the perfect: threshold override is identical at both, SelfServiceCheckerService.js is not in the 6-commit diff at all, and the face_compare writer only moved by 19 lines; the whole config/docker.json diff is maxRetryCount: 4, documentRecognitionVersion 2→3 and two liveness labels. Staleness is a real citation hazard (it misled three agents) but is not an explanation for a wrong conclusion — do not use it as one.
  • rtk-mangles-curl-and-pipesRTK (the token-proxy that auto-rewrites shell commands) MUTATES some commands, not just their output → silently wrong results. curl <url> fails with curl error (3) “Malformed input to a URL function” (Rust proxy corrupts the URL; wget also unreliable); ls | sort returns empty; find|wc -l / |grep -c get zeroed (dangerous — reads like a legit “no results”). Bypass via python3: download with urllib.request.urlretrieve (not curl/wget); run multi-step CLI checks via subprocess.run([...]) argv list (no shell=True, not a shell pipeline); for counts cross-check two independent methods. rtk proxy <cmd> is a raw escape hatch but prefer the python bypass. Hit while fetching the actionlint binary for the FKITDEV-8239 review
  • rtk-git-log-hides-merge-commits — sibling RTK gotcha: output-filtering drops merge commits from git log --oneline --graph, producing a deceptively linear DAG → squash/rebase/reset can operate on the wrong range and lose a parent’s history. Use parent-aware plumbing instead (git log --pretty='%h | %p | %s', git rev-list --parents -n 1 <sha>, git rev-list --count A..B); verify a squash preserved content by comparing HEAD^{tree} hashes before/after; non-interactive squash via backup-ref + reset --soft + tree-hash equality gate (git rebase -i unavailable in this harness)
  • raiffeisen-1.9.11.100 — two more RTK mangling cases found 2026-08-11, both silently wrong rather than failing: git show <rev>:<path> has its <rev>:<path> argument mangled, and jest --verbose per-test lines get swallowed so a run looks like it produced no test names. Bypasses: python3 subprocess.run([...]) with an argv list (no shell=True) for the git show, and jest --json --outputFile=<f> instead of --verbose to get machine-readable per-test results. Consistent with rtk-mangles-curl-and-pipes: assume any RTK-proxied command whose arguments contain :-separated or pipe-like syntax may be rewritten, and prefer a structured-output flag over human-readable output whenever you need to assert on it
  • FKITDEV-9197five more RTK modes, 2026-08-11, all returning a plausible wrong answer rather than an error: git show <ref>:<file> | grep garbled so real matches were reported as ABSENT (nearly caused a wrong conflict-resolution decision during the CIB merge); diff -u reformatted into an unreadable line-offset view with a wrong exit code (so the exit-code fallback lies too); git diff --no-index rendered as if the whole file were new; git log -1 immediately after committing showed devel’s tip 56a63bd0 instead of the just-created merge commit b1a4bc94 — the rtk-git-log-hides-merge-commits merge-filtering behaviour landing at the exact moment you check “did my commit land, and onto what?”, which invites you to undo correct work; and ${PIPESTATUS[0]} blanked, making every pipeline pass/fail gate unverifiable. Workarounds proven here: rtk proxy <cmd> (preferred for verification claims precisely because the failure is silent), git grep <pat> <ref> -- <path> instead of git show | grep, shasum -a 256 for same-vs-different, and python difflib to generate a diff. Standing rule: never trust RTK-rendered git output for a verification claim — “the merge commit is X”, “the file does not contain Y”, “these are identical”, “it exited 0” all need re-verification. ⚠️ And the workaround has its own trap: calling jest directly to dodge RTK’s yarn rewriting DROPS the flags the npm script supplies — omitting --experimental-vm-modules manufactured 4 phantom failures in vuer_oss on this round. Prefer rtk proxy yarn <script>; if you must bypass the script runner, copy the full flag list out of package.json first
  • SLARAFIPI-84RTK silently DROPPED THREE COMMENT LINES while extracting a source file through git show, shifting every line number below them by 3 — so line references harvested that way pointed at the wrong code, in a note whose conclusions were all line-anchored. Same failure family as the wrong-commit-for-git log -1 case: plausible output, no error. Rule reaffirmed: use /usr/bin/git directly for anything load-bearing. Two non-RTK verification traps found in the same session and worth pairing with it: a worktree HEAD that is an ancestor of the tag by 6 commits touching exactly the files under investigation (read via git show <tag>:<path>, never the working tree), and git log -1 --format=%ad showing an author date preserved through a rebase (2026-05-12) instead of the real committer date (2026-08-10) — use %cd

Release management / YouTrack

  • vuer-build-never-pushesvuer_build/build.sh (the LEGACY partner path) never publishes: verified grep -n push build.sh → no docker push, no docker login. v0.4.1 only builds and tags locally (harbor.techteamer.com/${PROJECT_NAME}/<svc>:${VUER_VERSION}.${VUER_BUILD_NUMBER}-${SECURITY_NUMBER}; common infra → harbor.techteamer.com/vuer-common/…) and can export images as .tar into the customer ZIP (--build_package) — the harbor.… prefix is only a tag, nothing in the script contacts the registry. sign-partner.sh v0.1.1 wraps cosign (--annotations 'Signer=Facekom Kft', -i single / -t tag list) but signing is not publishing. Contrast the modern vuer-release path, which does publish: release-tool publish push in .github/workflows/autobuild.yml authenticating with secrets.HARBOR_USER/HARBOR_SECRET. ⚠️ OPEN QUESTION, explicitly not guessed: how legacy images actually reach Harbor is in NO file read so far (manual docker push? separate script? ops runbook outside the repo?) — and it sits directly on CIB, which is on the legacy path per client-registry. Version caveat: the note says 1.9.11.101 while client-registry records .102 per FKITDEV-9197 ⇒ resolve from the open ASSCIB Release issue or git ls-remote --tags, never from a note. Two traps: build.sh -l/--list-partners is broken (cd partners/ at :447, real dir is partner/ singular — 39 dirs) and builds fail without an operator-supplied base/<svc>/github.key (copied to /root/.ssh/id_rsa to clone private repos; *.key gitignored, README:22-28 ⇒ a fresh clone cannot build). Base OS is branch-dependent: main=UBI9, feature/FKITDEV-8868=UBI8, feature/FKITDEV-8252-ubi10=UBI10
  • facekom-test-tiers — the three release-testing tiers; tier 2 (booted, non-browser) is ~60% of a release runbook with zero tooling, and building it also dissolves the k6 seeding blocker. See the Dev / testing workflow entry for detail
  • devel-update-and-release-flowhow to collect a release’s ticket scope for the TjK and for author outreach (Phase 3), plus the release gates (Phase 4). Verified tool /Users/levander/coding/facekom/.claude/scripts/release_tickets.py <tag-prefix> <repo-path>… resolves the latest release tag per repo (fetching the tag explicitly — needed in narrowed-refspec clones) and emits two markdown tables with YouTrack links, GitHub commit + PR links, state, assignee and commit author: (1) landed DIRECTLY on the customization branch (git log <tag>..HEAD --no-merges --not origin/devel) = what the partner paid for, always a TjK entry; (2) came via devel but modified customization/ (git log <tag>..HEAD --no-merges -- customization/ minus bucket 1) = core work that nonetheless changed the partner surface, easy to miss entirely. Generali 1.9.11.19 vs generali-atvilagitas-1.9.11.18: direct = FKITDEV-8567 (Done, assignee Jurkiewicz, authored by Szecsődi, vuer_oss PR #7811) + FKITDEV-9194; via devel = FKITDEV-9119 (Done, Szvetkó, PR #8083) + FKITDEV-9080 (Pending but shipping anyway — flag it, Makkai, PR #8054). Two manual steps the tool cannot do: assignee ≠ commit author (both emitted, pick per question), and it only sees tickets in commit subjects — the headline changelog item can be a pure-core commit in neither bucket (1.9.11.19’s is ASSGRALI-63 / FKITDEV-8887 iOS audio), so cross-check the parent ASS* release ticket’s changelog. Release gates: chore/ branch merged into customization/<partner> via PR (the tag is cut from the customization branch), customization/RELEASE.md entry (historically its own ticket — FKITDEV-9153 for .18), tag <partner>-1.9.11.NN (package.json version stays 1.9.11 — the .NN lives only in tag + changelog), breaking-change/config sweep (BREAKING/!:, new db/migrate/, config/ diffs — .19 restructured browsers.showOldBrowserWarningbrowsers.oldBrowserWarning.{show,delay} with server/web/Template.js:123 on the new path, so any partner prod config on the old key silently loses the old-browser warning and docs/config/ was not updated), functional smoke test on a real deployment (a container unit run is not one), TjK written
  • vuer-release-build-flowhow the vuer-release build/release flow actually works, read from the TechTeamer/vuer-release-cli Python source (2026-07-22) — the release_tool binary is NOT a black box. Flow: release-tool release create project <p> (interactive VERSION/BUILD_NUMBER/TAG) → projects/<p>/release/<N>/release.json + tag <p>@<N>autobuild.yml on a self-hosted runner (RELEASE_PAT~/.git-credentials HTTPS x-access-token, download release_tool asset, genbuildpublish to Harbor with HARBOR_USER/HARBOR_SECRET). Corrections to previously-guessed assumptions: (1) component source is cloned from TAG, not VERSION (gen.py::download_sourcegit clone --branch <TAG> --single-branch --depth 1); VERSION only feeds the image tag {registry}/{PROJECT_NAME}/{NAME}:{VERSION}.{BUILD_NUMBER}-{SECURITY_NUMBER} + the generated .env. (2) a new base component needs no base@N release firstBASE_/PROJECT_/RELEASE_COMPONENT_IMAGE_TAG are the same locally-computed string, never resolved against the registry; build.py builds the base stage then the project stage against the just-built local image. (3) component NAME == GitHub repo name (repo_name = component["NAME"] under GIT_REMOTE_ORG), no override key; JANUS_REPOSITORY is only used by install-janus-build-env.sh. (4) release create writes COMPONENT_LIST, gen only reads itcomponent_env_values.json (base then project, project wins) supplies interactive prompt defaults at release create (release.py:239-259/269-297, written at :356); gen.py:76 reads release_json["COMPONENT_LIST"] and never re-reads the component env files. This corrects the old “gen snapshots component config” gotcha in vuer-release-cut-recipe (now fixed there): janus pins are the effective default but overridable at release time, not immutable. (5) TAG is NOT in REQUIRED_ENV_VALUES (release.py:49 = VERSION + BUILD_NUMBER only) — it is prompted only if already present in the merged env values; janus deliberately has no TAGhas_source() false → never cloned by the CLI (fetched by install-janus-build-env.sh via JANUS_REPOSITORY/JANUS_VERSION_COMMIT); trap: a new base component missing TAG is silently never cloned. Plus gen.py::rm_always sanitization (strips .yarnrc, config/dev.json, .git, any supervisor_*/nginx_* without docker in the name), tarball top-level dir == NAME, component availability = directory existence (release/.gitkeep required; component_env_values.json + Dockerfile optional), do not use release-tool component create (stale component.j2), and the FKITDEV-8349/DÁP application (forced dap-demo-partner hyphen naming → Harbor image rename; TAG 1.0.7.1 not 1.0.7)
  • youtrack-ready-for-release-nusz-queryYouTrack access + the NÚSZ “Ready for release” release-scope query. Tracker = YouTrack at https://youtrack.techteamer.com (REST /api/issues; MCP server at /mcp in ~/.claude.json, scoped to claude_orchestrator). Auth = Bearer token from ~/.config/facekom/youtrack.token (chmod 600, never on argv); the /fk-ticket command (.claude/commands/fk-ticket.md) + client.py extractor read the same file. “Ready for release” is a TAG (exact string Ready for release; search tag: {Ready for release} — braces for the spaces); it is tracker-wide (57 issues across clients), so the project: filter is what scopes it. The four NÚSZ projects: CRNUSZ (Business Requests), BUGNUSZ (Support Issues), SLANUSZ (SLA), ASSNUSZ (Assist). Verified scope query project: CRNUSZ, BUGNUSZ, SLANUSZ, ASSNUSZ tag: {Ready for release} returned 3 on 2026-06-15 (CRNUSZ-102, ASSNUSZ-58, SLANUSZ-28; BUGNUSZ 0); read-only curl -G --data-urlencode recipe included. CRNUSZ-102 verified example shows State Pending yet tag-flagged → the tag (not State) is the readiness signal. Don’t confuse with the distinct Upcoming Release tag. GET/read-only only.
  • nusz-1.9.11.47 — per-release tracking hub for nusz @13 / 1.9.11.47 (release issue ASSNUSZ-116 / FKITDEV-8938). Scope mechanism, dual-publish 🅼 Harbor / 🅻 vuer_build checklist, manual post-release YouTrack transitions, and a per-ticket payload detail section: CRNUSZ-102 (impl FKITDEV-8794/8801 — no code/PR found, business-accepted, UAT 06.18), SLANUSZ-28 (impl FKITDEV-8639 → PR #7862, NOT yet in customization/nusz, may be in .45/.46), ASSNUSZ-58 (stat-export, UAT/PROD gate, targeted .46.1). Merge-status warning: payload code not yet confirmed in customization/nusz; version target (.45/.46/.46.1/.47) needs confirming.
  • instacash-esign-1.3.0.11 — per-release hub for the InstaCash eSign 1.3.0.11 release (eSign pipeline, not vuer-release): esign_oss + esign_css only, tag instacash-1.3.0.11 / Harbor instacash-esign-{oss,css}:1.3.0.11-20260608; ASSICASH-92 release + ASSICASH-93 TESZT / ASSICASH-96 PROD (approved 2026-06-26 from 1.3.0.8); changelog = devel update + FKITDEV-8817 vuln fixes; no DB migration / no breaking change; rollback = redeploy 1.3.0.8. First InstaCash release to ship with a TJK.
  • client-registry — cross-system name glue for /fk-release: YouTrack suffix ≠ repo name (RAFIPI=raiffeisen, MNET=magnet, PB=polgaribank, GRALI/GRALIA=generali, etc.). 22 clients mapped with build-path (modern vuer-release/projects/<client> / legacy vuer_build/partner/<client> / eSign instacash) + resolution rules (YouTrack projects CR/BUG/SLA/ASS<suffix>, version from open ASS<suffix> Release issue, customization branch customization/<repo> incl. -instant/-v2/-f1 variants).
  • release-automation-design — reusable prepare-and-gate /fk-release <client> design. Now also covers client generalization (path auto-detection, universal ASS<CLIENT> Release-issue convention proven by CIB .101), PR fetch without YouTrack VCS (VcsChangeCategory empty → resolve via git log --all --grep=FKITDEV-NNNN merge-commit + comment-scan for pasted PR URLs), and comment extraction ranking (has-attachment > Hungarian QA keywords > embedded issue links; attachment URLs are signed/expiring).
  • FKITDEV-8354-mvm-supervisor-config-dedupvuer-release Gen2 partner-migration mechanics (FKITDEV-8354, MVM project, vuer_buildvuer-release lean format, PR #28 base master): partner component images layer overrides via projects/<client>/components/<svc>/Dockerfile COPY on top of the source-package files symlinked by base install/configure-app.sh; supervisor-config dedup decision rule (byte-identical ⇒ delete, else keep). Do not conflate with FKITDEV-8252 PR #31 feat: ubi10 (different ticket/branch). Reviewer bencelaszlo; round-1 set janus pins 1.4.1/cc0fdca8 + restored supervisor-stdout.
  • youtrack-tesztjegyzokonyv-attachment-recipewhere FaceKom test records (“tesztjegyzőkönyv” / “Tesztelési jegyzőkönyv”) live + a read-only REST attachment recipe. TJKs are PDF/DOCX attachments on per-client ASS<CLIENT> release / BUG<CLIENT> tickets, NOT standalone issues; 8-section branded template (Cél / háttér / teszttípus-összegzés / tesztesetek / jegyzőkönyv+evidencia / értékelés / hibák / mellékletek); canonical templates = FKITDEV-8329 (unified format) + FKITDEV-8330 (release/install template). Recipe (python urllib, same base + ~/.config/facekom/youtrack.token as youtrack-ready-for-release-nusz-query): GET /api/issues?query=…&fields=…,attachments(name,mimeType,created,url),comments(text,attachments(…)), Hungarian full-text works, scope by project:; download = prepend base to the attachment’s relative signed url, GET w/ Bearer, write bytes; timestamps epoch-ms. GET/read-only.
  • tesztjegyzokonyv-generation-flowthe /fk-tjk flow that GENERATES a partner’s Tesztelési jegyzőkönyv .docx + the tester’s manual runbook. REWRITTEN 2026-08-11 — treat everything after “LEGACY-ERA DETAIL” below as superseded. Now ZERO-CONTEXT: input is a partner key only and you must never carry a release number/ticket/partner in from conversation, because release trains are PER PARTNER (Raiffeisen 1.9.11.100 while NÚSZ was on 1.9.11.48, same day) — a borrowed version silently yields a correct-looking WRONG document; a version in $ARGUMENTS is a hint and a mismatch with the partner’s release ticket STOPS the flow. Phases 0 historian synthesis (live state wins over vault AND over the historian) → 1 partner/base/release → 2 gather → 3 evidence → 4 draft → 5 render → 6 runbook → 7 hand-off. Every partner is standardized on the release-doc shape (_meta.targetShape in partners.json); the sablon is the legacy path. Base doc = the newest TjK in ~/Downloads matched by NAME (Tesztelési jegyzőkönyv/Tesztjegyzokonyv) — live Google-Docs export, beats any ticket attachment; never match by size or list position (CVs at ~765K sit next to the 763K TjK). Two-step build via append_release_sections.py: newdoc rebases any partner’s TjK onto a new partner+release (drops old ticket sections, clears TOC cache, longest-first partner rename — pass both forms "Raiffeisen PION,Raiffeisen" or Raiffeisen PION fejlesztésNÚSZ PION fejlesztés; --replace OLD=NEW for lowercase build tags/dates), then append adds sections from JSON (p/bullets/code/evidence); both self-check, never mutate input, both take --selfcheck. ⚠️ TOC LEAK (proven, not theoretical): the TOC is a static Word field holding the PREVIOUS doc’s heading text until refreshed — a Raiffeisen→NÚSZ rebase left ASSRAFIPI-124/SLARAFIPI-59 in the NÚSZ TOC, invisible in the body; newdoc clears it and REFUSES to write if any source ASS…/SLA…/CR…/BUG… id survives ⇒ TOC renders empty until a human refreshes it. Phase 6 writes out/<partner>-<release>-teszt-runbook.md (előfeltételek / lépések / elvárt eredmény + failure signature / bizonyíték naming which TjK slot it fills / the arming condition — e.g. socket-reuse-only bugs where reopening the app proves nothing and yields a false pass). Evidence is REGENERATED, not quoted; gotchas: rtk swallows jest --verbose--json --outputFile, worktree node_modules out of sync with its branch lock (missing babel plugin) → yarn install --frozen-lockfile, textutil cannot read PDFpdftotext -layout. fileStem stays per partner though the shape is shared; nusz/ASSNUSZ was missing from partners.json entirely until 2026-08-11 and an unknown key halts the flow on step one by design. — LEGACY-ERA DETAIL (2026-06-26, sablon path): Practice: one TJK per AFFECTED partner per 1.9.11.NN (not all 39), attached to that partner’s release ticket; shortName usually ASS<PARTNER>/BUG<PARTNER> but VARIES (MicroSec=MF, DÁP=DAP/ASSDAP) so partners.json pins ytProject; a core change reuses byte-identical body text across partners (MKB ASSMKB-90 == BB ASSBB-82). New std template tesztjegyzokonyv_sablon.docx (authored 2026-05-29, replaces 3 inconsistent legacy formats) = 19 <…> placeholders each intact in a single <w:t> run, all in word/document.xml → plain string substitution preserves styling (no docxtemplater/pandoc). Tool = Claude command .claude/commands/fk-tjk.md (pulls dev ticket via fkticket + 1 past report/partner for house style → draft → render) + stdlib renderer .claude/scripts/tjk/render_tjk.py (clones test-case block document.xml paras 27–36 per case w/ 1.k., \n<w:br/>, verbatim zip-repackage swapping ONLY document.xml, self-check) + partners.json (13 seededinstacash/ASSICASH added 2026-06-26) + pinned sablon. v1 leaves screenshots / pass-fail underline / PDF export / YouTrack-attach MANUAL (no write-back). Self-check + tests pass; design spec /Users/levander/coding/facekom/docs/superpowers/specs/2026-06-26-teszt-jegyzokonyv-flow-design.md. First real use (2026-06-26): generated the first-ever InstaCash eSign TJK (5 cases) for eSign 1.3.0.11~/Downloads/tesztjegyzokonyv_instacash_1.3.0.11.docx.
  • nusz-1.9.11.48-test-runbookTJK-source manual test runbook for NÚSZ 1.9.11.48 (ASSNUSZ-126 / prep FKITDEV-9217, next release/17 + nusz@17). Prioritized (P1/P2/P3) plan across payload verification (ASSNUSZ-76/FKITDEV-9150 remove-old-data cron + audio-only webm; FKITDEV-8975 text), AI-Act (present but config-gated OFF by default — CSS aiAct.videoCall/selfService=false), core 12-flow NÚSZ E2E smoke, and devel-update regression by area (reports/SL, portal/PortalData, camera-permission + iOS audio recovery, S3 attachments/export, MJML v5 ESM emails, crons, TS-entrypoint sweep). Flags what UAT cannot prove (real disk reclaim, reconversion backlog scale, customer-key-offline deletion branch — prod-only) + open retention question (7d vs 28d).
  • tesztjegyzokonyv-partner-release-document-structurethe partner-facing RELEASE TjK is a DIFFERENT artifact from /fk-tjk’s template — do not conflate them. [[tesztjegyzokonyv-generation-flow|/fk-tjk]]‘s pinned tesztjegyzokonyv_sablon.docx is 19-placeholder substitution for one dev ticket; the release TjK is hand-authored per release with one Heading2 section per shipped ticket (<Partner> - Tesztelési jegyzőkönyv - VUER OSS CSS - Release 1.9.11.<NN>.docx, attached as PDF to ASS<PARTNER>-<n>). Shape: Title/Normal/Subtitle → static TOC field → Heading2 Bevezetés → per ticket Heading2 <id> - <title> / Heading3 Fejlesztés / Heading3 Teszteset / optional Heading4 Elvárt működés. Hard rule: PARTNER-side ticket ids only (ASSRAFIPI-/SLARAFIPI-/CRRAFIPI-) — FKITDEV-<n> never appears in a customer document; section order = release changelog order, not ticket number. Evidence forms ranked: monospace dump (one Normal para per line, Roboto Mono / color 37474f / sz 21) > inline <w:drawing> screenshot > narrative; bullets numId=1/ilvl=0/ind left=720 hanging=360; numbered sub-cases are plain paragraphs, not Word lists. Two traps: the TOC is a static Word field and does NOT refresh on programmatic append (reopen in Word/Docs before PDF export — a mis-styled body paragraph is invisible in the body but loud in the TOC), and the closing “telepítésre ajánlott … a tesztek mind sikeresek voltak” paragraph of Bevezetés IS the formal pass/fail statement to the bank — rewrite it if any case failed. Working append script /Users/levander/coding/facekom/.claude/scripts/tjk/append_release_sections.py (the append_sections_example.py name is gone) (splice <w:p> before <w:sectPr>, rewrite the zip entry-for-entry, self-check); source docs are Google Docs exports (all w:rsid* = 00000000) with Roboto + Roboto Mono embedded. The full spec — including the VERBATIM Hungarian Bevezetés boilerplate and its closing recommendation paragraph — now lives in the note itself: the on-disk docs/tjk-raiffeisen-document-structure.md was folded in and retired 2026-08-11 (only executable config stays in facekom: .claude/commands/fk-tjk.md, .claude/scripts/tjk/*.py + partners.json). Reverse-engineered from the 2026-08-10 raiffeisen-1.9.11.100 draft (whose known defects are listed in both notes)
  • release-cut-mechanicsdurable git/GitHub mechanics of a partner release cut (live-verified executing NÚSZ 1.9.11.48 on 2026-08-25), the customization/<partner> half that complements vuer-release-cut-recipe’s Harbor-autobuild half. FOUR ops: (1) land the devel-update onto customization/<partner> by SQUASH merge gh pr merge --squash --admin (compliant PR title first, since the squash subject = the title; --admin skips only red checks not the ruleset — see the Git / rulesets topic); (2) changelog = direct compliant commit to customization/<partner> (no PR rule) via gh api -X PUT …/contents/customization/RELEASE.MD, oss & css RELEASE.MD are separate per-repo entries — compute real delta with compare/nusz-1.9.11.47...customization/nusz; (3) source tags <partner>-<version> via gh api …/git/refs (do NOT trigger a build); (4) vuer-release cut — default branch master (not main), copy release/16→17/release.json, bump RELEASE_VERSION top-level + DEFAULT_ARGS + both COMPONENT_LIST VERSION/TAG, tag nusz@17 = the Harbor build trigger (nusz@N names don’t match vuer_oss tag rules → vuer-release has looser tag rules). Inherited-not-regression red: oss translations.test.js “Directory not found: client/features”. Executed .48: PR squashes e01bf599/f8511f79, changelog c8c5f7e7/08fc5380, release/17 3bd20ec7, nusz@17 pushed.
  • vuer-release-cli-pinningvuer-release/.cliversion is an EXACT-EQUALITY pin (1.0.3), and the toolchain around it is broken in four independent ways. release_tool/pkg/util.py:validate_cli_version does Version(current) != Version(required) — a newer CLI is rejected exactly like an older one. Do not bump to 1.1.1: the runner’s .github/scripts/download-release-cli.sh selects the release asset named release_tool and 1.1.1 renamed its asset to release-tool ⇒ CI dies with ERROR: Asset 'release_tool' not found. To run 1.0.3 locally you must first patch its invalid pyproject.toml (pyyaml/typing_extensions unquoted ⇒ tomllib.TOMLDecodeError at line 16) and invoke python run.py (it has no [project.scripts]); 1.1.1’s requirements.txt omits packaging. The two versions emit different release.json key sets (1.0.3 REQUIRED_ENV_VALUES = {"VERSION":"","BUILD_NUMBER":""} vs 1.1.1 {"VERSION":…,"TAG":…} with required-flag dicts) and read incompatible schemas from the same ~/.release-tool/config.json (flat keys vs current_context/contexts) ⇒ the CLI-source facts in vuer-release-build-flow are 1.0.3 facts, which is what CI actually runs
  • component-list-is-not-a-full-manifestCORRECTION: COMPONENT_LIST is a BUILD SET, not an inventory of the partner’s running stack. Verified 2026-09-04 with Python over the parsed JSON (never a shell pipe — rtk zeroes | wc -l): 91 projects/*/release/*/release.json files, 90 with a COMPONENT_LIST, 88 excluding the two new cib cuts; component ENTRY counts over those 88 = vuer_oss 84, vuer_css 84, portal_css 8, janus 5, resource-manager 3, report-engine 1 (with the cib cuts: 86/86/9, a clean +1/+1/+1 ⇒ self-consistent). The four manifests with no vuer_oss/vuer_css are all SINGLE-COMPONENT BUNDLESdemo-facekom/release/6['report-engine'], demo-project/release/{1,2,3}['resource-manager']. janus has a component dir in 12 partners but ships in only 3 (barion/cofidis/kh); mkb-instant has rabbitmq+turn dirs and ships neither; 12 of 18 latest cuts are exactly vuer_oss+vuer_css. GOTCHA: projects/equilor/release/1/release.json has NO COMPONENT_LIST key at all — an older all-lowercase legacy schema (default_args/docker_registry/git_remote/project/project_args/services/timestamp); equilor has exactly one release, so any script iterating all manifests must guard for it or KeyError. BUT release-tool gen builds the delivered docker-compose FROM COMPONENT_LIST ⇒ an omission is a silent delivery gap, the exact shape of SLARAFIPI-83 / janus-memory-leak-rca (janus absent from all ten Raiffeisen manifests ⇒ never built, never shipped). DECIDING CHECK: count the image: lines in vuer_build/partner/<client>/docker-compose.yml — that is what the customer actually pulls. CIB’s has exactly three (vuer_css, vuer_oss, portal_css, the last on its own ${PORTAL_VERSION}.${PORTAL_BUILD_NUMBER} vars) and no janus lineportal_css REQUIRED in cib@2, janus correctly excluded
  • cib-1.9.11.102CIB release hub. Source-tagged 2026-09-03; CIB’s FIRST-EVER vuer-release cut cib@1 (commit ac8aa5cc55 on master, tag cib@1, autobuild run 33855979398) was made 2026-09-04 and FAILED at the Harbor publish — images BUILT fine, Harbor login SUCCEEDED, then docker push harbor.techteamer.com/cib-facekom/vuer_css:1.9.11.102.1-20221206 was rejected. DELIVERY BLOCKED, needs a Harbor admin (hypothesis, NOT confirmed: the cib-facekom project does not exist or the robot lacks push rights — login worked and the same runner pushed nusz@18 the day before). Image tag anatomy = <VERSION>.<BUILD_NUMBER>-<SECURITY_NUMBER>, not the all-hyphen form. CIB is a MODERN (vuer-release) partner as of .102client-registry’s legacy row corrected. cib@2 (adds portal_css 1.4.0.74) prepared but UNCOMMITTED — justified by CIB’s own PROD compose, vuer_build/partner/cib/docker-compose.yml, which has exactly three image: lines (vuer_css/vuer_oss/portal_css) and no janus line ⇒ portal REQUIRED, janus correctly excluded. vuer_build contains NO docker push/docker login anywhere, so nothing in the repo shows the robot ever had write access to cib-facekom; and Harbor’s API returns [] unauthenticated for every project, so non-existence could NOT be inferred — the root cause stays a hypothesis. The .102 changelog was NEVER written in either repo — deliberately left alone, since the source tags already point past it. vuer-release has no commit-message ruleset: one ruleset (12260273 “main-protect”, ~ALL), one rule, non_fast_forward. Original hub facts: (ASSCIB-166 / FKITDEV-9197), a pure core-update release: 334 commits / ~11 months of devel drift, 212 tickets, zero partner commits since .101. Components are vuer_oss + vuer_css ONLY per ASSCIB-166’s Komponensekportal_css [#712] is deliberately out of the train and still open (it also runs its own version line, cib-1.4.0.*, and never carries a cib-1.9.11.* number). Tags are annotated, tagger Andras Lederer, message the bare 1.9.11.102\n → vuer_oss 12a8a9e328221829ae6d383fd5e23eda9cf81a38, vuer_css ca60fac34ac95b661336587b455924ab55e52def; created through the GitHub API rather than a local push specifically to avoid the vuer_css narrowed-fetch-refspec stale-ref trap (narrowed-fetch-refspec-stale-devel-merge). Tag-kind convention changed at .101: cib-1.9.11.100 was LIGHTWEIGHT, .101 (annotated, 1.9.11.101\n, Szabó Márton, 2026-05-29) and .102 are annotated — do not pattern-match on .100. Both PRs squash-merged → devel ancestry lost, see squash-merge-erases-partner-devel-ancestry; tagged content verified byte-identical to the reviewed PR heads before tagging. Open after tagging: Docker image builds (./build.sh -b cib-1.9.11.102 -i vuer_oss from vuer_build main — the legacy path, see vuer-build-never-pushes) and a TjK with blank tester/verifier names and a date mismatch (doc 2026.08.13 vs evidence 2026.08.17). NOT verified: whether earlier CIB releases used real merge commits rather than squashes
  • raiffeisen-1.9.11.100CORRECTED 2026-09-08: the release IS tagged, and the export script is still marooned on that lineage. raiffeisen-1.9.11.100 is an annotated tag, object eaeacd0799 → commit 2352f5117f, tagged 2026-08-18 12:36 +0200; the release branch chore/FKITDEV-9156-raiffeisen-release-1.9.11.100 sits at the same commit, which supersedes the note’s 2026-08-11 “NOT tagged” warning and makes the recorded branch head 350d3e626b 6 commits stale (facekom-worktree-vs-tag-trap). Still open: customization/bin/raiffeisen-facecomparison-export.js exists only on that release branch/tag — absent from origin/customization/raiffeisen (fa983a0eba) and from devel (git ls-remote + git ls-tree, 2026-09-08) ⇒ the next cut from customization/raiffeisen silently loses a delivered partner feature, along with the SLARAFIPI-84 recovery work built on top of it. Verify with ls-remote/ls-tree, never with rtk