Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/navigation-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ jobs:
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: '3.11.16'
- name: Resolve every link from the containing document and check every file-sourced quotation, in both interpreter modes
- name: Resolve every link from the containing document and authenticate every navigation quotation, in both interpreter modes
env:
PYTHONDONTWRITEBYTECODE: '1'
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
python -B -S tools/check_doc_links.py PROOF_INDEX.md docs/integration/*.md
python -B -O -S tools/check_doc_links.py PROOF_INDEX.md docs/integration/*.md
python -B -S tools/check_doc_quotes.py PROOF_INDEX.md docs/integration/2026-09-29-navigation-refresh.md
python -B -O -S tools/check_doc_quotes.py PROOF_INDEX.md docs/integration/2026-09-29-navigation-refresh.md
python -B -S tools/check_doc_quotes.py --github --require-online PROOF_INDEX.md docs/integration/2026-09-29-navigation-refresh.md
python -B -O -S tools/check_doc_quotes.py --github --require-online PROOF_INDEX.md docs/integration/2026-09-29-navigation-refresh.md
python -B -S -m unittest -v tests.test_navigation_links tests.test_navigation_quotes
python -B -O -S -m unittest -v tests.test_navigation_links tests.test_navigation_quotes
git diff --exit-code
115 changes: 115 additions & 0 deletions tests/test_navigation_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ def test_github_link_is_unchecked_offline_not_failed(self):
self.assertEqual(report['failures'], [])
self.assertEqual((report['checked'], report['unchecked_offline']), (0, 1))


def test_require_online_rejects_unchecked_github_link(self):
doc = self.root / 'docs' / 'nested' / 'a.md'
doc.write_text(HEADER + '| #1 | [c](https://github.com/o/r/pull/1#issuecomment-2) — “anything at all” |\n', encoding='utf-8')
rc = check_doc_quotes.main([str(doc), '--root', self.tmp, '--require-online'])
self.assertEqual(rc, 1)

def test_github_transport_failure_is_not_counted_as_offline(self):
doc = self.root / 'docs' / 'nested' / 'a.md'
doc.write_text(HEADER + '| #1 | [c](https://github.com/o/r/pull/1#issuecomment-2) — “anything at all” |\n', encoding='utf-8')
original = check_doc_quotes.Sources._fetch
try:
check_doc_quotes.Sources._fetch = lambda self, match: (None, 'unreachable')
report = check_doc_quotes.check_document(str(doc), self.tmp, github=True, token='x')
finally:
check_doc_quotes.Sources._fetch = original
self.assertEqual(report['unchecked_offline'], 0)
self.assertEqual([f[0] for f in report['failures']], ['source-unreachable'])

def test_nearest_preceding_link_governs_each_passage(self):
(self.root / 'reviews' / 's.md').write_text('Disposition: AMEND the ledger.\n', encoding='utf-8')
row = ('| #1 | [r](../../reviews/r.md) — “No defect was found.”'
Expand All @@ -103,5 +122,101 @@ def test_header_row_quotation_marks_are_ignored(self):
self.assertEqual((report['quotes'], report['summaries']), (0, 1))


class VerdictWordStart(unittest.TestCase):
"""Vocabulary membership only: not negation, suffix equality or entailment."""

def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix='verdict-boundary-')
self.addCleanup(self.tmp.cleanup)
self.root = pathlib.Path(self.tmp.name)
self.source = self.root / 'record.md'
self.doc = self.root / 'navigation.md'

def _report(self, source, passage):
self.source.write_text(source, encoding='utf-8')
self.doc.write_text(HEADER + '| #1 | [r](record.md) — summary: ' + passage + '|\n',
encoding='utf-8')
return check_doc_quotes.check_document(str(self.doc), str(self.root))

def test_prefixed_source_words_do_not_supply_verdict_roots(self):
for word in check_doc_quotes.VERDICT:
for prefix in ('UN', 'NON_', 'x', '_', '0', 'é'):
with self.subTest(word=word, prefix=prefix):
report = self._report(prefix + word, word)
self.assertEqual(report['failures'],
[['verdict-word-not-in-source', 'record.md', word + ': ' + word]])
self.assertEqual((report['checked'], report['unchecked_offline']), (1, 0))

def test_scoped_summary_cannot_use_a_prefixed_source_word(self):
report = self._report('UNVERIFIED_SCOPED', 'VERIFIED_SCOPED')
self.assertEqual(report['failures'],
[['verdict-word-not-in-source', 'record.md', 'VERIFIED: VERIFIED_SCOPED']])

def test_existing_word_start_families_and_punctuation_remain_supported(self):
for word, family in zip(check_doc_quotes.VERDICT,
('ACCEPTED', 'AMEND_REQUIRED', 'HOLD_PENDING', 'CONFIRMED_SCOPED',
'VERIFIED_SCOPED', 'REJECTED')):
for source in (word, family, '**' + word + '**.', '`' + family + '`',
'Disposition:\n(' + family + ');'):
with self.subTest(word=word, source=source):
self.assertEqual(self._report(source, word)['failures'], [])
self.assertEqual(self._report(source, family)['failures'], [])

def test_missing_roots_and_multiple_failures_keep_exact_diagnostics(self):
for word in check_doc_quotes.VERDICT:
with self.subTest(word=word):
self.assertEqual(self._report('No disposition.', word)['failures'],
[['verdict-word-not-in-source', 'record.md', word + ': ' + word]])
passage = 'ACCEPT HOLD VERIFIED'
self.assertEqual(self._report('ACCEPT UNVERIFIED', passage)['failures'],
[['verdict-word-not-in-source', 'record.md', 'HOLD: ' + passage],
['verdict-word-not-in-source', 'record.md', 'VERIFIED: ' + passage]])

def test_embedded_summary_words_are_not_new_verdict_claims(self):
# Preserve the existing left-boundary policy on the summary side.
for word in check_doc_quotes.VERDICT:
with self.subTest(word=word):
self.assertEqual(self._report('No disposition.', 'UN' + word)['failures'], [])

def test_case_sensitivity_and_semantic_limits_are_explicit(self):
self.assertEqual(self._report('verified', 'VERIFIED')['failures'],
[['verdict-word-not-in-source', 'record.md', 'VERIFIED: VERIFIED']])
self.assertEqual(self._report('No disposition.', 'verified')['failures'], [])
# Presence does not establish entailment or equality of scoped verdicts.
self.assertEqual(self._report('NOT VERIFIED', 'VERIFIED')['failures'], [])
self.assertEqual(self._report('VERIFIED_A', 'VERIFIED_B')['failures'], [])

def test_other_source_segments_cannot_supply_the_missing_verdict(self):
self.source.write_text('UNVERIFIED', encoding='utf-8')
(self.root / 'other.md').write_text('VERIFIED', encoding='utf-8')
self.doc.write_text(HEADER + '| #1 | [r](record.md) summary: VERIFIED'
'<br>[s](other.md) summary: VERIFIED |\n', encoding='utf-8')
report = check_doc_quotes.check_document(str(self.doc), str(self.root))
self.assertEqual(report['failures'],
[['verdict-word-not-in-source', 'record.md', 'VERIFIED: VERIFIED']])
self.assertEqual((report['checked'], report['summaries']), (2, 2))

def test_cli_reports_boundary_failure_and_genuine_success(self):
import json
import subprocess
flags = ['-B', '-S'] if not sys.flags.optimize else ['-B', '-O', '-S']
for source, expected_exit in (('UNVERIFIED', 1), ('VERIFIED_SCOPED', 0)):
with self.subTest(source=source):
self._report(source, 'VERIFIED')
process = subprocess.run(
[sys.executable, *flags, str(ROOT / 'tools/check_doc_quotes.py'),
str(self.doc), '--root', str(self.root), '--require-online'],
capture_output=True, text=True, timeout=15)
self.assertEqual(process.returncode, expected_exit, process.stderr)
self.assertEqual(process.stderr, '')
report = json.loads(process.stdout)
self.assertIs(report['passed'], expected_exit == 0)
result = report['documents'][str(self.doc)]
self.assertEqual((result['checked'], result['unchecked_offline']), (1, 0))
expected = [] if expected_exit == 0 else [
['verdict-word-not-in-source', 'record.md', 'VERIFIED: VERIFIED']]
self.assertEqual(result['failures'], expected)


if __name__ == '__main__':
unittest.main()
13 changes: 10 additions & 3 deletions tools/check_doc_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
``--github`` (token read from ``GITHUB_TOKEN``); without it the passage is reported as
``unchecked_offline`` and does not fail the check;
* a ``summary:`` passage must not use a verdict word (ACCEPT, AMEND, HOLD, CONFIRMED,
VERIFIED, REJECT) that the linked record does not use, under the same offline rule;
VERIFIED, REJECT) that the linked record does not use, under the same offline rule.
Both sides use the existing case-sensitive word-start rule: ACCEPTED and
AMEND_REQUIRED retain their root, but UNVERIFIED does not supply VERIFIED.
Root presence does not check negation, scoped-suffix equality or entailment;
* a passage with no preceding link in its segment fails (``no-source-link``).

Header and separator rows are skipped. Exit status 0 only when no checked passage fails;
Expand Down Expand Up @@ -177,7 +180,7 @@ def check_document(doc_path, root, github=False, token=None):
report['failures'].append(['not-verbatim', source, prefix])
else:
for word in VERDICT:
if re.search(r'\b' + word, passage) and word not in body:
if re.search(r'\b' + word, passage) and not re.search(r'\b' + word, body):
report['failures'].append(['verdict-word-not-in-source', source, word + ': ' + prefix])
return report

Expand All @@ -187,14 +190,18 @@ def main(argv=None):
parser.add_argument('documents', nargs='+')
parser.add_argument('--root', default=os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
parser.add_argument('--github', action='store_true', help='also check GitHub review/comment links (GITHUB_TOKEN)')
parser.add_argument('--require-online', action='store_true', help='fail if any linked GitHub/HTTP source is left unchecked offline')
args = parser.parse_args(argv)
token = os.environ.get('GITHUB_TOKEN')
report = {'root': os.path.realpath(args.root), 'github': args.github, 'documents': {}, 'passed': True}
report = {'root': os.path.realpath(args.root), 'github': args.github, 'require_online': args.require_online, 'documents': {}, 'passed': True}
for doc in args.documents:
result = check_document(doc, args.root, args.github, token)
report['documents'][doc] = result
if result['failures']:
report['passed'] = False
if args.require_online and result['unchecked_offline']:
result['failures'].append(['unchecked-offline-required', '', str(result['unchecked_offline'])])
report['passed'] = False
print(json.dumps(report, indent=2, sort_keys=True))
return 0 if report['passed'] else 1

Expand Down
Loading