@open-charging-cloud/wwcp-poi 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +155 -1
  2. package/README.md +70 -8
  3. package/dist/common.d.ts +9 -0
  4. package/dist/common.d.ts.map +1 -1
  5. package/dist/history/apply.d.ts.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1671 -198
  9. package/dist/index.js.map +1 -1
  10. package/dist/keys.d.ts.map +1 -1
  11. package/dist/network/certificates.d.ts +6 -4
  12. package/dist/network/certificates.d.ts.map +1 -1
  13. package/dist/network/evTaler.d.ts +13 -0
  14. package/dist/network/evTaler.d.ts.map +1 -1
  15. package/dist/network/identifications.d.ts +15 -2
  16. package/dist/network/identifications.d.ts.map +1 -1
  17. package/dist/network/partyKeys.d.ts.map +1 -1
  18. package/dist/network/roamingNetwork.d.ts +9 -0
  19. package/dist/network/roamingNetwork.d.ts.map +1 -1
  20. package/dist/network/versionTexts.d.ts +3 -2
  21. package/dist/network/versionTexts.d.ts.map +1 -1
  22. package/dist/network/writtenForm.d.ts +24 -0
  23. package/dist/network/writtenForm.d.ts.map +1 -0
  24. package/dist/proofs/keyStatement.d.ts +6 -1
  25. package/dist/proofs/keyStatement.d.ts.map +1 -1
  26. package/dist/proofs/signingKeyCertificate.d.ts +5 -0
  27. package/dist/proofs/signingKeyCertificate.d.ts.map +1 -1
  28. package/dist/proofs/stationProof.d.ts +3 -2
  29. package/dist/proofs/stationProof.d.ts.map +1 -1
  30. package/dist/proofs/trustContext.d.ts +50 -0
  31. package/dist/proofs/trustContext.d.ts.map +1 -0
  32. package/dist/verifier/chargingStation.d.ts +2 -1
  33. package/dist/verifier/chargingStation.d.ts.map +1 -1
  34. package/dist/verifier/trust.d.ts +3 -2
  35. package/dist/verifier/trust.d.ts.map +1 -1
  36. package/dist/verifier/version.d.ts +2 -1
  37. package/dist/verifier/version.d.ts.map +1 -1
  38. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -736,6 +736,25 @@ function optionalTimestamp(object, property, what) {
736
736
  const text = optionalString(object, property, what);
737
737
  return text === void 0 ? void 0 : parseTimestamp(text, what ?? `The timestamp '${property}'`);
738
738
  }
739
+ function timestampAt(object, property) {
740
+ const text = get(object, property);
741
+ if (typeof text !== "string")
742
+ return void 0;
743
+ try {
744
+ return parseTimestamp(text);
745
+ } catch {
746
+ return void 0;
747
+ }
748
+ }
749
+ function mandatoryText(object, property, what) {
750
+ if (get(object, property) === void 0)
751
+ throw new POIError(`Missing JSON property '${property}'!`);
752
+ return requireString(object, property, what);
753
+ }
754
+ function nonemptyText(object, property) {
755
+ const text = get(object, property);
756
+ return typeof text === "string" && text.length > 0 ? text : void 0;
757
+ }
739
758
  var timeOf = (at) => at === void 0 ? Date.now() : typeof at === "number" ? at : typeof at === "string" ? parseTimestamp(at) : at.getTime();
740
759
  function firstText(i18n2) {
741
760
  if (!isObject(i18n2))
@@ -859,6 +878,13 @@ var isDotNetBase64 = (text) => {
859
878
  const significant = text.replace(/[ \t\r\n]/gu, "");
860
879
  return significant.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/u.test(significant);
861
880
  };
881
+ var isDeterministicCbor = (bytes) => {
882
+ try {
883
+ return bytesEqual(encode(decodeDeterministicCbor(bytes)), bytes);
884
+ } catch {
885
+ return false;
886
+ }
887
+ };
862
888
  var POIPublicKey = class _POIPublicKey {
863
889
  constructor(id, key, keyBytes, notBefore, notAfter, holder, usages, json) {
864
890
  this.id = id;
@@ -914,11 +940,11 @@ var POIPublicKey = class _POIPublicKey {
914
940
  }
915
941
  try {
916
942
  key = CoseKey.parse(keyBytes);
917
- if (!bytesEqual(encode(decodeDeterministicCbor(keyBytes)), keyBytes))
918
- throw new POIError("not deterministic CBOR");
919
943
  } catch (e) {
920
944
  throw new POIError(`Invalid COSE key: ${e instanceof Error ? e.message : String(e)}`);
921
945
  }
946
+ if (!isDeterministicCbor(keyBytes))
947
+ throw new POIError(`Invalid COSE key: Invalid deterministic CBOR: the key of the public key '${id}' is not written as the deterministic CBOR of its COSE key!`);
922
948
  if (key.isPrivate)
923
949
  throw new POIError("A public key must not contain private parts!");
924
950
  const notBefore = optionalTimestamp(json, "notBefore", `The notBefore of the key '${id}'`);
@@ -1102,6 +1128,7 @@ function parseAuditPath(value, algorithm, what) {
1102
1128
  // src/network/identifications.ts
1103
1129
  var objectsOf = (value) => isArray(value) ? value.filter(isObject) : [];
1104
1130
  var idOf = (entity) => typeof entity["@id"] === "string" ? entity["@id"] : "";
1131
+ var inRoamingNetwork = (error, parameter) => `The given JSON representation of a roaming network is invalid: ${error} (Parameter '${parameter}')`;
1105
1132
  var ignoringCase = (text) => Array.from(text, (character) => {
1106
1133
  const upper = character.toUpperCase();
1107
1134
  return upper.length === character.length ? upper : character;
@@ -1170,7 +1197,80 @@ function duplicateParking(parkingOperators, operators) {
1170
1197
  ], ignoringCase) ?? givenTwice("parking sensor", childrenOf(parkingOperators, "parkingSensors"), ignoringCase) ?? givenTwice("parking space group", childrenOf(parkingOperators, "parkingSpaceGroups"), ignoringCase) ?? givenTwice("parking space template", childrenOf(parkingOperators, "parkingSpaceTemplates"), ignoringCase) ?? // Parking products compare with case.
1171
1198
  givenTwice("parking product", childrenOf(parkingOperators, "parkingProducts"), withCase);
1172
1199
  }
1173
- function duplicateIdentification(content) {
1200
+ function duplicateNotifiedBodyNumber(bodies) {
1201
+ const seen = /* @__PURE__ */ new Map();
1202
+ for (const body of bodies) {
1203
+ const number = get(body, "notifiedBodyNumber");
1204
+ if (typeof number !== "string")
1205
+ continue;
1206
+ const other = seen.get(number);
1207
+ if (other !== void 0)
1208
+ return `The notified body number '${number}' of the conformity assessment body '${idOf(body)}' is the one of the conformity assessment body '${other}' already!`;
1209
+ seen.set(number, idOf(body));
1210
+ }
1211
+ return void 0;
1212
+ }
1213
+ var PARTIES_IN_ORDER = [
1214
+ "chargingStationManufacturers",
1215
+ "eMobilityProviders",
1216
+ "gridOperators",
1217
+ "parkingOperators",
1218
+ "transparencySoftwareManufacturers",
1219
+ "conformityAssessmentBodies",
1220
+ "energyMeterManufacturers",
1221
+ "chargingStationOperators"
1222
+ ];
1223
+ var IDENTIFIED = {
1224
+ chargingStationManufacturers: void 0,
1225
+ chargingStationModels: void 0,
1226
+ transparencySoftwareManufacturers: void 0,
1227
+ conformityAssessmentBodies: void 0,
1228
+ energyMeterManufacturers: void 0,
1229
+ energyMeterModels: void 0,
1230
+ chargingStationOperators: "charging station operator",
1231
+ chargingTariffs: "charging tariff",
1232
+ chargingPools: "charging pool",
1233
+ chargingStations: "charging station",
1234
+ EVSEs: "EVSE",
1235
+ energyMeter: void 0,
1236
+ EVSETemplates: "EVSE template",
1237
+ chargingConnectors: void 0,
1238
+ chargingStationTemplates: "charging station template",
1239
+ energyMeterTemplates: void 0,
1240
+ EVSEGroups: "EVSE group",
1241
+ chargingStationGroups: "charging station group"
1242
+ };
1243
+ function invalidIdentification(content) {
1244
+ const ofEntity = (entity, kind) => {
1245
+ const id = entity["@id"];
1246
+ if (id === void 0)
1247
+ return "Missing JSON property '@id'!";
1248
+ if (typeof id !== "string" && kind !== void 0)
1249
+ return `Invalid '${kind} identification'!`;
1250
+ return within(entity);
1251
+ };
1252
+ const within = (object) => {
1253
+ for (const [property, value] of Object.entries(object)) {
1254
+ if (property === "customData" || !(property in IDENTIFIED))
1255
+ continue;
1256
+ const kind = IDENTIFIED[property];
1257
+ for (const entity of isArray(value) ? value.filter(isObject) : isObject(value) ? [value] : []) {
1258
+ const error = ofEntity(entity, kind);
1259
+ if (error !== void 0)
1260
+ return error;
1261
+ }
1262
+ }
1263
+ return void 0;
1264
+ };
1265
+ for (const property of PARTIES_IN_ORDER)
1266
+ for (const party of objectsOf(get(content, property))) {
1267
+ const error = ofEntity(party, IDENTIFIED[property]);
1268
+ if (error !== void 0)
1269
+ return error;
1270
+ }
1271
+ return void 0;
1272
+ }
1273
+ function duplicateIdentification(content, of) {
1174
1274
  const operators = objectsOf(get(content, "chargingStationOperators"));
1175
1275
  const providers = objectsOf(get(content, "eMobilityProviders"));
1176
1276
  const gridOperators = objectsOf(get(content, "gridOperators"));
@@ -1179,7 +1279,20 @@ function duplicateIdentification(content) {
1179
1279
  const bodies = objectsOf(get(content, "conformityAssessmentBodies"));
1180
1280
  const meterOwners = [...operators, ...gridOperators];
1181
1281
  const groupOwners = [...operators, ...providers];
1182
- return ofAnother(gridOperators, "gridConnectionPoints", (point, gridOperator, other) => `The grid connection point '${point}' of the grid operator '${gridOperator}' must be unique within the roaming network, but the grid operator '${other}' has it too!`) ?? givenTwice("energy meter", energyMetersOf(operators, gridOperators), ignoringCase) ?? duplicateParking(parkingOperators, operators) ?? ofAnother(softwareManufacturers, "transparencySoftware", (release, manufacturer, other) => `The transparency software release '${release}' of the manufacturer '${manufacturer}' is one of the manufacturer '${other}' already!`) ?? ofAnother(bodies, "certificates", (certificate, body, other) => `The certificate '${certificate}' of the conformity assessment body '${body}' is one of the conformity assessment body '${other}' already!`) ?? givenTwice("energy meter group", childrenOf(meterOwners, "energyMeterGroups"), ignoringCase) ?? givenTwice("EVSE group", childrenOf(groupOwners, "EVSEGroups"), groupId("EG")) ?? givenTwice("charging station group", childrenOf(groupOwners, "chargingStationGroups"), groupId("SG")) ?? givenTwice("charging pool group", childrenOf(groupOwners, "chargingPoolGroups"), groupId("PG")) ?? givenTwice("charging tariff offer", childrenOf(groupOwners, "chargingTariffOffers"), ignoringCase) ?? givenTwice("energy meter template", childrenOf(meterOwners, "energyMeterTemplates"), ignoringCase);
1282
+ const gridConnectionPoint = () => ofAnother(gridOperators, "gridConnectionPoints", (point, gridOperator, other) => `The grid connection point '${point}' of the grid operator '${gridOperator}' must be unique within the roaming network, but the grid operator '${other}' has it too!`);
1283
+ const energyMeter = () => givenTwice("energy meter", energyMetersOf(operators, gridOperators), ignoringCase);
1284
+ const parking = () => duplicateParking(parkingOperators, operators);
1285
+ const release = () => ofAnother(softwareManufacturers, "transparencySoftware", (id, manufacturer, other) => `The transparency software release '${id}' of the manufacturer '${manufacturer}' is one of the manufacturer '${other}' already!`);
1286
+ const certificate = () => ofAnother(bodies, "certificates", (id, body, other) => `The certificate '${id}' of the conformity assessment body '${body}' is one of the conformity assessment body '${other}' already!`);
1287
+ const number = () => duplicateNotifiedBodyNumber(bodies);
1288
+ const energyMeterGroup = () => givenTwice("energy meter group", childrenOf(meterOwners, "energyMeterGroups"), ignoringCase);
1289
+ const providerGroup = () => givenTwice("EVSE group", childrenOf(groupOwners, "EVSEGroups"), groupId("EG")) ?? givenTwice("charging station group", childrenOf(groupOwners, "chargingStationGroups"), groupId("SG")) ?? givenTwice("charging pool group", childrenOf(groupOwners, "chargingPoolGroups"), groupId("PG"));
1290
+ const tariffOffer = () => givenTwice("charging tariff offer", childrenOf(groupOwners, "chargingTariffOffers"), ignoringCase);
1291
+ const energyMeterTemplate = () => givenTwice("energy meter template", childrenOf(meterOwners, "energyMeterTemplates"), ignoringCase);
1292
+ if (of === "change set")
1293
+ return gridConnectionPoint() ?? energyMeter() ?? parking() ?? release() ?? certificate() ?? number() ?? energyMeterGroup() ?? providerGroup() ?? tariffOffer() ?? energyMeterTemplate();
1294
+ const ofParameter = (parameter, error) => error === void 0 ? void 0 : inRoamingNetwork(error, parameter);
1295
+ return ofParameter("GridOperators", gridConnectionPoint()) ?? ofParameter("ChargingStationOperators", energyMeter()) ?? ofParameter("ParkingOperators", parking()) ?? ofParameter("EMobilityProviders", providerGroup() ?? tariffOffer()) ?? ofParameter("TransparencySoftwareManufacturers", release()) ?? ofParameter("ConformityAssessmentBodies", certificate() ?? number()) ?? ofParameter("ChargingStationOperators", energyMeterGroup() ?? energyMeterTemplate());
1183
1296
  }
1184
1297
 
1185
1298
  // src/network/effective.ts
@@ -1513,11 +1626,11 @@ var AT_EDGES = new RegExp(`^[${WHITESPACE}]+|[${WHITESPACE}]+$`, "gu");
1513
1626
  var isText = (value) => typeof value === "string" && AS_GIVEN.test(value);
1514
1627
  var trimmed = (text) => text.replace(AT_EDGES, "");
1515
1628
  var shown = (value) => typeof value === "string" ? value : JSON.stringify(value);
1516
- function invalidText(what, entity, properties) {
1629
+ function invalidText(entity, properties) {
1517
1630
  for (const [property, description] of Object.entries(properties)) {
1518
1631
  const value = get(entity, property);
1519
1632
  if (value !== void 0 && !isText(value))
1520
- return `The ${what} '${idOf2(entity)}': Invalid ${description} '${shown(value)}'!`;
1633
+ return `Invalid ${description} '${shown(value)}'!`;
1521
1634
  }
1522
1635
  return void 0;
1523
1636
  }
@@ -1528,22 +1641,23 @@ function versionTwice(holder, whose) {
1528
1641
  for (const [property, description] of Object.entries(MODEL_VERSIONS)) {
1529
1642
  const versions = get(holder, property);
1530
1643
  if (isArray(versions) && new Set(versions).size !== versions.length)
1531
- return `A ${description} of ${whose} must not be given twice!`;
1644
+ return `A ${description} of ${whose} must not be given twice! (Parameter 'Versions')`;
1532
1645
  }
1533
1646
  return void 0;
1534
1647
  }
1535
- function invalidModelVersion(what, model) {
1648
+ function invalidModelVersion(what, kind, model) {
1536
1649
  for (const [property, description] of Object.entries(MODEL_VERSIONS)) {
1537
1650
  const versions = get(model, property);
1538
1651
  if (versions === void 0)
1539
1652
  continue;
1540
1653
  if (!isArray(versions))
1541
- return `The ${what} '${idOf2(model)}': The ${description} must be a JSON array!`;
1654
+ return `The ${description} must be a JSON array!`;
1542
1655
  const invalid = versions.find((version) => !isText(version));
1543
1656
  if (invalid !== void 0)
1544
- return `The ${what} '${idOf2(model)}': Invalid ${description} '${shown(invalid)}'!`;
1657
+ return `Invalid ${description} '${shown(invalid)}'!`;
1545
1658
  }
1546
- return versionTwice(model, `the ${what} '${idOf2(model)}'`);
1659
+ const twice2 = versionTwice(model, `the ${what} '${idOf2(model)}'`);
1660
+ return twice2 === void 0 ? void 0 : `The given JSON representation of ${kind} is invalid: ${twice2}`;
1547
1661
  }
1548
1662
  function invalidVersionText(content) {
1549
1663
  const operators = objectsOf2(get(content, "chargingStationOperators"));
@@ -1551,11 +1665,28 @@ function invalidVersionText(content) {
1551
1665
  const stations = operators.flatMap((operator) => objectsOf2(get(operator, "chargingPools"))).flatMap((pool) => objectsOf2(get(pool, "chargingStations")));
1552
1666
  const children = (parties, property) => parties.flatMap((party) => objectsOf2(get(party, property)));
1553
1667
  const first = (items, error) => items.map(error).find((found) => found !== void 0);
1554
- return first(stations, (station) => invalidText("charging station", station, DEVICE_TEXTS)) ?? first(children(operators, "chargingStationTemplates"), (template) => invalidText("charging station template", template, TEMPLATE_TEXTS)) ?? first(energyMetersOf(operators, gridOperators), (meter) => invalidText("energy meter", meter, DEVICE_TEXTS)) ?? first(children([...operators, ...gridOperators], "energyMeterTemplates"), (template) => invalidText("energy meter template", template, TEMPLATE_TEXTS)) ?? first(children(objectsOf2(get(content, "chargingStationManufacturers")), "chargingStationModels"), (model) => invalidModelVersion("charging station model", model)) ?? first(children(objectsOf2(get(content, "energyMeterManufacturers")), "energyMeterModels"), (model) => invalidModelVersion("energy meter model", model));
1668
+ return first(stations, (station) => invalidText(station, DEVICE_TEXTS)) ?? first(children(operators, "chargingStationTemplates"), (template) => invalidText(template, TEMPLATE_TEXTS)) ?? first(energyMetersOf(operators, gridOperators), (meter) => invalidText(meter, DEVICE_TEXTS)) ?? first(children([...operators, ...gridOperators], "energyMeterTemplates"), (template) => invalidText(template, TEMPLATE_TEXTS)) ?? first(children(objectsOf2(get(content, "chargingStationManufacturers")), "chargingStationModels"), (model) => invalidModelVersion("charging station model", "a charging station model", model)) ?? first(children(objectsOf2(get(content, "energyMeterManufacturers")), "energyMeterModels"), (model) => invalidModelVersion("energy meter model", "an energy meter model", model));
1555
1669
  }
1556
1670
 
1557
1671
  // src/network/certificates.ts
1558
1672
  var MODEL_PROPERTIES = ["chargingStationManufacturerId", "chargingStationModel", "energyMeterManufacturerId", "energyMeterModel"];
1673
+ var CERTIFICATE_PROPERTIES = [
1674
+ "@context",
1675
+ "@id",
1676
+ ...MODEL_PROPERTIES,
1677
+ "hardwareRevisions",
1678
+ "firmwareVersions",
1679
+ "verifiedTransparencySoftwareIds",
1680
+ "compatibleTransparencySoftwareIds",
1681
+ "energyMeters",
1682
+ "documentNumber",
1683
+ "documentURL",
1684
+ "notBefore",
1685
+ "notAfter",
1686
+ "legislation",
1687
+ "statedBy"
1688
+ ];
1689
+ var CERTIFIED_METER_PROPERTIES = ["manufacturerId", "modelId", "hardwareRevisions", "firmwareVersions"];
1559
1690
  var RELEASES = ["verifiedTransparencySoftwareIds", "compatibleTransparencySoftwareIds"];
1560
1691
  var idAt = (json, property) => {
1561
1692
  const value = get(json, property);
@@ -1630,13 +1761,26 @@ function invalidReleaseId(certificate) {
1630
1761
  function invalidEnergyMeter(meter) {
1631
1762
  if (!isObject(meter))
1632
1763
  return "Expected an object.";
1633
- for (const property of ["manufacturerId", "modelId"])
1764
+ const unknown = Object.keys(meter).find((property) => !CERTIFIED_METER_PROPERTIES.includes(property));
1765
+ if (unknown !== void 0)
1766
+ return `Unknown property '${unknown}'.`;
1767
+ for (const property of ["manufacturerId", "modelId"]) {
1768
+ if (meter[property] !== void 0 && typeof meter[property] !== "string")
1769
+ return `${property}: expected a string.`;
1634
1770
  if (idAt(meter, property) === "")
1635
1771
  return `${property}: expected a nonempty string.`;
1772
+ }
1636
1773
  const model = `certified energy meter model '${idAt(meter, "modelId")}' of '${idAt(meter, "manufacturerId")}'`;
1637
- return invalidCertifiedVersion(meter) ?? versionTwice(meter, `the ${model}`) ?? (textsOf(meter, "hardwareRevisions").length === 0 ? `The ${model} must name at least one hardware revision!` : void 0) ?? (textsOf(meter, "firmwareVersions").length === 0 ? `The ${model} must name at least one firmware version!` : void 0);
1774
+ return invalidCertifiedVersion(meter) ?? versionTwice(meter, `the ${model}`) ?? (textsOf(meter, "hardwareRevisions").length === 0 ? `The ${model} must name at least one hardware revision! (Parameter 'HardwareRevisions')` : void 0) ?? (textsOf(meter, "firmwareVersions").length === 0 ? `The ${model} must name at least one firmware version! (Parameter 'FirmwareVersions')` : void 0);
1638
1775
  }
1639
1776
  function invalidArgument(certificate) {
1777
+ const unknown = Object.keys(certificate).find((property) => !CERTIFICATE_PROPERTIES.includes(property));
1778
+ if (unknown !== void 0)
1779
+ return `Unknown property '${unknown}'.`;
1780
+ if (certificate["@id"] !== void 0 && typeof certificate["@id"] !== "string")
1781
+ return "@id: expected a string.";
1782
+ if (idAt(certificate, "@id") === "")
1783
+ return "@id: expected a nonempty string.";
1640
1784
  const modelError = MODEL_PROPERTIES.map((property) => notText(certificate, property)).find((error) => error !== void 0);
1641
1785
  if (modelError !== void 0)
1642
1786
  return modelError;
@@ -1667,25 +1811,25 @@ function invalidConstruction(certificate, id) {
1667
1811
  if (twice2 !== void 0)
1668
1812
  return twice2;
1669
1813
  if (textsOf(certificate, "hardwareRevisions").length === 0)
1670
- return `The certificate '${id}' must name at least one hardware revision of its model!`;
1814
+ return `The certificate '${id}' must name at least one hardware revision of its model! (Parameter 'HardwareRevisions')`;
1671
1815
  if (textsOf(certificate, "firmwareVersions").length === 0)
1672
- return `The certificate '${id}' must name at least one firmware version of its model!`;
1816
+ return `The certificate '${id}' must name at least one firmware version of its model! (Parameter 'FirmwareVersions')`;
1673
1817
  const legislation = get(certificate, "legislation");
1674
1818
  if (typeof legislation === "string" && trimmed(legislation) === "")
1675
- return `The certificate '${id}': A legislation must not be empty.`;
1819
+ return "A legislation must not be empty. (Parameter 'Legislation')";
1676
1820
  if (endsBeforeItBegins(certificate))
1677
- return `The certificate '${id}': notBefore must not exceed notAfter.`;
1821
+ return "notBefore must not exceed notAfter. (Parameter 'NotBefore')";
1678
1822
  const [verified = [], compatible = []] = RELEASES.map((property) => textsOf(certificate, property).map(trimmed));
1679
1823
  if (new Set(verified).size !== verified.length || new Set(compatible).size !== compatible.length)
1680
- return `The certificate '${id}': Empty or duplicate TransparencySoftware reference.`;
1824
+ return "Empty or duplicate TransparencySoftware reference.";
1681
1825
  if (verified.length === 0 && compatible.length === 0)
1682
- return `The certificate '${id}': A document must identify at least one software release.`;
1826
+ return "A document must identify at least one software release.";
1683
1827
  const verifiedKeys = new Set(verified.map(idKey));
1684
1828
  if (compatible.some((release) => verifiedKeys.has(idKey(release))))
1685
- return `The certificate '${id}': A release cannot be both verified and merely compatible in the same document.`;
1829
+ return "A release cannot be both verified and merely compatible in the same document.";
1686
1830
  const energyMeters = objectsOf2(get(certificate, "energyMeters"));
1687
1831
  if (forMeter && energyMeters.length > 0)
1688
- return `The certificate '${id}' of an energy meter model names no energy meters!`;
1832
+ return `The certificate '${id}' of an energy meter model names no energy meters! (Parameter 'EnergyMeters')`;
1689
1833
  const models = /* @__PURE__ */ new Map();
1690
1834
  for (const meter of energyMeters) {
1691
1835
  const key = `${idKey(idAt(meter, "manufacturerId"))}\0${idKey(idAt(meter, "modelId"))}`;
@@ -1693,15 +1837,47 @@ function invalidConstruction(certificate, id) {
1693
1837
  models.set(key, { first: model?.first ?? meter, count: (model?.count ?? 0) + 1 });
1694
1838
  }
1695
1839
  const twiceNamed = [...models.values()].find((model) => model.count > 1)?.first;
1696
- return twiceNamed === void 0 ? void 0 : `The energy meter model '${idAt(twiceNamed, "modelId")}' of '${idAt(twiceNamed, "manufacturerId")}' must not be given twice in the certificate '${id}'!`;
1840
+ return twiceNamed === void 0 ? void 0 : `The energy meter model '${idAt(twiceNamed, "modelId")}' of '${idAt(twiceNamed, "manufacturerId")}' must not be given twice in the certificate '${id}'! (Parameter 'EnergyMeters')`;
1697
1841
  }
1698
1842
  function invalidCertificate(certificate) {
1699
- const id = idAt(certificate, "@id");
1700
- const error = invalidArgument(certificate);
1701
- return error !== void 0 ? `The certificate '${id}': ${error}` : invalidConstruction(certificate, id);
1843
+ return invalidArgument(certificate) ?? invalidConstruction(certificate, idAt(certificate, "@id"));
1844
+ }
1845
+ var NOTIFIED_BODY_NUMBER = /^[0-9]{4}$/u;
1846
+ function invalidNotifiedTexts(body) {
1847
+ const number = get(body, "notifiedBodyNumber");
1848
+ const notifiedFor = get(body, "notifiedFor");
1849
+ if (number !== void 0 && typeof number !== "string")
1850
+ return "The notified body number must be a text!";
1851
+ if (notifiedFor !== void 0 && (!isArray(notifiedFor) || notifiedFor.some((legislation) => typeof legislation !== "string")))
1852
+ return "The legislations notified for must be a JSON array of texts!";
1853
+ const legislations = textsOf(body, "notifiedFor");
1854
+ if (legislations.slice(1).some((legislation, index) => ordinal(legislations[index] ?? "", legislation) > 0))
1855
+ return `The legislations the conformity assessment body '${typeof body["@id"] === "string" ? body["@id"] : ""}' is notified for are written in their ordinal order!`;
1856
+ return void 0;
1857
+ }
1858
+ function invalidBody(body) {
1859
+ const id = typeof body["@id"] === "string" ? body["@id"] : "";
1860
+ const invalid = (message, parameter) => `The given JSON representation of a conformity assessment body is invalid: ${message} (Parameter '${parameter}')`;
1861
+ const number = get(body, "notifiedBodyNumber");
1862
+ if (typeof number === "string" && !NOTIFIED_BODY_NUMBER.test(number))
1863
+ return invalid(`The notified body number '${number}' of the conformity assessment body '${id}' must be four digits!`, "NotifiedBodyNumber");
1864
+ const notifiedFor = textsOf(body, "notifiedFor");
1865
+ if (notifiedFor.some((legislation) => trimmed(legislation) === ""))
1866
+ return invalid(`A legislation the conformity assessment body '${id}' is notified for must not be empty!`, "NotifiedFor");
1867
+ if (new Set(notifiedFor).size !== notifiedFor.length)
1868
+ return invalid(`A legislation the conformity assessment body '${id}' is notified for must not be given twice!`, "NotifiedFor");
1869
+ const notNotified = objectsOf2(get(body, "certificates")).sort((left, right) => ordinal(idAt(left, "@id"), idAt(right, "@id"))).find((certificate) => typeof certificate["legislation"] === "string" && !notifiedFor.includes(certificate["legislation"]));
1870
+ if (notNotified !== void 0)
1871
+ return invalid(`The certificate '${idAt(notNotified, "@id")}' is issued under the legislation '${notNotified["legislation"]}', which the conformity assessment body '${id}' is not notified for!`, "Certificates");
1872
+ return void 0;
1702
1873
  }
1703
1874
  function invalidCertificates(content) {
1704
- return objectsOf2(get(content, "conformityAssessmentBodies")).flatMap((body) => objectsOf2(get(body, "certificates"))).map(invalidCertificate).find((error) => error !== void 0);
1875
+ for (const body of objectsOf2(get(content, "conformityAssessmentBodies"))) {
1876
+ const error = invalidNotifiedTexts(body) ?? objectsOf2(get(body, "certificates")).map(invalidCertificate).find((certificateError) => certificateError !== void 0) ?? invalidBody(body);
1877
+ if (error !== void 0)
1878
+ return error;
1879
+ }
1880
+ return void 0;
1705
1881
  }
1706
1882
 
1707
1883
  // src/qidigital/json.ts
@@ -1973,6 +2149,8 @@ var EV_TALER_EXCHANGE_CONTEXT = CONTEXT_PREFIX + "roamingNetwork/evTalerExchange
1973
2149
  var EV_TALER_PAYMENT_OPTION = "EVTaler";
1974
2150
  var EV_TALER_PAYMENT_MEANS = "EV_TALER";
1975
2151
  var CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
2152
+ var MERCHANT_PROPERTIES = ["@context", "merchantBaseURL", "merchantPublicKey", "exchanges"];
2153
+ var EXCHANGE_PROPERTIES = ["@context", "masterPublicKey", "baseURL", "currency", "name"];
1976
2154
  var CURRENCIES = new Set(`EUR CHF USD CZK GBP HRK PLN RON RSD RUB SEK AED AFN ALL AMD AOA ARS AUD AWG AZN BAM BBD BDT BGN BHD BIF
1977
2155
  BMD BND BOB BRL BSD BTN BWP BYN BZD CAD CDF CLP CNY COP CRC CUP CVE DJF DKK DOP DZD EGP ERN ETB FJD FKP
1978
2156
  GEL GHS GIP GMD GNF GTQ GYD HKD HNL HTG HUF IDR ILS INR IQD IRR ISK JMD JOD JPY KES KGS KHR KMF KPW KRW
@@ -2044,6 +2222,26 @@ function lenientI18N(value) {
2044
2222
  }
2045
2223
  return [...texts2];
2046
2224
  }
2225
+ var unread = (json, properties) => Object.keys(json).find((property) => !properties.includes(property));
2226
+ function notWrittenAsRead(json, read) {
2227
+ if ("merchantPublicKey" in read) {
2228
+ const property2 = unread(json, MERCHANT_PROPERTIES);
2229
+ if (property2 !== void 0)
2230
+ return `The EV Taler merchant must not name the property '${property2}'!`;
2231
+ if (get(json, "merchantBaseURL") !== read.merchantBaseURL)
2232
+ return `The merchant base URL '${tokenText(get(json, "merchantBaseURL") ?? "")}' is written as '${read.merchantBaseURL}'!`;
2233
+ return void 0;
2234
+ }
2235
+ const exchange = (error) => `Invalid exchange of an EV Taler merchant: ${error}`;
2236
+ const property = unread(json, EXCHANGE_PROPERTIES);
2237
+ if (property !== void 0)
2238
+ return exchange(`The exchange '${read.masterPublicKey}' must not name the property '${property}'!`);
2239
+ if (get(json, "baseURL") !== read.baseURL)
2240
+ return exchange(`The base URL '${tokenText(get(json, "baseURL") ?? "")}' of the exchange '${read.masterPublicKey}' is written as '${read.baseURL}'!`);
2241
+ if (get(json, "currency") !== read.currency)
2242
+ return exchange(`The currency '${tokenText(get(json, "currency") ?? "")}' of the exchange '${read.masterPublicKey}' is written as '${read.currency}'!`);
2243
+ return void 0;
2244
+ }
2047
2245
  function exchangeOf(json) {
2048
2246
  const context = get(json, "@context");
2049
2247
  if (context !== void 0 && context !== EV_TALER_EXCHANGE_CONTEXT)
@@ -2089,6 +2287,7 @@ function evTalerMerchantOf(value) {
2089
2287
  if (!isArray(exchangesJSON))
2090
2288
  return invalid("The exchanges of an EV Taler merchant must be a JSON array!");
2091
2289
  const exchanges = [];
2290
+ const read = [];
2092
2291
  for (const exchangeJSON of exchangesJSON) {
2093
2292
  if (!isObject(exchangeJSON))
2094
2293
  return invalid("An exchange of an EV Taler merchant must be a JSON object!");
@@ -2096,6 +2295,7 @@ function evTalerMerchantOf(value) {
2096
2295
  if (typeof exchange === "string")
2097
2296
  return invalid(`Invalid exchange of an EV Taler merchant: ${exchange}`);
2098
2297
  exchanges.push(exchange);
2298
+ read.push([exchangeJSON, exchange]);
2099
2299
  }
2100
2300
  const urlError = invalidBaseURL(merchantBaseURL, "merchant base URL");
2101
2301
  if (urlError !== void 0)
@@ -2104,11 +2304,13 @@ function evTalerMerchantOf(value) {
2104
2304
  return invalid("A merchant accepts at least one exchange! (Parameter 'Exchanges')");
2105
2305
  if (new Set(exchanges.map((exchange) => exchange.masterPublicKey)).size !== exchanges.length)
2106
2306
  return invalid("An exchange must not be given twice! (Parameter 'Exchanges')");
2107
- return {
2307
+ const merchant = {
2108
2308
  merchantBaseURL: merchantBaseURL.text,
2109
2309
  merchantPublicKey,
2110
2310
  exchanges: exchanges.sort((left, right) => ordinal(left.masterPublicKey, right.masterPublicKey))
2111
2311
  };
2312
+ const notWritten2 = notWrittenAsRead(value, merchant) ?? read.map(([json, exchange]) => notWrittenAsRead(json, exchange)).find((error) => error !== void 0);
2313
+ return notWritten2 === void 0 ? merchant : invalid(notWritten2);
2112
2314
  }
2113
2315
  var namesEVTaler = (paymentOptions) => isArray(paymentOptions) && paymentOptions.some((option) => typeof option === "string" && ignoringCase(trimmed(option)) === ignoringCase(EV_TALER_PAYMENT_OPTION));
2114
2316
  function invalidPaymentOptions(entity, what) {
@@ -2176,6 +2378,7 @@ var PARTIES = [
2176
2378
  ["energyMeterManufacturers", "energy meter manufacturer", "an"],
2177
2379
  ["chargingStationOperators", "charging station operator", "a"]
2178
2380
  ];
2381
+ var KEY_PROPERTIES = ["@id", "@context", "key", "notBefore", "notAfter", "holder", "usages"];
2179
2382
  function keysOf(party, property, description) {
2180
2383
  const value = get(party, property);
2181
2384
  if (value === void 0)
@@ -2186,7 +2389,9 @@ function keysOf(party, property, description) {
2186
2389
  if (!isObject(item))
2187
2390
  throw new POIError(`Invalid ${description} '${compactJSON(item)}'!`);
2188
2391
  try {
2189
- return POIPublicKey.parse(item);
2392
+ const key = POIPublicKey.parse(item);
2393
+ onlyProperties(item, KEY_PROPERTIES, `The public key '${key.id}'`);
2394
+ return key;
2190
2395
  } catch (e) {
2191
2396
  throw new POIError(`Invalid ${description} '${compactJSON(item)}': ${e instanceof Error ? e.message : String(e)}`);
2192
2397
  }
@@ -2229,6 +2434,1139 @@ function invalidPartyKeys(content) {
2229
2434
  return void 0;
2230
2435
  }
2231
2436
 
2437
+ // src/network/writtenForm.ts
2438
+ var idOf4 = (value) => isObject(value) && typeof value["@id"] === "string" ? value["@id"] : "";
2439
+ var ORDERED_TEXTS = /* @__PURE__ */ new Set([
2440
+ "facilities",
2441
+ "paymentOptions",
2442
+ "uiFeatures",
2443
+ "features",
2444
+ "parkingRestrictions",
2445
+ "services",
2446
+ "standards",
2447
+ "nutsCodes",
2448
+ "vehicleTypes",
2449
+ "euVehicleCategories",
2450
+ "parkingSpaceIds",
2451
+ "certificateIds",
2452
+ "chargingTariffIds",
2453
+ "acceptedEMobilityProviderIds",
2454
+ "paymentMeans",
2455
+ "paymentBrands",
2456
+ "signatureMethods",
2457
+ "connectorTypes",
2458
+ "EVSEIds",
2459
+ "chargingStationIds",
2460
+ "chargingPoolIds",
2461
+ "energyMeterIds",
2462
+ "EVSEGroupIds",
2463
+ "chargingStationGroupIds",
2464
+ "chargingPoolGroupIds",
2465
+ "chargingStationOperatorIds",
2466
+ "eMobilityProviderIds",
2467
+ "parkingSensorIds",
2468
+ "parkingProductIds",
2469
+ "measurands",
2470
+ "notifiedFor",
2471
+ "verifiedTransparencySoftwareIds",
2472
+ "compatibleTransparencySoftwareIds",
2473
+ "cardTypes",
2474
+ "dimensions",
2475
+ "usages"
2476
+ ]);
2477
+ var LEFT_OUT_WHEN_EMPTY = /* @__PURE__ */ new Set([
2478
+ "acceptedEMobilityProviderIds",
2479
+ "authenticationModes",
2480
+ "brands",
2481
+ "cardTypes",
2482
+ "certificateIds",
2483
+ "certificates",
2484
+ "chargingConnectors",
2485
+ "chargingPoolGroupIds",
2486
+ "chargingPoolGroups",
2487
+ "chargingPoolIds",
2488
+ "chargingPoolTemplates",
2489
+ "chargingPools",
2490
+ "chargingStationGroupIds",
2491
+ "chargingStationGroups",
2492
+ "chargingStationIds",
2493
+ "chargingStationManufacturers",
2494
+ "chargingStationModels",
2495
+ "chargingStationOperatorIds",
2496
+ "chargingStationOperators",
2497
+ "chargingStationTemplates",
2498
+ "chargingStations",
2499
+ "chargingTariffGroups",
2500
+ "chargingTariffIds",
2501
+ "chargingTariffOffers",
2502
+ "chargingTariffTemplates",
2503
+ "chargingTariffs",
2504
+ "conditions",
2505
+ "conformityAssessmentBodies",
2506
+ "connectorTypes",
2507
+ "daysOfWeek",
2508
+ "dimensions",
2509
+ "eMobilityProviderIds",
2510
+ "eMobilityProviders",
2511
+ "euVehicleCategories",
2512
+ "EVSEGroupIds",
2513
+ "EVSEGroups",
2514
+ "EVSEIds",
2515
+ "EVSETemplates",
2516
+ "EVSEs",
2517
+ "energyMeterGroups",
2518
+ "energyMeterIds",
2519
+ "energyMeterManufacturers",
2520
+ "energyMeterModels",
2521
+ "energyMeterTemplates",
2522
+ "energyMeters",
2523
+ "facilities",
2524
+ "features",
2525
+ "gridConnectionPoints",
2526
+ "gridOperators",
2527
+ "identityKeys",
2528
+ "images",
2529
+ "locationLanguages",
2530
+ "marketLocationIds",
2531
+ "measurands",
2532
+ "meteringLocationIds",
2533
+ "models",
2534
+ "notifiedFor",
2535
+ "nutsCodes",
2536
+ "parkingGarages",
2537
+ "parkingOperators",
2538
+ "parkingProductIds",
2539
+ "parkingProducts",
2540
+ "parkingRestrictions",
2541
+ "parkingSensorIds",
2542
+ "parkingSensors",
2543
+ "parkingSpaceGroups",
2544
+ "parkingSpaceIds",
2545
+ "parkingSpaceTemplates",
2546
+ "parkingSpaces",
2547
+ "paymentBrands",
2548
+ "paymentMeans",
2549
+ "paymentOptions",
2550
+ "publicKeys",
2551
+ "relatedLocations",
2552
+ "services",
2553
+ "signatureMethods",
2554
+ "signingKeys",
2555
+ "standards",
2556
+ "templateIds",
2557
+ "transparencySoftware",
2558
+ "transparencySoftwareManufacturers",
2559
+ "transparencySoftwareStatus",
2560
+ "uiFeatures",
2561
+ "usages",
2562
+ "vehicleTypes"
2563
+ ]);
2564
+ var TEXTS_IN_LANGUAGES = /* @__PURE__ */ new Set(["name", "description", "comment", "assistanceServiceDetails", "arrivalInstructions", "additionalInformation"]);
2565
+ var WRITTEN_ALSO_EMPTY = {
2566
+ certificates: ["verifiedTransparencySoftwareIds", "compatibleTransparencySoftwareIds"],
2567
+ openSourceLicenses: ["URLs"]
2568
+ };
2569
+ var PARTY_NAMES = {
2570
+ chargingStationOperators: "Charging Station Operator",
2571
+ eMobilityProviders: "E-Mobility Provider",
2572
+ chargingStationManufacturers: "Charging Station Manufacturer",
2573
+ energyMeterManufacturers: "Energy Meter Manufacturer",
2574
+ transparencySoftwareManufacturers: "Transparency Software Manufacturer",
2575
+ conformityAssessmentBodies: "Conformity Assessment Body"
2576
+ };
2577
+ var KEY_PROPERTIES2 = ["@id", "@context", "key", "notBefore", "notAfter", "holder", "usages"];
2578
+ var ENTITY_PROPERTIES = {
2579
+ "": [
2580
+ "@context",
2581
+ "@id",
2582
+ "chargingStationManufacturerIds",
2583
+ "chargingStationManufacturers",
2584
+ "chargingStationOperatorIds",
2585
+ "chargingStationOperators",
2586
+ "conformityAssessmentBodies",
2587
+ "conformityAssessmentBodyIds",
2588
+ "created",
2589
+ "customData",
2590
+ "dataLicenseIds",
2591
+ "dataLicenses",
2592
+ "dataSource",
2593
+ "description",
2594
+ "eMobilityProviderIds",
2595
+ "eMobilityProviders",
2596
+ "eTags",
2597
+ "energyMeterManufacturerIds",
2598
+ "energyMeterManufacturers",
2599
+ "gridOperatorIds",
2600
+ "gridOperators",
2601
+ "lastChange",
2602
+ "name",
2603
+ "operator",
2604
+ "parkingOperatorIds",
2605
+ "parkingOperators",
2606
+ "transparencySoftwareManufacturerIds",
2607
+ "transparencySoftwareManufacturers",
2608
+ "treeHashAlgorithms"
2609
+ ],
2610
+ "operator": [
2611
+ "@context",
2612
+ "@id",
2613
+ "address",
2614
+ "created",
2615
+ "customData",
2616
+ "dataSource",
2617
+ "description",
2618
+ "eMailAddress",
2619
+ "homepage",
2620
+ "hotline",
2621
+ "hotlineEMailAddress",
2622
+ "identityKeys",
2623
+ "keyStatement",
2624
+ "lastChange",
2625
+ "minConsensus",
2626
+ "name",
2627
+ "signingKeyCertificates"
2628
+ ],
2629
+ "chargingStationOperators": [
2630
+ "@context",
2631
+ "@id",
2632
+ "EVSEGroupIds",
2633
+ "EVSEGroups",
2634
+ "EVSETemplateIds",
2635
+ "EVSETemplates",
2636
+ "address",
2637
+ "brandIds",
2638
+ "brands",
2639
+ "chargingPoolGroupIds",
2640
+ "chargingPoolGroups",
2641
+ "chargingPoolIds",
2642
+ "chargingPoolTemplateIds",
2643
+ "chargingPoolTemplates",
2644
+ "chargingPools",
2645
+ "chargingStationGroupIds",
2646
+ "chargingStationGroups",
2647
+ "chargingStationTemplateIds",
2648
+ "chargingStationTemplates",
2649
+ "chargingTariffGroupIds",
2650
+ "chargingTariffGroups",
2651
+ "chargingTariffIds",
2652
+ "chargingTariffOfferIds",
2653
+ "chargingTariffOffers",
2654
+ "chargingTariffTemplateIds",
2655
+ "chargingTariffTemplates",
2656
+ "chargingTariffs",
2657
+ "created",
2658
+ "customData",
2659
+ "dataLicenseIds",
2660
+ "dataLicenses",
2661
+ "dataSource",
2662
+ "description",
2663
+ "eMailAddress",
2664
+ "energyMeterGroupIds",
2665
+ "energyMeterGroups",
2666
+ "energyMeterTemplateIds",
2667
+ "energyMeterTemplates",
2668
+ "evTaler",
2669
+ "exception",
2670
+ "geoLocation",
2671
+ "homepage",
2672
+ "hotline",
2673
+ "identityKeys",
2674
+ "lastChange",
2675
+ "logos",
2676
+ "name",
2677
+ "parkingSpaceIds",
2678
+ "parkingSpaces",
2679
+ "signingKeys",
2680
+ "stackTrace",
2681
+ "telephone",
2682
+ "termsAndConditions",
2683
+ "uri"
2684
+ ],
2685
+ "eMobilityProviders": [
2686
+ "@context",
2687
+ "@id",
2688
+ "EVSEGroupIds",
2689
+ "EVSEGroups",
2690
+ "address",
2691
+ "chargingPoolGroupIds",
2692
+ "chargingPoolGroups",
2693
+ "chargingStationGroupIds",
2694
+ "chargingStationGroups",
2695
+ "chargingTariffOfferIds",
2696
+ "chargingTariffOffers",
2697
+ "created",
2698
+ "customData",
2699
+ "dataLicenses",
2700
+ "dataSource",
2701
+ "description",
2702
+ "eMailAddress",
2703
+ "geoLocation",
2704
+ "homepage",
2705
+ "hotline",
2706
+ "identityKeys",
2707
+ "lastChange",
2708
+ "logos",
2709
+ "name",
2710
+ "signingKeys",
2711
+ "telephone",
2712
+ "termsAndConditions",
2713
+ "uri"
2714
+ ],
2715
+ "gridOperators": [
2716
+ "@context",
2717
+ "@id",
2718
+ "address",
2719
+ "created",
2720
+ "customData",
2721
+ "dataSource",
2722
+ "description",
2723
+ "energyMeterGroupIds",
2724
+ "energyMeterGroups",
2725
+ "energyMeterTemplateIds",
2726
+ "energyMeterTemplates",
2727
+ "gridConnectionPointIds",
2728
+ "gridConnectionPoints",
2729
+ "homepage",
2730
+ "hotline",
2731
+ "identityKeys",
2732
+ "lastChange",
2733
+ "name",
2734
+ "signingKeys"
2735
+ ],
2736
+ "parkingOperators": [
2737
+ "@context",
2738
+ "@id",
2739
+ "address",
2740
+ "created",
2741
+ "customData",
2742
+ "dataSource",
2743
+ "description",
2744
+ "homepage",
2745
+ "hotline",
2746
+ "identityKeys",
2747
+ "lastChange",
2748
+ "name",
2749
+ "parkingGarageIds",
2750
+ "parkingGarages",
2751
+ "parkingProductIds",
2752
+ "parkingProducts",
2753
+ "parkingSensorIds",
2754
+ "parkingSensors",
2755
+ "parkingSpaceGroupIds",
2756
+ "parkingSpaceGroups",
2757
+ "parkingSpaceIds",
2758
+ "parkingSpaceTemplateIds",
2759
+ "parkingSpaceTemplates",
2760
+ "parkingSpaces",
2761
+ "signingKeys"
2762
+ ],
2763
+ "chargingStationManufacturers": [
2764
+ "@context",
2765
+ "@id",
2766
+ "address",
2767
+ "chargingStationModelIds",
2768
+ "chargingStationModels",
2769
+ "created",
2770
+ "customData",
2771
+ "dataLicenses",
2772
+ "dataSource",
2773
+ "description",
2774
+ "eMailAddress",
2775
+ "geoLocation",
2776
+ "homepage",
2777
+ "hotline",
2778
+ "identityKeys",
2779
+ "lastChange",
2780
+ "logos",
2781
+ "name",
2782
+ "signingKeys",
2783
+ "telephone",
2784
+ "termsAndConditions",
2785
+ "uri"
2786
+ ],
2787
+ "energyMeterManufacturers": [
2788
+ "@context",
2789
+ "@id",
2790
+ "address",
2791
+ "created",
2792
+ "customData",
2793
+ "dataLicenses",
2794
+ "dataSource",
2795
+ "description",
2796
+ "eMailAddress",
2797
+ "energyMeterModelIds",
2798
+ "energyMeterModels",
2799
+ "geoLocation",
2800
+ "homepage",
2801
+ "hotline",
2802
+ "identityKeys",
2803
+ "lastChange",
2804
+ "logos",
2805
+ "name",
2806
+ "signingKeys",
2807
+ "telephone",
2808
+ "termsAndConditions",
2809
+ "uri"
2810
+ ],
2811
+ "transparencySoftwareManufacturers": [
2812
+ "@context",
2813
+ "@id",
2814
+ "address",
2815
+ "craRole",
2816
+ "created",
2817
+ "customData",
2818
+ "dataLicenses",
2819
+ "dataSource",
2820
+ "description",
2821
+ "eMailAddress",
2822
+ "geoLocation",
2823
+ "homepage",
2824
+ "hotline",
2825
+ "identityKeys",
2826
+ "lastChange",
2827
+ "logos",
2828
+ "name",
2829
+ "securityAdvisories",
2830
+ "signingKeys",
2831
+ "telephone",
2832
+ "termsAndConditions",
2833
+ "transparencySoftware",
2834
+ "transparencySoftwareIds",
2835
+ "uri",
2836
+ "vulnerabilityContact",
2837
+ "vulnerabilityDisclosurePolicy",
2838
+ "vulnerabilityReporting"
2839
+ ],
2840
+ "conformityAssessmentBodies": [
2841
+ "@context",
2842
+ "@id",
2843
+ "address",
2844
+ "certificateIds",
2845
+ "certificates",
2846
+ "created",
2847
+ "customData",
2848
+ "dataLicenses",
2849
+ "dataSource",
2850
+ "description",
2851
+ "eMailAddress",
2852
+ "geoLocation",
2853
+ "homepage",
2854
+ "hotline",
2855
+ "identityKeys",
2856
+ "lastChange",
2857
+ "logos",
2858
+ "name",
2859
+ "notifiedBodyNumber",
2860
+ "notifiedFor",
2861
+ "signingKeys",
2862
+ "telephone",
2863
+ "termsAndConditions",
2864
+ "uri"
2865
+ ],
2866
+ "chargingTariffs": [
2867
+ "@context",
2868
+ "@id",
2869
+ "created",
2870
+ "currency",
2871
+ "customData",
2872
+ "dataSource",
2873
+ "description",
2874
+ "elements",
2875
+ "energyMix",
2876
+ "lastChange",
2877
+ "maxPrice",
2878
+ "minPrice",
2879
+ "name",
2880
+ "paymentBrands",
2881
+ "paymentMeans",
2882
+ "tariffType",
2883
+ "tariffURL",
2884
+ "templateIds",
2885
+ "validFrom",
2886
+ "validUntil"
2887
+ ],
2888
+ "chargingTariffTemplates": [
2889
+ "@context",
2890
+ "@id",
2891
+ "created",
2892
+ "currency",
2893
+ "customData",
2894
+ "dataSource",
2895
+ "description",
2896
+ "elements",
2897
+ "energyMix",
2898
+ "lastChange",
2899
+ "maxPrice",
2900
+ "minPrice",
2901
+ "name",
2902
+ "paymentBrands",
2903
+ "paymentMeans",
2904
+ "tariffType",
2905
+ "tariffURL",
2906
+ "validFrom",
2907
+ "validUntil"
2908
+ ],
2909
+ "chargingTariffGroups": [
2910
+ "@context",
2911
+ "@id",
2912
+ "chargingTariffIds",
2913
+ "created",
2914
+ "customData",
2915
+ "dataSource",
2916
+ "description",
2917
+ "lastChange",
2918
+ "name"
2919
+ ],
2920
+ "chargingTariffOffers": [
2921
+ "@context",
2922
+ "@id",
2923
+ "EVSEGroupIds",
2924
+ "chargingPoolGroupIds",
2925
+ "chargingStationGroupIds",
2926
+ "chargingStationOperatorIds",
2927
+ "chargingTariff",
2928
+ "created",
2929
+ "customData",
2930
+ "dataSource",
2931
+ "description",
2932
+ "discount",
2933
+ "eMobilityProviderIds",
2934
+ "lastChange",
2935
+ "maxSessionsPerContract",
2936
+ "name",
2937
+ "notAfter",
2938
+ "notBefore",
2939
+ "openingTimes",
2940
+ "sessions"
2941
+ ],
2942
+ "elements": ["@context", "priceComponents", "restrictions"],
2943
+ "chargingPools": [
2944
+ "@context",
2945
+ "@id",
2946
+ "accessibility",
2947
+ "address",
2948
+ "arrivalInstructions",
2949
+ "assistanceServiceDetails",
2950
+ "authenticationModes",
2951
+ "brandIds",
2952
+ "brands",
2953
+ "chargingStationIds",
2954
+ "chargingStations",
2955
+ "chargingWhenClosed",
2956
+ "created",
2957
+ "customData",
2958
+ "dataLicenses",
2959
+ "dataSource",
2960
+ "description",
2961
+ "energyMeterIds",
2962
+ "energyMeters",
2963
+ "energyMix",
2964
+ "entranceAddress",
2965
+ "entranceLocation",
2966
+ "exitAddress",
2967
+ "exitLocation",
2968
+ "facilities",
2969
+ "features",
2970
+ "geoLocation",
2971
+ "gridConnectionPoint",
2972
+ "gridConnectionPointId",
2973
+ "hotline",
2974
+ "images",
2975
+ "lastChange",
2976
+ "locationLanguages",
2977
+ "name",
2978
+ "nutsCodes",
2979
+ "openingTimes",
2980
+ "parkingRestrictions",
2981
+ "parkingType",
2982
+ "paymentOptions",
2983
+ "relatedLocations",
2984
+ "services",
2985
+ "standards",
2986
+ "templateIds",
2987
+ "timeZone",
2988
+ "uiFeatures"
2989
+ ],
2990
+ "chargingPoolTemplates": [
2991
+ "@context",
2992
+ "@id",
2993
+ "accessibility",
2994
+ "address",
2995
+ "arrivalInstructions",
2996
+ "assistanceServiceDetails",
2997
+ "authenticationModes",
2998
+ "brandIds",
2999
+ "brands",
3000
+ "chargingTariffId",
3001
+ "chargingWhenClosed",
3002
+ "created",
3003
+ "customData",
3004
+ "dataLicenseIds",
3005
+ "dataLicenses",
3006
+ "dataSource",
3007
+ "description",
3008
+ "energyMix",
3009
+ "entranceAddress",
3010
+ "entranceLocation",
3011
+ "exitAddress",
3012
+ "exitLocation",
3013
+ "facilities",
3014
+ "features",
3015
+ "geoLocation",
3016
+ "gridConnectionPointId",
3017
+ "hotline",
3018
+ "images",
3019
+ "lastChange",
3020
+ "locationLanguages",
3021
+ "name",
3022
+ "nutsCodes",
3023
+ "openingTimes",
3024
+ "parkingRestrictions",
3025
+ "parkingType",
3026
+ "paymentOptions",
3027
+ "relatedLocations",
3028
+ "services",
3029
+ "standards",
3030
+ "timeZone",
3031
+ "uiFeatures"
3032
+ ],
3033
+ "chargingPoolGroups": [
3034
+ "@context",
3035
+ "@id",
3036
+ "chargingPoolIds",
3037
+ "created",
3038
+ "customData",
3039
+ "dataSource",
3040
+ "description",
3041
+ "lastChange",
3042
+ "name"
3043
+ ],
3044
+ "chargingStations": [
3045
+ "@context",
3046
+ "@id",
3047
+ "EVSEIds",
3048
+ "EVSEs",
3049
+ "accessibility",
3050
+ "address",
3051
+ "brandIds",
3052
+ "brands",
3053
+ "calibrationInfo",
3054
+ "certificateIds",
3055
+ "certificationInfo",
3056
+ "created",
3057
+ "customData",
3058
+ "dataSource",
3059
+ "description",
3060
+ "energyMeterIds",
3061
+ "energyMeters",
3062
+ "euVehicleCategories",
3063
+ "features",
3064
+ "firmwareVersion",
3065
+ "geoLocation",
3066
+ "hardwareRevision",
3067
+ "hotline",
3068
+ "images",
3069
+ "lastChange",
3070
+ "manufacturerId",
3071
+ "maxPower",
3072
+ "modelId",
3073
+ "modelURL",
3074
+ "name",
3075
+ "openingTimes",
3076
+ "parkingSpaceIds",
3077
+ "physicalReference",
3078
+ "publicKeys",
3079
+ "serialNumber",
3080
+ "signatureMethods",
3081
+ "templateIds",
3082
+ "transparencySoftwareStatus",
3083
+ "vehicleTypes"
3084
+ ],
3085
+ "chargingStationTemplates": [
3086
+ "@context",
3087
+ "@id",
3088
+ "accessibility",
3089
+ "address",
3090
+ "brandIds",
3091
+ "brands",
3092
+ "calibrationInfo",
3093
+ "certificateIds",
3094
+ "certificationInfo",
3095
+ "chargingTariffId",
3096
+ "created",
3097
+ "customData",
3098
+ "dataSource",
3099
+ "description",
3100
+ "euVehicleCategories",
3101
+ "features",
3102
+ "firmwareVersion",
3103
+ "geoLocation",
3104
+ "hardwareRevision",
3105
+ "hotline",
3106
+ "images",
3107
+ "lastChange",
3108
+ "manufacturerId",
3109
+ "maxPower",
3110
+ "modelId",
3111
+ "modelURL",
3112
+ "name",
3113
+ "openingTimes",
3114
+ "parkingSpaceIds",
3115
+ "physicalReference",
3116
+ "signatureMethods",
3117
+ "transparencySoftwareStatus",
3118
+ "vehicleTypes"
3119
+ ],
3120
+ "chargingStationGroups": [
3121
+ "@context",
3122
+ "@id",
3123
+ "chargingStationIds",
3124
+ "created",
3125
+ "customData",
3126
+ "dataSource",
3127
+ "description",
3128
+ "lastChange",
3129
+ "name"
3130
+ ],
3131
+ "EVSEs": [
3132
+ "@context",
3133
+ "@id",
3134
+ "acceptedEMobilityProviderIds",
3135
+ "brandIds",
3136
+ "brands",
3137
+ "chargingConnectorIds",
3138
+ "chargingConnectors",
3139
+ "chargingModes",
3140
+ "chargingTariffIds",
3141
+ "created",
3142
+ "currentTypes",
3143
+ "customData",
3144
+ "dataSource",
3145
+ "description",
3146
+ "energyMeter",
3147
+ "extendedOperationTimeout",
3148
+ "floorLevel",
3149
+ "geoLocation",
3150
+ "images",
3151
+ "iso15118Certificate",
3152
+ "lastChange",
3153
+ "maxCurrent",
3154
+ "maxPower",
3155
+ "maxReservationDuration",
3156
+ "maxVoltage",
3157
+ "name",
3158
+ "operationTimeout",
3159
+ "physicalReference",
3160
+ "reachDistance",
3161
+ "standards",
3162
+ "templateIds"
3163
+ ],
3164
+ "EVSETemplates": [
3165
+ "@context",
3166
+ "@id",
3167
+ "acceptedEMobilityProviderIds",
3168
+ "brandIds",
3169
+ "brands",
3170
+ "chargingConnectorIds",
3171
+ "chargingConnectors",
3172
+ "chargingModes",
3173
+ "chargingTariffId",
3174
+ "created",
3175
+ "currentTypes",
3176
+ "customData",
3177
+ "dataSource",
3178
+ "description",
3179
+ "extendedOperationTimeout",
3180
+ "floorLevel",
3181
+ "geoLocation",
3182
+ "images",
3183
+ "lastChange",
3184
+ "maxCurrent",
3185
+ "maxPower",
3186
+ "maxReservationDuration",
3187
+ "maxVoltage",
3188
+ "name",
3189
+ "operationTimeout",
3190
+ "physicalReference",
3191
+ "reachDistance",
3192
+ "standards"
3193
+ ],
3194
+ "EVSEGroups": [
3195
+ "@context",
3196
+ "@id",
3197
+ "EVSEIds",
3198
+ "created",
3199
+ "customData",
3200
+ "dataSource",
3201
+ "description",
3202
+ "lastChange",
3203
+ "name"
3204
+ ],
3205
+ "chargingConnectors": [
3206
+ "@context",
3207
+ "@id",
3208
+ "cableLength",
3209
+ "cableManagementSystem",
3210
+ "cableWeight",
3211
+ "chargingTariffIds",
3212
+ "currentTypes",
3213
+ "format",
3214
+ "images",
3215
+ "lockable",
3216
+ "maxCurrent",
3217
+ "maxPower",
3218
+ "maxVoltage",
3219
+ "standards",
3220
+ "type"
3221
+ ],
3222
+ "energyMeter": [
3223
+ "@context",
3224
+ "@id",
3225
+ "certificateIds",
3226
+ "created",
3227
+ "customData",
3228
+ "dataSource",
3229
+ "description",
3230
+ "firmwareVersion",
3231
+ "hardwareRevision",
3232
+ "lastChange",
3233
+ "manufacturerId",
3234
+ "modelId",
3235
+ "modelURL",
3236
+ "name",
3237
+ "publicKeys",
3238
+ "role",
3239
+ "serialNumber",
3240
+ "signatureMethods",
3241
+ "templateIds",
3242
+ "transparencySoftwareStatus"
3243
+ ],
3244
+ "energyMeterTemplates": [
3245
+ "@context",
3246
+ "@id",
3247
+ "certificateIds",
3248
+ "created",
3249
+ "customData",
3250
+ "dataSource",
3251
+ "description",
3252
+ "firmwareVersion",
3253
+ "hardwareRevision",
3254
+ "lastChange",
3255
+ "manufacturerId",
3256
+ "modelId",
3257
+ "modelURL",
3258
+ "name",
3259
+ "role",
3260
+ "signatureMethods",
3261
+ "transparencySoftwareStatus"
3262
+ ],
3263
+ "energyMeterGroups": [
3264
+ "@context",
3265
+ "@id",
3266
+ "created",
3267
+ "customData",
3268
+ "dataSource",
3269
+ "description",
3270
+ "energyMeterIds",
3271
+ "lastChange",
3272
+ "name"
3273
+ ],
3274
+ "transparencySoftwareStatus": ["@context", "certificateId", "legalStatus", "transparencySoftwareId"],
3275
+ "gridConnectionPoints": [
3276
+ "@context",
3277
+ "@id",
3278
+ "address",
3279
+ "connectionAgreementId",
3280
+ "connectionType",
3281
+ "contractedExportApparentPower",
3282
+ "contractedExportPower",
3283
+ "contractedImportApparentPower",
3284
+ "contractedImportPower",
3285
+ "customData",
3286
+ "description",
3287
+ "energyMeter",
3288
+ "geoLocation",
3289
+ "gridOperator",
3290
+ "gridOperatorId",
3291
+ "maintainedBy",
3292
+ "marketLocationIds",
3293
+ "meteringLocationIds",
3294
+ "name",
3295
+ "networkLocationId",
3296
+ "nominalFrequency",
3297
+ "nominalVoltage",
3298
+ "voltageLevel"
3299
+ ],
3300
+ "chargingStationModels": [
3301
+ "@context",
3302
+ "@id",
3303
+ "connectorTypes",
3304
+ "currentTypes",
3305
+ "customData",
3306
+ "datasheetURL",
3307
+ "description",
3308
+ "firmwareVersions",
3309
+ "hardwareRevisions",
3310
+ "images",
3311
+ "manufacturerId",
3312
+ "maxPower",
3313
+ "name",
3314
+ "numberOfEVSEs",
3315
+ "productURL"
3316
+ ],
3317
+ "energyMeterModels": [
3318
+ "@context",
3319
+ "@id",
3320
+ "accuracyClass",
3321
+ "currentTypes",
3322
+ "customData",
3323
+ "datasheetURL",
3324
+ "description",
3325
+ "firmwareVersions",
3326
+ "hardwareRevisions",
3327
+ "images",
3328
+ "manufacturerId",
3329
+ "maxCurrent",
3330
+ "measurands",
3331
+ "name",
3332
+ "productURL",
3333
+ "signatureMethods"
3334
+ ]
3335
+ };
3336
+ var PROPERTIES_OF = {
3337
+ ...ENTITY_PROPERTIES,
3338
+ geoLocation: ["@context", "lat", "lng", "alt", "projection"],
3339
+ priceComponents: ["@context", "type", "price", "stepSize", "vat"],
3340
+ publicKeys: ["@context", "@id", "key", "certificateChain"],
3341
+ signingKeys: KEY_PROPERTIES2,
3342
+ identityKeys: KEY_PROPERTIES2,
3343
+ signingKey: KEY_PROPERTIES2,
3344
+ keyStatement: ["@context", "roamingNetworkId", "roamingNetworkOperatorId", "identityKeys", "minConsensus", "signedAt", "validUntil", "signatures"],
3345
+ signatures: ["algorithm", "keyId", "value"],
3346
+ signingKeyCertificates: ["@context", "roamingNetworkOperatorId", "signingKey", "signedAt", "algorithm", "keyId", "value"],
3347
+ evTaler: ["@context", "merchantBaseURL", "merchantPublicKey", "exchanges"],
3348
+ exchanges: ["@context", "masterPublicKey", "baseURL", "currency", "name"]
3349
+ };
3350
+ var TRIMMED = /* @__PURE__ */ new Set([
3351
+ "@id",
3352
+ "currency",
3353
+ "chargingTariffId",
3354
+ "manufacturerId",
3355
+ "modelId",
3356
+ "transparencySoftwareId",
3357
+ "version",
3358
+ "vulnerabilityContact",
3359
+ "energyMeterManufacturerId",
3360
+ "energyMeterModel",
3361
+ "roamingNetworkId",
3362
+ "roamingNetworkOperatorId",
3363
+ "sourceCodeRepository",
3364
+ "declarationOfConformity",
3365
+ "softwareBillOfMaterials",
3366
+ "documentURL"
3367
+ ]);
3368
+ var URLS = /* @__PURE__ */ new Set(["homepage", "datasheetURL", "vulnerabilityReporting", "vulnerabilityDisclosurePolicy", "securityAdvisories", "merchantBaseURL", "baseURL"]);
3369
+ var NAMED_VALUES = {
3370
+ role: ["battery", "evse", "grid", "gridConnectionPoint", "pool", "pv", "station", "wind"],
3371
+ legalStatus: ["forInformationOnly", "germanCalibrationLaw", "verified"],
3372
+ signatureMethods: ["AlfenCrypt01", "AlfenCrypt03", "ChargePointCrypt01", "EDL40", "EMHCrypt01", "MennekesCrypt01", "OCMF", "PCDF"]
3373
+ };
3374
+ var RELEASE_PROPERTIES = [
3375
+ "@context",
3376
+ "@id",
3377
+ "name",
3378
+ "version",
3379
+ "openSourceLicenses",
3380
+ "sourceCodeRepository",
3381
+ "supportUntil",
3382
+ "declarationOfConformity",
3383
+ "softwareBillOfMaterials",
3384
+ "howToUse",
3385
+ "moreInformation",
3386
+ "logo"
3387
+ ];
3388
+ var LICENSE_PROPERTIES = ["@context", "@id", "description", "URLs"];
3389
+ var KNOWN_DATA_LICENSES = /* @__PURE__ */ new Set([
3390
+ "None",
3391
+ "PDDL",
3392
+ "ODC-By",
3393
+ "ODbL",
3394
+ "dl-de/by-2-0",
3395
+ "dl-de/zero-2-0",
3396
+ "GeoLizenz_V1.3",
3397
+ "CC BY 4.0",
3398
+ "CC BY-SA 4.0",
3399
+ "CC BY-ND 4.0",
3400
+ "CC BY-NC 4.0",
3401
+ "CC BY-NC-SA 4.0",
3402
+ "CC BY-NC-ND 4.0"
3403
+ ]);
3404
+ var DAYS_OF_WEEK = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];
3405
+ var TIMESTAMP_PROPERTIES = /* @__PURE__ */ new Set(["created", "lastChange", "validFrom", "validUntil", "notBefore", "notAfter", "signedAt", "supportUntil"]);
3406
+ var TIMESTAMP3 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
3407
+ function orderKeys(items, property) {
3408
+ if (property === "signingKeyCertificates" && items.every(isObject))
3409
+ return items.map((item) => `${idOf4(isObject(item) ? item["signingKey"] : void 0)}\0${isObject(item) ? asText(item["keyId"]) : ""}`);
3410
+ if (property === "authenticationModes" && items.every(isObject))
3411
+ return items.map((item) => isObject(item) ? asText(item["type"]) : "");
3412
+ if (property === "transparencySoftwareStatus" && items.every(isObject))
3413
+ return items.map((item) => isObject(item) ? asText(item["transparencySoftwareId"]) : "");
3414
+ if (property === "exchanges" && items.every(isObject))
3415
+ return items.map((item) => isObject(item) ? asText(item["masterPublicKey"]) : "");
3416
+ if (property === "daysOfWeek")
3417
+ return items.map((item) => String(DAYS_OF_WEEK.indexOf(typeof item === "string" ? item : "")).padStart(2, "0"));
3418
+ if (items.length > 0 && items.every((item) => isObject(item) && typeof item["@id"] === "string"))
3419
+ return items.map(idOf4);
3420
+ if (property !== void 0 && ORDERED_TEXTS.has(property) && items.every((item) => typeof item === "string"))
3421
+ return items.map((item) => typeof item === "string" ? item : "");
3422
+ return void 0;
3423
+ }
3424
+ var languagesOf = (text) => [...new Set(Object.entries(text).filter(([, value]) => typeof value === "string").map(([language]) => LANGUAGES.has(language.toLowerCase()) ? language.toLowerCase() : /^\d+$/u.test(language) && Number(language) < LANGUAGES.size ? `(language ${language})` : /^[+-]?\d+$/u.test(language) ? language : void 0).filter((language) => language !== void 0))];
3425
+ function writtenValueOf(property, value, holder) {
3426
+ if ((property === "@id" || property === "type" && holder === "chargingConnectors") && (typeof value === "number" || typeof value === "boolean"))
3427
+ return String(value);
3428
+ if (typeof value === "string") {
3429
+ if (property === "@id" && holder === "chargingStationOperators")
3430
+ return trimmed(value).toUpperCase();
3431
+ if (TRIMMED.has(property) || property === "type" && holder === "chargingConnectors")
3432
+ return trimmed(value);
3433
+ if (URLS.has(property))
3434
+ return hermodURL(value)?.text;
3435
+ const names = NAMED_VALUES[property];
3436
+ if (names !== void 0)
3437
+ return names.find((name) => name.toLowerCase() === trimmed(value).toLowerCase()) ?? trimmed(value);
3438
+ }
3439
+ if (holder === "geoLocation" && (property === "lat" || property === "lng") && (typeof value === "number" || typeof value === "object") && value !== null && !isArray(value)) {
3440
+ const number = Number(canonicalJSON(value));
3441
+ return Number.isFinite(number) ? number : void 0;
3442
+ }
3443
+ return void 0;
3444
+ }
3445
+ var contentText = (value) => typeof value === "number" && !Number.isSafeInteger(value) ? String(value) : canonicalJSON(value);
3446
+ function asRead(value, kind) {
3447
+ if (isArray(value))
3448
+ return value.map((element) => asRead(element, kind));
3449
+ if (!isObject(value))
3450
+ return value;
3451
+ const known = PROPERTIES_OF[kind];
3452
+ return Object.fromEntries(Object.entries(value).filter(([key]) => known === void 0 || known.includes(key)).map(([key, element]) => [key, PROPERTIES_OF[key] === void 0 ? element : asRead(element, key)]));
3453
+ }
3454
+ function notWritten(value, property, path, of) {
3455
+ const eachOnce = of === "new value";
3456
+ const join = (at, key) => at === "" ? key : `${at}.${key}`;
3457
+ const holdsAs = (at, written) => ({ path: at, reason: `the content holds it as '${contentText(written)}'` });
3458
+ const leftOut = (key, element, kind, keepsEmptyName) => {
3459
+ if (element === null)
3460
+ return "a property without a value is left out";
3461
+ if (isArray(element) && element.length === 0 && LEFT_OUT_WHEN_EMPTY.has(key) && !(key === "identityKeys" && kind === "keyStatement"))
3462
+ return "a list without elements is left out";
3463
+ if (TEXTS_IN_LANGUAGES.has(key) && isObject(element) && languagesOf(element).length === 0 && !(key === "name" && keepsEmptyName))
3464
+ return "the content leaves it out";
3465
+ if (key === "dataLicenseIds")
3466
+ return "the content leaves it out";
3467
+ const known = kind === void 0 ? void 0 : PROPERTIES_OF[kind];
3468
+ if (known !== void 0 && !known.includes(key))
3469
+ return "the content leaves it out";
3470
+ return void 0;
3471
+ };
3472
+ const check2 = (item, name, holder, at, root = false) => {
3473
+ if (name === "customData")
3474
+ return void 0;
3475
+ if (isArray(item)) {
3476
+ if (name !== void 0 && ORDERED_TEXTS.has(name) && item.some((element) => typeof element === "string" && trimmed(element) !== element))
3477
+ return { path: at, reason: "the texts of its elements are trimmed" };
3478
+ const keys = orderKeys(item, name);
3479
+ if (keys?.some((key, index) => index > 0 && ordinal(keys[index - 1] ?? "", key) >= (eachOnce ? 0 : 1)) === true)
3480
+ return { path: at, reason: eachOnce ? "its elements are in the order the content holds them in, each once" : "its elements are in the order the content holds them in" };
3481
+ for (const [index, element] of item.entries()) {
3482
+ const elementAt = `${at}[${String(index)}]`;
3483
+ if (typeof element === "string" && name !== void 0 && NAMED_VALUES[name] !== void 0) {
3484
+ const written = writtenValueOf(name, element, holder);
3485
+ if (written !== void 0 && written !== element)
3486
+ return holdsAs(elementAt, written);
3487
+ }
3488
+ const found = check2(element, void 0, name, elementAt);
3489
+ if (found !== void 0)
3490
+ return found;
3491
+ }
3492
+ return void 0;
3493
+ }
3494
+ if (isObject(item)) {
3495
+ const kind = root ? "" : name ?? holder;
3496
+ const party = name === void 0 && holder !== void 0 ? PARTY_NAMES[holder] : void 0;
3497
+ const keepsEmptyName = name === void 0 && holder === "chargingStationOperators";
3498
+ if (name !== void 0 && TEXTS_IN_LANGUAGES.has(name) && [...Object.keys(item)].sort(ordinal).join("\0") !== languagesOf(item).sort(ordinal).join("\0"))
3499
+ return { path: at, reason: "a text in languages names each language as Styx writes it, in lower case, with a text" };
3500
+ for (const [key, element] of Object.entries(item)) {
3501
+ if (key === "@context") {
3502
+ if (root)
3503
+ continue;
3504
+ return { path: at, reason: "embedded, an object names no JSON-LD context" };
3505
+ }
3506
+ const elementAt = join(at, key);
3507
+ const omitted2 = leftOut(key, element, kind, keepsEmptyName);
3508
+ if (omitted2 !== void 0)
3509
+ return { path: elementAt, reason: omitted2 };
3510
+ if (typeof element === "string" && TIMESTAMP_PROPERTIES.has(key) && !TIMESTAMP3.test(element))
3511
+ return { path: elementAt, reason: "a timestamp is UTC to the millisecond, yyyy-MM-ddTHH:mm:ss.fffZ" };
3512
+ const written = key === "customData" ? void 0 : writtenValueOf(key, element, kind);
3513
+ if (written !== void 0 && contentText(written) !== contentText(element))
3514
+ return holdsAs(elementAt, written);
3515
+ const found = check2(element, key, kind, elementAt);
3516
+ if (found !== void 0)
3517
+ return found;
3518
+ }
3519
+ if (party !== void 0 && !("name" in item))
3520
+ return holdsAs(join(at, "name"), { en: `${party} '${asText(writtenValueOf("@id", item["@id"] ?? "", holder) ?? item["@id"])}'` });
3521
+ for (const list of name === void 0 && holder !== void 0 ? WRITTEN_ALSO_EMPTY[holder] ?? [] : [])
3522
+ if (!(list in item))
3523
+ return holdsAs(join(at, list), []);
3524
+ if (!eachOnce && typeof item["created"] === "string" && !("lastChange" in item))
3525
+ return holdsAs(join(at, "lastChange"), item["created"]);
3526
+ }
3527
+ return void 0;
3528
+ };
3529
+ const omitted = property === void 0 ? void 0 : leftOut(property, value, void 0, false);
3530
+ if (omitted !== void 0)
3531
+ return { path, reason: omitted };
3532
+ return check2(value, property, void 0, path, of === "version" && property === void 0);
3533
+ }
3534
+ function notWrittenAsHeld(content) {
3535
+ const unknownLicense = (value) => {
3536
+ if (isArray(value))
3537
+ return value.map(unknownLicense).find((found2) => found2 !== void 0);
3538
+ if (!isObject(value))
3539
+ return void 0;
3540
+ for (const [key, element] of Object.entries(value)) {
3541
+ if (key === "customData")
3542
+ continue;
3543
+ const unknown = key === "dataLicenseIds" && isArray(element) ? element.find((id) => typeof id === "string" && !KNOWN_DATA_LICENSES.has(id)) : void 0;
3544
+ if (unknown !== void 0)
3545
+ return `The data license '${asText(unknown)}' is not well-known, so it must be given completely!`;
3546
+ const found2 = unknownLicense(element);
3547
+ if (found2 !== void 0)
3548
+ return found2;
3549
+ }
3550
+ return void 0;
3551
+ };
3552
+ const license = unknownLicense(content);
3553
+ if (license !== void 0)
3554
+ return license;
3555
+ for (const manufacturer of isArray(content["transparencySoftwareManufacturers"]) ? content["transparencySoftwareManufacturers"].filter(isObject) : [])
3556
+ for (const release of isArray(manufacturer["transparencySoftware"]) ? manufacturer["transparencySoftware"].filter(isObject) : []) {
3557
+ const unknown = Object.keys(release).find((property) => !RELEASE_PROPERTIES.includes(property));
3558
+ if (unknown !== void 0)
3559
+ return `TransparencySoftware: Unknown property '${unknown}'.`;
3560
+ for (const [index, license2] of (isArray(release["openSourceLicenses"]) ? release["openSourceLicenses"] : []).entries()) {
3561
+ const unknownOfLicense = isObject(license2) ? Object.keys(license2).find((property) => !LICENSE_PROPERTIES.includes(property)) : void 0;
3562
+ if (unknownOfLicense !== void 0)
3563
+ return `TransparencySoftware: openSourceLicenses[${String(index)}]: Unknown property '${unknownOfLicense}'.`;
3564
+ }
3565
+ }
3566
+ const found = notWritten(content, void 0, "", "version");
3567
+ return found === void 0 ? void 0 : `'${found.path}' is not written as the content holds it: ${found.reason}!`;
3568
+ }
3569
+
2232
3570
  // src/network/tree.ts
2233
3571
  var CHILD_ARRAYS = {
2234
3572
  roamingNetwork: {
@@ -2406,37 +3744,57 @@ var KeyStatement = class _KeyStatement {
2406
3744
  validUntil;
2407
3745
  signatures;
2408
3746
  json;
2409
- /** Reads a key statement, refusing properties it does not know: whatever is not signed is not taken. */
3747
+ /**
3748
+ * Reads a key statement as `RoamingNetworkKeyStatement.TryParse` of WWCP_POI3 reads it, in its words and order: its
3749
+ * context, the roaming network and its operator, its identity keys, `signedAt` and `validUntil` as POI writes them,
3750
+ * `minConsensus`, its signatures. Then what this library refuses besides, as whatever is not signed is not taken: a
3751
+ * property it does not know, identity keys out of the order of their `@id`s, a `validUntil` before its `signedAt`.
3752
+ */
2410
3753
  static parse(value) {
2411
3754
  const json = requireObject(value, "A key statement");
2412
- checkContext(json, KEY_STATEMENT_CONTEXT, "a key statement");
2413
- onlyProperties(json, ["@context", "roamingNetworkId", "roamingNetworkOperatorId", "identityKeys", "minConsensus", "signedAt", "validUntil", "signatures"], "A key statement");
2414
- const roamingNetworkId = requireString(json, "roamingNetworkId", "The roaming network of a key statement");
2415
- const roamingNetworkOperatorId = requireString(json, "roamingNetworkOperatorId", "The roaming network operator of a key statement");
2416
- const identityKeys = POIPublicKey.parseAll(json["identityKeys"], "identity keys of the key statement");
3755
+ if (get(json, "@context") !== void 0 && get(json, "@context") !== KEY_STATEMENT_CONTEXT)
3756
+ throw new POIError(`The JSON-LD context of the key statement must be '${KEY_STATEMENT_CONTEXT}'!`);
3757
+ const roamingNetworkId = trimmed(mandatoryText(json, "roamingNetworkId", "The roaming network of a key statement"));
3758
+ const roamingNetworkOperatorId = trimmed(mandatoryText(json, "roamingNetworkOperatorId", "The roaming network operator of a key statement"));
3759
+ const identityKeysJSON = get(json, "identityKeys");
3760
+ if (identityKeysJSON !== void 0 && !isArray(identityKeysJSON))
3761
+ throw new POIError("The identityKeys of a key statement must be a JSON array!");
3762
+ const identityKeys = (identityKeysJSON ?? []).map((item) => {
3763
+ try {
3764
+ return POIPublicKey.parse(item);
3765
+ } catch {
3766
+ throw new POIError("Invalid key in the identityKeys of a key statement!");
3767
+ }
3768
+ });
2417
3769
  if (identityKeys.length === 0)
2418
3770
  throw new POIError("A key statement requires its identityKeys!");
3771
+ const signedAt = timestampAt(json, "signedAt");
3772
+ const validUntil = timestampAt(json, "validUntil");
3773
+ if (signedAt === void 0 || validUntil === void 0)
3774
+ throw new POIError("The signedAt and validUntil of a key statement must be written as yyyy-MM-ddTHH:mm:ss.fffZ!");
3775
+ const minConsensus = get(json, "minConsensus");
3776
+ if (typeof minConsensus !== "number" || !Number.isInteger(minConsensus) || minConsensus < 1 || minConsensus > 255)
3777
+ throw new POIError("A key statement requires its minConsensus, a whole number from 1 to 255!");
3778
+ const signaturesJSON = get(json, "signatures");
3779
+ if (!isArray(signaturesJSON) || signaturesJSON.length === 0)
3780
+ throw new POIError("A key statement requires its signatures!");
3781
+ const signatures = signaturesJSON.map((item) => {
3782
+ const algorithm = isObject(item) ? nonemptyText(item, "algorithm") : void 0;
3783
+ const keyId = isObject(item) ? nonemptyText(item, "keyId") : void 0;
3784
+ const value2 = isObject(item) ? nonemptyText(item, "value") : void 0;
3785
+ if (!isObject(item) || algorithm === void 0 || keyId === void 0 || value2 === void 0)
3786
+ throw new POIError("Every signature of a key statement requires an algorithm, a key identification and a value!");
3787
+ onlyProperties(item, ["algorithm", "keyId", "value"], "A signature of a key statement");
3788
+ return { algorithm, keyId, value: value2 };
3789
+ });
3790
+ onlyProperties(json, ["@context", "roamingNetworkId", "roamingNetworkOperatorId", "identityKeys", "minConsensus", "signedAt", "validUntil", "signatures"], "A key statement");
2419
3791
  if (identityKeys.some((key, index) => {
2420
3792
  const before = identityKeys[index - 1];
2421
3793
  return before !== void 0 && before.id >= key.id;
2422
3794
  }))
2423
3795
  throw new POIError("The identity keys of a key statement are written in the order of their @ids!");
2424
- const minConsensus = requireInteger(json, "minConsensus", 1, 255, "The minConsensus of a key statement");
2425
- const signedAt = requireTimestamp(json, "signedAt", "The signedAt of a key statement");
2426
- const validUntil = requireTimestamp(json, "validUntil", "The validUntil of a key statement");
2427
3796
  if (validUntil < signedAt)
2428
3797
  throw new POIError("A key statement is not valid until before it was signed!");
2429
- const signatures = requireArray(json["signatures"], "The signatures of a key statement").map((item) => {
2430
- const signature = requireObject(item, "A signature of a key statement");
2431
- onlyProperties(signature, ["algorithm", "keyId", "value"], "A signature of a key statement");
2432
- return {
2433
- algorithm: requireString(signature, "algorithm"),
2434
- keyId: requireString(signature, "keyId"),
2435
- value: requireString(signature, "value")
2436
- };
2437
- });
2438
- if (signatures.length === 0)
2439
- throw new POIError("A key statement requires its signatures!");
2440
3798
  return new _KeyStatement(roamingNetworkId, roamingNetworkOperatorId, identityKeys, minConsensus, signedAt, validUntil, signatures, json);
2441
3799
  }
2442
3800
  /** The statement as it is signed: without `signatures`, with its `@context`, keys embedded without theirs. */
@@ -2546,7 +3904,7 @@ var KeyStatement = class _KeyStatement {
2546
3904
 
2547
3905
  // src/proofs/signingKeyCertificate.ts
2548
3906
  var SIGNING_KEY_CERTIFICATE_CONTEXT = CONTEXT_PREFIX + "roamingNetwork/signingKeyCertificate/v2";
2549
- var describeHolder = (key) => key.holder !== void 0 ? `'${key.holder}'` : "no named holder";
3907
+ var describeHolder = (key) => key.holder !== void 0 ? `the holder '${key.holder}'` : "no named holder";
2550
3908
  var SigningKeyCertificate = class _SigningKeyCertificate {
2551
3909
  constructor(roamingNetworkOperatorId, signingKey, signedAt, algorithm, keyId, value, json) {
2552
3910
  this.roamingNetworkOperatorId = roamingNetworkOperatorId;
@@ -2564,19 +3922,32 @@ var SigningKeyCertificate = class _SigningKeyCertificate {
2564
3922
  keyId;
2565
3923
  value;
2566
3924
  json;
3925
+ /**
3926
+ * Reads a certificate as `RoamingNetworkSigningKeyCertificate.TryParse` of WWCP_POI3 reads it, in its words and order:
3927
+ * its context, the roaming network operator, the signing key it certifies, `signedAt` as POI writes it, the algorithm,
3928
+ * key identification and value of its signature; then a property it does not know, which is not signed.
3929
+ */
2567
3930
  static parse(value) {
2568
3931
  const json = requireObject(value, "A certificate of a signing key");
2569
- checkContext(json, SIGNING_KEY_CERTIFICATE_CONTEXT, "a certificate of a signing key");
3932
+ if (get(json, "@context") !== void 0 && get(json, "@context") !== SIGNING_KEY_CERTIFICATE_CONTEXT)
3933
+ throw new POIError(`The JSON-LD context of a signing key certificate must be '${SIGNING_KEY_CERTIFICATE_CONTEXT}'!`);
3934
+ const roamingNetworkOperatorId = trimmed(mandatoryText(json, "roamingNetworkOperatorId", "The roaming network operator of a certificate"));
3935
+ let signingKey;
3936
+ try {
3937
+ signingKey = POIPublicKey.parse(isObject(json["signingKey"]) ? json["signingKey"] : void 0);
3938
+ } catch {
3939
+ throw new POIError("A signing key certificate requires the signingKey it certifies!");
3940
+ }
3941
+ const signedAt = timestampAt(json, "signedAt");
3942
+ if (signedAt === void 0)
3943
+ throw new POIError("The signedAt of a signing key certificate must be written as yyyy-MM-ddTHH:mm:ss.fffZ!");
3944
+ const algorithm = nonemptyText(json, "algorithm");
3945
+ const keyId = nonemptyText(json, "keyId");
3946
+ const signature = nonemptyText(json, "value");
3947
+ if (algorithm === void 0 || keyId === void 0 || signature === void 0)
3948
+ throw new POIError("A signing key certificate requires the algorithm, the key identification and the value of its signature!");
2570
3949
  onlyProperties(json, ["@context", "roamingNetworkOperatorId", "signingKey", "signedAt", "algorithm", "keyId", "value"], "A certificate of a signing key");
2571
- return new _SigningKeyCertificate(
2572
- requireString(json, "roamingNetworkOperatorId", "The roaming network operator of a certificate"),
2573
- POIPublicKey.parse(json["signingKey"], "The signing key of a certificate"),
2574
- requireTimestamp(json, "signedAt", "The signedAt of a certificate"),
2575
- requireString(json, "algorithm"),
2576
- requireString(json, "keyId"),
2577
- requireString(json, "value"),
2578
- json
2579
- );
3950
+ return new _SigningKeyCertificate(roamingNetworkOperatorId, signingKey, signedAt, algorithm, keyId, signature, json);
2580
3951
  }
2581
3952
  /** The signature input: the certificate without `value`, with its `@context`, its signing key embedded without its. */
2582
3953
  signingBytes() {
@@ -2614,6 +3985,9 @@ var SigningKeyCertificate = class _SigningKeyCertificate {
2614
3985
 
2615
3986
  // src/network/roamingNetwork.ts
2616
3987
  var ROAMING_NETWORK_CONTEXT = CONTEXT_PREFIX + "roamingNetwork/v1";
3988
+ var KeyStatementError = class extends POIError {
3989
+ };
3990
+ var sameId2 = (left, right) => ignoringCase(trimmed(left)) === ignoringCase(trimmed(right));
2617
3991
  var holdersOf = (keys) => new Set(keys.map((key) => key.holderOrUnnamed));
2618
3992
  var RoamingNetworkOperator = class _RoamingNetworkOperator {
2619
3993
  constructor(id, identityKeys, minConsensus, keyStatement, signingKeyCertificates, json) {
@@ -2647,68 +4021,72 @@ var RoamingNetworkOperator = class _RoamingNetworkOperator {
2647
4021
  */
2648
4022
  static parse(value, roamingNetworkId) {
2649
4023
  const json = requireObject(value, "The roaming network operator");
2650
- const id = requireString(json, "@id", "The identification of the roaming network operator");
4024
+ const id = mandatoryText(json, "@id", "The identification of the roaming network operator");
2651
4025
  checkContext(json, CONTEXT_PREFIX + "roamingNetwork/operator/v1", `the roaming network operator '${id}'`);
2652
4026
  if (get(json, "signingKeys") !== void 0)
2653
4027
  throw new POIError("The signing keys of a roaming network operator are those of its signingKeyCertificates!");
2654
4028
  const identityKeys = partyKeysOf(json, `the roaming network operator '${id}'`, "a roaming network operator").identityKeys;
2655
- const minConsensus = get(json, "minConsensus") === void 0 ? 1 : requireInteger(json, "minConsensus", 1, 255, `The minConsensus of the roaming network operator '${id}'`);
2656
- const keyStatement = get(json, "keyStatement") === void 0 ? void 0 : KeyStatement.parse(get(json, "keyStatement"));
4029
+ const minConsensus = get(json, "minConsensus") === void 0 ? 1 : requireInteger(json, "minConsensus", 1, 255, "The minConsensus of a roaming network operator");
4030
+ const keyStatement = get(json, "keyStatement") === void 0 ? void 0 : KeyStatement.parse(asRead(requireObject(get(json, "keyStatement"), "The key statement"), "keyStatement"));
2657
4031
  const certificatesJSON = get(json, "signingKeyCertificates");
2658
4032
  const certificates = certificatesJSON === void 0 ? [] : (() => {
2659
4033
  if (!isArray(certificatesJSON) || certificatesJSON.length === 0)
2660
4034
  throw new POIError("The signingKeyCertificates of a roaming network operator must be a nonempty array!");
2661
- return certificatesJSON.map((certificate) => SigningKeyCertificate.parse(certificate));
4035
+ return certificatesJSON.map((certificate) => SigningKeyCertificate.parse(asRead(certificate, "signingKeyCertificates")));
2662
4036
  })();
4037
+ const invalid = (error) => new POIError(`The given JSON representation of a roaming network operator is invalid: ${error}`);
4038
+ const certifications = certificates.map((certificate) => `${certificate.signingKey.id}/${certificate.keyId}`);
4039
+ if (new Set(certifications).size !== certifications.length)
4040
+ throw invalid(`An certificate of a signing key of the roaming network operator '${id}' must not be given twice!`);
2663
4041
  const certified = /* @__PURE__ */ new Map();
2664
4042
  for (const certificate of certificates) {
2665
4043
  const before = certified.get(certificate.signingKey.id);
2666
- if (before !== void 0 && !sameValue(without(before.json, "@context"), without(certificate.signingKey.json, "@context")))
2667
- throw new POIError(`The signing key '${certificate.signingKey.id}' of the roaming network operator '${id}' is certified twice in different forms!`);
4044
+ if (before !== void 0 && !sameValue(without(before.json, "@context"), without(asRead(certificate.signingKey.json, "signingKey"), "@context")))
4045
+ throw invalid(`An signing key of the roaming network operator '${id}' must not be given twice!`);
2668
4046
  certified.set(certificate.signingKey.id, certificate.signingKey);
2669
4047
  }
2670
4048
  const signingKeys = [...certified.values()];
2671
- const both = signingKeys.find((key) => identityKeys.some((identityKey) => identityKey.id === key.id));
4049
+ const both = [...signingKeys].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).find((key) => identityKeys.some((identityKey) => identityKey.id === key.id));
2672
4050
  if (both !== void 0)
2673
- throw new POIError(`The key '${both.id}' of the roaming network operator '${id}' is both a signing key and an identity key!`);
4051
+ throw invalid(`The key '${both.id}' of the roaming network operator '${id}' must not be both a signing and an identity key!`);
2674
4052
  for (const [kind, keys] of [["signing", signingKeys], ["identity", identityKeys]]) {
2675
4053
  const holders = holdersOf(keys).size;
2676
4054
  if (holders === 0) {
2677
4055
  if (minConsensus > 1)
2678
- throw new POIError(`The minConsensus ${minConsensus} of the roaming network operator '${id}' needs ${kind} keys of as many holders!`);
4056
+ throw invalid(`The minConsensus ${minConsensus} of the roaming network operator '${id}' needs ${kind} keys of as many holders!`);
2679
4057
  continue;
2680
4058
  }
2681
4059
  if (minConsensus > holders)
2682
- throw new POIError(`The minConsensus ${minConsensus} of the roaming network operator '${id}' needs ${kind} keys of as many holders, not of ${holders}!`);
4060
+ throw invalid(`The minConsensus ${minConsensus} of the roaming network operator '${id}' needs ${kind} keys of as many holders, not of ${holders}!`);
2683
4061
  if (2 * minConsensus <= holders)
2684
- throw new POIError(`The minConsensus ${minConsensus} of the roaming network operator '${id}' must be more than half of the ${holders} holders of its ${kind} keys!`);
4062
+ throw invalid(`The minConsensus ${minConsensus} of the roaming network operator '${id}' must be more than half of the ${holders} holders of its ${kind} keys!`);
2685
4063
  }
4064
+ if (keyStatement !== void 0 && !sameId2(keyStatement.roamingNetworkId, roamingNetworkId))
4065
+ throw new KeyStatementError(`The key statement of the roaming network operator '${id}' is one of the roaming network '${keyStatement.roamingNetworkId}', not of '${roamingNetworkId}'!`);
2686
4066
  for (const certificate of certificates) {
2687
- if (certificate.roamingNetworkOperatorId !== id)
2688
- throw new POIError(`The certificate of the signing key '${certificate.signingKey.id}' of the roaming network operator '${id}' is one of '${certificate.roamingNetworkOperatorId}'!`);
4067
+ if (!sameId2(certificate.roamingNetworkOperatorId, id))
4068
+ throw new KeyStatementError(`The certificate of the signing key '${certificate.signingKey.id}' of the roaming network operator '${id}' is one of '${certificate.roamingNetworkOperatorId}'!`);
2689
4069
  try {
2690
4070
  certificate.verify(identityKeys);
2691
4071
  } catch (e) {
2692
- throw new POIError(`The roaming network operator '${id}': ${e instanceof Error ? e.message : String(e)}`);
4072
+ throw new KeyStatementError(`The roaming network operator '${id}': ${e instanceof Error ? e.message : String(e)}`);
2693
4073
  }
2694
4074
  }
2695
4075
  if (keyStatement !== void 0) {
2696
- if (keyStatement.roamingNetworkId !== roamingNetworkId)
2697
- throw new POIError(`The key statement of the roaming network operator '${id}' is one of the roaming network '${keyStatement.roamingNetworkId}', not of '${roamingNetworkId}'!`);
2698
- if (keyStatement.roamingNetworkOperatorId !== id)
2699
- throw new POIError(`The key statement of the roaming network operator '${id}' is one of '${keyStatement.roamingNetworkOperatorId}'!`);
2700
- const unknown = keyStatement.identityKeys.find((key) => !identityKeys.some((identityKey) => sameValue(without(identityKey.json, "@context"), without(key.json, "@context"))));
4076
+ if (!sameId2(keyStatement.roamingNetworkOperatorId, id))
4077
+ throw new KeyStatementError(`The key statement of the roaming network operator '${id}' is one of '${keyStatement.roamingNetworkOperatorId}'!`);
4078
+ const unknown = keyStatement.identityKeys.find((key) => !identityKeys.some((identityKey) => sameValue(without(asRead(identityKey.json, "identityKeys"), "@context"), without(key.json, "@context"))));
2701
4079
  if (unknown !== void 0)
2702
- throw new POIError(`The key statement of the roaming network operator '${id}' names the identity key '${unknown.id}', which is not one of its identity keys!`);
4080
+ throw new KeyStatementError(`The key statement of the roaming network operator '${id}' names the identity key '${unknown.id}', which is not one of its identity keys!`);
2703
4081
  if (keyStatement.minConsensus !== minConsensus)
2704
- throw new POIError(`The key statement of the roaming network operator '${id}' states the minConsensus ${keyStatement.minConsensus}, not its ${minConsensus}!`);
4082
+ throw new KeyStatementError(`The key statement of the roaming network operator '${id}' states the minConsensus ${keyStatement.minConsensus}, not its ${minConsensus}!`);
2705
4083
  try {
2706
4084
  const holders = /* @__PURE__ */ new Map();
2707
4085
  for (const key of identityKeys)
2708
4086
  holders.set(key.holderOrUnnamed, [...holders.get(key.holderOrUnnamed) ?? [], key.id]);
2709
4087
  keyStatement.verifyHolders([...holders.values()], identityKeys, { minConsensus });
2710
4088
  } catch (e) {
2711
- throw new POIError(minConsensus > 1 ? `The key statement of the roaming network operator '${id}' is not signed by identity keys of ${minConsensus} of its holders: ${e instanceof Error ? e.message : String(e)}` : `The key statement of the roaming network operator '${id}' is not signed by one of its identity keys: ${e instanceof Error ? e.message : String(e)}`);
4089
+ throw new KeyStatementError(minConsensus > 1 ? `The key statement of the roaming network operator '${id}' is not signed by identity keys of ${minConsensus} of its holders: ${e instanceof Error ? e.message : String(e)}` : `The key statement of the roaming network operator '${id}' is not signed by one of its identity keys: ${e instanceof Error ? e.message : String(e)}`);
2712
4090
  }
2713
4091
  }
2714
4092
  return new _RoamingNetworkOperator(id, identityKeys, minConsensus, keyStatement, certificates, json);
@@ -2734,12 +4112,25 @@ var RoamingNetwork = class _RoamingNetwork {
2734
4112
  * revision, firmware version or serial number WWCP_POI refuses (`invalidVersionText`), nor a certificate WWCP_POI
2735
4113
  * refuses to read (`invalidCertificates`), nor an EV Taler merchant or payment options it refuses (`invalidEVTaler`),
2736
4114
  * nor keys of a party it refuses (`invalidPartyKeys`),
4115
+ * nor what WWCP_POI takes and writes otherwise than the content holds it (`notWrittenAsHeld`),
2737
4116
  * also the version a change set leads to. Its `eTags` may be left out: what binds a version is the commit whose ETags it
2738
4117
  * has (`matchesCommitETags`).
2739
4118
  */
2740
4119
  static parse(value) {
4120
+ return _RoamingNetwork.read(value, "version");
4121
+ }
4122
+ /**
4123
+ * Reads the version the given change set leads to, its content after the last change, and refuses it as `parse`
4124
+ * does, but in the words WWCP_POI3 names what it refuses of the network there (RoamingNetwork.ChangeSets.cs): its
4125
+ * operator and the identifications it keeps unique within the roaming network bare, not as arguments of its
4126
+ * constructor.
4127
+ */
4128
+ static afterChangeSet(content) {
4129
+ return _RoamingNetwork.read(content, "change set");
4130
+ }
4131
+ static read(value, of) {
2741
4132
  const json = requireObject(value, "A roaming network");
2742
- const id = requireString(json, "@id", "The identification of a roaming network");
4133
+ const id = mandatoryText(json, "@id", "The identification of a roaming network");
2743
4134
  checkContext(json, ROAMING_NETWORK_CONTEXT, `the roaming network '${id}'`);
2744
4135
  if (get(json, "signingKeys") !== void 0 || get(json, "identityKeys") !== void 0)
2745
4136
  throw new POIError(`The roaming network '${id}' names no keys of its own: those of its operator are its operator's!`);
@@ -2748,13 +4139,26 @@ var RoamingNetwork = class _RoamingNetwork {
2748
4139
  let treeHashAlgorithms = ["sha256"];
2749
4140
  const treeHashAlgorithmsJSON = get(json, "treeHashAlgorithms");
2750
4141
  if (treeHashAlgorithmsJSON !== void 0) {
2751
- if (!isArray(treeHashAlgorithmsJSON) || treeHashAlgorithmsJSON.length === 0 || !treeHashAlgorithmsJSON.every(isHashAlgorithm) || new Set(treeHashAlgorithmsJSON).size !== treeHashAlgorithmsJSON.length)
2752
- throw new POIError(`The treeHashAlgorithms of the roaming network '${id}' are at least one of sha256, sha384 and sha512, none twice!`);
4142
+ if (!isArray(treeHashAlgorithmsJSON))
4143
+ throw new POIError("The 'treeHashAlgorithms' must be a JSON array of hash algorithms!");
4144
+ const unknown = treeHashAlgorithmsJSON.find((algorithm) => !isHashAlgorithm(algorithm));
4145
+ if (unknown !== void 0)
4146
+ throw new POIError(`Unknown tree hash algorithm '${asText(unknown)}': sha256, sha384 or sha512!`);
4147
+ if (treeHashAlgorithmsJSON.length === 0)
4148
+ throw new POIError("The 'treeHashAlgorithms' must name at least one hash algorithm!");
4149
+ if (new Set(treeHashAlgorithmsJSON).size !== treeHashAlgorithmsJSON.length)
4150
+ throw new POIError("The 'treeHashAlgorithms' must not name a hash algorithm twice!");
2753
4151
  treeHashAlgorithms = treeHashAlgorithmsJSON;
2754
4152
  }
2755
4153
  const operatorJSON = get(json, "operator");
2756
- const operator = operatorJSON === void 0 ? void 0 : RoamingNetworkOperator.parse(operatorJSON, id);
2757
- const refusal = duplicateIdentification(content) ?? invalidVersionText(content) ?? invalidCertificates(content) ?? invalidEVTaler(content) ?? invalidPartyKeys(content);
4154
+ const operator = operatorJSON === void 0 ? void 0 : (() => {
4155
+ try {
4156
+ return RoamingNetworkOperator.parse(operatorJSON, id);
4157
+ } catch (e) {
4158
+ throw e instanceof KeyStatementError && of === "version" ? new POIError(inRoamingNetwork(e.message, "Operator")) : e;
4159
+ }
4160
+ })();
4161
+ const refusal = invalidIdentification(content) ?? invalidVersionText(content) ?? invalidCertificates(content) ?? invalidEVTaler(content) ?? invalidPartyKeys(content) ?? duplicateIdentification(content, of) ?? notWrittenAsHeld(content);
2758
4162
  if (refusal !== void 0)
2759
4163
  throw new POIError(refusal);
2760
4164
  const network = new _RoamingNetwork(id, content, operator, treeHashAlgorithms);
@@ -3051,6 +4455,125 @@ function checkTrustAnchor(urls, holders, minConsensus) {
3051
4455
  return required(networkId, "A trust anchor names at least one URL of its roaming network!");
3052
4456
  }
3053
4457
 
4458
+ // src/proofs/trustContext.ts
4459
+ var TrustContext = class _TrustContext {
4460
+ constructor(anchor, holders, statement, evaluatedAt) {
4461
+ this.evaluatedAt = evaluatedAt;
4462
+ this.anchor = frozenAnchor(anchor);
4463
+ this.trustedHolders = Object.freeze(holders.map((holder) => Object.freeze([...holder])));
4464
+ this.authorizedAnchor = frozenAnchor(TrustAnchor.create(
4465
+ anchor.id,
4466
+ anchor.urls,
4467
+ holders.filter((holder) => holder.length > 0),
4468
+ anchor.minConsensus,
4469
+ anchor.name,
4470
+ anchor.description
4471
+ ));
4472
+ this.roamingNetworkId = statement.roamingNetworkId;
4473
+ this.roamingNetworkOperatorId = statement.roamingNetworkOperatorId;
4474
+ this.keyStatementDigest = statementDigest(statement);
4475
+ this.requiredConsensus = Math.max(anchor.minConsensus, statement.minConsensus);
4476
+ this.validUntil = statement.validUntil;
4477
+ Object.freeze(this);
4478
+ }
4479
+ evaluatedAt;
4480
+ anchor;
4481
+ authorizedAnchor;
4482
+ trustedHolders;
4483
+ roamingNetworkId;
4484
+ roamingNetworkOperatorId;
4485
+ keyStatementDigest;
4486
+ requiredConsensus;
4487
+ validUntil;
4488
+ static create(anchor, statement, options) {
4489
+ const result = _TrustContext.evaluate(anchor, statement, options);
4490
+ if (result.context === void 0)
4491
+ throw new POIError(result.verificationError ?? "No locally authorized trust quorum");
4492
+ return result.context;
4493
+ }
4494
+ static evaluate(anchor, statement, options) {
4495
+ const at = timeOf(options.at);
4496
+ if (!Number.isFinite(at))
4497
+ throw new POIError("Invalid trust evaluation time");
4498
+ if (statement.roamingNetworkId !== anchor.id)
4499
+ throw new POIError("The key statement belongs to another roaming network");
4500
+ const trusted = new Set(options.trustedIdentityKeyIds);
4501
+ const holders = anchor.holders.map((holder) => holder.filter((id) => trusted.has(id)));
4502
+ const pool = [...statement.identityKeys, ...options.otherIdentityKeys ?? []];
4503
+ const signedBy = new Set(statement.signatures.map((signature) => signature.keyId));
4504
+ const trustedHolderCount = holders.filter((holder) => holder.some((id) => signedBy.has(id))).length;
4505
+ let signaturesValid = true;
4506
+ let keysValid = true;
4507
+ let verificationError;
4508
+ try {
4509
+ for (const signature of statement.signatures) {
4510
+ const key = pool.find((key2) => key2.id === signature.keyId);
4511
+ if (key === void 0)
4512
+ throw new POIError("The public key of a signer was not provided");
4513
+ statement.verifySignature(signature, key);
4514
+ if (!key.isValidAt(statement.signedAt))
4515
+ keysValid = false;
4516
+ }
4517
+ } catch (error) {
4518
+ signaturesValid = false;
4519
+ verificationError = error instanceof Error ? error.message : String(error);
4520
+ }
4521
+ let quorumValid = false;
4522
+ try {
4523
+ statement.verifyHolders(holders, options.otherIdentityKeys, { minConsensus: anchor.minConsensus, at });
4524
+ quorumValid = true;
4525
+ } catch (error) {
4526
+ verificationError ??= error instanceof Error ? error.message : String(error);
4527
+ }
4528
+ const validityValid = keysValid && statement.isValidAt(at);
4529
+ return Object.freeze({
4530
+ signatures: signaturesValid ? "valid" : "invalid",
4531
+ signers: trustedHolderCount > 0 ? "valid" : "invalid",
4532
+ quorum: quorumValid ? "valid" : "invalid",
4533
+ validity: validityValid ? "valid" : "invalid",
4534
+ trustedHolderCount,
4535
+ requiredConsensus: Math.max(anchor.minConsensus, statement.minConsensus),
4536
+ signatureCount: statement.signatures.length,
4537
+ canTrustKeys: signaturesValid && validityValid,
4538
+ ...verificationError === void 0 ? {} : { verificationError },
4539
+ ...signaturesValid && quorumValid && validityValid ? { context: new _TrustContext(anchor, holders, statement, at) } : {}
4540
+ });
4541
+ }
4542
+ /** The context represents exactly one evaluation time; a different time requires fresh local decisions. */
4543
+ evaluationTime(at) {
4544
+ if (at !== void 0 && timeOf(at) !== this.evaluatedAt)
4545
+ throw new POIError("Recreate the trust context for a different evaluation time");
4546
+ return this.evaluatedAt;
4547
+ }
4548
+ /** Rechecks supplied signatures and binds their signed content to the verified statement. */
4549
+ verifyStatement(statement, otherIdentityKeys = [], at) {
4550
+ const time = this.evaluationTime(at);
4551
+ if (statement.roamingNetworkId !== this.roamingNetworkId || statement.roamingNetworkOperatorId !== this.roamingNetworkOperatorId || statementDigest(statement) !== this.keyStatementDigest)
4552
+ throw new POIError("The operator or key statement differs from the authorized trust context");
4553
+ return statement.verifyHolders(
4554
+ this.trustedHolders,
4555
+ otherIdentityKeys,
4556
+ { minConsensus: this.anchor.minConsensus, at: time }
4557
+ );
4558
+ }
4559
+ };
4560
+ function anchorOf(basis) {
4561
+ return basis instanceof TrustContext ? basis.authorizedAnchor : basis;
4562
+ }
4563
+ function statementDigest(statement) {
4564
+ return toHex(hash("sha256", canonicalJSONBytes(statement.content())));
4565
+ }
4566
+ function frozenAnchor(anchor) {
4567
+ const copy = TrustAnchor.parse(anchor.toQRCode());
4568
+ for (const holder of copy.holders)
4569
+ Object.freeze(holder);
4570
+ Object.freeze(copy.holders);
4571
+ Object.freeze(copy.urls);
4572
+ Object.freeze(copy.name);
4573
+ Object.freeze(copy.description);
4574
+ return Object.freeze(copy);
4575
+ }
4576
+
3054
4577
  // src/proofs/attestation.ts
3055
4578
  var ATTESTATION_CONTEXT = CONTEXT_PREFIX + "roamingNetwork/attestation/v1";
3056
4579
  var MAX_ATTESTATION_VALIDITY = 24 * 60 * 60 * 1e3;
@@ -3212,13 +4735,14 @@ var ChargingStationProof = class _ChargingStationProof {
3212
4735
  *
3213
4736
  * Attestations of a subject (8.5) attest one entity, not the root: they do not count here.
3214
4737
  */
3215
- verify(anchor, at) {
3216
- const time = timeOf(at);
4738
+ verify(basis, at) {
4739
+ const anchor = anchorOf(basis);
4740
+ const time = basis instanceof TrustContext ? basis.evaluationTime(at) : timeOf(at);
3217
4741
  const networkId = this.roamingNetwork["@id"];
3218
4742
  if (networkId !== anchor.id)
3219
4743
  throw new POIError(`The trust anchor is one of the roaming network '${anchor.id}'!`);
3220
4744
  const { eTag, attested } = this.calculate();
3221
- const { keyStatement, required: required2, signingKeyOf } = this.certifiedSigningKeys(anchor, time);
4745
+ const { keyStatement, required: required2, signingKeyOf } = this.certifiedSigningKeys(basis, time);
3222
4746
  const certifiedSigningKeyOf = (attestation) => signingKeyOf(attestation.keyId, attestation.signedAt);
3223
4747
  const counted = [];
3224
4748
  const holders = /* @__PURE__ */ new Set();
@@ -3265,8 +4789,9 @@ var ChargingStationProof = class _ChargingStationProof {
3265
4789
  * signed and at `at`, and allowed to sign for the party, as attestations are verified. Signatures that do not count are
3266
4790
  * ignored, not refused; the other signatures, e.g. of witnesses, are not checked here. Returns the holders that signed.
3267
4791
  */
3268
- verifyCheckpoint(checkpoint, anchor, at) {
3269
- const time = timeOf(at);
4792
+ verifyCheckpoint(checkpoint, basis, at) {
4793
+ const anchor = anchorOf(basis);
4794
+ const time = basis instanceof TrustContext ? basis.evaluationTime(at) : timeOf(at);
3270
4795
  if (this.roamingNetwork["@id"] !== anchor.id || checkpoint.roamingNetworkId !== anchor.id)
3271
4796
  throw new POIError(`The trust anchor is one of the roaming network '${anchor.id}'!`);
3272
4797
  if (checkpoint.commitId !== this.commitId)
@@ -3277,7 +4802,7 @@ var ChargingStationProof = class _ChargingStationProof {
3277
4802
  const { eTag } = this.calculate();
3278
4803
  if (!bytesEqual(eTag.digest, named.digest))
3279
4804
  throw new POIError(`The proof leads to ${etagToText(eTag)}, not to ${etagToText(named)}!`);
3280
- const { required: required2, signingKeyOf } = this.certifiedSigningKeys(anchor, time);
4805
+ const { required: required2, signingKeyOf } = this.certifiedSigningKeys(basis, time);
3281
4806
  const holders = /* @__PURE__ */ new Set();
3282
4807
  for (const signature of checkpoint.signatures) {
3283
4808
  const certified = signature.role === "roamingNetworkOperator" ? signingKeyOf(signature.keyId, signature.signedAt) : void 0;
@@ -3301,7 +4826,8 @@ var ChargingStationProof = class _ChargingStationProof {
3301
4826
  * and operator and their holders as the trust anchor does, and is valid at `time`; its identity keys are trusted then
3302
4827
  * as well. A signing key counts where a trusted identity key of its holder certifies it for this operator.
3303
4828
  */
3304
- certifiedSigningKeys(anchor, time) {
4829
+ certifiedSigningKeys(basis, time) {
4830
+ const anchor = anchorOf(basis);
3305
4831
  const networkId = this.roamingNetwork["@id"];
3306
4832
  const operator = get(this.roamingNetwork, "operator");
3307
4833
  if (!isObject(operator))
@@ -3316,7 +4842,7 @@ var ChargingStationProof = class _ChargingStationProof {
3316
4842
  throw new POIError(`The key statement is one of the roaming network '${keyStatement.roamingNetworkId}'!`);
3317
4843
  if (keyStatement.roamingNetworkOperatorId !== operatorId)
3318
4844
  throw new POIError(`The key statement is one of the roaming network operator '${keyStatement.roamingNetworkOperatorId}', not of '${operatorId}'!`);
3319
- const { holderOf } = keyStatement.verifyHolders(anchor.holders, operatorIdentityKeys, { minConsensus: anchor.minConsensus, at: time });
4845
+ const { holderOf } = basis instanceof TrustContext ? basis.verifyStatement(keyStatement, operatorIdentityKeys, time) : keyStatement.verifyHolders(anchor.holders, operatorIdentityKeys, { minConsensus: anchor.minConsensus, at: time });
3320
4846
  const identityKeys = [...keyStatement.identityKeys, ...operatorIdentityKeys];
3321
4847
  const trustedIds = new Set(holderOf.keys());
3322
4848
  const signingKeyOf = (keyId, signedAt) => {
@@ -4142,9 +5668,10 @@ var PARTY_CHILD_TYPES = {
4142
5668
  property: "transparencySoftware",
4143
5669
  what: "transparency software release",
4144
5670
  usage: KeyUsages.Software,
5671
+ // Not its version: a new version is a new release, so that a certificate naming a release names the version that
5672
+ // was assessed.
4145
5673
  changeable: [
4146
5674
  "name",
4147
- "version",
4148
5675
  "openSourceLicenses",
4149
5676
  "logo",
4150
5677
  "howToUse",
@@ -4153,7 +5680,8 @@ var PARTY_CHILD_TYPES = {
4153
5680
  "supportUntil",
4154
5681
  "declarationOfConformity",
4155
5682
  "softwareBillOfMaterials"
4156
- ]
5683
+ ],
5684
+ keepsVersion: true
4157
5685
  },
4158
5686
  ConformityCertificate: {
4159
5687
  parentType: "ConformityAssessmentBody",
@@ -4206,7 +5734,7 @@ var ENERGY_METER_CHILD_TYPES = {
4206
5734
  changeable: ["name", "description", "dataSource", "energyMeterIds", "customData"]
4207
5735
  }
4208
5736
  };
4209
- var sameId2 = (left, right) => left.toUpperCase() === right.toUpperCase();
5737
+ var sameId3 = (left, right) => left.toUpperCase() === right.toUpperCase();
4210
5738
  var OPERATOR_CHILD_ARRAYS = {
4211
5739
  ChargingTariff: "chargingTariffs",
4212
5740
  EVSETemplate: "EVSETemplates",
@@ -4220,87 +5748,10 @@ var OPERATOR_CHILD_ARRAYS = {
4220
5748
  };
4221
5749
  var TARIFF_TYPES = /* @__PURE__ */ new Set(["ChargingTariff", "ChargingTariffTemplate", "ChargingTariffGroup", "ChargingTariffOffer"]);
4222
5750
  var INFRASTRUCTURE_TYPES = /* @__PURE__ */ new Set([...Object.keys(OPERATOR_CHILD_ARRAYS), "ChargingPool", "ChargingStation", "EVSE"]);
4223
- var idOf4 = (value) => isObject(value) && typeof value["@id"] === "string" ? value["@id"] : "";
4224
- var ORDERED_TEXTS = /* @__PURE__ */ new Set([
4225
- "facilities",
4226
- "paymentOptions",
4227
- "uiFeatures",
4228
- "features",
4229
- "parkingRestrictions",
4230
- "services",
4231
- "standards",
4232
- "nutsCodes",
4233
- "vehicleTypes",
4234
- "euVehicleCategories",
4235
- "parkingSpaceIds",
4236
- "certificateIds",
4237
- "chargingTariffIds",
4238
- "acceptedEMobilityProviderIds",
4239
- "paymentMeans",
4240
- "paymentBrands",
4241
- "signatureMethods",
4242
- "connectorTypes",
4243
- "EVSEIds",
4244
- "chargingStationIds",
4245
- "chargingPoolIds",
4246
- "energyMeterIds",
4247
- "EVSEGroupIds",
4248
- "chargingStationGroupIds",
4249
- "chargingPoolGroupIds",
4250
- "chargingStationOperatorIds",
4251
- "eMobilityProviderIds",
4252
- "parkingSensorIds",
4253
- "parkingProductIds",
4254
- "measurands",
4255
- "notifiedFor",
4256
- "verifiedTransparencySoftwareIds",
4257
- "compatibleTransparencySoftwareIds",
4258
- "cardTypes",
4259
- "dimensions",
4260
- "usages"
4261
- ]);
4262
- var DAYS_OF_WEEK = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];
4263
- var TIMESTAMP_PROPERTIES = /* @__PURE__ */ new Set(["created", "lastChange", "validFrom", "validUntil", "notBefore", "notAfter", "signedAt", "supportUntil"]);
4264
- var TIMESTAMP3 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
4265
- function orderKeys(items, property) {
4266
- if (property === "signingKeyCertificates" && items.every(isObject))
4267
- return items.map((item) => `${idOf4(isObject(item) ? item["signingKey"] : void 0)}\0${isObject(item) ? asText(item["keyId"]) : ""}`);
4268
- if (property === "authenticationModes" && items.every(isObject))
4269
- return items.map((item) => isObject(item) ? asText(item["type"]) : "");
4270
- if (property === "transparencySoftwareStatus" && items.every(isObject))
4271
- return items.map((item) => isObject(item) ? asText(item["transparencySoftwareId"]) : "");
4272
- if (property === "exchanges" && items.every(isObject))
4273
- return items.map((item) => isObject(item) ? asText(item["masterPublicKey"]) : "");
4274
- if (property === "daysOfWeek")
4275
- return items.map((item) => String(DAYS_OF_WEEK.indexOf(typeof item === "string" ? item : "")).padStart(2, "0"));
4276
- if (items.length > 0 && items.every((item) => isObject(item) && typeof item["@id"] === "string"))
4277
- return items.map(idOf4);
4278
- if (property !== void 0 && ORDERED_TEXTS.has(property) && items.every((item) => typeof item === "string"))
4279
- return items.map((item) => typeof item === "string" ? item : "");
4280
- return void 0;
4281
- }
4282
5751
  function asGiven(value, property, where) {
4283
- const notHeld = (path, reason) => new POIError(`${where}: ${path === "" ? "it" : `'${path}'`} is not given as the content holds it: ${reason}!`);
4284
- const check2 = (item, name, path) => {
4285
- if (name === "customData")
4286
- return;
4287
- if (isArray(item)) {
4288
- const keys = orderKeys(item, name);
4289
- if (keys?.some((key, index) => index > 0 && ordinal(keys[index - 1] ?? "", key) >= 0) === true)
4290
- throw notHeld(path, "its elements are in the order the content holds them in, each once");
4291
- item.forEach((element, index) => {
4292
- check2(element, void 0, `${path}[${String(index)}]`);
4293
- });
4294
- } else if (isObject(item)) {
4295
- for (const [key, element] of Object.entries(item)) {
4296
- if (key === "@context")
4297
- throw notHeld(path, "embedded, an object names no JSON-LD context");
4298
- check2(element, key, path === "" ? key : `${path}.${key}`);
4299
- }
4300
- } else if (typeof item === "string" && name !== void 0 && TIMESTAMP_PROPERTIES.has(name) && !TIMESTAMP3.test(item))
4301
- throw notHeld(path, "a timestamp is UTC to the millisecond, yyyy-MM-ddTHH:mm:ss.fffZ");
4302
- };
4303
- check2(value, property, property ?? "");
5752
+ const found = notWritten(value, property, property ?? "", "new value");
5753
+ if (found !== void 0)
5754
+ throw new POIError(`${where}: ${found.path === "" ? "it" : `'${found.path}'`} is not given as the content holds it: ${found.reason}!`);
4304
5755
  return clone(value);
4305
5756
  }
4306
5757
  function insertOrdered(array, entity) {
@@ -4505,7 +5956,7 @@ function applyNetworkArrayChange(content, change, index, createdAt) {
4505
5956
  function partyChildOf(content, type, childId) {
4506
5957
  for (const party of objectsOf4(get(content, type.parents))) {
4507
5958
  const children = get(party, type.property);
4508
- const child = objectsOf4(children).find((item) => typeof item["@id"] === "string" && sameId2(item["@id"], childId));
5959
+ const child = objectsOf4(children).find((item) => typeof item["@id"] === "string" && sameId3(item["@id"], childId));
4509
5960
  if (child !== void 0)
4510
5961
  return { party, children, child };
4511
5962
  }
@@ -4513,14 +5964,14 @@ function partyChildOf(content, type, childId) {
4513
5964
  }
4514
5965
  function partyNamed(content, type, id) {
4515
5966
  const property = NETWORK_ARRAYS[type];
4516
- return property === void 0 || id === void 0 ? void 0 : objectsOf4(get(content, property)).find((party) => typeof party["@id"] === "string" && sameId2(party["@id"], id));
5967
+ return property === void 0 || id === void 0 ? void 0 : objectsOf4(get(content, property)).find((party) => typeof party["@id"] === "string" && sameId3(party["@id"], id));
4517
5968
  }
4518
5969
  function partyChildIn(content, type, change, index) {
4519
5970
  const party = change.parentEntityType === type.parentType ? partyNamed(content, type.parentType, change.parentEntityId) : void 0;
4520
5971
  if (party === void 0)
4521
5972
  throw new POIError(`Change ${index}: the ${type.what} '${change.entityId}' needs its ${type.parentType}, an existing one!`);
4522
5973
  const children = get(party, type.property);
4523
- const child = objectsOf4(children).find((item) => typeof item["@id"] === "string" && sameId2(item["@id"], change.entityId));
5974
+ const child = objectsOf4(children).find((item) => typeof item["@id"] === "string" && sameId3(item["@id"], change.entityId));
4524
5975
  return { party, found: child === void 0 ? void 0 : { party, children, child } };
4525
5976
  }
4526
5977
  function applyPartyChildChange(content, change, index, createdAt) {
@@ -4547,7 +5998,7 @@ function applyPartyChildChange(content, change, index, createdAt) {
4547
5998
  } else {
4548
5999
  if (found === void 0)
4549
6000
  throw new POIError(`Change ${index}: the ${what} does not exist!`);
4550
- if (change.parentEntityType !== void 0 && (change.parentEntityType !== type.parentType || change.parentEntityId === void 0 || !sameId2(change.parentEntityId, asText(found.party["@id"]))))
6001
+ if (change.parentEntityType !== void 0 && (change.parentEntityType !== type.parentType || change.parentEntityId === void 0 || !sameId3(change.parentEntityId, asText(found.party["@id"]))))
4551
6002
  throw new POIError(`Change ${index}: the ${what} belongs to ${type.parentType} '${asText(found.party["@id"])}', not to ${change.parentEntityType} '${change.parentEntityId ?? ""}'!`);
4552
6003
  party = found.party;
4553
6004
  }
@@ -4564,7 +6015,7 @@ function applyPartyChild(type, party, found, change, index, what, createdAt) {
4564
6015
  const property = change.propertyName ?? "";
4565
6016
  const child = existing().child;
4566
6017
  if (!type.changeable.includes(property))
4567
- throw new POIError(`Change ${index}: the property '${property}' of a ${type.what} can not be changed!`);
6018
+ throw new POIError(type.keepsVersion === true && property === "version" ? `Change ${index}: the version of the ${what} can not be changed: a new version is a new release!` : `Change ${index}: the property '${property}' of a ${type.what} can not be changed!`);
4568
6019
  if (change.kind === "RemoveProperty") {
4569
6020
  if (get(child, property) === void 0)
4570
6021
  throw new POIError(`Change ${index}: the property '${property}' of the ${what} does not exist!`);
@@ -4577,7 +6028,7 @@ function applyPartyChild(type, party, found, change, index, what, createdAt) {
4577
6028
  break;
4578
6029
  }
4579
6030
  case "Add": {
4580
- if (!isObject(change.newValue) || typeof change.newValue["@id"] !== "string" || !sameId2(change.newValue["@id"], change.entityId))
6031
+ if (!isObject(change.newValue) || typeof change.newValue["@id"] !== "string" || !sameId3(change.newValue["@id"], change.entityId))
4581
6032
  throw new POIError(`Change ${index}: the ${type.what} to add must be a JSON object with the identification '${change.entityId}'!`);
4582
6033
  const added = asGiven(without(change.newValue, "lastChange"), void 0, `Change ${String(index)}, the ${what} to add`);
4583
6034
  const children = get(party, type.property);
@@ -4605,7 +6056,7 @@ function energyMeterChildOf(content, type, childId) {
4605
6056
  for (const [partyType, parties] of [["ChargingStationOperator", "chargingStationOperators"], ["GridOperator", "gridOperators"]])
4606
6057
  for (const party of objectsOf4(get(content, parties))) {
4607
6058
  const children = get(party, type.property);
4608
- const child = objectsOf4(children).find((item) => typeof item["@id"] === "string" && sameId2(item["@id"], childId));
6059
+ const child = objectsOf4(children).find((item) => typeof item["@id"] === "string" && sameId3(item["@id"], childId));
4609
6060
  if (child !== void 0)
4610
6061
  return { party, partyType, children, child };
4611
6062
  }
@@ -4626,7 +6077,7 @@ function applyEnergyMeterChildChange(content, change, index, createdAt) {
4626
6077
  } else {
4627
6078
  if (found === void 0)
4628
6079
  throw new POIError(`Change ${index}: the ${what} does not exist!`);
4629
- if (change.parentEntityType !== void 0 && (change.parentEntityType !== found.partyType || change.parentEntityId === void 0 || !sameId2(change.parentEntityId, asText(found.party["@id"]))))
6080
+ if (change.parentEntityType !== void 0 && (change.parentEntityType !== found.partyType || change.parentEntityId === void 0 || !sameId3(change.parentEntityId, asText(found.party["@id"]))))
4630
6081
  throw new POIError(`Change ${index}: the ${what} belongs to ${found.partyType} '${asText(found.party["@id"])}', not to ${change.parentEntityType} '${change.parentEntityId ?? ""}'!`);
4631
6082
  party = found.party;
4632
6083
  }
@@ -4707,6 +6158,8 @@ function ownerOf(content, change, index, addedEnergyMeterChildren) {
4707
6158
  if (PARTIES2.has(change.entityType)) {
4708
6159
  if (change.kind === "Add" || change.kind === "Remove")
4709
6160
  return { kind: "rno", keys: "signing" };
6161
+ if (change.entityType === "ConformityAssessmentBody" && (change.propertyName === "notifiedBodyNumber" || change.propertyName === "notifiedFor"))
6162
+ return { kind: "rno", keys: "signing" };
4710
6163
  const id = asText(partyNamed(content, change.entityType, change.entityId)?.["@id"] ?? change.entityId);
4711
6164
  return {
4712
6165
  kind: "party",
@@ -4911,12 +6364,27 @@ function applyChangeSet(before, changeSet, options = {}) {
4911
6364
  if (withoutMerchant2 !== void 0)
4912
6365
  throw new POIError(withoutMerchant2);
4913
6366
  checkStatedBy(before, content, changeSet);
6367
+ const changedVersion = changedReleaseVersion(before.content, content);
6368
+ if (changedVersion !== void 0)
6369
+ throw new POIError(changedVersion);
4914
6370
  content["lastChange"] = createdAt;
4915
- const after = RoamingNetwork.parse(content);
6371
+ const after = RoamingNetwork.afterChangeSet(content);
4916
6372
  matchETags(changeSet.afterETags, after, "The result does not match the change set");
4917
6373
  checkKeyHolders(before, after);
4918
6374
  return after;
4919
6375
  }
6376
+ function changedReleaseVersion(before, after) {
6377
+ const releasesOf = (content) => objectsOf4(get(content, "transparencySoftwareManufacturers")).flatMap((manufacturer) => objectsOf4(get(manufacturer, "transparencySoftware")));
6378
+ const versionOf2 = (release) => trimmed(asText(get(release, "version") ?? ""));
6379
+ const versions = new Map(releasesOf(before).map((release) => [ignoringCase(asText(release["@id"])), versionOf2(release)]));
6380
+ for (const release of releasesOf(after)) {
6381
+ const was = versions.get(ignoringCase(asText(release["@id"])));
6382
+ const is = versionOf2(release);
6383
+ if (was !== void 0 && ignoringCase(was) !== ignoringCase(is))
6384
+ return `The version of the transparency software release '${asText(release["@id"])}' can not be changed from '${was}' to '${is}': a new version is a new release!`;
6385
+ }
6386
+ return void 0;
6387
+ }
4920
6388
  function checkStatedBy(before, content, changeSet) {
4921
6389
  const type = required(PARTY_CHILD_TYPES["ConformityCertificate"], "Certificates are children of their bodies!");
4922
6390
  const keyless = objectsOf4(get(before.content, type.parents)).filter((body) => objectsOf4(get(body, "signingKeys")).length === 0).map((body) => asText(body["@id"]));
@@ -4929,7 +6397,7 @@ function checkStatedBy(before, content, changeSet) {
4929
6397
  const statedBy = get(found.child, "statedBy");
4930
6398
  if (statedBy !== void 0 && statedBy !== "roamingNetworkOperator")
4931
6399
  throw new POIError(`The certificate '${certificateId}' is stated by its conformity assessment body, or by the roaming network operator in its place, 'roamingNetworkOperator', not by '${asText(statedBy)}'!`);
4932
- const byOperator = keyless.some((id) => sameId2(id, bodyId));
6400
+ const byOperator = keyless.some((id) => sameId3(id, bodyId));
4933
6401
  if (byOperator !== (statedBy !== void 0))
4934
6402
  throw new POIError(byOperator ? `The certificate '${certificateId}' of the conformity assessment body '${bodyId}' without signing keys is stated by the roaming network operator in its place: its statedBy is 'roamingNetworkOperator'!` : `The certificate '${certificateId}' of the conformity assessment body '${bodyId}' is stated by the body itself, with its keys: it names no statedBy!`);
4935
6403
  }
@@ -6928,17 +8396,19 @@ var OperatorTrust = class _OperatorTrust {
6928
8396
  * Verifies the key statement of the operator of the given version against the trust anchor (A1 step e) and returns the
6929
8397
  * trust in its keys. `at` is when the statement must be valid, now by default.
6930
8398
  */
6931
- static of(anchor, network, at) {
8399
+ static of(basis, network, at) {
8400
+ const anchor = anchorOf(basis);
6932
8401
  if (network.id !== anchor.id)
6933
8402
  throw new POIError("this is not the roaming network of the trust anchor");
6934
- return _OperatorTrust.ofOperator(anchor, network.operator, at);
8403
+ return _OperatorTrust.ofOperator(basis, network.operator, at);
6935
8404
  }
6936
8405
  /**
6937
8406
  * The same from the roaming network operator alone, e.g. as a proof of a charging station gives it (8.4): its key
6938
8407
  * statement verified against the trust anchor at `at`.
6939
8408
  */
6940
- static fromOperatorJSON(anchor, operatorJSON, at) {
6941
- return _OperatorTrust.ofOperator(anchor, operatorJSON === void 0 ? void 0 : RoamingNetworkOperator.parse(operatorJSON, anchor.id), at);
8409
+ static fromOperatorJSON(basis, operatorJSON, at) {
8410
+ const anchor = anchorOf(basis);
8411
+ return _OperatorTrust.ofOperator(basis, operatorJSON === void 0 ? void 0 : RoamingNetworkOperator.parse(operatorJSON, anchor.id), at);
6942
8412
  }
6943
8413
  /**
6944
8414
  * The trust in the operator of a later version of a history this trust follows (8.6, *Succession*): its key statement
@@ -6968,11 +8438,12 @@ var OperatorTrust = class _OperatorTrust {
6968
8438
  signedBy
6969
8439
  );
6970
8440
  }
6971
- static ofOperator(anchor, operator, at) {
8441
+ static ofOperator(basis, operator, at) {
8442
+ const anchor = anchorOf(basis);
6972
8443
  const statement = operator?.keyStatement;
6973
8444
  if (operator === void 0 || statement === void 0)
6974
8445
  throw new POIError("the roaming network operator states no keys");
6975
- const { signedBy, holderOf } = statement.verifyHolders(anchor.holders, operator.identityKeys, { minConsensus: anchor.minConsensus, at: timeOf(at) });
8446
+ const { signedBy, holderOf } = basis instanceof TrustContext ? basis.verifyStatement(statement, operator.identityKeys, at) : statement.verifyHolders(anchor.holders, operator.identityKeys, { minConsensus: anchor.minConsensus, at: timeOf(at) });
6976
8447
  return new _OperatorTrust(
6977
8448
  anchor,
6978
8449
  operator,
@@ -7051,14 +8522,15 @@ var OperatorTrust = class _OperatorTrust {
7051
8522
  };
7052
8523
 
7053
8524
  // src/verifier/version.ts
7054
- function verifyVersion(anchor, networkJSON, commitJSON, at) {
8525
+ function verifyVersion(basis, networkJSON, commitJSON, at) {
8526
+ const anchor = anchorOf(basis);
7055
8527
  const network = RoamingNetwork.parse(networkJSON);
7056
8528
  if (network.id !== anchor.id)
7057
8529
  throw new POIError("this is not the roaming network of the trust anchor");
7058
8530
  const commit = Commit.parse(commitJSON);
7059
8531
  if (commit.roamingNetworkId !== network.id || !network.matchesCommitETags(commit.eTags))
7060
8532
  throw new POIError("the commit does not lead to the roaming network");
7061
- const trust = OperatorTrust.of(anchor, network, at);
8533
+ const trust = OperatorTrust.of(basis, network, at);
7062
8534
  const signedBy = trust.verifyOperatorSignatures(commit.signatures, (signature, key) => {
7063
8535
  commit.verifySignature(signature, key);
7064
8536
  }, "the commit");
@@ -7155,6 +8627,7 @@ function verifyChargingStation(anchor, proof, options = {}) {
7155
8627
  const verified = parsed.verify(anchor, options.at);
7156
8628
  const attested = verified.attested;
7157
8629
  const warnings = [];
8630
+ const shownAnchor = anchor instanceof TrustContext ? anchor.anchor : anchor;
7158
8631
  const found = [];
7159
8632
  for (const operator2 of objects2(get(attested, "chargingStationOperators")))
7160
8633
  for (const pool2 of objects2(get(operator2, "chargingPools")))
@@ -7193,7 +8666,7 @@ function verifyChargingStation(anchor, proof, options = {}) {
7193
8666
  energyMeterModels.set(asText(meter["@id"]), manufactured);
7194
8667
  }
7195
8668
  const certificates = certificatesOf(attested, station, energyMeters, warnings);
7196
- for (const [kind, shown3, proven] of [["name", anchor.name, get(attested, "name")], ["description", anchor.description, get(attested, "description")]])
8669
+ for (const [kind, shown3, proven] of [["name", shownAnchor.name, get(attested, "name")], ["description", shownAnchor.description, get(attested, "description")]])
7197
8670
  for (const [language, text] of Object.entries(shown3))
7198
8671
  if (!isObject(proven) || get(proven, language) !== text)
7199
8672
  warnings.push(`the trust anchor shows the ${kind} '${text}' (${language}), not the one of the network in the proof`);
@@ -7550,6 +9023,6 @@ var RuntimeStatus = class _RuntimeStatus {
7550
9023
  }
7551
9024
  };
7552
9025
 
7553
- export { ATTESTATION_CONTEXT, ArchiveSegment, Attestation, CHANGE_SET_CONTEXT, CHECKPOINT_CONTEXT2 as CHECKPOINT_CONTEXT, CHILD_ARRAYS, COMMIT_CONTEXT2 as COMMIT_CONTEXT, CONTEXT_PREFIX, ChangeSet, ChargingStationProof, Checkpoint, CheckpointProof, Commit, DCCMeasurementResult, DCCResult, DCCStatement, DCC_PERFORMANCE_LOCATIONS, DCOC_MODULES, DDoCNotifiedBody, Decimal, DigitalCalibrationCertificate, DigitalCertificateOfConformity, DigitalDeclarationOfConformity, EPOCH_CONTEXT, EPOCH_LINK_CONTEXT, EPOCH_PROFILES_CONTEXT, EV_TALER_EXCHANGE_CONTEXT, EV_TALER_MERCHANT_CONTEXT, EV_TALER_PAYMENT_MEANS, EV_TALER_PAYMENT_OPTION, EpochProfiles, FIRST_EPOCH, History, KEY_STATEMENT_CONTEXT, KeyStatement, KeyUsages, LIVE_TOPICS, MAX_DECIMAL_SCALE, MAX_PRICE_STATEMENT_SEQUENCE, MERGE_CHANGE_SET_ID_PREFIX, OperatorTrust, POIClient, POIError, POIPublicKey, PROOF_CONTEXT, PriceStatement, PriceStatementProof, QIDigitalDocument, QIDigitalItem, QIDigitalLegislation, QIDigitalManufacturer, QIDigitalModel, QIDigitalPerson, QIDigitalQuantity, QIDigitalSignature, QIDigitalStandard, QI_DIGITAL_CONFORMITIES, ROAMING_NETWORK_CONTEXT, RUNTIME_STATUS_KINDS, RoamingNetwork, RoamingNetworkOperator, RuntimeStatus, SIGNATURE_ALGORITHMS, SIGNING_KEY_CERTIFICATE_CONTEXT, STATUS_UPDATE_CONTEXT, STYX_CANONICAL_JSON, SignatureRoles, SigningKeyCertificate, TRUST_ANCHOR_CONTEXT, TRUST_ANCHOR_QR_PREFIX, TrustAnchor, applyChangeSet, approvesQualitySystem, auditPath, canonicalJSON, canonicalJSONBytes, canonicalString, cborETag, cborToJson, changeSetIdentification, check, checkOwnership, clone, commitsRoot, decodeDeterministicCbor, describeVersion, deterministicCbor, effectiveChargingStationOperator, effectiveContent, effectiveTreeETag, entityDigest, etagToJSON, etagToText, evTalerExchangesAt, evTalerMerchantOf, evTalerTariffOf, fromBase45, fromBase64, fromHex, fromJavaScript, get, hash, isArray, isObject, isTalerPublicKey, jsonETag, jsonToCbor, leafHash, liveRequest, merkleRoot, merkleRootOfLeafHashes, nodeHash, parseArchiveSegmentInfo, parseETag, parseETags, parseEpoch, parseEpochLink, parseEvidence, parseFrame, parseJSON, parseJSONBytes, parseJSONObject, parsePriceStatements, parseStatusUpdate, parseTimestamp, parseTrustedWitnesses, paymentOptionName, prettyJSON, priceStatementChainOf, priceStatementsRoot, prunedTree, qiDigitalContext, rootFromAuditPath, sameETag, sameETags, sameEpoch, sameValue, skeleton, subscribeRequest, toBase45, toBase64, toHex, toTimestamp, verifyChargingStation, verifyCheckpointLine, verifyCheckpointPriceStatements, verifyCheckpointSignatures, verifyEvidence, verifyPriceStatementChain, verifyPriceStatements, verifySegmentLine, verifySignature, verifyVersion };
9026
+ export { ATTESTATION_CONTEXT, ArchiveSegment, Attestation, CHANGE_SET_CONTEXT, CHECKPOINT_CONTEXT2 as CHECKPOINT_CONTEXT, CHILD_ARRAYS, COMMIT_CONTEXT2 as COMMIT_CONTEXT, CONTEXT_PREFIX, ChangeSet, ChargingStationProof, Checkpoint, CheckpointProof, Commit, DCCMeasurementResult, DCCResult, DCCStatement, DCC_PERFORMANCE_LOCATIONS, DCOC_MODULES, DDoCNotifiedBody, Decimal, DigitalCalibrationCertificate, DigitalCertificateOfConformity, DigitalDeclarationOfConformity, EPOCH_CONTEXT, EPOCH_LINK_CONTEXT, EPOCH_PROFILES_CONTEXT, EV_TALER_EXCHANGE_CONTEXT, EV_TALER_MERCHANT_CONTEXT, EV_TALER_PAYMENT_MEANS, EV_TALER_PAYMENT_OPTION, EpochProfiles, FIRST_EPOCH, History, KEY_STATEMENT_CONTEXT, KeyStatement, KeyUsages, LIVE_TOPICS, MAX_DECIMAL_SCALE, MAX_PRICE_STATEMENT_SEQUENCE, MERGE_CHANGE_SET_ID_PREFIX, OperatorTrust, POIClient, POIError, POIPublicKey, PROOF_CONTEXT, PriceStatement, PriceStatementProof, QIDigitalDocument, QIDigitalItem, QIDigitalLegislation, QIDigitalManufacturer, QIDigitalModel, QIDigitalPerson, QIDigitalQuantity, QIDigitalSignature, QIDigitalStandard, QI_DIGITAL_CONFORMITIES, ROAMING_NETWORK_CONTEXT, RUNTIME_STATUS_KINDS, RoamingNetwork, RoamingNetworkOperator, RuntimeStatus, SIGNATURE_ALGORITHMS, SIGNING_KEY_CERTIFICATE_CONTEXT, STATUS_UPDATE_CONTEXT, STYX_CANONICAL_JSON, SignatureRoles, SigningKeyCertificate, TRUST_ANCHOR_CONTEXT, TRUST_ANCHOR_QR_PREFIX, TrustAnchor, TrustContext, applyChangeSet, approvesQualitySystem, auditPath, canonicalJSON, canonicalJSONBytes, canonicalString, cborETag, cborToJson, changeSetIdentification, check, checkOwnership, clone, commitsRoot, decodeDeterministicCbor, describeVersion, deterministicCbor, effectiveChargingStationOperator, effectiveContent, effectiveTreeETag, entityDigest, etagToJSON, etagToText, evTalerExchangesAt, evTalerMerchantOf, evTalerTariffOf, fromBase45, fromBase64, fromHex, fromJavaScript, get, hash, isArray, isObject, isTalerPublicKey, jsonETag, jsonToCbor, leafHash, liveRequest, merkleRoot, merkleRootOfLeafHashes, nodeHash, parseArchiveSegmentInfo, parseETag, parseETags, parseEpoch, parseEpochLink, parseEvidence, parseFrame, parseJSON, parseJSONBytes, parseJSONObject, parsePriceStatements, parseStatusUpdate, parseTimestamp, parseTrustedWitnesses, paymentOptionName, prettyJSON, priceStatementChainOf, priceStatementsRoot, prunedTree, qiDigitalContext, rootFromAuditPath, sameETag, sameETags, sameEpoch, sameValue, skeleton, subscribeRequest, toBase45, toBase64, toHex, toTimestamp, verifyChargingStation, verifyCheckpointLine, verifyCheckpointPriceStatements, verifyCheckpointSignatures, verifyEvidence, verifyPriceStatementChain, verifyPriceStatements, verifySegmentLine, verifySignature, verifyVersion };
7554
9027
  //# sourceMappingURL=index.js.map
7555
9028
  //# sourceMappingURL=index.js.map