From 34ccc58be713c8b8a965ba430f67acdc40e33380 Mon Sep 17 00:00:00 2001 From: LittlePeter52012 <94422715+LittlePeter52012@users.noreply.github.com> Date: Fri, 21 Aug 2026 08:36:50 +0800 Subject: [PATCH 1/3] fix: resolve invariant-computation license links --- skills/invariant-computation/README.md | 2 +- skills/invariant-computation/README.zh-CN.md | 2 +- tests/test_invariant_computation_skill.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/skills/invariant-computation/README.md b/skills/invariant-computation/README.md index aa895a4..2347d7e 100644 --- a/skills/invariant-computation/README.md +++ b/skills/invariant-computation/README.md @@ -149,4 +149,4 @@ Then check that repository docs point back to the package Skill and that no loca ## License -See [LICENSE](LICENSE). +See [LICENSE](../../LICENSE). diff --git a/skills/invariant-computation/README.zh-CN.md b/skills/invariant-computation/README.zh-CN.md index 0a14720..571e75b 100644 --- a/skills/invariant-computation/README.zh-CN.md +++ b/skills/invariant-computation/README.zh-CN.md @@ -135,4 +135,4 @@ python3 -m unittest discover -s tests -v ## 许可证 -见 [LICENSE](LICENSE)。 +见 [LICENSE](../../LICENSE)。 diff --git a/tests/test_invariant_computation_skill.py b/tests/test_invariant_computation_skill.py index ea57ae1..7dc2c1c 100644 --- a/tests/test_invariant_computation_skill.py +++ b/tests/test_invariant_computation_skill.py @@ -71,6 +71,21 @@ def test_package_readmes_explain_loading_and_interaction(self) -> None: ): self.assertIn(required, chinese) + def test_package_readme_local_links_resolve(self) -> None: + for relative_path in ( + "skills/invariant-computation/README.md", + "skills/invariant-computation/README.zh-CN.md", + ): + readme = ROOT / relative_path + for target in re.findall(r"\[[^\]]*\]\(([^)]+)\)", self.read(relative_path)): + if target.startswith(("https://", "http://", "#", "mailto:")): + continue + target = target.split("#", 1)[0] + self.assertTrue( + (readme.parent / target).is_file(), + f"Broken local link in {relative_path}: {target}", + ) + def test_reference_files_cover_route_families_and_tools(self) -> None: index = self.read("skills/invariant-computation/references/INDEX.md") route_map = self.read("skills/invariant-computation/references/method_route_map.md") From b9055f3694f6bac42acc79633b5d7b72b462bd44 Mon Sep 17 00:00:00 2001 From: LittlePeter52012 <94422715+LittlePeter52012@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:12:30 +0800 Subject: [PATCH 2/3] feat: add invariant summary result contract --- pyproject.toml | 2 +- skills/invariant-computation/README.md | 6 + skills/invariant-computation/README.zh-CN.md | 6 + skills/invariant-computation/SKILL.md | 5 + .../examples/invariant_summary.example.json | 45 +++++ .../schema/invariant_summary.schema.json | 169 ++++++++++++++++++ tests/test_invariant_summary_schema.py | 87 +++++++++ 7 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 skills/invariant-computation/examples/invariant_summary.example.json create mode 100644 skills/invariant-computation/schema/invariant_summary.schema.json create mode 100644 tests/test_invariant_summary_schema.py diff --git a/pyproject.toml b/pyproject.toml index 22ede6b..3b3d358 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires-python = ">=3.10" dependencies = [] [project.optional-dependencies] -dev = ["pytest>=8.0"] +dev = ["jsonschema>=4.23", "pytest>=8.0"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/skills/invariant-computation/README.md b/skills/invariant-computation/README.md index 2347d7e..9faa40d 100644 --- a/skills/invariant-computation/README.md +++ b/skills/invariant-computation/README.md @@ -111,6 +111,10 @@ outputs// └── RUN_SUMMARY.md ``` +The JSON summary has a +[versioned schema](schema/invariant_summary.schema.json) and a +[known-answer example](examples/invariant_summary.example.json). + Only create the files that the task needs. ## Safety And Review Rules @@ -131,8 +135,10 @@ skills/ README.md SKILL.md agents/openai.yaml + examples/ manifest.yaml references/ + schema/ tests/ ``` diff --git a/skills/invariant-computation/README.zh-CN.md b/skills/invariant-computation/README.zh-CN.md index 571e75b..5ebbaab 100644 --- a/skills/invariant-computation/README.zh-CN.md +++ b/skills/invariant-computation/README.zh-CN.md @@ -97,6 +97,10 @@ outputs// └── RUN_SUMMARY.md ``` +`invariant_summary.json` 提供 +[版本化 schema](schema/invariant_summary.schema.json)和 +[已知答案示例](examples/invariant_summary.example.json)。 + 只创建当前任务真正需要的文件。 ## 安全与审查规则 @@ -117,8 +121,10 @@ skills/ README.md SKILL.md agents/openai.yaml + examples/ manifest.yaml references/ + schema/ tests/ ``` diff --git a/skills/invariant-computation/SKILL.md b/skills/invariant-computation/SKILL.md index 54c65c8..93df68e 100644 --- a/skills/invariant-computation/SKILL.md +++ b/skills/invariant-computation/SKILL.md @@ -85,6 +85,11 @@ outputs// └── RUN_SUMMARY.md ``` +The JSON summary should validate against the +[versioned schema](schema/invariant_summary.schema.json). Use the +[known-answer example](examples/invariant_summary.example.json) as a shape +reference, not as evidence for a new computation. + 9. Validate results using `references/validation_checks.md`. 10. Report the invariant, assumptions, checks passed, checks failed, software versions, and what the invariant does not prove. diff --git a/skills/invariant-computation/examples/invariant_summary.example.json b/skills/invariant-computation/examples/invariant_summary.example.json new file mode 100644 index 0000000..1048414 --- /dev/null +++ b/skills/invariant-computation/examples/invariant_summary.example.json @@ -0,0 +1,45 @@ +{ + "schema_version": 1, + "object": { + "source": "Known-answer fixture: the boundary of a 2-simplex.", + "representation": "The maximal simplices are [0, 1], [1, 2], and [0, 2], with all faces included." + }, + "invariant": { + "requested": "Betti numbers over Q", + "computed": "Betti numbers in degrees 0 and 1", + "value": { + "0": 1, + "1": 1 + }, + "result_quality": "exact" + }, + "coefficient_ring_or_field": "Q", + "conventions": [ + "unreduced homology", + "degrees not listed are zero" + ], + "backend": { + "route": "finite_complexes", + "name": "documented exact chain-complex reduction", + "version_evidence": "known-answer schema fixture version 1" + }, + "command_or_code_path": "not applicable: format-only known-answer fixture", + "validation_checks": [ + { + "name": "simplicial closure", + "kind": "structural", + "status": "passed", + "evidence": "All vertices of the three edges are included." + }, + { + "name": "Euler characteristic", + "kind": "invariant_cross_check", + "status": "passed", + "evidence": "3 vertices - 3 edges = 0 = b_0 - b_1 = 1 - 1." + } + ], + "remaining_uncertainty": [], + "next_repair_route": null, + "classification_caveat": "Betti numbers alone do not classify finite simplicial complexes up to homotopy equivalence.", + "fixture_note": "This file demonstrates the result shape and is not evidence from a computation run." +} diff --git a/skills/invariant-computation/schema/invariant_summary.schema.json b/skills/invariant-computation/schema/invariant_summary.schema.json new file mode 100644 index 0000000..903f1b2 --- /dev/null +++ b/skills/invariant-computation/schema/invariant_summary.schema.json @@ -0,0 +1,169 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Invariant Computation Summary", + "description": "Backend-neutral evidence summary for one invariant-computation run.", + "type": "object", + "required": [ + "schema_version", + "object", + "invariant", + "coefficient_ring_or_field", + "conventions", + "backend", + "command_or_code_path", + "validation_checks", + "remaining_uncertainty", + "next_repair_route" + ], + "properties": { + "schema_version": { + "const": 1 + }, + "object": { + "type": "object", + "required": [ + "source", + "representation" + ], + "properties": { + "source": { + "type": "string", + "minLength": 1 + }, + "representation": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + }, + "invariant": { + "type": "object", + "required": [ + "requested", + "computed", + "value", + "result_quality" + ], + "properties": { + "requested": { + "type": "string", + "minLength": 1 + }, + "computed": { + "type": "string", + "minLength": 1 + }, + "value": {}, + "result_quality": { + "enum": [ + "exact", + "certified_numerical", + "heuristic", + "partial", + "failed" + ] + } + }, + "additionalProperties": true + }, + "coefficient_ring_or_field": { + "type": "string", + "minLength": 1 + }, + "conventions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "backend": { + "type": "object", + "required": [ + "route", + "name", + "version_evidence" + ], + "properties": { + "route": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "version_evidence": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + }, + "command_or_code_path": { + "type": "string", + "minLength": 1 + }, + "validation_checks": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "name", + "kind", + "status", + "evidence" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "kind": { + "enum": [ + "structural", + "invariant_cross_check", + "other" + ] + }, + "status": { + "enum": [ + "passed", + "failed", + "not_checked", + "inconclusive" + ] + }, + "evidence": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + } + }, + "remaining_uncertainty": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "next_repair_route": { + "type": [ + "string", + "null" + ], + "minLength": 1 + }, + "classification_caveat": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true +} diff --git a/tests/test_invariant_summary_schema.py b/tests/test_invariant_summary_schema.py new file mode 100644 index 0000000..6e58ec2 --- /dev/null +++ b/tests/test_invariant_summary_schema.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import copy +import json +import unittest +from pathlib import Path + +from jsonschema import Draft202012Validator, ValidationError + + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA_PATH = ( + ROOT + / "skills" + / "invariant-computation" + / "schema" + / "invariant_summary.schema.json" +) +EXAMPLE_PATH = ( + ROOT + / "skills" + / "invariant-computation" + / "examples" + / "invariant_summary.example.json" +) + + +class InvariantSummarySchemaTests(unittest.TestCase): + def load_json(self, path: Path) -> object: + self.assertTrue(path.is_file(), f"Missing JSON contract artifact: {path}") + with path.open(encoding="utf-8") as stream: + return json.load(stream) + + def test_schema_is_valid_draft_2020_12(self) -> None: + Draft202012Validator.check_schema(self.load_json(SCHEMA_PATH)) + + def test_public_example_conforms_to_schema(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = self.load_json(EXAMPLE_PATH) + + Draft202012Validator(schema).validate(example) + + def test_remaining_uncertainty_cannot_be_omitted(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = copy.deepcopy(self.load_json(EXAMPLE_PATH)) + del example["remaining_uncertainty"] + + with self.assertRaises(ValidationError): + Draft202012Validator(schema).validate(example) + + def test_theorem_backed_classification_may_omit_caveat(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = copy.deepcopy(self.load_json(EXAMPLE_PATH)) + del example["classification_caveat"] + + Draft202012Validator(schema).validate(example) + + def test_invalid_contract_variants_are_rejected(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = self.load_json(EXAMPLE_PATH) + + missing_version = copy.deepcopy(example) + del missing_version["backend"]["version_evidence"] + invalid_quality = copy.deepcopy(example) + invalid_quality["invariant"]["result_quality"] = "proved" + empty_checks = copy.deepcopy(example) + empty_checks["validation_checks"] = [] + + for label, invalid in ( + ("missing backend version evidence", missing_version), + ("unsupported result quality", invalid_quality), + ("empty validation checks", empty_checks), + ): + with self.subTest(label=label): + with self.assertRaises(ValidationError): + Draft202012Validator(schema).validate(invalid) + + def test_extension_fields_remain_forward_compatible(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = copy.deepcopy(self.load_json(EXAMPLE_PATH)) + example["producer_extension"] = {"name": "downstream-agent"} + + Draft202012Validator(schema).validate(example) + + +if __name__ == "__main__": + unittest.main() From 3632f4ae86de22d5b66d0218d03e73c264aa8688 Mon Sep 17 00:00:00 2001 From: LittlePeter52012 <94422715+LittlePeter52012@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:25:29 +0800 Subject: [PATCH 3/3] fix: tighten invariant summary validation --- skills/invariant-computation/README.md | 5 + skills/invariant-computation/README.zh-CN.md | 4 + skills/invariant-computation/SKILL.md | 3 +- .../examples/invariant_summary.example.json | 6 ++ .../schema/invariant_summary.schema.json | 91 +++++++++++++++++++ tests/test_invariant_summary_schema.py | 81 +++++++++++++++++ 6 files changed, 189 insertions(+), 1 deletion(-) diff --git a/skills/invariant-computation/README.md b/skills/invariant-computation/README.md index 9faa40d..ae799a8 100644 --- a/skills/invariant-computation/README.md +++ b/skills/invariant-computation/README.md @@ -114,6 +114,10 @@ outputs// The JSON summary has a [versioned schema](schema/invariant_summary.schema.json) and a [known-answer example](examples/invariant_summary.example.json). +The schema requires either `classification_caveat` or +`classification_theorem_evidence`. Heuristic, partial, and failed results must +also record remaining uncertainty and a next repair route. Every result other +than `failed` must provide a non-null `invariant.value`. Only create the files that the task needs. @@ -147,6 +151,7 @@ tests/ Run: ```bash +python3 -m pip install -e '.[dev]' python /scripts/quick_validate.py skills/invariant-computation python3 -m unittest discover -s tests -v ``` diff --git a/skills/invariant-computation/README.zh-CN.md b/skills/invariant-computation/README.zh-CN.md index 5ebbaab..1bd5472 100644 --- a/skills/invariant-computation/README.zh-CN.md +++ b/skills/invariant-computation/README.zh-CN.md @@ -100,6 +100,9 @@ outputs// `invariant_summary.json` 提供 [版本化 schema](schema/invariant_summary.schema.json)和 [已知答案示例](examples/invariant_summary.example.json)。 +Schema 要求 `classification_caveat` 与 `classification_theorem_evidence` +至少存在一项。启发式、部分完成和失败结果还必须记录剩余不确定性与下一步修复 +路线。除 `failed` 外,所有结果都必须提供非空的 `invariant.value`。 只创建当前任务真正需要的文件。 @@ -133,6 +136,7 @@ tests/ 运行: ```bash +python3 -m pip install -e '.[dev]' python /scripts/quick_validate.py skills/invariant-computation python3 -m unittest discover -s tests -v ``` diff --git a/skills/invariant-computation/SKILL.md b/skills/invariant-computation/SKILL.md index 93df68e..41dcc12 100644 --- a/skills/invariant-computation/SKILL.md +++ b/skills/invariant-computation/SKILL.md @@ -153,6 +153,7 @@ Every final report should include: - coefficient ring or field and conventions; - backend route and version evidence; - exact command or code path used; +- a non-null invariant value unless the computation failed; - validation checks and their results; - unresolved ambiguity and next repair route; -- a classification caveat unless classification is theorem-backed. +- either a classification caveat or explicit theorem evidence supporting its omission. diff --git a/skills/invariant-computation/examples/invariant_summary.example.json b/skills/invariant-computation/examples/invariant_summary.example.json index 1048414..d6737f3 100644 --- a/skills/invariant-computation/examples/invariant_summary.example.json +++ b/skills/invariant-computation/examples/invariant_summary.example.json @@ -31,6 +31,12 @@ "status": "passed", "evidence": "All vertices of the three edges are included." }, + { + "name": "boundary-map ranks", + "kind": "invariant_cross_check", + "status": "passed", + "evidence": "Over Q, rank d_1 = 2 and rank d_2 = 0, so b_0 = 3 - 2 = 1 and b_1 = (3 - 2) - 0 = 1." + }, { "name": "Euler characteristic", "kind": "invariant_cross_check", diff --git a/skills/invariant-computation/schema/invariant_summary.schema.json b/skills/invariant-computation/schema/invariant_summary.schema.json index 903f1b2..daeffc4 100644 --- a/skills/invariant-computation/schema/invariant_summary.schema.json +++ b/skills/invariant-computation/schema/invariant_summary.schema.json @@ -163,7 +163,98 @@ "classification_caveat": { "type": "string", "minLength": 1 + }, + "classification_theorem_evidence": { + "type": "string", + "minLength": 1 } }, + "allOf": [ + { + "anyOf": [ + { + "required": [ + "classification_caveat" + ] + }, + { + "required": [ + "classification_theorem_evidence" + ] + } + ] + }, + { + "if": { + "properties": { + "invariant": { + "properties": { + "result_quality": { + "enum": [ + "exact", + "certified_numerical", + "heuristic", + "partial" + ] + } + }, + "required": [ + "result_quality" + ] + } + }, + "required": [ + "invariant" + ] + }, + "then": { + "properties": { + "invariant": { + "properties": { + "value": { + "not": { + "type": "null" + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "invariant": { + "properties": { + "result_quality": { + "enum": [ + "heuristic", + "partial", + "failed" + ] + } + }, + "required": [ + "result_quality" + ] + } + }, + "required": [ + "invariant" + ] + }, + "then": { + "properties": { + "remaining_uncertainty": { + "minItems": 1 + }, + "next_repair_route": { + "type": "string", + "minLength": 1 + } + } + } + } + ], "additionalProperties": true } diff --git a/tests/test_invariant_summary_schema.py b/tests/test_invariant_summary_schema.py index 6e58ec2..f316cee 100644 --- a/tests/test_invariant_summary_schema.py +++ b/tests/test_invariant_summary_schema.py @@ -52,6 +52,80 @@ def test_theorem_backed_classification_may_omit_caveat(self) -> None: schema = self.load_json(SCHEMA_PATH) example = copy.deepcopy(self.load_json(EXAMPLE_PATH)) del example["classification_caveat"] + example["classification_theorem_evidence"] = ( + "validation_report.md records the theorem citation and matched hypotheses." + ) + + Draft202012Validator(schema).validate(example) + + def test_classification_requires_caveat_or_theorem_evidence(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = copy.deepcopy(self.load_json(EXAMPLE_PATH)) + del example["classification_caveat"] + + with self.assertRaises(ValidationError): + Draft202012Validator(schema).validate(example) + + def test_nonfailed_result_requires_nonnull_value(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = self.load_json(EXAMPLE_PATH) + + for result_quality in ( + "exact", + "certified_numerical", + "heuristic", + "partial", + ): + invalid = copy.deepcopy(example) + invalid["invariant"]["result_quality"] = result_quality + invalid["invariant"]["value"] = None + if result_quality in ("heuristic", "partial"): + invalid["remaining_uncertainty"] = ["The result is not yet conclusive."] + invalid["next_repair_route"] = "Run a stronger validation route." + + with self.subTest(result_quality=result_quality): + with self.assertRaises(ValidationError): + Draft202012Validator(schema).validate(invalid) + + def test_unsettled_result_requires_remaining_uncertainty(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = self.load_json(EXAMPLE_PATH) + + for result_quality in ("heuristic", "partial", "failed"): + invalid = copy.deepcopy(example) + invalid["invariant"]["result_quality"] = result_quality + invalid["remaining_uncertainty"] = [] + invalid["next_repair_route"] = "Run a stronger validation route." + if result_quality == "failed": + invalid["invariant"]["value"] = None + + with self.subTest(result_quality=result_quality): + with self.assertRaises(ValidationError): + Draft202012Validator(schema).validate(invalid) + + def test_unsettled_result_requires_next_repair_route(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = self.load_json(EXAMPLE_PATH) + + for result_quality in ("heuristic", "partial", "failed"): + invalid = copy.deepcopy(example) + invalid["invariant"]["result_quality"] = result_quality + invalid["remaining_uncertainty"] = ["The result is not yet conclusive."] + invalid["next_repair_route"] = None + if result_quality == "failed": + invalid["invariant"]["value"] = None + + with self.subTest(result_quality=result_quality): + with self.assertRaises(ValidationError): + Draft202012Validator(schema).validate(invalid) + + def test_failed_result_may_use_null_value_with_repair_evidence(self) -> None: + schema = self.load_json(SCHEMA_PATH) + example = copy.deepcopy(self.load_json(EXAMPLE_PATH)) + example["invariant"]["result_quality"] = "failed" + example["invariant"]["value"] = None + example["remaining_uncertainty"] = ["The requested invariant was not computed."] + example["next_repair_route"] = "Repair the representation and rerun validation." Draft202012Validator(schema).validate(example) @@ -59,17 +133,24 @@ def test_invalid_contract_variants_are_rejected(self) -> None: schema = self.load_json(SCHEMA_PATH) example = self.load_json(EXAMPLE_PATH) + unsupported_version = copy.deepcopy(example) + unsupported_version["schema_version"] = 2 missing_version = copy.deepcopy(example) del missing_version["backend"]["version_evidence"] invalid_quality = copy.deepcopy(example) invalid_quality["invariant"]["result_quality"] = "proved" empty_checks = copy.deepcopy(example) empty_checks["validation_checks"] = [] + empty_theorem_evidence = copy.deepcopy(example) + del empty_theorem_evidence["classification_caveat"] + empty_theorem_evidence["classification_theorem_evidence"] = "" for label, invalid in ( + ("unsupported schema version", unsupported_version), ("missing backend version evidence", missing_version), ("unsupported result quality", invalid_quality), ("empty validation checks", empty_checks), + ("empty classification theorem evidence", empty_theorem_evidence), ): with self.subTest(label=label): with self.assertRaises(ValidationError):