Preserve original ordered-list numbering during sanitization
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a192e9-c28b-7533-bf25-e2341364c22a
This commit is contained in:
parent
80fe387dcc
commit
a9526e7fb3
3 changed files with 68 additions and 1 deletions
|
|
@ -52,6 +52,7 @@ _ALLOWED_ATTRS = {
|
|||
"img": ["src", "alt", "title"],
|
||||
"a": ["href", "title", "rel"],
|
||||
"code": ["class"],
|
||||
"ol": ["start"],
|
||||
"th": ["align"],
|
||||
"td": ["align"],
|
||||
}
|
||||
|
|
@ -100,7 +101,8 @@ def render_markdown(text: str) -> str:
|
|||
"""Render the supported Markdown subset.
|
||||
|
||||
Extensions are fenced code, tables, newline-to-break and sane lists.
|
||||
Raw HTML, script, event handlers and non-http(s)/mailto URLs are removed.
|
||||
Ordered-list starting numbers are preserved. Unsupported HTML, scripts,
|
||||
event handlers and non-http(s)/mailto URLs are removed.
|
||||
Relative asset URLs are kept so copied page images still resolve. This is
|
||||
not acceptance of a private corpus or of arbitrary Bubble markup.
|
||||
"""
|
||||
|
|
|
|||
42
docs/release/ordered-list-fidelity-2026-10-31.json
Normal file
42
docs/release/ordered-list-fidelity-2026-10-31.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"schema": "coulomb.ordered-list-fidelity.v1",
|
||||
"verified_at": "2026-10-31T17:20:32.943192+00:00",
|
||||
"source_articles_sha256": "f33967c79c0f8a60a7d504f5d5e4082f8bc9e3c34f6a3160cb200fd380831d62",
|
||||
"prior_audit_sha256": "3926e24a3b903f5e1d1c7133dc5f9df1e958183432c5131949ce838eb60acf1e",
|
||||
"current_audit_sha256": "d8f382e24d7adfe3c43dc71f32f8f6e647bf68239cbcdc6769a6c8fb85d666b9",
|
||||
"records": 245,
|
||||
"rendered_cards_verified": 242,
|
||||
"source_bodies_unchanged": true,
|
||||
"missing_bodies_retained": 3,
|
||||
"ordered_list_starts_restored": 15,
|
||||
"affected_records": 4,
|
||||
"markup_change_cases_before": 6,
|
||||
"markup_change_cases_after": 3,
|
||||
"remaining_removed_tags": {
|
||||
"topic": 3
|
||||
},
|
||||
"remaining_removed_attributes": [
|
||||
{
|
||||
"tag": "topic",
|
||||
"attribute": "name",
|
||||
"count": 2
|
||||
}
|
||||
],
|
||||
"private_review_directory": "~/.config/coulomb-social/auth/exports/rendering-review-20261031-numbering",
|
||||
"source_hashes": {
|
||||
"coulomb_social/apps/spaces/content.py": "5d6bb5c800b3e776fff385fa73284f4916403676dc2a8bc06b8fadc7d2387633",
|
||||
"tests/test_rendering_audit.py": "ae092753f4d1d006bb88f1ce11581975c105c5231819f9409ddaaf2fc83d32f5"
|
||||
},
|
||||
"validation": {
|
||||
"focused_passed": 14,
|
||||
"full_gate_passed": 650,
|
||||
"full_gate_warnings": 69,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0,
|
||||
"full_gate_seconds": 91.85
|
||||
},
|
||||
"production_deployed": false,
|
||||
"original_bubble_render_compared": false,
|
||||
"productive_fidelity_accepted": false
|
||||
}
|
||||
|
|
@ -52,6 +52,29 @@ def test_serialized_line_break_difference_is_not_reported_as_lost_markup():
|
|||
assert not audit["visible_text_changed_by_sanitization"]
|
||||
|
||||
|
||||
def test_continued_numbered_lists_keep_their_starts_through_sanitization():
|
||||
body = "2. Second item\n3. Third item\n\nSeparate paragraph.\n\n8. Eighth item"
|
||||
audit, rendered = audit_body(body)
|
||||
assert '<ol start="2">' in rendered
|
||||
assert '<ol start="8">' in rendered
|
||||
assert not audit["markup_changed_by_sanitization"]
|
||||
assert not audit["visible_text_changed_by_sanitization"]
|
||||
assert not audit["productive_fidelity_accepted"]
|
||||
|
||||
|
||||
def test_list_start_permission_does_not_allow_event_handlers_or_custom_markup():
|
||||
body = ('<ol start="4" onclick="synthetic()"><li>Fourth item</li></ol>'
|
||||
'<topic name="synthetic">Placeholder</topic>'
|
||||
'<a href="javascript:synthetic()">Link</a>')
|
||||
audit, rendered = audit_body(body)
|
||||
assert '<ol start="4">' in rendered
|
||||
assert 'onclick' not in rendered and 'javascript:' not in rendered
|
||||
assert '<topic' not in rendered and 'Placeholder' in rendered
|
||||
assert {a["attribute"] for a in audit["removed_attributes"]} == {
|
||||
"onclick", "name", "href",
|
||||
}
|
||||
|
||||
|
||||
def test_review_card_escapes_source_and_blocks_resources_and_interaction():
|
||||
body = '<img src="https://example.invalid/synthetic.png"><script>synthetic()</script>'
|
||||
audit, rendered = audit_body(body)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue