Skip to content

V1.1.1 — rule-reachability, false-positive and reliability fixes - #1

Merged
geyutu6755 merged 7 commits into
mainfrom
fix/v1.1.1-false-positives
Nov 4, 2026
Merged

geyutu6755 merged 7 commits into
mainfrom
fix/v1.1.1-false-positives

Conversation

@geyutu6755

Copy link
Copy Markdown
Owner

Independent acceptance of this branch (executed locally, all green):

  • TypeScript strict / ESLint: clean
  • Vitest: 239 passed / 0 failed — includes the new reachability.test.ts
    gate that drives the REAL pipeline (DOCX bytes → rules) because hand-injected
    field unit cases could not detect unreachable rules, and bounds.test.ts
    which crafts archives with lying headers and asserts caps are enforced on
    bytes the inflater actually produces
  • Production build: ok; pnpm audit: no known vulnerabilities
  • Playwright: 14 passed / 4 skipped (skips: real-clipboard read and
    offline emulation are Chromium-only — recorded reasons)
  • PWA install/update/offline script: 10/10 PASS

Key fixes: entry-format rules made reachable from real documents; four false
positives removed (013/015/018 + copy-path); decompression caps enforced on
produced bytes instead of declarations; rule failures surfaced as warnings
instead of silent fake-success; SW install rejects incomplete precaches so the
previous cache always survives; honest parse warnings for unsegmentable
reference lists.

…sitives

V1.1.0 shipped entry-format rules that could never fire on a real .docx,
because the field extractor destroyed the very formatting it was meant to
police, and RR-FMT-015 read two fields nothing ever assigned.

Parser (fields.ts):
- keep pagesRaw / citedDateRaw as printed, alongside the normalised values
- capture the page range span verbatim, so RR-FMT-011 can still see ~ — –
- accept full-width punctuation and non-ISO printed cited dates; add
  normalizeCitedDate() so present-but-undeterminable stays distinguishable
  from absent
- extract place/publisher positionally (no institution keyword list), so a
  degree-granting unit is recognised whatever it is called
- publication year now takes the first plausible 1500-2100 group, so
  "GB/T 7714—2025" no longer yields year 7714

Rules (definitions.ts):
- RR-FMT-011/017 now read the printed spans and fire on real documents
- RR-FMT-013 skips any entry carrying a printed cited date; the format
  problem is reported once, by RR-FMT-017, never as "element missing"
- RR-FMT-015 stops flagging correctly formed thesis entries
- RR-FMT-018 recognises GB/T, IEC/IEEE, IEEE P, AIAA G, ANSI/ANS, ISO/IEC,
  part numbers and a responsible body before the number; it matched three of
  the standard's own examples as "missing" before
- RR-FMT-014 cites the table that actually governs each type (adds 表11 for
  网页 and 表14 for 数据集) and honours 表14's electronic-resource condition
- fix previews are only offered for a span that occurs exactly once

New src/core/rules/entry-requirements.ts holds the per-type element facts
(element, necessity, clause, table) verified against the official 2025 scan;
only bibliographic facts are recorded, the standard's text is not reproduced.

New src/core/rules/reachability.test.ts drives DOCX bytes through the whole
pipeline and is the permanent gate that unit tests with hand-injected fields
could never provide: 20 of its 37 cases failed before this commit.
…fuses

The preview could differ from what was copied, and a finding could offer no
preview at all without explaining why.

- copy now uses split/join on the exact span the panel displays, so `$&`, `$1`
  and back-quote sequences inside a document cannot be expanded by
  String.replace, and preview text equals clipboard text by construction
- Issue gains fixBlocked: when a mechanical fix is unsafe — the value occurs
  twice, or “2025-13-45” resolves to no real date — the reason is shown in
  place of the copy button instead of silence
- RR-FMT-011/017 report through mechanicalFix(), which returns either an
  actionable preview or that reason

New coverage: a chromium E2E uploads a real DOCX, reads the actual clipboard
and compares the whole corrected entry verbatim (not merely “it changed”),
and asserts the duplicated-date entry renders the refusal and no copy button;
plus four pipeline-level safety cases in reachability.test.ts.
The old unpacker inflated the whole archive with unzipSync and checked sizes
afterwards; the comment claimed periodic aborts that did not exist, and the
pre-scan trusted central-directory declarations that an author of the file
controls. A lying header therefore bypassed every cap (ratio checks were also
skipped whenever a size was declared as 0).

Rebuilt on fflate's streaming Unzip, fed in 64 KiB slices:

- per-entry and cumulative caps are applied to each chunk as it is produced,
  and feeding stops as soon as one trips — bounded memory and bounded work
- entry names are validated before anything is inflated
- only the seven parts the OOXML reader consumes are inflated at all; images,
  embedded fonts and themes are enumerated and skipped, which also stops a
  media-heavy paper being refused on size alone
- unknown compression methods, decode errors and truncated streams surface as
  corrupt-zip with fflate's message attached, and a part whose stream never
  finalised is not published at all (the loader then reports it) rather than
  parsing half a document
- chunk buffers are copied on receipt; the inflater may reuse them
- caps are injectable so the boundary can be tested with tiny payloads
P2 items from the takeover review, each with a failing-first case:

- rules/engine.ts: a rule that threw was dropped with `catch { continue }`, so
  a broken check looked like a clean report. runRules now takes an onFailure
  reporter; pipeline.ts and text-analysis.ts route it into parseWarnings, which
  already renders in 分析范围, the HTML report and the PDF. The paste-text path
  no longer hardcodes an empty warning list.
- docx/ooxml.ts: flagsEqual omitted `superscript`, so a superscript “[1]”
  coalesced into the preceding plain run and citations.ts lost its strongest
  signal.
- references/fields.ts: a four-digit group is only a year if it is plausible and
  not one end of a dash-joined span, checked against plausible years on both
  sides — so “GB/T 7714—2025” yields 2025 while “1901—2024 年…数据集” does not
  yield 1901.
- references/parse.ts + pipeline.ts: unlabeled paragraphs could be folded into
  one entry without limit, turning a list with lost auto-numbering into a giant
  entry plus bogus “missing reference” findings. Merging now caps at two
  continuations and records `entry-merge-suspect`; a detected section that
  yields no entries at all is disclosed. Neither case can pass silently again.
- public/sw.js: install used allSettled and then skipWaiting unconditionally, so
  a partially failed precache was committed while activate deleted the previous
  version’s complete cache — offline users got a shell whose chunks 404. The
  install now rejects unless every asset and the shell cached, leaving the old
  worker in charge.
- docx/unzip.ts: caps parameterised as plain numbers (UnpackCaps) so the
  boundary can be tested with kilobyte payloads instead of provoking a real OOM.
Documentation was describing a different build than the code:

- limitations.md still carried the v1.0.0 header, “12 rule families” and a
  blanket “field completeness not implemented”; it now states what is checked,
  what 有则必备 cannot be judged from text, the segmentation disclosures and the
  offline-PDF font condition
- README (en + zh): the offline claim no longer says report export “included” —
  the CJK font is deliberately not precached, so PDF needs one prior online
  export; the non-goals and the v1.3 roadmap now match what shipped in v1.1
- rule-coverage.md: RR-FMT-013/014 clause strings corrected to 8.11.2.1 表10 /
  8.11.3.1 表11 / 8.14.2 表14 / 8.15.1 表15; every RR-FMT row describes the
  behaviour as fixed; a V1.1.1 reachability-audit table records cause → fix; the
  test-status section now states that injected-field unit cases are necessary
  but not sufficient
- testing.md: V1.1.1 results recorded (239 unit, 14 passed / 4 skipped E2E,
  audit 0) plus the method note explaining why byte-level reachability exists
- verification.md: the element-table re-verification carried out for this
  release (表4/6/8/10/11/14/15 plus Annex A), failing-first evidence with the
  per-rule counts, the decompression-boundary method, the full gate table, and
  an explicit “not verified in this round” list
- real-docx-validation.md: a dated correction block instead of a rewrite — the
  v1.0.2 study’s “zero deterministic false positives” did not cover
  review-level findings, its cited-date expectation was superseded by the V1.1
  audit, and RR-FMT-011/017 silence was never evidence of correctness
- CHANGELOG: 1.1.1 entry describing all of the above; the 1.1.0 section’s
  “185 tests” corrected to the 184 the release commit measures, with its
  fix-preview verification claim marked as unsupported
package.json 1.1.0 → 1.1.1 and sw.js rr-v1.1.0 → rr-v1.1.1 so the update flow
actually ships the fixes to installed clients: an unchanged sw.js byte-for-byte
would leave installs on the broken build. Verified — a complete precache still
activates, the banner appears on the byte-diff, caches migrate to the new
version, the previous version's cache is then evicted, and analysis plus HTML
export work offline afterwards.

Nothing is tagged and no GitHub Release is created; the version is only merged
on the maintainer's decision.
scripts/pwa-update-offline-check.mjs installs the built worker, bumps only
dist/sw.js (gitignored build output, restored in finally), and asserts the ten
steps that matter for installed users: complete precache, shell cached, asset
list present, banner on the byte-diff, caches migrated, the previous version’s
cache evicted, offline analysis and offline HTML export after the update, and
zero page errors throughout. All ten pass on this branch; nothing under version
control is modified by running it.

testing.md and verification.md record it, and the “not verified” note is now
precise: the happy path is proven, the incomplete-precache refusal is not
forced by any test and rests on inspection.
@geyutu6755
geyutu6755 merged commit 28dc365 into main Nov 4, 2026
2 checks passed
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