Files
Jyotisha/frontend/src/mastra/consultation-workflow.ts
T
Jesse_Chen 8010245981
Independent Staging Quality Gate / validate (push) Failing after 6m42s
Independent Staging Quality Gate / publish (push) Has been skipped
feat(product): present consult and rectification in local skill form
Default ayanamsa to Raman with true_pushya support, attach governed Raman packets, restore Path C questionnaires and eight-method verification copy, and keep unique-minute confirmation blocked.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 21:42:47 +08:00

710 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod";
import { consultationEvidenceCategoryValues, createConsultationPlan, type ConsultationPlan } from "../lib/consultation-plan.ts";
import { consultationThemeValues, projectConsultationWorkflowRequest } from "../lib/consultation-workflow-request.ts";
export const consultationInputSchema = z.object({
year: z.number().int().min(1900).max(2100),
month: z.number().int().min(1).max(12),
day: z.number().int().min(1).max(31),
hour: z.number().int().min(0).max(23),
minute: z.number().int().min(0).max(59),
lat: z.number().min(-90).max(90),
lon: z.number().min(-180).max(180),
tz: z.number().min(-12).max(14),
city: z.string().trim().min(1).max(120),
question: z.string().trim().min(1).max(500),
theme: z.enum(consultationThemeValues),
entryMode: z.enum(["direct_chart", "rectification"]).default("direct_chart"),
});
export type ConsultationInput = z.infer<typeof consultationInputSchema>;
type JsonRecord = Record<string, unknown>;
const workflowConsumerContextSchema = z.object({
route: z.string().min(1),
core_status: z.enum(["ready", "degraded", "blocked"]),
available_layers: z.array(z.string()),
missing_route_layers: z.array(z.string()),
hard_blockers: z.array(z.string()),
technique_truth: z.record(z.unknown()).optional(),
answer_policy: z.object({
can_answer_direction: z.boolean(),
can_answer_precise_timing: z.boolean(),
}).passthrough(),
}).passthrough();
export const consultationWorkflowResponseSchema = z.object({
success: z.boolean(),
chart: z.record(z.unknown()),
routing: z.record(z.unknown()),
consumer_context: workflowConsumerContextSchema,
}).passthrough();
function record(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? value as JsonRecord : {};
}
const apiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200";
/**
* Closed vocabulary for why a consultation workflow call failed.
*
* The upstream error text is a provider payload and must not reach logs, so
* the failure is classified here into a machine code that observability can
* record without carrying free-form content.
*/
export const consultationWorkflowFailureCodes = [
"workflow_bad_request",
"workflow_forbidden",
"workflow_unsupported_media_type",
"workflow_rate_limited",
"workflow_queue_full",
"workflow_server_error",
"workflow_http_error",
"workflow_empty_response",
"workflow_contract_invalid",
"workflow_timeout",
"workflow_aborted",
"workflow_unreachable",
] as const;
export type ConsultationWorkflowFailureCode = typeof consultationWorkflowFailureCodes[number];
export class ConsultationWorkflowError extends Error {
readonly code: ConsultationWorkflowFailureCode;
constructor(code: ConsultationWorkflowFailureCode, message: string) {
super(message);
this.name = "ConsultationWorkflowError";
this.code = code;
}
}
function httpFailureCode(status: number): ConsultationWorkflowFailureCode {
if (status === 400) return "workflow_bad_request";
if (status === 403) return "workflow_forbidden";
if (status === 415) return "workflow_unsupported_media_type";
if (status === 429) return "workflow_rate_limited";
if (status === 503) return "workflow_queue_full";
if (status >= 500) return "workflow_server_error";
return "workflow_http_error";
}
export function consultationWorkflowFailureCode(error: unknown): ConsultationWorkflowFailureCode | undefined {
if (error instanceof ConsultationWorkflowError) return error.code;
if (error instanceof DOMException && error.name === "TimeoutError") return "workflow_timeout";
if (error instanceof DOMException && error.name === "AbortError") return "workflow_aborted";
return undefined;
}
export async function runConsultationWorkflow(
input: ConsultationInput,
options?: { foreground?: boolean; signal?: AbortSignal; plan?: ConsultationPlan; requestId?: string },
) {
const { entryMode, question, theme, ...workflowInput } = input;
const plan = options?.plan ?? createConsultationPlan({ userIntent: question, theme });
const workflowRequest = projectConsultationWorkflowRequest(question, theme, plan);
const timeout = AbortSignal.timeout(90_000);
const signal = options?.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
const response = await fetch(`${apiBase}/api/consultation_workflow`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
...workflowInput,
// Correlates this call with the API access log when a run fails.
...(options?.requestId ? { request_id: options.requestId } : {}),
entry_mode: entryMode,
question: workflowRequest.question,
question_text: workflowRequest.question,
theme: workflowRequest.themes,
defer_optional_external_evidence: options?.foreground === true,
plan_version: workflowRequest.plan_version,
strict_workflow_route: workflowRequest.strictWorkflowRoute,
required_layers: workflowRequest.requiredLayers,
claim_boundary: workflowRequest.claimBoundary,
plan_depth: workflowRequest.depth,
requested_domains: workflowRequest.requested_domains,
timing_horizon: workflowRequest.timing_horizon,
precision_boundary: workflowRequest.precision_boundary,
required_evidence_categories: workflowRequest.required_evidence_categories,
}),
signal,
});
const data = await response.json().catch(() => null);
if (!response.ok || !data) {
throw new ConsultationWorkflowError(
response.ok ? "workflow_empty_response" : httpFailureCode(response.status),
data?.error || data?.message || `Jyotish API returned ${response.status}`,
);
}
const parsed = consultationWorkflowResponseSchema.safeParse(data);
if (!parsed.success) {
throw new ConsultationWorkflowError(
"workflow_contract_invalid",
"Jyotish API returned an incomplete consultation contract",
);
}
return parsed.data;
}
export function consultationWorkflowReceipt(data: JsonRecord) {
const consumerContext = workflowConsumerContextSchema.parse(data.consumer_context);
return {
route: consumerContext.route,
status: consumerContext.core_status,
preciseTiming: consumerContext.answer_policy.can_answer_precise_timing ? "allowed" : "blocked",
missingLayers: consumerContext.missing_route_layers.join(",") || "none",
techniqueTruth: String(record(consumerContext.technique_truth).status || "unknown"),
evidenceStatus: record(consumerContext.commercial_evidence_status),
};
}
type ModelOutputValue = string | number | boolean | null | ModelOutputValue[] | { [key: string]: ModelOutputValue };
const modelOutputEvidenceSchema = z.unknown();
export const consultationEvidencePacketSchema = z.object({
packet_version: z.literal("consultation-evidence-packet-v2"),
question: z.string().optional(),
route: z.string(),
status: z.enum(["ready", "degraded", "blocked"]),
evidence_contract: z.object({
available_layers: modelOutputEvidenceSchema.optional(),
missing_route_layers: modelOutputEvidenceSchema.optional(),
hard_blockers: modelOutputEvidenceSchema.optional(),
answer_policy: modelOutputEvidenceSchema.optional(),
user_facing_limitation: modelOutputEvidenceSchema.optional(),
technique_audit_table: modelOutputEvidenceSchema.optional(),
varga_spectrum: modelOutputEvidenceSchema.optional(),
western_spectrum: modelOutputEvidenceSchema.optional(),
must_use_layers: modelOutputEvidenceSchema.optional(),
}),
claim_cards: z.array(z.object({
category: z.enum(consultationEvidenceCategoryValues),
source: z.literal("server_chart").or(z.literal("server_workflow")),
evidence: modelOutputEvidenceSchema,
})),
presentation: z.object({
template: z.literal("skill_level_2"),
required_blocks: z.array(z.string()),
}),
rectification: z.object({ boundary: z.string() }),
}).strict();
export type ConsultationEvidencePacket = z.infer<typeof consultationEvidencePacketSchema>;
function normalizeEvidenceKey(key: string) {
return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
}
function boundedPrimitive(value: unknown): ModelOutputValue | undefined {
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
if (typeof value === "string") return value.length > 800 ? `${value.slice(0, 797)}...` : value;
return undefined;
}
const astrologyEntityKeys = new Set([
"sun", "moon", "mars", "mercury", "jupiter", "venus", "saturn", "rahu", "ketu",
"aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpio",
"sagittarius", "capricorn", "aquarius", "pisces",
"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth",
"ninth", "tenth", "eleventh", "twelfth",
]);
function isAstrologyEntityKey(key: string) {
const normalized = normalizeEvidenceKey(key);
return astrologyEntityKeys.has(normalized)
|| /^h(?:ouse)?\d{1,2}$/.test(normalized)
|| /^\d{1,2}$/.test(normalized);
}
function projectAllowlistedTree(
value: unknown,
allowedKeys: ReadonlySet<string>,
options: Readonly<{ allowAstrologyEntityKeys?: boolean; depth?: number }> = {},
): ModelOutputValue | undefined {
const primitive = boundedPrimitive(value);
if (primitive !== undefined) return primitive;
const depth = options.depth ?? 0;
if (depth > 4) return undefined;
if (Array.isArray(value)) {
return value.slice(0, 24)
.map((item) => projectAllowlistedTree(item, allowedKeys, { ...options, depth: depth + 1 }))
.filter((item): item is ModelOutputValue => item !== undefined);
}
if (!value || typeof value !== "object") return undefined;
const output: { [key: string]: ModelOutputValue } = {};
for (const [key, item] of Object.entries(value)) {
const normalized = normalizeEvidenceKey(key);
if (!allowedKeys.has(normalized) && !(options.allowAstrologyEntityKeys && isAstrologyEntityKey(key))) continue;
const projected = projectAllowlistedTree(item, allowedKeys, { ...options, depth: depth + 1 });
if (projected !== undefined) output[key] = projected;
if (Object.keys(output).length >= 24) break;
}
return output;
}
const natalFoundationKeys = new Set([
"ascendant", "planets", "houses", "shadbala", "ashtakavarga", "sarvashtakavarga",
"name", "planet", "sign", "signidx", "degree", "degreeinsign", "degreeraw", "lon",
"nakshatra", "pada", "lord", "house", "number", "occupants", "aspects", "retrograde",
"isretrograde", "dignity", "status", "total", "totalrupa", "rupa", "ratio", "required",
"rank", "strength", "score", "value", "sav", "bav", "points", "count", "housescores",
"components", "matrix", "matrixshape", "signs", "sources", "source", "method", "boundary",
"functionalbeneficmalefic", "functionalbenefics", "functionalmalefics", "functionalneutrals",
"yogakarakas", "ownedhouses", "ascendantsign", "effectonconfidence",
"yogas", "category", "arudhapadas", "padas", "a10", "a7", "ul", "al", "gulika", "mandi", "kakshya",
"kpcusps", "cusps", "sublord", "star", "starlord", "claimboundary", "reason",
]);
const timingKeys = new Set([
"dasha", "dashaboundaries", "dashasubperiods", "narayanadasha", "status", "current", "next", "mahadasha",
"antardasha", "pratyantardasha", "currentdasha", "period", "dashaperiod", "periods", "timeline",
"boundaries", "boundarycount", "start", "end", "startyear", "endyear", "iscurrent",
"activationdescription", "sign", "lord", "planet", "name", "strength", "score", "source",
"method", "confidence", "confidencecap", "summary", "conclusion", "fingerprint",
"charadasha", "transits", "sadesati", "triggers", "triggercount", "searchperiod", "window", "sequence", "durationyears",
"fromage", "toage", "phase", "phasename", "moonsign", "saturnsign", "intensity", "active",
"date", "target", "kind", "orb", "boundary", "claimboundary",
]);
const validationKeys = new Set([
"techniquetruth", "referencetransparency", "shadbalaboundary", "status", "state", "verified",
"executed", "blocked", "notapplicable", "available", "coverage", "source", "sources", "method",
"methods", "technique", "techniques", "reason", "reasons", "boundary", "confidence",
"confidencecap", "summary", "conclusion", "publiccontextonly", "similarpubliccases",
"highsimilaritypublicreferencesavailable", "requesteduncovereddomains", "timingstate",
"partialmatch", "narayanastatus", "transitstatus", "productiontuningallowed", "nomajorityvote",
"vedastrocrosscheck", "officialclosurestate", "officialclosurereason", "natal", "ascendant",
]);
const domainDetailsKeys = new Set([
"source", "derived", "fragment", "sign", "signidx", "planets", "planetslabel", "lord", "house",
"score", "level", "clues", "marriagecount", "d9marriagequality", "headline", "strengths", "risks",
"boundaries", "monthlyframe", "primarystate", "manifestationmode", "frictionsource", "timeconfidence",
"current", "summary", "interpretation", "description", "note", "status", "confidence",
]);
function projectVargaChart(value: unknown): ModelOutputValue | undefined {
const chart = record(value);
const planetsIn = record(chart.planets);
const planets: { [key: string]: ModelOutputValue } = {};
for (const [name, sign] of Object.entries(planetsIn)) {
const projected = boundedPrimitive(sign);
if (projected !== undefined) planets[name] = projected;
}
const output: { [key: string]: ModelOutputValue } = {};
const lagna = boundedPrimitive(chart.lagna);
if (lagna !== undefined) output.lagna = lagna;
if (Object.keys(planets).length) output.planets = planets;
for (const key of ["name", "meaning", "boundary"] as const) {
const projected = boundedPrimitive(chart[key]);
if (projected !== undefined) output[key] = projected;
}
return Object.keys(output).length ? output : undefined;
}
function projectVargaGroup(value: unknown): ModelOutputValue | undefined {
const group = record(value);
const output: { [key: string]: ModelOutputValue } = {};
for (const [code, chart] of Object.entries(group)) {
const projected = projectVargaChart(chart);
if (projected !== undefined) output[code] = projected;
}
return Object.keys(output).length ? output : undefined;
}
function projectVargaSpectrum(value: unknown): ModelOutputValue | undefined {
const spectrum = record(value);
if (!Object.keys(spectrum).length) return undefined;
const output: { [key: string]: ModelOutputValue } = {};
for (const key of ["status", "mode", "boundary"] as const) {
const projected = boundedPrimitive(spectrum[key]);
if (projected !== undefined) output[key] = projected;
}
const counts = record(spectrum.counts);
if (Object.keys(counts).length) {
const compact: { [key: string]: ModelOutputValue } = {};
for (const [key, item] of Object.entries(counts)) {
const projected = boundedPrimitive(item);
if (projected !== undefined) compact[key] = projected;
}
if (Object.keys(compact).length) output.counts = compact;
}
for (const group of ["formal", "research_dn", "extended"] as const) {
const projected = projectVargaGroup(spectrum[group]);
if (projected !== undefined) output[group] = projected;
}
if (Array.isArray(spectrum.blocked)) {
output.blocked = spectrum.blocked.filter((item): item is string => typeof item === "string").slice(0, 60);
}
return Object.keys(output).length ? output : undefined;
}
function projectTechniqueAudit(value: unknown): ModelOutputValue | undefined {
if (!Array.isArray(value)) return undefined;
const rows = value.slice(0, 80).map((item) => {
const row = record(item);
const projected: { [key: string]: ModelOutputValue } = {};
for (const key of ["technique", "status", "system", "boundary", "effect_on_confidence"] as const) {
const scalar = boundedPrimitive(row[key]);
if (scalar !== undefined) projected[key] = scalar;
}
for (const key of ["key_functional_benefics", "key_functional_malefics"] as const) {
if (!Array.isArray(row[key])) continue;
const names = row[key]
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
.slice(0, 9);
if (names.length) projected[key] = names;
}
return projected;
}).filter((row) => Object.keys(row).length > 0);
return rows.length ? rows : undefined;
}
function projectWesternTechnique(value: unknown): ModelOutputValue | undefined {
const row = record(value);
if (!Object.keys(row).length) return undefined;
const projected: { [key: string]: ModelOutputValue } = {};
for (const key of [
"status", "boundary", "target_date", "start_date", "end_date", "target_year", "return_date", "event_count",
] as const) {
const scalar = boundedPrimitive(row[key]);
if (scalar !== undefined) projected[key] = scalar;
}
if (Array.isArray(row.aspects)) {
const aspects = row.aspects.slice(0, 6).map((item) => {
const aspect = record(item);
const compact: { [key: string]: ModelOutputValue } = {};
for (const key of ["transit_planet", "natal_point", "aspect", "orb"] as const) {
const scalar = boundedPrimitive(aspect[key]);
if (scalar !== undefined) compact[key] = scalar;
}
return compact;
}).filter((item) => Object.keys(item).length);
if (aspects.length) projected.aspects = aspects;
}
if (Array.isArray(row.windows)) {
const windows = row.windows.slice(0, 6).map((item) => {
const window = record(item);
const compact: { [key: string]: ModelOutputValue } = {};
for (const key of ["transit_planet", "natal_point", "aspect", "start_date", "end_date", "min_orb"] as const) {
const scalar = boundedPrimitive(window[key]);
if (scalar !== undefined) compact[key] = scalar;
}
return compact;
}).filter((item) => Object.keys(item).length);
if (windows.length) projected.windows = windows;
}
return Object.keys(projected).length ? projected : undefined;
}
function projectWesternSpectrum(value: unknown): ModelOutputValue | undefined {
const spectrum = record(value);
if (!Object.keys(spectrum).length) return undefined;
const output: { [key: string]: ModelOutputValue } = {};
for (const key of ["status", "zodiac", "house_system", "boundary"] as const) {
const projected = boundedPrimitive(spectrum[key]);
if (projected !== undefined) output[key] = projected;
}
const natal = record(spectrum.natal);
if (Object.keys(natal).length) {
const compact: { [key: string]: ModelOutputValue } = {};
for (const [key, item] of Object.entries(natal)) {
const projected = boundedPrimitive(item);
if (projected !== undefined) compact[key] = projected;
}
if (Object.keys(compact).length) output.natal = compact;
}
const techniques = record(spectrum.techniques);
if (Object.keys(techniques).length) {
const compact: { [key: string]: ModelOutputValue } = {};
for (const [name, layer] of Object.entries(techniques)) {
const projected = projectWesternTechnique(layer);
if (projected !== undefined) compact[name] = projected;
}
if (Object.keys(compact).length) output.techniques = compact;
}
return Object.keys(output).length ? output : undefined;
}
function projectNatalFoundation(context: ReturnType<typeof toAgentConsultationContext>) {
const foundation = projectAllowlistedTree({
ascendant: context.chart.ascendant,
planets: context.chart.planets,
houses: context.chart.houses,
shadbala: context.chart.shadbala,
ashtakavarga: context.chart.ashtakavarga,
functional_benefic_malefic: context.local_layers.functional_benefic_malefic,
yogas: context.local_layers.yogas,
arudha_padas: context.local_layers.arudha_padas,
gulika: context.local_layers.gulika,
kakshya: context.local_layers.kakshya,
kp_cusps: context.local_layers.kp_cusps,
}, natalFoundationKeys, { allowAstrologyEntityKeys: true });
const output = record(foundation);
const varga = projectVargaSpectrum(context.local_layers.varga_spectrum);
if (varga !== undefined) output.varga_spectrum = varga;
return Object.keys(output).length ? output : undefined;
}
function projectDomainEvidence(value: unknown): ModelOutputValue {
const theme = record(value);
const output: { [key: string]: ModelOutputValue } = {};
for (const key of ["theme", "summary", "narrative", "strength"] as const) {
const projected = boundedPrimitive(theme[key]);
if (projected !== undefined) output[key] = projected;
}
if (Array.isArray(theme.recommendations)) {
output.recommendations = theme.recommendations.slice(0, 12)
.map(boundedPrimitive)
.filter((item): item is ModelOutputValue => item !== undefined);
}
if (Array.isArray(theme.evidence)) {
output.evidence = theme.evidence.slice(0, 24).map((rawItem) => {
const item = record(rawItem);
const projected: { [key: string]: ModelOutputValue } = {};
for (const key of ["technique", "chart", "conclusion", "sentiment", "strength"] as const) {
const scalar = boundedPrimitive(item[key]);
if (scalar !== undefined) projected[key] = scalar;
}
const details = projectAllowlistedTree(item.details, domainDetailsKeys, { allowAstrologyEntityKeys: true });
if (details !== undefined) projected.details = details;
return projected;
});
}
if (Array.isArray(theme.conflicts)) {
const conflictKeys = new Set(["techniquea", "conclusiona", "techniqueb", "conclusionb", "resolution", "reasoning", "winner"]);
output.conflicts = theme.conflicts.slice(0, 12)
.map((item) => projectAllowlistedTree(item, conflictKeys))
.filter((item): item is ModelOutputValue => item !== undefined);
}
const timing = projectAllowlistedTree(theme.timing, timingKeys, { allowAstrologyEntityKeys: true });
if (timing !== undefined) output.timing = timing;
return output;
}
function projectTimingEvidence(context: ReturnType<typeof toAgentConsultationContext>) {
const timing = projectAllowlistedTree({
dasha: context.chart.dasha,
dasha_sub_periods: context.local_layers.dasha_sub_periods,
narayana_dasha: context.local_layers.narayana_dasha,
chara_dasha: context.local_layers.chara_dasha,
transits: context.local_layers.transits,
}, timingKeys, { allowAstrologyEntityKeys: true });
const output = record(timing);
const western = projectWesternSpectrum(context.evidence_contract.western_spectrum);
if (western !== undefined) output.western_spectrum = western;
return Object.keys(output).length ? output : undefined;
}
function projectValidationEvidence(context: ReturnType<typeof toAgentConsultationContext>) {
const validation = projectAllowlistedTree({
technique_truth: context.evidence_contract.technique_truth,
reference_transparency: context.reference_transparency,
shadbala_boundary: context.local_layers.shadbala_boundary,
vedastro_cross_check: context.vedastro_cross_check,
}, validationKeys, { allowAstrologyEntityKeys: true });
const output = record(validation);
const audit = projectTechniqueAudit(context.evidence_contract.technique_audit_table);
if (audit !== undefined) output.technique_audit_table = audit;
return Object.keys(output).length ? output : undefined;
}
function stringList(value: unknown, limit = 24) {
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string").slice(0, limit) : [];
}
const SHARED_MUST_USE_LAYERS = [
"Formal Vargas D1D60",
"Functional Benefic/Malefic",
"Yogas",
"Vimshottari sub-periods",
"Narayana Dasha",
"Shadbala components",
"Ashtakavarga",
"Arudha / UL / A10",
"Western natal (tropical)",
"Transits / Sade Sati",
"VedAstro Cloud State",
] as const;
const DOMAIN_MUST_USE_LAYERS: Readonly<Record<string, readonly string[]>> = {
career: ["Formal Vargas D1D60", "Arudha / UL / A10", "Narayana Dasha", "Chara Dasha"],
marriage: ["Formal Vargas D1D60", "Arudha / UL / A10"],
wealth: ["Formal Vargas D1D60", "Ashtakavarga"],
timing: ["Narayana Dasha", "Vimshottari sub-periods", "Transits / Sade Sati", "Chara Dasha"],
annual: ["Narayana Dasha", "Vimshottari sub-periods", "Chara Dasha"],
health: ["Formal Vargas D1D60", "Shadbala components"],
};
function mustUseExecutedLayers(route: string, audit: unknown): string[] {
const rows = Array.isArray(audit) ? audit : [];
const executed = new Set(
rows
.map((item) => record(item))
.filter((row) => row.status === "executed")
.map((row) => String(row.technique || "")),
);
const wanted = [...SHARED_MUST_USE_LAYERS, ...(DOMAIN_MUST_USE_LAYERS[route] ?? [])];
const output: string[] = [];
const seen = new Set<string>();
for (const name of wanted) {
if (!executed.has(name) || seen.has(name)) continue;
seen.add(name);
output.push(name);
}
return output.slice(0, 24);
}
function projectEvidenceContract(contract: ReturnType<typeof toAgentConsultationContext>["evidence_contract"]) {
const policy = record(contract.answer_policy);
const audit = projectTechniqueAudit(contract.technique_audit_table);
const varga = projectVargaSpectrum(contract.varga_spectrum);
const western = projectWesternSpectrum(contract.western_spectrum);
const mustUse = mustUseExecutedLayers(String(contract.route || ""), contract.technique_audit_table);
return {
available_layers: stringList(contract.available_layers, 80),
missing_route_layers: stringList(contract.missing_route_layers, 40),
hard_blockers: stringList(contract.hard_blockers),
answer_policy: {
can_answer_direction: policy.can_answer_direction === true,
can_answer_precise_timing: policy.can_answer_precise_timing === true,
...(typeof policy.should_lead_with_limitations === "boolean"
? { should_lead_with_limitations: policy.should_lead_with_limitations }
: {}),
},
...(typeof contract.user_facing_limitation === "string"
? { user_facing_limitation: boundedPrimitive(contract.user_facing_limitation) }
: {}),
...(audit !== undefined ? { technique_audit_table: audit } : {}),
...(varga !== undefined ? { varga_spectrum: varga } : {}),
...(western !== undefined ? { western_spectrum: western } : {}),
...(mustUse.length > 0 ? { must_use_layers: mustUse } : {}),
};
}
function hasModelOutputEvidence(value: unknown) {
return value !== undefined && value !== null && (typeof value !== "object" || Object.keys(value as object).length > 0);
}
/**
* Keep the complete workflow context for application/audit use, but expose only
* bounded, server-selected evidence to the model. This is not a claim generator:
* every card is a projection of an existing server result.
*/
export function toModelOutput(context: ReturnType<typeof toAgentConsultationContext>, plan?: ConsultationPlan): ConsultationEvidencePacket {
const contract = context.evidence_contract;
const requiredCategories = new Set(plan?.requiredEvidenceCategories ?? consultationEvidenceCategoryValues);
const cards = [
{
category: "natal_foundation" as const,
source: "server_chart" as const,
evidence: projectNatalFoundation(context),
},
{
category: "domain" as const,
source: "server_workflow" as const,
evidence: projectDomainEvidence(context.thematic_evidence),
},
{
category: "timing" as const,
source: "server_workflow" as const,
evidence: projectTimingEvidence(context),
},
{
category: "validation" as const,
source: "server_workflow" as const,
evidence: projectValidationEvidence(context),
},
]
.filter((card) => requiredCategories.has(card.category))
.filter((card) => hasModelOutputEvidence(card.evidence));
return consultationEvidencePacketSchema.parse({
packet_version: "consultation-evidence-packet-v2",
question: context.question,
route: contract.route,
status: contract.core_status,
evidence_contract: projectEvidenceContract(contract),
claim_cards: cards,
presentation: {
template: "skill_level_2",
required_blocks: [
"raw_structure",
"raman_six_step",
"yoga_table",
"timing",
"synthesis",
"technique_audit_table",
"modern_wrap",
],
},
rectification: { boundary: context.rectification.boundary },
});
}
export function toAgentConsultationContext(data: JsonRecord) {
const chart = record(data.chart);
const modules = record(chart.modules);
const routing = record(data.routing);
const thematicReport = record(data.thematic_report);
const themes = record(thematicReport.themes);
const primaryTheme = String(routing.primary_theme || routing.question_type || "general");
const selectedTheme = record(themes[primaryTheme]);
const rectification = record(data.rectification);
const consumerContext = record(data.consumer_context);
return {
success: data.success === true,
question: data.question,
routing,
consumer_context: consumerContext,
evidence_contract: {
route: consumerContext.route,
core_status: consumerContext.core_status,
available_layers: consumerContext.available_layers,
missing_route_layers: consumerContext.missing_route_layers,
hard_blockers: consumerContext.hard_blockers,
technique_truth: consumerContext.technique_truth,
commercial_evidence_status: consumerContext.commercial_evidence_status,
answer_policy: consumerContext.answer_policy,
user_facing_limitation: consumerContext.user_facing_limitation,
technique_audit_table: consumerContext.technique_audit_table,
varga_spectrum: consumerContext.varga_spectrum,
western_spectrum: consumerContext.western_spectrum,
},
chart: {
birth: chart.birth, ascendant: chart.ascendant, planets: chart.planets, houses: chart.houses,
dasha: chart.dasha, shadbala: chart.shadbala, ashtakavarga: chart.ashtakavarga, yogas: chart.yogas,
},
local_layers: {
shadbala_boundary: "Shadbala is a locally consistent relative-strength layer; external component-level absolute parity remains partial and must not be stated as closed.",
varga_full: modules.varga_full, varga_spectrum: modules.varga_spectrum, arudha_padas: modules.arudha_padas, ashtakavarga: modules.ashtakavarga,
// `modules.dasha_boundaries` was read here for months and never written by the engine, so
// every answer was composed without sub-period boundaries while the receipt still reported
// precise timing as allowed. The field now names the layer the engine actually attaches.
dasha_sub_periods: modules.dasha_sub_periods, narayana_dasha: modules.narayana_dasha, gulika: modules.gulika,
kakshya: modules.kakshya,
yogas: modules.yogas || chart.yogas,
kp_cusps: modules.kp_cusps,
chara_dasha: modules.chara_dasha,
transits: modules.transits,
functional_benefic_malefic: modules.functional_benefic_malefic
|| record(data.machine_evidence_packet).functional_benefic_malefic,
},
rectification: {
boundary: "not_auto_rectified", summary: rectification.summary,
enabled_vargas: rectification.enabled_vargas, lagna_boundary: rectification.lagna_boundary,
},
candidate_range: record(data.candidate_range),
range_boundary_contexts: record(data.range_boundary_contexts),
thematic_evidence: selectedTheme,
vedastro_gateway: record(data.vedastro_gateway),
vedastro_cross_check: record(consumerContext.vedastro_cross_check),
external_engine_evidence: {
runtime_truth: record(data.runtime_truth), numerical_parity: record(data.external_parity_gate),
real_case_calibration: record(data.real_case_calibration),
},
reference_transparency: record(data.reference_transparency),
};
}