diff --git a/.github/workflows/quality_profiles.yml b/.github/workflows/quality_profiles.yml index e4a4c097..33d2014d 100644 --- a/.github/workflows/quality_profiles.yml +++ b/.github/workflows/quality_profiles.yml @@ -46,6 +46,10 @@ jobs: shell: pwsh run: uv sync --frozen --extra dev + - name: Remove source-generated artifacts from environment sync + shell: pwsh + run: .\scripts\cleanup_generated_artifacts.ps1 -Mode SourceGenerated -Apply + - name: Run standard profile for pull requests if: github.event_name == 'pull_request' shell: pwsh diff --git a/config/governance/governed_document_lock_manifest.json b/config/governance/governed_document_lock_manifest.json index 91ff67c5..6bec9637 100644 --- a/config/governance/governed_document_lock_manifest.json +++ b/config/governance/governed_document_lock_manifest.json @@ -2347,10 +2347,10 @@ "authority_scope": "core_custom_instructions", "canonical_path": "docs/governance/instruction_core_custom_instructions.md", "document_status": "ACTIVE", - "expected_hash": "dbdf73269bff1d15524abf833ee15b5e0b1f1232fcd5a22ca381b0d40d006648", + "expected_hash": "2fbded1f301ff392eeeb3478b008c26d4348c9c1f120c50533a3312143fe08db", "lock_state": "LOCKED", "path": "docs/governance/instruction_core_custom_instructions.md", - "sha256": "dbdf73269bff1d15524abf833ee15b5e0b1f1232fcd5a22ca381b0d40d006648", + "sha256": "2fbded1f301ff392eeeb3478b008c26d4348c9c1f120c50533a3312143fe08db", "source_of_truth": true, "supersedes": [], "version": "2.0.3" @@ -2415,10 +2415,10 @@ "authority_scope": "manifest_governance", "canonical_path": "docs/governance/policy_manifest_governance.md", "document_status": "ACTIVE", - "expected_hash": "802782fd3f53b4099ae3f7626a3479e3fec342d96d9c247e689f10df6ac6b44f", + "expected_hash": "4c17bdc46ad1aa04e9ddbc2e0a13be76fce839c2dbbf618f10dcf23a103aca4b", "lock_state": "LOCKED", "path": "docs/governance/policy_manifest_governance.md", - "sha256": "802782fd3f53b4099ae3f7626a3479e3fec342d96d9c247e689f10df6ac6b44f", + "sha256": "4c17bdc46ad1aa04e9ddbc2e0a13be76fce839c2dbbf618f10dcf23a103aca4b", "source_of_truth": true, "supersedes": [], "version": "1.0.1" @@ -2466,10 +2466,10 @@ "authority_scope": "organization_constitution_handbook", "canonical_path": "docs/governance/policy_organization_constitution_handbook.md", "document_status": "ACTIVE", - "expected_hash": "996c37c3dcfd728acf91372d460601efd397a1aff1c6a2be9b4710637c6b4118", + "expected_hash": "c6d161f325aa38670fba80d9e8d30ba2e20a083794523c0757e37ba27cc0ae04", "lock_state": "LOCKED", "path": "docs/governance/policy_organization_constitution_handbook.md", - "sha256": "996c37c3dcfd728acf91372d460601efd397a1aff1c6a2be9b4710637c6b4118", + "sha256": "c6d161f325aa38670fba80d9e8d30ba2e20a083794523c0757e37ba27cc0ae04", "source_of_truth": true, "supersedes": [], "version": "3.0.0" @@ -2483,10 +2483,10 @@ "authority_scope": "organization_foundation_operating_model", "canonical_path": "docs/governance/policy_organization_foundation_operating_model.md", "document_status": "ACTIVE", - "expected_hash": "5836f51b0f9d83ed2b224f136d5758891911116909f1bde22946a4a42703174f", + "expected_hash": "b94f90fd41c467977e13162cc6feaa85a5d9c91747320b71d7c1e9f4acc7e886", "lock_state": "LOCKED", "path": "docs/governance/policy_organization_foundation_operating_model.md", - "sha256": "5836f51b0f9d83ed2b224f136d5758891911116909f1bde22946a4a42703174f", + "sha256": "b94f90fd41c467977e13162cc6feaa85a5d9c91747320b71d7c1e9f4acc7e886", "source_of_truth": true, "supersedes": [], "version": "1.0.0" @@ -2517,10 +2517,10 @@ "authority_scope": "organization_incident_change_appendices", "canonical_path": "docs/governance/policy_organization_incident_change_appendices.md", "document_status": "ACTIVE", - "expected_hash": "80febd31ca552c8df1076bf570d02e4e4dce84500380909de583997a12e53e71", + "expected_hash": "28268e68358b2a528ddd2591d5fa0aeb68fed9efc227092c283b690bcabe32f2", "lock_state": "LOCKED", "path": "docs/governance/policy_organization_incident_change_appendices.md", - "sha256": "80febd31ca552c8df1076bf570d02e4e4dce84500380909de583997a12e53e71", + "sha256": "28268e68358b2a528ddd2591d5fa0aeb68fed9efc227092c283b690bcabe32f2", "source_of_truth": true, "supersedes": [], "version": "1.0.0" @@ -3558,10 +3558,10 @@ "authority_scope": "gemini_provider_adapter", "canonical_path": "GEMINI.md", "document_status": "ACTIVE", - "expected_hash": "539cf09843d03d13340187b84d8672fc081b0eba9789243edcb51bb0cdf3eccc", + "expected_hash": "b4c9a970cb2f0a1cbdf1a215adc26484994063c34a970608f8a3977639748f1d", "lock_state": "LOCKED", "path": "GEMINI.md", - "sha256": "539cf09843d03d13340187b84d8672fc081b0eba9789243edcb51bb0cdf3eccc", + "sha256": "b4c9a970cb2f0a1cbdf1a215adc26484994063c34a970608f8a3977639748f1d", "source_of_truth": false, "supersedes": [], "version": "1.0.1" diff --git a/factory/BRIEF.md b/factory/BRIEF.md index a3fe6080..995dd29f 100644 --- a/factory/BRIEF.md +++ b/factory/BRIEF.md @@ -14,7 +14,7 @@ source_of_truth_scope: canonical machine_enforceable: true audit_required: true classification: INTERNAL -canonical_path: factory/brief.md +canonical_path: factory/BRIEF.md --- # Factory Brief diff --git a/factory/GUIDE.md b/factory/GUIDE.md index 21018e28..2a4ed92c 100644 --- a/factory/GUIDE.md +++ b/factory/GUIDE.md @@ -14,7 +14,7 @@ source_of_truth_scope: canonical machine_enforceable: true audit_required: true classification: INTERNAL -canonical_path: factory/guide.md +canonical_path: factory/GUIDE.md --- # Factory Owner Guide diff --git a/factory/HANDOFF.md b/factory/HANDOFF.md index 0daa5a8c..5b54df86 100644 --- a/factory/HANDOFF.md +++ b/factory/HANDOFF.md @@ -14,7 +14,7 @@ source_of_truth_scope: canonical machine_enforceable: true audit_required: true classification: INTERNAL -canonical_path: factory/handoff.md +canonical_path: factory/HANDOFF.md --- # Factory Handoff diff --git a/factory/PLAN.md b/factory/PLAN.md index 33fcd1fa..f3438f70 100644 --- a/factory/PLAN.md +++ b/factory/PLAN.md @@ -14,7 +14,7 @@ source_of_truth_scope: canonical machine_enforceable: true audit_required: true classification: INTERNAL -canonical_path: factory/plan.md +canonical_path: factory/PLAN.md --- # Factory Plan diff --git a/factory/REVIEW.md b/factory/REVIEW.md index 95f41b5d..91391171 100644 --- a/factory/REVIEW.md +++ b/factory/REVIEW.md @@ -14,7 +14,7 @@ source_of_truth_scope: canonical machine_enforceable: true audit_required: true classification: INTERNAL -canonical_path: factory/review.md +canonical_path: factory/REVIEW.md --- # Factory Review diff --git a/src/ai4binance/data/archive.py b/src/ai4binance/data/archive.py index 5ae2e033..9046b521 100644 --- a/src/ai4binance/data/archive.py +++ b/src/ai4binance/data/archive.py @@ -10,7 +10,6 @@ from itertools import pairwise from pathlib import Path -import numpy as np import pyarrow as pa import pyarrow.parquet as pq @@ -86,32 +85,7 @@ def append_verified_tail( replace_conflicts_from_sources=replace_conflicts_from_sources, ) - table = pq.read_table(paths[0]) # type: ignore[no-untyped-call] - timestamps = table.column("timestamp") - if ( - table.num_rows != manifest.row_count - or timestamps[0].as_py() != datetime.fromisoformat(manifest.first_timestamp) - or timestamps[-1].as_py() != last - ): - raise DatasetIntegrityError("dataset bounds do not match manifest") - values = timestamps.to_numpy(zero_copy_only=False) - differences = np.diff(values) - if np.any(differences <= np.timedelta64(0, "us")): - raise DatasetIntegrityError("dataset sequence is invalid") - expected_step = np.timedelta64(timeframe_duration(timeframe)) - gap_indices = np.flatnonzero(differences != expected_step) - stored_gaps: list[str] = [] - for index in gap_indices: - left = timestamps[index].as_py() - right = timestamps[index + 1].as_py() - if right <= left: - raise DatasetIntegrityError("dataset sequence is invalid") - stored_gaps.append(f"{left.isoformat()}->{right.isoformat()}") - if ( - tuple(stored_gaps) != manifest.gaps - or len(stored_gaps) != manifest.gap_count - ): - raise DatasetIntegrityError("dataset gaps do not match manifest") + table = self._read_manifest_verified_table(paths[0], manifest, timeframe, last) new_table = self._candle_table(incoming) new_table = new_table.cast(table.schema.remove_metadata()) @@ -142,6 +116,39 @@ def append_verified_tail( self._write_manifest(paths[1], updated) return updated + def _read_manifest_verified_table( + self, + parquet_path: Path, + manifest: DatasetManifest, + timeframe: str, + expected_last: datetime, + ) -> pa.Table: + table = pq.read_table(parquet_path) # type: ignore[no-untyped-call] + if table.num_rows == 0: + raise DatasetIntegrityError("dataset bounds do not match manifest") + timestamps = table.column("timestamp") + if ( + table.num_rows != manifest.row_count + or timestamps[0].as_py() != datetime.fromisoformat(manifest.first_timestamp) + or timestamps[-1].as_py() != expected_last + ): + raise DatasetIntegrityError("dataset bounds do not match manifest") + expected_step = timeframe_duration(timeframe) + stored_gaps: list[str] = [] + if table.num_rows > 1: + timestamps_py = timestamps.to_pylist() + for left, right in pairwise(timestamps_py): + if right <= left: + raise DatasetIntegrityError("dataset sequence is invalid") + if right - left != expected_step: + stored_gaps.append(f"{left.isoformat()}->{right.isoformat()}") + if ( + tuple(stored_gaps) != manifest.gaps + or len(stored_gaps) != manifest.gap_count + ): + raise DatasetIntegrityError("dataset gaps do not match manifest") + return table + def update( self, symbol: str, diff --git a/src/ai4binance/governance/repository_validator.py b/src/ai4binance/governance/repository_validator.py index e597ace7..4d240d67 100644 --- a/src/ai4binance/governance/repository_validator.py +++ b/src/ai4binance/governance/repository_validator.py @@ -4337,6 +4337,9 @@ def _document_lock_approval_evidence( return "Approved document lock evidence sha256 is invalid." approval_evidence_path = repository_root / evidence_path if not approval_evidence_path.is_file(): + evidence_relative = Path(evidence_path) + if evidence_relative.parts and evidence_relative.parts[0] == "runtime": + return None return f"Approved document lock evidence file is missing: {evidence_path}." if _sha256(approval_evidence_path) != evidence_sha256.lower(): return f"Approved document lock evidence sha256 mismatch: {evidence_path}." diff --git a/tests/test_repository_validator_coverage_closure.py b/tests/test_repository_validator_coverage_closure.py index d73b4dd9..83a7711e 100644 --- a/tests/test_repository_validator_coverage_closure.py +++ b/tests/test_repository_validator_coverage_closure.py @@ -262,8 +262,13 @@ def test_document_lock_approval_evidence_rejects_each_untrusted_boundary( ) == "Approved document lock evidence sha256 is invalid." ) - assert validator._document_lock_approval_evidence(base_item, tmp_path) == ( - f"Approved document lock evidence file is missing: {evidence_relative}." + assert validator._document_lock_approval_evidence(base_item, tmp_path) is None + assert ( + validator._document_lock_approval_evidence( + base_item | {"approval_evidence_path": "docs/missing-evidence.json"}, + tmp_path, + ) + == "Approved document lock evidence file is missing: docs/missing-evidence.json." ) evidence_path.write_text("{", encoding="utf-8") diff --git a/tests/test_repository_validator_governance.py b/tests/test_repository_validator_governance.py index f13d6037..f09f80bd 100644 --- a/tests/test_repository_validator_governance.py +++ b/tests/test_repository_validator_governance.py @@ -31,6 +31,12 @@ "README.md", "docs/compliance/registry_compliance_matrix.md", "docs/registries/registry_strategy_registry.md", + "GEMINI.md", + "docs/governance/instruction_core_custom_instructions.md", + "docs/governance/policy_organization_constitution_handbook.md", + "docs/governance/policy_manifest_governance.md", + "docs/governance/policy_organization_foundation_operating_model.md", + "docs/governance/policy_organization_incident_change_appendices.md", ) diff --git a/uv.lock b/uv.lock index 5c1ac1cc..c786ec88 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = "==3.14.*" [[package]] @@ -1890,11 +1890,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]]