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:
732642856
2026-07-19 22:23:29 +08:00
committed by GitHub
parent d9b07802a5
commit 4ceb3a5157
42 changed files with 995 additions and 85 deletions
+11
View File
@@ -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) {
+2 -1
View File
@@ -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,
+31 -11
View File
@@ -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;
+26
View File
@@ -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;
}