feat: enforce commercial rectification evidence contracts
* feat: enforce precise timing output contract * fix: recognize package imports in fragment audit * test: make workflow stream contract formatting-independent * fix: preserve VedAstro evidence across async workflows * feat: enforce commercial technique truth contract * feat: add rectification technique receipt * feat: extend rectification event evidence * feat: score rectification arudha evidence * feat: gate high rigor rectification confirmation * feat: add controlled transit to rectification * feat: include d11 in rectification finance scoring * feat: add ashtakavarga rectification auxiliary * feat: show rectification technique receipt * feat: use verified shadbala components in rectification * fix: trace transitive script references in fragment audit * feat: run request-level rectification parity packet
This commit is contained in:
@@ -83,6 +83,16 @@ const candidateEvidenceSchema = z.object({
|
||||
points: z.number(),
|
||||
}).strict().readonly();
|
||||
|
||||
const rectificationTechniqueReceiptSchema = z.object({
|
||||
calculationStatus: z.enum(["not_started", "evaluated"]),
|
||||
usedDivisionalCharts: z.array(z.string()),
|
||||
usedArudha: z.array(z.string()),
|
||||
dashaTracks: z.array(z.string()),
|
||||
missingLayers: z.array(z.string()),
|
||||
auxiliaryLayers: z.array(z.string()).default([]),
|
||||
hardBlockers: z.array(z.string()),
|
||||
}).strict().readonly();
|
||||
|
||||
export const candidateResultSchema = z.object({
|
||||
resultId: z.string().uuid(),
|
||||
confidence: z.enum(["low", "medium", "high"]),
|
||||
@@ -101,6 +111,7 @@ export const candidateResultSchema = z.object({
|
||||
reasons: z.array(z.string().trim().min(1)),
|
||||
evidence: z.array(candidateEvidenceSchema),
|
||||
algorithmVersion: z.string().trim().min(1),
|
||||
techniqueReceipt: rectificationTechniqueReceiptSchema.optional(),
|
||||
}).strict().readonly().superRefine((value, context) => {
|
||||
const eligible = value.confidence === "high" && highCandidateMeetsSafetyGates(value);
|
||||
if (value.confidence === "high" && value.eventCount < 4) {
|
||||
|
||||
@@ -47,7 +47,7 @@ const guideQuestionOutputSchema = z.object({
|
||||
}).strict().readonly();
|
||||
|
||||
export const evidenceDraftModelOutputSchema = z.object({
|
||||
domain: z.enum(["education", "relocation", "relationship", "career", "health_pressure"]),
|
||||
domain: z.enum(["education", "relocation", "relationship", "career", "finance", "health_pressure"]),
|
||||
precision: z.enum(["year", "month", "day"]).nullable(),
|
||||
date: z.string().trim().min(1).max(10).nullable(),
|
||||
}).strict().readonly();
|
||||
@@ -98,6 +98,7 @@ const subjectByDomain = {
|
||||
relocation: "一次搬家、离乡或长期居住地变化",
|
||||
relationship: "一次关系进入、关系结束或关系明显转变",
|
||||
career: "一次明显的工作、职业方向或身份变化",
|
||||
finance: "一次收入、资产、负债或资源渠道的明显变化",
|
||||
health_pressure: "一次持续的健康压力或生活压力变化",
|
||||
} as const satisfies Readonly<Record<EvidenceDomain, string>>;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ type ProposeDraft = (
|
||||
caseId: string,
|
||||
actionId: string,
|
||||
expectedVersion: number,
|
||||
proposal: { readonly domain: "education" | "relocation" | "relationship" | "career" | "health_pressure"; readonly precision: EvidenceDatePrecision; readonly date: string },
|
||||
proposal: { readonly domain: "education" | "relocation" | "relationship" | "career" | "finance" | "health_pressure"; readonly precision: EvidenceDatePrecision; readonly date: string },
|
||||
) => Promise<VersionedJourneyResponse>;
|
||||
type DraftRevision = {
|
||||
readonly userId: string;
|
||||
|
||||
@@ -54,6 +54,7 @@ const sampleSchema = z.object({
|
||||
ascendant: signSchema,
|
||||
varga_lagna: z.object({
|
||||
D4: signSchema,
|
||||
D2: signSchema,
|
||||
D9: signSchema,
|
||||
D10: signSchema,
|
||||
D24: signSchema,
|
||||
@@ -81,6 +82,7 @@ const eventDomainSchema = z.enum([
|
||||
"relocation",
|
||||
"relationship",
|
||||
"career",
|
||||
"finance",
|
||||
"health_pressure",
|
||||
]);
|
||||
const candidateResultApiSchema = z.object({
|
||||
@@ -107,6 +109,15 @@ const candidateResultApiSchema = z.object({
|
||||
points: z.number(),
|
||||
})),
|
||||
algorithm_version: z.string(),
|
||||
technique_contract: z.object({
|
||||
calculation_status: z.enum(["not_started", "evaluated"]),
|
||||
used_divisional_charts: z.array(z.string()),
|
||||
used_arudha: z.array(z.string()),
|
||||
dasha_tracks: z.array(z.string()),
|
||||
missing_layers: z.array(z.string()),
|
||||
auxiliary_layers: z.array(z.string()).default([]),
|
||||
hard_blockers: z.array(z.string()),
|
||||
}).optional(),
|
||||
}).passthrough();
|
||||
|
||||
class UnexpectedProfileSourceError extends Error {
|
||||
@@ -161,6 +172,7 @@ export function parseRectificationQuestionnaire(value: unknown): RectificationQu
|
||||
questions: parsed.questions.map(normalizeQuestion),
|
||||
samples: parsed.candidate_scan.samples.map((sample) => ({
|
||||
ascendantSign: sample.ascendant?.sign ?? null,
|
||||
...(sample.varga_lagna?.D2?.sign ? { d2Sign: sample.varga_lagna.D2.sign } : {}),
|
||||
d4Sign: sample.varga_lagna?.D4?.sign ?? null,
|
||||
d9Sign: sample.varga_lagna?.D9?.sign ?? null,
|
||||
d10Sign: sample.varga_lagna?.D10?.sign ?? null,
|
||||
@@ -226,5 +238,14 @@ function adaptCandidateResult(parsed: z.infer<typeof candidateResultApiSchema>):
|
||||
points: item.points,
|
||||
})),
|
||||
algorithmVersion: parsed.algorithm_version,
|
||||
...(parsed.technique_contract ? { techniqueReceipt: {
|
||||
calculationStatus: parsed.technique_contract.calculation_status,
|
||||
usedDivisionalCharts: parsed.technique_contract.used_divisional_charts,
|
||||
usedArudha: parsed.technique_contract.used_arudha,
|
||||
dashaTracks: parsed.technique_contract.dasha_tracks,
|
||||
missingLayers: parsed.technique_contract.missing_layers,
|
||||
auxiliaryLayers: parsed.technique_contract.auxiliary_layers,
|
||||
hardBlockers: parsed.technique_contract.hard_blockers,
|
||||
} } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const rectificationQuestionSchema = z.object({
|
||||
}).strict().readonly();
|
||||
const candidateVargaSampleSchema = z.object({
|
||||
ascendantSign: z.string().nullable(), d4Sign: z.string().nullable(), d9Sign: z.string().nullable(),
|
||||
d10Sign: z.string().nullable(), d24Sign: z.string().nullable(), d30Sign: z.string().nullable(),
|
||||
d2Sign: z.string().nullable().optional(), d10Sign: z.string().nullable(), d24Sign: z.string().nullable(), d30Sign: z.string().nullable(),
|
||||
}).strict().readonly();
|
||||
export const questionnaireSchema = z.object({
|
||||
questions: z.array(rectificationQuestionSchema).readonly(), samples: z.array(candidateVargaSampleSchema).readonly(), raw: z.record(z.unknown()).readonly(),
|
||||
|
||||
@@ -10,7 +10,7 @@ const timeSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/);
|
||||
export const questionSpecSchema: z.ZodType<QuestionSpec> = z.object({
|
||||
questionId: z.string().trim().min(1),
|
||||
phase: z.enum(["baseline", "adaptive"]),
|
||||
domain: z.enum(["education", "relocation", "relationship", "career", "health_pressure"]),
|
||||
domain: z.enum(["education", "relocation", "relationship", "career", "finance", "health_pressure"]),
|
||||
requestedPrecision: z.array(z.enum(["day", "month", "year"])).min(1),
|
||||
allowUnknown: z.literal(true),
|
||||
purposeCode: z.string().trim().min(1),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const evidenceDomains = [
|
||||
"education", "relocation", "relationship", "career", "health_pressure",
|
||||
"education", "relocation", "relationship", "career", "finance", "health_pressure",
|
||||
] as const;
|
||||
|
||||
export type EvidenceDomain = (typeof evidenceDomains)[number];
|
||||
@@ -7,6 +7,7 @@ export type EvidenceQuestionPhase = "baseline" | "adaptive";
|
||||
export type EvidenceDatePrecision = "day" | "month" | "year";
|
||||
|
||||
export type CandidateVargaSample = {
|
||||
readonly d2Sign?: string | null;
|
||||
readonly d4Sign: string | null;
|
||||
readonly d9Sign: string | null;
|
||||
readonly d10Sign: string | null;
|
||||
@@ -37,6 +38,7 @@ const layerByDomain = {
|
||||
relocation: "d4Sign",
|
||||
relationship: "d9Sign",
|
||||
career: "d10Sign",
|
||||
finance: "d2Sign",
|
||||
health_pressure: "d30Sign",
|
||||
} as const;
|
||||
|
||||
@@ -57,7 +59,8 @@ function questionSpecFor(
|
||||
}
|
||||
|
||||
export function planEvidenceQuestion(input: QuestionPlannerInput): QuestionSpec | null {
|
||||
const available = evidenceDomains.filter((domain) => !input.askedDomains.includes(domain));
|
||||
const available = evidenceDomains.filter((domain) => !input.askedDomains.includes(domain)
|
||||
&& (domain !== "finance" || (input.phase === "adaptive" && input.coveredDomains.length >= 2)));
|
||||
const ranked = available.map((domain) => ({
|
||||
domain,
|
||||
split: new Set(input.samples.map((sample) => sample[layerByDomain[domain]]).filter(Boolean)).size,
|
||||
|
||||
@@ -8,6 +8,7 @@ type StreamTextResponseOptions = StreamHooks & {
|
||||
readonly mode: "engine" | "mastra";
|
||||
readonly requestId: string;
|
||||
readonly headers?: Record<string, string>;
|
||||
readonly transformText?: (text: string) => string;
|
||||
};
|
||||
|
||||
export function streamTextResponse(
|
||||
@@ -16,27 +17,46 @@ export function streamTextResponse(
|
||||
) {
|
||||
const iterator = stream[Symbol.asyncIterator]();
|
||||
const encoder = new TextEncoder();
|
||||
// Keep a full natural-language clause unflushed so a later stream chunk cannot
|
||||
// turn an allowed prefix into a disallowed timing or guaranteed conclusion.
|
||||
const guardTailLength = options.transformText ? 1024 : 0;
|
||||
let pending = "";
|
||||
let settled = false;
|
||||
let emitted = false;
|
||||
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
async pull(controller) {
|
||||
try {
|
||||
const { done, value } = await iterator.next();
|
||||
if (done) {
|
||||
settled = true;
|
||||
if (!emitted) {
|
||||
const error = new Error("empty_stream");
|
||||
await options.onError?.(error, false);
|
||||
controller.error(error);
|
||||
while (true) {
|
||||
const { done, value } = await iterator.next();
|
||||
if (done) {
|
||||
if (pending)
|
||||
controller.enqueue(
|
||||
encoder.encode(options.transformText?.(pending) ?? pending),
|
||||
);
|
||||
settled = true;
|
||||
if (!emitted) {
|
||||
const error = new Error("empty_stream");
|
||||
await options.onError?.(error, false);
|
||||
controller.error(error);
|
||||
return;
|
||||
}
|
||||
await options.onComplete?.();
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
await options.onComplete?.();
|
||||
controller.close();
|
||||
if (/\S/.test(value)) emitted = true;
|
||||
pending += value;
|
||||
if (pending.length <= guardTailLength) continue;
|
||||
|
||||
const stableLength = pending.length - guardTailLength;
|
||||
const stable = pending.slice(0, stableLength);
|
||||
pending = pending.slice(stableLength);
|
||||
controller.enqueue(
|
||||
encoder.encode(options.transformText?.(stable) ?? stable),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (/\S/.test(value)) emitted = true;
|
||||
controller.enqueue(encoder.encode(value));
|
||||
} catch (error) {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const exactTimingPatterns = [
|
||||
/\b(?:19|20)\d{2}[-/.年]\s?\d{1,2}(?:[-/.月]\s?\d{1,2}(?:日|号)?)?\b/g,
|
||||
/\b(?:19|20)\d{2}\s+(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)(?:\s+\d{1,2})?\b/gi,
|
||||
/\b\d{1,2}\/\d{1,2}\/(?:\d{2}|\d{4})\b/g,
|
||||
/(?:今年|明年|后年|(?:19|20)\d{2}年)?\s*\d{1,2}月(?:\s*\d{1,2}[日号])?/g,
|
||||
/\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\s+\d{1,2}(?:,\s*(?:19|20)\d{2})?\b/gi,
|
||||
];
|
||||
|
||||
const guaranteeConclusionPatterns = [
|
||||
/(?:^|[。!?.!?\n])[^。!?.!?\n]*(?:一定|必然|保证|肯定|必定|注定|绝对)(?:会|能|将|发生|成功|结婚|复合|怀孕|发财|升职|得到|实现|出现)[^。!?.!?\n]*/g,
|
||||
/(?:^|[.?!\n])[^.?!\n]*\b(?:will definitely|guaranteed? to|certain to|without doubt)\b[^.?!\n]*/gi,
|
||||
];
|
||||
|
||||
/** Removes claims the evidence contract does not permit the model to make. */
|
||||
export function guardPreciseTimingOutput(text: string) {
|
||||
let guarded = text;
|
||||
for (const pattern of exactTimingPatterns)
|
||||
guarded = guarded.replace(pattern, "[具体时间已省略]");
|
||||
for (const pattern of guaranteeConclusionPatterns) {
|
||||
guarded = guarded.replace(pattern, (sentence) => {
|
||||
const prefix = /^[。!?.!?\n]/.exec(sentence)?.[0] ?? "";
|
||||
return prefix + "[保证性结论已省略]";
|
||||
});
|
||||
}
|
||||
return guarded;
|
||||
}
|
||||
Reference in New Issue
Block a user