Summary
ReferenceTurnRunner.resolveAndExecuteTool's permission-denied branch returns a HostToolResult without first recording a tool_result journal event, so the emitted journal diverges from spec/vectors/harness/replay_vectors.json.
This is the single remaining Go failure and one of the remaining TypeScript failures.
The vector requires the event
spec/vectors/harness/replay_vectors.json, scenario permission_denied:
json "expected": [ "session:session_start:session-1:turn-1", "turn:turn_start:0", "turn:llm_start:0", "turn:llm_complete:0", "session:checkpoint_created:session-1:turn-1", "turn:permission_requested:0:exec-1-permission", "turn:permission_completed:0:false", "turn:tool_result:0:add:false:permission_denied", "turn:messages_updated:0", ... ]
turn:tool_result:0:add:false:permission_denied must follow turn:permission_completed:0:false.
Compare the tool_success scenario in the same file, which records turn:tool_execution_start → turn:tool_execution_complete → turn:tool_result. The denied path is expected to emit tool_result without the two execution events, since nothing executed.
The code
runtime/go/prompty/model/turn_runner.go, in resolveAndExecuteTool:
go if err := r.recordTurn(TurnEventTypePermissionCompleted, turnId, iteration, decision.Save(NewSaveContext())); err != nil { return HostToolResult{}, err } if !decision.Approved { errorKind := "permission_denied" message := "Permission denied" if decision.Reason != nil { message = *decision.Reason } result := interface{}(map[string]interface{}{"message": message}) return HostToolResult{ // <-- returns without recording TurnEventTypeToolResult RequestId: toolRequest.RequestId, ToolCallId: toolRequest.ToolCallId, ToolName: toolRequest.ToolName, Success: false, ErrorKind: &errorKind, Result: &result, }, nil } if err := r.recordTurn(TurnEventTypeToolExecutionStart, turnId, iteration, toolRequest.Save(NewSaveContext())); err != nil {
The approved path continues on and records TurnEventTypeToolResult; the denied path returns early and skips it.
Observed
--- FAIL: TestReferenceTurnRunnerMatchesSharedGoldenReplayVectors/permission_denied expected: turn:tool_result:0:add:false:permission_denied
and, in TypeScript, tests/harness/turn-runner.test.ts > ReferenceTurnRunner > matches shared golden replay journal vectors.
This is handwritten code, not emitter output
Worth stating explicitly, because it was initially unclear whether this belonged to sethjuarez/typra:
turn_runner.go has no // <auto-generated by typra-emitter> banner; it begins directly with package prompty.
- Of 299
.go files in runtime/go/prompty/model, 287 carry the generator banner and 12 do not. turn_runner.go is one of the 12.
- A full
npm run generate leaves it untouched — git status --porcelain reports nothing for it afterwards.
runtime/typescript/packages/core/src/harness/turn-runner.ts is likewise handwritten.
Suggested fix
Record TurnEventTypeToolResult on the denied branch before returning, in both runtimes, then re-run against the shared vectors.
Not verified
I have not checked the Rust, Python, C#, Swift, or Java reference runners for the same omission.
Summary
ReferenceTurnRunner.resolveAndExecuteTool's permission-denied branch returns aHostToolResultwithout first recording atool_resultjournal event, so the emitted journal diverges fromspec/vectors/harness/replay_vectors.json.This is the single remaining Go failure and one of the remaining TypeScript failures.
The vector requires the event
spec/vectors/harness/replay_vectors.json, scenariopermission_denied:json "expected": [ "session:session_start:session-1:turn-1", "turn:turn_start:0", "turn:llm_start:0", "turn:llm_complete:0", "session:checkpoint_created:session-1:turn-1", "turn:permission_requested:0:exec-1-permission", "turn:permission_completed:0:false", "turn:tool_result:0:add:false:permission_denied", "turn:messages_updated:0", ... ]turn:tool_result:0:add:false:permission_deniedmust followturn:permission_completed:0:false.Compare the
tool_successscenario in the same file, which recordsturn:tool_execution_start→turn:tool_execution_complete→turn:tool_result. The denied path is expected to emittool_resultwithout the two execution events, since nothing executed.The code
runtime/go/prompty/model/turn_runner.go, inresolveAndExecuteTool:go if err := r.recordTurn(TurnEventTypePermissionCompleted, turnId, iteration, decision.Save(NewSaveContext())); err != nil { return HostToolResult{}, err } if !decision.Approved { errorKind := "permission_denied" message := "Permission denied" if decision.Reason != nil { message = *decision.Reason } result := interface{}(map[string]interface{}{"message": message}) return HostToolResult{ // <-- returns without recording TurnEventTypeToolResult RequestId: toolRequest.RequestId, ToolCallId: toolRequest.ToolCallId, ToolName: toolRequest.ToolName, Success: false, ErrorKind: &errorKind, Result: &result, }, nil } if err := r.recordTurn(TurnEventTypeToolExecutionStart, turnId, iteration, toolRequest.Save(NewSaveContext())); err != nil {The approved path continues on and records
TurnEventTypeToolResult; the denied path returns early and skips it.Observed
--- FAIL: TestReferenceTurnRunnerMatchesSharedGoldenReplayVectors/permission_denied expected: turn:tool_result:0:add:false:permission_deniedand, in TypeScript,
tests/harness/turn-runner.test.ts > ReferenceTurnRunner > matches shared golden replay journal vectors.This is handwritten code, not emitter output
Worth stating explicitly, because it was initially unclear whether this belonged to
sethjuarez/typra:turn_runner.gohas no// <auto-generated by typra-emitter>banner; it begins directly withpackage prompty..gofiles inruntime/go/prompty/model, 287 carry the generator banner and 12 do not.turn_runner.gois one of the 12.npm run generateleaves it untouched —git status --porcelainreports nothing for it afterwards.runtime/typescript/packages/core/src/harness/turn-runner.tsis likewise handwritten.Suggested fix
Record
TurnEventTypeToolResulton the denied branch before returning, in both runtimes, then re-run against the shared vectors.Not verified
I have not checked the Rust, Python, C#, Swift, or Java reference runners for the same omission.