@open-charging-cloud/chargy-core 0.15.0 → 0.16.0

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,252 @@ While the version number is below 1.0.0, breaking changes are released in minor
7
7
  versions and are always listed first below.
8
8
 
9
9
 
10
+ ## [0.16.0] - 2026-09-08
11
+
12
+ ### Added
13
+
14
+ - **A grid operator is a party to a charging session.** When the grid asks a
15
+ station to charge more slowly, the session's power drops for a reason that is
16
+ neither the car's nor the station's, and without the constraint and the key
17
+ it was signed with that dip is indistinguishable from a fault. `IGridOperator`
18
+ carries the identity and the keys to tell the two apart: a charging station
19
+ operator minus everything about charging infrastructure, because a grid
20
+ operator runs no pools, stations or tariffs. `@id` is all it requires. A live
21
+ link names one in `gridOperator`, a charge transparency record several in
22
+ `gridOperators` - a live link describes one ongoing session, a record may
23
+ span several grids.
24
+
25
+ - **`signGridPowerConstraints` is the key usage a grid operator signs under.**
26
+ It joins `signCTRs` for whole records and `signMeterValues` /
27
+ `signEnergyMeterValues` for readings. A constraint is signed with every key
28
+ the operator holds for that usage, so a verifier supporting either algorithm
29
+ can check it.
30
+
31
+ - **The OCMF-Test-01 series carries a power constraint.** It is now a five
32
+ minute session in 35 documents instead of a three minute one in twenty, and
33
+ the grid operator limits the charging power to 6 kW for one minute in the
34
+ middle of it. The constraint is a `legallyRelevantLogMessage` signed by
35
+ `DE*VEN` under `signGridPowerConstraints` with one ECDSA and one Ed25519 key,
36
+ the meter takes an extra reading where it begins and where it ends, the power
37
+ in between stays below the limit, and the charging periods are cut at both
38
+ ends of it - which is the whole point of the fixture: a dip in the curve with
39
+ a signed explanation next to it. The generator gained
40
+ `OCMF-Test-01__LRLMs.json`, the log messages whose times are relative to the
41
+ start reading, and `--log-messages` / `--no-log-messages` to steer them.
42
+
43
+ - **Every optional property of the live link document may now be explicitly
44
+ `undefined`.**
45
+ Under `exactOptionalPropertyTypes` a bare `?:` means the property may be
46
+ omitted but not set to `undefined`, which made building a live link from
47
+ optional sources need a conditional per property. `description`, `timeSource`,
48
+ `chargingStationOperator`, `chargingStation`, `chargingSessionId`,
49
+ `eMobilityProvider`, `contract`, `warnings` and `signatureVerification` now
50
+ say `| undefined` like their neighbours already did.
51
+
52
+ ### Fixed
53
+
54
+ - **The live link documentation described a format two releases old.** Every
55
+ one of these would have produced a document the current code turns away, and
56
+ the format documentation is where a producer looks first:
57
+
58
+ - The TOTP configuration was documented as `initialSharedSecret`, renamed to
59
+ `sharedSecret` in 0.15.0. `isTOTPConfig()` requires the new name, so a
60
+ transport copied from the documentation failed `isLiveTransport()` and was
61
+ dropped by a filtering reader without a word - the same failure 0.15.1
62
+ fixed in the fixtures, still sitting in the prose.
63
+ - Transports named their endpoint in `url`, removed in 0.15.0. A transport
64
+ copied from the documentation was well-formed and named no endpoint at all.
65
+ - The minimal document had no `created` and was described as the smallest
66
+ recognised live link. Since 0.15.0 it is not a live link at all:
67
+ recognition requires `@context`, `created` and `liveTransports`, and the
68
+ documentation still said `@context` alone decided it.
69
+ - Reading a document was described as filling in a missing `created`. It
70
+ stopped doing that in 0.15.0, because that timestamp recorded when the
71
+ document was *read*.
72
+ - The TOTP table listed two properties of seven and declared `timeStep`
73
+ required, which it is not; the paragraph below it said the format does not
74
+ specify a hash algorithm, digits or encoding, which it has since done in
75
+ `hashAlgorithm`, `totpLength` and `alphabet`.
76
+ - `refresh` was said to be validated by `IsAChargeTransparencyLiveLink()` and
77
+ the point-of-use guards to include an `isTransport`. Both are
78
+ `isLiveTransport()` now.
79
+
80
+ ### Changed
81
+
82
+ - **The format documentation lists the properties a live link declares.** The
83
+ interfaces grew in 0.14.0 and 0.15.0 without the documentation following, so
84
+ `chargingSessionId`, `eMobilityProvider`, `chargingPeriods`,
85
+ `legallyRelevantLogMessages` and `supportMessages` appeared nowhere, while
86
+ nine properties that *are* declared were still listed as carried by the
87
+ fixtures and unvalidated. That section now holds what it says: `@id`,
88
+ `imageURLs` and the superseded `geoLocation` and `connector`.
89
+
90
+ - **An operator's `contact`, `support` and `privacy` are optional.**
91
+ `IChargingStationOperator` had required all three since it was written, and
92
+ `IGridOperator` inherited that when it took its shape. No document in the
93
+ repository carries any of them, nothing validates them, and requiring them
94
+ forced anyone building an operator in TypeScript to invent three properties
95
+ the format's own fixtures do without.
96
+
97
+ - **The fixtures write an operator's `name` as the `I18NString` it is typed
98
+ as.** The template said `"GraphDefined"` and `"Vanaheimr Electric"` where the
99
+ interfaces say a language-tag-to-string object, so the one document meant to
100
+ show the format did not follow it. It is `{ "en": ... }` now, and the series
101
+ is regenerated.
102
+
103
+ - **`ILegallyRelevantLogMessage` names its `text` before its `data`.** The
104
+ message comes before the payload that qualifies it, the way `ISupportMessage`
105
+ already had it.
106
+
107
+ - **`CTR_Format.md` lists `gridOperators`** among the top-level properties of a
108
+ charge transparency record.
109
+
110
+ - **Two live link tests no longer spell out what the generator regenerates.**
111
+ One asserted `created` to be a fixed timestamp and one the meter values to
112
+ number twenty; both are properties of the moment the fixture was generated
113
+ and of the session parameters, so both broke the first time the series was
114
+ regenerated. They now compare `created` against what the document itself
115
+ states - which is the actual assertion, that reading neither fills in nor
116
+ replaces it - and count the OCMF documents rather than naming a number.
117
+
118
+
119
+ ## [0.15.1] - 2026-09-07
120
+
121
+ ### Fixed
122
+
123
+ - **`isTOTPConfig()` rejected every TOTP configuration that named its hash
124
+ algorithm.** It asked for a number where `TOTPConfig.hashAlgorithm` is a
125
+ string, so a transport stating `"SHA-256"` was not a transport any more.
126
+ Nothing said so: `isLiveTransport()` simply returned false, and a reader
127
+ filtering its transports through it dropped that one without a word. Only
128
+ configurations leaving the property out were unaffected, which is why the
129
+ fixtures did not catch it.
130
+
131
+ ### Changed
132
+
133
+ - **The OCMF-Test-01 series still spelled the TOTP secret the old way.** The
134
+ rename to `sharedSecret` reached the interfaces in 0.15.0 but not the
135
+ fixtures, so `isLiveTransport()` dropped the websocket and the httpSSE
136
+ transport of every document in it - the test data did not survive its own
137
+ type guard. The template says `sharedSecret` now, and the series and
138
+ `ChargeTransparencyLiveLink_1.json` are regenerated and re-signed from it.
139
+
140
+
141
+ ## [0.15.0] - 2026-09-07
142
+
143
+ ### Breaking
144
+
145
+ - **A live link is recognised by the three things every one of them has.**
146
+ `IsAChargeTransparencyLiveLink()` requires the `@context`, a `created`
147
+ timestamp and a `liveTransports` array; a document missing any of them is not
148
+ a live link. Everything else stays unvalidated there - a malformed optional
149
+ field, and a broken transport most of all, must not turn a document into an
150
+ unrecognised one that then fails as an "unknown format". A transport is
151
+ judged where the transports are read, not where the document is identified.
152
+
153
+ - **Reading a document no longer fills in a missing `created`.** It used to get
154
+ the current UTC time, which recorded when the document was *read*. In a
155
+ legally relevant document that is not what "created" means, and reading is
156
+ not creating. A live link states its own timestamp, and one that does not is
157
+ not a live link.
158
+
159
+ - **A transport names its endpoints in `urls`, and only there.** The singular
160
+ `url` was the older spelling of the same thing, was never part of
161
+ `ILiveTransport`, and `isLiveTransport()` no longer looks at it. A transport
162
+ that carries only `url` is still recognised - an unknown property is not an
163
+ error - but it names no endpoint, so there is nothing to show and nothing to
164
+ poll. The fixtures, the format documentation and the tests all say `urls`.
165
+
166
+ - **`Transport` is now `LiveTransports`, `ITransport` is `ILiveTransport`, and
167
+ `ITransportURL` is gone.** An entry of `urls` is either the URL itself or a
168
+ `chargyInterfaces.IURL` carrying it alongside a `priority` and a `weight`.
169
+ The type guard is `isLiveTransport()`; the top-level export keeps the name
170
+ `isTransport`, so a consumer importing it from the package index sees no
171
+ rename, while one reaching through the `ChargeTransparencyLiveLink` namespace
172
+ has to follow.
173
+
174
+ - **`TOTPConfig` moved to `chargyInterfaces`, and `initialSharedSecret` is now
175
+ `sharedSecret`.** It also gained `validityTime`, `totpLength`, `alphabet`,
176
+ `timestamp` and `hashAlgorithm`, and `timeStep` became optional - the shape
177
+ the TOTP library actually takes. `isTOTPConfig()` validates it, and
178
+ `isLiveTransport()` calls it, so a transport still spelling the secret the
179
+ old way is dropped along with its TOTP.
180
+
181
+ - **The top-level `IURL` is a different type now.** `chargyInterfaces.IURL` -
182
+ a `url` with an optional `priority` and `weight` - is what the name resolves
183
+ to when it is imported from the package index; the JSON-LD document with the
184
+ `@context` of `URLContext` is `SimpleURL.IURL` and is no longer re-exported
185
+ by name. The two guards keep them apart: `IsAURL()` judges the document,
186
+ `isURL()` the transport endpoint. Code importing `IURL` from the index keeps
187
+ compiling and means something else.
188
+
189
+ - **`IChargeTransparencyLiveLink` lost `imageURLs`, `geoLocation` and
190
+ `connector`.** Images belong to whoever they show, so they hang off
191
+ `IChargingStationOperator`, `IChargingStation` and the other things that have
192
+ one; the position belongs to the charging station, and the connector to its
193
+ EVSE. A document keeps saying all of it, one level further in.
194
+
195
+ - **`ILegallyRelevantLogMessage` moved from `IChargeTransparencyRecord` to
196
+ `chargyInterfaces`,** where a live link can reach it as easily as a record
197
+ can, and `isConnector()` moved to `chargyInterfaces` next to the `IConnector`
198
+ it validates. Both keep their names at the package index; only the
199
+ namespace-qualified paths change.
200
+
201
+ ### Added
202
+
203
+ - **The interfaces name the quantities they carry.** A metrological value is
204
+ text - `"22 kW"`, `"(230.00 ±0.12) V, k=2"` - because resolution and
205
+ uncertainty are part of the reading and a number would drop both, so `Volt`,
206
+ `Ampere`, `Watt`, `WattHour`, `Ohm`, `Meter`, `Second` and `Kelvin` are
207
+ aliases of `MetrologicalText` rather than of `number`. `Timestamp`,
208
+ `CalendarDate`, `LocalTime`, `ISO8601Duration`, `DurationSeconds`, `Degrees`,
209
+ `LinkedDataContext`, `URL`, `UnitSymbol`, `DecimalScale` and
210
+ `MeasurementMagnitude` do the same for the values that were plain strings and
211
+ numbers before. They document meaning and do not validate syntax: parse at
212
+ the serialization boundary before doing arithmetic.
213
+
214
+ - **A live link says what it is about, not only where to fetch it.** It may now
215
+ carry its `timeSource`, `lastUpdated`, `updates` and `docRefIdGeneration`,
216
+ the `chargingStationOperator`, `chargingStation`, `chargingSessionId`,
217
+ `eMobilityProvider` and `contract` of the session, its `signedMeterValues`,
218
+ `chargingPeriods`, `legallyRelevantLogMessages` and `supportMessages`, and
219
+ the `keyIdGeneration` its signatures are read under. Most of it was already
220
+ in the documents and read untyped; it is described now.
221
+
222
+ - **`@context` may be a list.** A document can name an extension context
223
+ alongside this one, and is recognised as long as the list contains the live
224
+ link context.
225
+
226
+ - **New shapes for what the documents already contained:**
227
+ `IDocumentSignature` - the signature `verifyDocumentSignatures()` consumes -,
228
+ `ISignedMeterValues`, `ITimeSource` with `ITimeServer`, `ISupportMessage`,
229
+ `ILocalController`, `ITemperatureSensor` and `IMetrologicalCable`, the cable
230
+ profile that states its length and resistance as quantity text without a
231
+ second unit field.
232
+
233
+ - **The topology interfaces carry ids next to their objects.** A charging pool,
234
+ station, EVSE or energy meter can name what it belongs to and what belongs to
235
+ it by id - `chargingStationIds`, `EVSEIds`, `energyMeterIds`,
236
+ `chargingTariffIds`, `temperaturSensorIds` and the singular counterparts -
237
+ so a document may reference rather than nest. They also gained `imageURLs`,
238
+ an operator gained a `name`, an EVSE a `powerType` and a `maxPower`, and a
239
+ public key `keyUsage` and `encodings`.
240
+
241
+ ### Changed
242
+
243
+ - **The OCMF-Test-01 fixture series is regenerated from its template and
244
+ re-signed,** which the changed transport of every document required.
245
+
246
+ ### Internal dependencies
247
+
248
+ - @types/node 26.4.1, eslint 10.10.0, globals 17.12.0, playwright 1.63.0 and
249
+ typescript-eslint 8.69.0. The last of those extends
250
+ `no-meaningless-void-operator` to expressions that are not calls, which
251
+ caught the `void x;` idiom marking a parameter as deliberately unused. Those
252
+ parameters now carry the underscore prefix `argsIgnorePattern` already
253
+ covers, the way `_Context` always did.
254
+
255
+
10
256
  ## [0.14.4] - 2026-09-05
11
257
 
12
258
  ### Changed
package/README.md CHANGED
@@ -185,7 +185,7 @@ npx playwright install chromium
185
185
  ## Publishing
186
186
 
187
187
  ```bash
188
- npm version 0.15.0 --no-git-tag-version
188
+ npm version 0.16.0 --no-git-tag-version
189
189
  npm run verify
190
190
  npm pack --dry-run
191
191
  npm pack
@@ -2006,7 +2006,7 @@ function OIDInfo(data) {
2006
2006
  return "";
2007
2007
  }
2008
2008
  function isTOTPConfig(data) {
2009
- return isObject(data) && typeof data["sharedSecret"] === "string" && (data["timeStep"] === void 0 || typeof data["timeStep"] === "number") && (data["validityTime"] === void 0 || typeof data["validityTime"] === "number") && (data["totpLength"] === void 0 || typeof data["totpLength"] === "number") && (data["alphabet"] === void 0 || typeof data["alphabet"] === "string") && (data["timestamp"] === void 0 || typeof data["timestamp"] === "string") && (data["hashAlgorithm"] === void 0 || typeof data["hashAlgorithm"] === "number");
2009
+ return isObject(data) && typeof data["sharedSecret"] === "string" && (data["timeStep"] === void 0 || typeof data["timeStep"] === "number") && (data["validityTime"] === void 0 || typeof data["validityTime"] === "number") && (data["totpLength"] === void 0 || typeof data["totpLength"] === "number") && (data["alphabet"] === void 0 || typeof data["alphabet"] === "string") && (data["timestamp"] === void 0 || typeof data["timestamp"] === "string") && (data["hashAlgorithm"] === void 0 || typeof data["hashAlgorithm"] === "string");
2010
2010
  }
2011
2011
  function isURL(data) {
2012
2012
  if (typeof data === "string")