Merge remote-tracking branch 'origin/main' into codex/optimize-runtime-ux
# Conflicts: # frontend/src/mastra/index.ts
This commit is contained in:
@@ -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.
|
||||
@@ -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":"..."}'
|
||||
```
|
||||
|
||||
## 结论边界
|
||||
|
||||
没有真实独立负样本前:
|
||||
|
||||
- 可以输出候选日期排序;
|
||||
- 可以说明触发信号;
|
||||
- 不能说“精确日期预测已验证”。
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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")
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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"]
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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("<Project />\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]
|
||||
Reference in New Issue
Block a user