#!/usr/bin/env python3 """Unit and policy-matrix coverage for workspace descriptor documentation regions.""" import unittest from dataclasses import dataclass try: from workspace_descriptor_doc_contract import ( ContractError, _render_active_markdown, check_document_text, check_project_state_text, scan_active_markdown, ) except ModuleNotFoundError: # Support `python -m unittest scripts.test_...` from repo root. from scripts.workspace_descriptor_doc_contract import ( ContractError, _render_active_markdown, check_document_text, check_project_state_text, scan_active_markdown, ) WORKSPACE_START = "" WORKSPACE_END = "" NON_WORKSPACE_START = "" NON_WORKSPACE_END = "" @dataclass(frozen=True) class PolicyCase: name: str expected: str content: str CASES = [ PolicyCase('m_schema_v1', 'R', 'Schema v1 descriptors return `migration_required`.'), PolicyCase('m_schema_dash_v2', 'R', 'Schema-v2 descriptors report `migration_required`.'), PolicyCase('m_schema_num1', 'R', 'Schema 1 descriptors remain `migration_required`.'), PolicyCase('m_schema_word1', 'R', 'Schema version 1 descriptors remain `migration_required`.'), PolicyCase('m_schema_under_colon', 'R', 'A descriptor with schema_version: 1 returns `migration_required`.'), PolicyCase('m_schema_under_eq', 'R', 'A descriptor with schema_version=2 returns `migration_required`.'), PolicyCase('m_schema_v1_and_v2', 'R', 'Schema v1 and v2 descriptors remain `migration_required`.'), PolicyCase('m_bare_and', 'R', 'v1 and v2 descriptors remain `migration_required`.'), PolicyCase('m_bare_or', 'R', 'v1 or v2 descriptors remain `migration_required`.'), PolicyCase('m_bare_slash', 'R', 'v1/v2 descriptors remain `migration_required`.'), PolicyCase('a_they', 'R', 'Schema v1 descriptors are rejected. They return `migration_required`.'), PolicyCase('a_these_desc', 'R', 'Schema v1 descriptors are rejected. These descriptors return `migration_required`.'), PolicyCase('a_those_desc', 'R', 'Schema v1 descriptors are rejected. Those descriptors return `migration_required`.'), PolicyCase('a_such_desc', 'R', 'Schema v1 descriptors are rejected. Such descriptors return `migration_required`.'), PolicyCase('a_their', 'R', 'Schema v1 descriptors are rejected. Their validation returns `migration_required`.'), PolicyCase('a_the_desc', 'R', 'Schema v1 descriptors are rejected. The descriptors return `migration_required`.'), PolicyCase('a_the_candidate', 'R', 'Schema v1 candidates are rejected. The candidate returns `migration_required`.'), PolicyCase('a_still', 'R', 'Schema v1 descriptors are rejected, but still return `migration_required`.'), PolicyCase('a_it', 'R', 'Schema v1 descriptor is rejected. It still returns `migration_required`.'), PolicyCase('a_this_desc', 'R', 'Schema v1 descriptor is rejected. This descriptor still returns `migration_required`.'), PolicyCase('a_that_desc', 'R', 'Schema v1 descriptor is rejected. That descriptor still returns `migration_required`.'), PolicyCase('a_such_candidate', 'R', 'Schema v1 candidate is rejected. Such a candidate still returns `migration_required`.'), PolicyCase('d_workspace', 'R', 'Workspace descriptors remain `migration_required`.'), PolicyCase('d_possessive', 'R', "Workspace descriptors' status is `migration_required`."), PolicyCase('d_validation', 'R', 'Workspace descriptor validation returns `migration_required`.'), PolicyCase('d_legacy', 'R', 'Legacy descriptors remain `migration_required`.'), PolicyCase('d_bare_descriptor', 'R', 'The descriptor remains `migration_required`.'), PolicyCase('r_workspace', 'R', '`migration_required` is returned by workspace descriptors.'), PolicyCase('r_workspace_status', 'R', '`migration_required` is the status for workspace descriptors.'), PolicyCase('r_legacy', 'R', '`migration_required` applies to legacy descriptors.'), PolicyCase('r_bare_descriptor', 'R', '`migration_required` is the status for the descriptor.'), PolicyCase('u_standalone', 'A', 'The unrelated session database may report `migration_required`.'), PolicyCase('u_after_period', 'A', 'Schema v1 descriptors are rejected. The unrelated session database may report `migration_required`.'), PolicyCase('u_while', 'A', 'Schema v1 descriptors are rejected while the unrelated session database may report `migration_required`.'), PolicyCase('u_and_the', 'A', 'Schema v1 descriptors are rejected and the unrelated session database may report `migration_required`.'), PolicyCase('u_and_a', 'A', 'Schema v1 descriptors are rejected and a session database upgrade may report `migration_required`.'), PolicyCase('u_and_this', 'A', 'Schema v1 descriptors are rejected and this unrelated session database may report `migration_required`.'), PolicyCase('u_and_these', 'A', 'Schema v1 descriptors are rejected and these unrelated session database upgrades may report `migration_required`.'), PolicyCase('u_and_our', 'A', 'Schema v1 descriptors are rejected and our unrelated session database may report `migration_required`.'), PolicyCase('u_and_bare_subject', 'A', 'Schema v1 descriptors are rejected and session database upgrades may report `migration_required`.'), PolicyCase('u_prev_then_these', 'A', 'Schema v1 descriptors are rejected. These unrelated session database upgrades report `migration_required`.'), PolicyCase('u_api_versions', 'A', 'The unrelated session API v1 and v2 may report `migration_required`.'), PolicyCase('u_db_schema_version', 'A', 'The unrelated session database schema version 1 may report `migration_required`.'), PolicyCase('u_reverse_negative', 'A', '`migration_required` is not returned by workspace descriptors; it belongs to the session database.'), PolicyCase('s_accept_although', 'A', 'Although schema v1 descriptors are rejected, the unrelated session database may report `migration_required`.'), PolicyCase('s_accept_because', 'A', 'Schema v1 descriptors are rejected because the unrelated session database may report `migration_required`.'), PolicyCase('s_accept_mentions_that', 'A', 'Schema v1 descriptor documentation mentions that the unrelated session database may report `migration_required`.'), PolicyCase('s_accept_unrelated_descriptor', 'A', 'The unrelated session database descriptor reports `migration_required`.'), PolicyCase('s_accept_unrelated_candidate', 'A', 'An unrelated candidate remains `migration_required`.'), PolicyCase('s_reject_workspace_anaphor_they', 'R', 'Workspace descriptors are rejected. They return `migration_required`.'), PolicyCase('s_reject_workspace_anaphor_it', 'R', 'A workspace descriptor is rejected. It returns `migration_required`.'), PolicyCase('s_reject_descriptor_anaphor', 'R', 'The descriptor is rejected. It returns `migration_required`.'), ] def workspace_region(extra=""): body = ( "Schema v3 is the only accepted workspace descriptor. " "Schema v1 and v2 workspace descriptors are rejected before activation." ) if extra: body += "\n" + extra return f"{WORKSPACE_START}\n{body}\n{WORKSPACE_END}" def non_workspace_region(content): return f"{NON_WORKSPACE_START}\n{content}\n{NON_WORKSPACE_END}" def generic_fixture(*, workspace_extra="", current_extra=""): return f"# Fixture\n\n{workspace_region(workspace_extra)}\n\n{current_extra}\n" def project_fixture(*, workspace_extra="", current_extra="", historical_extra=""): return ( "# Project state\n\n" + workspace_region(workspace_extra) + "\n\n" + current_extra + "\n\n# Historical archive\n" + "## Historical snapshots and archived reference notes\n\n" + historical_extra + "\n" ) class PolicyMatrixTests(unittest.TestCase): def test_52_case_matrix_across_both_entry_points(self): self.assertEqual(len(CASES), 52) for case in CASES: if case.expected == "R": generic = generic_fixture(workspace_extra=case.content) project = project_fixture(workspace_extra=case.content) else: allowed = non_workspace_region(case.content) generic = generic_fixture(current_extra=allowed) project = project_fixture(current_extra=allowed) for path, checker, text in ( ("generic", check_document_text, generic), ("project", check_project_state_text, project), ): with self.subTest(case=case.name, path=path, expected=case.expected): if case.expected == "R": with self.assertRaises(ContractError): checker(text, f"{path}-{case.name}") else: checker(text, f"{path}-{case.name}") class RegionStructureTests(unittest.TestCase): def assert_invalid_both(self, generic, project=None): with self.assertRaises(ContractError): check_document_text(generic, "generic-invalid") with self.assertRaises(ContractError): check_project_state_text(project or project_fixture(current_extra=generic), "project-invalid") def test_unmarked_unrelated_migration_required_is_rejected(self): text = "The unrelated session database may report `migration_required`." self.assert_invalid_both(generic_fixture(current_extra=text), project_fixture(current_extra=text)) def test_marked_unrelated_migration_required_is_accepted(self): text = non_workspace_region("The session database may report `migration_required`.") check_document_text(generic_fixture(current_extra=text), "generic-allowed") check_project_state_text(project_fixture(current_extra=text), "project-allowed") def test_missing_reversed_duplicate_and_nested_markers_are_rejected(self): malformed = ( WORKSPACE_START, WORKSPACE_END + "\n" + WORKSPACE_START, workspace_region() + "\n" + workspace_region(), WORKSPACE_START + "\n" + NON_WORKSPACE_START + "\n" + WORKSPACE_END + "\n" + NON_WORKSPACE_END, NON_WORKSPACE_START + "\n" + NON_WORKSPACE_START + "\n" + NON_WORKSPACE_END + "\n" + NON_WORKSPACE_END, NON_WORKSPACE_START + "\n" + workspace_region() + "\n" + NON_WORKSPACE_END, workspace_region() + "\n" + NON_WORKSPACE_END, ) for index, text in enumerate(malformed): with self.subTest(index=index): self.assert_invalid_both(text) def test_migration_required_inside_workspace_block_is_rejected(self): text = generic_fixture(workspace_extra="Descriptors return `migration_required`.") self.assert_invalid_both(text, project_fixture(workspace_extra="Descriptors return `migration_required`.")) def test_legacy_support_inside_workspace_block_is_rejected(self): text = generic_fixture(workspace_extra="Schema v1 descriptors are operational and readable.") self.assert_invalid_both( text, project_fixture(workspace_extra="Schema v1 descriptors are operational and readable."), ) def test_migrate_legacy_is_forbidden_even_in_non_workspace_region(self): allowed = non_workspace_region("Run `migrate-legacy` for the session database.") self.assert_invalid_both(generic_fixture(current_extra=allowed), project_fixture(current_extra=allowed)) def test_project_requires_terminal_historical_h1(self): valid = project_fixture(historical_extra="Schema v2 returned `migration_required` historically.") check_project_state_text(valid, "project-valid-history") for name, text in ( ("missing", valid.replace("# Historical archive\n", "")), ( "blank-physical-line", valid.replace( "# Historical archive\n## Historical", "# Historical archive\n\n## Historical", ), ), ( "prose-physical-line", valid.replace( "# Historical archive\n## Historical", "# Historical archive\nArchived notes follow.\n## Historical", ), ), ( "comment-physical-line", valid.replace( "# Historical archive\n## Historical", "# Historical archive\n\n## Historical", ), ), ( "intervening-h2", valid.replace( "# Historical archive\n## Historical", "# Historical archive\n## Other archive\n## Historical", ), ), ( "intervening-setext-h2", valid.replace( "# Historical archive\n## Historical", "# Historical archive\nOther archive\n-------------\n## Historical", ), ), ( "setext-replacement-h2", valid.replace( "## Historical snapshots", "Historical snapshots\n--------------------", ), ), ("later-h1", valid + "\n# Returned live section\n"), ("duplicate-h1", valid + "\n# Historical archive\n"), ): with self.subTest(name=name), self.assertRaises(ContractError): check_project_state_text(text, f"project-{name}") def test_markers_inside_fences_comments_or_code_do_not_count(self): fake = workspace_region() for name, wrapped in ( ("backtick-fence", f"```markdown\n{fake}\n```"), ("tilde-fence", f"~~~~\n{fake}\n~~~~"), ("outer-comment", f""), ("indented-code", "\n".join(" " + line for line in fake.splitlines())), ): generic = f"# Fixture\n\n{wrapped}\n" project = ( f"# Project\n\n{wrapped}\n\n# Historical archive\n" "## Historical snapshots\n" ) with self.subTest(name=name): self.assert_invalid_both(generic, project) def test_extra_inactive_marker_literals_do_not_duplicate_active_region(self): fake_region = f"{WORKSPACE_START}\ninactive example\n{WORKSPACE_END}" extras = ( f"```\n{fake_region}\n```", f" ~~~\n{fake_region}\n ~~~~", "\n".join(" " + line for line in fake_region.splitlines()), ) for index, extra in enumerate(extras): with self.subTest(index=index): check_document_text(generic_fixture(current_extra=extra), f"generic-extra-{index}") check_project_state_text( project_fixture(current_extra=extra), f"project-extra-{index}" ) nested_comment = f"" self.assert_invalid_both( generic_fixture(current_extra=nested_comment), project_fixture(current_extra=nested_comment), ) def test_inline_and_partially_indented_markers_are_misplaced(self): for name, marker in ( ("inline", "text "), ("one-space", " "), ("inline-allow", "text "), ("one-space-allow", " "), ): generic = generic_fixture(current_extra=marker) project = project_fixture(current_extra=marker) with self.subTest(name=name): self.assert_invalid_both(generic, project) def test_inactive_allow_markers_do_not_authorize_but_extra_literals_are_ignored(self): allow = non_workspace_region("migration_required") fake_token_region = non_workspace_region("migration_required") fake_empty_region = non_workspace_region("inactive example") wrappers = ( (f"```\n{fake_token_region}\n```", f"```\n{fake_empty_region}\n```", True), (f"", f"", False), ( "\n".join(" " + line for line in fake_token_region.splitlines()), "\n".join(" " + line for line in fake_empty_region.splitlines()), True, ), ) for index, (fake_with_token, fake_without_token, extra_allowed) in enumerate(wrappers): with self.subTest(index=index, mode="sole"): self.assert_invalid_both( generic_fixture(current_extra=fake_with_token + "\nmigration_required"), project_fixture(current_extra=fake_with_token + "\nmigration_required"), ) extra_generic = generic_fixture(current_extra=fake_without_token + "\n" + allow) extra_project = project_fixture(current_extra=fake_without_token + "\n" + allow) with self.subTest(index=index, mode="extra"): if extra_allowed: check_document_text(extra_generic, f"generic-extra-allow-{index}") check_project_state_text(extra_project, f"project-extra-allow-{index}") else: self.assert_invalid_both(extra_generic, extra_project) def test_fence_closer_must_match_character_and_minimum_length(self): fake = workspace_region() for name, fenced in ( ("short-backtick-close", f"````\n```\n{fake}"), ("wrong-character-close", f"~~~~\n```\n{fake}\n~~~~"), ): project = ( f"# Project\n\n{fenced}\n\n# Historical archive\n" "## Historical snapshots\n" ) with self.subTest(name=name): self.assert_invalid_both(f"# Fixture\n\n{fenced}\n", project) safe_fake = f"{WORKSPACE_START}\ninactive example\n{WORKSPACE_END}" valid_extra = f"````\n{safe_fake}\n`````" check_document_text(generic_fixture(current_extra=valid_extra), "valid-long-close") def test_noncanonical_contract_sentences_are_rejected(self): variants = ( workspace_region().replace( "Schema v3 is the only accepted workspace descriptor.", "Only schema v3 workspace descriptors are accepted.", ), workspace_region().replace( "Schema v3 is the only accepted workspace descriptor.", "Not Schema v3 is the only accepted workspace descriptor.", ), workspace_region().replace( "Schema v1 and v2 workspace descriptors are rejected before activation.", "Not Schema v1 and v2 workspace descriptors are rejected before activation.", ), ) for index, noncanonical in enumerate(variants): with self.subTest(index=index): self.assert_invalid_both( generic_fixture().replace(workspace_region(), noncanonical), project_fixture().replace(workspace_region(), noncanonical), ) def test_remaining_legacy_claim_and_deleted_transformer_are_rejected(self): for claim in ( "The system supports schema v1 descriptors.", "A v2 descriptor remains readable.", "Legacy-descriptor activation is operational.", ): with self.subTest(claim=claim): self.assert_invalid_both( generic_fixture(workspace_extra=claim), project_fixture(workspace_extra=claim), ) for deleted in ( "Run the deleted legacy descriptor transformer.", "Invoke the descriptor legacy migrator.", "Use the legacy transformer.", ): with self.subTest(deleted=deleted): self.assert_invalid_both( generic_fixture(current_extra=deleted), project_fixture(current_extra=deleted), ) def test_deleted_tools_are_rejected_after_active_markdown_rendering(self): examples = ( "migrate-`**legacy**`", "migrate-[legacy](https://example.invalid/tool)", "migrate-[legacy][tool]\n\n[tool]: https://example.invalid/tool", "legacy descriptor **transformer**", "migrate-legacy", "migrate-legacy", r"migrate\-legacy", "legacy descriptor transformer", "Run migrate-[legacy\n](https://example.invalid/tool)", "Run migrate-[legacy\n][tool]\n\n[tool]: https://example.invalid/tool", "migrate-``\nlegacy\n``", " migrate-legacy", " legacy descriptor transformer", "migrate-`\nlegacy\n`", " migrate-legacy", "migrate-legacy safe suffix", ) for index, example in enumerate(examples): with self.subTest(index=index, example=example): self.assert_invalid_both( generic_fixture(current_extra=example), project_fixture(current_extra=example), ) def test_deleted_tool_examples_in_inactive_markdown_are_allowed(self): inactive_examples = ( "", "", "[safe label](https://example.invalid/migrate-legacy)", "[safe label][tool]\n\n[tool]: https://example.invalid/migrate-legacy", 'safe text', "safe prefix safe suffix", "safe prefix safe suffix", ) for index, example in enumerate(inactive_examples): with self.subTest(index=index): check_document_text( generic_fixture(current_extra=example), f"generic-inactive-tool-{index}" ) check_project_state_text( project_fixture(current_extra=example), f"project-inactive-tool-{index}" ) def test_deleted_tools_in_operator_visible_code_blocks_are_rejected(self): examples = ( "```text\nmigrate-legacy\n```", "~~~~\nlegacy descriptor transformer\n~~~~", " migrate-legacy", "\tlegacy descriptor transformer", "```text\n[safe](https://example.invalid/migrate-legacy)\n```", " ", "```html\n\n```", "```html\n -->\n```", ) for index, example in enumerate(examples): with self.subTest(index=index): self.assert_invalid_both( generic_fixture(current_extra=example), project_fixture(current_extra=example), ) def test_hidden_canonical_sentences_cannot_satisfy_workspace_contract(self): canonical = workspace_region()[len(WORKSPACE_START) + 1 : -len(WORKSPACE_END) - 1] hidden_bodies = ( f"", f"```text\n{canonical}\n```", "\n".join(" " + line for line in canonical.splitlines()), f"`{canonical}`", ) for index, hidden in enumerate(hidden_bodies): hidden_region = f"{WORKSPACE_START}\n{hidden}\n{WORKSPACE_END}" with self.subTest(index=index): self.assert_invalid_both( generic_fixture().replace(workspace_region(), hidden_region), project_fixture().replace(workspace_region(), hidden_region), ) def test_current_legacy_schema_references_require_non_workspace_region(self): claim = "Schema v1 workspace descriptors remain operational and readable." visible_claims = ( claim, f"```text\n{claim}\n```", " " + claim, ) for index, visible_claim in enumerate(visible_claims): with self.subTest(index=index): self.assert_invalid_both( generic_fixture(current_extra=visible_claim), project_fixture(current_extra=visible_claim), ) hidden_claim = f"" check_document_text( generic_fixture(current_extra=hidden_claim), "generic-hidden-legacy" ) check_project_state_text( project_fixture(current_extra=hidden_claim), "project-hidden-legacy" ) allowed = non_workspace_region(claim) check_document_text(generic_fixture(current_extra=allowed), "generic-allowed-legacy") check_project_state_text( project_fixture(current_extra=allowed), "project-allowed-legacy" ) def test_html_comments_fail_closed_when_unclosed_or_nested(self): malformed = ( "\n-->\nmigrate-legacy", ( "\n-->\n" "Schema v1 workspace descriptors remain operational and readable." ), "\n-->\nsafe trailing text", ) for index, text in enumerate(malformed): with self.subTest(index=index): self.assert_invalid_both( generic_fixture(current_extra=text), project_fixture(current_extra=text), ) visible_suffixes = ( " migrate-legacy", " legacy descriptor transformer", ) for index, text in enumerate(visible_suffixes): with self.subTest(index=index, kind="visible-suffix"): self.assert_invalid_both( generic_fixture(current_extra=text), project_fixture(current_extra=text), ) pure_comments = ( "", "", ) for index, text in enumerate(pure_comments): with self.subTest(index=index, kind="pure-comment"): check_document_text( generic_fixture(current_extra=text), f"generic-pure-comment-{index}" ) check_project_state_text( project_fixture(current_extra=text), f"project-pure-comment-{index}" ) def test_alternate_html_comment_closer_fails_closed_outside_code(self): malformed = ( " migrate-legacy", ( " " "Schema v1 workspace descriptors remain operational and readable." ), " migrate-legacy", ) for index, text in enumerate(malformed): generic = generic_fixture(current_extra=text) project = project_fixture(current_extra=text) with ( self.subTest(index=index, entry_point="generic"), self.assertRaisesRegex(ContractError, "alternate HTML comment closer"), ): check_document_text(generic, f"generic-alternate-closer-{index}") with ( self.subTest(index=index, entry_point="project"), self.assertRaisesRegex(ContractError, "alternate HTML comment closer"), ): check_project_state_text(project, f"project-alternate-closer-{index}") safe_code = ( "```html\n safe fenced example\n```\n" " safe indented example" ) check_document_text(generic_fixture(current_extra=safe_code), "generic-safe-code-closer") check_project_state_text( project_fixture(current_extra=safe_code), "project-safe-code-closer" ) visible_code = ( "```html\n migrate-legacy\n```\n" " Schema v1 workspace descriptors remain operational." ) self.assert_invalid_both( generic_fixture(current_extra=visible_code), project_fixture(current_extra=visible_code), ) def test_visible_comment_prefix_and_suffix_are_preserved_without_structural_reparse(self): text = ( "safe prefix safe suffix\n" "before after\n" " # Reconstructed heading must not count\n" ) scan = scan_active_markdown(text, "visible-comment-test") self.assertEqual( _render_active_markdown(scan), "safe prefix safe suffix before after # Reconstructed heading must not count", ) self.assertFalse(any(heading.text.startswith("Reconstructed") for heading in scan.headings)) def test_setext_h1_after_archive_is_rejected(self): project = project_fixture(historical_extra="Returned live section\n=====================") with self.assertRaises(ContractError): check_project_state_text(project, "project-setext-live-tail") def test_heading_literals_in_adversarial_fences_and_comments_are_inactive(self): historical = ( "````\nFake live H1\n============\n```\n`````\n" "" ) check_project_state_text( project_fixture(historical_extra=historical), "project-inactive-headings" ) def test_historical_markers_cannot_satisfy_current_contract(self): current_without = "# Project state\n\nNo current workspace contract.\n" historical = "# Historical archive\n## Historical snapshots\n\n" + workspace_region() with self.assertRaises(ContractError): check_project_state_text(current_without + historical, "project-historical-marker") if __name__ == "__main__": unittest.main(verbosity=2)