Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"test:php-agent-runtime-execution": "php scripts/php-agent-runtime-execution-smoke.php",
"test:php-runtime-provider-registry": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-runtime-provider-registry-smoke.php",
"test:php-browser-provider-auth-strategy": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-provider-auth-strategy-smoke.php",
"test:php-browser-preview-only-session": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-preview-only-session-smoke.php",
"test:php-json-codec": "tsx tests/php-json-codec.test.ts",
"test:php-managed-host-command": "tsx tests/php-managed-host-command.test.ts",
"test:php-worker-runner": "tsx tests/php-worker-runner.test.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,9 @@ public static function runtime_requirements( array $requirements ): array {

/** @param array<string,mixed> $prepared Prepared runtime descriptor. @param array<string,mixed> $session Optional source session. @return array<string,mixed> */
public static function browser_blueprint_ref( array $prepared, array $session = array() ): array {
if ( ! empty( $prepared['has_post_runtime_blueprint'] ) && is_array( $session['playground']['blueprint'] ?? null ) ) {
$prepared = self::browser_session_blueprint_ref_prepared( $prepared, $session, $session['playground']['blueprint'] );
}
$cache_key = self::safe_key( (string) ( $prepared['cache_key'] ?? $prepared['key'] ?? '' ) );
$input_hash = strtolower( trim( (string) ( $prepared['input_hash'] ?? $prepared['hash'] ?? '' ) ) );
$ref = '' !== $cache_key && preg_match( '/^[a-f0-9]{64}$/', $input_hash ) ? 'prepared:' . $cache_key . ':' . $input_hash : '';
Expand All @@ -605,6 +608,30 @@ public static function browser_blueprint_ref( array $prepared, array $session =
);
}

/** @param array<string,mixed> $prepared @param array<string,mixed> $session @param array<string,mixed> $blueprint @return array<string,mixed> */
private static function browser_session_blueprint_ref_prepared( array $prepared, array $session, array $blueprint ): array {
$cache_key = self::safe_key( (string) ( $prepared['cache_key'] ?? $prepared['key'] ?? '' ) );
$input_hash = strtolower( trim( (string) ( $prepared['input_hash'] ?? $prepared['hash'] ?? '' ) ) );
if ( '' === $cache_key || ! preg_match( '/^[a-f0-9]{64}$/', $input_hash ) || ! function_exists( 'set_transient' ) ) {
return $prepared;
}

$session_id = (string) ( $session['session']['id'] ?? $session['session_id'] ?? '' );
$payload = function_exists( 'wp_json_encode' ) ? wp_json_encode( array( 'prepared' => $input_hash, 'session' => $session_id, 'blueprint' => $blueprint ) ) : json_encode( array( 'prepared' => $input_hash, 'session' => $session_id, 'blueprint' => $blueprint ) );
$ref_hash = hash( 'sha256', 'wp-codebox/browser-session-blueprint-ref/v1' . "\n" . (string) $payload );
$artifact = array(
'schema' => 'wp-codebox/browser-prepared-runtime-artifact/v1',
'cache_key' => $cache_key,
'input_hash' => $ref_hash,
'blueprint' => $blueprint,
);
$ttl = defined( 'WEEK_IN_SECONDS' ) ? WEEK_IN_SECONDS : 604800;
set_transient( self::prepared_runtime_transient_key( $cache_key, $ref_hash ), $artifact, $ttl );
$prepared['input_hash'] = $ref_hash;

return $prepared;
}

/** @param array<string,mixed> $input Prepared runtime, full session, or executable ref request. @return array<string,mixed> */
public static function executable_blueprint_ref( array $input ): array {
if ( is_array( $input['blueprint_ref'] ?? null ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,13 @@ public static function create_browser_playground_session( array $input ): array|
return $site_blueprint_artifact;
}

$base_blueprint = self::browser_blueprint_with_site_artifact( is_array( $input['blueprint'] ?? null ) ? $input['blueprint'] : array(), $site_blueprint_artifact );
$blueprint = self::browser_blueprint_with_runtime( $base_blueprint, $runtime, $playground );
$blueprint = self::browser_blueprint_with_post_runtime( $blueprint, is_array( $input['post_runtime_blueprint'] ?? null ) ? $input['post_runtime_blueprint'] : array() );
$prepared_runtime = self::browser_prepared_runtime_with_blueprints( is_array( $runtime['prepared_runtime'] ?? null ) ? $runtime['prepared_runtime'] : array(), $blueprint, $playground );
$base_blueprint = self::browser_blueprint_with_site_artifact( is_array( $input['blueprint'] ?? null ) ? $input['blueprint'] : array(), $site_blueprint_artifact );
$runtime_blueprint = self::browser_blueprint_with_runtime( $base_blueprint, $runtime, $playground );
$post_runtime_blueprint = is_array( $input['post_runtime_blueprint'] ?? null ) ? $input['post_runtime_blueprint'] : array();
$prepared_runtime = self::browser_prepared_runtime_with_blueprints( is_array( $runtime['prepared_runtime'] ?? null ) ? $runtime['prepared_runtime'] : array(), $runtime_blueprint, $playground );
$runtime_blueprint = self::browser_selected_prepared_runtime_blueprint( $prepared_runtime, $runtime_blueprint );
$blueprint = self::browser_blueprint_with_post_runtime( $runtime_blueprint, $post_runtime_blueprint );
$prepared_runtime['has_post_runtime_blueprint'] = ! empty( $post_runtime_blueprint );
$runtime['prepared_runtime'] = $prepared_runtime;
$contained_site = self::browser_contained_site_envelope( $input, $session_id, $playground, $runtime, $prepared_runtime, 'ready' );
$artifacts = self::browser_artifact_files( $input );
Expand All @@ -214,7 +217,7 @@ public static function create_browser_playground_session( array $input ): array|
$task_payload = array();
$recipe = array();
$materialization = array();
$recipe_blueprint = self::browser_playground_blueprint( self::browser_selected_prepared_runtime_blueprint( $prepared_runtime, $blueprint ), $playground );
$recipe_blueprint = self::browser_playground_blueprint( $blueprint, $playground );
if ( ! $preview_only ) {
$task_payload = self::browser_task_payload( $input, $task_input, $session_id, $artifacts, $inheritance_payload['inheritance'], $dependency_plan );
$recipe = self::browser_agent_recipe( $task_input, $session_id, $browser_runner, $blueprint, $playground, $task_payload );
Expand All @@ -225,7 +228,7 @@ public static function create_browser_playground_session( array $input ): array|
$materialization = self::browser_materialization_contract( $recipe );
}
if ( is_array( $runtime['prepared_runtime'] ?? null ) ) {
self::browser_prepared_runtime_cache_store( $runtime['prepared_runtime'], $recipe_blueprint );
self::browser_prepared_runtime_cache_store( $runtime['prepared_runtime'], $runtime_blueprint );
}
$blueprint = $recipe_blueprint;

Expand Down
23 changes: 21 additions & 2 deletions scripts/php-browser-preview-only-session-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ function has_runner_step( array $steps ): bool {
'steps' => array( array( 'step' => 'setSiteOptions', 'options' => array( 'description' => 'Site artifact applied' ) ) ),
),
),
'post_runtime_blueprint' => array(
'steps' => array( array( 'step' => 'importWxr', 'file' => 'https://example.test/first-caller.xml' ) ),
),
'include_internal_browser_session' => true,
);

Expand All @@ -139,10 +142,17 @@ function has_runner_step( array $steps ): bool {
fail( 'preview failed: ' . $preview->get_error_code() . ': ' . $preview->get_error_message() );
}
$hydrated = WP_Codebox_Abilities::hydrate_browser_blueprint_ref( array( 'ref' => $preview['product']['preview_boot']['blueprint_ref'] ?? '' ) );
$second_input = $input;
$second_input['sandbox_session_id'] = 'preview-only-session-second';
$second_input['post_runtime_blueprint'] = array(
'steps' => array( array( 'step' => 'importWxr', 'file' => 'https://example.test/second-caller.xml' ) ),
);
$second = WP_Codebox_Abilities::create_browser_playground_session( $second_input + array( 'preview_only' => true ) );
$second_hydrated = WP_Codebox_Abilities::hydrate_browser_blueprint_ref( array( 'ref' => $second['product']['preview_boot']['blueprint_ref'] ?? '' ) );
$agentic = WP_Codebox_Abilities::create_browser_playground_session( $input );
$task = WP_Codebox_Abilities::create_browser_task_contract( $input + array( 'preview_only' => true, 'include_internal_browser_contract' => true ) );

foreach ( array( 'hydrated' => $hydrated, 'agentic' => $agentic, 'task' => $task ) as $name => $result ) {
foreach ( array( 'hydrated' => $hydrated, 'second' => $second, 'second_hydrated' => $second_hydrated, 'agentic' => $agentic, 'task' => $task ) as $name => $result ) {
if ( is_wp_error( $result ) ) {
fail( $name . ' failed: ' . $result->get_error_code() . ': ' . $result->get_error_message() );
}
Expand Down Expand Up @@ -170,7 +180,16 @@ function has_runner_step( array $steps ): bool {
expect( true === ( $preview['product']['preview_boot']['blueprint_ref_dto']['hydratable'] ?? false ), 'Preview-only product DTO must expose a hydratable blueprint ref.' );

expect( ! is_wp_error( $hydrated ), 'Preview-only prepared runtime blueprint ref must hydrate.' );
expect( $preview['playground']['blueprint'] === ( $hydrated['blueprint'] ?? null ), 'Preview-only boot blueprint must be the cached executable runtime blueprint.' );
expect( 'miss' === ( $preview['runtime']['prepared_runtime']['status'] ?? '' ), 'First caller tail must materialize the shared runtime cache.' );
expect( 'hit' === ( $second['runtime']['prepared_runtime']['status'] ?? '' ), 'Second caller tail must reuse the shared runtime cache.' );
expect( $preview['playground']['blueprint'] === ( $hydrated['blueprint'] ?? null ), 'First caller hydration must return its executable blueprint.' );
expect( $second['playground']['blueprint'] === ( $second_hydrated['blueprint'] ?? null ), 'Second caller hydration must return its executable blueprint.' );
expect( str_contains( wp_json_encode( $hydrated['blueprint']['steps'] ?? array() ) ?: '', 'first-caller.xml' ), 'First caller hydration must retain its SSI import.' );
expect( ! str_contains( wp_json_encode( $hydrated['blueprint']['steps'] ?? array() ) ?: '', 'second-caller.xml' ), 'First caller hydration must not contain the second caller import.' );
expect( str_contains( wp_json_encode( $second_hydrated['blueprint']['steps'] ?? array() ) ?: '', 'second-caller.xml' ), 'Second caller hydration must retain its SSI import.' );
expect( ! str_contains( wp_json_encode( $second_hydrated['blueprint']['steps'] ?? array() ) ?: '', 'first-caller.xml' ), 'Second caller hydration must not contain the first caller import.' );
expect( 'importWxr' === ( $hydrated['blueprint']['steps'][ count( $hydrated['blueprint']['steps'] ?? array() ) - 1 ]['step'] ?? '' ), 'First caller import must run after shared runtime steps.' );
expect( 'importWxr' === ( $second_hydrated['blueprint']['steps'][ count( $second_hydrated['blueprint']['steps'] ?? array() ) - 1 ]['step'] ?? '' ), 'Second caller import must run after shared runtime steps.' );

expect( false === ( $agentic['preview_only'] ?? true ), 'Default browser session must remain agentic.' );
expect( 'inherited-provider' === ( $agentic['provider'] ?? '' ), 'Default browser session must retain provider inheritance.' );
Expand Down
Loading