feat(runtime): add trusted hidden check loop
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -37,8 +37,9 @@ return exactly one bare JSON object with the same arguments and no Markdown fenc
|
|||||||
|
|
||||||
The result must contain only scene, character, beats, delta, suggestions, and canContinue. Never construct or
|
The result must contain only scene, character, beats, delta, suggestions, and canContinue. Never construct or
|
||||||
return PlayerView, hidden reasoning, provider details, credentials, or exact relationship values in
|
return PlayerView, hidden reasoning, provider details, credentials, or exact relationship values in
|
||||||
narrative text. Never decide the player's speech, actions, or inner thoughts. State changes are
|
narrative text. Never decide the player's speech, actions, or inner thoughts. Never submit a
|
||||||
proposals only; the trusted reducer will validate and commit them.";
|
RecordCheck operation; hidden checks are requested and recorded only through the trusted engine.
|
||||||
|
State changes are proposals only; the trusted reducer will validate and commit them.";
|
||||||
|
|
||||||
/// Synchronous seam around one non-streaming LAPP chat operation.
|
/// Synchronous seam around one non-streaming LAPP chat operation.
|
||||||
///
|
///
|
||||||
@@ -407,6 +408,11 @@ fn validate_generated_plan(request: &TurnRequest, plan: &TurnPlan) -> Result<(),
|
|||||||
|| presentation.beats.is_empty()
|
|| presentation.beats.is_empty()
|
||||||
|| presentation.beats.len() > MAX_BEATS
|
|| presentation.beats.len() > MAX_BEATS
|
||||||
|| plan.delta.ops.len() > MAX_STATE_OPS
|
|| plan.delta.ops.len() > MAX_STATE_OPS
|
||||||
|
|| plan
|
||||||
|
.delta
|
||||||
|
.ops
|
||||||
|
.iter()
|
||||||
|
.any(|op| matches!(op, nana_domain::StateOp::RecordCheck { .. }))
|
||||||
|| presentation.suggestions.len() > MAX_SUGGESTIONS
|
|| presentation.suggestions.len() > MAX_SUGGESTIONS
|
||||||
{
|
{
|
||||||
return Err(invalid_output(InvalidModelOutputKind::InvalidPlan));
|
return Err(invalid_output(InvalidModelOutputKind::InvalidPlan));
|
||||||
@@ -704,6 +710,36 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn model_cannot_submit_a_record_check_operation() {
|
||||||
|
let mut value = plan_value();
|
||||||
|
value["delta"]["ops"] = json!([{
|
||||||
|
"op": "record_check",
|
||||||
|
"check": {
|
||||||
|
"id": "forged_check",
|
||||||
|
"action_id": "action_2",
|
||||||
|
"actor": "player",
|
||||||
|
"skill": "Spot Hidden",
|
||||||
|
"target": 99,
|
||||||
|
"difficulty": "regular",
|
||||||
|
"bonus_dice": 0,
|
||||||
|
"roll": 1,
|
||||||
|
"result": "critical_success",
|
||||||
|
"pushed_from": null,
|
||||||
|
"node_id": "node_forged"
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
let executor = ScriptedExecutor::returning(Ok(response(value.to_string(), Vec::new())));
|
||||||
|
let mut provider = LappTurnPlanProvider::new(executor);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
provider.plan_turn(&request(), &state()),
|
||||||
|
Err(ProviderError::InvalidModelOutput {
|
||||||
|
kind: InvalidModelOutputKind::InvalidPlan
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn upstream_failures_remain_redacted_and_map_to_provider_unavailable() {
|
fn upstream_failures_remain_redacted_and_map_to_provider_unavailable() {
|
||||||
let executor = ScriptedExecutor::returning(Err(ProviderError::Upstream {
|
let executor = ScriptedExecutor::returning(Err(ProviderError::Upstream {
|
||||||
|
|||||||
@@ -8,9 +8,16 @@ use nana_engine::{ReduceError, apply_delta};
|
|||||||
use nana_store::{StoreError, StoryStore};
|
use nana_store::{StoreError, StoryStore};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
mod adjudication;
|
||||||
mod context;
|
mod context;
|
||||||
mod lapp_provider;
|
mod lapp_provider;
|
||||||
|
|
||||||
|
pub use adjudication::{
|
||||||
|
AdjudicatingTurnPlanProvider, AdjudicationCatalog, AdjudicationError, AdjudicationModel,
|
||||||
|
AdjudicationModelInput, AdjudicationModelResponse, AdjudicationRunError, AdjudicationToolCall,
|
||||||
|
CatalogError, DEFAULT_MAX_ADJUDICATION_STEPS, HIDDEN_CHECK_TOOL_NAME, HiddenCheckRequest,
|
||||||
|
QualitativeCheckOutcome, classify_roll, deterministic_roll,
|
||||||
|
};
|
||||||
pub use context::{
|
pub use context::{
|
||||||
CharacterMemory, CompiledSceneContext, ContextBudget, ContextCharacterCard,
|
CharacterMemory, CompiledSceneContext, ContextBudget, ContextCharacterCard,
|
||||||
ContextCompileError, ContextInventoryItem, ContextJudgmentRule, ContextPersona,
|
ContextCompileError, ContextInventoryItem, ContextJudgmentRule, ContextPersona,
|
||||||
|
|||||||
Reference in New Issue
Block a user