Skip to content

Commit dca900c

Browse files
authored
Merge pull request #1235 from Automattic/cook/browser-contained-site-resolution-20260619
Clarify browser contained site reuse contract
2 parents 2b3d6e4 + 936d3ed commit dca900c

6 files changed

Lines changed: 323 additions & 37 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"test:php-managed-host-command": "tsx tests/php-managed-host-command.test.ts",
9898
"test:php-tool-policy-normalization": "php scripts/php-tool-policy-normalization-smoke.php",
9999
"test:php-browser-callback-contracts": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-callback-contracts-smoke.php",
100+
"test:php-browser-contained-site-contract": "php scripts/php-browser-contained-site-contract-smoke.php",
100101
"test:php-browser-runtime-local-package": "php scripts/php-browser-runtime-local-package-smoke.php",
101102
"test:mcp-client-configs": "tsx tests/mcp-client-configs.test.ts",
102103
"test:runtime-tool-policy": "tsx tests/runtime-tool-policy.test.ts",

packages/wordpress-plugin/src/class-wp-codebox-browser-ability-descriptors.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,22 @@ public static function descriptors( array $context ): array {
140140
'success' => array( 'type' => 'boolean' ),
141141
'schema' => array( 'type' => 'string', 'const' => 'wp-codebox/browser-contained-site-status/v1' ),
142142
'site_id' => array( 'type' => 'string' ),
143+
'open_mode' => array( 'type' => 'string' ),
144+
'reuse_level' => array( 'type' => 'string' ),
145+
'requires_materialization' => array( 'type' => 'boolean' ),
146+
'prepared_runtime_recoverable' => array( 'type' => 'boolean' ),
147+
'live' => array( 'type' => 'boolean' ),
148+
'current' => array( 'type' => 'boolean' ),
149+
'materialized' => array( 'type' => 'boolean' ),
143150
'source_digest' => array( 'type' => 'object' ),
151+
'artifact_digest' => array( 'type' => 'object' ),
152+
'materialization_digest' => array( 'type' => 'object' ),
144153
'status' => array( 'type' => 'string' ),
145154
'resolution' => array( 'type' => 'object' ),
146155
'prepared_runtime' => array( 'type' => 'object' ),
147156
'blueprint_ref' => array( 'type' => 'object' ),
157+
'recovery' => array( 'type' => 'object' ),
158+
'recovery_handle' => array( 'type' => 'string' ),
148159
),
149160
),
150161
'execute_callback' => array( WP_Codebox_Abilities::class, 'get_browser_contained_site_status' ),
@@ -176,17 +187,27 @@ public static function descriptors( array $context ): array {
176187
'success' => array( 'type' => 'boolean' ),
177188
'schema' => array( 'type' => 'string', 'const' => 'wp-codebox/browser-contained-site-open/v1' ),
178189
'site_id' => array( 'type' => 'string' ),
190+
'open_mode' => array( 'type' => 'string' ),
191+
'reuse_level' => array( 'type' => 'string' ),
192+
'requires_materialization' => array( 'type' => 'boolean' ),
193+
'prepared_runtime_recoverable' => array( 'type' => 'boolean' ),
194+
'live' => array( 'type' => 'boolean' ),
195+
'current' => array( 'type' => 'boolean' ),
196+
'materialized' => array( 'type' => 'boolean' ),
179197
'status' => array( 'type' => 'string' ),
180198
'resolution' => array( 'type' => 'object' ),
181199
'contained_site' => $context['browser_contained_site_schema'],
182200
'source_digest' => array( 'type' => 'object' ),
201+
'artifact_digest' => array( 'type' => 'object' ),
202+
'materialization_digest' => array( 'type' => 'object' ),
183203
'prepared_runtime' => array( 'type' => 'object' ),
184204
'blueprint_ref' => array( 'type' => 'object' ),
185205
'preview_boot' => array( 'type' => 'object' ),
186206
'preview_lease' => array( 'type' => 'object' ),
187207
'preview_session' => array( 'type' => 'object' ),
188208
'session' => array( 'type' => 'object' ),
189209
'recovery' => array( 'type' => 'object' ),
210+
'recovery_handle' => array( 'type' => 'string' ),
190211
),
191212
),
192213
'execute_callback' => array( WP_Codebox_Abilities::class, 'open_browser_contained_site' ),

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

Lines changed: 132 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -188,15 +188,22 @@ public static function open_browser_contained_site( array $input ): array|WP_Err
188188
$resolution = is_array( $status['resolution'] ?? null ) ? $status['resolution'] : array();
189189
$open_status = (string) ( $status['status'] ?? 'miss' );
190190
$open_success = true === ( $status['success'] ?? false );
191+
$lifecycle = self::browser_contained_site_lifecycle( $open_status, $resolution );
191192
if ( $open_success && true !== ( $boot_contract['valid'] ?? false ) ) {
192193
$open_success = false;
193194
$open_status = 'unusable';
194195
$resolution = self::browser_contained_site_resolution( $open_status, array( 'invalidation' => array( 'reason' => (string) ( $boot_contract['reason'] ?? 'preview-boot-contract-unusable' ) ) ) );
196+
$lifecycle = self::browser_contained_site_lifecycle( $open_status, $resolution );
195197
}
198+
$recovery = self::browser_contained_site_open_recovery( $site_id, (string) ( $status['source_digest']['value'] ?? '' ) );
199+
$recovery_handle = self::browser_contained_site_recovery_handle( $site_id, (string) ( $status['source_digest']['value'] ?? '' ) );
200+
$digest_refs = self::browser_contained_site_digest_refs( $status );
196201

197202
$opened_site = array_filter(
198203
array_merge(
199204
$contained_site,
205+
$lifecycle,
206+
$digest_refs,
200207
array(
201208
'schema' => 'wp-codebox/browser-contained-site/v1',
202209
'site_id' => $site_id,
@@ -211,7 +218,8 @@ public static function open_browser_contained_site( array $input ): array|WP_Err
211218
'preview_boot' => $preview_boot,
212219
'preview_lease' => $preview_lease,
213220
'session' => self::browser_contained_site_session_identity( $session_id, $preview_id, $scope ),
214-
'recovery' => self::browser_contained_site_open_recovery( $site_id, (string) ( $status['source_digest']['value'] ?? '' ) ),
221+
'recovery' => $recovery,
222+
'recovery_handle' => $recovery_handle,
215223
)
216224
),
217225
static fn( mixed $value ): bool => array() !== $value && '' !== $value
@@ -220,21 +228,26 @@ public static function open_browser_contained_site( array $input ): array|WP_Err
220228
$preview_session = WP_Codebox_Browser_Task_Builder::product_browser_session_dto( $session );
221229

222230
return array_filter(
223-
array(
224-
'success' => $open_success,
225-
'schema' => 'wp-codebox/browser-contained-site-open/v1',
226-
'site_id' => $site_id,
227-
'status' => $open_status,
228-
'resolution' => $resolution,
229-
'contained_site' => $opened_site,
230-
'source_digest' => is_array( $status['source_digest'] ?? null ) ? $status['source_digest'] : array(),
231-
'prepared_runtime' => is_array( $status['prepared_runtime'] ?? null ) ? $status['prepared_runtime'] : array(),
232-
'blueprint_ref' => $blueprint_ref,
233-
'preview_boot' => $preview_boot,
234-
'preview_lease' => $preview_lease,
235-
'preview_session' => $preview_session,
236-
'session' => self::browser_contained_site_session_identity( $session_id, $preview_id, $scope ),
237-
'recovery' => self::browser_contained_site_open_recovery( $site_id, (string) ( $status['source_digest']['value'] ?? '' ) ),
231+
array_merge(
232+
$lifecycle,
233+
$digest_refs,
234+
array(
235+
'success' => $open_success,
236+
'schema' => 'wp-codebox/browser-contained-site-open/v1',
237+
'site_id' => $site_id,
238+
'status' => $open_status,
239+
'resolution' => $resolution,
240+
'contained_site' => $opened_site,
241+
'source_digest' => is_array( $status['source_digest'] ?? null ) ? $status['source_digest'] : array(),
242+
'prepared_runtime' => is_array( $status['prepared_runtime'] ?? null ) ? $status['prepared_runtime'] : array(),
243+
'blueprint_ref' => $blueprint_ref,
244+
'preview_boot' => $preview_boot,
245+
'preview_lease' => $preview_lease,
246+
'preview_session' => $preview_session,
247+
'session' => self::browser_contained_site_session_identity( $session_id, $preview_id, $scope ),
248+
'recovery' => $recovery,
249+
'recovery_handle' => $recovery_handle,
250+
)
238251
),
239252
static fn( mixed $value ): bool => array() !== $value && '' !== $value
240253
);
@@ -1065,36 +1078,114 @@ private static function browser_contained_site_artifact_meta( array $input ): ar
10651078
/** @return array<string,mixed> */
10661079
private static function browser_contained_site_status_envelope( string $cache_key, string $input_hash, array $lookup ): array {
10671080
$artifact = is_array( $lookup['artifact'] ?? null ) ? $lookup['artifact'] : array();
1068-
$status = self::browser_contained_site_status_from_lookup( $lookup );
1081+
$status = self::browser_contained_site_status_from_lookup( $lookup );
10691082
$resolution = self::browser_contained_site_resolution( $status, $lookup );
1083+
$lifecycle = self::browser_contained_site_lifecycle( $status, $resolution );
1084+
$digest_refs = self::browser_contained_site_digest_refs( array_merge( $lookup, array( 'source_digest' => array( 'algorithm' => 'sha256', 'value' => $input_hash ) ) ) );
10701085

10711086
return array_filter(
1072-
array(
1073-
'success' => 'recoverable_prepared_runtime' === $status,
1074-
'schema' => 'wp-codebox/browser-contained-site-status/v1',
1075-
'site_id' => $cache_key,
1076-
'status' => $status,
1077-
'resolution' => $resolution,
1078-
'source_digest' => array(
1079-
'algorithm' => 'sha256',
1080-
'value' => $input_hash,
1081-
),
1082-
'prepared_runtime' => array_filter(
1083-
array(
1084-
'cache_key' => $cache_key,
1085-
'input_hash' => $input_hash,
1086-
'status' => (string) ( $lookup['status'] ?? '' ),
1087-
'reason' => (string) ( $resolution['reason'] ?? '' ),
1088-
'created_at' => (string) ( $artifact['created_at'] ?? '' ),
1087+
array_merge(
1088+
$lifecycle,
1089+
$digest_refs,
1090+
array(
1091+
'success' => 'recoverable_prepared_runtime' === $status,
1092+
'schema' => 'wp-codebox/browser-contained-site-status/v1',
1093+
'site_id' => $cache_key,
1094+
'status' => $status,
1095+
'resolution' => $resolution,
1096+
'source_digest' => array(
1097+
'algorithm' => 'sha256',
1098+
'value' => $input_hash,
10891099
),
1090-
static fn( string $value ): bool => '' !== $value
1091-
),
1092-
'blueprint_ref' => 'recoverable_prepared_runtime' === $status ? WP_Codebox_Browser_Task_Builder::browser_blueprint_ref( array( 'cache_key' => $cache_key, 'input_hash' => $input_hash, 'status' => 'recoverable_prepared_runtime' ) ) : array(),
1100+
'prepared_runtime' => array_filter(
1101+
array(
1102+
'cache_key' => $cache_key,
1103+
'input_hash' => $input_hash,
1104+
'status' => (string) ( $lookup['status'] ?? '' ),
1105+
'reason' => (string) ( $resolution['reason'] ?? '' ),
1106+
'created_at' => (string) ( $artifact['created_at'] ?? '' ),
1107+
),
1108+
static fn( string $value ): bool => '' !== $value
1109+
),
1110+
'blueprint_ref' => 'recoverable_prepared_runtime' === $status ? WP_Codebox_Browser_Task_Builder::browser_blueprint_ref( array( 'cache_key' => $cache_key, 'input_hash' => $input_hash, 'status' => 'recoverable_prepared_runtime' ) ) : array(),
1111+
'recovery' => self::browser_contained_site_open_recovery( $cache_key, $input_hash ),
1112+
'recovery_handle' => self::browser_contained_site_recovery_handle( $cache_key, $input_hash ),
1113+
)
10931114
),
10941115
static fn( mixed $value ): bool => array() !== $value && '' !== $value
10951116
);
10961117
}
10971118

1119+
/** @return array<string,mixed> */
1120+
private static function browser_contained_site_lifecycle( string $status, array $resolution ): array {
1121+
$prepared_runtime_recoverable = true === ( $resolution['prepared_runtime_recoverable'] ?? false );
1122+
$live = true === ( $resolution['live'] ?? false );
1123+
$current = true === ( $resolution['current'] ?? false );
1124+
$materialized = true === ( $resolution['materialized'] ?? false );
1125+
$open_mode = 'materialize';
1126+
$reuse_level = 'none';
1127+
1128+
if ( $current ) {
1129+
$open_mode = 'reuse_current';
1130+
$reuse_level = 'current';
1131+
} elseif ( $live ) {
1132+
$open_mode = 'reuse_live';
1133+
$reuse_level = 'live';
1134+
} elseif ( $prepared_runtime_recoverable ) {
1135+
$open_mode = 'reuse_prepared_runtime';
1136+
$reuse_level = 'prepared_runtime';
1137+
} elseif ( $materialized ) {
1138+
$open_mode = 'reuse_materialized';
1139+
$reuse_level = 'materialized';
1140+
} elseif ( in_array( $status, array( 'disabled', 'incompatible', 'unusable' ), true ) ) {
1141+
$open_mode = 'unavailable';
1142+
}
1143+
1144+
return array(
1145+
'open_mode' => $open_mode,
1146+
'reuse_level' => $reuse_level,
1147+
'requires_materialization' => ! ( $prepared_runtime_recoverable || $live || $current || $materialized ),
1148+
'prepared_runtime_recoverable' => $prepared_runtime_recoverable,
1149+
'live' => $live,
1150+
'current' => $current,
1151+
'materialized' => $materialized,
1152+
);
1153+
}
1154+
1155+
/** @return array<string,mixed> */
1156+
private static function browser_contained_site_digest_refs( array $input ): array {
1157+
$artifact = is_array( $input['artifact'] ?? null ) ? $input['artifact'] : array();
1158+
1159+
return array_filter(
1160+
array(
1161+
'source_digest' => self::browser_contained_site_digest_ref( $input['source_digest'] ?? $artifact['source_digest'] ?? $input['input_hash'] ?? '' ),
1162+
'artifact_digest' => self::browser_contained_site_digest_ref( $artifact['artifact_digest'] ?? $input['artifact_digest'] ?? $artifact['digest'] ?? $artifact['sha256'] ?? '' ),
1163+
'materialization_digest' => self::browser_contained_site_digest_ref( $artifact['materialization_digest'] ?? $input['materialization_digest'] ?? '' ),
1164+
),
1165+
static fn( mixed $value ): bool => array() !== $value
1166+
);
1167+
}
1168+
1169+
/** @return array{algorithm:string,value:string}|array{} */
1170+
private static function browser_contained_site_digest_ref( mixed $value ): array {
1171+
if ( is_array( $value ) ) {
1172+
$digest = strtolower( trim( (string) ( $value['value'] ?? $value['sha256'] ?? $value['hash'] ?? '' ) ) );
1173+
$algorithm = (string) ( $value['algorithm'] ?? 'sha256' );
1174+
} else {
1175+
$digest = strtolower( trim( (string) $value ) );
1176+
$algorithm = 'sha256';
1177+
}
1178+
1179+
if ( ! preg_match( '/^[a-f0-9]{64}$/', $digest ) ) {
1180+
return array();
1181+
}
1182+
1183+
return array(
1184+
'algorithm' => $algorithm,
1185+
'value' => $digest,
1186+
);
1187+
}
1188+
10981189
/** @return string */
10991190
private static function browser_contained_site_status_from_lookup( array $lookup ): string {
11001191
$lookup_status = (string) ( $lookup['status'] ?? 'miss' );
@@ -1247,6 +1338,10 @@ private static function browser_contained_site_open_recovery( string $site_id, s
12471338
);
12481339
}
12491340

1341+
private static function browser_contained_site_recovery_handle( string $site_id, string $source_digest ): string {
1342+
return '' !== $site_id && preg_match( '/^[a-f0-9]{64}$/', $source_digest ) ? 'browser-contained-site:' . $site_id . ':' . $source_digest : '';
1343+
}
1344+
12501345
private static function browser_contained_site_source_digest( array $input, array $playground, array $runtime, array $prepared_runtime ): string {
12511346
$input_hash = strtolower( trim( (string) ( $prepared_runtime['input_hash'] ?? '' ) ) );
12521347
if ( preg_match( '/^[a-f0-9]{64}$/', $input_hash ) ) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,12 +447,22 @@ private static function browser_contained_site_schema(): array {
447447
'session_id' => array( 'type' => 'string' ),
448448
'caller_id' => array( 'type' => 'string' ),
449449
'status' => array( 'type' => 'string', 'enum' => array( 'ready', 'blocked', 'recoverable_prepared_runtime', 'current', 'live', 'materialized', 'miss', 'disabled', 'incompatible', 'unusable' ) ),
450+
'open_mode' => array( 'type' => 'string', 'enum' => array( 'reuse_current', 'reuse_live', 'reuse_materialized', 'reuse_prepared_runtime', 'materialize', 'unavailable' ) ),
451+
'reuse_level' => array( 'type' => 'string', 'enum' => array( 'current', 'live', 'materialized', 'prepared_runtime', 'none' ) ),
452+
'requires_materialization' => array( 'type' => 'boolean' ),
453+
'prepared_runtime_recoverable' => array( 'type' => 'boolean' ),
454+
'live' => array( 'type' => 'boolean' ),
455+
'current' => array( 'type' => 'boolean' ),
456+
'materialized' => array( 'type' => 'boolean' ),
450457
'resolution' => array( 'type' => 'object' ),
451458
'persistence' => array( 'type' => 'string', 'enum' => array( 'browser-contained' ) ),
452459
'artifact_seed' => array( 'type' => 'string' ),
453460
'artifact_revision' => array( 'type' => 'string' ),
454461
'recovery' => array( 'type' => 'object' ),
462+
'recovery_handle' => array( 'type' => 'string' ),
455463
'source_digest' => array( 'type' => 'object' ),
464+
'artifact_digest' => array( 'type' => 'object' ),
465+
'materialization_digest' => array( 'type' => 'object' ),
456466
'preview' => array( 'type' => 'object' ),
457467
'prepared_runtime' => array( 'type' => 'object' ),
458468
'blueprint_ref' => array( 'type' => 'object' ),

0 commit comments

Comments
 (0)