diff --git a/docs/research/commercial_web_backport_boundary_2026_07_19.md b/docs/research/commercial_web_backport_boundary_2026_07_19.md new file mode 100644 index 00000000..6da7234e --- /dev/null +++ b/docs/research/commercial_web_backport_boundary_2026_07_19.md @@ -0,0 +1,42 @@ +# Research-to-commercial capability boundary — 2026-07-19 + +Purpose: keep the asset flow honest. Research repo is the personal core asset and source of astrological capability. Commercial repo productizes stable research contracts for users. + +## Research capabilities that commercial may productize + +| Capability | Research web status | Evidence | +|---|---|---| +| Daily starlanguage entry | local research UX exists | `jyotish-app/index.html#daily-guidance-card`; guarded by `tests/test_frontend_productization.py` | +| Birth-time rectification entry | local research UX exists | `jyotish-app/index.html#entry-rectification`; guarded by `tests/test_frontend_productization.py` | +| Local chart library | local research UX exists | `jyotish-app/index.html#saved-chart-panel`; `jyotish-app/main.js#saveCurrentChartToLibrary`; guarded by `tests/test_frontend_productization.py` | +| Display-name field | local research profile exists | `jyotish-app/index.html#profile-display-name`; `PROFILE_DISPLAY_NAME_KEY`; guarded by `tests/test_frontend_productization.py` | +| Chat history actions | local research sessions exist | rename/share/archive/delete in `jyotish-app/main.js`; guarded by `tests/test_frontend_productization.py` | + +## Not equivalent by design + +| Commercial capability | Research boundary | +|---|---| +| Supabase `/api/account` profile save | Research web is static/local-first; it must not claim cloud profile persistence. | +| `profiles` service-role upsert grants | Commercial database migration only; research repo may document it but should not require Supabase for local research web. | +| Cookie-authenticated account route | Commercial runtime only; research web stores display name locally. | +| Credits, billing, subscriptions | Commercial-only business layer; never part of research repo capability. | + +## Claim rule + +Research web may say: +- “research capability has a local UX/reference implementation” +- “profile display name and sessions persist in localStorage” +- “stable research contracts can be synced outward to commercial” + +Research web must not say: +- “cloud profile persistence is equivalent to commercial” +- “Supabase account/profile upsert is available in the static research site” +- “research web and commercial web are 100% identical” +- “commercial credits, billing, subscriptions, or account entitlements are research capabilities” + +## Current optimization priority + +1. Improve real research capability first; sync stable contracts outward to commercial. +2. Keep timing claims exploratory until independent negative holdout labels exist. +3. Keep external oracle mismatch reports as attribution, not majority-vote truth. +4. Keep commercial account/payment/runtime details out of research repo except as deployment contract notes. diff --git a/docs/research/day_level_holdout_labeling_guide_zh.md b/docs/research/day_level_holdout_labeling_guide_zh.md new file mode 100644 index 00000000..b397012b --- /dev/null +++ b/docs/research/day_level_holdout_labeling_guide_zh.md @@ -0,0 +1,76 @@ +# 日级应期 holdout 人工标签指南 + +目标:给研究仓提供真实、独立、可复验的正/负样本。你不需要会编程,只需要提供来源清楚的事实材料。 + +## 需要收集什么 + +每条标签只回答一个问题: + +某人在某个日期区间,某类事件是否发生? + +可用标签: + +- `target_event`:事件发生了。 +- `no_target_event`:有公开资料支持该区间没有发生这个目标事件。 + +## 优先领域 + +1. 事业:任命、创办公司、上市、获奖、重大作品发布。 +2. 婚恋:结婚、离婚、订婚、公开伴侣关系变化。 +3. 财富:上市、重大融资、破产、重大资产事件。 + +暂不优先健康/死亡,噪音和伦理风险高。 + +## 合格来源 + +优先: + +- 官方 biography / timeline +- Britannica / Nobel / company official timeline +- 出版传记中可核对页码或章节的时间线 +- IMDb / MusicBrainz / company history 等结构化公开资料 + +不合格: + +- “没搜到新闻所以没发生” +- ChatGPT 生成内容 +- 无来源论坛故事 +- 已被本项目观察过的旧控制日期 +- 模糊说法:“那一年很平静” + +## 最小可用规模 + +pilot 阶段: + +- 3 个公开人物 +- 每人 1 个领域 +- 每人 1 个正样本窗口 +- 每人 2 个负样本窗口 + +正式升级门槛: + +- 至少 20 个独立案例 +- 至少 80 个独立负样本区间 +- 标签冻结后才允许评分 + +## 填写方式 + +生成空模板: + +```bash +python3 scripts/day_level_holdout_template.py --output /tmp/holdout_annotation_template.json +``` + +把公开来源、日期区间、事件说明填进去,再交给 intake: + +```bash +python3 scripts/day_level_negative_holdout_intake.py references/real_case_calibration/day_level_holdout_v3_preregistration.json --row-json '{"case_id":"..."}' +``` + +## 结论边界 + +没有真实独立负样本前: + +- 可以输出候选日期排序; +- 可以说明触发信号; +- 不能说“精确日期预测已验证”。 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/docs/research/shadbala_av_normative_benchmark_plan_2026_07_19.md b/docs/research/shadbala_av_normative_benchmark_plan_2026_07_19.md new file mode 100644 index 00000000..2c3c73a4 --- /dev/null +++ b/docs/research/shadbala_av_normative_benchmark_plan_2026_07_19.md @@ -0,0 +1,75 @@ +# Shadbala / Ashtakavarga normative benchmark plan — 2026-07-19 + +## Current status + +Current Shadbala target set is closed, but global tuning is not allowed. + +- current target set: `external_verified` +- `can_claim_shadbala_absolute_closure = true` +- `production_tuning_allowed = false` +- three-engine mismatch report: 60 mismatches, 60 classified, 0 unclassified +- Shadbala formula/component variants: 35 component rows + 7 derived-total rows +- Ashtakavarga table/contributor variants: 8 rows + +## What this means + +The research repo can claim closure for the current curated Shadbala target package. It must not claim universal Shadbala or AV truth across all schools/software. + +## Required benchmark ladder + +1. Source provenance + - VP Jain: page/edition/source hash or explicit retrieval gap. + - Xalen: source commit, package hash, executable hash if available, method list. + - PyJHora/JHora: isolated AGPL oracle only; no copied implementation. + - jyotishganit: permissive observation adapter where license permits. + +2. Component normalization + - `sthana` + - `dig` + - `kala` + - `chesta` + - `naisargika` + - `drik` + - `total_rupa` + +3. Unit contract + - Virupa vs Rupa must be explicit. + - Totals cannot be arbitrated before components. + - Component caps/floors must be named. + +4. Formula variant registry + - legitimate school variants become `method_variant`; + - unresolved variants stay `classified_unresolved`; + - no majority vote. + +5. Independent ephemeris mode + - same raw input mode isolates formula layer; + - independent ephemeris mode checks longitude/ayanamsa layer; + - both must be reported separately. + +## Commercial sync rule + +Commercial may receive: + +- readiness status; +- user-safe confidence cap; +- claim boundary; +- supported component list. + +Commercial must not receive: + +- unresolved raw research debt; +- forced absolute Virupa truth; +- AGPL implementation code; +- production tuning flag while `production_tuning_allowed = false`. + +## Next implementation task + +Create a component provenance registry that maps each Shadbala/AV mismatch category to: + +- source artifact; +- component; +- unit; +- likely reason; +- allowed claim; +- next evidence required. diff --git a/docs/research/three_engine_mismatch_arbitration_2026_07_19.md b/docs/research/three_engine_mismatch_arbitration_2026_07_19.md new file mode 100644 index 00000000..be8807cb --- /dev/null +++ b/docs/research/three_engine_mismatch_arbitration_2026_07_19.md @@ -0,0 +1,27 @@ +# Three-engine mismatch arbitration + +manifest: `references/oracle/three_engine_parity_replay_manifest.json` +status: `classified_unresolved` +truth_policy: `no_majority_vote` +commercial_sync: `status_and_claim_boundary_only` +mismatch_count: `60` +classified_count: `60` +unclassified_count: `0` + +Do not copy raw research debt into commercial runtime. Commercial receives readiness, claim boundary, and user-safe status only. + +## Category counts + +| category | count | +|---|---:| +| `ashtakavarga_table_or_contributor_variant` | 8 | +| `derived_total_from_component_variants` | 7 | +| `endpoint_or_varga_semantics` | 10 | +| `shadbala_formula_variant` | 35 | + +## Closure requirements + +- `endpoint_or_varga_semantics`: Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract. +- `ashtakavarga_table_or_contributor_variant`: Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics. +- `derived_total_from_component_variants`: Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned. +- `shadbala_formula_variant`: Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals. diff --git a/docs/research/vedastro_identity_closure_plan_2026_07_19.md b/docs/research/vedastro_identity_closure_plan_2026_07_19.md new file mode 100644 index 00000000..55ebf819 --- /dev/null +++ b/docs/research/vedastro_identity_closure_plan_2026_07_19.md @@ -0,0 +1,61 @@ +# VedAstro identity closure plan — 2026-07-19 + +## Current status + +VedAstro hosted API remains observation-only. + +Reason: hosted output can be replayed, but build identity, method semantics, and deployment version are not archived. Stable mismatch replay proves the conflict is not random transport noise; it does not prove which side is true. + +## Acceptable closure paths + +### Path A — hosted metadata + +Required from upstream: + +- hosted build version; +- source commit or release tag; +- method semantic contract for each endpoint used; +- ayanamsa/node/timezone interpretation; +- deployment timestamp or immutable build ID. + +### Path B — pinned self-hosted version + +Required archive: + +- source commit; +- NuGet package hash; +- DLL SHA-256; +- assembly version; +- public method inventory; +- container image digest or reproducible local runner hash. + +`scripts/vedastro_identity_archive.py` now records the NuGet identity and a `required_self_host_evidence` checklist. Missing fields keep truth upgrade blocked. + +## Claim boundary + +Allowed: + +- use VedAstro as external observation; +- report stable conflicts; +- compare raw response hashes and normalized fields; +- use pinned self-hosted evidence if all required identity fields are present. + +Forbidden: + +- tune production predictions from hosted output with unknown build identity; +- call hosted mismatch arbitration a global truth decision; +- silently prefer VedAstro or local output by majority vote. + +## Commercial sync rule + +Commercial may receive: + +- `VedAstro: observation_only`; +- endpoint status; +- claim boundary text. + +Commercial must not receive: + +- raw hosted credentials; +- hosted output as truth; +- production tuning permission while `truth_upgrade_gate` is blocked. diff --git a/frontend/src/mastra/index.ts b/frontend/src/mastra/index.ts index 9718b553..6b6531e7 100644 --- a/frontend/src/mastra/index.ts +++ b/frontend/src/mastra/index.ts @@ -120,6 +120,7 @@ export function toAgentConsultationContext(data: JsonRecord) { houses: chart.houses, dasha: chart.dasha, shadbala: chart.shadbala, + ashtakavarga: chart.ashtakavarga, yogas: chart.yogas, }, local_layers: { @@ -172,9 +173,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. - Treat evidence_contract.answer_policy as a hard output contract. When can_answer_precise_timing is false, provide only direction or structure and do not state a month, date, or guaranteed timing outcome. - Treat rectification.boundary=not_auto_rectified as final: a candidate time or score is not a verified birth time and must not be presented as one. Usually answer in 2-5 short paragraphs. Ask one clarifying question only when the user's intent is genuinely unclear. diff --git a/frontend/tests/consultation-context.test.ts b/frontend/tests/consultation-context.test.ts index 0d16b2d7..591b6845 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,6 +17,11 @@ 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/); }); test("keeps strength, Ashtakavarga, and timing evidence available to the answer model", () => { diff --git a/references/cross_project_contract/sync_policy.v1.json b/references/cross_project_contract/sync_policy.v1.json index 0f5f468b..08dc1d96 100644 --- a/references/cross_project_contract/sync_policy.v1.json +++ b/references/cross_project_contract/sync_policy.v1.json @@ -24,6 +24,14 @@ ] } }, + "research_repo_exclusions": [ + "commercial_credits", + "billing", + "subscriptions", + "payment", + "account_entitlements", + "service_role_runtime" + ], "shared_files": [ "references/cross_project_contract/fixture_manifest.v1.json", "references/cross_project_contract/sync_ledger.json", 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/three_engine_mismatch_arbitration_2026_07_19.json b/references/oracle/three_engine_mismatch_arbitration_2026_07_19.json new file mode 100644 index 00000000..3a90137d --- /dev/null +++ b/references/oracle/three_engine_mismatch_arbitration_2026_07_19.json @@ -0,0 +1,1464 @@ +{ + "category_counts": { + "ashtakavarga_table_or_contributor_variant": 8, + "derived_total_from_component_variants": 7, + "endpoint_or_varga_semantics": 10, + "shadbala_formula_variant": 35 + }, + "classified_count": 60, + "manifest_path": "references/oracle/three_engine_parity_replay_manifest.json", + "mismatch_count": 60, + "rows": [ + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Sun.sign", + "local_value": "Leo", + "oracle_values": { + "PyJHora_JHora": "Leo", + "VedAstro": "Gemini", + "jyotishganit": "Leo" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Moon.sign", + "local_value": "Cancer", + "oracle_values": { + "PyJHora_JHora": "Cancer", + "VedAstro": "Pisces", + "jyotishganit": "Cancer" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Mars.sign", + "local_value": "Leo", + "oracle_values": { + "PyJHora_JHora": "Leo", + "VedAstro": "Aries", + "jyotishganit": "Leo" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Mercury.sign", + "local_value": "Leo", + "oracle_values": { + "PyJHora_JHora": "Leo", + "VedAstro": "Virgo", + "jyotishganit": "Leo" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Jupiter.sign", + "local_value": "Cancer", + "oracle_values": { + "PyJHora_JHora": "Cancer", + "VedAstro": "Aquarius", + "jyotishganit": "Cancer" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Venus.sign", + "local_value": "Cancer", + "oracle_values": { + "PyJHora_JHora": "Cancer", + "VedAstro": "Leo", + "jyotishganit": "Cancer" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Saturn.sign", + "local_value": "Cancer", + "oracle_values": { + "PyJHora_JHora": "Cancer", + "VedAstro": "Gemini", + "jyotishganit": "Cancer" + }, + "section": "D2", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Moon.sign", + "local_value": "Gemini", + "oracle_values": { + "PyJHora_JHora": "Gemini", + "VedAstro": "Pisces", + "jyotishganit": "Gemini" + }, + "section": "D4", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Moon.sign", + "local_value": "Scorpio", + "oracle_values": { + "PyJHora_JHora": "Scorpio", + "VedAstro": "Leo", + "jyotishganit": "Scorpio" + }, + "section": "D9", + "truth_status": "unresolved" + }, + { + "category": "endpoint_or_varga_semantics", + "closure_requirement": "Confirm VedAstro endpoint returns the requested varga under the same ayanamsa/node/method contract.", + "differing_engines": [ + "VedAstro" + ], + "field": "Moon.sign", + "local_value": "Pisces", + "oracle_values": { + "PyJHora_JHora": "Pisces", + "VedAstro": "Sagittarius", + "jyotishganit": "Pisces" + }, + "section": "D10", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro" + ], + "field": "Sun", + "local_value": [ + 3, + 7, + 4, + 3, + 3, + 2, + 6, + 7, + 4, + 4, + 3, + 2 + ], + "oracle_values": { + "PyJHora_JHora": [ + 3, + 7, + 4, + 3, + 3, + 2, + 6, + 7, + 4, + 4, + 3, + 2 + ], + "VedAstro": [ + 3, + 7, + 3, + 2, + 3, + 3, + 6, + 7, + 4, + 3, + 4, + 3 + ], + "jyotishganit": [ + 3, + 7, + 4, + 3, + 3, + 2, + 6, + 7, + 4, + 4, + 3, + 2 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Moon", + "local_value": [ + 4, + 4, + 4, + 3, + 6, + 5, + 3, + 3, + 4, + 5, + 3, + 5 + ], + "oracle_values": { + "PyJHora_JHora": [ + 4, + 4, + 4, + 3, + 6, + 5, + 3, + 3, + 4, + 5, + 3, + 5 + ], + "VedAstro": [ + 4, + 5, + 3, + 2, + 6, + 6, + 3, + 2, + 5, + 4, + 4, + 5 + ], + "jyotishganit": [ + 4, + 5, + 4, + 2, + 6, + 5, + 3, + 2, + 5, + 5, + 3, + 5 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro" + ], + "field": "Mars", + "local_value": [ + 4, + 7, + 4, + 4, + 3, + 0, + 4, + 5, + 1, + 4, + 1, + 2 + ], + "oracle_values": { + "PyJHora_JHora": [ + 4, + 7, + 4, + 4, + 3, + 0, + 4, + 5, + 1, + 4, + 1, + 2 + ], + "VedAstro": [ + 4, + 7, + 3, + 4, + 2, + 1, + 4, + 5, + 2, + 3, + 2, + 2 + ], + "jyotishganit": [ + 4, + 7, + 4, + 4, + 3, + 0, + 4, + 5, + 1, + 4, + 1, + 2 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro" + ], + "field": "Mercury", + "local_value": [ + 5, + 5, + 5, + 4, + 4, + 2, + 6, + 5, + 5, + 8, + 2, + 3 + ], + "oracle_values": { + "PyJHora_JHora": [ + 5, + 5, + 5, + 4, + 4, + 2, + 6, + 5, + 5, + 8, + 2, + 3 + ], + "VedAstro": [ + 5, + 5, + 4, + 5, + 3, + 2, + 7, + 4, + 6, + 8, + 2, + 3 + ], + "jyotishganit": [ + 5, + 5, + 5, + 4, + 4, + 2, + 6, + 5, + 5, + 8, + 2, + 3 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro" + ], + "field": "Jupiter", + "local_value": [ + 7, + 5, + 3, + 3, + 4, + 7, + 4, + 5, + 4, + 6, + 5, + 3 + ], + "oracle_values": { + "PyJHora_JHora": [ + 7, + 5, + 3, + 3, + 4, + 7, + 4, + 5, + 4, + 6, + 5, + 3 + ], + "VedAstro": [ + 7, + 5, + 3, + 3, + 4, + 7, + 5, + 4, + 4, + 6, + 4, + 4 + ], + "jyotishganit": [ + 7, + 5, + 3, + 3, + 4, + 7, + 4, + 5, + 4, + 6, + 5, + 3 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Venus", + "local_value": [ + 4, + 3, + 5, + 4, + 3, + 5, + 4, + 3, + 5, + 5, + 5, + 6 + ], + "oracle_values": { + "PyJHora_JHora": [ + 4, + 3, + 5, + 4, + 3, + 5, + 4, + 3, + 5, + 5, + 5, + 6 + ], + "VedAstro": [ + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 2, + 5, + 6, + 6, + 6 + ], + "jyotishganit": [ + 4, + 3, + 5, + 3, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 6 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro" + ], + "field": "Saturn", + "local_value": [ + 1, + 5, + 3, + 0, + 6, + 3, + 4, + 5, + 3, + 3, + 3, + 3 + ], + "oracle_values": { + "PyJHora_JHora": [ + 1, + 5, + 3, + 0, + 6, + 3, + 4, + 5, + 3, + 3, + 3, + 3 + ], + "VedAstro": [ + 1, + 5, + 2, + 0, + 5, + 4, + 4, + 4, + 4, + 2, + 4, + 4 + ], + "jyotishganit": [ + 1, + 5, + 3, + 0, + 6, + 3, + 4, + 5, + 3, + 3, + 3, + 3 + ] + }, + "section": "ashtakavarga_bav", + "truth_status": "unresolved" + }, + { + "category": "ashtakavarga_table_or_contributor_variant", + "closure_requirement": "Compare contributor tables, Lagna inclusion, shodhana state, and BAV/SAV row semantics.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "12_sign_scores", + "local_value": [ + 28, + 36, + 28, + 21, + 29, + 24, + 31, + 33, + 26, + 35, + 22, + 24 + ], + "oracle_values": { + "PyJHora_JHora": [ + 28, + 36, + 28, + 21, + 29, + 24, + 31, + 33, + 26, + 35, + 22, + 24 + ], + "VedAstro": [ + 24, + 28, + 37, + 28, + 19, + 30, + 24, + 31, + 32, + 27, + 35, + 22 + ], + "jyotishganit": [ + 28, + 37, + 28, + 19, + 30, + 24, + 31, + 32, + 27, + 35, + 22, + 24 + ] + }, + "section": "ashtakavarga_sav", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Sun", + "local_value": 480.62, + "oracle_values": { + "PyJHora_JHora": 444.63, + "VedAstro": 395.06, + "jyotishganit": 422.007 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Sun.sthana", + "local_value": 217.09, + "oracle_values": { + "PyJHora_JHora": 217.09, + "VedAstro": 202.0874074074074, + "jyotishganit": 153.341 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Sun.kala", + "local_value": 140.98, + "oracle_values": { + "PyJHora_JHora": 140.99, + "VedAstro": 128.0507461419753, + "jyotishganit": 148.893 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Sun.dig", + "local_value": 24.81, + "oracle_values": { + "PyJHora_JHora": 24.81, + "VedAstro": 24.810925925925925, + "jyotishganit": 29.175 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Sun.chesta", + "local_value": 36.0, + "oracle_values": { + "PyJHora_JHora": 0.0, + "VedAstro": 0.0, + "jyotishganit": 37.06 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Sun.drik", + "local_value": 1.74, + "oracle_values": { + "PyJHora_JHora": 1.74, + "VedAstro": -19.891041666666666, + "jyotishganit": -6.462 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Moon", + "local_value": 584.26, + "oracle_values": { + "PyJHora_JHora": 562.89, + "VedAstro": 421.8, + "jyotishganit": 420.655 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Moon.sthana", + "local_value": 246.34, + "oracle_values": { + "PyJHora_JHora": 246.34, + "VedAstro": 236.69703703703703, + "jyotishganit": 141.341 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Moon.kala", + "local_value": 149.61, + "oracle_values": { + "PyJHora_JHora": 149.58, + "VedAstro": 159.53537962962963, + "jyotishganit": 183.112 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Moon.dig", + "local_value": 95.48, + "oracle_values": { + "PyJHora_JHora": 95.48, + "VedAstro": 52.26824074074074, + "jyotishganit": 20.159 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Moon.chesta", + "local_value": 21.34, + "oracle_values": { + "PyJHora_JHora": 0.0, + "VedAstro": 0.0, + "jyotishganit": 10.667 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Moon.drik", + "local_value": 20.06, + "oracle_values": { + "PyJHora_JHora": 20.06, + "VedAstro": -18.131319444444447, + "jyotishganit": 13.946 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mars", + "local_value": 456.19, + "oracle_values": { + "PyJHora_JHora": 533.18, + "VedAstro": 397.89, + "jyotishganit": 438.227 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Mars.sthana", + "local_value": 198.63, + "oracle_values": { + "PyJHora_JHora": 198.63, + "VedAstro": 221.28777777777776, + "jyotishganit": 232.378 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mars.kala", + "local_value": 127.92, + "oracle_values": { + "PyJHora_JHora": 127.93, + "VedAstro": 148.2544363425926, + "jyotishganit": 106.407 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Mars.dig", + "local_value": 77.41, + "oracle_values": { + "PyJHora_JHora": 77.41, + "VedAstro": 2.3666666666666667, + "jyotishganit": 46.955 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mars.chesta", + "local_value": 21.6, + "oracle_values": { + "PyJHora_JHora": 98.58, + "VedAstro": 21.737741162570256, + "jyotishganit": 22.864 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Mars.drik", + "local_value": 13.49, + "oracle_values": { + "PyJHora_JHora": 13.49, + "VedAstro": -12.978854166666672, + "jyotishganit": 12.483 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mercury", + "local_value": 365.28, + "oracle_values": { + "PyJHora_JHora": 363.24, + "VedAstro": 376.04, + "jyotishganit": 241.84 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Mercury.sthana", + "local_value": 167.96, + "oracle_values": { + "PyJHora_JHora": 167.96, + "VedAstro": 182.95805555555555, + "jyotishganit": 111.704 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mercury.kala", + "local_value": 121.79, + "oracle_values": { + "PyJHora_JHora": 121.78, + "VedAstro": 126.59104166666665, + "jyotishganit": 72.089 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Mercury.dig", + "local_value": 12.64, + "oracle_values": { + "PyJHora_JHora": 12.64, + "VedAstro": 12.643148148148148, + "jyotishganit": 7.954 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mercury.chesta", + "local_value": 47.8, + "oracle_values": { + "PyJHora_JHora": 45.78, + "VedAstro": 47.51070370816793, + "jyotishganit": 35.368 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Mercury.drik", + "local_value": -10.62, + "oracle_values": { + "PyJHora_JHora": -10.63, + "VedAstro": -19.363888888888884, + "jyotishganit": -10.985 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Jupiter", + "local_value": 569.4, + "oracle_values": { + "PyJHora_JHora": 596.89, + "VedAstro": 427.22, + "jyotishganit": 596.043 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Jupiter.sthana", + "local_value": 201.81, + "oracle_values": { + "PyJHora_JHora": 201.8, + "VedAstro": 213.06314814814814, + "jyotishganit": 226.178 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Jupiter.kala", + "local_value": 199.32, + "oracle_values": { + "PyJHora_JHora": 199.31, + "VedAstro": 141.64764506172838, + "jyotishganit": 250.984 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Jupiter.dig", + "local_value": 80.59, + "oracle_values": { + "PyJHora_JHora": 80.6, + "VedAstro": 6.773333333333333, + "jyotishganit": 44.095 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Jupiter.chesta", + "local_value": 46.49, + "oracle_values": { + "PyJHora_JHora": 74.0, + "VedAstro": 46.05491068568356, + "jyotishganit": 36.424 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Jupiter.drik", + "local_value": 6.9, + "oracle_values": { + "PyJHora_JHora": 6.89, + "VedAstro": -14.818125000000006, + "jyotishganit": 4.072 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Venus", + "local_value": 406.93, + "oracle_values": { + "PyJHora_JHora": 408.64, + "VedAstro": 402.56, + "jyotishganit": 306.501 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Venus.sthana", + "local_value": 154.06, + "oracle_values": { + "PyJHora_JHora": 154.06, + "VedAstro": 210.05953703703705, + "jyotishganit": 112.816 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Venus.kala", + "local_value": 113.45, + "oracle_values": { + "PyJHora_JHora": 113.44, + "VedAstro": 97.43954166666667, + "jyotishganit": 63.186 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Venus.dig", + "local_value": 69.95, + "oracle_values": { + "PyJHora_JHora": 69.95, + "VedAstro": 29.630740740740737, + "jyotishganit": 45.684 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Venus.chesta", + "local_value": 29.36, + "oracle_values": { + "PyJHora_JHora": 31.08, + "VedAstro": 30.84685510809705, + "jyotishganit": 46.466 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Venus.drik", + "local_value": -2.75, + "oracle_values": { + "PyJHora_JHora": -2.75, + "VedAstro": -8.51038194444445, + "jyotishganit": -4.511 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "derived_total_from_component_variants", + "closure_requirement": "Do not arbitrate totals until all six component variants and Virupa/Rupa units are aligned.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Saturn", + "local_value": 430.05, + "oracle_values": { + "PyJHora_JHora": 429.42, + "VedAstro": 448.58, + "jyotishganit": 322.113 + }, + "section": "shadbala_total", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Saturn.sthana", + "local_value": 218.61, + "oracle_values": { + "PyJHora_JHora": 218.61, + "VedAstro": 239.23268518518518, + "jyotishganit": 151.104 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Saturn.kala", + "local_value": 136.76, + "oracle_values": { + "PyJHora_JHora": 136.77, + "VedAstro": 140.7160763888889, + "jyotishganit": 71.929 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Saturn.dig", + "local_value": 19.63, + "oracle_values": { + "PyJHora_JHora": 19.62, + "VedAstro": 19.623796296296295, + "jyotishganit": 24.313 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "PyJHora_JHora", + "VedAstro", + "jyotishganit" + ], + "field": "Saturn.chesta", + "local_value": 36.99, + "oracle_values": { + "PyJHora_JHora": 36.36, + "VedAstro": 39.438983295527784, + "jyotishganit": 58.137 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + }, + { + "category": "shadbala_formula_variant", + "closure_requirement": "Compare component formula, units, local solar context, aspect model, and Chesta lineage before totals.", + "differing_engines": [ + "VedAstro", + "jyotishganit" + ], + "field": "Saturn.drik", + "local_value": 9.49, + "oracle_values": { + "PyJHora_JHora": 9.49, + "VedAstro": 0.9979513888888878, + "jyotishganit": 8.06 + }, + "section": "shadbala_components", + "truth_status": "unresolved" + } + ], + "scope": "three_engine_field_level_mismatch_arbitration", + "status": "classified_unresolved", + "truth_policy": "no_majority_vote", + "unclassified_count": 0 +} diff --git a/references/oracle/vedastro_identity_archive_2026_07_19.json b/references/oracle/vedastro_identity_archive_2026_07_19.json new file mode 100644 index 00000000..548a9f42 --- /dev/null +++ b/references/oracle/vedastro_identity_archive_2026_07_19.json @@ -0,0 +1,76 @@ +{ + "boundary": "This archive fixes a NuGet self-host candidate identity only; it does not prove the hosted API is running this package or the same method semantics.", + "catalog_commit_id": "c707690b-f7bd-4813-a2cb-876e943e9667", + "catalog_commit_timestamp": "2023-03-22T22:24:11.811524Z", + "dependencies": [ + { + "id": "ByteSize", + "range": "[2.1.1, )", + "target_framework": "net7.0" + }, + { + "id": "Google.Apis", + "range": "[1.51.0, )", + "target_framework": "net7.0" + }, + { + "id": "Google.Apis.Auth", + "range": "[1.51.0, )", + "target_framework": "net7.0" + }, + { + "id": "Google.Apis.Calendar.v3", + "range": "[1.51.0.2237, )", + "target_framework": "net7.0" + }, + { + "id": "Google.Apis.Core", + "range": "[1.51.0, )", + "target_framework": "net7.0" + }, + { + "id": "Microsoft.AspNetCore.Components.WebAssembly", + "range": "[6.0.11, )", + "target_framework": "net7.0" + }, + { + "id": "Microsoft.Extensions.Caching.Memory", + "range": "[6.0.1, )", + "target_framework": "net7.0" + }, + { + "id": "Microsoft.JSInterop", + "range": "[6.0.11, )", + "target_framework": "net7.0" + }, + { + "id": "SwissEphNet", + "range": "[2.8.0.2, )", + "target_framework": "net7.0" + } + ], + "hosted_api_blocker": "api.vedastro.org does not expose a verified build commit, package hash, DLL hash, assembly version, container digest, or method-semantics contract.", + "hosted_api_status": "blocked", + "license": "MIT", + "nuget_catalog_url": "https://api.nuget.org/v3/catalog0/data/2023.03.22.22.24.11/vedastro.library.1.2.0.json", + "nuget_registration_url": "https://api.nuget.org/v3/registration5-semver1/vedastro.library/1.2.0.json", + "package": "VedAstro.Library", + "package_content_url": "https://api.nuget.org/v3-flatcontainer/vedastro.library/1.2.0/vedastro.library.1.2.0.nupkg", + "package_hash": "36gq1hggII7Ho27TSL2B85P0BQD1zsAE1xHoJgUrM9V5rnUJGXUZibDlutkXR9gJ+aMr8UCQa2771MfD6BAscA==", + "package_hash_algorithm": "SHA512", + "package_size": 230836, + "project_url": "https://vedastro.org/", + "published": "2023-03-22T22:22:01.81Z", + "required_self_host_evidence": { + "assembly_version": null, + "container_image_digest": null, + "dll_sha256": null, + "nuget_package_hash": "36gq1hggII7Ho27TSL2B85P0BQD1zsAE1xHoJgUrM9V5rnUJGXUZibDlutkXR9gJ+aMr8UCQa2771MfD6BAscA==", + "public_method_inventory": null, + "source_commit": null + }, + "scope": "vedastro_reproducible_identity_archive", + "self_host_candidate_status": "reproducible_package_identity_archived", + "truth_upgrade_gate": "blocked_until_source_commit_dll_hash_assembly_version_method_inventory_and_image_digest_are_archived", + "version": "1.2.0" +} 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_holdout_template.py b/scripts/day_level_holdout_template.py new file mode 100644 index 00000000..6c77749c --- /dev/null +++ b/scripts/day_level_holdout_template.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Create a blank independent day-level timing holdout annotation template.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +TEMPLATE = { + "case_id": "", + "subject": { + "name": "", + "public_profile_url": "", + "birth_time_rating": "AA/A only preferred", + }, + "domain": "career|marriage|wealth|health", + "label": "target_event|no_target_event", + "start": "YYYY-MM-DD", + "end": "YYYY-MM-DD", + "event_description": "", + "event_absent_assertion": "", + "source_url": "https://", + "source_quote_or_summary": "", + "adjudicator": "", + "time_uncertainty_days": 0, + "independent_human_reviewed": True, + "frozen_before_scoring": True, + "source_path": "", + "notes": "", +} + + +def build_template() -> dict: + return { + "template_type": "day_level_holdout_annotation_v3", + "instructions": [ + "Use target_event for known dated events.", + "Use no_target_event only when a public source supports that the target event did not occur in the interval.", + "Do not use old control dates or rows observed before preregistration for tuning.", + "Freeze labels before running timing_ranker_blind_eval.py.", + ], + "annotation": TEMPLATE, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + text = json.dumps(build_template(), ensure_ascii=False, indent=2) + "\n" + if args.output: + args.output.write_text(text, encoding="utf-8") + else: + print(text, end="") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/day_level_holdout_validator.py b/scripts/day_level_holdout_validator.py index 788fbfb6..2063404e 100644 --- a/scripts/day_level_holdout_validator.py +++ b/scripts/day_level_holdout_validator.py @@ -11,11 +11,14 @@ REQUIRED={"case_id","domain","label","start","end","source_url","adjudicator","t def validate(path: Path) -> dict: data=json.loads(path.read_text(encoding="utf-8")); rows=data.get("annotations") or []; errors=[] mode=data.get("validation_mode", "independent") + prohibited=set(data.get("prohibited_tuning_data") or []) allowed_labels={"target_event", "no_target_event"} if mode == "independent" else {"target_event", "observational_non_target_date"} for i,row in enumerate(rows): for key in sorted(REQUIRED-set(row)): errors.append({"row":i,"field":key,"error":"missing"}) if row.get("label") not in allowed_labels: errors.append({"row":i,"field":"label","error":"invalid"}) if not str(row.get("source_url") or "").startswith(("https://","http://")): errors.append({"row":i,"field":"source_url","error":"not_public_url"}) + if mode == "independent" and row.get("independent_human_reviewed") is not True: errors.append({"row":i,"field":"independent_human_reviewed","error":"not_independently_human_reviewed"}) + if row.get("source_path") in prohibited: errors.append({"row":i,"field":"source_path","error":"prohibited_tuning_source"}) positives=sum(r.get("label")=="target_event" for r in rows); negatives=sum(r.get("label") in {"no_target_event", "observational_non_target_date"} for r in rows) gate=data.get("frozen_gate") or {}; ready=not errors and positives>=gate.get("minimum_independent_cases",20) and negatives>=gate.get("minimum_independent_negative_intervals",80) status=("observational_ready_not_independent" if ready else "awaiting_observational_labels") if mode == "observational" else ("ready_for_blind_replay" if ready else "awaiting_independent_labels") 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/timing_ranker_blind_eval.py b/scripts/timing_ranker_blind_eval.py new file mode 100644 index 00000000..aa97a0d9 --- /dev/null +++ b/scripts/timing_ranker_blind_eval.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Evaluate frozen day/month timing candidates against independent holdout labels.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from scripts.day_level_holdout_validator import validate + + +def _load(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def _key(row: dict) -> tuple[str, str, str]: + return (str(row.get("case_id") or ""), str(row.get("start") or ""), str(row.get("end") or "")) + + +def evaluate(manifest_path: Path, candidates_path: Path) -> dict: + validation = validate(manifest_path) + manifest = _load(manifest_path) + candidates = sorted( + (_load(candidates_path).get("candidate_windows") or []), + key=lambda row: float(row.get("score") or 0), + reverse=True, + ) + labels = {_key(row): row.get("label") for row in manifest.get("annotations") or []} + ranked = [{**row, "rank": index + 1, "label": labels.get(_key(row))} for index, row in enumerate(candidates)] + positives = [row for row in ranked if row.get("label") == "target_event"] + negatives = [row for row in ranked if row.get("label") == "no_target_event"] + top_3_positive = sum(1 for row in positives if row["rank"] <= 3) + positive_top_3_rate = top_3_positive / len(positives) if positives else 0.0 + min_positive_score = min((float(row.get("score") or 0) for row in positives), default=0.0) + false_positive_negatives = sum(1 for row in negatives if float(row.get("score") or 0) >= min_positive_score) + specificity = 1 - (false_positive_negatives / len(negatives)) if negatives else 0.0 + gate = manifest.get("frozen_gate") or {} + blockers = [] + if validation["status"] != "ready_for_blind_replay": + blockers.append("holdout_not_ready") + if positive_top_3_rate < gate.get("minimum_positive_top_3_rate", 1): + blockers.append("positive_top_3_rate_below_gate") + if specificity < gate.get("minimum_specificity", 1): + blockers.append("specificity_below_gate") + passed = not blockers + return { + "scope": "timing_ranker_blind_eval", + "status": "pass" if passed else "blocked", + "claim_status": "calibrated_day_level" if passed else "exploratory_unvalidated", + "production_tuning_allowed": bool(passed), + "positive_count": len(positives), + "negative_count": len(negatives), + "positive_top_3_rate": positive_top_3_rate, + "specificity": specificity, + "blockers": blockers, + "validation": validation, + "ranked_windows": ranked, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("manifest", type=Path) + parser.add_argument("candidates", type=Path) + args = parser.parse_args() + print(json.dumps(evaluate(args.manifest, args.candidates), ensure_ascii=False, indent=2, sort_keys=True)) + 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_cross_project_sync_status.py b/tests/test_cross_project_sync_status.py index aabfed3f..6da125a6 100644 --- a/tests/test_cross_project_sync_status.py +++ b/tests/test_cross_project_sync_status.py @@ -36,6 +36,14 @@ def test_sync_policy_encodes_research_first_commercial_mature_rule() -> None: assert policy["sync_model"] == "research_validates_commercial_receives_mature" assert policy["directional_gates"]["research_to_commercial"]["source_required"] == "validated_in_research" assert policy["directional_gates"]["research_to_commercial"]["target_required"] == "commercial_safe" + assert set(policy["research_repo_exclusions"]) >= { + "commercial_credits", + "billing", + "subscriptions", + "payment", + "account_entitlements", + "service_role_runtime", + } assert "references/cross_project_contract/fixture_manifest.v1.json" in policy["shared_files"] diff --git a/tests/test_day_level_holdout_template.py b/tests/test_day_level_holdout_template.py new file mode 100644 index 00000000..aad25713 --- /dev/null +++ b/tests/test_day_level_holdout_template.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +from scripts.day_level_holdout_template import build_template + +ROOT = Path(__file__).resolve().parents[1] + + +def test_holdout_template_is_nontechnical_and_validator_compatible_shape() -> None: + template = build_template() + annotation = template["annotation"] + + for token in [ + "case_id", + "subject", + "domain", + "label", + "start", + "end", + "event_absent_assertion", + "source_url", + "adjudicator", + "independent_human_reviewed", + "frozen_before_scoring", + ]: + assert token in annotation + + assert "Do not use old control dates" in " ".join(template["instructions"]) + assert annotation["source_url"] == "https://" + assert annotation["independent_human_reviewed"] is True + + +def test_holdout_template_cli_writes_json(tmp_path: Path) -> None: + output = tmp_path / "template.json" + + subprocess.run( + [sys.executable, str(ROOT / "scripts" / "day_level_holdout_template.py"), "--output", str(output)], + check=True, + text=True, + ) + + data = json.loads(output.read_text(encoding="utf-8")) + assert data["template_type"] == "day_level_holdout_annotation_v3" + assert data["annotation"]["label"] == "target_event|no_target_event" 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_shadbala_av_normative_benchmark_plan.py b/tests/test_shadbala_av_normative_benchmark_plan.py new file mode 100644 index 00000000..2d830996 --- /dev/null +++ b/tests/test_shadbala_av_normative_benchmark_plan.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +PLAN = ROOT / "docs" / "research" / "shadbala_av_normative_benchmark_plan_2026_07_19.md" + + +def test_shadbala_av_plan_keeps_current_closure_separate_from_global_truth() -> None: + text = PLAN.read_text(encoding="utf-8") + + for token in [ + "current target set: `external_verified`", + "`can_claim_shadbala_absolute_closure = true`", + "`production_tuning_allowed = false`", + "must not claim universal Shadbala or AV truth", + "60 mismatches, 60 classified, 0 unclassified", + "no majority vote", + ]: + assert token in text + + +def test_shadbala_av_plan_requires_component_units_and_license_boundaries() -> None: + text = PLAN.read_text(encoding="utf-8") + + for token in [ + "VP Jain", + "Xalen", + "PyJHora/JHora", + "jyotishganit", + "Virupa vs Rupa must be explicit", + "`sthana`", + "`dig`", + "`kala`", + "`chesta`", + "`naisargika`", + "`drik`", + "`total_rupa`", + "AGPL implementation code", + "Independent ephemeris mode", + ]: + assert token in text diff --git a/tests/test_three_engine_mismatch_arbitrator.py b/tests/test_three_engine_mismatch_arbitrator.py index 2d697631..be3ac9d0 100644 --- a/tests/test_three_engine_mismatch_arbitrator.py +++ b/tests/test_three_engine_mismatch_arbitrator.py @@ -33,3 +33,15 @@ def test_vedastro_only_d2_difference_is_endpoint_semantics(tmp_path: Path) -> No row = arbitrate_manifest(path)["rows"][0] assert row["category"] == "endpoint_or_varga_semantics" assert row["differing_engines"] == ["VedAstro"] + + +def test_commercial_receives_mismatch_status_not_raw_truth_upgrade() -> None: + report = json.loads((ROOT / "references/oracle/three_engine_mismatch_arbitration_2026_07_19.json").read_text(encoding="utf-8")) + markdown = (ROOT / "docs/research/three_engine_mismatch_arbitration_2026_07_19.md").read_text(encoding="utf-8") + + assert report["mismatch_count"] == 60 + assert report["classified_count"] == 60 + assert report["unclassified_count"] == 0 + assert report["truth_policy"] == "no_majority_vote" + assert "commercial_sync: `status_and_claim_boundary_only`" in markdown + assert "Do not copy raw research debt into commercial runtime" in markdown diff --git a/tests/test_timing_ranker_blind_eval.py b/tests/test_timing_ranker_blind_eval.py new file mode 100644 index 00000000..e9dcef0c --- /dev/null +++ b/tests/test_timing_ranker_blind_eval.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from scripts.day_level_holdout_validator import validate +from scripts.day_level_negative_holdout_intake import append_annotation +from scripts.timing_ranker_blind_eval import evaluate + + +def test_holdout_validator_rejects_non_independent_and_prohibited_rows(tmp_path: Path) -> None: + manifest = { + "prohibited_tuning_data": ["old_controls.json"], + "frozen_gate": {"minimum_independent_cases": 1, "minimum_independent_negative_intervals": 1}, + "annotations": [ + { + "case_id": "case-1", + "domain": "career", + "label": "no_target_event", + "start": "2020-01-01", + "end": "2020-01-07", + "source_url": "https://example.org/timeline", + "adjudicator": "same_person", + "time_uncertainty_days": 0, + "independent_human_reviewed": False, + "source_path": "old_controls.json", + } + ], + } + path = tmp_path / "holdout.json" + path.write_text(json.dumps(manifest), encoding="utf-8") + + report = validate(path) + + assert report["status"] == "awaiting_independent_labels" + assert report["production_tuning_allowed"] is False + assert {error["error"] for error in report["errors"]} >= { + "not_independently_human_reviewed", + "prohibited_tuning_source", + } + + +def test_blind_eval_requires_positive_windows_to_rank_above_negative_windows(tmp_path: Path) -> None: + manifest = { + "frozen_gate": { + "minimum_positive_top_3_rate": 0.6, + "minimum_specificity": 0.6, + "minimum_independent_cases": 1, + "minimum_independent_negative_intervals": 1, + }, + "annotations": [ + { + "case_id": "case-1", + "domain": "career", + "label": "target_event", + "start": "2020-01-01", + "end": "2020-01-07", + "source_url": "https://example.org/event", + "adjudicator": "reviewer-a", + "time_uncertainty_days": 0, + "independent_human_reviewed": True, + }, + { + "case_id": "case-1-neg", + "domain": "career", + "label": "no_target_event", + "start": "2020-02-01", + "end": "2020-02-07", + "source_url": "https://example.org/non-event", + "adjudicator": "reviewer-b", + "time_uncertainty_days": 0, + "independent_human_reviewed": True, + }, + ], + } + candidates = { + "candidate_windows": [ + {"case_id": "case-1", "start": "2020-01-01", "end": "2020-01-07", "score": 0.90}, + {"case_id": "case-1-neg", "start": "2020-02-01", "end": "2020-02-07", "score": 0.20}, + ] + } + manifest_path = tmp_path / "holdout.json" + candidates_path = tmp_path / "candidates.json" + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + candidates_path.write_text(json.dumps(candidates), encoding="utf-8") + + report = evaluate(manifest_path, candidates_path) + + assert report["status"] == "pass" + assert report["claim_status"] == "calibrated_day_level" + assert report["production_tuning_allowed"] is True + assert report["positive_top_3_rate"] == 1.0 + assert report["specificity"] == 1.0 + + +def test_blind_eval_blocks_when_negative_scores_outrank_positive_scores(tmp_path: Path) -> None: + manifest = { + "frozen_gate": { + "minimum_positive_top_3_rate": 0.6, + "minimum_specificity": 0.6, + "minimum_independent_cases": 1, + "minimum_independent_negative_intervals": 1, + }, + "annotations": [ + { + "case_id": "positive", + "domain": "marriage", + "label": "target_event", + "start": "2020-01-01", + "end": "2020-01-07", + "source_url": "https://example.org/event", + "adjudicator": "reviewer-a", + "time_uncertainty_days": 0, + "independent_human_reviewed": True, + }, + { + "case_id": "negative", + "domain": "marriage", + "label": "no_target_event", + "start": "2020-02-01", + "end": "2020-02-07", + "source_url": "https://example.org/non-event", + "adjudicator": "reviewer-b", + "time_uncertainty_days": 0, + "independent_human_reviewed": True, + }, + ], + } + candidates = { + "candidate_windows": [ + {"case_id": "positive", "start": "2020-01-01", "end": "2020-01-07", "score": 0.10}, + {"case_id": "negative", "start": "2020-02-01", "end": "2020-02-07", "score": 0.95}, + ] + } + manifest_path = tmp_path / "holdout.json" + candidates_path = tmp_path / "candidates.json" + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + candidates_path.write_text(json.dumps(candidates), encoding="utf-8") + + report = evaluate(manifest_path, candidates_path) + + assert report["status"] == "blocked" + assert report["claim_status"] == "exploratory_unvalidated" + assert report["production_tuning_allowed"] is False + assert "specificity_below_gate" in report["blockers"] + + +def test_negative_holdout_intake_appends_valid_independent_label(tmp_path: Path) -> None: + path = tmp_path / "holdout.json" + path.write_text(json.dumps({"annotations": [], "prohibited_tuning_data": []}), encoding="utf-8") + + report = append_annotation( + path, + { + "case_id": "case-2-neg", + "domain": "career", + "label": "no_target_event", + "start": "2020-03-01", + "end": "2020-03-31", + "source_url": "https://example.org/biography", + "adjudicator": "reviewer-c", + "time_uncertainty_days": 0, + "independent_human_reviewed": True, + }, + ) + + saved = json.loads(path.read_text(encoding="utf-8")) + assert report["appended"] is True + assert saved["annotations"][0]["case_id"] == "case-2-neg" + assert saved["annotations"][0]["frozen_before_scoring"] is True + + +def test_negative_holdout_intake_rejects_non_independent_label(tmp_path: Path) -> None: + path = tmp_path / "holdout.json" + path.write_text(json.dumps({"annotations": []}), encoding="utf-8") + + report = append_annotation( + path, + { + "case_id": "bad", + "domain": "career", + "label": "no_target_event", + "start": "2020-03-01", + "end": "2020-03-31", + "source_url": "https://example.org/biography", + "adjudicator": "reviewer-c", + "time_uncertainty_days": 0, + "independent_human_reviewed": False, + }, + ) + + assert report["appended"] is False + assert report["errors"][0]["error"] == "not_independently_human_reviewed" diff --git a/tests/test_vedastro_identity_closure_plan.py b/tests/test_vedastro_identity_closure_plan.py new file mode 100644 index 00000000..ecbbe476 --- /dev/null +++ b/tests/test_vedastro_identity_closure_plan.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DOC = ROOT / "docs" / "research" / "vedastro_identity_closure_plan_2026_07_19.md" + + +def test_vedastro_identity_plan_keeps_hosted_output_observation_only() -> None: + text = DOC.read_text(encoding="utf-8") + + for token in [ + "VedAstro hosted API remains observation-only", + "build identity, method semantics, and deployment version are not archived", + "does not prove which side is true", + "tune production predictions from hosted output with unknown build identity", + "silently prefer VedAstro or local output by majority vote", + ]: + assert token in text + + +def test_vedastro_identity_plan_requires_self_host_supply_chain_fields() -> None: + text = DOC.read_text(encoding="utf-8") + + for token in [ + "source commit", + "NuGet package hash", + "DLL SHA-256", + "assembly version", + "public method inventory", + "container image digest", + "required_self_host_evidence", + "truth_upgrade_gate", + ]: + 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]