网络测出了什么
下面每一行都是测量结果,不是观点。每一条都链接到一个已发布的样本,其 contract 在断网的固定容器中执行,你可以自己重跑并反驳结论。
样本 id 是内容地址。GET /v1/samples/<id> 返回本页链接的同一个样本;csx 会在网络验证它时使用的同一个容器中运行其 contract。
页面会翻译,发现本身不翻译。每一条都原样引用了错误字符串、属性名或数值结果,而被转述的测量已经不是测量。
29 条发现,覆盖 8 个生态。
- cargo
- composer
- gem
- golang
- hex
- npm
- pub
- pypi
与官方来源相矛盾
这里的看法不是道听途说,而是项目自己的文档、或它所实现的规范写下的内容。两边都可核对:引文只有一个链接之遥,测量只有一次容器运行之遥。
-
cargoserde 1.0.229, serde_json 1.0.151
普遍认为serde's container-attribute documentation says deny_unknown_fields “is not supported in combination with flatten, neither on the outer struct nor on the flattened field”, which reads as a pair the derive will refuse.
实测serde_derive implements the pair: rustc compiles a struct carrying both with an empty stderr — no error, no warning — and the ordinary case works, since a flattened struct is accepted and only genuine leftovers are rejected. What does break is narrower than the note: a flattened map beside deny_unknown_fields can never collect a key, because every key it was meant to catch is reported unknown instead; and a struct carrying deny_unknown_fields accepts, once flattened into another struct, the same key it rejects when it stands alone.
证明它的 contract · 0c0a0329b6b2 来源: serde.rs container attributes
-
npmjose 6.2.8, Node 22
普遍认为RFC 7518 §3.2 says a key “of the same size as the hash output (for instance, 256 bits for HS256) or larger MUST be used with this algorithm”, and jose implements that specification, so a five-byte HMAC secret is expected to be refused somewhere in the stack.
实测jose signs and verifies an HS256 token with a five-byte key and reports nothing, and WebCrypto underneath imports the same 40-bit HMAC key just as willingly, so nothing below the library catches a weak secret either.
-
npm@modelcontextprotocol/sdk 1.30.0, protocol 2025-11-25, Node 22
普遍认为the MCP specification splits tool failures into two channels and puts “Unknown tools” in the first one — “Protocol Errors: Standard JSON-RPC errors” — printing the example as an error object with code -32602, so a call naming a tool that does not exist should arrive as a JSON-RPC error.
实测the SDK's own McpServer answers it as a successful response instead: await client.callTool({name: "no_such_tool", arguments: {}}) resolves, its isError is true, and the -32602 arrives inside the text — “MCP error -32602: Tool no_such_tool not found” — so the code is prose rather than an error object, and a caller that detects failure by catching sees none. The contradiction is that one bullet and no more — the same page assigns input validation errors to the isError channel by design, which is where they arrive — and the same server does reject with a real McpError carrying code -32602 for an unregistered resource URI, so which channel a failure uses is decided by the handler it reached rather than by the kind of failure it is.
证明它的 contract · d133f23612d4 来源: MCP specification 2025-11-25, tools
被普遍相信,实测却不是
这里的看法来自迁移指南、从邻近库带过来的习惯,或大家反复转述的说法。测量方式与上面相同。
-
npmbcryptjs 3.0.3, Node 22
普遍认为a password hash covers the whole password.
实测bcrypt truncates at 72 bytes and neither hashSync nor compareSync reports it: a second, different password sharing the first one's 72-byte prefix verifies against that hash, and so does the bare prefix, while 71 bytes does not; the limit is bytes, so 36 accented characters survive and 37 lose their tail, and bcryptjs's own truncates() is a separate call you have to make yourself.
-
npmnode:crypto scryptSync, Node 22
普遍认为the memory scrypt needs is 128 * N * r — the figure Node's own documentation quotes, hedged as “It is an error when (approximately) 128 * N * r > maxmem” — so budgeting exactly that raises the cost safely.
实测budgeting exactly 128 * N * r is rejected with ERR_CRYPTO_INVALID_SCRYPT_PARAMS; the accepted minimum is exactly 128 * r * (N + p + 2), pinned to the byte at six parameter sets that vary N, r and p independently — 3072 bytes above the quoted figure, at the defaults and at N=32768 alike, and at N=32768 the quoted figure is exactly the 32 MiB default maxmem, so the next cost step looks like it just fits and does not.
-
pypipolars 1.43.2, Python 3.12
普遍认为df[mask] filters rows, the way it does in pandas.
实测a boolean mask in brackets selects COLUMNS: against a frame whose column count differs from the mask length it raises ValueError, and against a frame with as many columns as the mask has rows — the shape of most test fixtures — the length check passes and it returns the wrong columns with no error at all.
-
composermonolog/monolog 3.9.0, PHP 8
普遍认为Monolog 3 removed the integer level constants, so an upgrade means replacing every Logger::WARNING.
实测Logger::WARNING is still defined, still 300, still equal to Level::Warning->value, and addRecord still accepts an int; what actually breaks is is_array($record), because a record is now a LogRecord object that keeps answering $record['message'] — so the migration reads as finished while the array branches quietly stop being taken.
-
npmlightningcss 1.33.0, npm 10, Alpine
普遍认为npm installs only the right native package on Alpine, because each platform package declares the libc it was built for.
实测npm ci installed BOTH linux-x64 variants, glibc and musl, because the lockfile this npm 10 image wrote records os and cpu on every optional entry and never libc — every darwin, win32 and arm64 package was correctly skipped, and the unusable glibc build is a full second copy of the 10 MB addon on disk. This is npm's lockfile writer, not lightningcss, and it is fixed upstream: npm records libc from 11.11.0 on, so the repair is regenerating the lockfile rather than upgrading the npm that reads it.
-
npmesbuild 0.25.12, Alpine
普遍认为running esbuild on Alpine means installing its musl-specific build.
实测there is no musl build to install: none of esbuild's optional platform dependencies mentions musl, the linux-x64 package npm picks declares no libc constraint, and its binary has no ELF interpreter at all — read from the program headers on a musl image where node itself names musl's loader.
-
npmjose 6.2.8, Node 22
普遍认为catching JWTClaimValidationFailed handles the claim checks jwtVerify performs.
实测JWTExpired is a sibling of JWTClaimValidationFailed rather than a subclass, so every expired token falls past that clause into the generic branch, while a not-yet-valid nbf — checked without being asked — is caught by it.
-
npmzod 4.4.3, Node 22
普遍认为z.coerce.number() parses a numeric string, rejecting what is not a number.
实测it is Number(input) followed by the number check, not a numeric parser, so "", " ", null, false and [] are all accepted and arrive as 0 — an empty form field or a null column silently becomes zero — while "1e999" is rejected, because Number() overflows it to Infinity.
-
npmvitest 4.1.10, Node 22
普遍认为a forgotten await on expect(...).rejects makes the test pass while asserting nothing.
实测which half is true depends on the test function: in a sync one the forgotten await still fails the test and carries the real rejection message, and in an async one the assertion settles first, so the test is reported passed and the failure becomes the run's single unhandled error — green test, red run, and any tooling reading only test states calls it a pass.
-
npmbun:sqlite, Bun 1.3.14
普遍认为the options argument to new Database(path, options) overrides defaults.
实测it replaces the open flags outright, so new Database(":memory:", {}) throws SQLiteError SQLITE_MISUSE where new Database(":memory:") works, and { create: false } lands on the same zero flags — only a true access mode or a strict/safeIntegers key puts the default back.
-
composerguzzlehttp/guzzle 8.0.2, PHP 8
普遍认为if ($e->hasResponse()) { $e->getResponse(); } is how you read the response off a Guzzle RequestException.
实测on guzzle 8 neither method is declared on RequestException — getResponse moved down to ResponseException with a non-nullable return type, and hasResponse is declared on neither class — so the guzzle 7 idiom is a fatal “Call to undefined method …::getResponse()”, not a deprecation.
-
gemjson 2.9.1, Ruby 3
普遍认为JSON.dump is JSON.generate under another name.
实测JSON.dump defaults to allow_nan, so it writes {"ratio":NaN} — a document JSON.parse refuses and only JSON.load will read back — while JSON.generate refuses the same float outright; JSON.load carries the matching asymmetry with allow_blank, returning nil for an empty string where JSON.parse raises.
-
hexElixir's built-in JSON vs jason 1.4.4
普遍认为dropping Jason for the JSON module in Elixir 1.18+ is a module rename.
实测the two encode the same payload byte for byte, but the decode error is JSON.DecodeError, so a rescue Jason.DecodeError clause compiles, still reads correctly and catches nothing; JSON.decode/1 returns a bare reason tuple rather than a struct, and JSON.decode/2 does not exist, so keys: :atoms has nowhere to go.
-
pubcollection 1.19.1, Dart 3
普遍认为two Lists holding the same values are equal.
实测== on a List, Map or Set is identity, and package:test's equals matcher deep-compares — so the assertion is green on exactly the values production calls unequal; a const collection is canonicalized and does compare equal, which is what makes the rule look inconsistent, and copying it loses the equality again.
-
golangshopspring/decimal 1.4.0, Go 1.26
普遍认为a decimal type is exact, which is the reason to reach for one.
实测Div is DivRound reading a mutable package-level global, decimal.DivisionPrecision, which defaults to 16 — so (1/3)*3 is 0.9999999999999999 and not 1, any dependency in the process can move the precision, and nothing at the call site says so; DivRound takes the precision as an argument and QuoRem is the one that keeps the remainder.
-
golangspf13/cobra 1.10.2, Go 1.26
普遍认为a cobra command tree with no SetArgs runs with no arguments.
实测with no SetArgs at all cobra parses os.Args[1:], so a command tree driven from a test binary parses that binary's own arguments and fails on a flag nobody wrote; the guard is c.args == nil, so SetArgs(nil) reopens the same fallback and only an empty non-nil slice means “no arguments”. cobra's one escape hatch keys on the program name being exactly cobra.test, which saves its own tests and nobody else's.
-
pypiorjson 3.11.9, Python 3.12
普遍认为orjson's README says “JSONEncodeError is a subclass of TypeError”, which reads as a narrower class, so except orjson.JSONEncodeError looks tighter than except TypeError.
实测orjson.JSONEncodeError is TypeError — the same object, so the README's sentence holds only in the sense that every class is a subclass of itself — and the two clauses are therefore identical: either one swallows every TypeError raised in the block, not only the encoder's. JSONDecodeError, by contrast, really is a distinct subclass of json.JSONDecodeError.
-
pypiattrs 26.1.0 vs dataclasses, CPython 3.12
普遍认为@dataclass(slots=True) is the stdlib equivalent of an attrs slotted class.
实测on CPython 3.12 the stdlib leaves the __class__ closure cell pointing at the class it discarded, so zero-argument super() inside a slotted dataclass raises TypeError at call time — not at definition — while the identical attrs class works because attrs rebinds the cell; weakref.ref also raises on the slotted dataclass and not on the attrs one.
-
pypifreezegun 1.5.5, Python 3.12
普遍认为freeze_time moves the wall clock, so monotonic timers are unaffected.
实测it patches time.monotonic and time.perf_counter to the same frozen wall clock, so a duration measured across the boundary of the freeze comes out about thirty years long, and moving the frozen clock backwards makes time.monotonic() go backwards — a deadline written as monotonic() + timeout is never reached.
-
cargoaxum 0.8.9
普遍认为axum answers a bad JSON body with 422.
实测it answers with three statuses and only one of them is 422: syntactically broken JSON is 400, a body that parses but does not fit the target type — a wrong field type, or a required field left out — is 422, and a request with no Content-Type at all is 415 carrying the rejection message where the handler's 201 would have been, so a single assertion for “bad input” is wrong on two of the three. The 400's rejection body is text/plain rather than JSON; and the Content-Type check is not string equality — the header is parsed and its type has to be application, so application/json; charset=utf-8 and application/vnd.csx+json are accepted while text/json is refused exactly like a header that was never sent.
-
cargoonce_cell 1.21.4 vs std
普遍认为std absorbed once_cell, so the dependency can go.
实测almost — and the remainder is two methods, both of them the fallible ones. Compiled with the rustc this sample was measured on, LazyLock::force_mut, DerefMut on LazyLock and OnceLock::wait all build, so the reasons usually quoted for keeping the crate are out of date, and std::cell::OnceCell and LazyCell cover its unsync half. OnceLock::get_or_try_init and try_insert do not build: both are E0658, the first behind once_cell_try with tracking issue 109737, the second behind once_cell_try_insert — so a fallible initialiser has no std spelling, and the get-then-set stand-in written in their place is not equivalent, because 16 threads racing it run the initialiser 16 times where get_or_init runs it once.
-
golanggorm.io/driver/sqlite 1.6.0 over mattn/go-sqlite3 1.14.49, Go 1.26
普遍认为a cgo package cannot be built with CGO_ENABLED=0, so the build catches it.
实测go-sqlite3 compiles a stub instead: the import builds, the binary links, and the stub still registers the database/sql driver name sqlite3, so finding that name in sql.Drivers() proves nothing about the driver working. The first thing that connects is what fails, with an error naming CGO_ENABLED=0 and saying it “requires cgo to work”, and database/sql defers even that: sql.Open only records the driver name and returns a nil error, and Ping is where “This is a stub” surfaces. So the build is green and the first connection is where it breaks.
-
golangspf13/viper 1.21.0, Go 1.26
普遍认为AutomaticEnv feeds the environment into Unmarshal the way it feeds Get.
实测Unmarshal enumerates AllKeys and reads each key it finds, and AutomaticEnv contributes no keys to that list — it cannot, since it would have to guess names — so with CSX_LOG_LEVEL exported, GetString("log.level") returns env while AllSettings is empty and the struct field stays "", with no error anywhere to say the two disagree. The repair is making the key enumerable: either a SetDefault that never wins the lookup it just enabled, or viper.ExperimentalBindStruct, which takes the key list from the destination struct.
-
composersymfony/console 8.1.4, PHP 8
普遍认为passing --no-interaction to CommandTester::execute turns the prompts off, the way it does on the command line.
实测what interprets that flag is the application run, and a CommandTester is not one, so under it the option binds and does nothing: getOption('no-interaction') is true and the question is asked anyway. -v is the same dead end — bound true while the output stays at VERBOSITY_NORMAL and the verbose writeln prints nothing — and what the tester does read are execute()'s own interactive and verbosity options, or the same command driven through ApplicationTester, where both flags mean what they say.
-
pubshelf_router 1.1.4, Dart 3
普遍认为a route handler's parameter names say which capture each one receives.
实测captures are applied positionally in the order the route pattern declares them and the closure's parameter names are never read, so a handler written (Request request, String id, String org) against /orgs/<org>/users/<id> receives the org capture in id: /orgs/acme/users/u42 answers “acme/u42” where the names promise “u42/acme”, and nothing reports it. The count is not checked at registration either — one argument too many registers cleanly and becomes a NoSuchMethodError on the first request that matches.
-
hexecto 3.14.1
普遍认为an empty string in the params either arrives as an empty string or clears the field.
实测cast compares it against empty_values — [""], compared after trimming, so a whitespace-only string counts too, while a value that survives the check is stored untrimmed — and substitutes the field's declared default, which is nil only for a field that has none: sending "" for a field defaulting to "member" over a stored "admin" writes "member", so an empty form field demotes rather than clears. When the substituted default equals the data the key is absent from changes rather than present and empty, which is why the debugger shows nothing there and validate_required reports “can't be blank” for a param that did arrive.
如何核对这里的任何一行
打开样本,读它的 contract,然后运行。contract 就是该样本自己的测试:它在断网的固定容器中运行,那次运行的签名回执才是网络所存储的东西。这里没有一条依赖我们对某个库的理解,只依赖这个库实际做了什么。
无法确认样本仍在线的发现不会出现在本页。清单比样本池短,是有意为之。