From 96caaeae2c97da48798e9664265e65d077685e36 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Thu, 25 Jun 2026 23:23:56 +0800 Subject: [PATCH] feat: add oracle evidence safeguards --- .gitignore | 2 + README.md | 14 ++ docs/user_jhora_capture_guide.md | 199 ++++++++++++++++ jyotish-app/main.js | 133 ++++++++++- jyotish-app/public/api-bridge.js | 5 + jyotish-app/style.css | 82 ++++++- progress.md | 22 ++ references/oracle/artifacts/.gitkeep | 1 + references/oracle/artifacts/README.md | 34 +++ references/oracle/ashtakoot_oracle_cases.json | 195 +++++++++++++++ .../jhora_steve_jobs_lahiri_first_packet.json | 115 +++++++++ scripts/jyotish_api_server.py | 13 + scripts/jyotish_engine.py | 16 ++ scripts/oracle_collection_queue.py | 23 ++ scripts/oracle_evidence_validator.py | 82 +++++++ task_plan.md | 6 +- tests/test_cli_smoke.py | 7 + tests/test_frontend_productization.py | 127 ++++++++++ tests/test_oracle_collection_queue.py | 61 +++++ tests/test_oracle_evidence_validator.py | 225 ++++++++++++++++-- 20 files changed, 1337 insertions(+), 25 deletions(-) create mode 100644 docs/user_jhora_capture_guide.md create mode 100644 references/oracle/artifacts/.gitkeep create mode 100644 references/oracle/artifacts/README.md create mode 100644 references/oracle/ashtakoot_oracle_cases.json create mode 100644 references/oracle/evidence_packet_templates/jhora_steve_jobs_lahiri_first_packet.json diff --git a/.gitignore b/.gitignore index 963df2af..8981cbd5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ htmlcov/ benchmarks/jyotish/outputs/ output_report.txt results_extracted.md +runtime-smoke-report-*.html +jyotish-app/runtime-smoke-report-*.html diff --git a/README.md b/README.md index d1ebc066..7554966b 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,22 @@ python3 scripts/oracle_collection_queue.py \ 该 JSON 的 scope 是 `external_oracle_collection_queue`。当前队列有 5 个 `template_only` 任务、`ready_for_calibration: 0`、`production_tuning_allowed: false`,说明只能继续采集 JHora/PyJHora/VedAstro 等外部黑盒目标值;在模板字段未填充、状态未升为 `external_verified` 前,不能用这些样本做 Dasha/Shadbala 生产调参。 +Ashtakoot 外部合婚 oracle 使用同一个队列生成器,但独立样本文件是 `references/oracle/ashtakoot_oracle_cases.json`: + +```bash +python3 scripts/oracle_collection_queue.py \ + --oracle-file references/oracle/ashtakoot_oracle_cases.json \ + --format json +``` + +该队列同样保持 `ready_for_calibration: 0`,用于采集 `ashtakoot_36_point` 外部合婚目标值,而不是重写现有 `scripts/ashtakoot.py` 算法。每条样本要补齐 `target.total_score`、`target.varna`、`target.vashya`、`target.tara`、`target.yoni`、`target.graha_maitri`、`target.gana`、`target.bhakoot`、`target.nadi`、`target.kuja_status`,并保留 JHora/VedAstro/AstroSage 等外部截图或 API artifact。 + 每个队列任务还包含 `evidence_packet.capture_id` 草稿证据包。人工或副手录入外部真值时,必须至少填写 `tool_name`、`tool_version_or_url`、`capture_date`、`source_artifact`、`ayanamsa`、`node_mode`、`timezone`、`operator_note`,并保留截图、API 响应或 stdout 等外部 artifact;不得把本仓库本地计算输出当作 `source_artifact`。 +外部截图和 stdout 片段统一存入 `references/oracle/artifacts/`,证据包里的 `source_artifact` 必须使用该目录下的 repo-relative 路径或明确标注的外部审阅位置。所有私人截图必须打码;不得提交私人 PDF 原件、不得提交完整出生报告,也不得提交浏览器 scratch 目录或含账号会话/cookie/token/桌面通知的截图。 + +第一条 JHora/PyJHora 黑盒证据采集按 `docs/user_jhora_capture_guide.md` 执行:优先使用 Steve Jobs 或合成样本,采集 Moon sidereal longitude、Vimshottari start date 与 Shadbala 七曜六分量,保存到 `references/oracle/artifacts/` 后再运行 evidence validator。 + 外部目标字段采用 `target_fields` + `target_placeholders` 双层结构:`target_fields` 固定记录该案例需要校验的目标,例如 `target.moon_sidereal_longitude_deg`、`target.vimshottari_start_date`、`target.shadbala_components`;当这些字段被真实外部来源填入并且证据包状态升为 `external_verified` 后,队列生成器会保留这些值,不会再把它们降级成 `draft`。这保证了“人工/JHora/PyJHora/VedAstro 采集 → JSON 填写 → 队列生成 → validator 复核”的路径可复验。 当外部证据包被填写回队列 JSON 后,用证据验证器做第二层防线: diff --git a/docs/user_jhora_capture_guide.md b/docs/user_jhora_capture_guide.md new file mode 100644 index 00000000..ad83cb6d --- /dev/null +++ b/docs/user_jhora_capture_guide.md @@ -0,0 +1,199 @@ +# JHora/PyJHora External Evidence Capture Guide + +This guide turns the first Dasha/Shadbala oracle sample from a draft into a +reviewable external evidence packet. It is for manual black-box capture only: +do not copy JHora/PyJHora implementation code, formulas, constants, or private +reports into this repository. + +## Safe Case Choice + +Start with one of these public or synthetic tasks: + +- `template_steve_jobs_dasha_lahiri`: public-figure Dasha boundary sample. +- `template_user_REDACTED_YEAR_moon_longitude_lahiri`: user-template sample; use only if + the screenshot is heavily redacted. + +Avoid private client material for the first pass. If a private screenshot is +unavoidable, it 必须打码 before it enters `references/oracle/artifacts/`. + +## Privacy Rules + +- Store evidence only under `references/oracle/artifacts/`. +- `source_artifact` must be a repo-relative path such as + `references/oracle/artifacts/jhora_steve_jobs_dasha_lahiri_v1.png`. +- 必须打码 names, addresses, account names, notifications, file paths, browser + tabs, and any personally identifying text. +- 不得提交私人 PDF 原件. +- 不得提交完整出生报告. +- 不得提交浏览器 scratch folders, cookies, account sessions, tokens, desktop + notifications, or downloaded temp folders. + +## External Tool Setup + +For JHora: + +1. Open a clean chart in JHora. +2. Enter the exact date, time, timezone, latitude, and longitude from + `references/oracle/dasha_shadbala_oracle_cases.json`. +3. Set ayanamsa explicitly. The first pass should use Lahiri; later samples + should repeat with Raman and KP. +4. Record whether the tool is using mean node or true node. The packet metadata + must write this exactly as `mean node` or `true node`. +5. Keep the tool version, build date, or download URL visible in notes if + possible. + +For PyJHora: + +1. Use an isolated environment and treat output as a black-box external + reference. +2. Record the package version and command or script path in operator notes. +3. Capture stdout as a text artifact only if it contains the requested target + values and no private data. +4. Because PyJHora license metadata can vary by source, use it as an external + behavior oracle unless a separate license review approves direct reuse. + +## Required Screenshots Or Stdout + +Capture enough external evidence to fill every requested target field. + +### Moon sidereal longitude + +Required for `template_user_REDACTED_YEAR_moon_longitude_lahiri`. + +- Capture Moon sidereal longitude in absolute 0-360 degrees if available. +- Also capture sign-local degrees/minutes/seconds when the tool shows it. +- Record ayanamsa, timezone, and node mode beside the value. + +### Vimshottari start date + +Required for `template_user_REDACTED_YEAR_moon_longitude_lahiri`, +`template_steve_jobs_dasha_lahiri`, and +`template_historical_epoch_lahiri`. + +- Capture the Vimshottari start date or first Mahadasha boundary shown by the + external tool. +- Write it as ISO date in `target.vimshottari_start_date`, for example + `1986-05-18`. +- If the tool uses a visible year length, DST, or local sunrise policy, record + that in `operator_note`. + +### Shadbala 七曜六分量 + +Required for every task whose target includes `shadbala_components`. + +The packet must include all seven planets: + +- Sun +- Moon +- Mars +- Mercury +- Jupiter +- Venus +- Saturn + +Each planet must include all six components: + +- `sthana` +- `dig` +- `kala` +- `chesta` +- `naisargika` +- `drik` + +Do not fill only a total. Do not apply a global scaling factor. If the tool +shows Rupa and Virupa, record the unit in `operator_note` and preserve the +component numbers exactly as read from the screenshot or stdout. + +## Artifact Naming + +Use short, stable names: + +- `references/oracle/artifacts/jhora_steve_jobs_lahiri_dasha_v1.png` +- `references/oracle/artifacts/jhora_steve_jobs_lahiri_shadbala_v1.png` +- `references/oracle/artifacts/pyjhora_steve_jobs_lahiri_stdout_v1.txt` +- `references/oracle/artifacts/jhora_user_REDACTED_YEAR_lahiri_redacted_v1.png` + +If a screenshot is corrected, keep the old artifact only if it is already +referenced by a packet; otherwise replace the draft before review. + +## Packet Fill Procedure + +Generate the current queue: + +```bash +python3 scripts/oracle_collection_queue.py \ + --oracle-file references/oracle/dasha_shadbala_oracle_cases.json \ + --format json > /tmp/jyotish_oracle_queue_capture.json +``` + +Choose the task matching the external tool output, then fill: + +- `evidence_packet.status`: `external_verified` +- `evidence_packet.metadata.tool_name`: `JHora` or `PyJHora` +- `evidence_packet.metadata.tool_version_or_url` +- `evidence_packet.metadata.capture_date` +- `evidence_packet.metadata.source_artifact` +- `evidence_packet.metadata.ayanamsa`: `Lahiri`, `Raman`, or `KP` +- `evidence_packet.metadata.node_mode`: `mean node` or `true node` +- `evidence_packet.metadata.timezone` +- `evidence_packet.metadata.operator_note` +- `evidence_packet.target_placeholders.target.moon_sidereal_longitude_deg` +- `evidence_packet.target_placeholders.target.vimshottari_start_date` +- `evidence_packet.target_placeholders.target.shadbala_components` + +For a valid first Shadbala row, the component object must look like this shape: + +```json +{ + "Sun": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0}, + "Moon": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0}, + "Mars": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0}, + "Mercury": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0}, + "Jupiter": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0}, + "Venus": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0}, + "Saturn": {"sthana": 0, "dig": 0, "kala": 0, "chesta": 0, "naisargika": 0, "drik": 0} +} +``` + +Replace every `0` with the external value. A zero is valid only if the external +tool literally shows zero. + +## Validation + +Run the validator against the filled queue: + +```bash +python3 scripts/oracle_evidence_validator.py \ + --queue-file /tmp/jyotish_oracle_queue_capture.json +``` + +The first successful sample should move the summary from `valid_packets: 0` to +`valid_packets: 1`, and from `ready_for_calibration: 0` to +`ready_for_calibration: 1`. This does not allow production tuning by itself; it +only means one packet is review-ready. + +## Reject And Recapture Cases + +Reject the packet and recapture if any of these occur: + +- `missing_external_artifact` +- `status_not_external_verified:draft` +- `local_engine_artifact_rejected` +- `missing_shadbala_component:all_planets` +- `missing_shadbala_component:Sun.kala` or any other component-level error +- Screenshot does not show ayanamsa. +- Screenshot does not show whether mean node or true node was used. +- Source artifact contains unredacted private data. +- Source artifact is a private PDF original or complete birth report. +- Values came from this repository's local engine instead of JHora/PyJHora or + another external oracle. + +## Minimal Recapture Checklist + +If the screenshot is not enough, recapture only the missing evidence: + +1. Tool/version and settings screen. +2. Moon sidereal longitude screen. +3. Vimshottari start date screen. +4. Shadbala component table with all seven planets and six components. +5. A short operator note explaining ayanamsa, node mode, timezone, and unit. diff --git a/jyotish-app/main.js b/jyotish-app/main.js index 7a8b9756..87eab203 100644 --- a/jyotish-app/main.js +++ b/jyotish-app/main.js @@ -894,8 +894,13 @@ function renderAIPromptPackPanel(cd) { AI Prompt Pack ${escapeHtml(pack.mode || 'jyotish_structured_prompt_pack')} - schema v${escapeHtml(String(pack.schema_version || 1))} +
+ + + schema v${escapeHtml(String(pack.schema_version || 1))} +
+ ${renderAyanamsaRuntimeStatus(cd, pack)}
Ayanamsa @@ -945,6 +950,79 @@ function renderAIPromptPackPanel(cd) {
`; + setupAIPromptPackActions(host, cd, pack); +} + +function renderAyanamsaRuntimeStatus(cd, pack) { + const settings = readCalculationSettings(); + const boundary = cd?._calculation_boundary || {}; + const evidenceAyanamsa = pack?.evidence_snapshot?.ayanamsa || {}; + const requested = evidenceAyanamsa.name || settings.ayanamsa; + const applied = boundary.appliedAyanamsa || evidenceAyanamsa.name || requested; + const status = cd?._fallback ? 'browser_fallback' : 'backend_computed'; + const label = status === 'backend_computed' ? '后端实算' : '浏览器降级'; + const note = boundary.note || ( + status === 'backend_computed' + ? '当前 Ayanamsa 来自本地 API 服务返回,可随导出和 AI Prompt Pack 追溯。' + : '浏览器 fallback 只能使用 Lahiri 兼容路径;Raman/KP 等需启动本地 API 服务。' + ); + return ` +
+ ${escapeHtml(label)} + ${escapeHtml(getCalculationSettingLabel('ayanamsa', requested))} + ${escapeHtml(`applied=${applied} · status=${status}`)} + ${escapeHtml(note)} +
+ `; +} + +function setupAIPromptPackActions(host, cd, pack) { + host.querySelectorAll('[data-ai-prompt-action]').forEach(button => { + button.addEventListener('click', async () => { + const action = button.getAttribute('data-ai-prompt-action'); + const copied = await copyAIPromptPackSection(action, cd, pack); + button.textContent = copied ? '已复制' : '复制失败'; + setTimeout(() => { + button.textContent = action === 'copy-evidence' ? '复制 Evidence' : '复制 Prompt'; + }, 1400); + }); + }); +} + +async function copyAIPromptPackSection(action, cd, pack) { + const payload = action === 'copy-evidence' + ? { + evidence_snapshot: pack?.evidence_snapshot || {}, + retrieval_plan: pack?.retrieval_plan || {}, + calculation_boundary: cd?._calculation_boundary || null, + } + : { + prompt_zh: pack?.prompt_zh || '', + mode: pack?.mode || 'jyotish_structured_prompt_pack', + schema_version: pack?.schema_version || 1, + }; + const text = [ + 'AI Prompt Pack 审计上下文已复制', + JSON.stringify(payload, null, 2), + ].join('\n'); + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return true; + } + const area = document.createElement('textarea'); + area.value = text; + area.setAttribute('readonly', 'true'); + area.style.position = 'fixed'; + area.style.left = '-9999px'; + document.body.appendChild(area); + area.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(area); + return ok; + } catch { + return false; + } } function normalizeAIPromptPack(cd = {}) { @@ -981,6 +1059,7 @@ function normalizeAIPromptPack(cd = {}) { strength: { shadbala_ranking: Object.entries(cd.shadbala || {}).map(([planet, pdata]) => ({ planet, rupas: pdata?.rupas, level: pdata?.level })), }, + oracle_progress: oracleProgressSnapshot(), }, retrieval_plan: { local_reference_docs: [ @@ -993,6 +1072,20 @@ function normalizeAIPromptPack(cd = {}) { }; } +function oracleProgressSnapshot() { + return { + scope: 'external_oracle_evidence_validation', + collection_queue: 'external_oracle_collection_queue', + total_packets: 5, + valid_packets: 0, + ready_for_calibration: 0, + production_tuning_allowed: false, + artifact_policy: 'references/oracle/artifacts/', + promotion_rule: 'external_verified requires source_artifact, filled target values, and non-local-engine external evidence.', + boundary: 'Dasha/Shadbala absolute values are not externally calibrated until enough packets pass validation.', + }; +} + function formatCompactDegree(value) { const num = Number(value); return Number.isFinite(num) ? `${num.toFixed(2)}°` : '-'; @@ -1827,6 +1920,7 @@ function renderOracleEvidenceIntakePanel() { Oracle Evidence Intake 下载 5 个外部真值空白包;只接受 JHora/PyJHora/VedAstro 黑盒证据,禁止本地输出冒充 external_verified。
+ ${renderOracleEvidenceProgressDashboard()}
${ORACLE_EVIDENCE_INTAKE_TASKS.map(task => `
@@ -1859,6 +1953,38 @@ function renderOracleEvidenceIntakePanel() { `; } +function renderOracleEvidenceProgressDashboard() { + const totalPackets = ORACLE_EVIDENCE_INTAKE_TASKS.length; + const validPackets = 0; + const readyPackets = 0; + const shadbalaTargets = ORACLE_EVIDENCE_INTAKE_TASKS + .filter(task => task.targetFields.includes('shadbala_components')).length; + const dashaTargets = ORACLE_EVIDENCE_INTAKE_TASKS + .filter(task => task.targetFields.includes('vimshottari_start_date')).length; + const progress = totalPackets ? Math.round((validPackets / totalPackets) * 100) : 0; + return ` +
+
+ Dasha/Shadbala 真实进度 + ${validPackets} / ${totalPackets} valid_packets · ${readyPackets} ready_for_calibration · production_tuning_allowed=false +
+
+ +
+
+ ${renderValidationTransparencyMetric('Evidence', `${validPackets} / ${totalPackets}`, '0 / 5 外部证据包通过前,生产调参保持关闭。')} + ${renderValidationTransparencyMetric('Dasha', `${dashaTargets} targets`, 'vimshottari_start_date 需要 JHora/PyJHora 黑盒对照。')} + ${renderValidationTransparencyMetric('Shadbala', `${shadbalaTargets} targets`, 'missing_shadbala_component 会拦截缺 sthana/dig/kala/chesta/naisargika/drik 的七曜分量。')} + ${renderValidationTransparencyMetric('Artifacts', 'references/oracle/artifacts/', 'source_artifact 必须指向脱敏后的外部截图或 stdout。')} +
+
+ 隐私 + 私人截图必须打码;不得上传私人 PDF 原件、完整出生报告或浏览器 scratch 会话文件。 +
+
+ `; +} + function buildOracleEvidencePacket(task) { const target = {}; task.targetFields.forEach(field => { @@ -1895,7 +2021,8 @@ function buildOracleEvidencePacket(task) { }, operator_checklist: [ 'Fill tool_name and tool_version_or_url from the external product.', - 'Attach source_artifact as a screenshot/export path outside this local engine.', + 'Attach source_artifact as a repo-relative references/oracle/artifacts/... path after 必须打码.', + 'Do not attach private PDF originals, complete birth reports, browser scratch folders, account sessions, cookies, tokens, or desktop notifications.', 'Fill every target value from black-box output before requesting review.', ], }; @@ -1911,7 +2038,7 @@ function downloadOracleEvidencePacket(caseId) { const packet = buildOracleEvidencePacket(task); downloadText(JSON.stringify(packet, null, 2), `jyotish-oracle-evidence-${task.caseId}.json`, 'application/json;charset=utf-8'); if (status) { - status.textContent = `已下载 ${task.caseId} 空白证据包;填入外部截图和数值后再运行 evidence validator。`; + status.textContent = `已下载 ${task.caseId} 空白证据包;source_artifact 请指向 references/oracle/artifacts/ 下已打码文件,再运行 evidence validator。`; } } diff --git a/jyotish-app/public/api-bridge.js b/jyotish-app/public/api-bridge.js index cc3c1264..e6fe9b50 100644 --- a/jyotish-app/public/api-bridge.js +++ b/jyotish-app/public/api-bridge.js @@ -172,6 +172,10 @@ async function generateReportArtifact(payload) { return postJson('/api/report_artifact', payload); } +async function validateOracleEvidence(payload) { + return postJson('/api/oracle_evidence', payload); +} + async function computeThematicReport(payload) { return postJson('/api/thematic_report', payload); } @@ -437,6 +441,7 @@ window.JyotishAPI = { computeRelationship, importChart, generateReportArtifact, + validateOracleEvidence, computeThematicReport, getAPIHealth, getCapabilityAudit, diff --git a/jyotish-app/style.css b/jyotish-app/style.css index 125c15d8..8d81fdb6 100644 --- a/jyotish-app/style.css +++ b/jyotish-app/style.css @@ -5217,6 +5217,64 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t font-size: 12px; font-style: normal; } +.ai-prompt-pack-actions { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 6px; + min-width: 0; +} +.ai-prompt-pack-actions button { + min-height: 30px; + padding: 5px 9px; + border: 1px solid var(--border-light); + border-radius: var(--radius-sm); + background: var(--bg-surface); + color: var(--text-body); + font-size: 12px; + cursor: pointer; +} +.ai-prompt-pack-actions button:hover { + border-color: var(--accent); + color: var(--accent); +} +.ayanamsa-runtime-status { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 4px 10px; + align-items: center; + margin: 12px 0; + padding: 10px 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius-sm); + background: var(--bg-surface); +} +.ayanamsa-runtime-status span { + padding: 3px 7px; + border-radius: var(--radius-sm); + background: rgba(32, 111, 83, 0.12); + color: var(--accent); + font-size: 11px; + font-weight: 800; +} +.ayanamsa-runtime-status-browser_fallback span { + background: rgba(160, 98, 0, 0.13); + color: #8a5600; +} +.ayanamsa-runtime-status strong { + min-width: 0; + color: var(--text-heading); + font-size: 13px; + overflow-wrap: anywhere; +} +.ayanamsa-runtime-status small, +.ayanamsa-runtime-status em { + grid-column: 1 / -1; + color: var(--text-muted); + font-size: 11px; + font-style: normal; + overflow-wrap: anywhere; +} .ai-prompt-pack-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -5884,6 +5942,28 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t border-radius: var(--radius-sm); background: var(--bg-surface); } +.oracle-evidence-progress-dashboard { + display: grid; + gap: 10px; + margin-top: 8px; + padding: 10px; + border: 1px solid rgba(21, 94, 117, 0.18); + border-radius: var(--radius-sm); + background: rgba(236, 254, 255, 0.74); +} +.oracle-evidence-progress-bar { + position: relative; + overflow: hidden; + height: 8px; + border-radius: 999px; + background: rgba(148, 163, 184, 0.22); +} +.oracle-evidence-progress-bar span { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--blue), var(--green)); +} .oracle-evidence-intake-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -6522,9 +6602,9 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t .first-use-grid, .runtime-health-grid, .trust-status-grid, - .oracle-evidence-intake-grid, .terminology-mode-options, .case-workspace-controls { grid-template-columns: 1fr; } + .oracle-evidence-intake-grid { grid-template-columns: 1fr; } .static-demo-boundary-grid { grid-template-columns: 1fr; } .validation-transparency-grid { grid-template-columns: 1fr; } .case-workspace-counts, diff --git a/progress.md b/progress.md index 0543093c..9e608e6b 100644 --- a/progress.md +++ b/progress.md @@ -356,3 +356,25 @@ - 完成副手任务升级:新增 Round 13/14/15 工作单;Round 15 改为 6 个并行包,覆盖 Evidence 判卷闭环、Shadbala 六分量、Dasha 边界日期、高需求技法、外部截图存档规范与下一轮 Codex 任务建议。 - 验证完成:`python3 -B -m pytest tests/test_frontend_productization.py::test_trust_center_exposes_oracle_evidence_intake_cards tests/test_frontend_productization.py::test_trust_center_and_ai_expose_dasha_shadbala_calibration_status tests/test_frontend_productization.py::test_provenance_panchanga_workspace_panel_is_productized tests/test_api_server_security.py::test_oracle_evidence_api_validates_uploaded_packets tests/test_oracle_collection_queue.py tests/test_oracle_evidence_validator.py -q` 通过;`python3 scripts/oracle_collection_queue.py ...` + `python3 scripts/oracle_evidence_validator.py ...` 仍显示 `valid_packets: 0` / `ready_for_calibration: 0`;`npm run build --prefix jyotish-app` 通过;`git diff --check` 通过。 - 当前下一最高优先级:创建 `references/oracle/artifacts/` 存档规范和第一条真实 JHora/PyJHora 黑盒证据包,优先推动 Shadbala 六分量与 Vimshottari Dasha 边界日期从 0/5 进入至少 1/5 可人工复核状态。 +- 完成前端 Multi-Ayanamsa 与 AI Prompt Pack 可视化补强:完整解盘页 `AI Prompt Pack` 面板新增 Ayanamsa runtime status,区分 `backend_computed` 与 `browser_fallback`,显示 requested/applied ayanamsa 与降级说明;新增“复制 Prompt / 复制 Evidence”按钮,复制内容包含 prompt、evidence_snapshot、retrieval_plan 与 calculation_boundary。 +- TDD/验证完成:先让 `test_frontend_ayanamsa_settings_are_live_api_parameters` 和 `test_ai_prompt_pack_panel_exposes_copyable_audit_context` 红灯,随后实现转绿;`python3 -m pytest -q tests/test_frontend_productization.py -k "ayanamsa_settings or ai_prompt_pack_panel or ai_chat_prefers"` 通过;`npm run build --prefix jyotish-app` 通过;`git diff --check` 通过。 +- 当前下一最高优先级:继续 `references/oracle/artifacts/` 存档规范与第一条 JHora/PyJHora 黑盒证据包采集说明,和 Antigravity Round 16 的 artifact storage / Shadbala component validator / first JHora sample checklist 对齐。 +- 完成 Antigravity Round 18 重型副手任务单:新增 `docs/research/antigravity_sidecar_work_order_round18_2026_06_25.md`,要求副手至少产出 12 份报告,覆盖当前补丁复核、全网开源许可证矩阵、功能缺口重排、用户黑盒流程、隐私仓库卫生、测试债和 AI Prompt Pack/Skill 同步,明确只允许宽松许可证代码进入可复用候选。 +- 完成 Oracle artifact 存档规范:新增 `references/oracle/artifacts/.gitkeep` 与 `references/oracle/artifacts/README.md`,README 同步 `references/oracle/artifacts/`、`source_artifact`、`external_oracle_artifact`、必须打码、不得提交私人 PDF 原件、不得提交完整出生报告、不得提交浏览器 scratch 的规则;前端 Evidence Packet 下载文案同步相对路径与脱敏要求。 +- 完成 Shadbala evidence 强校验:`scripts/oracle_evidence_validator.py` 新增 `SHADBALA_REQUIRED_PLANETS` 与 `SHADBALA_REQUIRED_COMPONENTS`,对 Sun/Moon/Mars/Mercury/Jupiter/Venus/Saturn 七曜的 sthana/dig/kala/chesta/naisargika/drik 六分量逐项拦截;测试覆盖空 dict、只填 Sun、完整七曜六分量和 API 上传复用 validator。 +- 完成 Trust Center 真实采集进度面板:`renderOracleEvidenceProgressDashboard()` 在 Oracle Evidence Intake 顶部展示 `Dasha/Shadbala 真实进度`、`0 / 5`、`valid_packets`、`ready_for_calibration`、`production_tuning_allowed=false`、`references/oracle/artifacts/` 与 `missing_shadbala_component` 边界,避免用户误以为 Dasha/Shadbala 已完成外部绝对值校准。 +- 修复 Round 17 暴露的门禁失败:移动端布局测试失败根因是 CSS selector 组被新增 `.oracle-evidence-intake-grid` 打断,已恢复原断言组并单独声明 oracle intake 移动单列;`jyotish-app/public/api-bridge.js` 已同步 `validateOracleEvidence`,确保 public/static bridge 与源码 bridge 一致。 +- 验证完成:`python3 -m pytest -q tests/test_frontend_productization.py::test_mobile_layout_keeps_dense_sections_single_column tests/test_frontend_productization.py::test_trust_center_exposes_oracle_evidence_intake_cards tests/test_frontend_productization.py::test_oracle_artifact_storage_policy_is_documented` 通过;`python3 -m pytest -q tests/test_api_server_security.py::test_oracle_evidence_api_validates_uploaded_packets tests/test_oracle_evidence_validator.py` 通过;`python3 -m pytest tests/test_frontend_productization.py tests/test_cli_smoke.py tests/test_api_server_security.py tests/test_jaimini.py tests/test_shadbala_complete.py tests/test_transit_trigger.py tests/test_oracle_collection_queue.py tests/test_oracle_evidence_validator.py -q` 238 项通过;`npm run build --prefix jyotish-app` 通过;`python3 scripts/run_quality_gate.py --profile quick --skip-yoga-logic` 通过,包含 runtime smoke。 +- 当前下一最高优先级:把第一条 JHora/PyJHora 黑盒 evidence packet 教程/模板落成可交给人工执行的文档,并等待外部截图或 stdout 后把 `valid_packets` 从 0/5 推到 1/5;同时接收 Round 18 副手报告,优先吸收宽松许可证可复用候选和真实用户流程缺口。 +- 完成 Antigravity Round 19 副手任务单:新增 `docs/research/antigravity_sidecar_work_order_round19_2026_06_25.md`,要求副手以当前工作树重新复核 Round 18 过期结论、JHora/PyJHora 教程、第一条 evidence packet 模板、1/5 人工 runbook、runtime artifact 卫生、progress dashboard DOM/UX、Top 30 开源许可证复筛、Ashtakoot 破冰、Shadbala 单位校验、AI Prompt Pack oracle progress 和 Git tracking 策略。 +- 完成第一条外部证据包执行资产:新增 `docs/user_jhora_capture_guide.md` 与 `references/oracle/evidence_packet_templates/jhora_steve_jobs_lahiri_first_packet.json`;模板保持 `status=draft`,只预留 Steve Jobs Lahiri/true node 的 metadata、Vimshottari start date 和 Shadbala 七曜六分量空位,不伪造任何外部数值。 +- 验证完成:`python3 -m pytest -q tests/test_oracle_collection_queue.py::test_first_jhora_evidence_packet_template_is_safe_and_fillable tests/test_frontend_productization.py::test_first_jhora_capture_guide_is_actionable` 通过;`python3 -m json.tool references/oracle/evidence_packet_templates/jhora_steve_jobs_lahiri_first_packet.json` 通过。 +- 当前下一最高优先级:接收 Round 19 副手报告并继续修正其发现;并在没有人工 JHora/PyJHora 截图前,优先补 `.gitignore` runtime-smoke HTML 卫生、Shadbala 数值单位/类型校验和 AI Prompt Pack 的 oracle progress 摘要。 +- 完成 Round 19 反馈的无需人工 P0/P1 修复:`.gitignore` 新增 `runtime-smoke-report-*.html` 与 `jyotish-app/runtime-smoke-report-*.html`,避免 runtime smoke HTML 上云;`oracle_evidence_validator.py` 对 Shadbala 七曜六分量增加 int/float 且非 bool、非负校验,拒绝字符串数字和负数;`jyotish_engine.py`、`jyotish_api_server.py` 与前端 fallback `normalizeAIPromptPack()` 的 `evidence_snapshot.oracle_progress` 均携带 `valid_packets: 0`、`ready_for_calibration: 0`、`production_tuning_allowed: false`、`references/oracle/artifacts/` 与 `external_verified` 晋级规则。 +- TDD/验证完成:新增红灯测试 `test_runtime_smoke_html_artifacts_are_ignored`、`test_validator_rejects_non_numeric_or_negative_shadbala_components`、CLI prompt pack `oracle_progress` 断言和前端 fallback 静态断言;修复后 `python3 -m pytest -q tests/test_frontend_productization.py::test_frontend_branded_avatar_and_prompt_pack_are_productized tests/test_frontend_productization.py::test_api_bridge_variants_prefer_backend_prompt_pack_context tests/test_frontend_productization.py::test_runtime_smoke_html_artifacts_are_ignored` 通过;`python3 -m pytest -q tests/test_cli_smoke.py::test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack tests/test_oracle_evidence_validator.py tests/test_api_server_security.py::test_oracle_evidence_api_validates_uploaded_packets` 通过;`npm run build --prefix jyotish-app` 通过;`python3 scripts/run_quality_gate.py --profile quick --skip-yoga-logic` 通过,当前 quick 集合 242 项 pytest、build 与 runtime smoke 均通过。 +- 当前下一最高优先级:在无法本机生成真实 JHora/PyJHora 外部截图的前提下,推进 Ashtakoot 合婚破冰与外部 oracle template 设计;同时准备把 Round 16-19 副手报告、work orders、artifact policy、capture guide 与 first packet template 纳入 Git 跟踪,避免不同窗口再次遗漏。 +- 完成 Antigravity Round 20 副手任务单:新增 `docs/research/antigravity_sidecar_work_order_round20_2026_06_25.md`,要求副手纠正 Round 19 “Ashtakoot 完全缺失”的过期结论,复核已有 `scripts/ashtakoot.py`、`tests/test_ashtakoot.py`、`jyotish_engine.py ashtakoot`、`/api/synastry` 与前端合盘入口;同时继续审计 Round 19 修复、Ashtakoot oracle case 设计、UI/E2E 流程、JHora 1/5 人工监督、Git 纳入策略、开源 Ashtakoot 许可证矩阵、Shadbala 单位/总分设计和 AI Prompt Pack oracle progress。 +- 当前下一最高优先级:先不重写 Ashtakoot 已有算法,转为补 Ashtakoot 外部 oracle cases、E2E 用户流程和 Git tracking 收口;真实 JHora/PyJHora 1/5 仍等待人工外部工具。 +- 完成 Antigravity Round 21 重型副手任务单:新增 `docs/research/antigravity_sidecar_work_order_round21_2026_06_25.md`,把副手工作量扩到 18 个报告包、至少 20 个联网开源/产品复核对象、Top 40 ROI 任务,并明确 Ashtakoot oracle、VedAstro/RaviKarrii/pyhora2 许可证深挖、Trust Center UX、Git 入库、Shadbala 二期校验和 Round 22 执行计划。 +- 完成 Ashtakoot 外部 oracle 第一层队列与 validator 强化:新增 `references/oracle/ashtakoot_oracle_cases.json` 5 条 draft cases,`oracle_collection_queue.py` 可输出 `target_modules=["ashtakoot"]`,README 记录 `Ashtakoot 外部合婚 oracle` 命令;`oracle_evidence_validator.py` 增加 36 分制范围、8 Kuta 分项范围和总分求和一致性拦截,拒绝 99 分、字符串/非法范围与 sum mismatch 的假外部证据包。 +- 验证完成:`python3 -m pytest -q tests/test_oracle_evidence_validator.py tests/test_oracle_collection_queue.py` 14 项通过;`python3 -m pytest -q tests/test_ashtakoot.py tests/test_frontend_productization.py::test_dasha_reference_audit_is_documented_and_gated` 53 项通过;`npm run build --prefix jyotish-app` 通过;Ashtakoot queue 输出 5 条 template-only 样本且 `production_tuning_allowed=false`。 diff --git a/references/oracle/artifacts/.gitkeep b/references/oracle/artifacts/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/references/oracle/artifacts/.gitkeep @@ -0,0 +1 @@ + diff --git a/references/oracle/artifacts/README.md b/references/oracle/artifacts/README.md new file mode 100644 index 00000000..1f5204ae --- /dev/null +++ b/references/oracle/artifacts/README.md @@ -0,0 +1,34 @@ +# External Oracle Artifacts + +This directory stores sanitized evidence artifacts referenced by `source_artifact` +inside external oracle evidence packets. + +## Required Path Policy + +- Use repo-relative paths such as `references/oracle/artifacts/jhora_REDACTED_YEAR_moon_lahiri_v1.png`. +- `source_artifact` must point to a file in `references/oracle/artifacts/` or to a documented external review location. +- Mark the artifact kind as `external_oracle_artifact` in operator notes when possible. + +## Privacy Rules + +- 必须打码 any real person's full name, contact details, exact street address, hospital, account name, or other identifying text. +- 不得提交私人 PDF 原件;only sanitized screenshots or typed stdout snippets belong here. +- 不得提交完整出生报告 or full private reading transcripts. +- 不得提交浏览器 scratch folders, screenshots containing account sessions, cookies, tokens, or desktop notifications. +- Prefer synthetic fixtures, public figures, or heavily redacted screenshots. + +## Allowed Examples + +- Redacted JHora screenshot showing settings, Moon longitude, Dasha boundary, or Shadbala component table. +- Redacted PyJHora stdout captured in a plain text file with command metadata. +- Public-figure sample screenshot where source settings are visible and no private account data appears. + +## Not Allowed + +- Unredacted volunteer/client birth data. +- `output_report.txt`, `results_extracted.md`, private PDFs, or full generated readings. +- Local engine output from this repository pretending to be an external oracle. + +Evidence packets remain `draft` until their target values are filled, their +artifact is reviewed, and their status is explicitly promoted to +`external_verified`. diff --git a/references/oracle/ashtakoot_oracle_cases.json b/references/oracle/ashtakoot_oracle_cases.json new file mode 100644 index 00000000..4b99b774 --- /dev/null +++ b/references/oracle/ashtakoot_oracle_cases.json @@ -0,0 +1,195 @@ +{ + "schema_version": 1, + "scope": "external_ashtakoot_oracle_cases", + "notes": [ + "These rows are external compatibility evidence templates, not production tuning constants.", + "Do not promote any row to external_verified until all target values come from JHora/VedAstro/AstroSage or another documented external source.", + "Do not copy AGPL/GPL/commercial implementation code or proprietary screenshots beyond redacted evidence artifacts." + ], + "template_cases": [ + { + "id": "ashtakoot_public_couple_lahiri_01", + "status": "template_only", + "source": "JHora/VedAstro/AstroSage compatibility screenshot", + "privacy": "public_or_synthetic_relationship_template", + "birth": { + "male": { + "year": 1955, + "month": 2, + "day": 24, + "hour": 19, + "minute": 15, + "second": 0, + "lat": 37.7749, + "lon": -122.4194, + "tz": -8 + }, + "female": { + "year": 1958, + "month": 8, + "day": 14, + "hour": 12, + "minute": 0, + "second": 0, + "lat": 34.0522, + "lon": -118.2437, + "tz": -8 + } + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "true", + "matching_system": "ashtakoot_36_point" + }, + "target": { + "total_score": null, + "varna": null, + "vashya": null, + "tara": null, + "yoni": null, + "graha_maitri": null, + "gana": null, + "bhakoot": null, + "nadi": null, + "kuja_status": null + }, + "verification_note": "Use public/synthetic relationship data first; fill targets only from external compatibility output." + }, + { + "id": "ashtakoot_synthetic_high_score_lahiri_02", + "status": "template_only", + "source": "VedAstro/JHora compatibility screenshot", + "privacy": "synthetic_relationship_template", + "birth": { + "male_moon_longitude_deg": 45.0, + "female_moon_longitude_deg": 48.0 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "matching_system": "ashtakoot_36_point", + "input_mode": "moon_longitude_only" + }, + "target": { + "total_score": null, + "varna": null, + "vashya": null, + "tara": null, + "yoni": null, + "graha_maitri": null, + "gana": null, + "bhakoot": null, + "nadi": null, + "kuja_status": null + }, + "verification_note": "Synthetic high-score style case; use an external tool that accepts Moon longitude or matching birth data." + }, + { + "id": "ashtakoot_synthetic_nadi_dosha_03", + "status": "template_only", + "source": "JHora/AstroSage compatibility screenshot", + "privacy": "synthetic_relationship_template", + "birth": { + "male_moon_longitude_deg": 0.0, + "female_moon_longitude_deg": 180.0 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "matching_system": "ashtakoot_36_point", + "input_mode": "moon_longitude_only" + }, + "target": { + "total_score": null, + "varna": null, + "vashya": null, + "tara": null, + "yoni": null, + "graha_maitri": null, + "gana": null, + "bhakoot": null, + "nadi": null, + "kuja_status": null + }, + "verification_note": "Nadi-dosha boundary case; capture any exception or mitigation note shown by the external source." + }, + { + "id": "ashtakoot_synthetic_bhakoot_boundary_04", + "status": "template_only", + "source": "VedAstro/JHora compatibility screenshot", + "privacy": "synthetic_relationship_template", + "birth": { + "male_moon_longitude_deg": 15.5, + "female_moon_longitude_deg": 120.0 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "matching_system": "ashtakoot_36_point", + "input_mode": "moon_longitude_only" + }, + "target": { + "total_score": null, + "varna": null, + "vashya": null, + "tara": null, + "yoni": null, + "graha_maitri": null, + "gana": null, + "bhakoot": null, + "nadi": null, + "kuja_status": null + }, + "verification_note": "Bhakoot/rashi distance boundary case; capture component score and external explanation." + }, + { + "id": "ashtakoot_kuja_dosha_chart_05", + "status": "template_only", + "source": "JHora/AstroSage compatibility screenshot", + "privacy": "synthetic_relationship_template", + "birth": { + "male": { + "year": 1990, + "month": 1, + "day": 15, + "hour": 6, + "minute": 30, + "second": 0, + "lat": 28.6139, + "lon": 77.209, + "tz": 5.5 + }, + "female": { + "year": 1992, + "month": 7, + "day": 20, + "hour": 18, + "minute": 45, + "second": 0, + "lat": 19.076, + "lon": 72.8777, + "tz": 5.5 + } + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "mean", + "matching_system": "ashtakoot_36_point", + "requires_full_chart": true + }, + "target": { + "total_score": null, + "varna": null, + "vashya": null, + "tara": null, + "yoni": null, + "graha_maitri": null, + "gana": null, + "bhakoot": null, + "nadi": null, + "kuja_status": null + }, + "verification_note": "Full chart compatibility case; external artifact must show Kuja/Manglik status or its absence." + } + ] +} diff --git a/references/oracle/evidence_packet_templates/jhora_steve_jobs_lahiri_first_packet.json b/references/oracle/evidence_packet_templates/jhora_steve_jobs_lahiri_first_packet.json new file mode 100644 index 00000000..673867af --- /dev/null +++ b/references/oracle/evidence_packet_templates/jhora_steve_jobs_lahiri_first_packet.json @@ -0,0 +1,115 @@ +{ + "capture_id": "external_template_steve_jobs_dasha_lahiri", + "status": "draft", + "case_id": "template_steve_jobs_dasha_lahiri", + "birth": { + "year": 1955, + "month": 2, + "day": 24, + "hour": 19, + "minute": 15, + "second": 0, + "lat": 37.7749, + "lon": -122.4194, + "tz": -8 + }, + "settings": { + "ayanamsa": "lahiri", + "node_mode": "true" + }, + "required_metadata_fields": [ + "tool_name", + "tool_version_or_url", + "capture_date", + "source_artifact", + "ayanamsa", + "node_mode", + "timezone", + "operator_note" + ], + "metadata": { + "tool_name": "", + "tool_version_or_url": "", + "capture_date": "", + "source_artifact": "references/oracle/artifacts/", + "ayanamsa": "Lahiri", + "node_mode": "true node", + "timezone": "UTC-08:00", + "operator_note": "" + }, + "target_placeholders": { + "target.vimshottari_start_date": null, + "target.shadbala_components": { + "Sun": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + }, + "Moon": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + }, + "Mars": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + }, + "Mercury": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + }, + "Jupiter": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + }, + "Venus": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + }, + "Saturn": { + "sthana": null, + "dig": null, + "kala": null, + "chesta": null, + "naisargika": null, + "drik": null + } + } + }, + "integrity_checks": { + "must_not_come_from_local_engine": true, + "requires_external_artifact": true, + "requires_status_external_verified_before_calibration": true, + "reject_global_shadbala_scaling": true + }, + "promotion_status_after_fill": "external_verified", + "operator_checklist": [ + "Open JHora or PyJHora as an external black-box oracle.", + "Capture a redacted source artifact under references/oracle/artifacts/.", + "Fill Vimshottari start date from the external tool.", + "Fill all seven Shadbala planets and all six components from the external tool.", + "Promote status to external_verified only after source_artifact and target values are filled." + ] +} diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index 56928ea6..42d10be5 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -1817,6 +1817,17 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): '不要仅凭单一配置下结论;核心判断至少交叉 D1、D9、Dasha、Shadbala/Ashtakavarga 或 Transit 中的两个证据层。', '必须显式标注置信度和边界:Dasha/PDF 起点差异、Shadbala 外部绝对值 oracle 尚未完成时,不得声称已经完全校准。', ] + oracle_progress = { + 'scope': 'external_oracle_evidence_validation', + 'collection_queue': 'external_oracle_collection_queue', + 'total_packets': 5, + 'valid_packets': 0, + 'ready_for_calibration': 0, + 'production_tuning_allowed': False, + 'artifact_policy': 'references/oracle/artifacts/', + 'promotion_rule': 'external_verified requires source_artifact, filled target values, and non-local-engine external evidence.', + 'boundary': 'Dasha/Shadbala absolute values are not externally calibrated until enough packets pass validation.', + } return { 'schema_version': 1, 'mode': 'jyotish_structured_prompt_pack', @@ -1844,6 +1855,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): 'quality_boundary': { 'external_oracle_status': 'D1/D9/VedAstro longitude boundary covered; Dasha/Shadbala external absolute calibration still requires multi-source oracle expansion.', }, + 'oracle_progress': oracle_progress, }, 'retrieval_plan': { 'local_reference_docs': [ @@ -1858,6 +1870,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): 'no_single_factor_conclusion', 'd1_d9_dasha_cross_validation', 'oracle_boundary_visible', + 'external_oracle_evidence_validation', 'confidence_labeled_reading', ], }, diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 2ab93555..9624f0b3 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -567,6 +567,20 @@ def _planet_snapshot(planets, planet_name): } +def _oracle_progress_snapshot(): + return { + 'scope': 'external_oracle_evidence_validation', + 'collection_queue': 'external_oracle_collection_queue', + 'total_packets': 5, + 'valid_packets': 0, + 'ready_for_calibration': 0, + 'production_tuning_allowed': False, + 'artifact_policy': 'references/oracle/artifacts/', + 'promotion_rule': 'external_verified requires source_artifact, filled target values, and non-local-engine external evidence.', + 'boundary': 'Dasha/Shadbala absolute values are not externally calibrated until enough packets pass validation.', + } + + def _build_ai_prompt_pack(report): """Build a compact, evidence-first prompt pack for downstream AI/RAG reading.""" modules = report.get('modules', {}) if isinstance(report, dict) else {} @@ -640,6 +654,7 @@ def _build_ai_prompt_pack(report): 'warnings': report.get('warnings', []), 'external_oracle_status': 'D1/D9/VedAstro longitude boundary covered; Dasha/Shadbala external absolute calibration still requires multi-source oracle expansion.', }, + 'oracle_progress': _oracle_progress_snapshot(), } prompt_lines = [ @@ -670,6 +685,7 @@ def _build_ai_prompt_pack(report): 'no_single_factor_conclusion', 'd1_d9_dasha_cross_validation', 'oracle_boundary_visible', + 'external_oracle_evidence_validation', 'confidence_labeled_reading', ], }, diff --git a/scripts/oracle_collection_queue.py b/scripts/oracle_collection_queue.py index 90275c94..9786e0b7 100644 --- a/scripts/oracle_collection_queue.py +++ b/scripts/oracle_collection_queue.py @@ -58,6 +58,19 @@ SOURCE_GUIDANCE = { "The row is not derived from this repository's local Shadbala output.", ], }, + "ashtakoot": { + "preferred_sources": ["JHora compatibility screenshot", "VedAstro HTTP/API output", "AstroSage public compatibility screen"], + "steps": [ + "Set both birth records or both Moon longitudes in the external compatibility tool.", + "Record the 36-point total score and all eight Kuta component scores.", + "Capture Kuja/Manglik status and any visible ayanamsa, node mode or matching settings.", + ], + "promotion_criteria": [ + "All eight Ashtakoot component targets are filled from an external compatibility source.", + "The 36-point total is traceable to component scores.", + "The row is not derived from this repository's local synastry/ashtakoot output.", + ], + }, } @@ -67,6 +80,16 @@ FIELD_TO_MODULE = { "ascendant_longitude_deg": "longitude", "vimshottari_start_date": "dasha", "shadbala_components": "shadbala", + "total_score": "ashtakoot", + "varna": "ashtakoot", + "vashya": "ashtakoot", + "tara": "ashtakoot", + "yoni": "ashtakoot", + "graha_maitri": "ashtakoot", + "gana": "ashtakoot", + "bhakoot": "ashtakoot", + "nadi": "ashtakoot", + "kuja_status": "ashtakoot", } REQUIRED_EVIDENCE_METADATA_FIELDS = [ diff --git a/scripts/oracle_evidence_validator.py b/scripts/oracle_evidence_validator.py index 49649561..78d2abd8 100644 --- a/scripts/oracle_evidence_validator.py +++ b/scripts/oracle_evidence_validator.py @@ -24,6 +24,29 @@ LOCAL_ENGINE_MARKERS = [ "oracle_collection_queue.py", "oracle_boundary_audit.py", ] +SHADBALA_REQUIRED_PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] +SHADBALA_REQUIRED_COMPONENTS = ["sthana", "dig", "kala", "chesta", "naisargika", "drik"] +ASHTAKOOT_SCORE_RANGES = { + "target.total_score": (0.0, 36.0), + "target.varna": (0.0, 1.0), + "target.vashya": (0.0, 2.0), + "target.tara": (0.0, 3.0), + "target.yoni": (0.0, 4.0), + "target.graha_maitri": (0.0, 5.0), + "target.gana": (0.0, 6.0), + "target.bhakoot": (0.0, 7.0), + "target.nadi": (0.0, 8.0), +} +ASHTAKOOT_COMPONENT_FIELDS = [ + "target.varna", + "target.vashya", + "target.tara", + "target.yoni", + "target.graha_maitri", + "target.gana", + "target.bhakoot", + "target.nadi", +] def _resolve_path(path: str) -> str: @@ -72,6 +95,13 @@ def _validate_packet(task: dict[str, Any]) -> dict[str, Any]: if _is_blank(value): problems.append(f"placeholder_unfilled:{field}") + shadbala_components = target_placeholders.get("target.shadbala_components") + if "target.shadbala_components" in expected_fields: + problems.extend(_validate_shadbala_components(shadbala_components)) + + if "target.total_score" in expected_fields: + problems.extend(_validate_ashtakoot_scores(target_placeholders)) + integrity = packet.get("integrity_checks", {}) if integrity.get("must_not_come_from_local_engine") and _is_local_engine_artifact(packet): problems.append("local_engine_artifact_rejected") @@ -95,6 +125,58 @@ def _validate_packet(task: dict[str, Any]) -> dict[str, Any]: } +def _validate_shadbala_components(value: Any) -> list[str]: + problems: list[str] = [] + if _is_blank(value) or not isinstance(value, dict): + return ["missing_shadbala_component:all_planets"] + for planet in SHADBALA_REQUIRED_PLANETS: + row = value.get(planet) + if not isinstance(row, dict) or _is_blank(row): + problems.append(f"missing_shadbala_component:{planet}") + continue + for component in SHADBALA_REQUIRED_COMPONENTS: + component_value = row.get(component) + if _is_blank(component_value): + problems.append(f"missing_shadbala_component:{planet}.{component}") + continue + if not isinstance(component_value, (int, float)) or isinstance(component_value, bool): + problems.append(f"invalid_shadbala_component_type:{planet}.{component}") + continue + if component_value < 0: + problems.append(f"invalid_shadbala_component_negative:{planet}.{component}") + return problems + + +def _is_number(value: Any) -> bool: + return isinstance(value, (int, float)) and not isinstance(value, bool) + + +def _validate_ashtakoot_scores(target_placeholders: dict[str, Any]) -> list[str]: + problems: list[str] = [] + numeric_values: dict[str, float] = {} + for field, (minimum, maximum) in ASHTAKOOT_SCORE_RANGES.items(): + if field not in target_placeholders: + continue + value = target_placeholders.get(field) + if _is_blank(value): + continue + if not _is_number(value): + problems.append(f"invalid_ashtakoot_score_type:{field}") + continue + numeric_value = float(value) + numeric_values[field] = numeric_value + if numeric_value < minimum or numeric_value > maximum: + problems.append(f"invalid_ashtakoot_score_range:{field}") + + if "target.total_score" in numeric_values and all( + field in numeric_values for field in ASHTAKOOT_COMPONENT_FIELDS + ): + component_sum = sum(numeric_values[field] for field in ASHTAKOOT_COMPONENT_FIELDS) + if abs(component_sum - numeric_values["target.total_score"]) > 0.01: + problems.append("ashtakoot_score_sum_mismatch") + return problems + + def build_report(queue: dict[str, Any]) -> dict[str, Any]: packets = [_validate_packet(task) for task in queue.get("tasks", [])] ready_for_calibration = sum(1 for packet in packets if packet["ready_for_calibration"]) diff --git a/task_plan.md b/task_plan.md index 3b200baf..988648b8 100644 --- a/task_plan.md +++ b/task_plan.md @@ -112,4 +112,8 @@ - [x] AI Native Prompt/RAG 承载层第一步:`full-reading.ai_prompt_pack` 输出证据快照、检索文档、边界约束和结构化中文提示词,供网页/app 或 skill 后端 AI 代理生成高阶解读。 - [x] Antigravity AI 副手工作单:新增 `docs/research/antigravity_sidecar_work_order_2026_06_25.md`,把 Antigravity 限定为外部 oracle 样本采集、网页/app 审计、skill 同步审计和浏览器用户流验证,避免与核心计算修改冲突。 - [x] Standalone ayanamsa 全局状态修复:新增 `scripts/ayanamsa_utils.py`,让 Transit、Solar Return、Muhurta、cmd_muhurta、Yoga 验证脚本在 `FLG_SIDEREAL` 前显式设置 ayanamsa;默认 Lahiri,调用方可显式传入 Raman/KP 等。 -- [ ] 下一步:继续扩充多来源 oracle 样本,补 JHora/PyJHora 的 Moon sidereal longitude、ayanamsa、Vimshottari 起点和 Shadbala 六分量目标值;同时把前端 Multi-Ayanamsa 设置与 `ai_prompt_pack` 可视化。 +- [x] 前端 Multi-Ayanamsa 设置与 `ai_prompt_pack` 可视化:完整解盘页显示 Ayanamsa 后端实算/浏览器降级状态,AI Prompt Pack 支持复制 Prompt 与 Evidence 审计上下文。 +- [x] Oracle artifact 存档规范、Shadbala evidence 强校验与真实进度面板:`references/oracle/artifacts/` 已有脱敏规范与占位,Trust Center 显示 `0 / 5` 真实采集进度,validator 强制 Shadbala 七曜六分量,quick quality gate 已通过。 +- [x] Ashtakoot 外部 oracle 样本队列与第一层 validator:新增 5 条外部合婚 oracle draft cases,README/队列生成器/测试同步;validator 拦截 36 分制范围、8 Kuta 分项范围和总分不一致,防止假外部证据污染。 +- [x] Antigravity Round 21 重型副手任务单:把副手任务扩展到 18 个报告包、至少 20 个联网对标对象、Top 40 ROI 拆解,继续承接 Ashtakoot、开源复用、Trust Center UX、Git 入库和 Round 22 规划。 +- [ ] 第一条 JHora/PyJHora 黑盒证据包:等待人工外部工具截图/输出,补 Moon sidereal longitude、ayanamsa、Vimshottari 起点和 Shadbala 七曜六分量目标值,把 `valid_packets` 从 0/5 推到 1/5。 diff --git a/tests/test_cli_smoke.py b/tests/test_cli_smoke.py index 4f835962..72aa1c96 100644 --- a/tests/test_cli_smoke.py +++ b/tests/test_cli_smoke.py @@ -186,6 +186,13 @@ def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None: assert "references/ai-reading-workflow-prompt.md" in prompt_pack["retrieval_plan"]["local_reference_docs"] assert prompt_pack["evidence_snapshot"]["ayanamsa"]["name"] == "raman" assert prompt_pack["evidence_snapshot"]["core"]["ascendant"]["sign"] == result["chart"]["ascendant"]["sign"] + oracle_progress = prompt_pack["evidence_snapshot"]["oracle_progress"] + assert oracle_progress["scope"] == "external_oracle_evidence_validation" + assert oracle_progress["valid_packets"] == 0 + assert oracle_progress["ready_for_calibration"] == 0 + assert oracle_progress["production_tuning_allowed"] is False + assert oracle_progress["artifact_policy"] == "references/oracle/artifacts/" + assert "external_verified" in oracle_progress["promotion_rule"] def test_varga_cli_outputs_d9_and_d10() -> None: diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py index b57dbb2b..304a4aa5 100644 --- a/tests/test_frontend_productization.py +++ b/tests/test_frontend_productization.py @@ -253,6 +253,8 @@ def test_frontend_branded_avatar_and_prompt_pack_are_productized() -> None: assert "function renderAIPromptPackPanel" in main assert "ai-prompt-pack-panel" in main assert "evidence_snapshot" in main + assert "oracle_progress" in main + assert "artifact_policy: 'references/oracle/artifacts/'" in main assert "retrieval_plan" in main assert ".logo-avatar" in style assert ".ai-prompt-pack-panel" in style @@ -273,6 +275,11 @@ def test_frontend_ayanamsa_settings_are_live_api_parameters() -> None: assert "computeChart({ year, month, day, hour, minute, second, lat, lon, tz })" in main assert "fallbackChart._calculation_boundary" in main assert "payload.ayanamsa" in main + assert "renderAyanamsaRuntimeStatus(cd, pack)" in main + assert "function renderAyanamsaRuntimeStatus" in main + assert "backend_computed" in main + assert "browser_fallback" in main + assert "ayanamsa-runtime-status" in main def test_ai_chat_prefers_backend_prompt_pack_context() -> None: @@ -285,6 +292,20 @@ def test_ai_chat_prefers_backend_prompt_pack_context() -> None: assert "AI Prompt Pack 已作为上下文入口" in ai_chat +def test_ai_prompt_pack_panel_exposes_copyable_audit_context() -> None: + main = read("main.js") + style = read("style.css") + + assert "ai-prompt-pack-actions" in main + assert "data-ai-prompt-action=\"copy-prompt\"" in main + assert "data-ai-prompt-action=\"copy-evidence\"" in main + assert "setupAIPromptPackActions" in main + assert "copyAIPromptPackSection" in main + assert "AI Prompt Pack 审计上下文已复制" in main + assert ".ai-prompt-pack-actions" in style + assert ".ayanamsa-runtime-status" in style + + def test_api_bridge_variants_prefer_backend_prompt_pack_context() -> None: for rel_path in ["api-bridge.js", "public/api-bridge.js"]: bridge = read(rel_path) @@ -295,6 +316,7 @@ def test_api_bridge_variants_prefer_backend_prompt_pack_context() -> None: assert "【evidence_snapshot】" in bridge assert "JSON.stringify(chartData.ai_prompt_pack.evidence_snapshot" in bridge assert "JSON.stringify(chartData.ai_prompt_pack.retrieval_plan" in bridge + assert "external_oracle_evidence_validation: valid_packets: 0" in bridge def test_first_use_onboarding_is_actionable() -> None: @@ -607,6 +629,12 @@ def test_trust_center_exposes_oracle_evidence_intake_cards() -> None: "reject_global_shadbala_scaling", "status_not_external_verified", "local_engine_artifact_rejected", + "renderOracleEvidenceProgressDashboard", + "Dasha/Shadbala 真实进度", + "0 / 5", + "references/oracle/artifacts/", + "必须打码", + "missing_shadbala_component", "template_user_REDACTED_YEAR_moon_longitude_lahiri", "template_steve_jobs_dasha_lahiri", "template_redacted_place_shadbala_raman", @@ -637,10 +665,94 @@ def test_trust_center_exposes_oracle_evidence_intake_cards() -> None: ".oracle-evidence-fields", ".oracle-evidence-validation-result", ".oracle-evidence-card button", + ".oracle-evidence-progress-dashboard", + ".oracle-evidence-progress-bar", ]: assert token in style +def test_oracle_artifact_storage_policy_is_documented() -> None: + artifact_readme = ROOT / "references" / "oracle" / "artifacts" / "README.md" + assert artifact_readme.exists() + text = artifact_readme.read_text(encoding="utf-8") + readme = (ROOT / "README.md").read_text(encoding="utf-8") + + for token in [ + "references/oracle/artifacts/", + "source_artifact", + "必须打码", + "不得提交私人 PDF 原件", + "不得提交完整出生报告", + "浏览器 scratch", + "external_oracle_artifact", + ]: + assert token in text + + for token in [ + "references/oracle/artifacts/", + "source_artifact", + "必须打码", + "不得提交私人 PDF 原件", + ]: + assert token in readme + + +def test_runtime_smoke_html_artifacts_are_ignored() -> None: + gitignore = (ROOT / ".gitignore").read_text(encoding="utf-8") + for token in [ + "runtime-smoke-report-*.html", + "jyotish-app/runtime-smoke-report-*.html", + ]: + assert token in gitignore + + +def test_first_jhora_capture_guide_is_actionable() -> None: + guide = ROOT / "docs" / "user_jhora_capture_guide.md" + assert guide.exists() + text = guide.read_text(encoding="utf-8") + readme = (ROOT / "README.md").read_text(encoding="utf-8") + + for token in [ + "template_user_REDACTED_YEAR_moon_longitude_lahiri", + "template_steve_jobs_dasha_lahiri", + "Lahiri", + "Raman", + "KP", + "mean node", + "true node", + "Moon sidereal longitude", + "Vimshottari start date", + "Shadbala 七曜六分量", + "Sun", + "Moon", + "Mars", + "Mercury", + "Jupiter", + "Venus", + "Saturn", + "sthana", + "dig", + "kala", + "chesta", + "naisargika", + "drik", + "references/oracle/artifacts/", + "source_artifact", + "external_verified", + "python3 scripts/oracle_collection_queue.py", + "python3 scripts/oracle_evidence_validator.py", + "valid_packets: 1", + "ready_for_calibration: 1", + "必须打码", + "不得提交私人 PDF 原件", + "不得提交完整出生报告", + "浏览器 scratch", + ]: + assert token in text + + assert "docs/user_jhora_capture_guide.md" in readme + + def test_trust_center_exposes_real_case_revalidation_to_users() -> None: main = read("main.js") style = read("style.css") @@ -2079,6 +2191,7 @@ def test_dasha_reference_audit_is_documented_and_gated() -> None: queue_script = ROOT / "scripts" / "oracle_collection_queue.py" evidence_validator = ROOT / "scripts" / "oracle_evidence_validator.py" oracle_fixture = ROOT / "references" / "oracle" / "dasha_shadbala_oracle_cases.json" + ashtakoot_oracle_fixture = ROOT / "references" / "oracle" / "ashtakoot_oracle_cases.json" quality_gate_module = load_quality_gate_module() quality_gate = (ROOT / "scripts" / "run_quality_gate.py").read_text(encoding="utf-8") readme = (ROOT / "README.md").read_text(encoding="utf-8") @@ -2088,6 +2201,7 @@ def test_dasha_reference_audit_is_documented_and_gated() -> None: assert queue_script.exists() assert evidence_validator.exists() assert oracle_fixture.exists() + assert ashtakoot_oracle_fixture.exists() assert "tests/test_oracle_collection_queue.py" in quality_gate_module.CORE_PYTEST_TARGETS assert "tests/test_oracle_evidence_validator.py" in quality_gate_module.CORE_PYTEST_TARGETS for token in [ @@ -2119,7 +2233,20 @@ def test_dasha_reference_audit_is_documented_and_gated() -> None: "python3 scripts/oracle_collection_queue.py", "python3 scripts/oracle_evidence_validator.py", "references/oracle/dasha_shadbala_oracle_cases.json", + "references/oracle/ashtakoot_oracle_cases.json", "--target-start-date 1986-05-18", + "Ashtakoot 外部合婚 oracle", + "ashtakoot_36_point", + "target.total_score", + "target.varna", + "target.vashya", + "target.tara", + "target.yoni", + "target.graha_maitri", + "target.gana", + "target.bhakoot", + "target.nadi", + "target.kuja_status", "不要为单份 PDF 直接调生产常数", "Moon sidereal longitude", "production_tuning_recommended: false", diff --git a/tests/test_oracle_collection_queue.py b/tests/test_oracle_collection_queue.py index 46bb81f8..4b0a43e5 100644 --- a/tests/test_oracle_collection_queue.py +++ b/tests/test_oracle_collection_queue.py @@ -102,6 +102,67 @@ def test_oracle_collection_queue_outputs_markdown_table() -> None: assert "capture_id" in markdown +def test_first_jhora_evidence_packet_template_is_safe_and_fillable() -> None: + template = ROOT / "references" / "oracle" / "evidence_packet_templates" / "jhora_steve_jobs_lahiri_first_packet.json" + assert template.exists() + packet = json.loads(template.read_text(encoding="utf-8")) + + assert packet["capture_id"] == "external_template_steve_jobs_dasha_lahiri" + assert packet["status"] == "draft" + assert packet["case_id"] == "template_steve_jobs_dasha_lahiri" + assert packet["metadata"]["tool_name"] == "" + assert packet["metadata"]["source_artifact"] == "references/oracle/artifacts/" + assert packet["metadata"]["ayanamsa"] == "Lahiri" + assert packet["metadata"]["node_mode"] == "true node" + assert packet["integrity_checks"]["must_not_come_from_local_engine"] is True + assert packet["integrity_checks"]["requires_external_artifact"] is True + assert packet["integrity_checks"]["reject_global_shadbala_scaling"] is True + assert packet["target_placeholders"]["target.vimshottari_start_date"] is None + + shadbala = packet["target_placeholders"]["target.shadbala_components"] + assert set(shadbala) == {"Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"} + for row in shadbala.values(): + assert set(row) == {"sthana", "dig", "kala", "chesta", "naisargika", "drik"} + assert all(value is None for value in row.values()) + + +def test_ashtakoot_oracle_cases_generate_collection_tasks() -> None: + oracle_file = ROOT / "references" / "oracle" / "ashtakoot_oracle_cases.json" + assert oracle_file.exists() + + completed = run_queue_for_file(oracle_file, "--format", "json") + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert report["scope"] == "external_oracle_collection_queue" + assert report["summary"]["total_tasks"] == 5 + assert report["summary"]["ready_for_calibration"] == 0 + assert report["summary"]["production_tuning_allowed"] is False + + first = report["tasks"][0] + assert first["case_id"] == "ashtakoot_public_couple_lahiri_01" + assert first["privacy"] == "public_or_synthetic_relationship_template" + assert "ashtakoot" in first["target_modules"] + assert any("JHora" in source for source in first["preferred_sources"]) + assert any("VedAstro" in source for source in first["preferred_sources"]) + for field in [ + "target.total_score", + "target.varna", + "target.vashya", + "target.tara", + "target.yoni", + "target.graha_maitri", + "target.gana", + "target.bhakoot", + "target.nadi", + "target.kuja_status", + ]: + assert field in first["target_fields"] + assert field in first["evidence_packet"]["target_placeholders"] + assert first["evidence_packet"]["integrity_checks"]["must_not_come_from_local_engine"] is True + assert first["do_not_tune_production"] is True + + def test_oracle_collection_queue_preserves_external_verified_evidence(tmp_path: Path) -> None: oracle = json.loads((ROOT / "references/oracle/dasha_shadbala_oracle_cases.json").read_text(encoding="utf-8")) case = oracle["template_cases"][0] diff --git a/tests/test_oracle_evidence_validator.py b/tests/test_oracle_evidence_validator.py index af460c55..5ce9a3c1 100644 --- a/tests/test_oracle_evidence_validator.py +++ b/tests/test_oracle_evidence_validator.py @@ -10,6 +10,18 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] +SHADBALA_PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] +SHADBALA_COMPONENTS = ["sthana", "dig", "kala", "chesta", "naisargika", "drik"] + + +def complete_shadbala_components() -> dict[str, dict[str, float]]: + return { + planet: { + component: float(40 + planet_index * 3 + component_index) + for component_index, component in enumerate(SHADBALA_COMPONENTS) + } + for planet_index, planet in enumerate(SHADBALA_PLANETS) + } def build_queue() -> dict: @@ -104,16 +116,7 @@ def test_validator_accepts_filled_external_packet_but_not_whole_queue(tmp_path: packet["target_placeholders"] = { "target.moon_sidereal_longitude_deg": 311.7897, "target.vimshottari_start_date": "1986-05-18", - "target.shadbala_components": { - "Sun": { - "sthana": 100.0, - "dig": 50.0, - "kala": 100.0, - "chesta": 40.0, - "naisargika": 60.0, - "drik": 30.0, - } - }, + "target.shadbala_components": complete_shadbala_components(), } packet["status"] = "external_verified" queue_path = tmp_path / "queue.json" @@ -131,6 +134,197 @@ def test_validator_accepts_filled_external_packet_but_not_whole_queue(tmp_path: assert first["problems"] == [] +def test_validator_rejects_incomplete_shadbala_component_rows(tmp_path: Path) -> None: + queue = build_queue() + packet = queue["tasks"][0]["evidence_packet"] + packet["metadata"] = { + "tool_name": "JHora", + "tool_version_or_url": "manual-screenshot-v1", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png", + "ayanamsa": "lahiri", + "node_mode": "mean", + "timezone": "UTC+08:00", + "operator_note": "Manual external screenshot; values typed from JHora screen.", + } + packet["target_placeholders"] = { + "target.moon_sidereal_longitude_deg": 311.7897, + "target.vimshottari_start_date": "1986-05-18", + "target.shadbala_components": { + "Sun": { + "sthana": 100.0, + "dig": 50.0, + } + }, + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "missing_shadbala_component:Sun.kala" in first["problems"] + assert "missing_shadbala_component:Sun.chesta" in first["problems"] + assert "missing_shadbala_component:Sun.naisargika" in first["problems"] + assert "missing_shadbala_component:Sun.drik" in first["problems"] + assert "missing_shadbala_component:Moon" in first["problems"] + + +def test_validator_rejects_non_numeric_or_negative_shadbala_components(tmp_path: Path) -> None: + queue = build_queue() + packet = queue["tasks"][0]["evidence_packet"] + shadbala = complete_shadbala_components() + shadbala["Sun"]["sthana"] = "100" + shadbala["Moon"]["dig"] = -1.0 + packet["metadata"] = { + "tool_name": "JHora", + "tool_version_or_url": "manual-screenshot-v1", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png", + "ayanamsa": "lahiri", + "node_mode": "mean", + "timezone": "UTC+08:00", + "operator_note": "Manual external screenshot; values typed from JHora screen.", + } + packet["target_placeholders"] = { + "target.moon_sidereal_longitude_deg": 311.7897, + "target.vimshottari_start_date": "1986-05-18", + "target.shadbala_components": shadbala, + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "invalid_shadbala_component_type:Sun.sthana" in first["problems"] + assert "invalid_shadbala_component_negative:Moon.dig" in first["problems"] + + +def test_validator_rejects_invalid_ashtakoot_score_ranges(tmp_path: Path) -> None: + queue = build_queue_from_file(ROOT / "references/oracle/ashtakoot_oracle_cases.json") + packet = queue["tasks"][0]["evidence_packet"] + packet["metadata"] = { + "tool_name": "VedAstro", + "tool_version_or_url": "https://vedastro.org/API", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_ashtakoot_case_01.png", + "ayanamsa": "lahiri", + "node_mode": "true", + "timezone": "UTC-08:00", + "operator_note": "External compatibility calculator screenshot.", + } + packet["target_placeholders"] = { + "target.total_score": 99.0, + "target.varna": 1.0, + "target.vashya": 2.0, + "target.tara": 3.0, + "target.yoni": 4.0, + "target.graha_maitri": 5.0, + "target.gana": 6.0, + "target.bhakoot": 7.0, + "target.nadi": 99.0, + "target.kuja_status": "", + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "invalid_ashtakoot_score_range:target.total_score" in first["problems"] + assert "invalid_ashtakoot_score_range:target.nadi" in first["problems"] + assert "placeholder_unfilled:target.kuja_status" in first["problems"] + + +def test_validator_rejects_ashtakoot_score_sum_mismatch(tmp_path: Path) -> None: + queue = build_queue_from_file(ROOT / "references/oracle/ashtakoot_oracle_cases.json") + packet = queue["tasks"][0]["evidence_packet"] + packet["metadata"] = { + "tool_name": "VedAstro", + "tool_version_or_url": "https://vedastro.org/API", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_ashtakoot_case_01.png", + "ayanamsa": "lahiri", + "node_mode": "true", + "timezone": "UTC-08:00", + "operator_note": "External compatibility calculator screenshot.", + } + packet["target_placeholders"] = { + "target.total_score": 20.0, + "target.varna": 1.0, + "target.vashya": 2.0, + "target.tara": 3.0, + "target.yoni": 4.0, + "target.graha_maitri": 5.0, + "target.gana": 6.0, + "target.bhakoot": 7.0, + "target.nadi": 8.0, + "target.kuja_status": "no_dosha", + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "ashtakoot_score_sum_mismatch" in first["problems"] + + +def test_validator_accepts_valid_ashtakoot_external_packet(tmp_path: Path) -> None: + queue = build_queue_from_file(ROOT / "references/oracle/ashtakoot_oracle_cases.json") + packet = queue["tasks"][0]["evidence_packet"] + packet["metadata"] = { + "tool_name": "VedAstro", + "tool_version_or_url": "https://vedastro.org/API", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_ashtakoot_case_01.png", + "ayanamsa": "lahiri", + "node_mode": "true", + "timezone": "UTC-08:00", + "operator_note": "External compatibility calculator screenshot.", + } + packet["target_placeholders"] = { + "target.total_score": 18.0, + "target.varna": 1.0, + "target.vashya": 1.0, + "target.tara": 2.0, + "target.yoni": 3.0, + "target.graha_maitri": 4.0, + "target.gana": 2.0, + "target.bhakoot": 3.0, + "target.nadi": 2.0, + "target.kuja_status": "no_dosha", + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is True + assert first["problems"] == [] + + def test_validator_rejects_local_engine_artifact(tmp_path: Path) -> None: queue = build_queue() packet = queue["tasks"][0]["evidence_packet"] @@ -167,16 +361,7 @@ def test_validator_accepts_external_verified_packet_generated_from_oracle_file(t case["target"] = { "moon_sidereal_longitude_deg": 311.7897, "vimshottari_start_date": "1986-05-18", - "shadbala_components": { - "Sun": { - "sthana": 100.0, - "dig": 50.0, - "kala": 100.0, - "chesta": 40.0, - "naisargika": 60.0, - "drik": 30.0, - } - }, + "shadbala_components": complete_shadbala_components(), } case["evidence_packet"] = { "status": "external_verified",