@@ -5,20 +5,32 @@ import fs from "node:fs";
55
66import {
77 assertOpenShellGatewayPortBinding ,
8+ inspectOpenShellSandboxPolicyReadiness ,
89 inspectSandboxPolicy ,
910 PolicyObservationError ,
1011} from "../../adapters/openshell/policy-state" ;
1112import { assertPolicyRequirementContainment , parseOpenShellPolicy } from "../../policy/merge" ;
1213
14+ const POLICY_READINESS_MAX_OBSERVATIONS = 5 ;
15+ const POLICY_READINESS_POLL_INTERVAL_MS = 1_000 ;
16+
17+ function sleepForPolicyConvergence ( milliseconds : number ) : void {
18+ Atomics . wait ( new Int32Array ( new SharedArrayBuffer ( 4 ) ) , 0 , 0 , milliseconds ) ;
19+ }
20+
1321export interface LiveCreatedSandboxPolicyRequirementsInput {
1422 readonly sandboxName : string ;
1523 readonly gatewayName : string ;
1624 readonly gatewayPort : number ;
25+ readonly lifecycleLiveIdentityFingerprint : string ;
1726 readonly policySourcePath : string ;
1827}
1928
2029export interface LiveCreatedSandboxPolicyRequirementsDeps {
21- readonly readFile ?: typeof fs . readFileSync ;
30+ readonly readFile ?: ( path : string , encoding : "utf8" ) => string ;
31+ readonly inspectPolicy ?: typeof inspectSandboxPolicy ;
32+ readonly inspectPolicyReadiness ?: typeof inspectOpenShellSandboxPolicyReadiness ;
33+ readonly sleep ?: ( milliseconds : number ) => void ;
2234}
2335
2436export interface LiveCreatedSandboxPolicyRequirementsCheck extends LiveCreatedSandboxPolicyRequirementsInput {
@@ -34,10 +46,6 @@ export function verifyLiveCreatedSandboxPolicyRequirements(
3446 gatewayName : input . gatewayName ,
3547 gatewayPort : input . gatewayPort ,
3648 } ) ;
37- const inspection = inspectSandboxPolicy ( {
38- sandboxName : input . sandboxName ,
39- gatewayName : input . gatewayName ,
40- } ) ;
4149 let requiredPolicy : ReturnType < typeof parseOpenShellPolicy > [ "policy" ] ;
4250 try {
4351 requiredPolicy = parseOpenShellPolicy (
@@ -48,10 +56,61 @@ export function verifyLiveCreatedSandboxPolicyRequirements(
4856 `Refusing to ${ input . operation } : the required sandbox policy could not be read.` ,
4957 ) ;
5058 }
51- try {
52- assertPolicyRequirementContainment ( inspection , requiredPolicy ) ;
53- } catch ( error ) {
54- const detail = error instanceof Error ? error . message : String ( error ) ;
55- throw new PolicyObservationError ( `Refusing to ${ input . operation } : ${ detail } .` ) ;
59+ const inspectPolicy = deps . inspectPolicy ?? inspectSandboxPolicy ;
60+ const inspectPolicyReadiness =
61+ deps . inspectPolicyReadiness ?? inspectOpenShellSandboxPolicyReadiness ;
62+ let lastFailure = "the exact sandbox policy did not converge" ;
63+ let ready = false ;
64+ for ( let attempt = 0 ; attempt < POLICY_READINESS_MAX_OBSERVATIONS ; attempt += 1 ) {
65+ ready = ( ( ) => {
66+ const before = inspectPolicy ( {
67+ sandboxName : input . sandboxName ,
68+ gatewayName : input . gatewayName ,
69+ } ) ;
70+ try {
71+ assertPolicyRequirementContainment ( before , requiredPolicy ) ;
72+ } catch ( error ) {
73+ lastFailure = error instanceof Error ? error . message : String ( error ) ;
74+ return false ;
75+ }
76+ const readiness = inspectPolicyReadiness ( {
77+ sandboxName : input . sandboxName ,
78+ gatewayName : input . gatewayName ,
79+ sandboxIdentityFingerprint : input . lifecycleLiveIdentityFingerprint ,
80+ policyVersion : before . policyIdentity . activeVersion ,
81+ } ) ;
82+ if ( readiness . state !== "ready" ) {
83+ lastFailure =
84+ readiness . reason === "sandbox-not-ready"
85+ ? "the exact sandbox is not Ready"
86+ : "the observed policy version is not active" ;
87+ return false ;
88+ }
89+ const after = inspectPolicy ( {
90+ sandboxName : input . sandboxName ,
91+ gatewayName : input . gatewayName ,
92+ } ) ;
93+ if (
94+ after . policyIdentity . hash !== before . policyIdentity . hash ||
95+ after . policyIdentity . activeVersion !== before . policyIdentity . activeVersion
96+ ) {
97+ lastFailure = "the live OpenShell policy changed during verification" ;
98+ return false ;
99+ }
100+ try {
101+ assertPolicyRequirementContainment ( after , requiredPolicy ) ;
102+ return true ;
103+ } catch ( error ) {
104+ lastFailure = error instanceof Error ? error . message : String ( error ) ;
105+ return false ;
106+ }
107+ } ) ( ) ;
108+ if ( ready ) break ;
109+ if ( attempt + 1 < POLICY_READINESS_MAX_OBSERVATIONS ) {
110+ ( deps . sleep ?? sleepForPolicyConvergence ) ( POLICY_READINESS_POLL_INTERVAL_MS ) ;
111+ }
112+ }
113+ if ( ! ready ) {
114+ throw new PolicyObservationError ( `Refusing to ${ input . operation } : ${ lastFailure } .` ) ;
56115 }
57116}
0 commit comments