Skip to content

Commit fa1a5cf

Browse files
authored
Consolidate preview boot contract (#1743) (#1744)
1 parent 40f066f commit fa1a5cf

11 files changed

Lines changed: 185 additions & 201 deletions

docs/browser-contained-site-facade.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The contained-site facade is the product-facing browser lane for WordPress previ
44

55
## Abilities
66

7-
- `wp-codebox/create-browser-contained-site-session` creates a browser-contained WordPress site session and returns `contained_site`, `boot`, `preview_lease`, `session`, and `startup_diagnostics`.
7+
- `wp-codebox/create-browser-contained-site-session` creates a browser-contained WordPress site session and returns `contained_site`, `preview_boot`, `preview_lease`, `session`, and `startup_diagnostics`.
88
- `wp-codebox/open-browser-contained-site` opens a stored contained-site handle when a prepared runtime is recoverable.
99
- `wp-codebox/boot-browser-contained-site-session` returns a product-safe boot descriptor for an existing contained-site handle.
1010
- `wp-codebox/destroy-browser-contained-site-session` releases the preview lease and returns terminal diagnostics.
@@ -17,7 +17,7 @@ The contained-site facade is the product-facing browser lane for WordPress previ
1717

1818
## Boundary
1919

20-
Consumers use `boot` as the public descriptor. It exposes Codebox session identity, preview lease, contained-site handle, and a blueprint ref hydrator. Playground fields such as `clientModuleUrl`, `remoteUrl`, runtime `scope`, and inline `blueprint` data are diagnostics for the runtime adapter; consumer boot inputs are the Codebox descriptor fields.
20+
Consumers use `preview_boot` as the public descriptor. It exposes Codebox session identity, preview lease, contained-site handle, and a blueprint ref hydrator. Playground fields such as `clientModuleUrl`, `remoteUrl`, runtime `scope`, and inline `blueprint` data stay out of the product-facing contained-site facade; product consumers hydrate through the Codebox blueprint ref endpoint.
2121

2222
## Diagnostics
2323

packages/wordpress-plugin/assets/browser-runtime.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,11 +620,11 @@
620620
const browserPreviewBootConfig = ( input ) => {
621621
const boot = input?.schema === 'wp-codebox/browser-preview-boot-config/v1'
622622
? input
623-
: ( input?.preview_boot && typeof input.preview_boot === 'object' ? input.preview_boot : ( input?.boot && typeof input.boot === 'object' ? input.boot : null ) );
623+
: ( input?.preview_boot && typeof input.preview_boot === 'object' ? input.preview_boot : null );
624624
if ( ! boot || typeof boot !== 'object' ) {
625625
throw runtimeError( 'browser_preview_start', 'browser_preview_boot_missing', 'A WP Codebox browser preview boot config is required.' );
626626
}
627-
if ( boot.schema && boot.schema !== 'wp-codebox/browser-preview-boot-config/v1' && boot.schema !== 'wp-codebox/browser-contained-site-boot/v1' ) {
627+
if ( boot.schema && boot.schema !== 'wp-codebox/browser-preview-boot-config/v1' ) {
628628
throw runtimeError( 'browser_preview_start', 'browser_preview_boot_schema_unsupported', `Unsupported WP Codebox browser preview boot schema: ${ boot.schema }` );
629629
}
630630

@@ -643,7 +643,7 @@
643643
? boot.blueprint_ref_dto
644644
: ( boot.blueprint_ref && typeof boot.blueprint_ref === 'object' ? boot.blueprint_ref : { ref: boot.blueprint_ref || '' } );
645645
const ref = String( blueprintRef.ref || blueprintRef.id || boot.blueprint_ref || '' );
646-
if ( ! ref || ref === 'inline-session-blueprint' ) {
646+
if ( ! ref ) {
647647
throw runtimeError( 'browser_preview_start', 'browser_preview_blueprint_ref_missing', 'Browser preview boot config is missing a hydratable Codebox blueprint ref.' );
648648
}
649649

packages/wordpress-plugin/src/class-wp-codebox-browser-contained-site-service.php

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,17 @@ public function get_browser_contained_site_status( array $input ): array|WP_Erro
3333
$cache_key = $this->safe_key( (string) ( $input['cache_key'] ?? $recovery['cache_key'] ?? $prepared['cache_key'] ?? $input['site_id'] ?? $contained_site['site_id'] ?? '' ) );
3434
$input_hash = strtolower( trim( (string) ( $input['input_hash'] ?? $recovery['input_hash'] ?? $prepared['input_hash'] ?? $source_digest ) ) );
3535
if ( '' === $cache_key || ! preg_match( '/^[a-f0-9]{64}$/', $input_hash ) ) {
36-
return new WP_Error( 'wp_codebox_browser_contained_site_ref_invalid', 'Browser contained site status requires cache_key/site_id and a 64-character source digest.', array( 'status' => 400 ) );
36+
return new WP_Error(
37+
'wp_codebox_browser_contained_site_ref_missing',
38+
'Browser contained site status requires a hydratable prepared runtime ref. Use prepare-new to create a preview session before opening.',
39+
array(
40+
'status' => 400,
41+
'schema' => 'wp-codebox/browser-contained-site-status/v1',
42+
'preview_state' => 'hydratable-ref-missing',
43+
'reason' => 'hydratable-ref-missing',
44+
'prepare_new_required' => true,
45+
)
46+
);
3747
}
3848

3949
$prepared_ref = array(
@@ -55,9 +65,9 @@ public function preview_reuse_decision( array $input ): array|WP_Error {
5565
'reuse_current' => 'reuse-current',
5666
'reuse_live', 'reuse_materialized', 'reuse_prepared_runtime' => 'hydrate-ref',
5767
'unavailable' => 'reload-required',
58-
default => 'create-new',
68+
default => 'prepare-new',
5969
};
60-
$reload_required = in_array( $action, array( 'create-new', 'reload-required' ), true );
70+
$reload_required = in_array( $action, array( 'prepare-new', 'reload-required' ), true );
6171
$identity_key = hash( 'sha256', implode( ':', array( (string) ( $status['site_id'] ?? '' ), (string) ( $status['source_digest']['value'] ?? '' ), $open_mode ) ) );
6272

6373
return array_filter(
@@ -72,6 +82,8 @@ public function preview_reuse_decision( array $input ): array|WP_Error {
7282
'open_mode' => $open_mode,
7383
'reuse_level' => (string) ( $status['reuse_level'] ?? 'none' ),
7484
'requires_materialization' => true === ( $status['requires_materialization'] ?? false ),
85+
'prepare_new_required' => true === ( $status['prepare_new_required'] ?? false ),
86+
'preview_state' => (string) ( $status['preview_state'] ?? '' ),
7587
'prepared_runtime_recoverable' => true === ( $status['prepared_runtime_recoverable'] ?? false ),
7688
'live' => true === ( $status['live'] ?? false ),
7789
'current' => true === ( $status['current'] ?? false ),
@@ -98,7 +110,7 @@ public function open_browser_contained_site( array $input ): array|WP_Error {
98110
$preview_boot = WP_Codebox_Browser_Task_Builder::preview_boot_config( $session );
99111
$preview_lease = WP_Codebox_Browser_Task_Builder::preview_lease( $session );
100112
$blueprint_ref = is_array( $status['blueprint_ref'] ?? null ) ? $status['blueprint_ref'] : array();
101-
$boot_contract = true === ( $status['success'] ?? false ) ? WP_Codebox_Browser_Task_Builder::validate_browser_preview_boot_contract( $preview_boot, $blueprint_ref ) : array( 'valid' => false, 'reason' => '' );
113+
$boot_contract = true === ( $status['success'] ?? false ) ? WP_Codebox_Browser_Task_Builder::validate_browser_preview_boot_contract( $preview_boot, $blueprint_ref ) : array( 'valid' => false, 'reason' => (string) ( $status['preview_state'] ?? '' ) );
102114
$site_id = (string) ( $status['site_id'] ?? $contained_site['site_id'] ?? $input['site_id'] ?? '' );
103115
$session_id = (string) ( $session['session']['id'] ?? '' );
104116
$preview_id = (string) ( $contained_site['preview_id'] ?? $input['preview_id'] ?? '' );
@@ -155,6 +167,8 @@ public function open_browser_contained_site( array $input ): array|WP_Error {
155167
'site_id' => $site_id,
156168
'status' => $open_status,
157169
'resolution' => $resolution,
170+
'preview_state' => (string) ( $resolution['reason'] ?? '' ),
171+
'prepare_new_required' => true === ( $lifecycle['requires_materialization'] ?? false ),
158172
'contained_site' => $opened_site,
159173
'source_digest' => is_array( $status['source_digest'] ?? null ) ? $status['source_digest'] : array(),
160174
'prepared_runtime' => is_array( $status['prepared_runtime'] ?? null ) ? $status['prepared_runtime'] : array(),
@@ -297,7 +311,7 @@ public function boot_browser_contained_site_session( array $input ): array|WP_Er
297311
'success' => true === ( $session['success'] ?? false ),
298312
'schema' => 'wp-codebox/browser-contained-site-boot-result/v1',
299313
'action' => (string) ( $session['action'] ?? '' ),
300-
'boot' => is_array( $session['boot'] ?? null ) ? $session['boot'] : array(),
314+
'preview_boot' => is_array( $session['preview_boot'] ?? null ) ? $session['preview_boot'] : array(),
301315
'preview_lease' => is_array( $session['preview_lease'] ?? null ) ? $session['preview_lease'] : array(),
302316
'contained_site' => is_array( $session['contained_site'] ?? null ) ? $session['contained_site'] : array(),
303317
'startup_diagnostics' => is_array( $session['startup_diagnostics'] ?? null ) ? $session['startup_diagnostics'] : array(),
@@ -312,7 +326,7 @@ public function preview_boot_ref( array $input ): array|WP_Error {
312326
return $boot_result;
313327
}
314328

315-
$boot = is_array( $boot_result['boot'] ?? null ) ? $boot_result['boot'] : array();
329+
$boot = is_array( $boot_result['preview_boot'] ?? null ) ? $boot_result['preview_boot'] : array();
316330
$contained_site = is_array( $boot_result['contained_site'] ?? null ) ? $boot_result['contained_site'] : array();
317331
$preview_lease = is_array( $boot_result['preview_lease'] ?? null ) ? $boot_result['preview_lease'] : array();
318332
$diagnostics = is_array( $boot_result['startup_diagnostics'] ?? null ) ? $boot_result['startup_diagnostics'] : array();
@@ -321,7 +335,7 @@ public function preview_boot_ref( array $input ): array|WP_Error {
321335

322336
$stable_boot = array_filter(
323337
array(
324-
'schema' => 'wp-codebox/browser-contained-site-boot/v1',
338+
'schema' => 'wp-codebox/browser-preview-boot-config/v1',
325339
'session_id' => (string) ( $boot['session_id'] ?? '' ),
326340
'site_id' => (string) ( $boot['site_id'] ?? '' ),
327341
'status' => (string) ( $boot['status'] ?? '' ),
@@ -335,19 +349,10 @@ public function preview_boot_ref( array $input ): array|WP_Error {
335349
array(
336350
'success' => true === ( $boot_result['success'] ?? false ),
337351
'schema' => 'wp-codebox/preview-boot-ref/v1',
338-
'boot' => $stable_boot,
352+
'preview_boot' => $stable_boot,
339353
'blueprint_ref' => $blueprint_ref,
340354
'preview_lease' => $preview_lease,
341355
'startup_diagnostics' => $diagnostics,
342-
'compatibility' => array_filter(
343-
array(
344-
'contained_site_schema' => (string) ( $contained_site['schema'] ?? '' ),
345-
'session_result_schema' => (string) ( $boot_result['schema'] ?? '' ),
346-
'legacy_contained_site' => $contained_site,
347-
'legacy_session_result' => $boot_result,
348-
),
349-
static fn( mixed $value ): bool => array() !== $value && '' !== $value
350-
),
351356
),
352357
static fn( mixed $value ): bool => array() !== $value && '' !== $value
353358
);
@@ -452,7 +457,7 @@ public function browser_session_response_for_input( array $session, array $input
452457
if ( false === ( $boot_contract['valid'] ?? false ) ) {
453458
return new WP_Error(
454459
'wp_codebox_browser_preview_boot_contract_invalid',
455-
'Browser preview session is missing a hydratable blueprint ref.',
460+
'Browser preview sessions require a hydratable blueprint ref. Use prepare-new to create a preview session before opening or hydrating it.',
456461
array(
457462
'status' => 500,
458463
'schema' => 'wp-codebox/browser-preview-boot-contract-error/v1',
@@ -490,7 +495,6 @@ public function browser_session_evidence_store( array $product_dto, array $sessi
490495
'created_at' => gmdate( 'c' ),
491496
'session_id' => $session_id,
492497
'preview_ref' => is_array( $product_dto['preview_ref'] ?? null ) ? $product_dto['preview_ref'] : array(),
493-
'preview_reference' => is_array( $product_dto['preview_reference'] ?? null ) ? $product_dto['preview_reference'] : array(),
494498
'preview_lease' => is_array( $product_dto['preview_lease'] ?? null ) ? $product_dto['preview_lease'] : array(),
495499
'preview_boot' => is_array( $product_dto['preview_boot'] ?? null ) ? $product_dto['preview_boot'] : array(),
496500
'blueprint_ref' => is_array( $product_dto['blueprint_ref'] ?? null ) ? $product_dto['blueprint_ref'] : array(),
@@ -610,6 +614,8 @@ public function browser_contained_site_status_envelope( string $cache_key, strin
610614
'algorithm' => 'sha256',
611615
'value' => $input_hash,
612616
),
617+
'preview_state' => (string) ( $resolution['reason'] ?? $status ),
618+
'prepare_new_required' => 'recoverable_prepared_runtime' !== $status,
613619
'prepared_runtime' => array_filter(
614620
array(
615621
'cache_key' => $cache_key,
@@ -649,7 +655,7 @@ public function browser_contained_site_lifecycle( string $status, array $resolut
649655
} elseif ( $materialized ) {
650656
$open_mode = 'reuse_materialized';
651657
$reuse_level = 'materialized';
652-
} elseif ( in_array( $status, array( 'disabled', 'incompatible', 'unusable' ), true ) ) {
658+
} elseif ( in_array( $status, array( 'disabled', 'incompatible', 'unusable', 'hydratable_ref_missing', 'input_hash_mismatch' ), true ) ) {
653659
$open_mode = 'unavailable';
654660
}
655661

@@ -701,6 +707,9 @@ public function browser_contained_site_status_from_lookup( array $lookup ): stri
701707
if ( 'hit' === $lookup_status ) {
702708
return 'recoverable_prepared_runtime';
703709
}
710+
if ( in_array( $lookup_status, array( 'expired_transient', 'cache_miss', 'input_hash_mismatch', 'hydratable_ref_missing' ), true ) ) {
711+
return $lookup_status;
712+
}
704713

705714
if ( ! empty( $lookup['invalidation'] ) ) {
706715
return 'incompatible';
@@ -715,6 +724,10 @@ public function browser_contained_site_resolution( string $status, array $lookup
715724
if ( '' === $reason ) {
716725
$reason = match ( $status ) {
717726
'recoverable_prepared_runtime' => 'prepared-runtime-cache-hit',
727+
'expired_transient' => 'expired-transient',
728+
'cache_miss' => 'cache-miss',
729+
'input_hash_mismatch' => 'input-hash-mismatch',
730+
'hydratable_ref_missing' => 'hydratable-ref-missing',
718731
'incompatible' => 'prepared-runtime-incompatible',
719732
'disabled' => 'prepared-runtime-cache-disabled',
720733
default => 'prepared-runtime-not-found-or-expired',
@@ -734,8 +747,9 @@ public function browser_contained_site_resolution( string $status, array $lookup
734747
'reused' => false,
735748
'created' => false,
736749
'expired' => 'miss' === $status ? null : false,
737-
'miss' => 'miss' === $status,
738-
'incompatible' => 'incompatible' === $status,
750+
'miss' => in_array( $status, array( 'miss', 'cache_miss' ), true ),
751+
'incompatible' => in_array( $status, array( 'incompatible', 'input_hash_mismatch' ), true ),
752+
'prepare_new_required' => 'recoverable_prepared_runtime' !== $status,
739753
),
740754
static fn( mixed $value ): bool => null !== $value && array() !== $value && '' !== $value
741755
);
@@ -840,7 +854,7 @@ public function browser_contained_site_facade_session( array $result, string $ac
840854
'schema' => 'wp-codebox/browser-contained-site-session/v1',
841855
'action' => $action,
842856
'contained_site' => $contained_site,
843-
'boot' => $boot,
857+
'preview_boot' => $boot,
844858
'preview_lease' => $preview_lease,
845859
'startup_diagnostics' => $this->browser_contained_site_startup_diagnostics( $result, $contained_site, $preview_lease, $boot_contract ),
846860
'session' => is_array( $result['session'] ?? null ) ? $result['session'] : array(),
@@ -852,7 +866,7 @@ public function browser_contained_site_facade_session( array $result, string $ac
852866
public function browser_contained_site_boot_descriptor( array $result, array $contained_site, array $preview_boot, array $preview_lease, array $blueprint_ref ): array {
853867
return array_filter(
854868
array(
855-
'schema' => 'wp-codebox/browser-contained-site-boot/v1',
869+
'schema' => 'wp-codebox/browser-preview-boot-config/v1',
856870
'session_id' => (string) ( $result['session']['session_id'] ?? $result['session']['id'] ?? $preview_boot['session_id'] ?? '' ),
857871
'site_id' => (string) ( $result['site_id'] ?? $contained_site['site_id'] ?? '' ),
858872
'status' => (string) ( $result['status'] ?? $contained_site['status'] ?? '' ),

0 commit comments

Comments
 (0)