cross-tab-worker-databus 0.21.45 → 0.21.46
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 +21 -1
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-36TEEZQ5.js → chunk-VUHBF4CY.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 +6 -1
- package/docs/roadmap.md +1 -1
- package/docs/zh/release-checklist.md +6 -1
- package/docs/zh/roadmap.md +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-36TEEZQ5.js.map → chunk-VUHBF4CY.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.21.46] - 2026-09-30
|
|
2
|
+
|
|
3
|
+
`0.21.45` shipped a guard whose own release note explained why it did not yet work in the case that mattered. This is that case, fixed, plus the dependency the release's audit should have caught and did not. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.45`'s.
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The host-load guard refused a loaded sample *after* writing it, and declined to judge the only case the archive actually presents.** Both halves had to close, because either alone leaves the gap open. `bench-compare` exits non-zero on a loaded sample, but the report is already on disk — and a report on disk **is** the baseline the next five comparisons read, so a verdict printed about a file that already exists decides nothing. The decision and the write are now one call (`archiveReport`), so a refusal is a file that does not exist. And the no-baseline leg returned "nothing to compare against", which on this archive — no report records a reading at all — meant the guard was weakest exactly where the archive is weakest, admitting the first sample taken on a busy host and making it the reference for everything after. That leg now falls back to an absolute, machine-relative ceiling: a one-minute load average above the host's **core count** is oversubscribed by definition and needs no history to establish.
|
|
8
|
+
- **A missing archive directory is an empty archive, not a crash.** The reader that now gates the write listed its directory unguarded, so the first `pnpm bench:browser` on a clean checkout — the run that is meant to *create* `bench-results/` — threw. It also broke `pnpm bench:compare` on a fresh clone, whose "need two reports" guard runs *after* the listing. Found by a test asserting the directory gets created, which failed for this reason rather than for the reason it was written.
|
|
9
|
+
- **`brace-expansion` 5.0.9 → 5.0.12**, carrying three advisories (two high: stack exhaustion via nested brace groups and via `parseCommaParts`; one moderate: quadratic-time `{a},b}` expansion) reached only through `eslint > minimatch`, so no consumer of this package was ever exposed. Pinned by an override in `pnpm-workspace.yaml`, inside `minimatch`'s own declared `^5.0.8` range rather than forcing a major.
|
|
10
|
+
|
|
11
|
+
### Corrected
|
|
12
|
+
|
|
13
|
+
- **`0.21.45`'s claim that the dependency audit found no known vulnerabilities was wrong**, and the `0.21.45` section now says so. The two high advisories were published at `2026-09-29T23:45Z` and the audit that reported a clean tree ran about three hours later against `registry.npmjs.org`, so neither publication timing nor a mirror explains it — the advisories existed and were findable. What is established is the version: the lockfile held `5.0.9`, inside all three vulnerable ranges. `pnpm audit` is the one gate in the release checklist whose signal is entirely a third party's document and the only one with no corroborating local reading, so a green local audit means *"the document I was served did not list my versions"* — a statement about a cache rather than about the tree. The clean result above is re-derived from the installed version against the published ranges rather than from the tool's exit code.
|
|
14
|
+
|
|
15
|
+
### Verification
|
|
16
|
+
|
|
17
|
+
- `pnpm check` — 39 test files, **1015 tests**, typecheck, build, 5 perf gates; `pnpm lint`; `pnpm test:coverage` at 99.03 / 97.60 / 99.27 / 99.69; `pnpm bench`; `verify:compat`, `verify:types`, `verify:pack` all against `v0.21.45`; `pnpm audit` on the public registry, clean and independently re-derived; `git diff --check` clean.
|
|
18
|
+
- Four mutants across the guard, each applied alone and restored, with the applied-line count printed and asserted **before** each run — keyed on changed lines rather than added ones, because every one of these is a substitution that adds nothing and a `>`-only check reports them all as unapplied.
|
|
19
|
+
- **The deferral was produced by the tool for the first time in 25 tags.** `pnpm bench:browser` ran to completion, printed its report, and then refused to archive it and exited non-zero: *"host load 24.67 exceeds the host's 8 core(s) and no archived report records a reading to compare against"*. The archive held 91 reports before and after. The standing rule in both release checklists — defer the sample and record its load numbers — is now a gate that runs rather than a judgment someone makes, and this release is the first to demonstrate it on the real script rather than in a test.
|
|
20
|
+
|
|
1
21
|
## [0.21.45] - 2026-09-30
|
|
2
22
|
|
|
3
23
|
A release ten cycles in the making, and the reason it was late is now part of the tooling: the browser benchmark's deferral rule was enforced by a human remembering it, so it slipped. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.44`'s.
|
|
@@ -20,7 +40,7 @@ A release ten cycles in the making, and the reason it was late is now part of th
|
|
|
20
40
|
|
|
21
41
|
- `pnpm check` — 39 test files, **999 tests**, typecheck, build, 5 perf gates; `pnpm lint`; the documentation gate at 25.
|
|
22
42
|
- Twenty-four mutants across the changes above, each applied alone and restored, each with its own named failure recorded in `docs/progress.md`.
|
|
23
|
-
- `pnpm audit --registry=https://registry.npmjs.org` — no known vulnerabilities.
|
|
43
|
+
- `pnpm audit --registry=https://registry.npmjs.org` — reported no known vulnerabilities, and that report was **wrong**: the tree carried `brace-expansion@5.0.9` with three advisories (two high, one moderate) through `eslint > minimatch`. The advisories were published about three hours *before* the audit ran, so this is not publication timing, and the registry named was the right one. Fixed in `0.21.46` by an override, with the finding recorded there rather than softened here.
|
|
24
44
|
- `docs/benchmarks.md` and its zh mirror regenerate byte-identical from the 91-report archive, so the generated tables and their committed copies have not drifted; the archive itself is untracked, which is why that is a run rather than a gate.
|
|
25
45
|
- **The browser benchmark was deferred again, with its load numbers, as the checklist's standing decision requires** — 79.78 / 120.85 / 129.80 / 145.31 across six readings on 8 cores at the end of this cycle. The archive's newest report is still `browser-2026-09-24T13-40-52-413Z.json`, and 24 tags have now shipped without a sample. What this release changes is that a loaded host can no longer *silently* produce the sample that ends that streak: the next run either produces a clean sample or is refused with its numbers.
|
|
26
46
|
|
package/dist/centrifuge.js
CHANGED
|
@@ -2540,7 +2540,7 @@ var SDK_VERSION = (
|
|
|
2540
2540
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2541
2541
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2542
2542
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2543
|
-
true ? "0.21.
|
|
2543
|
+
true ? "0.21.46" : ""
|
|
2544
2544
|
);
|
|
2545
2545
|
|
|
2546
2546
|
// src/core/data-bus.ts
|
|
@@ -3793,4 +3793,4 @@ export {
|
|
|
3793
3793
|
parseDataBusPublication,
|
|
3794
3794
|
selectWorkerBackend
|
|
3795
3795
|
};
|
|
3796
|
-
//# sourceMappingURL=chunk-
|
|
3796
|
+
//# sourceMappingURL=chunk-VUHBF4CY.js.map
|
package/dist/cjs/centrifuge.cjs
CHANGED
|
@@ -2669,7 +2669,7 @@ var SDK_VERSION = (
|
|
|
2669
2669
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2670
2670
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2671
2671
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2672
|
-
true ? "0.21.
|
|
2672
|
+
true ? "0.21.46" : ""
|
|
2673
2673
|
);
|
|
2674
2674
|
|
|
2675
2675
|
// src/core/data-bus.ts
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -2635,7 +2635,7 @@ var SDK_VERSION = (
|
|
|
2635
2635
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2636
2636
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2637
2637
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2638
|
-
true ? "0.21.
|
|
2638
|
+
true ? "0.21.46" : ""
|
|
2639
2639
|
);
|
|
2640
2640
|
|
|
2641
2641
|
// src/core/data-bus.ts
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,12 @@ Only `pnpm bench:browser` / `pnpm bench:compare` / `pnpm bench:trend` stay local
|
|
|
20
20
|
1. Update `package.json`, `CHANGELOG.md`, and both roadmap files.
|
|
21
21
|
2. Run `pnpm check`, `pnpm lint`, `pnpm test:coverage`, `pnpm bench`, `pnpm test:e2e`, `pnpm bench:browser`, `pnpm verify:pack`, `pnpm verify:compat`, `pnpm verify:types`, and `git diff --check`. (`verify:compat` asserts the package `exports` subpaths and type fields present in the `COMPAT_BASE_TAG` baseline still exist; `verify:pack` steam-imports the full root public surface plus every subpath in ESM and CJS from the packed tarball; `verify:types` freezes every public entry's declared export names against the same baseline and asserts no public signature names a type that no entry exports — a type-only export emits no runtime key, so neither the `Object.keys()` freeze in `tests/dual-format.test.ts` nor `verify:compat` can see one change. `verify:compat` and `verify:types` resolve their baseline from the most recent release tag, so a shallow or tag-less clone must first run `git fetch --tags` or they fail with "no version tag found".)
|
|
22
22
|
3. Gate dependency security: `pnpm audit --registry=https://registry.npmjs.org` (the configured mirror registry lacks the audit endpoint; CI runs it on the public registry in the verify job). Fail the release on any known-vulnerability advisory; `pnpm-workspace.yaml` overrides pin patched ranges.
|
|
23
|
-
4. Gate browser benchmark regressions: run `pnpm bench:browser` (twice at minimum; the gate compares the newest report against the median of the same metric in up to the five preceding archived reports, **and additionally requires the new value to exceed the highest of those samples**) and then `pnpm bench:compare --fail-above-pct 50`. The median baseline exists because the in-page hot-path metrics alternate between a fast and a slow mode on identical code — two consecutive runs of `dedup1000Ms` measured 12.7 ms then 25.6 ms, and a single noisy previous report could fail the documented ceiling with no code change at all. The second leg exists because a median over an alternating sample is only stable while the modes stay mixed: when several consecutive reports land in the same mode the median itself jumps, which is how one comment-only tree read +121% against one baseline and −56.7% against another minutes apart. A metric excused by that leg is printed under `within-baseline, not gated` rather than passing silently — measured on the current archive, `publish/dedicated/perMessageMs`, `publish/shared/perMessageMs` and `databus/publishBatch1000Ms` were all inside their own five-report range, so treat that list as the metric's real resolution, not as a clean bill. `publish/dedicated/perMessageMs` is noisy the same way: two consecutive runs of identical code measured 49.9 ms then 71.1 ms, failing the gate at +75.7% while `dedup1000Ms` and `traceAndPublish1000Ms` *improved* in the same report — that single-metric-up-everything-else-down shape is the noise signature, and the next run reported 49.4 ms and passed. Before accepting or acting on a failure, re-run and check the spread; if the change touches a hot path, size it directly (a micro-benchmark of the 0.20.95 bounded-map rewrites measured 80–86 ns/op across all three variants, i.e. inside run-to-run variance). A baseline shift (e.g. a metric becoming real instead of a no-op) is an expected one-time failure. Refresh the long-run trend doc with `pnpm bench:trend` and commit it when the tables change. **Re-running a failure can manufacture its own excuse, and that was measured.** `bench:compare`'s baseline — both the median and the maximum leg — is drawn from the most recent archived reports, and every `bench:browser` run appends to that same archive, so the samples you add while investigating a failure become the baseline that judges the next attempt. On 2026-09-24, at host load averages 7.7–15.1 with a zero non-comment `src/` diff in the tree, the first two runs measured `publish/dedicated 77.3 ms` and `publish/shared 67.7 ms` against a ~38/~34 ms fast mode and failed the 50% ceiling; five further re-runs reported **OK**, because the baseline median had slid to `70.6`. One clean run after taking those samples out of the archive showed every metric rising together (`dedicated +92.5%`, `shared +99.2%`, `dedup +43.4%`, `trace +21.0%`), which is the contention signature and the inverse of the single-metric-up shape above. So read the load average *before* re-running, and when a suspect failure occurs on a loaded host, record it as deferred with its numbers and move the off-mode samples aside instead of letting them into the baseline — and do not regenerate `docs/benchmarks.md` from them. **The standing decision, recorded at 0.21.17 (the fifth release in that state): when no quiet window exists across a cycle, defer the sample with its load numbers instead of taking it on a loaded host.** The deferral is what keeps `docs/benchmarks.md` stale, and staleness-by-recorded-opportunity is the intended outcome of this step, not a gate that passed quietly; what would not be acceptable is a loaded sample entering the archive, because that becomes the baseline the next release is judged against. **As of 0.21.45 that rule is enforced rather than remembered.** Every archived report now records the host it was taken on (`host.loadavg1m` with its core count)
|
|
23
|
+
4. Gate browser benchmark regressions: run `pnpm bench:browser` (twice at minimum; the gate compares the newest report against the median of the same metric in up to the five preceding archived reports, **and additionally requires the new value to exceed the highest of those samples**) and then `pnpm bench:compare --fail-above-pct 50`. The median baseline exists because the in-page hot-path metrics alternate between a fast and a slow mode on identical code — two consecutive runs of `dedup1000Ms` measured 12.7 ms then 25.6 ms, and a single noisy previous report could fail the documented ceiling with no code change at all. The second leg exists because a median over an alternating sample is only stable while the modes stay mixed: when several consecutive reports land in the same mode the median itself jumps, which is how one comment-only tree read +121% against one baseline and −56.7% against another minutes apart. A metric excused by that leg is printed under `within-baseline, not gated` rather than passing silently — measured on the current archive, `publish/dedicated/perMessageMs`, `publish/shared/perMessageMs` and `databus/publishBatch1000Ms` were all inside their own five-report range, so treat that list as the metric's real resolution, not as a clean bill. `publish/dedicated/perMessageMs` is noisy the same way: two consecutive runs of identical code measured 49.9 ms then 71.1 ms, failing the gate at +75.7% while `dedup1000Ms` and `traceAndPublish1000Ms` *improved* in the same report — that single-metric-up-everything-else-down shape is the noise signature, and the next run reported 49.4 ms and passed. Before accepting or acting on a failure, re-run and check the spread; if the change touches a hot path, size it directly (a micro-benchmark of the 0.20.95 bounded-map rewrites measured 80–86 ns/op across all three variants, i.e. inside run-to-run variance). A baseline shift (e.g. a metric becoming real instead of a no-op) is an expected one-time failure. Refresh the long-run trend doc with `pnpm bench:trend` and commit it when the tables change. **Re-running a failure can manufacture its own excuse, and that was measured.** `bench:compare`'s baseline — both the median and the maximum leg — is drawn from the most recent archived reports, and every `bench:browser` run appends to that same archive, so the samples you add while investigating a failure become the baseline that judges the next attempt. On 2026-09-24, at host load averages 7.7–15.1 with a zero non-comment `src/` diff in the tree, the first two runs measured `publish/dedicated 77.3 ms` and `publish/shared 67.7 ms` against a ~38/~34 ms fast mode and failed the 50% ceiling; five further re-runs reported **OK**, because the baseline median had slid to `70.6`. One clean run after taking those samples out of the archive showed every metric rising together (`dedicated +92.5%`, `shared +99.2%`, `dedup +43.4%`, `trace +21.0%`), which is the contention signature and the inverse of the single-metric-up shape above. So read the load average *before* re-running, and when a suspect failure occurs on a loaded host, record it as deferred with its numbers and move the off-mode samples aside instead of letting them into the baseline — and do not regenerate `docs/benchmarks.md` from them. **The standing decision, recorded at 0.21.17 (the fifth release in that state): when no quiet window exists across a cycle, defer the sample with its load numbers instead of taking it on a loaded host.** The deferral is what keeps `docs/benchmarks.md` stale, and staleness-by-recorded-opportunity is the intended outcome of this step, not a gate that passed quietly; what would not be acceptable is a loaded sample entering the archive, because that becomes the baseline the next release is judged against. **As of 0.21.45 that rule is enforced rather than remembered, and the refusal happens before the write rather than after it.** Every archived report now records the host it was taken on (`host.loadavg1m` with its core count). `bench:browser` judges the sample it has just taken and **exits non-zero without archiving it**, printing the reason; a sample that reached the disk would be the baseline the next five comparisons are judged against, so a verdict printed about a file that already exists decides nothing. Two thresholds, and which one applies depends on the archive:
|
|
24
|
+
|
|
25
|
+
- **With a recorded baseline**, a reading above four times the baseline's own median is refused. The threshold is a ratio rather than an absolute number because the archive spans machines and a load average means nothing without its core count.
|
|
26
|
+
- **With no recorded baseline** — which is the archive's entire current state, since every report predates the field — a reading above the host's own core count is refused, because more runnable work than cores is oversubscribed by definition and needs no history to establish. Without this leg the guard is weakest exactly where the archive is weakest, and the first sample taken on a busy host is the one that would end a twenty-five-tag deferral streak.
|
|
27
|
+
|
|
28
|
+
Both boundaries are inclusive and both are a refusal to *trust* rather than a claim the sample is invalid, so a false positive costs one deferred run rather than a release. A failed *write* is deliberately not treated as a refusal: the numbers were produced, a full disk is not a judgment about the host, and conflating the two would fail a release over a sample that was correctly taken. A platform that publishes neither a finite reading nor a core count admits the sample, since there is nothing to judge it on.
|
|
24
29
|
5. Confirm the package contains only intended files with `npm pack --dry-run --json`.
|
|
25
30
|
6. Commit on a feature branch, push that branch, and merge its green PR using squash or fast-forward (no merge commit). Fetch the merged commit, tag that exact commit, and push only the specific version tag (with the local tag present, run the gate set once more before that push — see *Tagged-release workflow* below for why that moment is the one no earlier CI sees); never push `main`/`master` directly or force-push. The workflow runs `node scripts/verify-release-version.mjs` to require `RELEASE_TAG` to equal `v` + the package version and to require exactly one non-empty CHANGELOG section. The same step compares every version heading in `CHANGELOG.md` against the registry's own `time` map and refuses a release record that names a version npm never had — but only from `0.20.85` upward, because below that floor 40 sections name versions the registry never recorded, 27 of them tagged (both re-derived 2026-09-25; the command to re-derive them intersects three sets — `grep -oE '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md`, `npm view cross-tab-worker-databus time --json --registry https://registry.npmjs.org`, and `git tag -l` — because the registry read alone answers neither count, let alone the tagged half), and the gate exists to stop a new hole rather than to litigate history. The version being tagged is exempt, since it legitimately cannot be on npm yet, and a registry read that fails prints a skip warning and exits 0: refusing a good release over one transient HTTP call is the failure mode the published-consumer ceiling was sized to avoid, so only a *confirmed* absence is fatal. Note the `--registry` in that re-derive command, and that the gate passes it too: a configured **mirror** can answer successfully while 3,418 s behind. Measured while preparing `0.21.35` on a machine whose `npm` points at `registry.npmmirror.com` — its packument still reported `modified: 00:32:51Z` (the `0.21.33` publish) when the gate ran at `03:41:18Z` — 3,207 s (53 min) after npmjs had recorded `0.21.34` at `02:47:50Z`, and the mirror's own `modified` did not move until `03:44:48.778Z`, 3,418 s after the record — so the gate declared a public version "never recorded", from a read that *succeeded*. A mirror's sync lag is not HTTP caching, so `cache-control: no-cache` returns the same stale document; naming the registry is the only thing that fixes it. `verify-published-consumer.mjs` had already pinned it for both of its reads; this was the last npm-based registry read in the repository that did not.
|
|
26
31
|
|
package/docs/roadmap.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
0.21.
|
|
3
|
+
0.21.45 was released on September 30, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
|
|
4
4
|
|
|
5
5
|
## 0.21.44 delivered scope
|
|
6
6
|
|
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
2. 运行 `pnpm check`、`pnpm lint`、`pnpm test:coverage`、`pnpm bench`、`pnpm test:e2e`、`pnpm bench:browser`、`pnpm verify:pack`、`pnpm verify:compat`、`pnpm verify:types` 以及 `git diff --check`(`verify:compat` 断言 `COMPAT_BASE_TAG` 基线中的 package `exports` 子路径与类型字段仍然存在;`verify:pack` 从打包产物冒烟导入完整根公共面与全部子路径的 ESM/CJS;`verify:types` 用同一基线冻结每个公共入口声明层导出的名字,并断言没有任何公共签名引用了「任何入口都未导出」的类型——纯类型导出不会在运行时产生任何键,因此 `tests/dual-format.test.ts` 的 `Object.keys()` 冻结和 `verify:compat` 都看不到它的变化。`verify:compat` 与 `verify:types` 从最近的发布 tag 解析基线,因此浅克隆或缺少 tag 的克隆需先执行 `git fetch --tags`,否则会以 "no version tag found" 失败)。
|
|
22
22
|
3. 依赖安全门禁:`pnpm audit --registry=https://registry.npmjs.org`(配置的镜像 registry 缺少 audit 端点;CI 在 verify job 中于公共 registry 运行)。任一已知漏洞公告即视为发布失败;`pnpm-workspace.yaml` overrides 钉住补丁版本。
|
|
23
23
|
4. 浏览器基准回归门禁:运行 `pnpm bench:browser`(至少两次;门禁将最新报告与之前最多五份归档报告中同一指标的中位数比较,**并且额外要求新值超过这些样本中的最大值**),随后执行 `pnpm bench:compare --fail-above-pct 50`。采用中位数基线是因为页内热路径指标在同一份代码上会在快/慢两档之间来回跳——`dedup1000Ms` 相邻两次实测分别为 12.7 ms 与 25.6 ms,只看"上一份报告"时,一次偶发噪声就能在无代码改动的情况下击穿既定上限。第二个条件则是因为:在两档交替的样本上,中位数只有当两档保持混合时才稳定——一旦连续几份报告落在同一档,中位数本身就会跳变,这正是同一份纯注释改动在一版基线前读出 +121%、几分钟后在另一版基线前读出 −56.7% 的原因。被该条件放过的指标会打印在 `within-baseline, not gated` 下,而不是静默通过——在当前归档上实测,`publish/dedicated/perMessageMs`、`publish/shared/perMessageMs` 与 `databus/publishBatch1000Ms` 三项都落在各自五份报告的区间内,因此应把这份清单读作该指标的真实分辨率,而不是一张合格证。`publish/dedicated/perMessageMs` 同样如此:同一份代码相邻两次实测为 49.9 ms 与 71.1 ms,令门禁以 +75.7% 失败,而同一份报告里 `dedup1000Ms` 与 `traceAndPublish1000Ms` 反而*变快*——"单指标上涨、其余下降"正是噪声的特征,第三次实测为 49.4 ms 并通过。因此在采信或处理失败前先复跑并观察离散度;若改动涉及热路径,就直接量化其开销(0.20.95 有界映射改写的微基准显示三种写法均为 80–86 ns/op,落在运行间波动之内)。基线迁移(例如某指标从空操作变为真实路径)属预期内的一次性失败。用 `pnpm bench:trend` 刷新长期趋势文档,表格变化时一并提交。**复跑失败可能亲手制造出让它通过的基线,这一点已被实测。** `bench:compare` 的基线(中位数与最大值两个条件)取自最近若干份归档报告,而每一次 `pnpm bench:browser` 都会往同一个归档里追加——于是排查失败时新增的样本,就变成了裁决下一次尝试的基线。2026-09-24:主机负载均值 7.7–15.1、待发布树的非注释 `src/` 差异为 0 行,前两次实测 `publish/dedicated 77.3 ms`、`publish/shared 67.7 ms`,相对约 38/34 ms 的快档在 50% 上限上失败;随后五次复跑报告 **OK**,因为基线中位数已经滑到 `70.6`。把这些样本移出归档后的一次干净实测显示所有指标同时上涨(`dedicated +92.5%`、`shared +99.2%`、`dedup +43.4%`、`trace +21.0%`),那是资源竞争的形态,与上文"单指标上涨、其余下降"的噪声形态相反。因此:复跑之前先看负载(`uptime`);在满载主机上遇到可疑失败,就把数字连同负载一起记为"本轮推迟",并将偏离常态的样本移出基线——也不要据它们重新生成 `docs/benchmarks.md`。
|
|
24
|
-
**这条既定决策在 0.21.17 被写下,那是处于该状态下的第五个发布周期:一个发布周期里始终没有安静的窗口时,就把这份采样连同当时的负载数字一起记为"本轮推迟",而不是在满载主机上取它。** 推迟正是 `docs/benchmarks.md` 保持陈旧的原因,而"按机会更新"的这份陈旧是本步骤期望的结果,不是一道悄悄通过的门禁;真正不可接受的,是把一个受资源竞争的样本放进归档——那会变成下一次发布用来判定回归的基线。 **自 0.21.45
|
|
24
|
+
**这条既定决策在 0.21.17 被写下,那是处于该状态下的第五个发布周期:一个发布周期里始终没有安静的窗口时,就把这份采样连同当时的负载数字一起记为"本轮推迟",而不是在满载主机上取它。** 推迟正是 `docs/benchmarks.md` 保持陈旧的原因,而"按机会更新"的这份陈旧是本步骤期望的结果,不是一道悄悄通过的门禁;真正不可接受的,是把一个受资源竞争的样本放进归档——那会变成下一次发布用来判定回归的基线。 **自 0.21.45 起,这条规则由工具执行,而不再依赖记忆,并且拒绝发生在写入之前,而不是之后。** 每份归档报告现在都会记录采样时的主机状态(`host.loadavg1m` 及其核心数)。`bench:browser` 会判定刚采到的样本并**非零退出且不归档**,同时打印原因;已经落到磁盘上的样本,会成为此后五次比较的基线,所以针对一个已存在文件打印出的判定等于没有判定。阈值有两条,适用哪一条取决于归档本身:
|
|
25
|
+
|
|
26
|
+
- **归档中已有可比较的读数时**,超过基线中位数四倍的读数被拒绝。取比值而非绝对值,是因为归档跨机器,而负载均值脱离核心数没有意义。
|
|
27
|
+
- **归档中没有任何读数时**——这正是当前归档的全部状态,因为每份报告都早于该字段——超过主机自身核心数的读数被拒绝,因为可运行负载超过核心数按定义就是超订,且这不需要任何历史即可成立。没有这一条,守卫恰好在归档最弱的地方也最弱,而第一个在繁忙主机上采到的样本,正是那个会让二十五次发布推迟 streak 结束的样本。
|
|
28
|
+
|
|
29
|
+
两条边界都取闭区间,且都是"不予*采信*"而不是"样本无效"的判定,因此误报只代价一次推迟,而不是一次发布。写入*失败*被刻意区别对待、不算拒绝:数字已经产出,磁盘写满不是对主机的判断,把两者混为一谈会让一个采样正确、只是存不下来的样本卡住一次发布。既不发布有限读数也不发布核心数的平台会放行该样本,因为没有任何东西可以据以判定它。
|
|
25
30
|
5. 用 `npm pack --dry-run --json` 确认发布包只包含预期文件。
|
|
26
31
|
6. 在功能分支提交并推送该分支,PR 验证通过后使用 squash 或 fast-forward 合入(不创建 merge commit)。获取合入后的精确提交并打 tag,只推送该版本 tag(推送之前,带着本地已存在的 tag 再把门禁跑一遍——原因见下文“打 tag 的发布工作流”,那是此前的 CI 覆盖不到的构建状态);禁止直接推送 `main`/`master` 或 force-push。工作流运行 `node scripts/verify-release-version.mjs`,要求 `RELEASE_TAG` 等于 `v` 加 package 版本,且 CHANGELOG 中恰好有一个非空的对应版本章节。同一步骤还会把 `CHANGELOG.md` 里每个版本标题与 registry 自己的 `time` 映射相对照,拒绝一份"点名了 npm 从未有过的版本"的发布记录——但只从 `0.20.85` 往上要求:在那之下有 40 个章节点名了 registry 从未记录过的版本,其中 27 个还带着 git tag(两个数都在 2026-09-25 重新核过;重算的命令要把三个集合求交——`grep -oE '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md`、`npm view cross-tab-worker-databus time --json --registry https://registry.npmjs.org`、`git tag -l`——因为只读 registry 既得不出 40,更得不出其中带 tag 的 27),这道门禁要拦的是新的漏洞,不是去翻历史旧账。而 registry 读取失败时,这一半会被跳过并打印一行警告、退出 0,而不是把一次好发布判红:为一个瞬时的 HTTP 失败拒绝一次成功发布,正是已发布消费者上限当初要避免的那类失败,所以只有*确认存在*的缺失才会中止发布。请注意上面那条重算命令里的 `--registry`,也请注意门禁自己也会带上它:一台配置了**镜像**的机器可以成功应答,却落后 3,418 秒。这是准备 `0.21.35` 时在本机量到的——这台机器的 `npm` 指向 `registry.npmmirror.com`,它的 packument 仍报 `modified: 00:32:51Z`(也就是 `0.21.33` 那次发布),而门禁运行在 `03:41:18Z`——比 npmjs 在 `02:47:50Z` 记下 `0.21.34` 晚 3,207 秒(53 分钟)——镜像自己的 `modified` 直到 `03:44:48.778Z` 才动,比那条记录晚 3,418 秒,于是门禁把一个已经公开的版本判成"registry 从未记录",而这一次读取是**成功**的。镜像的同步滞后不是 HTTP 缓存,所以 `cache-control: no-cache` 拿回来的还是同一份旧文档;只有显式点名 registry 才能纠正。`verify-published-consumer.mjs` 早已为它的两次读取钉住了 registry,这里是仓库里最后一处没有钉的 npm 读取。正在打 tag 的那个版本本身被豁免,因为它此刻本来就不该在 npm 上。
|
|
27
32
|
|
package/docs/zh/roadmap.md
CHANGED
package/package.json
CHANGED
|
File without changes
|