Skip to content

fix: two files shipped in 0.3.0 that cannot compile from a tarball - #62

Merged
sshaplygin merged 5 commits into
mainfrom
fix/package-includes
Aug 16, 2026
Merged

fix: two files shipped in 0.3.0 that cannot compile from a tarball#62
sshaplygin merged 5 commits into
mainfrom
fix/package-includes

Conversation

@sshaplygin

Copy link
Copy Markdown
Owner

fix: two files shipped in 0.3.0 that cannot compile from a tarball

ytsaurus-skiff/tests/wire.rs and ytsaurus-job/tests/skiff_reader_tests.rs
include_str! reference vectors from tests/skiff-go-interop/, which is
outside their crates. Those macros resolve at compile time, so both files
compile in this repository and cannot compile from the published .crate — the
fixtures are not in the tarball and no consumer can put them there. Verified
against the actual artifacts downloaded from crates.io: of the nine published,
ytsaurus-skiff and ytsaurus-job are the two whose cargo test --no-run
fails.

Consumers are unaffected: cargo does not build a dependency's tests. It bites
whoever unpacks the crate and runs its suite, and vendoring or packaging
workflows that do the same.

Four files of this kind were found and excluded before 0.3.0 went out. These two
were missed, and the reason is worth recording: the search was a line-based
grep for

include_str!("../../../tests/…

which does not match

hex_fixture(include_str!(
    "../../../tests/…"
))

— the same macro with a newline in it. The audit that caught the other four did
not catch these either.

So scripts/check-package-includes.sh replaces the grep. It asks cargo which
files actually ship, parses each with a multi-line-aware regex, resolves every
include target and fails if one escapes its crate root. It runs in CI, and it
was checked to fail: re-including tests/wire.rs makes it exit 1 naming the
file and the three fixtures it reaches.

0.3.0 is published and cannot be replaced. This lands the fix so it is correct
from here, and so nothing else ships with it.

`ytsaurus-skiff/tests/wire.rs` and `ytsaurus-job/tests/skiff_reader_tests.rs`
`include_str!` reference vectors from `tests/skiff-go-interop/`, which is
outside their crates. Those macros resolve at compile time, so both files
compile in this repository and cannot compile from the published `.crate` — the
fixtures are not in the tarball and no consumer can put them there. Verified
against the actual artifacts downloaded from crates.io: of the nine published,
`ytsaurus-skiff` and `ytsaurus-job` are the two whose `cargo test --no-run`
fails.

Consumers are unaffected: cargo does not build a dependency's tests. It bites
whoever unpacks the crate and runs its suite, and vendoring or packaging
workflows that do the same.

Four files of this kind were found and excluded before 0.3.0 went out. These two
were missed, and the reason is worth recording: the search was a line-based
grep for

    include_str!("../../../tests/…

which does not match

    hex_fixture(include_str!(
        "../../../tests/…"
    ))

— the same macro with a newline in it. The audit that caught the other four did
not catch these either.

So `scripts/check-package-includes.sh` replaces the grep. It asks cargo which
files actually ship, parses each with a multi-line-aware regex, resolves every
include target and fails if one escapes its crate root. It runs in CI, and it
was checked to fail: re-including `tests/wire.rs` makes it exit 1 naming the
file and the three fixtures it reaches.

0.3.0 is published and cannot be replaced. This lands the fix so it is correct
from here, and so nothing else ships with it.
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown

📊 Criterion benchmarks: main vs PR

🔍 Main 55e299ad4c46 → PR edaa405e1ae5

📊 25 benchmark(s) compared · ⚠️ 0 regression(s) at ≥20% · ✅ 0 improvement(s) at ≥20%

ℹ️ Time is lower-is-better. Both revisions ran on the same GitHub-hosted VM; ±20% is an advisory marker, not a merge gate.

Benchmark main PR change signal
read/read_table_rows/1000 663.12 µs 651.85 µs -1.7% ℹ️ within runner noise
read/read_table_rows/10000 5.8144 ms 5.8122 ms +0.0% ℹ️ within runner noise
read/read_table_rows/100000 66.311 ms 65.535 ms -1.2% ℹ️ within runner noise
Skiff codec throughput/decode_dynamic 42.791 ms 43.472 ms +1.6% ℹ️ within runner noise
Skiff codec throughput/encode_dynamic 4.9743 ms 5.0203 ms +0.9% ℹ️ within runner noise
Skiff codec throughput/validate_and_skip 6.5880 ms 6.4909 ms -1.5% ℹ️ within runner noise
Skiff job throughput/skiff_dynamic 47.225 ms 47.898 ms +1.4% ℹ️ within runner noise
write/encode_then_write_table/1000 288.82 µs 286.09 µs -0.9% ℹ️ within runner noise
write/encode_then_write_table/10000 2.0379 ms 2.0011 ms -1.8% ℹ️ within runner noise
write/encode_then_write_table/100000 21.335 ms 20.807 ms -2.5% ℹ️ within runner noise
write/write_table_rows/1000 283.47 µs 280.19 µs -1.2% ℹ️ within runner noise
write/write_table_rows/10000 1.9514 ms 1.9322 ms -1.0% ℹ️ within runner noise
write/write_table_rows/100000 18.262 ms 18.021 ms -1.3% ℹ️ within runner noise
YSON job throughput/parse_borrowed 74.000 ms 74.174 ms +0.2% ℹ️ within runner noise
YSON job throughput/parse_dynamic 114.29 ms 113.73 ms -0.5% ℹ️ within runner noise
YSON job throughput/parse_owned 81.756 ms 79.004 ms -3.4% ℹ️ within runner noise
YSON job throughput/pass_through 26.138 ms 24.641 ms -5.7% ℹ️ within runner noise
YSON Throughput/Deserialize Binary 7.6092 ms 7.6106 ms +0.0% ℹ️ within runner noise
YSON Throughput/Deserialize Text 9.5580 ms 9.5137 ms -0.5% ℹ️ within runner noise
YSON Throughput/Serialize Binary 1.0229 ms 1.0346 ms +1.1% ℹ️ within runner noise
YSON Throughput/Serialize Text 3.6420 ms 3.6732 ms +0.9% ℹ️ within runner noise
YSON vs Skiff dynamic encoding/skiff_dynamic 24.235 ms 23.512 ms -3.0% ℹ️ within runner noise
YSON vs Skiff dynamic encoding/yson_dynamic 87.230 ms 81.170 ms -6.9% ℹ️ within runner noise
YSON vs Skiff dynamic job API/skiff_dynamic 48.394 ms 48.947 ms +1.1% ℹ️ within runner noise
YSON vs Skiff dynamic job API/yson_dynamic 116.61 ms 114.10 ms -2.2% ℹ️ within runner noise

`ytsaurus-skiff` and `ytsaurus-job` needed the fix in the commit before this
one, and the workspace version is one version — "they move as one" is stated in
the README, in AGENTS.md and in every changelog here, and giving two crates a
private version to avoid a patch bump would start exactly the kind of drift
between documentation and reality that 0.3.0 spent its whole diff correcting.

So all nine go to 0.3.1. Seven of them record it as what it is: no change beyond
the version. The two that were broken say what was broken, that a consumer
depending on the crate was never affected, and why the check that was supposed
to catch it did not.

AGENTS.md's shipped section now carries the same account, next to the note about
0.2.6 never having been released.
It was a bash entrypoint wrapping a Python heredoc — two languages to read
for one job, and the Python half was a string as far as any tool was
concerned: not linted by ruff, not formatted, no syntax check until CI ran it.

Now it is `scripts/check_package_includes.py`, plain stdlib, invoked directly.
ruff lints and formats it with the rest, which is what `pyproject.toml` exists
for. It is still checked to fail: re-including `tests/skiff_reader_tests.rs`
makes it exit 1 naming the file and the fixture it reaches.

The count of Python files was stated as four in three places and is now five.
Automation here was bash, Python, JavaScript and Rust, with the largest pieces
of it inlined into workflow YAML where no linter, formatter or type checker
could see them. The rule is now written down in AGENTS.md Fixed decisions:
Python for anything that computes, parses or asserts; bash for glue only, with
no source of another language embedded in it and none of it inlined into a
workflow; Rust only where genuinely required; Go only in the interop tests.

**JavaScript is gone.** `scripts/compare-criterion.mjs` was the one file outside
every mechanism this repository has — no lint, no format, no test, and Node
inherited unpinned from the runner image. Its port is byte-identical, and that
is verified rather than asserted: a fixture covering an improvement, a
regression past the threshold, one under it, a new benchmark, a removed one and
the rounding boundary renders identically under both, and all three error paths
produce the same message and exit code. Two pieces of JavaScript arithmetic are
load-bearing and are reproduced rather than tidied: `toFixed` strips the sign
before rounding, so a tie goes away from zero and `-6.25` prints `-6.3` where
Python's banker's rounding gives `-6.2`; and `localeCompare` is ICU collation,
not codepoint order. The *rounded* value is what gets classified against the
20% threshold, so either would have silently changed a published report.

The `actions/github-script` block went with it — 27 lines of JavaScript that
found the previous benchmark comment and edited it. `gh` is authenticated from
GH_TOKEN exactly as that action's client was. The matching stays on the report's
HTML marker and deliberately not on the comment's author, which is what
`gh pr comment --edit-last` does: a second bot on the same pull request would
silently steal the edit. Verified against the live API, read-only — it finds
the existing marked comments on #61 and #62, so it will edit them rather than
post duplicates.

Two assertions came out of ci.yml. The worker-graph check and the
static-linkage check were 38 lines of shell between them, and the shell version
of the second one had the failure mode this repository keeps meeting: a glob
matching nothing walks its loop zero times and reports success. Both are now
scripts that were checked to fail — the static one against stubbed `file`/`ldd`
in all three states (no binaries, static, dynamic), the graph one by adding a
crate that is present to the forbidden list.

The five Python heredocs inside the cluster-e2e shell scripts are real files
now. Two of them were `python3 -c "` with double quotes, so the shell expanded
`$` inside what was meant to be Python source — no live bug, one edit away from
one.

Type checking is `mypy --strict`, not `ty`. ty is astral's, like ruff and uv,
and would keep the toolchain to one vendor, but it is preview and a checker
whose diagnostics move between releases is a poor thing to gate CI on; on the
same two files mypy found a real gap that ty did not. The reasoning is in
pyproject.toml so the next person does not have to re-derive it. Four helpers
written before the rule needed annotating to pass; each was checked for
semantic inertness, and the committed fixtures two of them generate are
byte-identical afterwards.

`scripts/check_package_includes.py`, added yesterday, had the vacuity bug
itself: a package whose `cargo package --list` failed was skipped with a
warning and counted as clean. In the script written to stop a defect that
reached crates.io twice. It is fatal now, and that was verified.

Also corrected: AGENTS.md said the cluster end-to-end run was not in CI, and it
has been on every push to main since e5b5198.
ruff lints, uv runs, and now ty type-checks — all three astral, one binary
each and one place to look when a version moves. mypy --strict was in place
first and was equally clean; the switch is for that consistency, not because
either found something the other missed. Verified on the same injected
return-type error: both report it.

The two untyped third-party imports are suppressed per import line, with
`# ty: ignore[unresolved-import]` — `yt`, the official YTsaurus Python client,
and `yt_yson_bindings`, a compiled C extension over upstream's
library/cpp/skiff. Neither ships type information and neither will. Turning
`unresolved-import` off in pyproject.toml would have been one line and would
also have hidden a typo in a first-party import; six narrow comments cannot.

Four `# type: ignore[call-arg]` comments went away with mypy. They existed
because mypy does not run class decorators and so saw `@yt_dataclass` types as
having no constructor at all; ty resolves the decorator and needs nothing. The
comment that explained them now says which construct to look at if the checker
is ever changed again, rather than describing a checker that is no longer here.
@sshaplygin
sshaplygin merged commit 87d5ecb into main Aug 16, 2026
10 checks passed
@sshaplygin
sshaplygin deleted the fix/package-includes branch August 16, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant