diff --git a/docs/netplan-v4-numeric-mapping-fix.md b/docs/netplan-v4-numeric-mapping-fix.md new file mode 100644 index 0000000..a834702 --- /dev/null +++ b/docs/netplan-v4-numeric-mapping-fix.md @@ -0,0 +1,52 @@ +# Numeric mapping identity defect - 2026-10-03 + +## Confirmed from installed code and read-only evidence + +The pending batch contains 120 observations: zero duplicate timestamps, zero +backwards pairs, but 75 identity failures. The initial 45 observations in this +batch are imported observer records; later native captures use the saved manager +configuration. Evidence: test-host `PENDING_DELIVERY_REVIEW.json`. + +The configured mapping fingerprint is +`517d1907631c7f152096e21759bb3452cdd0b4b1b73f91f1c7cd3bd62d8aaa8b`. +Reproducing native configuration loading generates +`3a1a9cf42aae49f55385d2350da74cae780ae12f170e4976849591e48a393994`. +The sole difference in the compared configurations is numeric representation of +`accounting.splitToleranceW`: prepared float 100.0 versus saved integer 100. +Both inventory fingerprints and all measurement definitions match. +`NetzfahrplanV4Bilanzierung::konfigurieren` validated this field using a function +which returns a float, but discarded the normalized return value. Other numeric +factors were already assigned from that validator. + +## Tested source correction + +Assign the returned float to `splitToleranceW`. Equal numeric settings now retain +the original fingerprint through a plain JSON property round trip. Actual numeric +changes still produce distinct fingerprints, and boolean/string values are still +rejected. Input configurations are not mutated. + +42 isolated PHP checks passed on the test host: 34 accounting regressions plus +8 numeric identity regressions. A separate check with the actual prepared capture +configuration reproduces the configured fingerprint after the property round trip. +Evidence: `/srv/agent/netplan-v4-application-build/NUMERIC_MAPPING_TEST_RESULTS.json` +and `NUMERIC_MAPPING_TEST_RESULTS.txt`. Library SHA256: +`ffda2a03dbb226a4405a2ad66704c080a9dc475ce64282ad3f0d7d60034ab199`. + +## Not deployed / recovery still incomplete + +No installed library, original outbox, cursor, server validator, settings, dataset +registration or actuator permission was changed in this turn. Future-capture +normalization alone does not recover already stored native observations. The +server correctly rejects an entire batch containing a mismatched mapping. + +An untested proposal to send distinct immutable dataset versions in separate +batches was removed from the development source and preserved only as a draft at +`/home/agent/services/qa/numeric-mapping-20261003/UNTESTED_SENDER_DRAFT.patch`. +Metadata preparation files are not an installation or permission to ingest into +an unregistered dataset. Tool writes for the complete server recovery package were +blocked; no alternative deployment or identity-check bypass was performed. + +Next recovery must preserve original records and provenance, retain exact +per-dataset identity validation, acknowledge only accepted data and include tests +for the mixed 45/75 batch, retries, partial ACKs and no cursor/data loss. Do not +skip the rejected block, rewrite hashes in raw journals or re-run old installers. diff --git a/libs/NetzfahrplanV4Bilanzierung.php b/libs/NetzfahrplanV4Bilanzierung.php index 38f61a2..a468dc4 100644 --- a/libs/NetzfahrplanV4Bilanzierung.php +++ b/libs/NetzfahrplanV4Bilanzierung.php @@ -34,7 +34,8 @@ final class NetzfahrplanV4Bilanzierung || !is_int($c['maxSkewSeconds']) || $c['maxSkewSeconds'] < 0 || $c['maxSkewSeconds'] > 60) { throw new InvalidArgumentException('Bilanzkonfiguration unvollstaendig.'); } - self::number($c['splitToleranceW'], 'Zuordnungstoleranz W', 0, 500); + // Preserve a stable numeric representation across JSON property round-trips. + $c['splitToleranceW'] = self::number($c['splitToleranceW'], 'Zuordnungstoleranz W', 0, 500); $seen=[];$keys=[];$roles=[]; foreach ($c['sources'] as &$s) { if (!is_array($s) || count($s)!==8 || array_diff(['key','role','variableId','parentId','ident','factorToW','maxAgeSeconds','dependsOn'],array_keys($s))) { diff --git a/tests/V4Accounting/checks.php b/tests/V4Accounting/checks.php index 2d9c806..64084f7 100644 --- a/tests/V4Accounting/checks.php +++ b/tests/V4Accounting/checks.php @@ -53,4 +53,14 @@ $c['sources'][0]['factorToW']=2.;check(take($c,$m,$n)['mappingId']!==$a,'factor [$c,$m,$n]=fixture();$calls=[]; fails(function()use($c,$m,$n,&$calls){B::aufnehmen($c,function($id)use($m,&$calls){$calls[$id]=($calls[$id]??0)+1;$v=$m[$id];if($calls[$id]>1)$v['value']++;return $v;},$n);},'same-second concurrent value change rejected'); $source=file_get_contents(__DIR__.'/../../libs/NetzfahrplanV4Bilanzierung.php');check(!preg_match('/\b(?:SetValue|IPS_RequestAction|RequestAction|curl_exec|IPS_SetProperty|AC_SetLoggingStatus)\s*\(/',$source),'adapter has no runtime or external write calls'); +[$c,$m,$n]=fixture();$integer=$c;$integer['splitToleranceW']=100; +check(B::konfigurieren($c)===B::konfigurieren($integer),'integer and float tolerance normalize identically'); +check(take($c,$m,$n)['mappingId']===take($integer,$m,$n)['mappingId'],'equal tolerance gives identical mapping hash'); +$roundtrip=json_decode(json_encode($c,JSON_THROW_ON_ERROR),true,64,JSON_THROW_ON_ERROR); +check(take($roundtrip,$m,$n)['mappingId']===take($c,$m,$n)['mappingId'],'plain JSON property roundtrip preserves mapping'); +$normalized=B::konfigurieren($c);check(B::konfigurieren($normalized)===$normalized,'normalization remains idempotent'); +check(is_int($integer['splitToleranceW']) && is_float($c['splitToleranceW']),'input configurations are not mutated'); +$changed=$c;$changed['splitToleranceW']=101; +check(take($changed,$m,$n)['mappingId']!==take($c,$m,$n)['mappingId'],'real tolerance change still changes identity'); +foreach([true,'100'] as $invalid){$bad=$c;$bad['splitToleranceW']=$invalid;fails(fn()=>B::konfigurieren($bad),'non-numeric tolerance remains rejected '.gettype($invalid));} echo "TOTAL $count checks passed; synthetic snapshots only.\n";