fix(accounting): stabilize numeric mapping identity across property storage
This commit is contained in:
@@ -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.
|
||||
@@ -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))) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user