fix(fase3): stamp real proposer model into provenance, kill fake-model leak (F1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019any9zfGNNwWJPX5Zq2QRz
This commit is contained in:
parent
4253dd6388
commit
e0f93dfa7b
2 changed files with 28 additions and 2 deletions
|
|
@ -190,11 +190,19 @@ async def run_project(
|
||||||
gen_context = debate_output or context
|
gen_context = debate_output or context
|
||||||
|
|
||||||
# 5. Structured candidate -> blocking validation; token bound = the meter in this loop.
|
# 5. Structured candidate -> blocking validation; token bound = the meter in this loop.
|
||||||
outcome = await generate_via_llm(factory("proposer"), project, gen_context, meter)
|
proposer_client = factory("proposer")
|
||||||
|
outcome = await generate_via_llm(proposer_client, project, gen_context, meter)
|
||||||
proposal = outcome.proposal
|
proposal = outcome.proposal
|
||||||
|
|
||||||
# 6. First-class provenance stamp (authoritative; independent of MAF Annotation).
|
# 6. First-class provenance stamp (authoritative; independent of MAF Annotation).
|
||||||
model = "fake-model" if client_factory is not None else resolve_model(profile, "proposer")
|
# F1: an injected client_factory stamps the injected client's REAL model ("unknown" is the
|
||||||
|
# neutral fallback for a client that doesn't surface one -- never a fabricated name); the
|
||||||
|
# default path keeps the deterministic resolve_model.
|
||||||
|
model = (
|
||||||
|
(getattr(proposer_client, "model", None) or "unknown")
|
||||||
|
if client_factory is not None
|
||||||
|
else resolve_model(profile, "proposer")
|
||||||
|
)
|
||||||
stamp = ProvenanceStamp(
|
stamp = ProvenanceStamp(
|
||||||
citations=citations,
|
citations=citations,
|
||||||
model=model,
|
model=model,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,24 @@ async def test_a_valid_proposal_end_to_end(docs_dir, make_client_factory, fresh_
|
||||||
assert stamp.token_usage > 0 # sourced from the synthetic UsageDetails
|
assert stamp.token_usage > 0 # sourced from the synthetic UsageDetails
|
||||||
|
|
||||||
|
|
||||||
|
async def test_a1_provenance_stamps_injected_client_model_not_sentinel(
|
||||||
|
docs_dir, make_client_factory, fresh_store
|
||||||
|
) -> None:
|
||||||
|
"""F1 regression: an injected client_factory must stamp the injected client's REAL model
|
||||||
|
(SyntheticUsageChatClient.model == 'synthetic'), never the 'fake-model' literal — the leak
|
||||||
|
falsified provenance on the public deployer seam (run.py:197)."""
|
||||||
|
result = await run_project(
|
||||||
|
"FV42-GSV-E1",
|
||||||
|
"local",
|
||||||
|
docs_dir=docs_dir,
|
||||||
|
verdict_input=_VI,
|
||||||
|
client_factory=make_client_factory(_VALID),
|
||||||
|
store=fresh_store,
|
||||||
|
)
|
||||||
|
assert result.provenance.model == "synthetic"
|
||||||
|
assert result.provenance.model != "fake-model"
|
||||||
|
|
||||||
|
|
||||||
async def test_b_out_of_range_is_rejected(docs_dir, make_client_factory, fresh_store) -> None:
|
async def test_b_out_of_range_is_rejected(docs_dir, make_client_factory, fresh_store) -> None:
|
||||||
result = await run_project(
|
result = await run_project(
|
||||||
"FV42-GSV-E1",
|
"FV42-GSV-E1",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue