From 612ca552311fc1cf1b1ce42e6926382eee05b57f Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sat, 1 Aug 2026 17:51:04 +1000 Subject: [PATCH 1/2] Fix npm provenance: add repository field, bump to 0.2.2 --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1793497..41d779d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,11 @@ { "name": "tesla-fleet-api", - "version": "0.2.1", + "version": "0.2.2", "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/Teslemetry/node-tesla-fleet-api" + }, "author": { "name": "Teslemetry", "email": "hello@teslemetry.com", From 41c179ea42fdd914a4fb7445301aac974dde8cae Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sat, 1 Aug 2026 17:51:30 +1000 Subject: [PATCH 2/2] Document npm provenance repository.url requirement --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 69bca62..12e095c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - For `DOMAIN_VEHICLE_SECURITY`, `Commands.dispatch` holds the domain's session lock across the *entire* dispatch (handshake + build + send + retry), not just message-build - VCSEC requires messages to arrive in strict counter order and the spec warns against simultaneous requests to it at all, unlike Infotainment (sliding window), which keeps the narrower build-only lock. - `Commands`'s `#privateKey`/`#publicKey` are native private class fields (not `protected`/TS-only `private`) - the raw signing key must not be reachable off the instance at all (e.g. via `JSON.stringify` or a structured log), not merely inaccessible to outside *code*. Tests that need to observe key derivation do so through what actually goes out on the wire (a captured handshake message), not by reading the field. - `src/tariff.ts`'s `getTariffPeriods(tariff, now, opts)` is a pure Tariff V2 rate resolver mirroring `python-tesla-fleet-api`'s sibling. The tariff object carries no timezone; the caller must pass `opts.timeZone` (an IANA string, e.g. from `site_info.installation_time_zone`) - the resolver has no other way to get site-local wall-clock parts from a JS `Date`, which is always a UTC instant. It resolves one calendar day at a time (`dayPeriods`), not a multi-day minute-of-week span, because a `tou_periods` entry's `fromDayOfWeek..toDayOfWeek` means "this daily time window recurs on each of these weekdays", not "one span from this day+time to that day+time"; `nextChange`/`upcoming` re-resolve the season fresh on each day so a horizon crossing a season boundary re-prices correctly, and a gap between scheduled periods reports the gap (not the next period found arbitrarily far out). Converting a resolved wall-clock boundary back to a `Date` goes through `wallClockToUtcMillis` (iterative, since the zone's UTC offset at the target instant is what's being solved for) rather than adding elapsed real minutes to `now` - the two diverge across a DST transition. The reverse direction - "what calendar day/weekday is N minutes of wall-clock time from now" (used to walk forward day by day, or peek at tomorrow) - must go through `wallClockAt` (pure calendar arithmetic, no `Intl` round trip), never `now.getTime() + minutes*60000`; the latter silently lands on the wrong calendar day on a DST fall-back day (25 real hours) or spring-forward day (23). Buy and sell resolve independently via `scheduleAt`, which reports both a `nextChangeGM` and a `sinceGM` even for a grid currently sitting in a gap (e.g. a sell/export window not open yet, or already closed) - `nextChange` (earlier of the two) and `currentStart` (later of the two) must fold in the sell side even when sell has no period active right now, or a differently-scheduled sell tariff gets silently ignored or backdated. Both are nullable (bounded to one day of lookahead/lookback, mirroring each other) and must be excluded from the combination via `!= null`, not treated as `0`, when a grid has nothing scheduled in that window at all. `TariffContentV2` (`src/types/site_info.ts`) types `seasons` as `Record` (an object keyed by season name, not an array) - that mismatch was a real bug fixed alongside the resolver; don't regress it back to an array shape. -- `.github/workflows/publish.yml` publishes to npm on GitHub Release `published` (matching this repo's own pre-2024 convention, restored) via npm trusted publishing (OIDC) - `id-token: write`, no `NPM_TOKEN` secret. It builds with `npx tsc` before `npm publish --provenance`, so a version bump landing on `main` still ships nothing by itself; publishing happens only when a GitHub Release is cut for that tag, and only once trusted publishing is enabled on the npm package side for this repo/workflow. Before assuming a version is live, check `npm view tesla-fleet-api@` rather than trusting `package.json`. +- `.github/workflows/publish.yml` publishes to npm on GitHub Release `published` (matching this repo's own pre-2024 convention, restored) via npm trusted publishing (OIDC) - `id-token: write`, no `NPM_TOKEN` secret. It builds with `npx tsc` before `npm publish --provenance`, so a version bump landing on `main` still ships nothing by itself; publishing happens only when a GitHub Release is cut for that tag, and only once trusted publishing is enabled on the npm package side for this repo/workflow. Before assuming a version is live, check `npm view tesla-fleet-api@` rather than trusting `package.json`. `npm publish --provenance` also requires `package.json`'s `repository.url` to resolve to this exact GitHub repo (`https://github.com/Teslemetry/node-tesla-fleet-api`) - sigstore provenance verification checks it against the GitHub Actions run's own repo and fails the publish (`E422`) if it's missing or mismatched. ## Maintaining this file