From 82e7b0a95a3e2d3b6250abac0bec51128aab45e2 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sun, 19 Jul 2026 08:59:33 +0800 Subject: [PATCH] feat: sync astrology provenance gates --- ...se_timing_optimization_audit_2026_07_19.md | 47 ++++++++ ..._component_provenance_report_2026_07_19.md | 48 ++++++++ frontend/src/mastra/index.ts | 4 +- frontend/tests/consultation-context.test.ts | 6 + ...ponent_provenance_registry_2026_07_19.json | 110 ++++++++++++++++++ ...eproducible_build_identity_2026_07_17.json | 39 +++++++ scripts/day_level_negative_holdout_intake.py | 54 +++++++++ scripts/shadbala_av_component_registry.py | 95 +++++++++++++++ scripts/vedastro_reproducible_build_probe.py | 101 ++++++++++++++++ ...est_real_case_timing_optimization_audit.py | 24 ++++ tests/test_shadbala_av_component_registry.py | 63 ++++++++++ .../test_vedastro_reproducible_build_probe.py | 33 ++++++ 12 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 docs/research/real_case_timing_optimization_audit_2026_07_19.md create mode 100644 docs/research/shadbala_av_component_provenance_report_2026_07_19.md create mode 100644 references/oracle/shadbala_av_component_provenance_registry_2026_07_19.json create mode 100644 references/oracle/vedastro_reproducible_build_identity_2026_07_17.json create mode 100644 scripts/day_level_negative_holdout_intake.py create mode 100644 scripts/shadbala_av_component_registry.py create mode 100644 scripts/vedastro_reproducible_build_probe.py create mode 100644 tests/test_real_case_timing_optimization_audit.py create mode 100644 tests/test_shadbala_av_component_registry.py create mode 100644 tests/test_vedastro_reproducible_build_probe.py diff --git a/docs/research/real_case_timing_optimization_audit_2026_07_19.md b/docs/research/real_case_timing_optimization_audit_2026_07_19.md new file mode 100644 index 00000000..9fb4df51 --- /dev/null +++ b/docs/research/real_case_timing_optimization_audit_2026_07_19.md @@ -0,0 +1,47 @@ +# Real-case timing optimization audit — 2026-07-19 + +## Current verified layer + +Positive-event replay is healthy: + +| Manifest | Cases | Ready | Boundary | +|---|---:|---:|---| +| `references/real_case_calibration/replay_manifest.json` | 10 | 10 | known positive events only | +| `references/real_case_calibration/replay_manifest_holdout_v2.json` | 10 | 10 | blind positive holdout; not specificity proof | +| `references/real_case_calibration/replay_manifest_probe3_v2.json` | 3 | 3 | probe batch only | + +This verifies technical recall around known dated events. It does not verify day/month predictive specificity. + +## Current blocked layer + +Day-level negative holdout remains empty: + +- `references/real_case_calibration/day_level_holdout_v3_preregistration.json` +- `annotation_count = 0` +- `negative_count = 0` +- `positive_count = 0` +- `production_tuning_allowed = false` +- `status = awaiting_independent_labels` + +Existing 40 control dates remain diagnostic only because they were already observed before preregistration and are not independent human-reviewed labels. + +Pilot source queue exists at `references/real_case_calibration/day_level_holdout_v3_pilot_source_queue_2026_07_19.json`. + +Boundary: the queue contains public-source candidates for Steve Jobs, Barack Obama, and Albert Einstein. It is not a holdout manifest and must not be used for timing evaluation until independent adjudication converts rows into frozen annotations. + +## Optimization needed + +1. Collect independent human-labeled non-event intervals for the same subjects/domains. +2. Freeze labels before scoring. +3. Run candidate day/month ranking over positive and negative windows together. +4. Promote timing claims only if positive windows rank above negative windows under frozen rules. + +Until then, precise day/month output must stay: + +- `timing_precision = candidate_day_window` +- `claim_status = exploratory_unvalidated` +- `production_tuning_allowed = false` + +Allowed UX: ranked candidate windows w/ evidence and confidence caps. + +Forbidden UX: packaging candidate dates as verified event promises. diff --git a/docs/research/shadbala_av_component_provenance_report_2026_07_19.md b/docs/research/shadbala_av_component_provenance_report_2026_07_19.md new file mode 100644 index 00000000..3f691494 --- /dev/null +++ b/docs/research/shadbala_av_component_provenance_report_2026_07_19.md @@ -0,0 +1,48 @@ +# Shadbala / AV component provenance report — 2026-07-19 + +Source registry: `references/oracle/shadbala_av_component_provenance_registry_2026_07_19.json` + +Status: `classified_unresolved` + +Truth policy: `method_variant_not_majority_vote` + +Production tuning: `false` + +## Component families + +| Category | Rows | Component family | Allowed claim | +|---|---:|---|---| +| `shadbala_formula_variant` | 35 | Shadbala six-force components | `component_method_variant` | +| `derived_total_from_component_variants` | 7 | Shadbala total | `derived_total_blocked_until_components_close` | +| `ashtakavarga_table_or_contributor_variant` | 8 | Ashtakavarga tables | `table_variant` | +| `endpoint_or_varga_semantics` | 10 | Varga endpoint semantics | `current_target_observation_only` | + +## Unit / source obligations + +- Sthana, Dig, Kala, Chesta, Naisargika, and Drik Bala each require formula source, unit, cap/floor, and worked example. +- Total Rupa/Virupa cannot be arbitrated until component units close. +- Ashtakavarga BAV/SAV requires contributor table, shodhana state, Lagna inclusion, and row/column schema. +- VedAstro varga endpoint rows require explicit endpoint/method semantics before truth arbitration. + +## Current claim boundary + +Allowed: + +- current target observations; +- named method variants; +- component-level unresolved status; +- user-safe confidence caps. + +Forbidden: + +- global Shadbala/AV truth; +- production tuning; +- majority-vote truth; +- copying raw research debt into runtime. + +## Next source-evidence queue + +1. Map 35 Shadbala component rows to `sthana/dig/kala/chesta/naisargika/drik`. +2. Attach VP Jain / Xalen / jyotishganit / PyJHora observation source per component. +3. Mark each component as `source_explicit`, `method_variant`, or `insufficient_evidence`. +4. Recompute totals only after component status closes. diff --git a/frontend/src/mastra/index.ts b/frontend/src/mastra/index.ts index ff4214d5..32ffd360 100644 --- a/frontend/src/mastra/index.ts +++ b/frontend/src/mastra/index.ts @@ -77,6 +77,7 @@ export function toAgentConsultationContext(data: JsonRecord) { houses: chart.houses, dasha: chart.dasha, shadbala: chart.shadbala, + ashtakavarga: chart.ashtakavarga, yogas: chart.yogas, }, local_layers: { @@ -120,9 +121,10 @@ When reference_transparency is present: - Treat similarity.timing_state as authoritative: status=matched means Vimshottari MD and AD both match; partial_match means only Vimshottari MD matches. Read narayana_status and transit_status separately; never infer either from Vimshottari status. A transit_status match means only Jupiter and Saturn relative houses match, not that every transit matches. - When similar_public_cases.coverage.requested_uncovered_domains is non-empty, say the current public-case catalog does not yet cover those themes; do not infer that no comparable real-world case exists. - When method_variants applies, present parallel methods and their source paths rather than silently picking one result as the only truth. +- Treat Shadbala/Ashtakavarga component differences under production_tuning_allowed=false as method boundaries, not absolute calculation errors. Use no_majority_vote and method_variant_not_majority_vote: do not decide truth by engine count, and do not say one school is wrong unless a pinned authoritative worked example is present. - If should_lead_with_limitations is false, do not lead with limitations. If a limitation is relevant, put it in one short sentence at the end. - Only say the chart calculation failed when hard_blockers is non-empty. -- Never claim D9, D10, A10, UL, or Narayana Dasha is missing when it appears in available_layers or local_layers. +- Never claim D2, D11, D9, D10, A10, UL, or Narayana Dasha is missing when it appears in available_layers, chart, or local_layers. Usually answer in 2-5 short paragraphs. Ask one clarifying question only when the user's intent is genuinely unclear. After every substantive answer, append exactly two hidden blocks in this order and nothing after the second block: diff --git a/frontend/tests/consultation-context.test.ts b/frontend/tests/consultation-context.test.ts index 52605011..a89b07d2 100644 --- a/frontend/tests/consultation-context.test.ts +++ b/frontend/tests/consultation-context.test.ts @@ -7,6 +7,7 @@ test("passes transparent public-case references into the agent context", () => { assert.match(source, /reference_transparency:\s*record\(data\.reference_transparency\)/); assert.match(source, /vedastro_gateway:\s*record\(data\.vedastro_gateway\)/); + assert.match(source, /ashtakavarga:\s*chart\.ashtakavarga/); assert.match(source, /high_similarity_public_references_available/); assert.match(source, /requested_uncovered_domains/); assert.match(source, /public_context_only/); @@ -16,4 +17,9 @@ test("passes transparent public-case references into the agent context", () => { assert.match(source, /transit_status/); assert.match(source, /Jupiter and Saturn relative houses/); assert.match(source, /exact_triggers as technical trigger points/); + assert.match(source, /production_tuning_allowed=false/); + assert.match(source, /no_majority_vote/); + assert.match(source, /method_variant_not_majority_vote/); + assert.match(source, /Shadbala\/Ashtakavarga component differences/); + assert.match(source, /D2, D11/); }); diff --git a/references/oracle/shadbala_av_component_provenance_registry_2026_07_19.json b/references/oracle/shadbala_av_component_provenance_registry_2026_07_19.json new file mode 100644 index 00000000..ff844a05 --- /dev/null +++ b/references/oracle/shadbala_av_component_provenance_registry_2026_07_19.json @@ -0,0 +1,110 @@ +{ + "production_tuning_allowed": false, + "registry": [ + { + "allowed_claim": "table_variant", + "category": "ashtakavarga_table_or_contributor_variant", + "component_family": "ashtakavarga", + "next_evidence_required": "public worked table with same contributor semantics and row/column schema.", + "row_count": 8, + "sample_fields": [ + "Sun", + "Moon", + "Mars", + "Mercury", + "Jupiter", + "Venus", + "Saturn", + "12_sign_scores" + ], + "sections": [ + "ashtakavarga_bav", + "ashtakavarga_sav" + ], + "truth_status": "classified_unresolved", + "unit_contract": "BAV/SAV tables must name contributor set, shodhana state, and Lagna inclusion." + }, + { + "allowed_claim": "derived_total_blocked_until_components_close", + "category": "derived_total_from_component_variants", + "component_family": "shadbala_total", + "next_evidence_required": "close sthana/dig/kala/chesta/naisargika/drik first, then recompute totals.", + "row_count": 7, + "sample_fields": [ + "Sun", + "Moon", + "Mars", + "Mercury", + "Jupiter", + "Venus", + "Saturn" + ], + "sections": [ + "shadbala_total" + ], + "truth_status": "classified_unresolved", + "unit_contract": "Total Rupa/Virupa cannot be arbitrated before component units close." + }, + { + "allowed_claim": "current_target_observation_only", + "category": "endpoint_or_varga_semantics", + "component_family": "varga_endpoint", + "next_evidence_required": "identified endpoint contract for D2/D4/D9/D10 ayanamsa/node/method.", + "row_count": 10, + "sample_fields": [ + "Sun.sign", + "Moon.sign", + "Mars.sign", + "Mercury.sign", + "Jupiter.sign", + "Venus.sign", + "Saturn.sign", + "Moon.sign" + ], + "sections": [ + "D10", + "D2", + "D4", + "D9" + ], + "truth_status": "classified_unresolved", + "unit_contract": "Sign values only; endpoint must prove requested varga/method semantics." + }, + { + "allowed_claim": "component_method_variant", + "category": "shadbala_formula_variant", + "component_family": "shadbala_components", + "next_evidence_required": "worked example or source text for each six-force component formula and unit.", + "row_count": 35, + "sample_fields": [ + "Sun.sthana", + "Sun.kala", + "Sun.dig", + "Sun.chesta", + "Sun.drik", + "Moon.sthana", + "Moon.kala", + "Moon.dig" + ], + "sections": [ + "shadbala_components" + ], + "truth_status": "classified_unresolved", + "unit_contract": "Virupa/Rupa component unit must be explicit before parity claims." + } + ], + "scope": "shadbala_av_component_provenance_registry", + "source_arbitration": "references/oracle/three_engine_mismatch_arbitration_2026_07_19.json", + "status": "classified_unresolved", + "summary": { + "category_counts": { + "ashtakavarga_table_or_contributor_variant": 8, + "derived_total_from_component_variants": 7, + "endpoint_or_varga_semantics": 10, + "shadbala_formula_variant": 35 + }, + "registry_count": 4, + "source_mismatch_count": 60 + }, + "truth_policy": "method_variant_not_majority_vote" +} diff --git a/references/oracle/vedastro_reproducible_build_identity_2026_07_17.json b/references/oracle/vedastro_reproducible_build_identity_2026_07_17.json new file mode 100644 index 00000000..1004a0c1 --- /dev/null +++ b/references/oracle/vedastro_reproducible_build_identity_2026_07_17.json @@ -0,0 +1,39 @@ +{ + "base_images": [ + "mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated7.0", + "mcr.microsoft.com/dotnet/sdk:7.0" + ], + "boundary": "Identifies the pinned local candidate only; it does not identify api.vedastro.org.", + "dockerfile_path": "API/Dockerfile", + "dockerfile_sha256": "5a22647f6402e22e583c03eedde1a2cee0661baa262e07efe36c4fcab90203b2", + "image_id": "", + "project_file_hashes": { + "API/API.csproj": "320c08b14d3b89f453c855ba08e4bb4cc03c379f540e7db0ffc07035b2fda173", + "APITester/APITester.csproj": "677e6b436b830fcd4495643b03f3a7a74f06601eac8140aa27c07f9377cecd7c", + "Console/Console.csproj": "c93f66eed9bb2ef4da31642802e00876f7eb733d0c4b06802774e678ddff4a78", + "Desktop/APILauncher/APILauncher.csproj": "600f96b7cb38786451438265fc9db06f6a8a4e3efdb8effb1d8fa4f69d9630b2", + "Desktop/Desktop.csproj": "bf4f16228da6708be85955349557810dcff2b550473e2cc504c0432e96d11347", + "Desktop/Windows/Desktop_Windows.csproj": "b118be0e8025862c27e5bfb4beb4584db19009695b7f6995ee2c1ad0e073c12f", + "DocToEmbeddings/DocToEmbeddings.csproj": "a3b92db8e84a5cad287fc3d769c7b4f7ad4992ff30b18cf2b47a246fcabc2559", + "LLMCoder/LLMCoder.csproj": "1d3905a1059e2dd5b677c1e10b6d9b8e256a7a8189dc013e5c1147d1cc2106e6", + "Library/Library.csproj": "d74bc184a225c77247c29ee74542f3ba4daf67912d32b587da7a30a3aa78a2b2", + "LibraryTests/LibraryTests.csproj": "2fe83ecbb72455dcd0df2e4744a6407acba526b61b747a835bc5944164626f35", + "MatchMLPipeline/MatchMLPipeline.csproj": "610a80be75226cdfa4ad70ea278d1bd14782856445c0dac03bcf47ccd62065fb", + "MigrateGeoLocationData/MigrateGeoLocationData.csproj": "ded1da7b81c85fa44b61df66b376ae1fea94230283051f5f6f1adf0373d395e6", + "Others/ArchivedCode/APITests/APITests.csproj": "cf687a39e84fb91bee847e5544a41abb8c3f911c305aa6bf13f78c73f66d1c89", + "Others/ArchivedCode/Compatibility.Desktop/Compatibility.Desktop.csproj": "1102b834b79badf241671faeb799a3f9acc5fa93e6dfe6cddf4bc45bf21a6bda", + "Others/ArchivedCode/Horoscope.Desktop/Horoscope.Desktop.csproj": "1341b72198bf757da183656b02630cb6a416de808acdc1eb75ba82157f07e2a3", + "Others/ArchivedCode/Muhurtha.Desktop/Muhurtha.Desktop.csproj": "4ac800f5bfc468703f62dc2c0a997bf9e67d14d9a7be772f226177eae6caebfb", + "Others/ArchivedCode/Tests/Tests.csproj": "f84e998698fdf6e8f007b9a01be94b07bf8de8877c3ea6679f2c3a4b755e7b0f", + "Others/MLTableGenerator-MK3/MLTableGenerator.csproj": "7dcf5b42246077d47f383cf023fa33e7c5b485b9fd260c949ad579c67e0490ea", + "Publisher/Publisher.csproj": "64622175d2a669f80c788e684d49bfad26eb34c3f2343b4fed862941bc96ae98", + "StaticTableGenerator/StaticTableGenerator.csproj": "a2779e770268badcf8ebd92c20a3e927210429c4621d248dac406d7efff5f6c7", + "Website/Website.csproj": "81b8b712286839f15472afa4dc2b449f6616d421b51ba86ac7c8f21ad2acc257", + "Website_Mobile/Website_Mobile.csproj": "2e0e1232a24295326ea26cbb5c48cd1b01efd8e20bf6c9d9e41b798746219e48" + }, + "repo_digests": [], + "scope": "vedastro_reproducible_build_identity", + "source_commit": "1f3a46482ab98eb414ab03fa44496a37d35a3a96", + "source_root": "/private/tmp/VedAstro-official", + "status": "source_pinned_image_not_built" +} diff --git a/scripts/day_level_negative_holdout_intake.py b/scripts/day_level_negative_holdout_intake.py new file mode 100644 index 00000000..c1e39682 --- /dev/null +++ b/scripts/day_level_negative_holdout_intake.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Append independently sourced day-level timing holdout annotations.""" +from __future__ import annotations + +import argparse +import json +from datetime import datetime, timezone +from pathlib import Path + +from scripts.day_level_holdout_validator import REQUIRED, validate + + +def _row_errors(row: dict, prohibited: set[str]) -> list[dict]: + errors = [] + for key in sorted(REQUIRED - set(row)): + errors.append({"field": key, "error": "missing"}) + if row.get("label") not in {"target_event", "no_target_event"}: + errors.append({"field": "label", "error": "invalid"}) + if not str(row.get("source_url") or "").startswith(("https://", "http://")): + errors.append({"field": "source_url", "error": "not_public_url"}) + if row.get("independent_human_reviewed") is not True: + errors.append({"field": "independent_human_reviewed", "error": "not_independently_human_reviewed"}) + if row.get("source_path") in prohibited: + errors.append({"field": "source_path", "error": "prohibited_tuning_source"}) + return errors + + +def append_annotation(path: Path, row: dict) -> dict: + data = json.loads(path.read_text(encoding="utf-8")) + prohibited = set(data.get("prohibited_tuning_data") or []) + errors = _row_errors(row, prohibited) + if errors: + return {"appended": False, "errors": errors, "validation": validate(path)} + next_row = { + **row, + "frozen_before_scoring": True, + "ingested_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), + } + data.setdefault("annotations", []).append(next_row) + path.write_text(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return {"appended": True, "errors": [], "validation": validate(path)} + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("manifest", type=Path) + parser.add_argument("--row-json", required=True, help="One annotation JSON object.") + args = parser.parse_args() + print(json.dumps(append_annotation(args.manifest, json.loads(args.row_json)), ensure_ascii=False, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/shadbala_av_component_registry.py b/scripts/shadbala_av_component_registry.py new file mode 100644 index 00000000..0f924401 --- /dev/null +++ b/scripts/shadbala_av_component_registry.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Build Shadbala/Ashtakavarga component provenance registry from mismatch arbitration.""" +from __future__ import annotations + +import argparse +import json +from collections import Counter, defaultdict +from pathlib import Path +from typing import Any + + +CATEGORY_POLICY = { + "shadbala_formula_variant": { + "component_family": "shadbala_components", + "unit_contract": "Virupa/Rupa component unit must be explicit before parity claims.", + "allowed_claim": "component_method_variant", + "next_evidence_required": "worked example or source text for each six-force component formula and unit.", + }, + "derived_total_from_component_variants": { + "component_family": "shadbala_total", + "unit_contract": "Total Rupa/Virupa cannot be arbitrated before component units close.", + "allowed_claim": "derived_total_blocked_until_components_close", + "next_evidence_required": "close sthana/dig/kala/chesta/naisargika/drik first, then recompute totals.", + }, + "ashtakavarga_table_or_contributor_variant": { + "component_family": "ashtakavarga", + "unit_contract": "BAV/SAV tables must name contributor set, shodhana state, and Lagna inclusion.", + "allowed_claim": "table_variant", + "next_evidence_required": "public worked table with same contributor semantics and row/column schema.", + }, + "endpoint_or_varga_semantics": { + "component_family": "varga_endpoint", + "unit_contract": "Sign values only; endpoint must prove requested varga/method semantics.", + "allowed_claim": "current_target_observation_only", + "next_evidence_required": "identified endpoint contract for D2/D4/D9/D10 ayanamsa/node/method.", + }, +} + + +def build_registry(arbitration_path: str | Path) -> dict[str, Any]: + path = Path(arbitration_path) + arbitration = json.loads(path.read_text(encoding="utf-8")) + grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) + for row in arbitration.get("rows") or []: + grouped[row["category"]].append(row) + registry = [] + for category, rows in sorted(grouped.items()): + policy = CATEGORY_POLICY.get(category, { + "component_family": "unknown", + "unit_contract": "unknown", + "allowed_claim": "current_target_observation_only", + "next_evidence_required": "manual provenance review required.", + }) + registry.append({ + "category": category, + "component_family": policy["component_family"], + "row_count": len(rows), + "sections": sorted({str(row.get("section")) for row in rows}), + "sample_fields": [str(row.get("field")) for row in rows[:8]], + "unit_contract": policy["unit_contract"], + "allowed_claim": policy["allowed_claim"], + "next_evidence_required": policy["next_evidence_required"], + "truth_status": "classified_unresolved", + }) + return { + "scope": "shadbala_av_component_provenance_registry", + "source_arbitration": str(path), + "status": "classified_unresolved", + "truth_policy": "method_variant_not_majority_vote", + "production_tuning_allowed": False, + "summary": { + "source_mismatch_count": arbitration.get("mismatch_count", 0), + "registry_count": len(registry), + "category_counts": dict(Counter({row["category"]: row["row_count"] for row in registry})), + }, + "registry": registry, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("arbitration", nargs="?", default="references/oracle/three_engine_mismatch_arbitration_2026_07_19.json") + parser.add_argument("--output", type=Path) + args = parser.parse_args() + registry = build_registry(args.arbitration) + text = json.dumps(registry, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(text, encoding="utf-8") + print(text, end="") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/vedastro_reproducible_build_probe.py b/scripts/vedastro_reproducible_build_probe.py new file mode 100644 index 00000000..14d3ccf3 --- /dev/null +++ b/scripts/vedastro_reproducible_build_probe.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Create a secret-free identity contract for a pinned VedAstro container build.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import subprocess +from pathlib import Path +from typing import Any + + +def _sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _git_commit(root: Path) -> str: + completed = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=root, text=True, capture_output=True, check=False + ) + return completed.stdout.strip() if completed.returncode == 0 else "" + + +def _inspect_image(tag: str) -> dict[str, Any] | None: + completed = subprocess.run( + ["docker", "image", "inspect", tag], text=True, capture_output=True, check=False + ) + if completed.returncode != 0: + return None + payload = json.loads(completed.stdout) + return payload[0] if isinstance(payload, list) and payload else None + + +def build_identity( + source_root: Path, + *, + source_commit: str | None = None, + image_inspect: dict[str, Any] | None = None, +) -> dict[str, Any]: + source_root = source_root.resolve() + dockerfile = source_root / "API/Dockerfile" + if not dockerfile.is_file(): + raise FileNotFoundError(dockerfile) + docker_text = dockerfile.read_text(encoding="utf-8") + base_images: list[str] = [] + stage_names: set[str] = set() + for match in re.finditer( + r"^FROM\s+([^\s]+)(?:\s+AS\s+([^\s]+))?", + docker_text, + flags=re.MULTILINE | re.IGNORECASE, + ): + image, stage = match.group(1), match.group(2) + if image not in stage_names: + base_images.append(image) + if stage: + stage_names.add(stage) + project_files = sorted(source_root.glob("**/*.csproj")) + project_hashes = { + str(path.relative_to(source_root)): _sha256(path) + for path in project_files + if "/bin/" not in path.as_posix() and "/obj/" not in path.as_posix() + } + image_id = (image_inspect or {}).get("Id", "") + repo_digests = (image_inspect or {}).get("RepoDigests") or [] + return { + "scope": "vedastro_reproducible_build_identity", + "source_root": str(source_root), + "source_commit": source_commit or _git_commit(source_root), + "dockerfile_path": "API/Dockerfile", + "dockerfile_sha256": _sha256(dockerfile), + "base_images": base_images, + "project_file_hashes": project_hashes, + "image_id": image_id, + "repo_digests": repo_digests, + "status": "reproducible_candidate_built" if image_id else "source_pinned_image_not_built", + "boundary": "Identifies the pinned local candidate only; it does not identify api.vedastro.org.", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("source_root", type=Path) + parser.add_argument("--image-tag", default="") + parser.add_argument("--output", type=Path) + args = parser.parse_args() + report = build_identity( + args.source_root, + image_inspect=_inspect_image(args.image_tag) if args.image_tag else None, + ) + text = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(text, encoding="utf-8") + print(text, end="") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_real_case_timing_optimization_audit.py b/tests/test_real_case_timing_optimization_audit.py new file mode 100644 index 00000000..2d8456df --- /dev/null +++ b/tests/test_real_case_timing_optimization_audit.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DOC = ROOT / "docs" / "research" / "real_case_timing_optimization_audit_2026_07_19.md" + + +def test_real_case_timing_audit_separates_positive_replay_from_specificity() -> None: + text = DOC.read_text(encoding="utf-8") + + for token in [ + "Positive-event replay is healthy", + "known positive events only", + "does not verify day/month predictive specificity", + "Day-level negative holdout remains empty", + "status = awaiting_independent_labels", + "day_level_holdout_v3_pilot_source_queue_2026_07_19.json", + "not a holdout manifest", + "production_tuning_allowed = false", + "claim_status = exploratory_unvalidated", + "Forbidden UX: packaging candidate dates as verified event promises", + ]: + assert token in text diff --git a/tests/test_shadbala_av_component_registry.py b/tests/test_shadbala_av_component_registry.py new file mode 100644 index 00000000..c090593e --- /dev/null +++ b/tests/test_shadbala_av_component_registry.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from scripts.shadbala_av_component_registry import build_registry + +ROOT = Path(__file__).resolve().parents[1] +ARBITRATION = ROOT / "references" / "oracle" / "three_engine_mismatch_arbitration_2026_07_19.json" + + +def test_component_registry_groups_shadbala_and_av_mismatches_without_truth_upgrade() -> None: + registry = build_registry(ARBITRATION) + + assert registry["scope"] == "shadbala_av_component_provenance_registry" + assert registry["truth_policy"] == "method_variant_not_majority_vote" + assert registry["status"] == "classified_unresolved" + assert registry["production_tuning_allowed"] is False + assert registry["summary"]["source_mismatch_count"] == 60 + assert registry["summary"]["registry_count"] >= 4 + + categories = {row["category"] for row in registry["registry"]} + assert "shadbala_formula_variant" in categories + assert "derived_total_from_component_variants" in categories + assert "ashtakavarga_table_or_contributor_variant" in categories + + for row in registry["registry"]: + assert row["allowed_claim"] in { + "current_target_observation_only", + "component_method_variant", + "table_variant", + "derived_total_blocked_until_components_close", + } + assert row["unit_contract"] + assert row["next_evidence_required"] + + +def test_component_registry_json_artifact_matches_source_counts() -> None: + artifact = ROOT / "references" / "oracle" / "shadbala_av_component_provenance_registry_2026_07_19.json" + data = json.loads(artifact.read_text(encoding="utf-8")) + + assert data["summary"]["source_mismatch_count"] == 60 + assert data["summary"]["category_counts"]["shadbala_formula_variant"] == 35 + assert data["summary"]["category_counts"]["derived_total_from_component_variants"] == 7 + assert data["summary"]["category_counts"]["ashtakavarga_table_or_contributor_variant"] == 8 + assert data["production_tuning_allowed"] is False + + +def test_component_provenance_markdown_report_is_human_readable() -> None: + report = ROOT / "docs" / "research" / "shadbala_av_component_provenance_report_2026_07_19.md" + text = report.read_text(encoding="utf-8") + + for token in [ + "Shadbala / AV component provenance report", + "method_variant_not_majority_vote", + "Production tuning: `false`", + "`shadbala_formula_variant`", + "`derived_total_from_component_variants`", + "`ashtakavarga_table_or_contributor_variant`", + "Total Rupa/Virupa cannot be arbitrated until component units close", + "Next source-evidence queue", + ]: + assert token in text diff --git a/tests/test_vedastro_reproducible_build_probe.py b/tests/test_vedastro_reproducible_build_probe.py new file mode 100644 index 00000000..4f461196 --- /dev/null +++ b/tests/test_vedastro_reproducible_build_probe.py @@ -0,0 +1,33 @@ +from pathlib import Path + +from scripts.vedastro_reproducible_build_probe import build_identity + + +def test_build_identity_binds_source_and_container_inputs(tmp_path: Path) -> None: + (tmp_path / "API").mkdir() + (tmp_path / "API/Dockerfile").write_text( + "FROM example/sdk:7@sha256:" + "a" * 64 + " AS build\nFROM build AS final\n", + encoding="utf-8", + ) + (tmp_path / "API/API.csproj").write_text("\n", encoding="utf-8") + report = build_identity(tmp_path, source_commit="1f3a464", image_inspect=None) + + assert report["source_commit"] == "1f3a464" + assert len(report["dockerfile_sha256"]) == 64 + assert report["base_images"] == ["example/sdk:7@sha256:" + "a" * 64] + assert len(report["project_file_hashes"]["API/API.csproj"]) == 64 + assert report["status"] == "source_pinned_image_not_built" + + +def test_build_identity_records_built_image_digest(tmp_path: Path) -> None: + (tmp_path / "API").mkdir() + (tmp_path / "API/Dockerfile").write_text("FROM example/sdk:7\n", encoding="utf-8") + report = build_identity( + tmp_path, + source_commit="abc123", + image_inspect={"Id": "sha256:" + "b" * 64, "RepoDigests": ["repo@sha256:" + "c" * 64]}, + ) + + assert report["status"] == "reproducible_candidate_built" + assert report["image_id"] == "sha256:" + "b" * 64 + assert report["repo_digests"] == ["repo@sha256:" + "c" * 64]