Spaces:
Sleeping
Sleeping
| defined( 'ABSPATH' ) || exit; | |
| class SA_Orch_Arbitration { | |
| /** | |
| * Write an arbitration record + optional plan_link rows. | |
| * | |
| * Required: | |
| * - demand_surface (string) e.g. 'fluent-support' | |
| * - demand_ref (string) e.g. 'ticket#12' | |
| * - rationale (non-empty string) | |
| * - projection_type (string) e.g. 'scope-direct-no-plan' | |
| * | |
| * Optional: | |
| * - actor_user_id (int; default current user) | |
| * - correlation_id (string; default UUID v4) | |
| * - causation_id (string|null) | |
| * - truth_class (string; default 'canonical') | |
| * - plan_refs (array of [ surface, ref, link_kind? ]) | |
| * | |
| * Returns array on success, WP_Error on failure. | |
| */ | |
| public static function write( array $args ) { | |
| global $wpdb; | |
| $demand_surface = isset( $args['demand_surface'] ) ? trim( (string) $args['demand_surface'] ) : ''; | |
| $demand_ref = isset( $args['demand_ref'] ) ? trim( (string) $args['demand_ref'] ) : ''; | |
| $rationale = isset( $args['rationale'] ) ? trim( (string) $args['rationale'] ) : ''; | |
| $projection_type = isset( $args['projection_type'] ) ? trim( (string) $args['projection_type'] ) : ''; | |
| if ( $demand_surface === '' || $demand_ref === '' ) { | |
| return new WP_Error( 'missing_demand', 'demand_surface and demand_ref are required' ); | |
| } | |
| if ( $rationale === '' ) { | |
| return new WP_Error( 'missing_rationale', 'rationale is required (mandatory per SARA discipline)' ); | |
| } | |
| if ( $projection_type === '' ) { | |
| return new WP_Error( 'missing_projection_type', 'projection_type is required' ); | |
| } | |
| if ( ! in_array( $demand_surface, SA_Orch_Bridge_Fluent::known_surfaces(), true ) ) { | |
| return new WP_Error( 'unknown_surface', "demand_surface '$demand_surface' is not in known surface vocabulary" ); | |
| } | |
| if ( ! SA_Orch_Bridge_Fluent::validate( $demand_surface, $demand_ref ) ) { | |
| return new WP_Error( 'invalid_demand_ref', "demand_ref does not resolve: {$demand_surface}/{$demand_ref}" ); | |
| } | |
| $actor_user_id = isset( $args['actor_user_id'] ) ? (int) $args['actor_user_id'] : get_current_user_id(); | |
| if ( $actor_user_id === 0 ) { $actor_user_id = null; } | |
| $correlation_id = isset( $args['correlation_id'] ) && $args['correlation_id'] !== '' | |
| ? (string) $args['correlation_id'] | |
| : wp_generate_uuid4(); | |
| $causation_id = isset( $args['causation_id'] ) && $args['causation_id'] !== '' | |
| ? (string) $args['causation_id'] | |
| : null; | |
| $truth_class = isset( $args['truth_class'] ) && $args['truth_class'] !== '' | |
| ? (string) $args['truth_class'] | |
| : 'canonical'; | |
| $plan_refs = isset( $args['plan_refs'] ) && is_array( $args['plan_refs'] ) ? $args['plan_refs'] : []; | |
| // Validate plan_refs each | |
| foreach ( $plan_refs as $i => $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 ); | |
| } | |
| } | |