Repository navigation
docs: retarget every dead :rfc:959 sub-section anchor (#944) - #946
Conversation
|
NEEDS CHANGES at Two corrections to its detail, neither weakening the finding. It attributed those four to #945/ Two further dead roles the sweep then turned up, same class, cited for the case rule: That last file already knew it: its docstring says "The citation is section 5.3 … not section 4.1 … #582 cited 4.1 and a cross-review caught it." So §4.1-for-the-case-rule was caught once before and the wrong-anchor habit outlived the correction. #947 is therefore not a new finding and I have said so there. Fixing in this PR rather than deferring, since its title promises exactly this: all thirteen roles retargeted, |
2f54638 to
05805fc
Compare
959#section-4.1 citations (#944)959 sub-section anchor (#944)
05805fc to
fbe2b2a
Compare
|
NEEDS CHANGES at 1. My lookbehind was not a literal-vs-role classifier. So an ordinary way of writing a code literal beside a citation defeated the check entirely — the "cannot come back unnoticed" claim was false for a second reason. Replaced with masking inline literal spans before matching. Now: Two residual gaps stated rather than claimed away: the 2. Other RFCs have the same defect. It asked the right question — which other cited RFCs lack sub-anchors: Out of #944's scope, so filed as #950 rather than folded in. Older RFCs are the pattern: 959 (1985), 1122 (1989), 719 (1976) all predate per-subsection anchors. It independently confirmed the 13-site inventory, the scan roots, the one-line Model substitution, reported rather than silent: this PR's round 1 ran on Fable, which found the five missing sites. Its round 2 was killed by a bare Also in this revision: the module docstring no longer quotes the maintainer, per his request on #918, and its "six sites" count is corrected to eleven. New revision |
|
One question before this merges, raised by the #950 ruling rather than by the review. On #950 the maintainer ruled that a dead anchor is only worth dropping if the anchored ref would generate no hyperlink, and that where it still links to the RFC itself the anchor stays as a notation to the reader. Measured through this repo's own Sphinx, a hyperlink is always generated — Under that principle none of this PR's thirteen retargets was strictly necessary — every one of the original citations already produced a working link to RFC 959. The #944 question was which replacement to use, which presupposed replacing; it was never whether to replace. So, explicitly:
My lean is (1), and this PR currently implements it. If (1), one consequence worth naming: Marking |
fbe2b2a to
a18532d
Compare
|
NEEDS CHANGES at
A live Fixed by forbidding a blank line inside a span and consuming backtick runs atomically: INLINE_LITERAL = re.compile(r'``(?:[^\n`]|`(?!`)|\n(?!\s*\n))+?``+')All 28 mis-spans gone, and the six role-vs-literal cases still classify correctly. It also falsified my own safety claim. The comment said both residual gaps "fail in the safe direction"; it demonstrated each producing a false negative — the direction I asserted was impossible. The comment now records that rather than the reassurance, and the remaining Three prose defects fixed alongside: a dangling New test, because the defect was invisible to every existing one: New revision |
a18532d to
8f39535
Compare
|
NEEDS CHANGES at The close was The orphaned tail pairs with the next literal downstream on the same line, blanking everything between. That needs a third literal to complete — a two-literal probe does not reproduce it, which is worth stating since it is why this survived three rounds. Fixed by closing on exactly two backticks: All five role-vs-literal cases still classify correctly, including this repo's own idiom where a literal quotes a role, and the tree still has zero blank-line spans. The round-3 invariant could not see this, exactly as the review says: it checks for Scope, so this is not overstated: Its other checks all held, several by independent re-derivation: the 28 mis-spans and the hidden New revision |
|
take (1) |
|
Ruling: take (1) — the retargeting stands. So all thirteen RFC 959 citations keep their corrected anchors, and #950's principle covers only the cases where no better anchor exists, RFC 719 being the clean one since it renders none at all. That settles the question posted above. Round 5's verdict was missing from this PR, which is my error — it went into a status report and never here. Recording it now, because a cross-review's verdict exists nowhere else. NEEDS CHANGES at Verified both myself. Neither invariant fires — nothing crosses a blank line, and the inner content holds no complete The review's substantive answer, which I asked for and agree with: a hand-rolled regex is the wrong instrument here. Five mechanisms in five rounds is not converging, and the pattern's own comment already concedes a permanent So the second question, which has not been asked here before:
My lean is (1): it removes the bug class rather than the bug, and it is smaller than what it replaces. I have not implemented it, so the branch still carries the masker.
|
8f39535 to
0d025d2
Compare
|
#947's ruling applied, now that keep-versus-revert is settled. Six of the seven sites I listed there move from The The seventh I deliberately left at
That is a claim about how §4.1's command list presents the kind letters, so §4 encloses the right prose and §5.3 does not mention them. I listed it among the seven when I put the question to you, so moving it would have followed your ruling literally while making the citation worse. Say the word if you want it at Also unchanged, as #947 said: the two Revision The design question above — bounded string-search, keep the documented-gap masker, or drop the guard — is still open and is the only thing holding this PR now. |
|
(1) as well |
|
Ruling: (1) as well — swap in the bounded design. So Recording the scale, since it is the argument for the change: the masker had to classify 23,869 inline-literal spans across 926 files correctly, and got it wrong five times in five rounds. The bounded check faces 2 occurrences tree-wide, both currently the documentation idiom: classified by
One thing I will carry over rather than drop: the honest statement of what the check does not prove. The denylist is not an existence oracle — it catches only anchors someone has already confirmed dead and added, and its absence from the table is not evidence a fragment is live. That limitation survives the redesign unchanged. |
0d025d2 to
7dd1e11
Compare
RFC 959's rendered HTML carries anchors only for its eight top-level sections, so **every** sub-numbered ``959`` fragment is a live link to nothing. Per the rulings on #944, #947 and #950, they now cite the section that supports the claim they carry. - ``#section-4.1`` -> ``#section-4`` at eleven sites, and ``#section-5.3`` -> ``#section-5`` at two more. #947 then moved six of the eleven on to ``#section-5``, the section that actually states FTP command case-insensitivity. ``registry-galoshes``... see below for the one left behind. - The ``ftp/command.py`` pair keeps ``#section-4``: it carries the command-*kind* claim, which §4 does support. ``registry-protocol.rst:337`` also keeps it -- that row claims the RFC *presents* the kind and conformance letters upper case, which is §4.1's command list rather than §5.3's case rule, so following #947 literally there would have made the citation worse. Flagged on the pull request rather than decided silently. - ``CHANGELOG.md`` regenerated with ``util/changelog_md.py``, one line, because ``docs/source/changelog/1.5.0.rst`` is one of its sources. - The three ``pcapkit/`` template/generated pairs stay byte-identical on the cited lines; each string is a literal in the ``vendor/`` template, so a regeneration reproduces the edit. No crawl was run. **The guard is rewritten, not patched again.** ``KNOWN_DEAD_ANCHORS`` and ``test_no_known_dead_anchor_citations`` in ``tests/project/test_rfc_anchor_fragments.py`` previously decided "is this text inside an inline literal" over the whole tree, and five rounds of review found five ways that was wrong -- a one-character lookbehind, a ``re.DOTALL`` span that crossed paragraph breaks and **hid a live** ``:rfc:`4303#section-2.1``` **role**, a greedy close that swallowed the next literal's opener, and a role's own backtick fusing with an adjacent literal. Each fix addressed the mechanism the previous postmortem had found and missed the next. Per the ruling on #946 that approach is replaced by a bounded one: - ``QUOTED_FORMS`` lists the exact ways this repository writes a citation in order to *name* the defect, and ``_is_documentation`` checks only the characters either side of one citation. The general classifier had to be right about **23,869** literal spans across **926** files; this asks about the **2** places a denylisted fragment actually appears. - ``INLINE_LITERAL``, ``_mask_literals`` and both masker invariants are removed -- they guarded machinery that no longer exists. - The opener must *begin a token*. Without that, ``` ``foo``:rfc:`...`` ``` read as documentation, because a literal's closing delimiter is indistinguishable from the idiom's opener by two characters alone -- the second of the five mechanisms reappearing in the new design, caught by measurement before it shipped. - ``test_the_documentation_exemption_holds_on_every_known_mechanism`` pins the whole table rather than the latest fix, so a sixth mechanism has to be added there to count as handled and a redesign has to keep every row passing. Its inputs are built from parts, since writing them literally trips the guard under test. What the check still does not prove is stated rather than implied: the denylist is not an existence oracle, it catches only anchors someone has already confirmed dead, and absence from the table is not evidence a fragment is live. tests/project: 208 passed, 1 skipped, 570 subtests. tests/protocols/application/test_ftp_unit.py + tests/const/test_const_enum_no_mint.py: 79 passed, 602 subtests.
RFC 959's rendered HTML carries anchors only for its eight top-level sections, so **every** sub-numbered ``959`` fragment is a live link to nothing. Per the rulings on #944, #947 and #950, they now cite the section that supports the claim they carry. - ``#section-4.1`` -> ``#section-4`` at eleven sites, and ``#section-5.3`` -> ``#section-5`` at two more. #947 then moved six of the eleven on to ``#section-5``, the section that actually states FTP command case-insensitivity. See below for the one left behind. - The ``ftp/command.py`` pair keeps ``#section-4``: it carries the command-*kind* claim, which §4 does support. ``registry-protocol.rst:337`` also keeps it -- that row claims the RFC *presents* the kind and conformance letters upper case, which is §4.1's command list rather than §5.3's case rule, so following #947 literally there would have made the citation worse. Flagged on the pull request rather than decided silently. - ``CHANGELOG.md`` regenerated with ``util/changelog_md.py``, one line, because ``docs/source/changelog/1.5.0.rst`` is one of its sources. - The three ``pcapkit/`` template/generated pairs stay byte-identical on the cited lines; each string is a literal in the ``vendor/`` template, so a regeneration reproduces the edit. No crawl was run. **The guard is rewritten, not patched again.** ``KNOWN_DEAD_ANCHORS`` and ``test_no_known_dead_anchor_citations`` in ``tests/project/test_rfc_anchor_fragments.py`` previously decided "is this text inside an inline literal" over the whole tree, and five rounds of review found five ways that was wrong -- a one-character lookbehind, a ``re.DOTALL`` span that crossed paragraph breaks and **hid a live** ``:rfc:`4303#section-2.1``` **role**, a greedy close that swallowed the next literal's opener, and a role's own backtick fusing with an adjacent literal. Each fix addressed the mechanism the previous postmortem had found and missed the next. Per the ruling on #946 that approach is replaced by a bounded one: - ``QUOTED_FORMS`` lists the exact ways this repository writes a citation in order to *name* the defect, and ``_is_documentation`` checks only the characters either side of one citation. The general classifier had to be right about **23,869** literal spans across **926** files; this asks about the **2** places a denylisted fragment actually appears. - ``INLINE_LITERAL``, ``_mask_literals`` and both masker invariants are removed -- they guarded machinery that no longer exists. - The opener must *begin a token*. Without that, ``` ``foo``:rfc:`...`` ``` read as documentation, because a literal's closing delimiter is indistinguishable from the idiom's opener by two characters alone -- the second of the five mechanisms reappearing in the new design, caught by measurement before it shipped. - ``test_the_documentation_exemption_holds_on_every_known_mechanism`` pins the whole table rather than the latest fix, so a sixth mechanism has to be added there to count as handled and a redesign has to keep every row passing. Its inputs are built from parts, since writing them literally trips the guard under test. What the check still does not prove is stated rather than implied: the denylist is not an existence oracle, it catches only anchors someone has already confirmed dead, and absence from the table is not evidence a fragment is live. tests/project: 208 passed, 1 skipped, 570 subtests. tests/protocols/application/test_ftp_unit.py + tests/const/test_const_enum_no_mint.py: 79 passed, 602 subtests.
71dcf69 to
95f11b3
Compare
RFC 959's rendered HTML carries anchors only for its eight top-level sections, so **every** sub-numbered ``959`` fragment is a live link to nothing. Per the rulings on #944, #947 and #950, they now cite the section that supports the claim they carry. - ``#section-4.1`` -> ``#section-4`` at eleven sites, and ``#section-5.3`` -> ``#section-5`` at two more. #947 then moved six of the eleven on to ``#section-5``, the section that actually states FTP command case-insensitivity. - Five keep ``#section-4``, deliberately: the four ``ftp/command.py`` sites carry the command-*kind* claim, which §4 does support, and ``registry-protocol.rst:337``'s row claims the RFC *presents* the kind and conformance letters upper case -- §4.1's command list rather than §5.3's comparison rule. Following #947 literally there would have made the citation worse, so it is flagged on the pull request rather than decided silently. - ``CHANGELOG.md`` regenerated with ``util/changelog_md.py``, one line, because ``docs/source/changelog/1.5.0.rst`` is one of its sources. - The three ``pcapkit/`` template/generated pairs stay byte-identical on the cited lines; each string is a literal in the ``vendor/`` template, so a regeneration reproduces the edit. No crawl was run. **The guard is rewritten rather than patched again, per the ruling on #946.** Deciding "is this text inside an inline literal" over the whole tree was tried and was wrong five times -- a one-character lookbehind, a ``re.DOTALL`` span that crossed paragraph breaks and **hid a live** ``:rfc:`4303#section-2.1``` **role**, a greedy close that swallowed the next literal's opener, and a role's own backtick fusing with an adjacent literal. Each fix addressed the mechanism the previous postmortem had found and missed the next. - ``QUOTED_FORMS`` lists the exact ways this repository writes a citation in order to *name* the defect, and ``_is_documentation`` looks only at the characters either side of one citation. The general classifier had to be right about 23,869 literal spans across 926 files; this asks about the **2** places a denylisted fragment actually appears. - ``INLINE_LITERAL``, ``_mask_literals`` and both masker invariants are gone. - **A sixth mechanism was found in the rewrite's own boundary check and fixed.** Allowing ``([{"'`` before the opener was wrong: those characters are equally valid as a literal's last *content* character, making the delimiter a **closer** and the role after it live. So ``` ``'base'``:rfc:`959#…`` ``` read as documentation -- and ``'base'`` is a real literal in ``pcapkit/const/ftp/command.py``. Measured: **764** literals across **188** files in the scanned roots end in one of those five characters. The boundary is now whitespace or start-of-file only, which RST's own grammar makes safe since an inline literal's end-string cannot follow whitespace. - The suffix is the real two-backtick close, not one character of it. - ``test_the_documentation_exemption_holds_on_every_known_mechanism`` pins the whole table, now fifteen rows, and was verified by mutation: dropping the suffix check, widening the boundary set back, and reverting to a one-character lookbehind are each caught. The first two were **not** caught by the table's first version, which is why the rows now use the full idiom suffix -- otherwise the suffix condition masked the boundary condition and a boundary regression passed. Inputs are built from ``%`` parts, since writing them literally trips the guard under test. What the check still does not prove is stated rather than implied: the denylist is not an existence oracle, it catches only anchors someone has already confirmed dead, and absence from the table is not evidence a fragment is live. A citation inside a ``::`` literal block remains a false positive, which fails loudly. tests/project: 208 passed, 1 skipped, 577 subtests, and 5/5 under python3.10. tests/protocols/application/test_ftp_unit.py + tests/const/test_const_enum_no_mint.py: 79 passed, 602 subtests.
|
NEEDS CHANGES at The boundary check allowed
That comment was the real defect. It had been measured only against Fixed: the boundary is whitespace or start-of-file only, which RST's grammar makes safe since an inline literal's end-string cannot follow whitespace. The suffix is now the real two-backtick close rather than one character of it. Its sharpest finding was about my test, not my code. The mechanism table missed two regressions — dropping the suffix check, and widening the boundary set back — so the All three now caught. The first attempt at those rows still missed M6 — the suffix condition masked the boundary condition, so the rows needed the full idiom suffix for the boundary to be the deciding factor. I only found that by re-running the matrix rather than trusting that adding rows was enough. It also confirmed, against a docutils oracle: every existing row's liveness claim, the three template/generated pairs byte-identical, the guard catching planted sites in all three roots, the citation arithmetic reconciling with the commit message, and New revision |
RFC 959's rendered HTML carries anchors only for its eight top-level sections, so **every** sub-numbered ``959`` fragment is a live link to nothing. Per the rulings on #944, #947 and #950, they now cite the section that supports the claim they carry. - ``#section-4.1`` -> ``#section-4`` at eleven sites, and ``#section-5.3`` -> ``#section-5`` at two more. #947 then moved six of the eleven on to ``#section-5``, the section that actually states FTP command case-insensitivity. - Five keep ``#section-4``, deliberately: the four ``ftp/command.py`` sites carry the command-*kind* claim, which §4 does support, and ``registry-protocol.rst:337``'s row claims the RFC *presents* the kind and conformance letters upper case -- §4.1's command list rather than §5.3's comparison rule. Following #947 literally there would have made the citation worse, so it is flagged on the pull request rather than decided silently. - ``CHANGELOG.md`` regenerated with ``util/changelog_md.py``, one line, because ``docs/source/changelog/1.5.0.rst`` is one of its sources. - The three ``pcapkit/`` template/generated pairs stay byte-identical on the cited lines; each string is a literal in the ``vendor/`` template, so a regeneration reproduces the edit. No crawl was run. **The guard is rewritten rather than patched again, per the ruling on #946.** Deciding "is this text inside an inline literal" over the whole tree was tried and was wrong five times -- a one-character lookbehind, a ``re.DOTALL`` span that crossed paragraph breaks and **hid a live** ``:rfc:`4303#section-2.1``` **role**, a greedy close that swallowed the next literal's opener, and a role's own backtick fusing with an adjacent literal. Each fix addressed the mechanism the previous postmortem had found and missed the next. - ``QUOTED_FORMS`` lists the exact ways this repository writes a citation in order to *name* the defect, and ``_is_documentation`` looks only at the characters either side of one citation. The general classifier had to be right about 23,869 literal spans across 926 files; this asks about the **2** places a denylisted fragment actually appears. - ``INLINE_LITERAL``, ``_mask_literals`` and both masker invariants are gone. - **A sixth mechanism was found in the rewrite's own boundary check and fixed.** Allowing ``([{"'`` before the opener was wrong: those characters are equally valid as a literal's last *content* character, making the delimiter a **closer** and the role after it live. So ``` ``'base'``:rfc:`959#…`` ``` read as documentation -- and ``'base'`` is a real literal in ``pcapkit/const/ftp/command.py``. Measured: **764** literals across **188** files in the scanned roots end in one of those five characters. The boundary is now whitespace or start-of-file only, which RST's own grammar makes safe: an inline literal's end-string carries ``(?<!\s)``, so ``` `` ``` after whitespace is always an opener -- and ``str.isspace`` agrees with ``re``'s ``\s`` on every whitespace character tested. - The suffix is the real two-backtick close, not one character of it. - ``test_the_documentation_exemption_holds_on_every_known_mechanism`` pins the whole table, seventeen rows, and is verified by mutation rather than by inspection: dropping the suffix check, widening the boundary set back, reverting to a one-character lookbehind, and **narrowing** ``isspace()`` to a literal space are each caught. Three of those four escaped an earlier version of the table -- the rows needed the full idiom suffix before a boundary regression could fail, and no row exercised a non-space whitespace character. Inputs are built from ``%`` parts, since writing them literally trips the guard under test. What the check still does not prove is stated rather than implied: the denylist is not an existence oracle, it catches only anchors someone has already confirmed dead, and absence from the table is not evidence a fragment is live. A citation inside a ``::`` literal block remains a false positive, which fails loudly. tests/project: 208 passed, 1 skipped, 579 subtests, and 5/5 under python3.10. tests/protocols/application/test_ftp_unit.py + tests/const/test_const_enum_no_mint.py: 79 passed, 602 subtests.
95f11b3 to
8df0fee
Compare
|
GOOD TO GO at The first clean verdict on this PR after seven rounds. No seventh mechanism. It built a docutils oracle — a local It also verified the whitespace-safety claim rather than accepting it: docutils' Its one non-blocking finding, verified and acted on. Narrowing Two rows added — the idiom preceded by a tab, and by a newline — which is what a reader hits writing it after a line wrap or first on an indented line. Full matrix now: dropping the suffix check, widening the boundary back, the one-character lookbehind, and the space-narrowing are all caught. Three of those four escaped an earlier version of the table. That is the whole delta from Also confirmed by the review: the citation arithmetic reconciles (6+5=11 from
Unpublished and yours to merge. |
…view The by-module restructure rewrote ``docs/source/changelog/1.5.0.rst`` wholesale, which conflicted with #946's one-word anchor fix on ``main``. Resolved by taking the restructured file and applying that fix to it (``959#section-5.3`` -> ``959#section-5``), then regenerating ``CHANGELOG.md``. Merging ``main`` brings #948's tests, which the restructure makes **vacuous** rather than merely stale, so they are rewritten rather than renumbered: * ``test_the_page_describes_the_changelog_kind_runs_as_they_are`` measured the inline kind labels the restructure removed: its ``findall`` matched nothing, ``runs`` was empty, and both ``assertGreater`` floors failed before the prose needle. Replaced by ``..._grouping_as_it_is``, which counts the ``-`` and ``~`` underlined sections, requires the kinds nested inside the modules, requires no inline label anywhere, and requires the section count in the page's prose. * ``test_the_changelog_file_is_not_shaped_one_entry_per_commit`` and ``test_the_page_pins_its_own_measured_numbers`` both keyed on ``^\* \*\*Kind\*\*``; an entry is now a column-zero bullet. * ``process.rst`` described the file as "neither shape", with figures for a layout that no longer exists. It now describes what shipped: 9 module-level sections holding 155 entries, no inline kind labels. Cross-review findings on ``a84020c3a``, both confirmed before fixing: * The stated reason for retargeting ``test_a_sub_heading_underline_joined_into_the_prose_is_fatal`` was backwards, in the commit message and in the test's own comment. ``_reject`` is built on ``assertRaises``, so the old ``-`` input converting cleanly made that test **fail**; the retarget was required, not a tidy-up. Comment corrected. * ``tests/project/test_isort_clean.py`` cited ``1.5.0.rst`` lines 691 and 1169 -- already stale by 2 and 18 before this work, and off by ~2700 after the reshuffle. Line numbers dropped; the file is cited alone. * Added ``test_an_over_long_sub_heading_underline_is_fatal``: ``_RESIDUAL``'s comment now claims ``=``, ``-`` and ``~`` all need to stay in its alternation, and only ``=`` was pinned. ``pytest tests/project``: 225 passed, 1 skipped, 660 subtests, 0 failed; ``unittest`` agrees at 226 tests OK. Changelog drift gate exit 0.
make pylint,make mypy,make isort)make testpasses, and a test case covers the changedocs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — changelog centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Closes #944.
RFC 959's rendered HTML carries only
section-1throughsection-8, so the six:rfc:959#section-4.1citations named an anchor that does not exist — a live link to nothing. Per the ruling on #944 (*"Sure `section-4` is good."*) all six now cite `:rfc:`959#section-4.Three template/generated pairs, each string a literal in the
vendor/template, so both halves are hand-edited; I verified the cited lines end up byte-identical across all three pairs. No crawl, no regeneration.A finding this change deliberately does not act on. One of the six is different in kind, and the ruling was given before it was known.
pcapkit/{const,vendor}/http/method.pysays "case-insensitive because :rfc:959#section-4says FTP command codes are not" — but §4 FILE TRANSFER FUNCTIONS contains no case-sensitivity language at all. The statement lives in §5.3 COMMANDS: "The command codes are four or fewer alphabetic characters. Upper and lower case alphabetic characters are to be treated identically." That is the only such sentence in the whole document, and it sits betweenid="section-5"andid="section-6", so it is outside §4. The twoftp/command.pypairs are fine — §4 does contain §4.1 FTP COMMANDS, which supports "type of kind of command".So this PR fixes the dead anchor at all six sites as ruled, and leaves that one site citing a section that does not say what the docstring says it says. Raised separately rather than widened into here, since picking
#section-5for it is a second editorial call.Test.
#943's shape invariant acceptssection-4.1as well-formed and by construction cannot see a dead anchor, so this adds a narrower second check: aKNOWN_DEAD_ANCHORSdenylist andtest_no_known_dead_anchor_citations. Offline by design — CI has no network — and the docstring states plainly that a denylist proves nothing about a fragment it has never heard of.tests/project: 207 passed, 1 skipped, 562 subtests. Without the fix the new test fails withDeadAnchorFinding(path='pcapkit/vendor/ftp/command.py', rfc=959, fragment='section-4.1').