@riddledc/riddle-proof 0.8.12 → 0.8.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  export { b as runner } from '../runner-4LJ5z0D-.cjs';
2
2
  export { l as engineHarness } from '../engine-harness-LBfqbFSe.cjs';
3
- export { p as proofRunCore } from '../proof-run-core-CrpYH-qH.cjs';
4
- export { p as proofRunEngine } from '../proof-run-engine-C6vYAZd8.cjs';
3
+ export { p as proofRunCore } from '../proof-run-core-C8FDUhle.cjs';
4
+ export { p as proofRunEngine } from '../proof-run-engine-D80hVFMf.cjs';
5
5
  import '../types.cjs';
@@ -1,5 +1,5 @@
1
1
  export { b as runner } from '../runner-BdQpOkZD.js';
2
2
  export { l as engineHarness } from '../engine-harness-CMACHP6A.js';
3
- export { p as proofRunCore } from '../proof-run-core-CrpYH-qH.js';
4
- export { p as proofRunEngine } from '../proof-run-engine-h9C1lC0w.js';
3
+ export { p as proofRunCore } from '../proof-run-core-C8FDUhle.js';
4
+ export { p as proofRunEngine } from '../proof-run-engine-By7oLsF-.js';
5
5
  import '../types.js';
@@ -1 +1 @@
1
- export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from '../proof-run-core-CrpYH-qH.cjs';
1
+ export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from '../proof-run-core-C8FDUhle.cjs';
@@ -1 +1 @@
1
- export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from '../proof-run-core-CrpYH-qH.js';
1
+ export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from '../proof-run-core-C8FDUhle.js';
@@ -1,2 +1,2 @@
1
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-C6vYAZd8.cjs';
2
- import '../proof-run-core-CrpYH-qH.cjs';
1
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-D80hVFMf.cjs';
2
+ import '../proof-run-core-C8FDUhle.cjs';
@@ -1,2 +1,2 @@
1
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-h9C1lC0w.js';
2
- import '../proof-run-core-CrpYH-qH.js';
1
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-By7oLsF-.js';
2
+ import '../proof-run-core-C8FDUhle.js';
@@ -121,7 +121,7 @@ declare function buildSetupArgs(params: WorkflowParams, config: ReturnType<typeo
121
121
  target_image_hash: string;
122
122
  viewport_matrix_json: string;
123
123
  deterministic_setup_json: string;
124
- reference: "prod" | "before" | "both";
124
+ reference: "before" | "prod" | "both";
125
125
  base_branch: string;
126
126
  before_ref: string;
127
127
  allow_static_preview_fallback: string;
@@ -121,7 +121,7 @@ declare function buildSetupArgs(params: WorkflowParams, config: ReturnType<typeo
121
121
  target_image_hash: string;
122
122
  viewport_matrix_json: string;
123
123
  deterministic_setup_json: string;
124
- reference: "prod" | "before" | "both";
124
+ reference: "before" | "prod" | "both";
125
125
  base_branch: string;
126
126
  before_ref: string;
127
127
  allow_static_preview_fallback: string;
@@ -1 +1 @@
1
- export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from './proof-run-core-CrpYH-qH.cjs';
1
+ export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from './proof-run-core-C8FDUhle.cjs';
@@ -1 +1 @@
1
- export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from './proof-run-core-CrpYH-qH.js';
1
+ export { B as BUNDLED_RIDDLE_PROOF_DIR, C as CHECKPOINT_CONTRACT_VERSION, b as CheckpointInputContract, P as PluginConfig, R as RIDDLE_PROOF_DIR_CANDIDATES, S as ShipGateValidation, c as WORKFLOW_STAGE_ORDER, d as WorkflowAction, W as WorkflowParams, a as WorkflowStage, e as buildCheckpointContract, f as buildSetupArgs, g as checkpointContinueStage, h as clearStageDecisionRequest, i as ensureAction, j as ensureStageLoopState, k as invalidateVerifyEvidence, m as mergeStateFromParams, n as noImplementationModeFor, l as previewModeFromWorkflowMode, o as proofAssessmentHardBlockersForState, q as readState, s as recordStageAttempt, t as requiredBaselineLabelsForState, r as resolveConfig, u as resolveRiddleProofDir, v as setStageDecisionRequest, w as summarizeState, x as validateShipGate, y as visualDeltaForState, z as visualDeltaRequiredForState, A as visualDeltaShipGateReason, D as workflowFile, E as writeState } from './proof-run-core-C8FDUhle.js';
@@ -1,4 +1,4 @@
1
- import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-CrpYH-qH.js';
1
+ import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-C8FDUhle.js';
2
2
 
3
3
  declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, resolvedConfig?: ReturnType<typeof resolveConfig>): Promise<{
4
4
  ok: boolean;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -1,4 +1,4 @@
1
- import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-CrpYH-qH.cjs';
1
+ import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-C8FDUhle.cjs';
2
2
 
3
3
  declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, resolvedConfig?: ReturnType<typeof resolveConfig>): Promise<{
4
4
  ok: boolean;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -1,2 +1,2 @@
1
- import './proof-run-core-CrpYH-qH.cjs';
2
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-C6vYAZd8.cjs';
1
+ import './proof-run-core-C8FDUhle.cjs';
2
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-D80hVFMf.cjs';
@@ -1,2 +1,2 @@
1
- import './proof-run-core-CrpYH-qH.js';
2
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-h9C1lC0w.js';
1
+ import './proof-run-core-C8FDUhle.js';
2
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-By7oLsF-.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -2029,7 +2029,7 @@ def interaction_proof_route_match(expected_path, proof_evidence):
2029
2029
  return None
2030
2030
  for record in proof_evidence_records_deep(proof_evidence):
2031
2031
  flag = explicit_route_match_flag(record)
2032
- candidate = terminal_path_from_record(record)
2032
+ candidate = observed_terminal_path_from_record(record)
2033
2033
  if candidate and route_matches_expected(expected, candidate):
2034
2034
  return {
2035
2035
  'matched': True,
@@ -2042,23 +2042,64 @@ def interaction_proof_route_match(expected_path, proof_evidence):
2042
2042
 
2043
2043
 
2044
2044
  EXPLICIT_TERMINAL_PATH_KEYS = (
2045
+ 'expected_url', 'expectedUrl',
2046
+ 'expected_href', 'expectedHref',
2045
2047
  'expected_terminal_path', 'expectedTerminalPath',
2046
2048
  'expected_terminal_url', 'expectedTerminalUrl',
2049
+ 'expected_terminal_href', 'expectedTerminalHref',
2047
2050
  'expected_terminal_route', 'expectedTerminalRoute',
2051
+ 'expected_route', 'expectedRoute',
2048
2052
  'terminal_path', 'terminalPath',
2049
2053
  'terminal_url', 'terminalUrl',
2054
+ 'terminal_href', 'terminalHref',
2050
2055
  'terminal_route', 'terminalRoute',
2051
2056
  'expected_after_path', 'expectedAfterPath',
2052
2057
  'expected_after_url', 'expectedAfterUrl',
2058
+ 'expected_after_href', 'expectedAfterHref',
2053
2059
  'expected_after_route', 'expectedAfterRoute',
2054
2060
  'after_path', 'afterPath',
2055
2061
  'after_url', 'afterUrl',
2062
+ 'after_href', 'afterHref',
2056
2063
  'after_route', 'afterRoute',
2057
2064
  'expected_final_path', 'expectedFinalPath',
2058
2065
  'expected_final_url', 'expectedFinalUrl',
2066
+ 'expected_final_href', 'expectedFinalHref',
2059
2067
  'expected_final_route', 'expectedFinalRoute',
2060
2068
  'final_path', 'finalPath',
2061
2069
  'final_url', 'finalUrl',
2070
+ 'final_href', 'finalHref',
2071
+ 'final_route', 'finalRoute',
2072
+ )
2073
+ EXPECTED_TERMINAL_PATH_KEYS = (
2074
+ 'expected_url', 'expectedUrl',
2075
+ 'expected_href', 'expectedHref',
2076
+ 'expected_path', 'expectedPath',
2077
+ 'expected_route', 'expectedRoute',
2078
+ 'expected_terminal_path', 'expectedTerminalPath',
2079
+ 'expected_terminal_url', 'expectedTerminalUrl',
2080
+ 'expected_terminal_href', 'expectedTerminalHref',
2081
+ 'expected_terminal_route', 'expectedTerminalRoute',
2082
+ 'expected_after_path', 'expectedAfterPath',
2083
+ 'expected_after_url', 'expectedAfterUrl',
2084
+ 'expected_after_href', 'expectedAfterHref',
2085
+ 'expected_after_route', 'expectedAfterRoute',
2086
+ 'expected_final_path', 'expectedFinalPath',
2087
+ 'expected_final_url', 'expectedFinalUrl',
2088
+ 'expected_final_href', 'expectedFinalHref',
2089
+ 'expected_final_route', 'expectedFinalRoute',
2090
+ )
2091
+ OBSERVED_TERMINAL_PATH_KEYS = (
2092
+ 'terminal_path', 'terminalPath',
2093
+ 'terminal_url', 'terminalUrl',
2094
+ 'terminal_href', 'terminalHref',
2095
+ 'terminal_route', 'terminalRoute',
2096
+ 'after_path', 'afterPath',
2097
+ 'after_url', 'afterUrl',
2098
+ 'after_href', 'afterHref',
2099
+ 'after_route', 'afterRoute',
2100
+ 'final_path', 'finalPath',
2101
+ 'final_url', 'finalUrl',
2102
+ 'final_href', 'finalHref',
2062
2103
  'final_route', 'finalRoute',
2063
2104
  )
2064
2105
  FULL_LOCATION_PATH_KEYS = (
@@ -2087,6 +2128,19 @@ AFTER_STATE_KEYS = (
2087
2128
  'final', 'final_state', 'finalState',
2088
2129
  'expected_final', 'expectedFinal',
2089
2130
  )
2131
+ EXPECTED_STATE_KEYS = (
2132
+ 'expected', 'expectation',
2133
+ 'route_expectation', 'routeExpectation',
2134
+ 'expected_after', 'expectedAfter',
2135
+ 'expected_terminal', 'expectedTerminal',
2136
+ 'expected_final', 'expectedFinal',
2137
+ )
2138
+ OBSERVED_STATE_KEYS = (
2139
+ 'observed', 'actual',
2140
+ 'after', 'after_state', 'afterState',
2141
+ 'terminal', 'terminal_state', 'terminalState',
2142
+ 'final', 'final_state', 'finalState',
2143
+ )
2090
2144
  EVIDENCE_CONTAINER_KEYS = (
2091
2145
  'proofEvidence', 'proof_evidence',
2092
2146
  'interactionEvidence', 'interaction_evidence',
@@ -2113,10 +2167,10 @@ def path_candidate(value):
2113
2167
  return ''
2114
2168
 
2115
2169
 
2116
- def record_path_candidate(record, allow_location_keys=False):
2170
+ def record_path_candidate_for_keys(record, keys, allow_location_keys=False):
2117
2171
  if not isinstance(record, dict):
2118
2172
  return ''
2119
- keys = list(EXPLICIT_TERMINAL_PATH_KEYS)
2173
+ keys = list(keys)
2120
2174
  if allow_location_keys:
2121
2175
  keys.extend(LOCATION_PATH_KEYS)
2122
2176
  for key in keys:
@@ -2126,6 +2180,10 @@ def record_path_candidate(record, allow_location_keys=False):
2126
2180
  return ''
2127
2181
 
2128
2182
 
2183
+ def record_path_candidate(record, allow_location_keys=False):
2184
+ return record_path_candidate_for_keys(record, EXPLICIT_TERMINAL_PATH_KEYS, allow_location_keys)
2185
+
2186
+
2129
2187
  def terminal_path_from_record(record, depth=0):
2130
2188
  if not isinstance(record, dict) or depth > 4:
2131
2189
  return ''
@@ -2168,6 +2226,74 @@ def terminal_path_from_record(record, depth=0):
2168
2226
  return ''
2169
2227
 
2170
2228
 
2229
+ def expected_terminal_path_from_record(record, depth=0):
2230
+ if not isinstance(record, dict) or depth > 4:
2231
+ return ''
2232
+ candidate = record_path_candidate_for_keys(record, EXPECTED_TERMINAL_PATH_KEYS)
2233
+ if candidate:
2234
+ return candidate
2235
+ for key in EXPECTED_STATE_KEYS:
2236
+ value = record.get(key)
2237
+ if isinstance(value, dict):
2238
+ candidate = (
2239
+ record_path_candidate_for_keys(value, EXPECTED_TERMINAL_PATH_KEYS, allow_location_keys=True)
2240
+ or expected_terminal_path_from_record(value, depth + 1)
2241
+ )
2242
+ if candidate:
2243
+ return candidate
2244
+ elif isinstance(value, list):
2245
+ for item in value:
2246
+ candidate = expected_terminal_path_from_record(item, depth + 1)
2247
+ if candidate:
2248
+ return candidate
2249
+ for key in EVIDENCE_CONTAINER_KEYS:
2250
+ value = record.get(key)
2251
+ if isinstance(value, dict):
2252
+ candidate = expected_terminal_path_from_record(value, depth + 1)
2253
+ if candidate:
2254
+ return candidate
2255
+ elif isinstance(value, list):
2256
+ for item in value:
2257
+ candidate = expected_terminal_path_from_record(item, depth + 1)
2258
+ if candidate:
2259
+ return candidate
2260
+ return ''
2261
+
2262
+
2263
+ def observed_terminal_path_from_record(record, depth=0):
2264
+ if not isinstance(record, dict) or depth > 4:
2265
+ return ''
2266
+ candidate = record_path_candidate_for_keys(record, OBSERVED_TERMINAL_PATH_KEYS)
2267
+ if candidate:
2268
+ return candidate
2269
+ for key in OBSERVED_STATE_KEYS:
2270
+ value = record.get(key)
2271
+ if isinstance(value, dict):
2272
+ candidate = (
2273
+ record_path_candidate_for_keys(value, OBSERVED_TERMINAL_PATH_KEYS, allow_location_keys=True)
2274
+ or observed_terminal_path_from_record(value, depth + 1)
2275
+ )
2276
+ if candidate:
2277
+ return candidate
2278
+ elif isinstance(value, list):
2279
+ for item in value:
2280
+ candidate = observed_terminal_path_from_record(item, depth + 1)
2281
+ if candidate:
2282
+ return candidate
2283
+ for key in EVIDENCE_CONTAINER_KEYS:
2284
+ value = record.get(key)
2285
+ if isinstance(value, dict):
2286
+ candidate = observed_terminal_path_from_record(value, depth + 1)
2287
+ if candidate:
2288
+ return candidate
2289
+ elif isinstance(value, list):
2290
+ for item in value:
2291
+ candidate = observed_terminal_path_from_record(item, depth + 1)
2292
+ if candidate:
2293
+ return candidate
2294
+ return ''
2295
+
2296
+
2171
2297
  def text_path_candidate(value):
2172
2298
  if not isinstance(value, str):
2173
2299
  return ''
@@ -2421,17 +2547,20 @@ def interaction_capture_failure_evidence_summary(proof_evidence):
2421
2547
 
2422
2548
  def interaction_terminal_path_from_evidence(proof_evidence):
2423
2549
  for record in proof_evidence_records(proof_evidence):
2424
- candidate = terminal_path_from_record(record)
2550
+ candidate = expected_terminal_path_from_record(record)
2425
2551
  if candidate:
2426
2552
  return candidate, 'proof_evidence_contract'
2427
- if interaction_assertions_pass(proof_evidence):
2553
+ route_evidence_passed = interaction_assertions_pass(proof_evidence)
2554
+ if not route_evidence_passed:
2555
+ for record in proof_evidence_records_deep(proof_evidence):
2556
+ if interaction_assertions_pass(record) or explicit_route_match_flag(record) is True:
2557
+ route_evidence_passed = True
2558
+ break
2559
+ if route_evidence_passed:
2428
2560
  for record in proof_evidence_records(proof_evidence):
2429
- for key in AFTER_STATE_KEYS:
2430
- value = record.get(key)
2431
- if isinstance(value, dict):
2432
- candidate = record_path_candidate(value, allow_location_keys=True)
2433
- if candidate:
2434
- return candidate, 'proof_evidence_after_state'
2561
+ candidate = observed_terminal_path_from_record(record)
2562
+ if candidate:
2563
+ return candidate, 'proof_evidence_contract'
2435
2564
  return '', ''
2436
2565
 
2437
2566
 
@@ -2469,6 +2598,16 @@ def proof_evidence_should_override_state_terminal_path(state_candidate, evidence
2469
2598
  return False
2470
2599
  if not state_candidate:
2471
2600
  return True
2601
+ state_parts = route_parts(state_candidate)
2602
+ evidence_parts = route_parts(evidence_candidate)
2603
+ if (
2604
+ state_parts.get('pathname') == evidence_parts.get('pathname')
2605
+ and (
2606
+ (evidence_parts.get('query') and not state_parts.get('query'))
2607
+ or (evidence_parts.get('hash') and not state_parts.get('hash'))
2608
+ )
2609
+ ):
2610
+ return True
2472
2611
  if route_matches_expected(state_candidate, evidence_candidate):
2473
2612
  return False
2474
2613
  if interaction_assertions_pass(proof_evidence):
@@ -2953,6 +3092,25 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
2953
3092
  and route_expectation.get('terminal_path')
2954
3093
  and route_expectation_source != 'recon_start_path'
2955
3094
  )
3095
+ if authored_terminal_route:
3096
+ expected = route_mismatch.get('expected_path') or ''
3097
+ observed = route_mismatch.get('observed_after_path') or ''
3098
+ summary = 'Interaction proof terminal route mismatch: expected ' + (expected or '(unknown)') + ', got ' + (observed or '(unknown)') + '.'
3099
+ if error_messages:
3100
+ reasons.append('Capture script error: ' + error_messages[0][:500])
3101
+ summary += ' Capture script error: ' + error_messages[0][:300]
3102
+ reasons.append('Route mismatch: expected after capture path ' + (expected or '(unknown)') + ', observed ' + (observed or '(unknown)') + '.')
3103
+ reasons.append('The terminal route came from authored interaction proof evidence, so the captured browser evidence is the terminal blocker instead of an author retry loop.')
3104
+ return {
3105
+ 'decision': 'failed_interaction_capture',
3106
+ 'summary': summary,
3107
+ 'recommended_stage': None,
3108
+ 'continue_with_stage': None,
3109
+ 'blocking': True,
3110
+ 'terminal_blocker': True,
3111
+ 'reasons': reasons,
3112
+ 'mismatch': route_mismatch,
3113
+ }
2956
3114
  recommended_stage = 'recon' if 'wrong route' in reason and not authored_terminal_route else 'author'
2957
3115
  mismatch = None
2958
3116
  if recommended_stage == 'recon':
@@ -325,6 +325,70 @@ class FakeRiddle:
325
325
  'proof.json': {'script_error': message},
326
326
  },
327
327
  }
328
+ if 'pricingQueryHashStructuredNegativeControl' in script:
329
+ page_state = {
330
+ 'bodyTextLength': 260,
331
+ 'visibleTextSample': 'Pricing One rate Browser Compute Example Costs',
332
+ 'interactiveElements': 8,
333
+ 'visibleInteractiveElements': 8,
334
+ 'pathname': '/pricing/',
335
+ 'search': '',
336
+ 'hash': '',
337
+ 'title': 'Pricing',
338
+ 'buttons': [],
339
+ 'headings': ['Pricing', 'Browser Compute'],
340
+ 'links': [{'text': 'Pricing', 'href': '/pricing/?rp_probe=1#pricing-probe'}],
341
+ 'canvasCount': 0,
342
+ 'largeVisibleElements': [{'tag': 'main', 'text': 'Pricing'}],
343
+ }
344
+ proof_evidence = {
345
+ 'version': 'riddle-proof.interaction.v1',
346
+ 'probe': 'query-hash-dropped-negative-control',
347
+ 'negativeControl': True,
348
+ 'routeExpectationSource': 'capture_script.expectedUrl',
349
+ 'expectedUrl': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
350
+ 'expectedHref': '/pricing/?rp_probe=1#pricing-probe',
351
+ 'intentionalObservedUrl': 'https://riddledc.com/pricing/',
352
+ 'start': {'href': 'https://riddledc.com/', 'pathname': '/', 'search': '', 'hash': ''},
353
+ 'action': {
354
+ 'type': 'rewrite-pricing-link-click-then-drop-query-hash',
355
+ 'afterClickHref': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
356
+ 'afterClickPathname': '/pricing/',
357
+ 'afterClickSearch': '?rp_probe=1',
358
+ 'afterClickHash': '#pricing-probe',
359
+ 'expectedNavigationReached': True,
360
+ },
361
+ 'terminal': {
362
+ 'href': 'https://riddledc.com/pricing/',
363
+ 'pathname': '/pricing/',
364
+ 'search': '',
365
+ 'hash': '',
366
+ },
367
+ 'assertions': {
368
+ 'expectedUrlPreserved': True,
369
+ 'expectedUrlReachedBeforeDrop': True,
370
+ 'routeExpectationSourceIsCaptureScriptExpectedUrl': True,
371
+ 'terminalIntentionallyDroppedQueryHash': True,
372
+ 'terminalUrlMismatchIsIntentional': True,
373
+ 'terminalMainVisible': True,
374
+ },
375
+ 'errors': [],
376
+ }
377
+ return {
378
+ 'ok': True,
379
+ 'screenshots': [{'url': 'https://cdn.example.com/pricing-negative-control.png'}],
380
+ 'outputs': [{'name': 'after-pricing-negative-control.png', 'url': 'https://cdn.example.com/pricing-negative-control.png'}],
381
+ 'result': {'pageState': page_state, 'proofEvidence': proof_evidence},
382
+ 'console': [
383
+ 'RIDDLE_PROOF_STATE:' + json.dumps(page_state),
384
+ 'RIDDLE_PROOF_EVIDENCE:' + json.dumps(proof_evidence),
385
+ ],
386
+ 'visual_diff': {
387
+ 'diffPercentage': 1.2,
388
+ 'differentPixels': 12000,
389
+ 'totalPixels': 972000,
390
+ },
391
+ }
328
392
  if 'pricingQueryHashPassesWithPageStateHashGap' in script:
329
393
  page_state = {
330
394
  'bodyTextLength': 260,
@@ -2934,6 +2998,78 @@ def run_verify_interaction_query_hash_pass_uses_proof_evidence_route():
2934
2998
  shutil.rmtree(tempdir, ignore_errors=True)
2935
2999
 
2936
3000
 
3001
+ def run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route():
3002
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-explicit-expected-url-mismatch-'))
3003
+ state_path = tempdir / 'state.json'
3004
+ try:
3005
+ state = base_state(tempdir, reference='before')
3006
+ state.update({
3007
+ 'recon_status': 'ready_for_proof_plan',
3008
+ 'author_status': 'ready',
3009
+ 'proof_plan_status': 'ready',
3010
+ 'implementation_status': 'changes_detected',
3011
+ 'verification_mode': 'interaction',
3012
+ 'server_path': '/',
3013
+ 'before_cdn': 'https://cdn.example.com/before-home.png',
3014
+ 'proof_plan': 'Start at /, click Pricing, and intentionally prove the query/hash route mismatch.',
3015
+ 'capture_script': "pricingQueryHashStructuredNegativeControl();",
3016
+ 'supervisor_author_packet': {
3017
+ 'proof_plan': 'Use expectedUrl as the route expectation and return structured evidence for the dropped query/hash terminal URL.',
3018
+ 'capture_script': "pricingQueryHashStructuredNegativeControl();",
3019
+ 'refined_inputs': {
3020
+ 'server_path': '/',
3021
+ },
3022
+ },
3023
+ 'recon_results': {
3024
+ 'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
3025
+ },
3026
+ })
3027
+ write_state(state_path, state)
3028
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
3029
+
3030
+ fake = FakeRiddle()
3031
+ load_util_with_fake(fake)
3032
+ load_module('verify_interaction_explicit_expected_url_blocks_dropped_terminal_route', VERIFY_PATH)
3033
+ after_verify = json.loads(state_path.read_text())
3034
+
3035
+ request = after_verify['verify_decision_request']
3036
+ assert after_verify['verify_status'] == 'capture_incomplete'
3037
+ assert after_verify['merge_recommendation'] == 'do-not-merge'
3038
+ assert after_verify['route_expectation']['source'] == 'proof_evidence_contract'
3039
+ assert after_verify['route_expectation']['expected_path'] == '/pricing?rp_probe=1#pricing-probe'
3040
+ assert after_verify['route_expectation']['expected_query'] == 'rp_probe=1'
3041
+ assert after_verify['route_expectation']['expected_hash'] == '#pricing-probe'
3042
+ assert request['recommended_stage'] is None
3043
+ assert request['continue_with_stage'] is None
3044
+ capture_quality = request['capture_quality']
3045
+ assert capture_quality['decision'] == 'failed_interaction_capture'
3046
+ assert capture_quality['blocking'] is True
3047
+ assert capture_quality['terminal_blocker'] is True
3048
+ assert capture_quality['mismatch']['expected_path'] == '/pricing?rp_probe=1#pricing-probe'
3049
+ assert capture_quality['mismatch']['observed_after_path'] in ('/pricing', '/pricing/')
3050
+ assert 'Interaction proof terminal route mismatch' in capture_quality['summary']
3051
+ assert after_verify['proof_assessment_request'] == {}
3052
+ observation = request['latest_observation']
3053
+ assert observation['valid'] is False
3054
+ assert 'wrong route' in observation['reason']
3055
+ supporting = after_verify['verify_results']['after']['supporting_artifacts']
3056
+ assert supporting['proof_evidence_present'] is True
3057
+ assert supporting['has_structured_payload'] is True
3058
+ route = after_verify['evidence_bundle']['semantic_context']['route']
3059
+ assert route['expected_terminal_query'] == 'rp_probe=1'
3060
+ assert route['expected_terminal_hash'] == '#pricing-probe'
3061
+ assert route['after_observed_path'] == '/pricing'
3062
+ assert route['after_observed_query'] == ''
3063
+ assert route['after_observed_hash'] == ''
3064
+ return {
3065
+ 'ok': True,
3066
+ 'decision': capture_quality['decision'],
3067
+ 'summary': capture_quality['summary'],
3068
+ }
3069
+ finally:
3070
+ shutil.rmtree(tempdir, ignore_errors=True)
3071
+
3072
+
2937
3073
  def run_verify_interaction_thrown_error_terminal_blocker():
2938
3074
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-thrown-error-'))
2939
3075
  state_path = tempdir / 'state.json'
@@ -3416,6 +3552,7 @@ if __name__ == '__main__':
3416
3552
  'verify_interaction_hash_terminal_route_from_proof_evidence': run_verify_interaction_hash_terminal_route_from_proof_evidence(),
3417
3553
  'verify_interaction_authored_query_hash_mismatch_blocks_with_evidence': run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence(),
3418
3554
  'verify_interaction_query_hash_pass_uses_proof_evidence_route': run_verify_interaction_query_hash_pass_uses_proof_evidence_route(),
3555
+ 'verify_interaction_explicit_expected_url_blocks_dropped_terminal_route': run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route(),
3419
3556
  'verify_interaction_thrown_error_terminal_blocker': run_verify_interaction_thrown_error_terminal_blocker(),
3420
3557
  'verify_capture_retry_surfaces_script_timeout': run_verify_capture_retry_surfaces_script_timeout(),
3421
3558
  'missing_baseline_guard': run_verify_missing_baseline(),
@@ -55,6 +55,12 @@ CASES = [
55
55
  'function': 'run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence',
56
56
  'expected_terminal': 'specific_blocker',
57
57
  },
58
+ {
59
+ 'name': 'query-hash-dropped-structured-negative-blocker',
60
+ 'covers': ['query/hash/trailing-slash URLs', 'invalid browser evidence', 'proof-evidence-present'],
61
+ 'function': 'run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route',
62
+ 'expected_terminal': 'specific_blocker',
63
+ },
58
64
  {
59
65
  'name': 'same-page-hash-pass',
60
66
  'covers': ['same-page hashes'],