cross-tab-worker-databus 0.21.39 → 0.21.41

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
@@ -1,3 +1,28 @@
1
+ ## [0.21.41] - 2026-09-25
2
+
3
+ The release that `0.21.39` was — a run that dies inside its own tag build, having published nothing — has no branch in the shipped release checklist, so the procedure that recovered it lived only in `AGENTS.md` and `docs/progress.md`, neither of which is in the tarball. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.40`'s.
4
+
5
+ ### Added
6
+
7
+ - **Both `docs/release-checklist.md` languages now carry the dead-tag recovery procedure.** The section that handles a failed release run said, in three clauses, "rerun against the unchanged tag" for propagation failures, "ship a new patch version" for artifact defects, and "never move or reuse a published tag". All three are true and none applies when the run published nothing: the second leaves an unpublished version standing in `CHANGELOG.md`, which `scripts/verify-release-version.mjs` then refuses on every later tag — so one abandoned publication would block the whole series. The procedure is now written out where a maintainer will read it: the two reads that establish "nothing was published" and the way each can lie (a configured mirror answers *"not there"* about a version it has simply not learned, and a wrong repository slug answers `release not found`, which is the expected shape of the answer and therefore gets accepted); the delete-and-recreate, stated as the two ordinary pushes it is, with the immutability rule left intact because it constrains a *published* tag; the `creatordate` consequence that makes re-cutting on the same calendar day keep the release-date gate green; and the step that prevents the case altogether — create the annotated tag, re-run `pnpm check`, `pnpm lint` and the `verify:*` trio with the tag present, and only then push the ref. That last half is why `0.21.39` died where it did: the tag build is the one moment in the cycle when `package.json`'s version already carries its own tag, so a check keyed on "the newest tag" is invisible to PR CI *and* to `main`-push CI, and three green runs said nothing. Step 6 of "Before tagging" now points at that paragraph, because the reader who needs it is the one holding the tag.
8
+
9
+ ### Fixed
10
+
11
+ - **`scripts/verify-release-version.mjs` offered a remedy in a form that does nothing.** Its refusal read "Each needs either its artifact published or its section marked as a version that was never released", and the second half is a prose edit — so which prose edit is the contract, and it was unstated. Measured against the exported `findUnpublishedReleases` with a four-shape fixture and a standing-section control: `## [0.20.91] - never released` is reported exactly like the plain section it is meant to excuse, while `## 0.20.91 — never released` and `## ~~[0.20.91]~~ …` come back clean. The exemption belongs to the `## [x.y.z]` heading *shape* the scan matches, never to a keyword — which is the property worth keeping (no phrase a typo can trip), and the reason the message now names the shape instead of the word. Pinned by three legs that each die to their own mutant: a keyword exemption added to the predicate fails the still-reported leg, a regex widened to accept bare headings fails the exempted leg, and reverting the message fails the leg that reads the message. The first two legs pass under a keyword exemption whether or not the third is read, which is why the wording is asserted rather than trusted to be legible. `scripts/` and `tests/` are not in `package.json` `files`, so this half reaches a maintainer in a clone and not a consumer running `npm install` — the checklist half is the shipped part.
12
+
13
+ ## [0.21.40] - 2026-09-25
14
+
15
+ `0.21.39` added a gate, then replaced that gate's rule in the test -- and the prose describing the gate, written a day earlier by the release-prep PR, was in a file the fix never opened. So the notes that shipped with `0.21.39` state the requirement its own fix deleted. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.39`'s.
16
+
17
+ ### Fixed
18
+
19
+ - **Both roadmap languages still required the preamble to name the newest \*tagged\* release.** That is the rule which killed Release run 159, and PR #328 replaced it in `tests/documentation.test.ts` with the set that holds at every buildable moment: the greatest tag strictly below `package.json`'s version, plus that version once its own tag exists. The sentence fails in exactly the state the fix was for -- a release's own tag build, where `package.json`'s tag *is* the newest tag while the preamble legitimately still names the previous release, because the record commit that advances it lands after the tag. Corrected here in `docs/roadmap.md` and `docs/zh/roadmap.md`, inside the `0.21.39` block where the claim sits. The third copy -- this file's own `0.21.39` `### Added` bullet -- stands, and the line to state is why that does not contradict `0.20.10`'s heading being corrected in the section below: that heading was never true of anything -- no event of that release happened on the day it names -- whereas this sentence was true of the release that shipped it and was superseded inside its own version's lifecycle. A wrong record is corrected; a superseded one is left standing, and the correction is filed against the copies that still instruct a reader. `0.21.35`'s three-sample sentence sits on the second footing, and note how thinly that precedent is actually honoured here: nothing in this file retracts it. The only later text that contradicts it is `0.21.37`'s series bullet, which prints `127.3` and `126.4` beside the three without naming the claim those two falsify -- the retraction itself went into `AGENTS.md` and `docs/progress.md`, neither of which is in the tarball.
20
+
21
+ ### Added
22
+
23
+ - **The preamble rule's release-cycle moments are tabulated, because the live case can only ever see the one the repository happens to be in.** `allowedPreambleVersions(packageVersion, tagRefs)` is extracted from that case and exercised over ten synthetic rows: prep, tag build, record commit, two-releases-behind in each of the first two, a preamble naming an untagged version, a clone carrying a *newer* tag, and double-digit minors where a lexical sort would pick the wrong winner. That the table was needed is measured, not asserted: against the live case alone (probe and mutation record in `docs/progress.md`), deleting the "own tag counts" member reddens it, while deleting the filter that keeps tags at-or-above `packageVersion` out of the previous tag does **not**, and neither does turning `>=` into `>` -- two of the rule's three legs had no test that could see them, and one of those two *is* the run-159 bug. After the extraction all four mutants die, and the probe records where each one lands: M2 and M3 both fail the tag-build row (`>=` widened to `>`, and the filter dropped, produce the same wrong `previousTag` there), M4 -- numeric sort made lexical -- fails the double-digit-minor row with `allowed [0.9.0]`, and M1 fails only the table's record-commit row, `allowed [0.21.38]`. That last asymmetry is the finding rather than a detail: on a *prep* tree, where `package.json`'s version has no tag yet, the own-tag member contributes nothing to the live case at all, so the leg that decides whether a released version may name itself is visible only in the synthetic moment. Which is the argument for the table.
24
+ - **The lag sweep's `p50` is named and pinned.** `scripts/publication-lag.mjs` reports the upper-middle element (`sorted[floor(n/2)]`), not the mean of the two middle values; the two coincide at odd `n` and not at even `n`, so one added sample moves the printed figure with nothing changed in what is measured -- the modern gap window read `p50=97.3` at `n=45` and `p50=126.4` at `n=46`, where the interpolated median of those same values is `111.85`. `tests/publication-lag.test.ts` now pins which convention the line uses, and the header's `EXCLUDED-NON-SUCCESS` note is corrected to say it names a run the sweep passed over rather than a verdict on the tag: `v0.21.39` prints that line *and* its own reading, having shipped from a re-pointed ref.
25
+
1
26
  ## [0.21.39] - 2026-09-25
2
27
 
3
28
  One section of this file carried a date on which no release event happened — not a figure that aged, but a line written a day behind the one above it. The check that now reads every section date does so against the tag rather than against npm, and the two rows that forced that choice are recorded below. No library behavior, export, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.38`'s.
@@ -7,7 +7,7 @@ import {
7
7
  publicationMetadata,
8
8
  selectWorkerBackend,
9
9
  serializeError
10
- } from "./chunk-6DLLPWNT.js";
10
+ } from "./chunk-37U5AL4M.js";
11
11
  import {
12
12
  CENTRIFUGE_INPUT_TYPE,
13
13
  CENTRIFUGE_OUTPUT_TYPE,
@@ -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.39" : ""
2478
+ true ? "0.21.41" : ""
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-6DLLPWNT.js.map
3718
+ //# sourceMappingURL=chunk-37U5AL4M.js.map
@@ -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.39" : ""
2607
+ true ? "0.21.41" : ""
2608
2608
  );
2609
2609
 
2610
2610
  // src/core/data-bus.ts
@@ -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.39" : ""
2573
+ true ? "0.21.41" : ""
2574
2574
  );
2575
2575
 
2576
2576
  // src/core/data-bus.ts
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  selectRebalanceTarget,
21
21
  selectWorkerBackend,
22
22
  topicMatchesPattern
23
- } from "./chunk-6DLLPWNT.js";
23
+ } from "./chunk-37U5AL4M.js";
24
24
  import {
25
25
  DEFAULT_STORAGE_PREFIX,
26
26
  PRUNE_STRATEGY,
@@ -22,7 +22,7 @@ Only `pnpm bench:browser` / `pnpm bench:compare` / `pnpm bench:trend` stay local
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
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.
24
24
  5. Confirm the package contains only intended files with `npm pack --dry-run --json`.
25
- 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; 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.
25
+ 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
26
 
27
27
  ## Security and dependency scanning
28
28
 
@@ -30,7 +30,13 @@ The repository runs CodeQL (`javascript-typescript`; on push, on pull request, a
30
30
 
31
31
  ## Tagged-release workflow
32
32
 
33
- Pushing a version tag triggers the `Release` GitHub Action: it runs `pnpm check` and `pnpm lint` (a tag can point at a commit that never passed CI's lint step), runs `verify:compat`, `verify:types` and `verify:pack`, opens the GitHub release from the `CHANGELOG` section, publishes to npm when the `NPM_TOKEN` secret is set, and then runs the **blocking** published-consumer verification at a budget twenty-four times the manual default — the workflow sets `PUBLISHED_VERIFY_ATTEMPTS=96` and `PUBLISHED_VERIFY_DELAY_MS=7500` (a 12-minute ceiling) in its own env, while `scripts/verify-published-consumer.mjs` defaults to 6 × 5000 ms = 30 s when run by hand. A release whose published package cannot be imported by a clean consumer fails the workflow — treat every `verify:published` failure as a failed release. For registry propagation or infrastructure failures, rerun the workflow against the unchanged tag; for artifact defects, ship a new patch version. Never move or reuse a published tag. When no token is configured the publish step is skipped, but verification still passes against whatever version is already on npm (e.g. one published manually).
33
+ Pushing a version tag triggers the `Release` GitHub Action: it runs `pnpm check` and `pnpm lint` (a tag can point at a commit that never passed CI's lint step), runs `verify:compat`, `verify:types` and `verify:pack`, opens the GitHub release from the `CHANGELOG` section, publishes to npm when the `NPM_TOKEN` secret is set, and then runs the **blocking** published-consumer verification at a budget twenty-four times the manual default — the workflow sets `PUBLISHED_VERIFY_ATTEMPTS=96` and `PUBLISHED_VERIFY_DELAY_MS=7500` (a 12-minute ceiling) in its own env, while `scripts/verify-published-consumer.mjs` defaults to 6 × 5000 ms = 30 s when run by hand. A release whose published package cannot be imported by a clean consumer fails the workflow — treat every `verify:published` failure as a failed release. For registry propagation or infrastructure failures, rerun the workflow against the unchanged tag; for artifact defects, ship a new patch version — unless the failed run published nothing, which is its own case and is handled below. Never move or reuse a published tag. When no token is configured the publish step is skipped, but verification still passes against whatever version is already on npm (e.g. one published manually).
34
+
35
+ **A run that dies on its own tag build, having published nothing, is recovered by re-pointing the ref rather than by skipping the number.** Two reads settle that state before anything is touched, and both have a way to lie. First the packument: `npm view cross-tab-worker-databus versions time --json --registry https://registry.npmjs.org`, naming the registry, because the *configured* mirror can be an hour behind and will answer "not there" about a version that exists. Second `gh release view v<X>` — with the slug read from `git remote -v`, since a wrong slug answers `release not found`, which is exactly the expected answer and therefore gets accepted. If both come back empty, the tag produced no artifact and the recovery is: delete the ref locally and on the remote (`git push origin :refs/tags/v<X>`), create the annotated tag again at the fixed commit, and push it again. Those are two ordinary pushes, so nothing is force-pushed and the immutability rule above is untouched — it is about a *published* tag, and this one was never published.
36
+
37
+ Skipping the version instead is not available here, and the repository's own gate is why: `scripts/verify-release-version.mjs` refuses any later tag while `CHANGELOG.md` carries a `## [x.y.z]` section for a version the registry never recorded, so one abandoned publication blocks the whole series. That script's message offers "mark its section as a version that was never released" as the alternative, and the form matters: measured against the exported predicate, writing the words is not enough (`## [0.20.91] - never released` is reported exactly like a standing section) — only a heading that leaves the bracketed shape is exempt. Re-cutting on the same calendar day also keeps the release-date gate green, because it reads the tag object's `creatordate`, which a fresh annotated tag resets.
38
+
39
+ **The step that makes this case rare is a local one: create the annotated tag, run the gates with the tag present, and push the ref only after they pass.** The tag build is the only moment in the cycle when `package.json`'s version already carries its own tag, so any check keyed on "the newest tag" is invisible to PR CI *and* to `main`-push CI — which is how the `0.21.39` release died inside its own `pnpm check` on a gate that same release had just added. Run `pnpm check`, `pnpm lint`, `pnpm verify:compat`, `pnpm verify:types` and `pnpm verify:pack` against the local tag before `git push origin v<X>`; costs minutes, and the alternative costs a dead tag plus the re-point above.
34
40
 
35
41
  ## Publishing
36
42
 
package/docs/roadmap.md CHANGED
@@ -1,12 +1,23 @@
1
1
  # Roadmap
2
2
 
3
- 0.21.38 was released on September 25, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
3
+ 0.21.40 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.41 delivered scope
6
+
7
+ - **The shipped release checklist now says what to do when a release run dies on its own tag.** `0.21.39` reached exactly that state — the run failed inside its own `pnpm check`, so the tag existed and no artifact did — and the checklist's three branches ("rerun against the unchanged tag", "ship a new patch version", "never move a published tag") do not include it, because the version that follows the second branch leaves an unpublished section behind and the completeness gate then refuses every later tag. Written out in both languages: the two confirming reads and how each can lie, the delete-and-recreate as the two ordinary pushes it is, and the preventive step — build the tag locally, run the gates *with the tag present*, push only after they pass, since that build state is the one neither PR CI nor `main`-push CI ever sees.
8
+ - **A remedy the release gate prints was rewritten into the form that actually applies it.** `scripts/verify-release-version.mjs` refuses a tag while `CHANGELOG.md` names an unpublished version and tells the reader to mark the section as never released; measured against the exported predicate with a standing-section control, `## [0.20.91] - never released` is refused exactly like the section it was meant to excuse, and only a heading that leaves the bracketed shape is exempt. The message now names the shape, and three test legs pin it, one per mutant: a keyword exemption, a bare-heading match, and the reverted wording each fail a different assertion — the first two of which the behavior legs alone would not distinguish.
9
+
10
+ ## 0.21.40 delivered scope
11
+
12
+ - **The release notes described the rule their own fix had deleted.** `0.21.39`'s third bullet required the roadmap preamble to name the newest *tagged* release; PR #328 — the reason `0.21.39` exists at all — replaced that with "the greatest tag below `package.json`'s version, or that version once it is tagged", and changed only the test and `AGENTS.md`. Both roadmap copies are corrected where they stand; the `CHANGELOG.md` copy is left as that release published it, on the same footing as `0.21.35`'s three-sample sentence.
13
+ - **The rule is now exercised across the release cycle, not at whatever moment the clone happens to sit in.** `allowedPreambleVersions()` is extracted from the live-file case and tabled over ten rows: prep, the tag build, the record commit, two releases behind, a preamble naming a version that was never tagged, a clone carrying a newer tag, and double-digit minors where a lexical sort picks the wrong winner. Before and after, measured: with only the live case, two of the rule's three legs — including the one that broke release run 159 — survived every mutant tried against them. After the table all four die, and where they die is itself the point: two land on the tag-build row, one on the double-digit-minor row, and the member that admits a released version's own tag fails **nowhere in the live files** — on a prep tree, where `package.json`'s version is not tagged yet, that leg contributes nothing an end-state read can see, so it is decidable only in a synthesized moment.
14
+ - **The lag sweep's printed median is named for what it is.** Its `p50` is the upper-middle element rather than the mean of the middle two, so adding one sample to an odd-length series moves the figure on its own: the modern gap window went `97.3 → 126.4`, where the interpolated median of those same values is `111.85`. Pinned in `tests/publication-lag.test.ts`, whose `n=2` fixture is the only shape that exposes the whole sorted list.
4
15
 
5
16
  ## 0.21.39 delivered scope
6
17
 
7
18
  - **One release in this changelog was dated on a day nothing happened.** `## [0.20.10]` said 2026-09-02 while its tag (`2026-09-03T12:27:29+08:00`) and its registry record (`2026-09-03T04:28:11Z`) are the same instant on the same day under either reading; the release before it was tagged at 21:33 on 09-02, so the heading simply never advanced. It is corrected, and of the 151 dated sections that carry a tag it was the only one outside both readings of its own tag.
8
19
  - **The new check keys on the tag, not on npm, and that choice is measured.** `tests/documentation.test.ts` now requires each section date to be the calendar day of its tag instant in either the UTC or the `+08:00` frame. Keying the same rule on `time[<version>]` would report three bad sections instead of one, because publishing finishes after midnight UTC far often enough to matter: `0.4.0` is tagged 2026-08-30T21:28Z and recorded 2026-08-31T00:03Z, and `0.20.85` has the same shape. Two of those three "findings" would have been the gate being wrong.
9
- - **Why both frames, and what stops an empty read from passing.** Accepting only one frame fails dozens of sections that each describe a real event (measured: 84 agree in either frame, 46 only in `+08:00`, 20 only in UTC), so the rule is "some reading of the tag", not "the local day". A floor on the number of sections checked turns a clone with no tags into a failure instead of a clean pass, and an assertion that the checked rows span both tag object types catches the silent version of that — this repository tags releases both ways, and only `%(creatordate)` answers for the two. A sibling case reads the same claim from both roadmap languages, where the date is written out (`September 25, 2026` / `2026 年 9 月 25 日`) rather than as an ISO heading, and requires the named version to be the newest *tagged* one — not `package.json`'s, which is ahead of the tag for the whole duration of a release prep.
20
+ - **Why both frames, and what stops an empty read from passing.** Accepting only one frame fails dozens of sections that each describe a real event (measured: 84 agree in either frame, 46 only in `+08:00`, 20 only in UTC), so the rule is "some reading of the tag", not "the local day". A floor on the number of sections checked turns a clone with no tags into a failure instead of a clean pass, and an assertion that the checked rows span both tag object types catches the silent version of that — this repository tags releases both ways, and only `%(creatordate)` answers for the two. A sibling case reads the same claim from both roadmap languages, where the date is written out (`September 25, 2026` / `2026 年 9 月 25 日`) rather than as an ISO heading. The version it names must be either the release before `package.json`'s, or `package.json`'s own once that tag exists — because the preamble is advanced by the record commit, which lands *after* the tag, so on the build of a release's own tag the newest tag is exactly the version the preamble has not reached. Corrected in `0.21.40`: this sentence first read "the newest *tagged* release, not `package.json`'s", which is the rule that killed release run 159 and that the same release's fix replaced, in the test only.
10
21
 
11
22
  ## 0.21.38 delivered scope
12
23
 
@@ -23,7 +23,7 @@
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
24
  **这条既定决策在 0.21.17 被写下,那是处于该状态下的第五个发布周期:一个发布周期里始终没有安静的窗口时,就把这份采样连同当时的负载数字一起记为"本轮推迟",而不是在满载主机上取它。** 推迟正是 `docs/benchmarks.md` 保持陈旧的原因,而"按机会更新"的这份陈旧是本步骤期望的结果,不是一道悄悄通过的门禁;真正不可接受的,是把一个受资源竞争的样本放进归档——那会变成下一次发布用来判定回归的基线。
25
25
  5. 用 `npm pack --dry-run --json` 确认发布包只包含预期文件。
26
- 6. 在功能分支提交并推送该分支,PR 验证通过后使用 squash 或 fast-forward 合入(不创建 merge commit)。获取合入后的精确提交并打 tag,只推送该版本 tag;禁止直接推送 `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 上。
26
+ 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
27
 
28
28
  ## 安全与依赖扫描
29
29
 
@@ -31,7 +31,13 @@
31
31
 
32
32
  ## 打 tag 的发布工作流
33
33
 
34
- 推送版本 tag 会触发 `Release` GitHub Action:先跑 `pnpm check` 与 `pnpm lint`(tag 可能指向从未通过 CI lint 步骤的提交),再跑 `verify:compat`、`verify:types` 与 `verify:pack`,从 `CHANGELOG` 对应章节生成 GitHub release,配置了 `NPM_TOKEN` 时自动发布到 npm,然后运行**阻塞式**消费者验证,其预算是手动默认值的二十四倍——工作流在自己的 env 里设置 `PUBLISHED_VERIFY_ATTEMPTS=96`、`PUBLISHED_VERIFY_DELAY_MS=7500`(12 分钟上限),而手动运行 `scripts/verify-published-consumer.mjs` 时的默认值是 6 × 5000 ms = 30 秒。已发布包若无法被干净消费者导入,工作流即失败——任何 `verify:published` 失败都应视为发布失败。若为 registry 传播延迟或基础设施故障,针对不变的 tag 重跑工作流;若为产物缺陷,发布新的 patch 版本。禁止移动或重用已发布 tag。未配置 token 时跳过发布步骤,但验证仍会针对 npm 上已有的版本(例如手动发布的)通过。
34
+ 推送版本 tag 会触发 `Release` GitHub Action:先跑 `pnpm check` 与 `pnpm lint`(tag 可能指向从未通过 CI lint 步骤的提交),再跑 `verify:compat`、`verify:types` 与 `verify:pack`,从 `CHANGELOG` 对应章节生成 GitHub release,配置了 `NPM_TOKEN` 时自动发布到 npm,然后运行**阻塞式**消费者验证,其预算是手动默认值的二十四倍——工作流在自己的 env 里设置 `PUBLISHED_VERIFY_ATTEMPTS=96`、`PUBLISHED_VERIFY_DELAY_MS=7500`(12 分钟上限),而手动运行 `scripts/verify-published-consumer.mjs` 时的默认值是 6 × 5000 ms = 30 秒。已发布包若无法被干净消费者导入,工作流即失败——任何 `verify:published` 失败都应视为发布失败。若为 registry 传播延迟或基础设施故障,针对不变的 tag 重跑工作流;若为产物缺陷,发布新的 patch 版本——但如果失败的那次运行什么都没有发布,那是另一种情形,见下文。禁止移动或重用**已发布**的 tag。未配置 token 时跳过发布步骤,但验证仍会针对 npm 上已有的版本(例如手动发布的)通过。
35
+
36
+ **一次运行死在自己的 tag 构建上、且什么都没有发布时,正确的恢复是把 ref 重新指到修好的提交,而不是跳过这个版本号。** 动手之前有两次读取要确认,而它们各有一种说谎的方式。第一次读 packument:`npm view cross-tab-worker-databus versions time --json --registry https://registry.npmjs.org`,必须显式点名 registry,因为*配置里*的镜像可能落后一小时,会对一个确实存在的版本回答“没有”。第二次是 `gh release view v<X>`——slug 要取自 `git remote -v`,因为一个写错的 slug 同样回答 `release not found`,而那正是你预期答案的形状,所以它会被照单收下。两次都是空,就说明这个 tag 没有产出任何产物,恢复步骤是:在本地和远端删掉这个 ref(`git push origin :refs/tags/v<X>`),在修好的提交上重新创建带注释的 tag,然后再推送一次。这是两次普通推送,没有任何强推,上面那条不可变规则也没有被碰到——它约束的是**已发布**的 tag,而这个 tag 从未发布。
37
+
38
+ 跳过版本号在这里是走不通的,原因正是仓库自己的门禁:只要 `CHANGELOG.md` 里还留着某个 registry 从未记录过的版本的 `## [x.y.z]` 章节,`scripts/verify-release-version.mjs` 就会拒绝之后每一个 tag,于是一次被放弃的发布会堵住整个序列。那个脚本报错时给的替代方案是“把这一节标成从未发布过的版本”,而写法有讲究:拿导出的谓词实测过,光写字不够(`## [0.20.91] - never released` 和原样保留的章节被同样上报),只有让标题不再长成方括号那种形状才会被豁免。在同一天里重新打 tag 还能让发布日期门禁保持绿,因为它读的是 tag 对象的 `creatordate`,而新建的带注释 tag 会重置这个值。
39
+
40
+ **让这个情形变少见的那一步是本地的:先创建带注释的 tag,在 tag 存在的情况下跑一遍门禁,全绿之后再推送 ref。** tag 构建是这个周期里唯一 `package.json` 的版本号已经带着自己的 tag 的时刻,所以任何以“最新的 tag”为基准的检查,PR CI 和 `main` 推送 CI **都**看不见——`0.21.39` 就是这样死在自己的 `pnpm check` 上,而杀掉它的那条门禁正是同一个版本刚加进来的。先对着本地 tag 跑 `pnpm check`、`pnpm lint`、`pnpm verify:compat`、`pnpm verify:types`、`pnpm verify:pack`,再执行 `git push origin v<X>`;花掉的是几分钟,另一种选择的代价是一个死 tag 加上上面整套重指流程。
35
41
 
36
42
  ## 发布(手动场景)
37
43
 
@@ -1,12 +1,23 @@
1
1
  # 路线图
2
2
 
3
- 0.21.38 已于 2026 年 9 月 25 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
3
+ 0.21.40 已于 2026 年 9 月 25 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
4
+
5
+ ## 0.21.41 已完成范围
6
+
7
+ - **随包发布的发布清单现在写明了:发布运行死在自己的 tag 上时该怎么办。** `0.21.39` 恰好到达过这个状态——运行在自己的 `pnpm check` 里失败,于是 tag 存在而产物不存在——而清单给的三条路(“针对不变的 tag 重跑”“发布新的 patch 版本”“禁止移动已发布的 tag”)都不覆盖它:按第二条走会把一节从未发布的版本留在 `CHANGELOG.md` 里,随后完整性门禁会拒绝之后的每一个 tag。两种语言都写清了全过程:两次确认读取以及它们各自的撒谎方式、删除重建本质上就是两次普通推送、以及预防性那一步——先在本地建好 tag,**带着 tag 存在**再跑一遍门禁,全绿之后才推送 ref,因为这个构建状态是 PR CI 和 `main` 推送 CI 都永远看不到的。
8
+ - **发布门禁打印出来的补救措施,被改写成真正能执行它的形式。** `scripts/verify-release-version.mjs` 会在 `CHANGELOG.md` 点名一个从未发布的版本时拒绝打 tag,并让读者“把该节标成从未发布过的版本”;拿导出的谓词实测、并配一个原样保留的章节作为对照,`## [0.20.91] - never released` 仍然和它想豁免的那个章节被同样拒绝,只有让标题脱离方括号那种形状才会被豁免。报错信息现在点名的就是这个形状,并由三条用例钉住、每个变异体各杀一条断言:加关键字豁免、让正则匹配裸标题、以及把措辞改回原来的模糊说法——前两种是只看行为那两条腿区分不出来的。
9
+
10
+ ## 0.21.40 已完成范围
11
+
12
+ - **发布说明描述的是它自己的修复已经删掉的那条规则。** `0.21.39` 的第三条要求路线图的 preamble 写出*已打标*的最新版本;而 PR #328 —— `0.21.39` 之所以存在的理由 —— 把它换成了"`package.json` 版本之下最大的那个标签,或者等它打上标签之后就是它自己",并且只改了测试和 `AGENTS.md`。两种语言的路线图副本都在原处更正;`CHANGELOG.md` 里的那一份照它发布时的样子留着,和 `0.21.35` 那句三个样本的结论同等对待。
13
+ - **这条规则现在跨越整个发布周期被检验,而不是停在克隆恰好处于的那一瞬间。** `allowedPreambleVersions()` 从读真实文件的用例里抽出来,铺成十行:准备、标签构建、记录提交、落后两个发布、preamble 写了一个从未打标的版本、克隆里带着一个更新的标签,以及两位数小版本号(按字典序排序会挑错赢家)。前后都是量出来的:只有真实文件用例时,这条规则三条腿里的两条 —— 包括让 Release run 159 失败的那一条 —— 对所有朝它们下的变异都存活。写成表里的行之后,四个变异都会死,而死在哪儿本身就是结论:两个落在"标签构建"那一行,一个落在"两位数小版本号"那一行,而"承认某个已发布版本自己的标签"这一条,在真实文件里**哪儿都不死** —— 在准备阶段的那棵树上,`package.json` 的版本还没打标签,这条腿对任何终态读取都不产生可见贡献,所以它只能在合成出来的瞬间里被判定。
14
+ - **滞后扫描打印的中位数,按它本来的名字说明。** 那里的 `p50` 是中间偏上的那一个元素,不是中间两个的平均值,所以往奇数长度的序列里再加一个样本,数字自己就会动:现代窗口从 `97.3` 到 `126.4`,而同样那批值的插值中位数是 `111.85`。钉在 `tests/publication-lag.test.ts` 里 —— 那里 `n=2` 的 fixture 是唯一能把排序后的列表整个暴露出来的形状。
4
15
 
5
16
  ## 0.21.39 已完成范围
6
17
 
7
18
  - **变更日志里有一个发布被标成了一个什么都没发生的日期。** `## [0.20.10]` 写的是 2026-09-02,而它的标签(`2026-09-03T12:27:29+08:00`)和它的 registry 记录(`2026-09-03T04:28:11Z`)是同一个瞬间,且在两种读法下同一天;它前一个发布的标签打在 09-02 的 21:33,所以那行标题只是没跟着往前走。这条已经改正,而在带标签的 151 个有日期的段落里,它是唯一一个落在自己标签两种读法之外的。
8
19
  - **新增的检查以标签为准,而不是以 npm 为准,而且这个选择是量出来的。** `tests/documentation.test.ts` 现在要求每个段落日期,是其标签瞬间在 UTC 或 `+08:00` 任一框架下的日历日。把同一条规则改成用 `time[<version>]`,会报出三个错段落而不是一个,因为发布完成的时间跨过 UTC 零点的情况多到不能忽略:`0.4.0` 的标签是 2026-08-30T21:28Z,记录却是 2026-08-31T00:03Z,`0.20.85` 同形。那三个"发现"里有两个是检查自己错了。
9
- - **为什么两种框架都收,以及一次空读取为什么骗不过去。** 只收一种框架会让几十段各自描述真实事件的段落被判错(实测:84 段两种框架都对,46 段只在 `+08:00` 对,20 段只在 UTC 对),所以规则是"标签的某一种读法",不是"本地那一天"。对被检查段落数的下限,会把一个没有标签的克隆变成失败而不是干净通过;而要求被检查的行同时覆盖两种标签对象类型,挡住的正是它的静默版本——本仓库两种打标签方式都有,而只有 `%(creatordate)` 对两者都给出答案。 同级用例如从两种语言的路线图中读出同一个说法。那里的日期是写出来的(`September 25, 2026` / `2026 年 9 月 25 日`)而不是 ISO 标题,并且要求所写的版本是**已打标**的最新版本 —— 不是 `package.json` 的那个,因为在整个发布准备期间,待发布的版本还没有标签。
20
+ - **为什么两种框架都收,以及一次空读取为什么骗不过去。** 只收一种框架会让几十段各自描述真实事件的段落被判错(实测:84 段两种框架都对,46 段只在 `+08:00` 对,20 段只在 UTC 对),所以规则是"标签的某一种读法",不是"本地那一天"。对被检查段落数的下限,会把一个没有标签的克隆变成失败而不是干净通过;而要求被检查的行同时覆盖两种标签对象类型,挡住的正是它的静默版本——本仓库两种打标签方式都有,而只有 `%(creatordate)` 对两者都给出答案。 同级用例如从两种语言的路线图中读出同一个说法。那里的日期是写出来的(`September 25, 2026` / `2026 年 9 月 25 日`)而不是 ISO 标题。它所写的版本必须是 `package.json` 那一个之前最新的已打标发布,或者在它的标签已经存在之后,就是 `package.json` 自己 —— 因为 preamble 由记录提交推进,而那次提交落在标签**之后**,所以在某一个发布自己的标签构建上,最新的标签恰恰是 preamble 还没写到的那个版本。`0.21.40` 更正:这句话最初要求的是"已打标的最新版本,而不是 `package.json` 的那个",而那正是让 Release run 159 失败、并由同一个版本的修复只在测试里替换掉的规则。
10
21
 
11
22
  ## 0.21.38 已完成范围
12
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-tab-worker-databus",
3
- "version": "0.21.39",
3
+ "version": "0.21.41",
4
4
  "description": "Framework-agnostic cross-tab data bus with Dedicated/Shared Worker clustering and Centrifuge support.",
5
5
  "type": "module",
6
6
  "license": "MIT",