/** * Section 3, the user-message labels: the model CLASSIFIES every user message of the span as a * standing rule, a lift of one, or not a rule, and code copies the words into the correction or * lift. A misquote is impossible for the default case; only a long message labelled a rule needs * the model's exact words, and those still pass the store's verify gate. On a switchover the * offered earlier messages are kept unless the answer lists them in `oneOffs`, the single call's * contract (prompt.ts SEED_RULES_GUIDANCE): the pass records the rest (pass.ts seedRuleEvents). A * forced label per earlier message made "not a rule" the cheap answer and dropped half the earlier * rules in a replay; opt-out keeps the default safe. */ import { listedExcerpt, SPAN_EXCERPT_CHARS, spanExcerpt as excerpt } from "../prepare.ts"; import { EARLIER_REQUEST, LABEL_FORMS, LIFT_GUIDANCE, labelProposals, NOT_A_RULE, RULE_DEFINITION, ruleLabelProblem, readLabels, RULE_WHOLE_CHARS, type Label } from "../message-labels.ts"; import type { Proposal } from "../../store/verify.ts"; import type { Complaint, Facts, Parsed, Section } from "./section.ts"; export type Labels = Map; interface LabelsAnswer { labels: Labels; invalid: Map; oneOffs: string[] } export { RULE_WHOLE_CHARS }; /** The earlier messages are listed cut to their start (excerpt above); a span message is in the Digest whole. */ const shownOf = (facts: Facts) => (ordinal: string): number | undefined => (facts.contextOrdinals?.includes(ordinal) ? SPAN_EXCERPT_CHARS : undefined); function instruction(facts: Facts): string { const span = facts.spanOrdinals.map((ordinal) => `${ordinal}: ${listedExcerpt(facts.textOf(ordinal) ?? "")}`); const seed = facts.seedOrdinals.map((ordinal) => `${ordinal}: ${listedExcerpt(facts.textOf(ordinal) ?? "")}`); const context = (facts.contextOrdinals ?? []).map((ordinal) => `${ordinal}: ${listedExcerpt(facts.textOf(ordinal) ?? "")}`); const rules = facts.activeCorrections.map((rule) => `${rule.id} [${rule.ordinal}] ${excerpt(rule.text)}`); return [ "Section 3 of 5: the user's messages. Label EVERY message listed under Messages in this span with one of:", `${LABEL_FORMS}. ${RULE_DEFINITION} ${LIFT_GUIDANCE} ${NOT_A_RULE}`, `Code copies the user's words. A message longer than ${RULE_WHOLE_CHARS} characters that you label a rule must come with the rule's exact words instead of "rule": {"u7": {"rule": ""}}.`, 'Answer: {"labels": {"u3": "not a rule", "u4": "rule", "u5": "lifts R1"}}', `Standing rules already recorded:\n${rules.length ? rules.join("\n") : "(none)"}`, `Messages in this span:\n${span.length ? span.join("\n") : "(none)"}`, ...(seed.length ? [`Earlier messages that read like standing rules. Each one is kept as a rule automatically; do not label them. Only if one was a one-off request, an answer or a question rather than a rule still in force, list its id in a top-level "oneOffs" array, e.g. "oneOffs": ["u3"]; list none when all of them still hold:\n${seed.join("\n")}`] : []), ...(context.length ? [`The user's other earlier messages, each cut to its start. Label every one of these too: "rule" when it is a standing rule still in force, however it is worded ("tests go under tests/", "our servers run Node 22"), "not a rule" otherwise. ${EARLIER_REQUEST}\n${context.join("\n")}`] : []), ].join("\n\n"); } function oneOffsOf(value: unknown): string[] { return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string").map((item) => item.trim()) : []; } function parse(value: Record, facts: Facts): Parsed { const raw = value.labels; if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { ok: false, error: 'expected {"labels": {"u": "