feat: add oracle evidence safeguards

This commit is contained in:
732642856
2026-06-25 23:23:56 +08:00
parent dbfe603df1
commit 96caaeae2c
20 changed files with 1337 additions and 25 deletions
+2
View File
@@ -21,3 +21,5 @@ htmlcov/
benchmarks/jyotish/outputs/
output_report.txt
results_extracted.md
runtime-smoke-report-*.html
jyotish-app/runtime-smoke-report-*.html
+14
View File
@@ -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 后,用证据验证器做第二层防线:
+199
View File
@@ -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.
+130 -3
View File
@@ -894,8 +894,13 @@ function renderAIPromptPackPanel(cd) {
<span>AI Prompt Pack</span>
<strong>${escapeHtml(pack.mode || 'jyotish_structured_prompt_pack')}</strong>
</div>
<em>schema v${escapeHtml(String(pack.schema_version || 1))}</em>
<div class="ai-prompt-pack-actions">
<button type="button" data-ai-prompt-action="copy-prompt">复制 Prompt</button>
<button type="button" data-ai-prompt-action="copy-evidence">复制 Evidence</button>
<em>schema v${escapeHtml(String(pack.schema_version || 1))}</em>
</div>
</div>
${renderAyanamsaRuntimeStatus(cd, pack)}
<div class="ai-prompt-pack-grid">
<div class="ai-prompt-pack-card">
<span>Ayanamsa</span>
@@ -945,6 +950,79 @@ function renderAIPromptPackPanel(cd) {
</div>
</section>
`;
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 `
<div class="ayanamsa-runtime-status ayanamsa-runtime-status-${escapeAttr(status)}">
<span>${escapeHtml(label)}</span>
<strong>${escapeHtml(getCalculationSettingLabel('ayanamsa', requested))}</strong>
<small>${escapeHtml(`applied=${applied} · status=${status}`)}</small>
<em>${escapeHtml(note)}</em>
</div>
`;
}
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() {
<strong>Oracle Evidence Intake</strong>
<span>下载 5 个外部真值空白包只接受 JHora/PyJHora/VedAstro 黑盒证据禁止本地输出冒充 external_verified</span>
</div>
${renderOracleEvidenceProgressDashboard()}
<div class="oracle-evidence-intake-grid">
${ORACLE_EVIDENCE_INTAKE_TASKS.map(task => `
<article class="oracle-evidence-card">
@@ -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 `
<section class="oracle-evidence-progress-dashboard" aria-label="Dasha/Shadbala 真实进度">
<div class="calculation-settings-head">
<strong>Dasha/Shadbala 真实进度</strong>
<span>${validPackets} / ${totalPackets} valid_packets · ${readyPackets} ready_for_calibration · production_tuning_allowed=false</span>
</div>
<div class="oracle-evidence-progress-bar" aria-label="Oracle evidence progress ${validPackets} / ${totalPackets}">
<span style="width: ${progress}%"></span>
</div>
<div class="dasha-shadbala-calibration-grid">
${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。')}
</div>
<div class="dasha-shadbala-calibration-boundary">
<strong>隐私</strong>
<span>私人截图必须打码不得上传私人 PDF 原件完整出生报告或浏览器 scratch 会话文件</span>
</div>
</section>
`;
}
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。`;
}
}
+5
View File
@@ -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,
+81 -1
View File
@@ -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,
+22
View File
@@ -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`
+1
View File
@@ -0,0 +1 @@
+34
View File
@@ -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`.
@@ -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."
}
]
}
@@ -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."
]
}
+13
View File
@@ -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',
],
},
+16
View File
@@ -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',
],
},
+23
View File
@@ -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 = [
+82
View File
@@ -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"])
+5 -1
View File
@@ -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 casesREADME/队列生成器/测试同步;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。
+7
View File
@@ -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:
+127
View File
@@ -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",
+61
View File
@@ -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]
+205 -20
View File
@@ -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",