Skip to content

Commit e52ea2f

Browse files
authored
Preserve post-runtime blueprint steps across cache hits (#1819)
1 parent b5b1fc7 commit e52ea2f

4 files changed

Lines changed: 58 additions & 8 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
"test:php-agent-runtime-execution": "php scripts/php-agent-runtime-execution-smoke.php",
155155
"test:php-runtime-provider-registry": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-runtime-provider-registry-smoke.php",
156156
"test:php-browser-provider-auth-strategy": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-provider-auth-strategy-smoke.php",
157+
"test:php-browser-preview-only-session": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-preview-only-session-smoke.php",
157158
"test:php-json-codec": "tsx tests/php-json-codec.test.ts",
158159
"test:php-managed-host-command": "tsx tests/php-managed-host-command.test.ts",
159160
"test:php-worker-runner": "tsx tests/php-worker-runner.test.ts",

packages/wordpress-plugin/src/class-wp-codebox-browser-task-builder.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,9 @@ public static function runtime_requirements( array $requirements ): array {
579579

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

611+
/** @param array<string,mixed> $prepared @param array<string,mixed> $session @param array<string,mixed> $blueprint @return array<string,mixed> */
612+
private static function browser_session_blueprint_ref_prepared( array $prepared, array $session, array $blueprint ): array {
613+
$cache_key = self::safe_key( (string) ( $prepared['cache_key'] ?? $prepared['key'] ?? '' ) );
614+
$input_hash = strtolower( trim( (string) ( $prepared['input_hash'] ?? $prepared['hash'] ?? '' ) ) );
615+
if ( '' === $cache_key || ! preg_match( '/^[a-f0-9]{64}$/', $input_hash ) || ! function_exists( 'set_transient' ) ) {
616+
return $prepared;
617+
}
618+
619+
$session_id = (string) ( $session['session']['id'] ?? $session['session_id'] ?? '' );
620+
$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 ) );
621+
$ref_hash = hash( 'sha256', 'wp-codebox/browser-session-blueprint-ref/v1' . "\n" . (string) $payload );
622+
$artifact = array(
623+
'schema' => 'wp-codebox/browser-prepared-runtime-artifact/v1',
624+
'cache_key' => $cache_key,
625+
'input_hash' => $ref_hash,
626+
'blueprint' => $blueprint,
627+
);
628+
$ttl = defined( 'WEEK_IN_SECONDS' ) ? WEEK_IN_SECONDS : 604800;
629+
set_transient( self::prepared_runtime_transient_key( $cache_key, $ref_hash ), $artifact, $ttl );
630+
$prepared['input_hash'] = $ref_hash;
631+
632+
return $prepared;
633+
}
634+
608635
/** @param array<string,mixed> $input Prepared runtime, full session, or executable ref request. @return array<string,mixed> */
609636
public static function executable_blueprint_ref( array $input ): array {
610637
if ( is_array( $input['blueprint_ref'] ?? null ) ) {

packages/wordpress-plugin/src/trait-wp-codebox-abilities-execution.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,13 @@ public static function create_browser_playground_session( array $input ): array|
195195
return $site_blueprint_artifact;
196196
}
197197

198-
$base_blueprint = self::browser_blueprint_with_site_artifact( is_array( $input['blueprint'] ?? null ) ? $input['blueprint'] : array(), $site_blueprint_artifact );
199-
$blueprint = self::browser_blueprint_with_runtime( $base_blueprint, $runtime, $playground );
200-
$blueprint = self::browser_blueprint_with_post_runtime( $blueprint, is_array( $input['post_runtime_blueprint'] ?? null ) ? $input['post_runtime_blueprint'] : array() );
201-
$prepared_runtime = self::browser_prepared_runtime_with_blueprints( is_array( $runtime['prepared_runtime'] ?? null ) ? $runtime['prepared_runtime'] : array(), $blueprint, $playground );
198+
$base_blueprint = self::browser_blueprint_with_site_artifact( is_array( $input['blueprint'] ?? null ) ? $input['blueprint'] : array(), $site_blueprint_artifact );
199+
$runtime_blueprint = self::browser_blueprint_with_runtime( $base_blueprint, $runtime, $playground );
200+
$post_runtime_blueprint = is_array( $input['post_runtime_blueprint'] ?? null ) ? $input['post_runtime_blueprint'] : array();
201+
$prepared_runtime = self::browser_prepared_runtime_with_blueprints( is_array( $runtime['prepared_runtime'] ?? null ) ? $runtime['prepared_runtime'] : array(), $runtime_blueprint, $playground );
202+
$runtime_blueprint = self::browser_selected_prepared_runtime_blueprint( $prepared_runtime, $runtime_blueprint );
203+
$blueprint = self::browser_blueprint_with_post_runtime( $runtime_blueprint, $post_runtime_blueprint );
204+
$prepared_runtime['has_post_runtime_blueprint'] = ! empty( $post_runtime_blueprint );
202205
$runtime['prepared_runtime'] = $prepared_runtime;
203206
$contained_site = self::browser_contained_site_envelope( $input, $session_id, $playground, $runtime, $prepared_runtime, 'ready' );
204207
$artifacts = self::browser_artifact_files( $input );
@@ -214,7 +217,7 @@ public static function create_browser_playground_session( array $input ): array|
214217
$task_payload = array();
215218
$recipe = array();
216219
$materialization = array();
217-
$recipe_blueprint = self::browser_playground_blueprint( self::browser_selected_prepared_runtime_blueprint( $prepared_runtime, $blueprint ), $playground );
220+
$recipe_blueprint = self::browser_playground_blueprint( $blueprint, $playground );
218221
if ( ! $preview_only ) {
219222
$task_payload = self::browser_task_payload( $input, $task_input, $session_id, $artifacts, $inheritance_payload['inheritance'], $dependency_plan );
220223
$recipe = self::browser_agent_recipe( $task_input, $session_id, $browser_runner, $blueprint, $playground, $task_payload );
@@ -225,7 +228,7 @@ public static function create_browser_playground_session( array $input ): array|
225228
$materialization = self::browser_materialization_contract( $recipe );
226229
}
227230
if ( is_array( $runtime['prepared_runtime'] ?? null ) ) {
228-
self::browser_prepared_runtime_cache_store( $runtime['prepared_runtime'], $recipe_blueprint );
231+
self::browser_prepared_runtime_cache_store( $runtime['prepared_runtime'], $runtime_blueprint );
229232
}
230233
$blueprint = $recipe_blueprint;
231234

scripts/php-browser-preview-only-session-smoke.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ function has_runner_step( array $steps ): bool {
131131
'steps' => array( array( 'step' => 'setSiteOptions', 'options' => array( 'description' => 'Site artifact applied' ) ) ),
132132
),
133133
),
134+
'post_runtime_blueprint' => array(
135+
'steps' => array( array( 'step' => 'importWxr', 'file' => 'https://example.test/first-caller.xml' ) ),
136+
),
134137
'include_internal_browser_session' => true,
135138
);
136139

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

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

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

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

0 commit comments

Comments
 (0)