Skip to content
Open
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
17 changes: 17 additions & 0 deletions scripts/lint_frontmatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
REQUIRED_FIELDS = ["title", "abbrev", "docname", "version", "category", "ipr", "submissiontype", "consensus", "author"]
AUTHOR_FIELD_ORDER = ["name", "ins", "email", "org"]

EXPECTED_IPR = "noModificationTrust200902"
ID_HTTPAUTH_PAYMENT_TITLE = "The 'Payment' HTTP Authentication Scheme"
ID_HTTPAUTH_PAYMENT_TARGET = "https://datatracker.ietf.org/doc/draft-ryan-httpauth-payment/"


def lint_file(path: Path) -> list[str]:
Expand All @@ -23,6 +25,17 @@ def lint_file(path: Path) -> list[str]:
if field not in meta:
errors.append(f"missing required field '{field}'")

# Check IPR value
ipr = meta.get("ipr", "")
if ipr and ipr != EXPECTED_IPR:
errors.append(f"ipr should be '{EXPECTED_IPR}', got '{ipr}'")

# Check consensus/submissiontype compatibility
submissiontype = meta.get("submissiontype", "")
consensus = meta.get("consensus")
if submissiontype == "independent" and consensus is True:
errors.append("consensus must be false for independent submissions")

# Check version format (two-digit string like "00", "01", … or int 0)
version = meta.get("version")
if not (isinstance(version, int) and 0 <= version <= 99) and not (
Expand All @@ -45,6 +58,8 @@ def lint_file(path: Path) -> list[str]:
actual = [k for k in keys if k in AUTHOR_FIELD_ORDER]
if actual != expected:
errors.append(f"author[{i}] field order should be {expected}, got {actual}")
if "organization" in author:
errors.append(f"author[{i}] uses 'organization' instead of 'org'; use 'org' for consistency")

# Check I-D.httpauth-payment reference format (if present)
normative = meta.get("normative", {})
Expand All @@ -53,6 +68,8 @@ def lint_file(path: Path) -> list[str]:
if ref:
if "target" not in ref:
errors.append("I-D.httpauth-payment missing 'target' field")
elif ref["target"] != ID_HTTPAUTH_PAYMENT_TARGET:
errors.append(f"I-D.httpauth-payment target should be '{ID_HTTPAUTH_PAYMENT_TARGET}', got '{ref['target']}'")
title = ref.get("title", "")
# Normalize title by stripping outer quotes for comparison
normalized_title = title.strip('"').strip("'")
Expand Down
188 changes: 174 additions & 14 deletions scripts/test_lint_frontmatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_all_required_fields_present(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -47,7 +47,7 @@ def test_missing_required_field(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
---
Expand All @@ -65,7 +65,7 @@ def test_version_string_00(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -86,7 +86,7 @@ def test_version_int_0(self, tmp_path):
docname: draft-test-00
version: 0
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -107,7 +107,7 @@ def test_version_invalid(self, tmp_path):
docname: draft-test-00
version: 1
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -130,7 +130,7 @@ def test_title_capitalized(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -151,7 +151,7 @@ def test_title_lowercase_start(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -174,7 +174,7 @@ def test_correct_order(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -195,7 +195,7 @@ def test_wrong_order(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -210,6 +210,138 @@ def test_wrong_order(self, tmp_path):
assert any("field order" in e for e in errors)


class TestIPR:
def test_correct_ipr(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
- name: Test Author
ins: T. Author
email: test@example.com
org: Test Org
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert not any("ipr" in e for e in errors)

def test_wrong_ipr(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
submissiontype: IETF
consensus: true
author:
- name: Test Author
ins: T. Author
email: test@example.com
org: Test Org
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert any("ipr should be 'noModificationTrust200902'" in e for e in errors)


class TestConsensusSubmissiontype:
def test_independent_with_consensus_false(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: noModificationTrust200902
submissiontype: independent
consensus: false
author:
- name: Test Author
ins: T. Author
email: test@example.com
org: Test Org
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert not any("consensus" in e for e in errors)

def test_independent_with_consensus_true(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: noModificationTrust200902
submissiontype: independent
consensus: true
author:
- name: Test Author
ins: T. Author
email: test@example.com
org: Test Org
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert any("consensus must be false for independent" in e for e in errors)


class TestAuthorOrganization:
def test_org_is_correct(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
- name: Test Author
ins: T. Author
email: test@example.com
org: Test Org
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert not any("organization" in e for e in errors)

def test_organization_flagged(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
- name: Test Author
ins: T. Author
email: test@example.com
organization: Test Org
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert any("'organization' instead of 'org'" in e for e in errors)


class TestIDReference:
def test_valid_id_reference(self, tmp_path):
content = """---
Expand All @@ -218,7 +350,7 @@ def test_valid_id_reference(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -229,7 +361,7 @@ def test_valid_id_reference(self, tmp_path):
normative:
I-D.httpauth-payment:
title: "The 'Payment' HTTP Authentication Scheme"
target: https://datatracker.ietf.org/doc/draft-httpauth-payment/
target: https://datatracker.ietf.org/doc/draft-ryan-httpauth-payment/
author:
- name: Jake Moxey
date: 2026-01
Expand All @@ -246,7 +378,7 @@ def test_missing_target(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -273,7 +405,7 @@ def test_wrong_author_format(self, tmp_path):
docname: draft-test-00
version: "00"
category: info
ipr: trust200902
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
Expand All @@ -284,7 +416,7 @@ def test_wrong_author_format(self, tmp_path):
normative:
I-D.httpauth-payment:
title: "The 'Payment' HTTP Authentication Scheme"
target: https://datatracker.ietf.org/doc/draft-httpauth-payment/
target: https://datatracker.ietf.org/doc/draft-ryan-httpauth-payment/
author:
- ins: J. Moxey
date: 2026-01
Expand All @@ -293,3 +425,31 @@ def test_wrong_author_format(self, tmp_path):
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert any("should use 'name'" in e for e in errors)

def test_wrong_target_url(self, tmp_path):
content = """---
title: Test Spec
abbrev: Test
docname: draft-test-00
version: "00"
category: info
ipr: noModificationTrust200902
submissiontype: IETF
consensus: true
author:
- name: Test Author
ins: T. Author
email: test@example.com
org: Test Org
normative:
I-D.httpauth-payment:
title: "The 'Payment' HTTP Authentication Scheme"
target: https://datatracker.ietf.org/doc/draft-ietf-httpauth-payment/
author:
- name: Jake Moxey
date: 2026-01
---
"""
spec = write_spec(tmp_path, content)
errors = lint_file(spec)
assert any("target should be" in e for e in errors)
Loading