fix: resolve three campaign residual defects - #2266
Conversation
|
Correction to candidate 1 in the body, from a source readback of The body says the lossy inode identity guards
The genuine data-loss consequence belongs to the dedup callers, not the overwrite guard: So the candidate stands, and its severity is real, but it must be filed as:
No code in this pull request changes as a result; the correction is to the candidate write-up that will become its own issue. |
`gather` sorted directory entries with `String.prototype.localeCompare`,
which is locale-dependent. Under ICU en-US `"alias-a".localeCompare("alias_a")`
is 1, so `alias_a` traverses first, while `Buffer.compare` orders the
other way. Traversal order decides which lexical alias wins the
visited-directory dedup, so the emitted output tree and the diagnostic
path both varied by locale - the "nondeterministic overwrite order"
named in #2082's consequence surface.
`Buffer.compare` makes the order byte-lexical and deterministic.
Scope note: an earlier candidate for this issue also added a second
`path:<normalized realpath>` identity key beside the inode key. It was
dropped after measurement. Its stated root cause - that a Windows
junction can expose the same physical directory with a different
`stat.dev` - is false: `fs.promises.stat` follows the reparse point, so
junction and target report identical `dev` and `ino`. And the guard
matches with `.some(...)`, so a canonical-key hit means the same
normalized realpath was visited before, hence the same path, hence the
same object, hence the same `(dev, ino)` - the inode key already hit.
The only way it can hit where the inode key did not is if the object at
that path changed identity between visits, where traversing the new
object is correct and the added key would skip it. Redundant, or wrong.
On win32 `fs.realpathSync` also does not case-canonicalize, so `\real`
and `\REAL` yield equal inode keys but different canonical keys, making
the canonical key strictly weaker on the alias-spelling axis and
bypassing `FileSystemIdentity`'s own `caseSensitive` policy.
Closes #2082
`_ProtobufReader` accepted over-long varints. A ten-byte encoding whose final byte carries payload above bit 63 silently wrapped into a different value, so bytes a Go peer rejects became data. The bound is now enforced the way `protowire.ConsumeVarint` enforces it - the tenth byte must be below 2 - across field values, field tags, length prefixes, and message framing, including the unknown-field skip path. The oracle is a JSON corpus shared by the TypeScript suite and a Go test that replays it through `protowire`, so both languages answer the same expectations from the authoritative parser rather than from typia's own output. The corpus covers every legal varint width, the exact 64-bit maximum, the one-past-boundary value, a continuation bit set on the last permitted byte, and truncation at every byte position. The reference is named as the Go `protowire` parser rather than "the official parser": upstream Java `CodedInputStream.readRawVarint64` accepts and discards a tenth byte above bit 63, and the encoding specification fixes only the ten-byte maximum while staying silent on excess payload. Rejecting matches `proto.Unmarshal`, keeps typia from accepting bytes a Go peer refuses, and no encoder emits that form - but the divergence is recorded so the corpus cannot later be "disproved" by pointing at Java. Closes #2256
…uting `CompareEqualProgrammer` routed a union member by a rule that disagreed with the validator's own function rule, so a functional member of a union could be matched by the wrong arm during equality comparison. The routing now decides membership by the same rule the emitted validator uses, keeping one answer for one type. This unit was previously blocked on #2238's declaration-spelling boundary. Consolidating both into one pull request removes the merge ordering constraint: the callable-spelling fix is present in the same tree, so the routing rule and the boundary it depends on are verified together. NOT REVERIFIED ON THIS BASE. The candidate carries an earlier 52-case Node runtime proof and a `Functional=false` mutation witness, but those were executed against an older master and cannot be reused as evidence. CI is the first thing to run it here. Closes #2252
218e663 to
3bdfa05
Compare
Scope reduced after CI, with diagnosisCI compiled and ran everything for the first time. Results on I then ran those three locally, one at a time, and instrumented the native identity predicate to get ground truth. Here is what each failure actually was. #2200 — the product change is correct; the fixtures encode an unreachable premiseInstrumenting The fixture sets
Why the premise is unreachable, which is the more important finding: So #2200's residual has no reachable failing scenario while that reference stands, and its product change has no regression that can distinguish it. That is the same standard this pull request already applied when it dropped #2082's second identity key, so it is applied here too: #2200 is removed from this pull request pending re-adjudication. Its work is preserved on The forced DOM library is filed as its own candidate: it changes the global scope of every typia consumer, including pure Node projects, and it is upstream of what #2200 even means. Separately, that fixture's #2238 — the fix works; the three-way identity assertion also indicts something older
So the fix did what it set out to do — a type literal is no longer expanded as a structural object and now routes exactly like the mutually assignable function type. What remains is a disagreement in the JSON-schema path between the interface spelling and plain function types, which is #2250's merged boundary rather than anything this diff introduced, and it is not obvious which of the two behaviours is canonical: omitting a function member from a JSON schema, or emitting a Deciding that is a separate product question. #2238 is removed from this pull request rather than shipped with a red mandatory regression or with that regression weakened. Its work is preserved on What remains here
CI is now re-running on the reduced branch. |
Correction: the DOM finding is narrower than I stated, and it changes what #2200 meansI checked the published declaration on npm — So my earlier statement on this thread, that every project importing typia loads the DOM library, is wrong for npm consumers and I withdraw it. The correct scope:
Two consequences, and the second one matters more than the first. 1. #2200's residual is not unreachable after all. It is unreachable in an in-repo transform fixture, because such a fixture cannot construct a DOM-free project while importing typia from source. For a real npm consumer with neither DOM nor 2. This is a test-oracle defect in its own right. Any in-repo transform fixture that believes it has excluded the DOM library has not. Every native-identity fixture that reasons about A correct #2200 regression needs a fixture that reproduces the npm-consumer resolution — a packed or stubbed |
Purpose
This consolidates the residual issue-campaign work into one pull request, replacing five separate campaign pull requests.
Two maintainer directives shaped it. First, the local machine ran out of resource headroom, so all local building and testing was stopped and verification is delegated to GitHub Actions CI. Second, the residual pull requests are to be combined into a single one. Accordingly the campaign's per-push exact-SHA CI cancellation rule is suspended for this branch: these runs must be allowed to finish, because they are now the verification.
Every commit here is a squashed net diff of one campaign unit, one commit per issue, so each issue keeps its own acceptance boundary, rollback unit, and failure attribution inside the shared pull request.
What each commit closes
fix(generate): order traversal entries by bytes, not localegathersorted directory entries withlocaleCompare. Traversal order decides which lexical alias wins the visited-directory dedup, so the emitted output tree and the diagnostic path were locale-dependent.fix(protobuf): reject malformed varints across values and framing_ProtobufReaderaccepted over-long varints; a ten-byte encoding with payload above bit 63 silently wrapped into a different value. Now bounded across values, tags, length prefixes, framing, and the unknown-field skip.fix(native): require a runtime provider for native identitymetadata_symbol_is_global_typetreated global-table resolution as proof that a runtime provides the constructor, so a project's owndeclare globalFile/Blobwon the lookup.fix(native): preserve function semantics across callable spellingsKindInterfaceDeclaration, sotype X = { (v: number): string }and the anonymous type literal fell through to structural object metadata.fix(compare): preserve functional union membership during equality routing#2238 and #2252 were previously serialized by a merge-order dependency. Consolidation removes it: the callable-spelling boundary and the routing rule that depends on it are now verified in one tree.
Evidence status — read this before reviewing
The campaign's normal gates are local build, focused suite, mutation proof, Self-Review, independent verification, and a full integration gate. The resource stop landed mid-wave, so those are not uniformly satisfied here. Stated exactly:
repeated directory aliases are rejectedfailed with a realERR_ASSERTION, not a timeout. Adjacent cycle controls passed. Junction/inode, stability, case-spelling, and replacement probes all executed.mutual-cyclecell, which timed out under fleet load and is recordedUNTESTED. The five CLI-mode rows.protowirev1.34.2 source read; Java/Go divergence checked upstream.Three commits contain Go that has never been compiled. CI is the first thing to compile or run them, and failures here are expected to be diagnosed and repaired in this pull request.
The #2238 invariant that #2252 builds on is stated in its commit message and is derived from source reading, not from an executed matrix. If CI contradicts it, the routing fix must be re-examined, not patched.
Not included: #2253
#2253 (contextual
any/unknownJSON serialization) is excluded and stays open. Its work is preserved onfix/json-contextual-undefinedand described on #2254. It is not a salvageable implementation:_jsonStringifyArrayruntime helper is orphaned —StringifyJoiner.Arraystill emitsinput.map(arrow).join(",")and nothing imports or calls it;undefined: falseoracle question is unresolved, and nativeJSON.stringifyis not an unqualified oracle in a mode where typia may legitimately skip undefined checks.Its root-cause analysis is worth keeping: the class is wider than the reported witness. A sparse
number[]emits[,3], and a required property whosetoJSONreturns undefined emits{"k":undefined}— text that is not valid JSON at all.Superseded pull requests
#2262, #2258, #2257, #2264, #2255 are superseded by this pull request. #2254 stays open for #2253.
Defect candidates raised while implementing, deliberately kept out of this diff
Recorded so they are not silently deferred; each needs its own issue.
fileIdentityKeybuilds identities fromfs.Stats.inoas a JS number. In a 4000-directory probe, 1879 NTFS directory IDs exceeded 2^53, where the double ulp is 8. Two distinct directories can fold into one identity, silently skipping a real directory or raising a bogus revisit error. It also guardsensureTargetFiles' input/output overwrite check, which makes it a data-loss path.{ bigint: true }is necessary but not sufficient: it addresses neitherino === 0filesystems nor the win32 case-folding gap in the path fallback.metadata_symbol_from_default_libdecides default-library provenance from thelib.*.d.tsbase name, whichlibReplacementdefeats — real lib files resolve tonode_modules/@typescript/lib-es2022/index.d.ts. This pull request routes only its own new predicate around it via a checker-keyed registry; the old helper's other callers, includingmetadata_is_global_function_interface, keep the wrong answer.tests/test-typia-generatefilesystem cases are a flaky oracle by construction. Each of the 10 cases pays fullttsx --no-pluginsstartup with no reuse, so cost is dominated by compiler startup rather than the behaviour under test — two consecutive full runs took 570 s and 644 s. The 180 s per-case bound sits only ~2.5x above unloaded cost. The durable fix is to stop paying startup ten times, not to raise the bound.