4H Phase 1 — approver briefing: all six PRs, every change explained

Prepared 2026-08-03 for reviewer/approver discussion. Covers pakas #394, siunta #90, go-import #49, veniship #743, venipak-helper #43, and pakas #516 (parked) — what each diff does, why, and what actually happens when it merges.


1. The one-paragraph story

Phase 1 turns on the 4-Hands (4H) service for Lithuania: heavy parcels (per-package weight, LT ≥26 kg, LV/EE ≥31 kg — the communicated 25/30 kg floors plus the approved 1 kg tolerance) get flagged siunt_4rp=1 and billed the 4H tariff, doubling to 2×4H above 50 kg (LT) / 60 kg (LV·EE), with a hard eligibility ceiling at 75/90 kg. The single authoritative auto-writer is the pakas pack_4rp cron, and it only auto-flags shipments whose payer opted in (tbl_klientai.kl_4rp_auto=1, a Sales-only switch on the PAKAS client card; default is MANUAL). The three creation channels (siunta UI, go-import XML/API, veniship) are aligned to the same rule: client-declared 4H is always honored, creation-time auto-4H happens only where the payer flag is readable (siunta, go-import) and never client-blind. Nothing bills until the LT posti_service1 data flip — postcodes are the master switch, and they stay off until every gate below is green.

2. Why merging is safe now (the dormancy model)

Layer Master switch State after merge
Postcode allowlist tbl_post_info.posti_service1='1' LT mass zones off → cron + creation-time auto paths find no eligible postcodes
Payer opt-in tbl_klientai.kl_4rp_auto (VDT-3384.sql) Column defaults 0 (MANUAL) → auto-4H fires for nobody until Sales flips a client; if the SQL hasn't been applied on an env yet, every reader fails closed (no auto-4H, no error)
Client-declared 4H import XML four_hands=1, siunta checkbox, veniship service Unchanged semantics — works today, keeps working
Pricing (2×4H doubling) vendored venipak-helper release 0.8.3 + pakas pin Config maps ship in #394; the two-file library patch is PR #43 (§8) and the 0.8.3/0.9.1 tags are a separate gated step

So the merge order is flexible and low-risk: the PRs are the plumbing; the flip is the tap. The exception is veniship #743 — its merge is itself a prod deploy (see §6) and it fixes a live billing bug, so it has day-one behavior changes (all of them corrections).

3. Current path to LT enable (where we are)

  1. ✅ All five active PRs pushed and gated (suites green vs baselines, veni-guard 42/0 on each app PR, adversarial review rounds on #394/#90/#49/#743; helper #43 suite 170 green). The sixth, pakas #516, is parked off the 4H path (§9).
  2. 2 approvals per PR ← you are here.
  3. ⏳ venipak-helper PR #43 (§8) merge → tag 0.8.3 (cut from the 0.8.2 tag, patches Price.php + DirectionProvider.php scalar→country maps) → cherry-pick → 0.9.1 → pakas pin ^0.8.3.
  4. ⏳ Promotion dev → uat → prod with VDT-3384.sql applied before/with code on every env (incl. dev + demo).
  5. ⏳ Pre-flip gates: VDT-3395 (terminal re-weigh re-eval — Product ruled it Phase-1 scope 2026-08-03) · tariff reconciliation (Evelina states 14 €/28 €; programme record 6/12 €; kain_4rp schema default 50.00 — Finance must pick) · AUTO seed list (legacy four vs Confluence six — Sales) · prod kain_4rp/posti_service1 probes · client comms.
  6. ⏳ Wave-by-zone LT posti_service1 flip (zones 2→3→4→11) with per-wave monitors and a pre-staged reverse UPDATE.

Recommended review order: #43 first. It is the smallest diff (two library files + tests), and everything downstream waits on the tags it unblocks — the pakas pin is a promotion gate and siunta #90 cannot merge before the 0.9.1 tag exists. Then #394 → #90 → #49 (they share the band table and payer-gate concepts, in that order of depth), then #743 on its own release decision (merge = prod deploy), and #516 not at all until its refresh (§9).

4. pakas PR #394 (VDT-3203 + VDT-3384 fold) → dev

Branch VDT-3203dev · head 425bf63c pushed · 15 files, +676/−28 (4 prod PHP areas, 4 lang files, 1 SQL migration, 4 test files, test bootstrap).

4.1 What it is

The heart of Phase 1: the pack_4rp cron rewrite + the client AUTO/MANUAL switch (VDT-3384 fold) + the 2×4H pricing config tiers.

4.2 File-by-file

config/config.php (+8/−4) — 2×4H pricing tiers. Legacy scalars $config_2x4rp_low_limit=80 / high=100 become per-country maps with LT-derived scalar aliases:

$config_2x4rp_low_limit_by_country  = array('LT' => 50, 'LV' => 60, 'EE' => 60);
$config_2x4rp_high_limit_by_country = array('LT' => 75, 'LV' => 90, 'EE' => 90);
$config_2x4rp_low_limit  = $config_2x4rp_low_limit_by_country['LT'];   // = 50

⚠️ This is the one change that is NOT dormant at deploy (see 4.3).

inc/inc.function.php (+81/−1) — the 4H helper trio. getFourHandsPackageWeightThresholds() (single source of truth: LT 26–75, LV/EE 31–90, inclusive; max is the hard stop, not the doubling point) · isFourHandsRequiredByPackageWeight[s]() (per-package check — any single pack qualifies, never summed; unknown countries and bad input → false) · getFourHandsPackageWeightSqlCondition() (emits the identical rule as SQL so the cron cannot drift from the PHP path; values interpolated from the hardcoded table, no injection surface). Only the cron and the tests call these — no existing entry/pricing path was rewired.

mod/auto_script/pack/pack_4rp.php (+31/−17) — the cron. Old rule (removed): pack ≥26.5 kg AND volume >0.179 m³, postcode posti_zone='1', payer in hardcoded IN('89398','157673','158280','3464'). New single UPDATE:

UPDATE tbl_siuntos
  INNER JOIN tbl_pack       ON (pack_siunt_id = siunt_id AND {band-per-country} AND pack_paletes_tipas = '0')
  INNER JOIN tbl_post_info  ON (siunt_g_country = posti_salis AND siunt_g_post = posti_kodas AND posti_service1 = '1')
  INNER JOIN tbl_klientai   ON (kl_id = siunt_moketojo_id AND kl_4rp_auto = 1)
  SET siunt_4rp = '1'
  WHERE siunt_priemimo_data BETWEEN yesterday AND today
    AND UPPER(siunt_g_name) NOT LIKE '%VENIPAK PICKUP%' AND UPPER(siunt_g_name) NOT LIKE '%VENIPAK LOCKER%'

Eight deltas: per-country weight bands (volume criterion removed), pallet exclusion, posti_service1 gate (the dormancy switch), payer whitelist → kl_4rp_auto JOIN, sender-country gate (both ends Baltic, inside the band fragment), OOH receiver-name exclusion, unchanged daily window, and real error handling (logs UPDATE failed: <mysqli_error> + rows:N instead of always echoing ok). The statement stays monotonic — it only ever sets 1, never clears (that's what VDT-3395 will add).

inc/sql/VDT-3384.sql (new). ALTER TABLE tbl_klientai ADD kl_4rp_auto TINYINT(1) NOT NULL DEFAULT 0 + index + seed UPDATE … WHERE kl_id IN (89398,157673,158280,3464) → the legacy whitelist four become AUTO, everyone else MANUAL. Manual deploy artifact (house style), must run before/with the code.

mod/client/kl_add.php (+15/−4), kl_edit.php (+13/−2), kl_view.php (+6), 4 lang files (+2 each). Client-card UI for the switch. Save AND render are gated behind the kl_edit_plius permission (Sales-only — the 425bf63c amend per Product 2026-08-03); non-Sales users can't set the flag even by hand-posting the field (kl_add forces 0). kl_view shows a read-only "4H AUTO: yes/no" row to all viewers (deliberate: operators can see the client's mode; flag for approvers as an intentional asymmetry).

Tests. FourHandsWeightTest (29-case boundary matrix incl. 25.99/26.01, 30.99/31.0, 75.01-no, 90.01-no, comma decimals, case/whitespace, per-package never-sum; plus a literal assertion on the SQL fragment so SQL and PHP paths can't diverge). FourHandsPricingConfigTest (pins 50/75 + LT/LV/EE maps + cross-checks eligibility ceiling == high-limit map; docblock records that published helper 0.8.2/0.9.0 read only the scalar). Pack4rpCronTest (runs the cron's real UPDATE against CREATE TEMPORARY TABLE shadows with a column-count tripwire proving shadowing before any write; 18-row eligibility matrix asserting exactly 6 flagged; preservation test proving pre-existing siunt_4rp=1 outside criteria survives; idempotency test). tests/bootstrap.php defines placeholder DB constants only when config.local.php is absent — real creds are never overridden.

4.3 What happens if merged today

Immediate (NOT dormant): 1. The 2×4H threshold drops 80→50 for everything that reads the scalar: the vendored helper Price.php doubling and DirectionProvider label direction, plus three internal display readers (inc.siuntos.nurodymai.php:89, two courier load prints). This affects only shipments that already carry siunt_4rp=1 (manual/declared/legacy) — but for those it applies at deploy, not at the flip. LT >50 doubling is the ratified figure; LV/EE will double at >50 instead of >60 until the patched helper (0.8.3) is tagged and pinned — that is exactly why the helper release is a promotion gate. 2. The four legacy whitelist payers stop being auto-flagged the moment this merges (old cron rule gone; new rule finds no open postcodes). Deliberate, same-day effect. 3. Client-card UI appears; cron log format changes (rows:N / possible error line) — tell whoever monitors cron output.

Dormant until the LT posti_service1 flip: the entire flag-setting side — the JOIN matches zero postcodes. Dormant until VDT-3384.sql: the cron UPDATE errors on the unknown column → logged, echoes error, flags nothing (fail-closed). The client-card save would also fail — sequence the SQL first.

4.4 Reviewer flags / deliberately untouched

5. siunta PR #90 (VDT-3204) → dev

Branch VDT-3204dev · head aa31edc3 · 27 files, +1521/−30. The client-facing creation channel: seven auto-4H write points collapsed onto one decision chokepoint, gated on the payer's client-card mode.

5.1 The core: inc/inc.function.php (+120/−1)

Seven new global helpers: - getFourHandsPackageWeightThresholds() — the band table (LT 26–75, LV/EE 31–90, inclusive, per package). Doubling thresholds deliberately live elsewhere (config_2x4rp_*). - isFourHandsEligibleCountry() / isFourHandsRequiredByPackageWeight[s]() — country gate, comma-decimal normalization, non-numeric fail-closed, hard ceiling (75.01 LT is NOT auto-4H), per-package OR (never summed, order-independent). - isFourHandsAutoClient($payerId) — the client-card gate: memoized SELECT kl_4rp_auto FROM tbl_klientai per payer per request; (int)-cast (injection-safe); fail-closed on invalid payer AND on query failure (missing column → everyone MANUAL, no error — verified from code: sql() is a thin mysqli_query wrapper, no mysqli_report() anywhere, PHP 7.4 default = REPORT_OFF). - getFourHandsDoublingLowLimit() — per-country 2× threshold for label wording, scalar fallback. - isFourHandsOohReceiver() — cron-parity OOH match (VENIPAK PICKUP/LOCKER, comma-free; Pastomatas deliberately NOT matched on the 4H path, mirroring the cron). - shouldApplyFourHandsByPackageWeights(sender, receiver, postcode, name, weights, payerId)the single chokepoint, check order load-bearing: sender-country → OOH → weight band → payer AUTO → postcode 4rp. Payer before postcode = MANUAL clients cost zero postcode reads.

5.2 The seven write points (all route through the chokepoint)

  1. shipment_register_resolver.php (new shipment): operates on a copy of $weights (the originals feed the pack writer), removes pallet-flagged packs first, and guards with !isset($_POST['rp4']) — if the form posted the checkbox at all, the client decided and auto never overrides. $rp4 is only ever raised to '1'.
  2. shipment_updater.php (edit): byte-identical block.
  3. jquery.get_local_price.php (price preview): same precedence — the quoted price now includes 4H exactly when it will actually apply, so preview and saved shipment agree.
  4. lib/shipment/shipment.php::add() (international/API): previously hardcoded $rp4=0 — this path never consulted 4H at all. Now picks the true consignee in both branches (export used to see the LT depot address — a real bug fixed here: an FI export via LT depot would have been flagged off the depot) and evaluates the persisted sender (= cron's view; an FI-origin import with LT depot as persisted sender + eligible LT consignee flags '1' — deliberate cron parity, test expectation flipped to pin it).
  5. App/Service/ShipmentImportManager.php (SIM): auto-set before the pre-existing postcode gate; per-pack weights from the model. 6–7. Both XLS import paths: inline logic collapsed onto the chokepoint with the blank-vs-explicit-0 distinction (blank cell = "no opinion" → may auto-fill; literal 0 = client declaration → respected), plus the notice-free empty($postcodeInfo['4rp']) rewrite (same truth table).

5.3 Labels, UI, config

5.4 Tests

5.5 What happens if merged today

Immediate: config scalars 80→50 / 100→75; label wording shift (see 5.3); country-aware JS validation (blocks above 75/90 instead of 80); new lang strings; shipment.php stops hardcoding 0 (but can't produce a 1 without the payer flag + postcode). Dormant: all seven auto-write points need payer AUTO + posti_service1 postcode; with the column missing, everyone is MANUAL, silently (one failed memoized query per payer per request). Client-declared 4H unchanged throughout.

5.6 Reviewer flags / untouched quirks

6. veniship PR #743 (VDT-3205) → master ⚠️ merge = prod deploy

Branch VDT-3205master · head 99f5a8b (7 commits on master 5caf391) · 14 files, +1001/−7 (8 app files, 6 new test files).

6.1 Read this first: the pipeline

bitbucket-pipelines.yml: the *test step is commented out at every trigger (pull-requests, dev, uat, master) — CI runs no tests for this PR, ever. A merge to master runs *build then four parallel prod deploys (eff_master1/2, self_master1/2) with no manual gate. The PR adds no migrations, so the migrate --force calls are no-ops; the container swap is real and immediate. There is no uat hop — destination is master. The manual suite run recorded on the PR is the only test gate.

Commit shape note for reviewers: commits 1–3 built a larger design (weight auto-detect), commits 4–7 cut it back and hardened it. Review the net diff (origin/master...HEAD), not commit 1 in isolation.

6.2 The live bug this fixes

Master Rates.php hardcodes '4rp' => false in every quote request while booking sends four_hands=1 when the merchant ticks the service. Merchants are quoted a non-4H price and shipped/invoiced a 4H shipment — today, on every explicit 4H order.

6.3 File-by-file

6.4 Tests (6 files, 866 lines — revert-detection for humans; CI won't run them)

Gate test (fake wire client records every call; every assertion checks the boolean AND zero client calls — catches restoring 4rp=false, reordering the gate, reintroducing auto-detect, or dropping the provenance stamp) · booking-gate test (catches reverting to unconditional serviceEnabled) · threshold matrix (catches EE 31→26 revert; documents the gram-rounding quirk) · skip-seam test (catches removing the skip, moving the skip above the throw, or breaking the flag→skip mapping) · fillFromOrder strip test.

6.5 Day-one production behavior (merge = deploy)

Fixes of live wrongness: (1) quoted prices for explicit-4H orders go UP to the correct 4H price — the correction, but the change merchants will notice; expect support contacts; (2) locker+4H orders stop hard-failing import (136) and book cleanly without 4H; (3) no double charge on use_api_rates pricelists; (4) locker orders stop being charged 4H at order time. Behavior changes that aren't bug fixes: terminal rows are never quoted 4H even when requested (matches booking); merchant services now reach the Venipak rates API on both quote entry points (only 4rp is consumed by veniship, but the payload changes — if the courier's engine reacts to other fields, that arrives with this deploy); global rows priced from a services-less layer (master parity — and the very thing that keeps them appearing). Unchanged: non-Venipak couriers, table-pricelist charge arithmetic, and the "no charge row → cannot sell 4H" policy. veniship still performs ZERO automatic 4H detection.

6.6 Risks / residuals (stated openly in the PR body)

7. go-import PR #49 (VDT-3202) → dev

Branch VDT-3202dev · head 7fc0030 (13 linear commits on dev tip cbc30fd; composer untouched per review 2026-08-03) · 5 files, +622/-1 — only ONE production file changes (lib/import_helper.php, +83); everything else is test harness or hygiene.

7.1 The single production change: lib/import_helper.php

New private field $fourHandsAutoPermission — per-request memoization slot for the payer lookup (untyped, PHP 7.4-safe).

Weight bands (lines 118–123):

private const FOUR_HANDS_PACKAGE_WEIGHT_THRESHOLDS = array(
    'LT' => array('min' => 26.0, 'max' => 75.0),
    'EE' => array('min' => 31.0, 'max' => 90.0),
    'LV' => array('min' => 31.0, 'max' => 90.0),
);

Per-package inclusive windows; the map doubles as the "is this a 4H country" test.

The gated call site (validateXmlData, ~:1160):

if ($this->getFourHandsAutoPermission()) {
    $validShipmentData = $this->applyFourHandsPackageWeightThreshold($validShipmentData, $isPickupReceiver);
}

Ordering inside the method (all pre-existing except the last): client four_hands validated (~:905) → validateByPostcode() silent strip (:938) → locker/pickup error-136 check (:1045-1058) → new gated auto-apply (:1160). When the payer is not AUTO, not one line of new logic executes — output byte-identical to today's dev.

applyFourHandsPackageWeightThreshold() — four sequential gates, then an additive set: early-out on pickup receiver OR already-declared 4H (additive-only: can only turn 4H on, never off, never overrides the client); sender must be LT/LV/EE; receiver postcode must offer 4rp (posti_service1=1, via the shared warm cache); then per-package band check, first match wins, sets string '1'.

getFourHandsAutoPermission() — the payer gate: memoized SELECT kl_4rp_auto FROM tbl_klientai WHERE kl_id='{(int)$payerId}' (int-cast → injection-safe, guard-annotated). Fail-closed in every branch: no payer → false; query failure — including "unknown column" on an env that hasn't run VDT-3384.sql yet — → false. The import proceeds normally, just without auto-4H, no error surfaces.

validateByPostcode() condition rewrite (:2086): !($postcodeInfo && $postcodeInfo['4rp'])empty($postcodeInfo['4rp']) — behaviour-identical in every reachable state, removes a PHP 7.4 array-offset-on-bool notice. The strip stays silent (VDT-2773 contract, now characterization-tested).

7.2 Hygiene + tests

7.3 What happens if merged today

Immediate: one extra memoized SELECT per import request; the notice-free strip rewrite. Dormant: on envs without the VDT-3384 column the whole feature is silently OFF (fail-closed — merging out of order is safe, not broken); with the column, everyone defaults MANUAL; even AUTO payers need posti_service1=1 postcodes. Unchanged in all cases: client-declared 4H (honored / silently stripped / error 136 to lockers) — no error code, message, or response shape changes.

7.4 Reviewer flags / deliberately untouched

8. venipak-helper PR #43 (VDT-3203) → master — review this one first

Branch VDT-3203master · head e868978, cut from the 0.8.2 tag · 3 files, +60/−4 — src diff vs 0.8.2 is exactly two files (src/SS1/Pricing/Price.php +14/−2, src/Microservices/Label/DirectionProvider.php +14/−2) plus 7 new DirectionProviderTest cases. Suite: 170 tests green on PHP 7.4.33 (the one Redis cache test is environment-dependent and excluded — it needs the repo's compose Redis).

8.1 What it is

The published helper — 0.8.2 and 0.9.0 — reads only the legacy scalar $config_2x4rp_low_limit (historically 80) when deciding 2×4H, in two places: the price doubling in Price.php:699 and the parcel-label "2x4 hands"/"4 hands" direction in DirectionProvider.php:53. The ratified rule is per-country: double above LT 50 / LV·EE 60. #394 and #90 ship the country maps in app config, but until the library reads them, LV/EE shipments in the 51–60 kg window would be doubled (price) and mislabeled. This PR is the library half of that fix.

8.2 The change (identical pattern in both files)

Each site now also imports $config_2x4rp_low_limit_by_country, uppercases/trims the shipment's siunt_g_country, and uses the country's low limit iff the map exists, is an array, and has that key — otherwise it falls back to the legacy scalar. The doubling/label comparison itself (> $lowLimit) is untouched. Consequences of the guarded fallback: - An app that deploys helper 0.8.3 without the #394/#90 config maps behaves byte-identically to today (scalar). No consumer can be broken by the tag alone. - An app that has the maps but a row without a country (or an unknown country) also falls back to the scalar — fail-safe to current behavior, never to a wrong country's limit.

8.3 Tests

7 new data-provider cases on the label direction: LT 55 kg → "2x4 hands"; LV 55 → "4 hands"; EE 60 → "4 hands" / 61 → "2x4 hands" (boundary); lowercase lt normalized; and two no-country rows proving the scalar fallback (55 → "4 hands", 85 → "2x4 hands" against the stub scalar 80).

8.4 What happens if merged today

Nothing, anywhere — consumers pin tags, and merging to master cuts no tag. What it unblocks is the release chain: tag 0.8.3 off this branch (release gate: git diff 0.8.2..0.8.3 = exactly the two src files) → cherry-pick the patch onto the 0.9.x line → tag 0.9.1 (0.9.0 = 0.8.2 + SmsService, which siunta uses, so siunta can never re-pin down to 0.8.x) → pakas pins ^0.8.3 (a small #394 follow-up commit). The pakas pin is a promotion gate; the 0.9.1 tag is a merge gate on siunta #90.

8.5 Reviewer flags

9. pakas PR #516 (VDT-3343) → dev — parked, not on the 4H path

Branch VDT-3343dev · head 8bcfde19 · 13 commits, 14 new test files, +2668/−0 — zero production files. Do not merge it in this wave; it needs a refresh first (below). It is in this briefing so approvers know what it is and why it is deliberately not moving.

9.1 What it is

The HF/LW test groundwork from the research phase (Waves 1–3): characterization tests that pin today's behavior (charge_works write/read semantics, entity-clone (array) cast, split/late-package derivation, pricing-component structure, recount-vs-engine divergence) plus pre-ratification executable specs for the future HF/LW build (HF applicability oracle, provenance state machine, the T-NUR guard that HF must never render under Nurodymai, DPDT forward guards, return-exclusion fence, HF×LW combinatorial legality, LW ordered-eligibility). Tests only — it changes no runtime behavior and shares no files with #394 (whose test additions are all new files too), so merge order between them can never conflict textually.

9.2 Why it is parked

9.3 If an approver wants to act on it now

The only sensible action is the refresh commit (merge dev in, fix the 7-param call, 350→250, relabel docblocks, re-run the suite on PHP 7.4.33 + veni-guard, rewrite the PR body). Approving/merging it unrefreshed is the one wrong move — it would break the local suite silently.

10. Cross-PR consistency (what reviewers should check against each other)

Rule pakas #394 (cron) siunta #90 go-import #49 veniship #743 helper #43 pakas #516
Bands (per package, never summed) LT 26–75 / LV·EE 31–90, SQL fragment generated from the same table the PHP path uses identical table in inc.function.php, hard ceiling enforced identical const in import_helper.php floors only (26/31/31) in dormant helpers — runtime is explicit-request-only n/a — doubling only, no eligibility logic n/a — HF/LW tests only
Payer gate (kl_4rp_auto) INNER JOIN … kl_4rp_auto = 1 memoized isFourHandsAutoClient(), payer-before-postcode memoized getFourHandsAutoPermission(), fail-closed not readable → auto-detect removed; cron is the writer for veniship traffic n/a n/a
Missing-column behavior UPDATE errors → logged, flags nothing (fail-closed) everyone MANUAL, silent, memoized everyone MANUAL, silent, memoized n/a n/a n/a
Client-declared 4H untouched (cron is additive) never overridden (!isset($_POST['rp4']); XLS blank-vs-0) additive-only (!empty(four_hands) early-out) explicit request honored (the whole point) n/a n/a
OOH exclusion %VENIPAK PICKUP/LOCKER% name match in SQL isFourHandsOohReceiver() — same two names, comma-free, no pastomatas aliases (cron parity) pre-existing isLocker()/isPickup() heuristics (divergent — flagged, ask P11) terminal-service / pickup_point gates, first in both trees n/a n/a
Pallets pack_paletes_tipas='0' on the JOIN — the compensating control filtered where representable (resolver/updater/price); SIM/XLS/API can't represent pallets unrepresentable at creation — no code by construction pallet passes through to the courier probe only n/a n/a
Monotonicity set-only, test-locked; clearing = VDT-3395 $rp4 only raised additive-only n/a n/a n/a
2×4H config per-country maps + scalar 80→50 (live at merge) same maps, same scalar shift, labels + JS country-aware n/a n/a (pricing upstream) the library half: per-country low limit in Price.php + DirectionProvider.php, guarded scalar fallback n/a

Known, accepted divergences to mention proactively: go-import's OOH matcher vs the cron's (P11, with Product); siunta SIM's != 1 vs XLS blank-distinction; the LV/EE >50 doubling window between merge and the helper 0.8.3 pin (why #43 and its tags are a promotion gate).

11. Anticipated approver questions

"If I approve and merge, does anyone get billed 4H?" Not from the auto paths — postcodes are closed and everyone defaults MANUAL. Two real day-one effects to own: the 2×4H threshold shift for already-flagged shipments (pakas/siunta scalar 80→50; correct for LT, temporarily wrong direction for LV/EE until helper 0.8.3 — LV/EE aren't Phase-1-enabled, and the helper pin is a promotion gate), and veniship's price-quote correction (merge=prod there; the higher quotes are the fix of a live under-quote).

"Why does veniship not have the payer gate the other two channels have?" It physically can't — no legacy-DB connection, no API that carries kl_4rp_auto. Options were: ship auto-detect ungated (a client-blind writer that silently self-activates at the LT flip — the worst sequencing possible), invent a veniship-side duplicate flag (second source of truth, product decision pending), or ship explicit-only and let the cron cover AUTO payers. We shipped explicit-only. Cost: AUTO payers quoting through veniship see creation-time prices without 4H until a payer-flag mechanism exists (recorded product ask); the cron flags and bills them correctly next run.

"Who can turn a client AUTO?" Only users with kl_edit_plius (Sales) — Product ruled it a commercial field on 2026-08-03; the ACL gate is the latest #394 commit. Everyone can see the mode on the client card (deliberate).

"What clears the flag if a package is re-weighed lighter?" Nothing, today — all four PRs are set-only/additive by design. Product ruled (2026-08-03) that re-weigh re-evaluation is Phase-1 scope, so VDT-3395 now carries that mechanism and joins the flip gate. It does not block these merges; it blocks the flip.

"What if the VDT-3384.sql column isn't on an environment yet?" Everything fails closed, verified by execution: pakas cron errors-and-logs, flags nothing; siunta and go-import silently treat every payer as MANUAL. Deploy ordering (SQL before/with code) matters for correctness of the feature, not for safety.

"Can we merge these in any order?" Helper #43 first is strictly best — it is inert on merge (consumers pin tags) and everything else waits on its tags. The dev-targeted PRs (#394/#90/#49): yes, any order — each is independently dormant (except #90 must wait for the 0.9.1 tag). #743: independent of the others but is a prod deploy in its own right; it needs its own release decision and the manual-run evidence stands in for CI (which runs no tests in that repo). #516: do not merge until refreshed (§9).

"What's still open before the LT flip?" Helper 0.8.3 tag + pakas pin → promotion (SQL-before-code per env) → VDT-3395 → tariff reconciliation (Evelina: 14/28 €; programme record: 6/12 €; kain_4rp default: 50 — Finance must pick one) → AUTO seed list (legacy four vs Confluence six — Sales) → prod probes + comms → wave-by-zone flip with per-wave monitors and a pre-staged reverse UPDATE.