cross-tab-worker-databus 0.21.32 → 0.21.33
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 +20 -1
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-PNEOQX4O.js → chunk-FEWEXHZA.js} +2 -2
- package/dist/cjs/centrifuge.cjs +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/docs/release-checklist.md +1 -1
- package/docs/roadmap.md +11 -2
- package/docs/zh/release-checklist.md +1 -1
- package/docs/zh/roadmap.md +11 -2
- package/package.json +1 -1
- /package/dist/{chunk-PNEOQX4O.js.map → chunk-FEWEXHZA.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## [0.21.33] - 2026-09-25
|
|
2
|
+
|
|
3
|
+
A sentence in the release checklist was wrong in a way only a new measurement could show, and the release it described was the one that produced it: `0.21.32`'s published-consumer step took 304 s while its registry record existed 76.1 s in. No library code changed.
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Documentation tooling: the counter-observation against a purely-local npm cache account is retracted, because it joined two machines.** The prose cited 0.21.30 as evidence *against* that account on the grounds that "the retry resolved before a fresh 21:44 fetch's 300 s lifetime had run out". Read against the run records, the two halves come from different hosts: the 21:44:35Z "fresh read" was a `npm pack` on a laptop, while the 1 s green at 21:47:10Z was a **new runner job**, and a new job starts with an empty npm cache — which is what the local account *predicts*, not what refutes it. What such an account still cannot explain is exactly one reading, and the text now names that one instead: the runner's own `ETARGET` at 21:45:07Z, 364 s after the poll loop opened at 21:39:03Z and so ~60 s after a copy taken then must have expired. The verdict stays *not established*, now resting on an anomaly rather than on a comparison that never was.
|
|
8
|
+
|
|
9
|
+
### Documentation
|
|
10
|
+
|
|
11
|
+
- **`0.21.32` is the cache-lifetime model's cleanest sample, and it is recorded as one.** Ack 23:34:46Z, registry `time['0.21.32']` 23:36:02.091Z (**76.1 s** — the second-smallest of the six now listed), a `no-cache` packument read from a laptop reporting the new `latest` 151 s in, and the runner's blocking verify finishing at **304 s** — one 300 s lifetime plus a few seconds of command time, measured from that runner's own first fetch. The verify-duration band is now **seven green samples at 304–308 s** (307, 304, 305, 304, 304 under the old ceiling; 308 and 304 under the raised one). Both sample lists — the ack→record gaps and the per-run durations — were extended in `docs/release-checklist.md` (en + zh), the workflow comment and `tests/workflows.test.ts`, and the `AGENTS.md` bullet that restates the lesson.
|
|
12
|
+
- **`maxMeasuredAckToRecordMs` deliberately did not move.** 76.1 s does not beat the 310 s that sizes the 720 s ceiling, so the floor's constant stays put and `tests/workflows.test.ts` still passes unchanged at 6/6 — which is the check that the new sample widened the list without invalidating the derivation.
|
|
13
|
+
- **The retired claim's earlier copies carry pointers rather than silent rewrites.** `CHANGELOG.md`'s `0.21.31` entry and both language `docs/roadmap.md` blocks keep the sentence they got wrong and add what superseded it, naming `0.21.33`, so the history reads as the sequence it was rather than as a claim that never existed.
|
|
14
|
+
- **Three numbers in that prose named the wrong quantity, and all three were caught before the tag.** (1) "the second-smallest of the **seven** now listed" counted the ack→record list with the verify-duration list's size: the gap list carries six values — the seven is `307 / 304 / 305 / 304 / 304 / 308 / 304` s of *step duration* — and the sentence's own parenthesis enumerated six. Fixed in this file and in `docs/progress.md`'s Phase 199 entry. (2) Three copies said 0.21.30 "burned its whole **364 s budget**"; that run's ceiling was **360 s** (48 × 7.5 s), and 364 s is the observed span from the step's first poll (21:39:03Z) to its last `ETARGET` (21:45:07Z) — a budget and a reading written as one number. `docs/release-checklist.md` (en + zh) and the workflow comment now say the 360 s ceiling and keep 364 s for the span. (3) `docs/progress.md` still carried the first draft of the anomaly sentence — "about 5 min 40 s after the fetch that should have gone stale by 21:44:05Z" — which matches neither the 364 s span nor the ~60 s past-expiry figure the corrected copies use; it now states those. While checking (1) the list's stated range, the two releases below it were sampled for the first time the same way: **157.8 s** for 0.21.25 (ack 17:56:53Z, record 17:59:30.781Z) and **96.1 s** for 0.21.26 (ack 18:41:40Z, record 18:43:16.084Z), whose step durations come back at 307 s and 304 s, exactly the two the duration list already records — so the run↔version attribution is confirmed by a number already on file. Eight gaps now exist and neither beats 310.0 s, so `maxMeasuredAckToRecordMs`, the 720 s ceiling and the shipped lists' stated range (0.21.27–0.21.32) are all unchanged; the two new readings live here rather than in every restatement, because a fourth copy of a list is a fourth thing to keep in sync.
|
|
15
|
+
|
|
16
|
+
### Compatibility
|
|
17
|
+
|
|
18
|
+
No library code, export, option, default, frame or storage key changed; `verify:compat`, `verify:types` and `verify:pack` run against `v0.21.32`. The edits are confined to `docs/`, `.github/workflows/release.yml`'s comment, `tests/workflows.test.ts`'s comment and `AGENTS.md`, none of which changes runtime behaviour; the workflow's `PUBLISHED_VERIFY_ATTEMPTS` stays at 96.
|
|
19
|
+
|
|
1
20
|
## [0.21.32] - 2026-09-25
|
|
2
21
|
|
|
3
22
|
A delivery bug, found while looking for something else. A topic's **first** publication from a tab that held a matching wildcard was dispatched locally and never forwarded, even while a live durable route named a different owner for it — and delivery is at-most-once, so that publication was gone for good. The fix is a deletion: the memo that made the decision decided nothing.
|
|
@@ -32,7 +51,7 @@ The `0.21.30` tag run failed its blocking published-consumer gate after a *succe
|
|
|
32
51
|
|
|
33
52
|
### Fixed
|
|
34
53
|
|
|
35
|
-
- **Release tooling: the published-consumer retry ceiling sat below the propagation lag it exists to absorb.** `PUBLISHED_VERIFY_ATTEMPTS=48` × 7500 ms is 360 s, and the 0.21.30 tag run consumed all of it on `ETARGET` while that version's tarball was already fetchable; the re-run against the unchanged tag then passed in 1 s. Two measured terms now size the number. (1) `npm publish`'s success ack precedes the registry's own `time[<version>]` record — by 74.8 s on 0.21.29, 96.8 s on 0.21.28, 248.7 s on 0.21.27 and 310.0 s on 0.21.30. (2) The packument that `npm pack` resolves against is served `cache-control: public, max-age=300`, so a copy of it anywhere in the path may be a full cache lifetime stale by design; that term is also what the three preceding green tag runs were reporting when they consumed 305 s, 304 s and 304 s of the ceiling in turn. The budget is now 96 × 7500 ms = 720 s, clearing the 310 + 300 = 610 s the two add to. Which copy served this run's stale views is **not** established: a purely npm-local-cache account predicts the retry should have waited for the same 300 s object and it did not, so the comment records the open question instead of naming a cache.
|
|
54
|
+
- **Release tooling: the published-consumer retry ceiling sat below the propagation lag it exists to absorb.** `PUBLISHED_VERIFY_ATTEMPTS=48` × 7500 ms is 360 s, and the 0.21.30 tag run consumed all of it on `ETARGET` while that version's tarball was already fetchable; the re-run against the unchanged tag then passed in 1 s. Two measured terms now size the number. (1) `npm publish`'s success ack precedes the registry's own `time[<version>]` record — by 74.8 s on 0.21.29, 96.8 s on 0.21.28, 248.7 s on 0.21.27 and 310.0 s on 0.21.30. (2) The packument that `npm pack` resolves against is served `cache-control: public, max-age=300`, so a copy of it anywhere in the path may be a full cache lifetime stale by design; that term is also what the three preceding green tag runs were reporting when they consumed 305 s, 304 s and 304 s of the ceiling in turn. The budget is now 96 × 7500 ms = 720 s, clearing the 310 + 300 = 610 s the two add to. Which copy served this run's stale views is **not** established: a purely npm-local-cache account predicts the retry should have waited for the same 300 s object and it did not, so the comment records the open question instead of naming a cache. *(That last inference was wrong, and `0.21.33` retracts it: the two readings it joins come from different machines — a laptop's `npm pack` and a new runner job whose cache starts empty — which is what the local account predicts rather than refutes. Its corrected form, plus `0.21.32`'s supporting sample, is in that release's notes.)*
|
|
36
55
|
- **`tests/workflows.test.ts` pinned a floor that the value which just failed satisfied.** The assertion was `totalMs >= 5 * 60 * 1000`, so the 360 s budget that had just turned a good release red passed the gate whose entire purpose is to stop that happening, and nothing would have objected to lowering it again. The floor is now the sum above, written as two named constants beside the rule that re-derives each. Measured against it: setting the workflow's attempts from 96 back to 48 fails exactly that test, and both the 360 s budget that failed here and the 120 s one 0.20.89 exhausted are rejected by it.
|
|
37
56
|
|
|
38
57
|
### Documentation
|
package/dist/centrifuge.js
CHANGED
|
@@ -2475,7 +2475,7 @@ var SDK_VERSION = (
|
|
|
2475
2475
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2476
2476
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2477
2477
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2478
|
-
true ? "0.21.
|
|
2478
|
+
true ? "0.21.33" : ""
|
|
2479
2479
|
);
|
|
2480
2480
|
|
|
2481
2481
|
// src/core/data-bus.ts
|
|
@@ -3715,4 +3715,4 @@ export {
|
|
|
3715
3715
|
parseDataBusPublication,
|
|
3716
3716
|
selectWorkerBackend
|
|
3717
3717
|
};
|
|
3718
|
-
//# sourceMappingURL=chunk-
|
|
3718
|
+
//# sourceMappingURL=chunk-FEWEXHZA.js.map
|
package/dist/cjs/centrifuge.cjs
CHANGED
|
@@ -2604,7 +2604,7 @@ var SDK_VERSION = (
|
|
|
2604
2604
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2605
2605
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2606
2606
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2607
|
-
true ? "0.21.
|
|
2607
|
+
true ? "0.21.33" : ""
|
|
2608
2608
|
);
|
|
2609
2609
|
|
|
2610
2610
|
// src/core/data-bus.ts
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -2570,7 +2570,7 @@ var SDK_VERSION = (
|
|
|
2570
2570
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2571
2571
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2572
2572
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2573
|
-
true ? "0.21.
|
|
2573
|
+
true ? "0.21.33" : ""
|
|
2574
2574
|
);
|
|
2575
2575
|
|
|
2576
2576
|
// src/core/data-bus.ts
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,6 @@ For a manual release (no `NPM_TOKEN` in the workflow), run `npm publish --access
|
|
|
42
42
|
2. Install the published tarball or version in a clean consumer and import the root plus every public subpath.
|
|
43
43
|
3. Record the result in the release notes. Do not advance to `1.0.0` until the public API and protocol deprecation policy are explicitly frozen.
|
|
44
44
|
|
|
45
|
-
The tagged-release workflow already ran the consumer verification above. Run `PUBLISHED_VERSION=<version> pnpm verify:published` by hand only when you need an offline repeat. The workflow's 12-minute ceiling is not padding, and the number is a sum of two measured lags. First: the registry records the publish minutes after `npm publish` returns — the ack→`time[<version>]` gap measured on 0.21.27 through 0.21.
|
|
45
|
+
The tagged-release workflow already ran the consumer verification above. Run `PUBLISHED_VERSION=<version> pnpm verify:published` by hand only when you need an offline repeat. The workflow's 12-minute ceiling is not padding, and the number is a sum of two measured lags. First: the registry records the publish minutes after `npm publish` returns — the ack→`time[<version>]` gap measured on 0.21.27 through 0.21.32 was 74.8–310.0 s, and 248.7 / 96.8 / 74.8 / 310.0 / 127.2 / 76.1 s in version order (read `time[<version>]` from the packument and compare it with the `Publish to npm` step's completion time). Second: the packument that `npm pack` resolves against is served with `cache-control: public, max-age=300`, so a copy of it anywhere in the path is allowed to be up to five minutes stale, and resolution cannot be trusted to beat one cache lifetime after a write that lands inside that window. Which copy — npm's local cache or an intermediate — served the stale views below is *not* established, and the counter-observation this note used to offer against a purely local account does not survive being read carefully, because it joins two machines: the 21:44:34Z "fresh fetch" was a `npm pack` run on a laptop, while the 1 s green at 21:47:10Z was a *new runner job*, and a new job's npm cache starts empty — which is what the local account predicts rather than what contradicts it. What a purely-local account still cannot explain is one specific reading: the runner's own `ETARGET` at 21:45:07Z — 364 s after the step that opened its first poll (21:39:03Z), so ~60 s *after* the point where a copy taken then must have gone stale (21:44:03Z, give or take the second the request actually left). The ceiling is therefore arithmetic rather than a model: worst-case ack→`time[]` gap plus one cache lifetime. That sum is not theoretical: the previous 6-minute ceiling was exhausted by a *successful* publish, the 0.21.30 tag run spending its whole 360 s budget (48 × 7.5 s) on `ETARGET` while that version's tarball HEAD and a local `npm pack` had both already succeeded, and the same step 123 s later taking 1 s. The 0.20.89 run had shown the same shape against that release's 2-minute budget. So a long retrying verify is normal propagation, not an unusually slow mirror — raise `PUBLISHED_VERIFY_ATTEMPTS` / `PUBLISHED_VERIFY_DELAY_MS` further only if the ack→`time[]` gap has itself grown. Note the asymmetry when repeating by hand: `verify-published-consumer.mjs` defaults to `6` attempts × `5_000` ms — 30 seconds, not 12 minutes — because the tag run passes the larger pair as env rather than the script owning it. So a manual repeat can report `ETARGET` on a version whose CI run was green, which reads like a failed publish and is not one; reproduce the CI budget explicitly with `PUBLISHED_VERSION=<version> PUBLISHED_VERIFY_ATTEMPTS=96 PUBLISHED_VERIFY_DELAY_MS=7500 pnpm verify:published`, or judge a manual failure against the 30 s budget you actually ran. Read the step's duration as a diagnostic rather than a mood: the five green tag runs from 0.21.25 to 0.21.29 took 307, 304, 305, 304 and 304 s, 0.21.31's took 308 s and 0.21.32's 304 s — seven samples in a 5-second band, which is what one 300 s cache lifetime plus a few seconds of command time predicts (take the numbers from each release run's `Verify published npm consumers` step, `steps[].started_at`/`completed_at` from the same `gh api` call). 0.21.31 and 0.21.32 are the first two releases to run under the raised 720 s ceiling, and their two lags split differently, which is the useful contrast: 127.2 s of record plus ~181 s of cache for the one, and 76.1 s of record plus ~228 s before the runner saw it for the other — on 0.21.32 a `no-cache` packument read from a laptop reported the new `latest` 151 s in, while the runner's own poll kept failing until 304 s, i.e. one lifetime after *its* first fetch. So the sample that set the previous ceiling has not been beaten: if the *ack→`time[]`* figure grows past 310 s, raise `PUBLISHED_VERIFY_ATTEMPTS` again; a verify that finishes in seconds means whatever copy it read already carried the new version.
|
|
46
46
|
|
|
47
47
|
**Do not adjudicate a publish from `dist-tags.latest`.** The package document endpoint and the tarball URL are two different read paths, and their timestamps are worth more than the impression they first give. Read during the 0.21.30 release, in order: `npm publish` returned success at `21:39:03Z`; a `curl -s -H "Cache-Control: no-cache" https://registry.npmjs.org/<pkg>` at `21:43:15Z` reported `latest` still on the previous version, no `0.21.30` in `versions`, and no `time['0.21.30']`; npm's own stamp for that version is `21:44:12.987Z` and the document's `last-modified` is `21:44:14 GMT`. So the 21:43:15 read was **not** a replica refusing an artifact that already existed — the registry had not finished recording the publish, and `no-cache` could not have made it see a version that did not exist yet. Absence from `versions` and from `latest`, minutes after a green publish step, is the expected state of a successful release. The genuine read-path disagreement came 60 s later: between `21:44:14Z` and `21:44:35Z`, `curl -I …/-/<pkg>-0.21.30.tgz` returned `HTTP 200` and a local `npm pack <pkg>@0.21.30 --registry https://registry.npmjs.org` completed and wrote the tarball, while the workflow runner's own `npm pack` kept returning `ETARGET` until it exhausted its budget at `21:45:07Z`. Same version, two machines, opposite answers a minute apart. To answer "did the publish happen", HEAD the tarball URL and try the pack — those reach the artifact — and try it **from the place that is failing**, because "fetchable" is a per-machine answer; read `latest` only for the question "which version do consumers get by default", which is a different one and settles later. `npm view <pkg> version` adds a third path on top: it can be served from npm's *local* packument cache, so on a machine that queried the registry before the publish it can return the superseded version even after the global `latest` has moved (that is why this gate resolves its target from `PUBLISHED_VERSION` and refuses an inferred version that disagrees with `package.json`). Never re-push, move, or replace a tag because a metadata read looked stale; the tag is immutable once published.
|
package/docs/roadmap.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
0.21.
|
|
3
|
+
0.21.33 was released on September 25, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
|
|
4
|
+
|
|
5
|
+
## 0.21.33 delivered scope
|
|
6
|
+
|
|
7
|
+
- **A measurement cited as evidence against a model turned out to join two machines.** The publish-lag prose argued that a purely-local npm cache account was refuted because "the retry resolved before a fresh 21:44 fetch's 300 s lifetime had run out". Against the run records, the fresh read was a laptop's `npm pack` at 21:44:35Z and the 1 s green was a **new runner job** at 21:47:10Z — and a new job's cache starts empty, which is what the local model predicts rather than what refutes it. The claim is withdrawn, and what a local account still cannot explain is stated as the single reading it is: the runner's own `ETARGET` at 21:45:07Z, 364 s after its poll loop opened at 21:39:03Z and ~60 s after a copy taken then must have expired. The verdict stays *not established*.
|
|
8
|
+
- **`0.21.32` supplied the model's cleanest sample while this was being written.** Ack 23:34:46Z, registry record 23:36:02.091Z (**76.1 s**), a laptop reading the new `latest` at 151 s with `no-cache`, and the runner's blocking step finishing at **304 s** — one lifetime plus command time, measured from its own first fetch. The verify-duration band is now seven green samples at 304–308 s; both sample lists were extended in both languages, in the workflow comment and in the test that enforces the floor.
|
|
9
|
+
- **Three of those numbers named the wrong quantity, and all three went before the tag.** "the second-smallest of the **seven** now listed" had borrowed the duration list's size for the gap list, which carries six values (the seven is 307 / 304 / 305 / 304 / 304 / 308 / 304 s of *step duration*), and the sentence's own parenthesis already enumerated six. Three copies described 0.21.30 as spending "its whole **364 s** budget", but that run's ceiling was 360 s (48 × 7.5 s) and 364 s is only the span from its first poll to its last `ETARGET` — a budget and a reading written as one number. And `docs/progress.md` still carried the superseded first draft of that span, "about 5 min 40 s after the fetch that should have gone stale by 21:44:05Z", matching neither the 364 s span nor the ~60 s past expiry every other copy states. Checking the first of those meant sampling the two releases below the gap list's stated range for the first time: 0.21.25 is **157.8 s** (ack 17:56:53Z, record 17:59:30.781Z) and 0.21.26 **96.1 s** (ack 18:41:40Z, record 18:43:16.084Z) — and their verify steps come back at 307 s and 304 s, exactly the two values the duration list already carries, so the run↔version attribution is confirmed by a number already on file rather than by the new ones. Eight gaps now exist, none beats 310.0 s, and the two new readings are recorded here and in `CHANGELOG.md` rather than added to every restatement of the list, because a fifth copy of a set of numbers is a fifth thing to keep in sync.
|
|
10
|
+
- **What did *not* change, and the check that proves it.** 76.1 s does not beat the 310 s that sizes the 720 s ceiling, so `maxMeasuredAckToRecordMs` stays at 310 s and `tests/workflows.test.ts` passes 6/6 unmodified — widening the sample list without moving the derivation is the result this release was looking for, not a reason to raise the budget again.
|
|
11
|
+
- **History corrected by pointer, not by rewrite.** `CHANGELOG.md`'s `0.21.31` entry and both `docs/roadmap.md` copies still carry the sentence they got wrong, each with a note naming `0.21.33` as what superseded it, so the record reads as the sequence it actually was. `AGENTS.md` gained the general rule beside the one it already had: before citing an observation against a model, check both halves came from the same machine and the same path.
|
|
12
|
+
- **What did not move.** No library code, export, option, default, frame or storage key changed; `verify:compat`, `verify:types` and `verify:pack` run against `v0.21.32`, and the arm ledger and coverage aggregates are `0.21.32`'s (46 zero-count arms of 1951; 99.02 / 97.64 / 99.27 / 99.69).
|
|
4
13
|
|
|
5
14
|
## 0.21.32 delivered scope
|
|
6
15
|
|
|
@@ -12,7 +21,7 @@
|
|
|
12
21
|
|
|
13
22
|
## 0.21.31 delivered scope
|
|
14
23
|
|
|
15
|
-
- **The release gate that failed on a good publish, sized by measurement instead of by feel.** `0.21.30`'s tag run consumed the whole 48 × 7.5 s published-consumer budget on `ETARGET` and then passed in 1 s when the same tag was re-run. The ceiling turns out to be a sum of two lags, both now measured: `npm publish`'s ack precedes the registry's own `time[<version>]` record by 74.8 s (0.21.29), 96.8 s (0.21.28), 248.7 s (0.21.27) and 310.0 s (0.21.30), and the packument `npm pack` resolves against is served `cache-control: public, max-age=300`, so a copy of it anywhere in the path may be a full cache lifetime stale by design. That second term is also what the three preceding green runs had been reporting with their suspiciously identical 305 / 304 / 304 s — a regularity that reads as noise until something explains it. The budget is now 96 × 7.5 s = 720 s, clearing 610 s. Which copy served the stale views is deliberately left open: a purely npm-local-cache account predicts the retry should have waited for the same 300 s object and it did not.
|
|
24
|
+
- **The release gate that failed on a good publish, sized by measurement instead of by feel.** `0.21.30`'s tag run consumed the whole 48 × 7.5 s published-consumer budget on `ETARGET` and then passed in 1 s when the same tag was re-run. The ceiling turns out to be a sum of two lags, both now measured: `npm publish`'s ack precedes the registry's own `time[<version>]` record by 74.8 s (0.21.29), 96.8 s (0.21.28), 248.7 s (0.21.27) and 310.0 s (0.21.30), and the packument `npm pack` resolves against is served `cache-control: public, max-age=300`, so a copy of it anywhere in the path may be a full cache lifetime stale by design. That second term is also what the three preceding green runs had been reporting with their suspiciously identical 305 / 304 / 304 s — a regularity that reads as noise until something explains it. The budget is now 96 × 7.5 s = 720 s, clearing 610 s. Which copy served the stale views is deliberately left open: a purely npm-local-cache account predicts the retry should have waited for the same 300 s object and it did not. *(Superseded by `0.21.33`: that inference joined a laptop's `npm pack` read with a new runner job's 1 s green, i.e. two machines, and a new job starting with an empty cache is what the local account predicts. Its surviving anomaly is one reading — the runner's `ETARGET` ~60 s after its own copy must have expired — and 0.21.32's 304 s verify is the model's cleanest sample, with the band now seven releases wide at 304–308 s.)*
|
|
16
25
|
- **A floor that blessed the value which had just failed.** `tests/workflows.test.ts` asserted `totalMs >= 5 * 60 * 1000`, so the 360 s budget that had just turned a good release red satisfied the gate whose only job is to prevent that, and a further lowering would have gone unnoticed. The floor is now the two terms above, as named constants beside the rule for re-deriving each. Mutation-checked in both directions: reverting the workflow to 48 fails exactly that test with a message naming both lags, and the same floor rejects the 360 s budget that failed here and the 120 s one 0.20.89 exhausted.
|
|
17
26
|
- **A note about that failure, retracted against its own timestamps.** The checklist paragraph written during the same release said the package document endpoint and the tarball URL had disagreed "in the same minute". The readings it quotes put the stale-document read at 21:43:15Z and the registry's record of the version at 21:44:12.987Z — so that read was not lagging an artifact that already existed; it was correctly reporting a publish the registry had not finished writing, and no `no-cache` request could have helped. The genuine disagreement is one machine fetching the tarball by 21:44:35Z while the runner was still on `ETARGET` at 21:45:07Z, which is why the text now says to probe from the machine that is failing, and why absence from `versions`/`latest` minutes after a green publish step is documented as the *expected* state of a successful release.
|
|
18
27
|
- **`wildcardPublishCache`: the per-topic map the documented cap does not cover, and why a cap is not free.** It maps a concrete topic to the local wildcard pattern that matched it, or `null` for "scanned, nothing matched"; it has no bound, and is cleared only by the two lifecycle teardowns beside each `routeOwnerCache.clear()`. Measured: 1,200 `publish()` calls on distinct topic names leave `knownTopics` at its 500-entry cap and this map holding 1,200. The cost of evicting either half is asymmetric — dropping a *pattern* re-runs the first local dispatch the entry exists to prevent, dropping a `null` re-scans and, if a wildcard was assigned in between, hands that publication the fan-out a first one gets — so the note records that bounding it needs a rule about which half may be forgotten rather than a number, and `docs/architecture.md` says the same in the paragraph after "Cap and eviction".
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
2. 在干净消费者中安装已发布版本或 tarball,并导入主入口及所有公开子路径。
|
|
44
44
|
3. 将结果记录到发布说明。在公开 API 和协议弃用策略明确冻结前,不进入 `1.0.0`。
|
|
45
45
|
|
|
46
|
-
打 tag 的发布工作流已自动执行上述消费者验证;仅在需要离线复验时才手动运行 `PUBLISHED_VERSION=<version> pnpm verify:published`。工作流的 12 分钟上限不是凑数,而是两段实测延迟之和。第一段:`npm publish` 返回成功后,registry 还要几分钟才把这条版本记进去——0.21.27 到 0.21.
|
|
46
|
+
打 tag 的发布工作流已自动执行上述消费者验证;仅在需要离线复验时才手动运行 `PUBLISHED_VERSION=<version> pnpm verify:published`。工作流的 12 分钟上限不是凑数,而是两段实测延迟之和。第一段:`npm publish` 返回成功后,registry 还要几分钟才把这条版本记进去——0.21.27 到 0.21.32 的"发布确认 → `time[<version>]`"间隔实测为 74.8–310.0 秒,按版本顺序分别是 248.7 / 96.8 / 74.8 / 310.0 / 127.2 / 76.1 秒(自己去 packument 读 `time[<version>]`,再和工作流 `Publish to npm` 步骤的完成时刻相减)。第二段:`npm pack` 用来解析版本的 packument 是以 `cache-control: public, max-age=300` 提供的,也就是说路径上任何一份副本按设计都可以旧五分钟,落在这一窗口内写入的版本,解析不太可能快过一整个缓存生命周期。究竟是哪个副本(npm 本地缓存,还是中间的 CDN)在下面提供了过期视图**并未确立**,而这段文字此前用作"反对纯本地解释"的那个观测,仔细读过之后站不住,因为它把两台机器拼成了一句话:21:44:34Z 那次"新鲜读取"是在笔记本上跑 `npm pack`,而 21:47:10Z 一秒就绿的是一次*全新的 runner 任务*,新任务的 npm 缓存本来是空的——这恰好是纯本地模型预测的结果,不是反驳它的结果。纯本地模型仍然解释不了的,只有一个读数:runner 自己在 21:45:07Z 仍然报 `ETARGET`——距离开始轮询的那一步(21:39:03Z)已经 364 秒,也就是比"那一刻抓到的副本必然已过期"的时间点(21:44:03Z,误差在请求真正发出的那一秒之内)还要晚约 60 秒。因此上限靠的是算术而不是模型:最坏情况的"确认 → `time[]`"间隔,加上一个缓存生命周期。这个和并非理论值——上一个 6 分钟上限被一次*成功*的发布排空过:0.21.30 的 tag 运行把整整 360 秒预算(48 × 7.5 秒)花在 `ETARGET` 上,而那时该版本的 tarball HEAD 和本地 `npm pack` 都已经成功;123 秒后同一步骤只用了 1 秒。0.20.89 针对它当时的 2 分钟预算也是同一形状。所以长时间的反复重试是正常的传播延迟,不是镜像异常慢——只有"确认 → `time[]`"间隔本身变长了,才需要继续调大 `PUBLISHED_VERIFY_ATTEMPTS` 和 `PUBLISHED_VERIFY_DELAY_MS`。手动复验时要注意这个不对称:`verify-published-consumer.mjs` 的默认值是 `6` 次 × `5_000` ms——30 秒而不是 12 分钟——因为更大的那一对是由 tag 工作流以 env 传入、而非脚本自带。于是手动复跑可能在一个 CI 全绿的版本上报出 `ETARGET`,这看起来像发布失败而实际不是;要对齐 CI 预算就显式执行 `PUBLISHED_VERSION=<version> PUBLISHED_VERIFY_ATTEMPTS=96 PUBLISHED_VERIFY_DELAY_MS=7500 pnpm verify:published`,否则请按你实际跑到的 30 秒预算来判断手动失败。把这个步骤的耗时当诊断读数而不是情绪指标:0.21.25 到 0.21.29 那五次*绿色* tag 运行分别用了 307、304、305、304、304 秒,0.21.31 用了 308 秒,0.21.32 用了 304 秒——七个样本落在 5 秒的区间里,而这正是"一个 300 秒缓存生命周期 + 几秒命令时间"会预测出的形状(数字自己取:对每次发布运行 `gh api` 出来的 jobs 里,`Verify published npm consumers` 这一步的 `steps[].started_at`/`completed_at`)。0.21.31 与 0.21.32 是头两个跑在提高后的 720 秒上限下的发布,而它们两段延迟的切分方式不同,这个对照正是有用的地方:前一次是 127.2 秒落库 + 约 181 秒缓存;后一次是 76.1 秒落库 + 之后约 228 秒 runner 仍看不见它——在 0.21.32 上,笔记本在 151 秒时用 `no-cache` 读 packument 已经能看到新的 `latest`,而 runner 自己的轮询一直到 304 秒才成功,也就是从*它自己*首次抓取算起整整一个生命周期。所以当初定这个上限的那个样本至今没有被超过:如果*确认 → `time[]`*这一段自己长过了 310 秒,就再调大 `PUBLISHED_VERIFY_ATTEMPTS`;而几秒就结束的 verify 只说明它读到的那份副本已经带上了新版本。
|
|
47
47
|
|
|
48
48
|
**不要用 `dist-tags.latest` 来判断发布是否发生。** 包文档端点与 tarball URL 是两条不同的读取路径,而它们的时间戳比这两条路径给的第一手印象更值得读。0.21.30 发布期间按顺序读到:`npm publish` 在 `21:39:03Z` 返回成功;`21:43:15Z` 的一次 `curl -s -H "Cache-Control: no-cache" https://registry.npmjs.org/<pkg>` 仍报 `latest` 是上一个版本、`versions` 里没有 `0.21.30`、`time['0.21.30']` 也不存在;而 npm 自己给这个版本盖的时间戳是 `21:44:12.987Z`,文档的 `last-modified` 是 `21:44:14 GMT`。所以 21:43:15 那次读取**不是**一份副本在拒绝已经存在的产物——registry 当时确实还没把这次发布记完,`no-cache` 也不可能让它看见尚不存在的版本;在发布步骤全绿之后的几分钟里,`versions` 与 `latest` 里没有这个版本,正是一次成功发布的正常状态。真正的读取路径分歧出现在 60 秒之后:`21:44:14Z` 到 `21:44:35Z` 之间,`curl -I …/-/<pkg>-0.21.30.tgz` 返回 `HTTP 200`,本地一次 `npm pack <pkg>@0.21.30 --registry https://registry.npmjs.org` 也成功写出了 tarball,而工作流 runner 自己的 `npm pack` 一直 `ETARGET` 到 `21:45:07Z` 把预算排空。同一个版本、两台机器、相隔一分钟给出相反答案。要回答"发布到底成没成",去 HEAD 那个 tarball URL 并试一次 pack——它们摸到的是产物本身——而且要从**正在失败的那台机器**上去试,因为"能取到"是一台机器才有的答案;`latest` 只回答"消费者默认拿到哪个版本",那是另一个问题,而且会更晚才稳定。`npm view <pkg> version` 在这之上还有第三条路径:它可能来自 npm 的**本地** packument 缓存,于是在发布前查过一次的机器上,即使全局 `latest` 已经前进,它仍会返回被取代的旧版本——这正是本门禁把目标版本固定取自 `PUBLISHED_VERSION`、并拒绝与 `package.json` 不一致的推断值的原因。不要因为一次元数据读取看起来过期就重推、移动或替换 tag;发布出去的 tag 是不可变的。
|
package/docs/zh/roadmap.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# 路线图
|
|
2
2
|
|
|
3
|
-
0.21.
|
|
3
|
+
0.21.33 已于 2026 年 9 月 25 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
|
|
4
|
+
|
|
5
|
+
## 0.21.33 已完成范围
|
|
6
|
+
|
|
7
|
+
- **一条被当作"反证"的观测,其实把两台机器拼在了一起。** 关于发布延迟的正文曾用"重跑成功的时间早于 21:44 那次新鲜读取的 300 秒有效期到期"来反驳"纯 npm 本地缓存"的说法。对着运行记录看:那次新鲜读取是 21:44:35Z 笔记本上的 `npm pack`,而 1 秒就绿的那次是 21:47:10Z 的**全新 runner 任务**——新任务的缓存本来是空的,这正是本地模型预测的结果,不是反例。该论断撤回;本地模型仍然解释不了的,现在明确写成它本来的样子:只剩一个读数——runner 自己在 21:45:07Z 仍报 `ETARGET`,距轮询开启(21:39:03Z)已 364 秒,比那份副本理应过期的时刻还晚约 60 秒。结论依旧保持"未确立"。
|
|
8
|
+
- **写下这段的同时,`0.21.32` 送来了这个模型最干净的样本。** 发布确认 23:34:46Z,registry 记录 23:36:02.091Z(**76.1 秒**),笔记本在 151 秒时用 `no-cache` 已经读到新的 `latest`,而 runner 的阻塞步骤一直到 **304 秒** 才通过——从它自己首次抓取算起正好一个生命周期再加命令时间。于是 verify 耗时区间扩为七个绿色样本、304–308 秒;两份样本清单在两种语言、工作流注释、以及钉住下限的那条测试里都补齐了。
|
|
9
|
+
- **其中三个数字说的是另一个量,而且都在打 tag 之前改掉了。** "七个里第二小的"把耗时清单的个数借给了延迟清单——后者只有六个值(七个的那份是步骤*耗时*:307 / 304 / 305 / 304 / 304 / 308 / 304 秒),而且同一句话自己的括号里已经列了六个。另有三处写 0.21.30"把整整 **364 秒预算**花在 `ETARGET` 上",可那一次的上限是 360 秒(48 × 7.5 秒),364 秒只是从它首次轮询到最后一个 `ETARGET` 的间隔——预算和读数被写成了同一个数。而 `docs/progress.md` 还留着那句的第一稿"比 21:44:05Z 本该失效的那次抓取晚大约 5 分 40 秒",既不等于 364 秒的间隔,也不等于其它所有副本都写的"过期后再约 60 秒"。为了核对第一条,延迟清单声明区间之下的两个发布第一次被同样取样:0.21.25 是 **157.8 秒**(确认 17:56:53Z、记录 17:59:30.781Z),0.21.26 是 **96.1 秒**(确认 18:41:40Z、记录 18:43:16.084Z)——而它们的 verify 步骤耗时分别回到 307 秒和 304 秒,正是耗时清单里已有的那两个值,于是"哪次运行属于哪个版本"是被一个早就在案的数字确认的,不是靠新数字自证。现在共有八个延迟样本,没有一个超过 310.0 秒;这两个新读数写在这里和 `CHANGELOG.md`,而不是补进清单的每一处复述——多一份副本就多一处需要同步。
|
|
10
|
+
- **没有改的东西,以及证明它没改的检查。** 76.1 秒没有超过决定 720 秒上限的 310 秒,所以 `maxMeasuredAckToRecordMs` 原样不动,`tests/workflows.test.ts` 未作修改仍 6/6 通过——扩大样本而不必重算推导,正是这次发布想要的结果,而不是再调预算的理由。
|
|
11
|
+
- **历史记录按指针修正,而不是改写。** `CHANGELOG.md` 的 `0.21.31` 条目和两份 `docs/roadmap.md` 仍然保留当时写错的那句话,各自补上指明由 `0.21.33` 取代的说明,让记录读起来就是它本来的先后顺序。`AGENTS.md` 在"要从正在失败的那台机器去探"这条既有规则旁边,添了更一般的那条:把一次观测当作反证引用之前,先确认它的两半来自同一台机器、同一条读取路径。
|
|
12
|
+
- **没有动的东西。** 库代码、导出、配置项、默认值、帧格式与存储键都未变化;`verify:compat`、`verify:types`、`verify:pack` 针对 `v0.21.32` 运行,分支账本与覆盖率沿用 `0.21.32` 的(1951 支里 46 支零计数;99.02 / 97.64 / 99.27 / 99.69)。
|
|
4
13
|
|
|
5
14
|
## 0.21.32 已完成范围
|
|
6
15
|
|
|
@@ -12,7 +21,7 @@
|
|
|
12
21
|
|
|
13
22
|
## 0.21.31 已完成范围
|
|
14
23
|
|
|
15
|
-
- **那次"发布成功但闸门失败"的排查:预算改成测量出来的和,而不是凭感觉。** `0.21.30` 的 tag 运行把 48 × 7.5 秒的已发布消费者预算整个耗在 `ETARGET` 上,而针对同一个 tag 重跑时这一步只用了 1 秒。这个上限其实是两段延迟之和,两段现在都测过:`npm publish` 返回确认早于 registry 自己给这条版本盖的 `time[<version>]`——0.21.29 是 74.8 秒,0.21.28 是 96.8 秒,0.21.27 是 248.7 秒,0.21.30 是 310.0 秒;而 `npm pack` 用来解析的 packument 是以 `cache-control: public, max-age=300` 提供的,也就是说路径上任何一份副本按设计都可以旧一整个缓存生命周期。第二段也正是前三次绿色运行那可疑一致的 305 / 304 / 304 秒在报告的东西——一个在没人解释之前只会被读成噪声的规律。于是预算改为 96 × 7.5 秒 = 720 秒,覆盖 610 秒。究竟是哪一份副本提供了过期视图,这里是故意留空的:纯 npm 本地缓存的解释会预测重跑同样要等满 300
|
|
24
|
+
- **那次"发布成功但闸门失败"的排查:预算改成测量出来的和,而不是凭感觉。** `0.21.30` 的 tag 运行把 48 × 7.5 秒的已发布消费者预算整个耗在 `ETARGET` 上,而针对同一个 tag 重跑时这一步只用了 1 秒。这个上限其实是两段延迟之和,两段现在都测过:`npm publish` 返回确认早于 registry 自己给这条版本盖的 `time[<version>]`——0.21.29 是 74.8 秒,0.21.28 是 96.8 秒,0.21.27 是 248.7 秒,0.21.30 是 310.0 秒;而 `npm pack` 用来解析的 packument 是以 `cache-control: public, max-age=300` 提供的,也就是说路径上任何一份副本按设计都可以旧一整个缓存生命周期。第二段也正是前三次绿色运行那可疑一致的 305 / 304 / 304 秒在报告的东西——一个在没人解释之前只会被读成噪声的规律。于是预算改为 96 × 7.5 秒 = 720 秒,覆盖 610 秒。究竟是哪一份副本提供了过期视图,这里是故意留空的:纯 npm 本地缓存的解释会预测重跑同样要等满 300 秒,而它没有。(这一句已被 `0.21.33` 撤回:它把笔记本上的一次 `npm pack` 读取,和一个全新 runner 任务 1 秒就绿拼成了一句话,也就是两台机器;新任务的缓存本来是空的,"读到新鲜"正是纯本地模型预测的结果而不是反例。它真正解释不了的只剩一个读数——runner 在自己的副本理应过期约 60 秒之后仍然报 `ETARGET`——而 0.21.32 那次 304 秒的 verify 是这个模型最干净的一个样本,区间也因此扩到七个样本、304–308 秒。)
|
|
16
25
|
- **一道放过了"刚刚失败的那个值"的下限。** `tests/workflows.test.ts` 断言的是 `totalMs >= 5 * 60 * 1000`,也就是说把一次好发布判红的那个 360 秒预算,恰好满足这道专为阻止此事而存在的闸门,而且再往下调也不会有人拦。现在下限换成上面那两项之和,写成两个具名常量,并且把每一项怎么重新取数写在旁边。双向都做过核对:把工作流改回 48 会让这一条测试精确失败,报错信息同时点出两段延迟;同一条下限也会拒绝这里失败的 360 秒和 0.20.89 当年排空的 120 秒。
|
|
17
26
|
- **一条关于该失败的说明,被它自己的时间戳推翻。** 同一次发布期间写进发布清单的那段说,包文档端点与 tarball URL"在同一分钟里"给出了相反答案。而它自己引用的读数把那次过期文档读取放在 21:43:15Z,把 registry 对该版本的记录放在 21:44:12.987Z——所以那次读取根本不是在拒绝一个已经存在的产物,它是在如实报告一次 registry 还没写完的发布,`no-cache` 也救不了它。真正的分歧是:一台机器最迟在 21:44:35Z 已经能取到 tarball,而 runner 到 21:45:07Z 还在 `ETARGET`。因此正文现在要求**从正在失败的那台机器**去探,并且把"发布步骤全绿之后几分钟里 `versions`/`latest` 仍然没有它"写成一次成功发布的**预期**状态。
|
|
18
27
|
- **`wildcardPublishCache`:那条被文档化的上限并不覆盖的按 topic 映射,以及为什么加个上限不是免费的。** 它把具体 topic 映射到匹配它的本地通配模式,或映射到 `null` 表示"扫过,没匹配";它没有任何界限,只在两处生命周期拆解里清理,位置与每一次 `routeOwnerCache.clear()` 相邻。实测:对互不相同的 topic 名调用 1,200 次 `publish()` 之后,`knownTopics` 仍停在 500 条上限,而这张表持有 1,200 条。逐出哪一半的代价并不对称——丢一个*模式*会重跑这张表本要阻止的首次本地分发;丢一个 `null` 会重新扫描,而如果这期间分派过一个通配,这次发布就会拿到只有首发才有的那种扇出。所以注释记下的是:要给这张表设界,需要的是一条"允许忘掉哪一半"的规则,不是一个数字。`docs/architecture.md` 在"上限与逐出"那段之后也写了同一件事。
|
package/package.json
CHANGED
|
File without changes
|