Skip to content

Commit 25ae77d

Browse files
committed
Harden semantic verification actions
1 parent 7f78ab5 commit 25ae77d

13 files changed

Lines changed: 411 additions & 57 deletions

docs/plans/claim-investigation-research.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,20 +1068,32 @@ verbatim ordered atom parts, selects only one consequential proposition, and
10681068
adds explicit low-risk abstention guidance. The local guard remains strict and
10691069
now exposes precise structure reason codes for private development audits.
10701070

1071-
The Adapter may now make one reason-specific semantic repair inside the same
1072-
low-priority derived queue job, but only after a prepared output fails one of a
1073-
small set of repairable local-guard reasons. The retry receives the rejected
1074-
claim and reason, must rebuild from exact grounding text or abstain, and cannot
1075-
bypass the unchanged guard. Non-consequential and unsupported outputs are not
1076-
retried. The UI receives `prepared` only after the final claim forms the same
1077-
validated action task; there is no extra foreground job or persisted content.
1071+
An evaluation-only bounded semantic repair was then tested against the same old
1072+
30-row development slice. Eight repair requests produced zero accepted actions:
1073+
seven remained rejected by the unchanged guard and one ended in a format
1074+
failure. The apparent four-to-seven increase between separate runs came from
1075+
first-pass model variance, not from repair recovery. Product runtime therefore
1076+
returns to one Adapter attempt. `semantic_once` remains available only as an
1077+
explicit private diagnostic mode; the preregistered fresh audit requires
1078+
`repairMode=none` so its execution path matches runtime.
1079+
1080+
Independent review of the seven first-pass actions found five usable or
1081+
usable-with-tightening results, one underspecified comparison, and one false
1082+
action derived from a truncated related-link headline at the extraction tail.
1083+
The candidate now rejects labeled navigation sections and incomplete
1084+
tail-boundary fragments, rejects comparisons without a time, market or region,
1085+
and metric, and requires a named evidence family rather than generic
1086+
`evidence`. Regular Google search uses bounded claim/evidence anchors instead
1087+
of copying the whole claim or publisher name. Self-contained follow-up
1088+
questions no longer inherit an unrelated model summary; AI Mode alone may
1089+
receive sanitized page metadata and URL.
10781090

10791091
The private runner also verifies that each source hash is derived from the
10801092
actual input text, refuses to overwrite an evaluation path, and records exact
10811093
input and result digests. These changes improve evaluation integrity; they do
1082-
not authorize the Agent action or establish release-level coverage. The same
1083-
old development replay must improve before a preregistered fresh cohort is
1084-
opened, and fresh rows cannot be used for further tuning.
1094+
not authorize the Agent action or establish release-level coverage. The
1095+
updated old-development replay is the final tuning check before opening a
1096+
preregistered fresh cohort; fresh rows cannot be used for further tuning.
10851097

10861098
### C. Sufficiency and UX audit
10871099

docs/plans/general-page-reader.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,14 @@ next candidate gate closed; no fresh holdout should be created yet.
818818
interactive skeleton elements, no scroll movement, and a complete ready
819819
first frame. Generated screenshots and measurements remain gitignored under
820820
`tmp/`.
821+
- An old-30 product-semantic replay showed that eight reason-specific Adapter
822+
retries yielded no accepted actions. Runtime now performs one low-priority
823+
Adapter attempt only; the private runner exposes repair solely through an
824+
explicit diagnostic flag and records `repairMode` in run metadata. Local
825+
action guards also reject incomplete navigation-tail quotes,
826+
under-specified comparisons, and generic evidence requirements. Regular
827+
Google search receives bounded claim/evidence anchors, while page metadata
828+
and URL remain AI-Mode-only context.
821829

822830
## Verification Gates
823831

scripts/lib/private-general-page-semantic-audit.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from "node:fs";
33
import path from "node:path";
44

55
export const PRIVATE_SEMANTIC_AUDIT_CORE_FILES = [
6+
"src/background/general-page-investigation-background.ts",
67
"src/lib/general-page-analysis.ts",
78
"src/lib/general-page-investigation-adapter.ts",
89
"src/lib/reading-question-policy.ts",
@@ -15,6 +16,15 @@ export function sha256Text(value) {
1516
return crypto.createHash("sha256").update(String(value)).digest("hex");
1617
}
1718

19+
export function privateSemanticAuditRepairMode(argv) {
20+
const index = argv.indexOf("--repair-mode");
21+
const value = index >= 0 ? argv[index + 1] : "none";
22+
if (value !== "none" && value !== "semantic_once") {
23+
throw new Error("--repair-mode must be none or semantic_once");
24+
}
25+
return value;
26+
}
27+
1828
export function semanticAuditCompletionsUrl(endpoint) {
1929
const url = new URL(endpoint);
2030
if (!/^https?:$/.test(url.protocol) || url.username || url.password || url.search || url.hash) {

scripts/private-general-page-semantic-audit-entry.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
assertPrivateSemanticAuditFetchTarget,
2626
hashPrivateSemanticAuditCoreFiles,
2727
installPrivateSemanticAuditNetworkGuard,
28+
privateSemanticAuditRepairMode,
2829
semanticAuditCompletionsUrl,
2930
sha256Text,
3031
} from "./lib/private-general-page-semantic-audit.mjs";
@@ -131,6 +132,7 @@ const declaredCategories = required("--data-categories");
131132
const expectedCount = Number(required("--sample-count"));
132133
const concurrency = Math.max(1, Math.min(4, Number(option("--concurrency", "2")) || 2));
133134
const timeoutMs = Math.max(1_000, Math.min(120_000, Number(option("--timeout-ms", "45_000")) || 45_000));
135+
const repairMode = privateSemanticAuditRepairMode(process.argv);
134136
if (split !== "dev" && split !== "holdout") throw new Error("--split must be dev or holdout");
135137

136138
const allowedCompletionsUrl = semanticAuditCompletionsUrl(endpoint);
@@ -297,7 +299,7 @@ async function evaluateRow(row: InputRow): Promise<Record<string, unknown>> {
297299
source: row.sourceContext,
298300
}) : undefined;
299301
const firstReason = firstEligibility.ok ? (firstTask ? undefined : "invalid_question") : firstEligibility.reason;
300-
if (firstReason && isRepairablePageClaimIneligibilityReason(firstReason)) {
302+
if (repairMode === "semantic_once" && firstReason && isRepairablePageClaimIneligibilityReason(firstReason)) {
301303
repairReason = firstReason;
302304
const repairRequest: TierBGeneralPageInvestigationAdapterRequest = {
303305
...adapterRequest,
@@ -445,6 +447,10 @@ const manifest = {
445447
timeoutMs,
446448
concurrency,
447449
},
450+
adapter: {
451+
repairMode,
452+
runtimeParity: repairMode === "none",
453+
},
448454
data: {
449455
sampleCount: rows.length,
450456
declaredCategories: declaredCategories.split(",").map((value) => value.trim()).filter(Boolean).sort(),
@@ -486,6 +492,7 @@ console.log(JSON.stringify({
486492
result: results.every((result) => result.ok) ? "pass" : "partial",
487493
runId,
488494
split,
495+
repairMode,
489496
samples: rows.length,
490497
...manifest.counts,
491498
publicSearchRequests: 0,

src/background/general-page-investigation-background.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import {
1515
} from "./model-work-scheduler";
1616
import {
1717
buildPageClaimInvestigationTask,
18-
isRepairablePageClaimIneligibilityReason,
19-
pageClaimInvestigationEligibility,
2018
} from "../sidepanel/page-claim-investigation";
2119

2220
export interface ScheduleGeneralPageInvestigationPreparationOptions {
@@ -86,27 +84,11 @@ export function scheduleGeneralPageInvestigationPreparation(
8684
priority: "derived",
8785
dedupeKey: id,
8886
supersedeKey: `general-page-investigation:${request.tabId}:${request.scope}`,
89-
run: async () => {
90-
const first = await callAdapter(adapterRequest);
91-
const firstClaim = first.ok && first.value?.decision === "prepared" ? first.value.claim : undefined;
92-
if (!firstClaim) return first;
93-
const eligibility = pageClaimInvestigationEligibility(firstClaim, request.context.mainText);
94-
const task = eligibility.ok ? buildPageClaimInvestigationTask({
95-
analysisKey: request.analysisKey,
96-
scope: request.scope,
97-
claimIndex: 0,
98-
claim: firstClaim,
99-
groundingText: request.context.mainText,
100-
source,
101-
}) : undefined;
102-
const repairReason = eligibility.ok ? (task ? undefined : "invalid_question") : eligibility.reason;
103-
if (!repairReason || !isRepairablePageClaimIneligibilityReason(repairReason)) return first;
104-
return callAdapter({
105-
...adapterRequest,
106-
candidateClaim: firstClaim,
107-
repairReason: repairReason as TierBGeneralPageInvestigationAdapterRequest["repairReason"],
108-
});
109-
},
87+
// Semantic repair remains available to the private evaluation harness, but
88+
// runtime intentionally performs one adapter attempt only. The old-30
89+
// review found no accepted repair, so retrying here added latency without
90+
// producing a trustworthy user action.
91+
run: () => callAdapter(adapterRequest),
11092
});
11193

11294
void work.then((result) => {

src/lib/general-page-investigation-adapter.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface GeneralPageInvestigationAdapterInput {
1515
groundingText: string;
1616
source?: GeneralPageInvestigationSourceMetadata;
1717
outputLang?: Lang;
18-
/** One bounded retry after the unchanged local guard rejects a prepared claim. */
18+
/** Evaluation-only bounded retry. Product runtime never issues this request. */
1919
repairReason?: "atom_span_mismatch" | "compound_claim" | "vague_atom" | "generic_subject" |
2020
"ungrounded_atom" | "missing_attribution" | "invalid_attribution" | "invalid_question";
2121
}
@@ -187,12 +187,15 @@ export function buildGeneralPageInvestigationAdapterSystemPrompt(outputLang?: La
187187
"Use decision=prepared and reason=actionable only when the supplied page text supports one consequential, externally checkable atomic assertion.",
188188
"A prepared claim must contain c, why, need, q, atom:{s,p,o}, policy:{claimKind,consequence}, and sourceQuote; attribution:{source,relation,modality} is allowed only for a real outer source frame.",
189189
"sourceQuote must be one concise verbatim span copied from Exact grounding text that directly supports c. Preserve its source language and do not translate it.",
190+
"Never prepare an action from a related or recommended link, navigation-tail headline, or incomplete fragment touching the Exact grounding text boundary; abstain instead.",
190191
"atom.s, atom.p, and atom.o must each be exact substrings of c, appearing once in that order. Never paraphrase, shorten, translate, or recombine an atom part.",
191192
"c must end with sentence punctuation and contain exactly one proposition. If the candidate is compound, select only one consequential proposition that the sourceQuote supports; otherwise abstain.",
192193
"If attribution is present, modality must be statement|report|estimate|allegation|forecast|analysis. Omit attribution when uncertain; never invent another modality.",
193194
"Source metadata alone is never claim attribution. Add attribution only when claim c itself contains a verbatim source and reporting relation outside atom s, p, and o; attribution source and relation must both be exact substrings of c.",
194195
"Do not use generic atom subjects such as death toll, number, report, officials, government, company, or agency. Include the event, place, organization, or other identifier already present in Exact grounding text, or abstain.",
195196
"Keep one proposition and preserve legal stage and attribution exactly. q must be one natural question containing the exact source-language s, p, and o.",
197+
"For a comparative claim, require the grounding text to name the comparison scope (time plus region or market) and measurement metric; otherwise abstain.",
198+
"need must name a named evidence family that could answer q, such as an official notice, registry record, court ruling, dataset, benchmark report, or result table. Never write only evidence, sources, data, or proof.",
196199
"policy.claimKind is fact|report|estimate|forecast|allegation|expert_analysis. policy.consequence is health|safety|money|rights|law|public_interest.",
197200
"Abstain for low-risk product availability or promotion, celebrity purchases or anecdotes, vague AI or marketing claims, pure opinion, generic controversy, or any assertion without a consequential externally checkable proposition.",
198201
"Otherwise output decision=abstain with reason=insufficient_context|unsafe_structure|non_consequential|unsupported_claim and omit claim.",
@@ -210,7 +213,7 @@ export function buildGeneralPageInvestigationAdapterPrompt(input: GeneralPageInv
210213
};
211214
return [
212215
...(input.repairReason ? [
213-
"This is the single allowed semantic repair attempt. The previous prepared claim failed the unchanged local guard.",
216+
"This is the single allowed semantic repair attempt in an evaluation-only audit. Product runtime does not issue repair requests. The previous prepared claim failed the unchanged local guard.",
214217
`Local guard reason: ${input.repairReason}. Rebuild from Exact grounding text or abstain; never work around the guard.`,
215218
] : []),
216219
"Prepare or abstain. URL is metadata only; it is not evidence.",

0 commit comments

Comments
 (0)