$pr ) { $ps = isset( $pr['surface'] ) ? trim( (string) $pr['surface'] ) : ''; $prr = isset( $pr['ref'] ) ? trim( (string) $pr['ref'] ) : ''; if ( $ps === '' || $prr === '' ) { return new WP_Error( 'invalid_plan_ref', "plan_refs[$i] missing surface or ref" ); } if ( ! in_array( $ps, SA_Orch_Bridge_Fluent::known_surfaces(), true ) ) { return new WP_Error( 'unknown_surface', "plan_refs[$i].surface '$ps' is not known" ); } if ( ! SA_Orch_Bridge_Fluent::validate( $ps, $prr ) ) { return new WP_Error( 'invalid_plan_ref', "plan_refs[$i] does not resolve: $ps/$prr" ); } } $created_at = current_time( 'mysql', true ); $hash = self::compute_hash( $demand_surface, $demand_ref, $created_at, $actor_user_id, $correlation_id ); // Asterion-first: ledger entry written before any DB row. $frontmatter = [ 'kind' => 'arbitration', 'hash' => $hash, 'demand_surface' => $demand_surface, 'demand_ref' => $demand_ref, 'projection_type' => $projection_type, 'actor_user_id' => $actor_user_id, 'correlation_id' => $correlation_id, 'causation_id' => $causation_id, 'truth_class' => $truth_class, 'plan_refs' => array_map( function ( $pr ) { return ( $pr['surface'] ?? '' ) . ':' . ( $pr['ref'] ?? '' ); }, $plan_refs ), 'created_at' => $created_at, ]; $body = "## Rationale\n\n" . $rationale . "\n"; $rel_path = SA_Orch_Asterion::write( 'arbitration', $hash, $frontmatter, $body ); if ( is_wp_error( $rel_path ) ) { return $rel_path; } // Now insert the runtime cache row. $table = $wpdb->prefix . SA_ORCH_DB_PREFIX . 'arbitration'; $ok = $wpdb->insert( $table, [ 'hash' => $hash, 'demand_surface' => $demand_surface, 'demand_ref' => $demand_ref, 'rationale' => $rationale, 'projection_type' => $projection_type, 'actor_user_id' => $actor_user_id, 'correlation_id' => $correlation_id, 'causation_id' => $causation_id, 'truth_class' => $truth_class, 'asterion_path' => $rel_path, 'created_at' => $created_at, ] ); if ( $ok === false ) { return new WP_Error( 'db_insert_failed', $wpdb->last_error, [ 'asterion_path' => $rel_path ] ); } $arbitration_id = (int) $wpdb->insert_id; $link_ids = []; foreach ( $plan_refs as $pr ) { $link_id = SA_Orch_Link::write( [ 'arbitration_id' => $arbitration_id, 'plan_surface' => $pr['surface'], 'plan_ref' => $pr['ref'], 'link_kind' => $pr['link_kind'] ?? 'derived', ] ); if ( is_wp_error( $link_id ) ) { return $link_id; } $link_ids[] = $link_id; } // v0.6.0: notify projection / side-effect listeners. Decoupled — listeners // (e.g. SA_Orch_Projection_Fluent_Support) react via WordPress action hooks. do_action( 'sa_orch_arbitration_written', [ 'id' => $arbitration_id, 'hash' => $hash, 'demand_surface' => $demand_surface, 'demand_ref' => $demand_ref, 'projection_type' => $projection_type, 'truth_class' => $truth_class, 'plan_link_ids' => $link_ids, 'correlation_id' => $correlation_id, 'asterion_path' => $rel_path, ] ); return [ 'id' => $arbitration_id, 'hash' => $hash, 'asterion_path' => $rel_path, 'plan_link_ids' => $link_ids, 'correlation_id' => $correlation_id, ]; } private static function compute_hash( $demand_surface, $demand_ref, $created_at, $actor_user_id, $correlation_id ) { $material = implode( '|', [ $demand_surface, $demand_ref, $created_at, (int) $actor_user_id, $correlation_id, ] ); return substr( hash( 'sha256', $material ), 0, 32 ); } }