@ionite/ecv 0.0.23-beta.20260729.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1035 -0
- package/dist/index.js +1 -0
- package/dist/runner.js +22 -0
- package/dist/shared/ecv-rcmx1a2s.js +15 -0
- package/package.json +44 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import{createRequire as $7}from"node:module";var n5=Object.create;var{getPrototypeOf:o5,defineProperty:ZQ,getOwnPropertyNames:a5}=Object;var r5=Object.prototype.hasOwnProperty;function t5(Z){return this[Z]}var s5,e5,e0=(Z,$,Q)=>{var X=Z!=null&&typeof Z==="object";if(X){var Y=$?s5??=new WeakMap:e5??=new WeakMap,K=Y.get(Z);if(K)return K}Q=Z!=null?n5(o5(Z)):{};let H=$||!Z||!Z.__esModule?ZQ(Q,"default",{value:Z,enumerable:!0}):Q;for(let J of a5(Z))if(!r5.call(H,J))ZQ(H,J,{get:t5.bind(Z,J),enumerable:!0});if(X)Y.set(Z,H);return H};var V0=(Z,$)=>()=>($||Z(($={exports:{}}).exports,$),$.exports);var r=$7(import.meta.url);var a0=V0((rK,dZ)=>{var uZ=["nodebuffer","arraybuffer","fragments"],lZ=typeof Blob<"u";if(lZ)uZ.push("blob");dZ.exports={BINARY_TYPES:uZ,CLOSE_TIMEOUT:30000,EMPTY_BUFFER:Buffer.alloc(0),GUID:"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",hasBlob:lZ,kForOnEventAttribute:Symbol("kIsForOnEventAttribute"),kListener:Symbol("kListener"),kStatusCode:Symbol("status-code"),kWebSocket:Symbol("websocket"),NOOP:()=>{}}});var E1=V0((tK,I$)=>{var{EMPTY_BUFFER:c3}=a0(),p4=Buffer[Symbol.species];function i3(Z,$){if(Z.length===0)return c3;if(Z.length===1)return Z[0];let Q=Buffer.allocUnsafe($),X=0;for(let Y=0;Y<Z.length;Y++){let K=Z[Y];Q.set(K,X),X+=K.length}if(X<$)return new p4(Q.buffer,Q.byteOffset,X);return Q}function cZ(Z,$,Q,X,Y){for(let K=0;K<Y;K++)Q[X+K]=Z[K]^$[K&3]}function iZ(Z,$){for(let Q=0;Q<Z.length;Q++)Z[Q]^=$[Q&3]}function n3(Z){if(Z.length===Z.buffer.byteLength)return Z.buffer;return Z.buffer.slice(Z.byteOffset,Z.byteOffset+Z.length)}function u4(Z){if(u4.readOnly=!0,Buffer.isBuffer(Z))return Z;let $;if(Z instanceof ArrayBuffer)$=new p4(Z);else if(ArrayBuffer.isView(Z))$=new p4(Z.buffer,Z.byteOffset,Z.byteLength);else $=Buffer.from(Z),u4.readOnly=!1;return $}I$.exports={concat:i3,mask:cZ,toArrayBuffer:n3,toBuffer:u4,unmask:iZ};if(!process.env.WS_NO_BUFFER_UTIL)try{let Z=(()=>{throw new Error("Cannot require module "+"bufferutil");})();I$.exports.mask=function($,Q,X,Y,K){if(K<48)cZ($,Q,X,Y,K);else Z.mask($,Q,X,Y,K)},I$.exports.unmask=function($,Q){if($.length<32)iZ($,Q);else Z.unmask($,Q)}}catch(Z){}});var rZ=V0((sK,aZ)=>{var nZ=Symbol("kDone"),l4=Symbol("kRun");class oZ{constructor(Z){this[nZ]=()=>{this.pending--,this[l4]()},this.concurrency=Z||1/0,this.jobs=[],this.pending=0}add(Z){this.jobs.push(Z),this[l4]()}[l4](){if(this.pending===this.concurrency)return;if(this.jobs.length){let Z=this.jobs.shift();this.pending++,Z(this[nZ])}}}aZ.exports=oZ});var Z1=V0((eK,Q5)=>{var S1=r("zlib"),tZ=E1(),o3=rZ(),{kStatusCode:sZ}=a0(),a3=Buffer[Symbol.species],r3=Buffer.from([0,0,255,255]),E$=Symbol("permessage-deflate"),r0=Symbol("total-length"),$1=Symbol("callback"),W8=Symbol("buffers"),Q1=Symbol("error"),C$;class eZ{constructor(Z){if(this._options=Z||{},this._threshold=this._options.threshold!==void 0?this._options.threshold:1024,this._maxPayload=this._options.maxPayload|0,this._isServer=!!this._options.isServer,this._deflate=null,this._inflate=null,this.params=null,!C$){let $=this._options.concurrencyLimit!==void 0?this._options.concurrencyLimit:10;C$=new o3($)}}static get extensionName(){return"permessage-deflate"}offer(){let Z={};if(this._options.serverNoContextTakeover)Z.server_no_context_takeover=!0;if(this._options.clientNoContextTakeover)Z.client_no_context_takeover=!0;if(this._options.serverMaxWindowBits)Z.server_max_window_bits=this._options.serverMaxWindowBits;if(this._options.clientMaxWindowBits)Z.client_max_window_bits=this._options.clientMaxWindowBits;else if(this._options.clientMaxWindowBits==null)Z.client_max_window_bits=!0;return Z}accept(Z){return Z=this.normalizeParams(Z),this.params=this._isServer?this.acceptAsServer(Z):this.acceptAsClient(Z),this.params}cleanup(){if(this._inflate)this._inflate.close(),this._inflate=null;if(this._deflate){let Z=this._deflate[$1];if(this._deflate.close(),this._deflate=null,Z)Z(Error("The deflate stream was closed while data was being processed"))}}acceptAsServer(Z){let $=this._options,Q=Z.find((X)=>{if($.serverNoContextTakeover===!1&&X.server_no_context_takeover||X.server_max_window_bits&&($.serverMaxWindowBits===!1||typeof $.serverMaxWindowBits==="number"&&$.serverMaxWindowBits>X.server_max_window_bits)||typeof $.clientMaxWindowBits==="number"&&!X.client_max_window_bits)return!1;return!0});if(!Q)throw Error("None of the extension offers can be accepted");if($.serverNoContextTakeover)Q.server_no_context_takeover=!0;if($.clientNoContextTakeover)Q.client_no_context_takeover=!0;if(typeof $.serverMaxWindowBits==="number")Q.server_max_window_bits=$.serverMaxWindowBits;if(typeof $.clientMaxWindowBits==="number")Q.client_max_window_bits=$.clientMaxWindowBits;else if(Q.client_max_window_bits===!0||$.clientMaxWindowBits===!1)delete Q.client_max_window_bits;return Q}acceptAsClient(Z){let $=Z[0];if(this._options.clientNoContextTakeover===!1&&$.client_no_context_takeover)throw Error('Unexpected parameter "client_no_context_takeover"');if(!$.client_max_window_bits){if(typeof this._options.clientMaxWindowBits==="number")$.client_max_window_bits=this._options.clientMaxWindowBits}else if(this._options.clientMaxWindowBits===!1||typeof this._options.clientMaxWindowBits==="number"&&$.client_max_window_bits>this._options.clientMaxWindowBits)throw Error('Unexpected or invalid parameter "client_max_window_bits"');return $}normalizeParams(Z){return Z.forEach(($)=>{Object.keys($).forEach((Q)=>{let X=$[Q];if(X.length>1)throw Error(`Parameter "${Q}" must have only a single value`);if(X=X[0],Q==="client_max_window_bits"){if(X!==!0){let Y=+X;if(!Number.isInteger(Y)||Y<8||Y>15)throw TypeError(`Invalid value for parameter "${Q}": ${X}`);X=Y}else if(!this._isServer)throw TypeError(`Invalid value for parameter "${Q}": ${X}`)}else if(Q==="server_max_window_bits"){let Y=+X;if(!Number.isInteger(Y)||Y<8||Y>15)throw TypeError(`Invalid value for parameter "${Q}": ${X}`);X=Y}else if(Q==="client_no_context_takeover"||Q==="server_no_context_takeover"){if(X!==!0)throw TypeError(`Invalid value for parameter "${Q}": ${X}`)}else throw Error(`Unknown parameter "${Q}"`);$[Q]=X})}),Z}decompress(Z,$,Q){C$.add((X)=>{this._decompress(Z,$,(Y,K)=>{X(),Q(Y,K)})})}compress(Z,$,Q){C$.add((X)=>{this._compress(Z,$,(Y,K)=>{X(),Q(Y,K)})})}_decompress(Z,$,Q){let X=this._isServer?"client":"server";if(!this._inflate){let Y=`${X}_max_window_bits`,K=typeof this.params[Y]!=="number"?S1.Z_DEFAULT_WINDOWBITS:this.params[Y];this._inflate=S1.createInflateRaw({...this._options.zlibInflateOptions,windowBits:K}),this._inflate[E$]=this,this._inflate[r0]=0,this._inflate[W8]=[],this._inflate.on("error",s3),this._inflate.on("data",$5)}if(this._inflate[$1]=Q,this._inflate.write(Z),$)this._inflate.write(r3);this._inflate.flush(()=>{let Y=this._inflate[Q1];if(Y){this._inflate.close(),this._inflate=null,Q(Y);return}let K=tZ.concat(this._inflate[W8],this._inflate[r0]);if(this._inflate._readableState.endEmitted)this._inflate.close(),this._inflate=null;else if(this._inflate[r0]=0,this._inflate[W8]=[],$&&this.params[`${X}_no_context_takeover`])this._inflate.reset();Q(null,K)})}_compress(Z,$,Q){let X=this._isServer?"server":"client";if(!this._deflate){let Y=`${X}_max_window_bits`,K=typeof this.params[Y]!=="number"?S1.Z_DEFAULT_WINDOWBITS:this.params[Y];this._deflate=S1.createDeflateRaw({...this._options.zlibDeflateOptions,windowBits:K}),this._deflate[r0]=0,this._deflate[W8]=[],this._deflate.on("data",t3)}this._deflate[$1]=Q,this._deflate.write(Z),this._deflate.flush(S1.Z_SYNC_FLUSH,()=>{if(!this._deflate)return;let Y=tZ.concat(this._deflate[W8],this._deflate[r0]);if($)Y=new a3(Y.buffer,Y.byteOffset,Y.length-4);if(this._deflate[$1]=null,this._deflate[r0]=0,this._deflate[W8]=[],$&&this.params[`${X}_no_context_takeover`])this._deflate.reset();Q(null,Y)})}}Q5.exports=eZ;function t3(Z){this[W8].push(Z),this[r0]+=Z.length}function $5(Z){if(this[r0]+=Z.length,this[E$]._maxPayload<1||this[r0]<=this[E$]._maxPayload){this[W8].push(Z);return}this[Q1]=RangeError("Max payload size exceeded"),this[Q1].code="WS_ERR_UNSUPPORTED_MESSAGE_LENGTH",this[Q1][sZ]=1009,this.removeListener("data",$5),this.reset()}function s3(Z){if(this[E$]._inflate=null,this[Q1]){this[$1](this[Q1]);return}Z[sZ]=1007,this[$1](Z)}});var X1=V0(($H,S$)=>{var{isUtf8:Z5}=r("buffer"),{hasBlob:e3}=a0(),$X=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0];function QX(Z){return Z>=1000&&Z<=1014&&Z!==1004&&Z!==1005&&Z!==1006||Z>=3000&&Z<=4999}function d4(Z){let $=Z.length,Q=0;while(Q<$)if((Z[Q]&128)===0)Q++;else if((Z[Q]&224)===192){if(Q+1===$||(Z[Q+1]&192)!==128||(Z[Q]&254)===192)return!1;Q+=2}else if((Z[Q]&240)===224){if(Q+2>=$||(Z[Q+1]&192)!==128||(Z[Q+2]&192)!==128||Z[Q]===224&&(Z[Q+1]&224)===128||Z[Q]===237&&(Z[Q+1]&224)===160)return!1;Q+=3}else if((Z[Q]&248)===240){if(Q+3>=$||(Z[Q+1]&192)!==128||(Z[Q+2]&192)!==128||(Z[Q+3]&192)!==128||Z[Q]===240&&(Z[Q+1]&240)===128||Z[Q]===244&&Z[Q+1]>143||Z[Q]>244)return!1;Q+=4}else return!1;return!0}function ZX(Z){return e3&&typeof Z==="object"&&typeof Z.arrayBuffer==="function"&&typeof Z.type==="string"&&typeof Z.stream==="function"&&(Z[Symbol.toStringTag]==="Blob"||Z[Symbol.toStringTag]==="File")}S$.exports={isBlob:ZX,isValidStatusCode:QX,isValidUTF8:d4,tokenChars:$X};if(Z5)S$.exports.isValidUTF8=function(Z){return Z.length<24?d4(Z):Z5(Z)};else if(!process.env.WS_NO_UTF_8_VALIDATE)try{let Z=(()=>{throw new Error("Cannot require module "+"utf-8-validate");})();S$.exports.isValidUTF8=function($){return $.length<32?d4($):Z($)}}catch(Z){}});var i4=V0((QH,z5)=>{var{Writable:XX}=r("stream"),X5=Z1(),{BINARY_TYPES:YX,EMPTY_BUFFER:Y5,kStatusCode:KX,kWebSocket:HX}=a0(),{concat:c4,toArrayBuffer:zX,unmask:GX}=E1(),{isValidStatusCode:JX,isValidUTF8:K5}=X1(),v$=Buffer[Symbol.species];class H5 extends XX{constructor(Z={}){super();this._allowSynchronousEvents=Z.allowSynchronousEvents!==void 0?Z.allowSynchronousEvents:!0,this._binaryType=Z.binaryType||YX[0],this._extensions=Z.extensions||{},this._isServer=!!Z.isServer,this._maxBufferedChunks=Z.maxBufferedChunks|0,this._maxFragments=Z.maxFragments|0,this._maxPayload=Z.maxPayload|0,this._skipUTF8Validation=!!Z.skipUTF8Validation,this[HX]=void 0,this._bufferedBytes=0,this._buffers=[],this._compressed=!1,this._payloadLength=0,this._mask=void 0,this._fragmented=0,this._masked=!1,this._fin=!1,this._opcode=0,this._totalPayloadLength=0,this._messageLength=0,this._fragments=[],this._errored=!1,this._loop=!1,this._state=0}_write(Z,$,Q){if(this._opcode===8&&this._state==0)return Q();if(this._maxBufferedChunks>0&&this._buffers.length>=this._maxBufferedChunks){Q(this.createError(RangeError,"Too many buffered chunks",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS"));return}this._bufferedBytes+=Z.length,this._buffers.push(Z),this.startLoop(Q)}consume(Z){if(this._bufferedBytes-=Z,Z===this._buffers[0].length)return this._buffers.shift();if(Z<this._buffers[0].length){let Q=this._buffers[0];return this._buffers[0]=new v$(Q.buffer,Q.byteOffset+Z,Q.length-Z),new v$(Q.buffer,Q.byteOffset,Z)}let $=Buffer.allocUnsafe(Z);do{let Q=this._buffers[0],X=$.length-Z;if(Z>=Q.length)$.set(this._buffers.shift(),X);else $.set(new Uint8Array(Q.buffer,Q.byteOffset,Z),X),this._buffers[0]=new v$(Q.buffer,Q.byteOffset+Z,Q.length-Z);Z-=Q.length}while(Z>0);return $}startLoop(Z){this._loop=!0;do switch(this._state){case 0:this.getInfo(Z);break;case 1:this.getPayloadLength16(Z);break;case 2:this.getPayloadLength64(Z);break;case 3:this.getMask();break;case 4:this.getData(Z);break;case 5:case 6:this._loop=!1;return}while(this._loop);if(!this._errored)Z()}getInfo(Z){if(this._bufferedBytes<2){this._loop=!1;return}let $=this.consume(2);if(($[0]&48)!==0){let X=this.createError(RangeError,"RSV2 and RSV3 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_2_3");Z(X);return}let Q=($[0]&64)===64;if(Q&&!this._extensions[X5.extensionName]){let X=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");Z(X);return}if(this._fin=($[0]&128)===128,this._opcode=$[0]&15,this._payloadLength=$[1]&127,this._opcode===0){if(Q){let X=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");Z(X);return}if(!this._fragmented){let X=this.createError(RangeError,"invalid opcode 0",!0,1002,"WS_ERR_INVALID_OPCODE");Z(X);return}this._opcode=this._fragmented}else if(this._opcode===1||this._opcode===2){if(this._fragmented){let X=this.createError(RangeError,`invalid opcode ${this._opcode}`,!0,1002,"WS_ERR_INVALID_OPCODE");Z(X);return}this._compressed=Q}else if(this._opcode>7&&this._opcode<11){if(!this._fin){let X=this.createError(RangeError,"FIN must be set",!0,1002,"WS_ERR_EXPECTED_FIN");Z(X);return}if(Q){let X=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");Z(X);return}if(this._payloadLength>125||this._opcode===8&&this._payloadLength===1){let X=this.createError(RangeError,`invalid payload length ${this._payloadLength}`,!0,1002,"WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH");Z(X);return}}else{let X=this.createError(RangeError,`invalid opcode ${this._opcode}`,!0,1002,"WS_ERR_INVALID_OPCODE");Z(X);return}if(!this._fin&&!this._fragmented)this._fragmented=this._opcode;if(this._masked=($[1]&128)===128,this._isServer){if(!this._masked){let X=this.createError(RangeError,"MASK must be set",!0,1002,"WS_ERR_EXPECTED_MASK");Z(X);return}}else if(this._masked){let X=this.createError(RangeError,"MASK must be clear",!0,1002,"WS_ERR_UNEXPECTED_MASK");Z(X);return}if(this._payloadLength===126)this._state=1;else if(this._payloadLength===127)this._state=2;else this.haveLength(Z)}getPayloadLength16(Z){if(this._bufferedBytes<2){this._loop=!1;return}this._payloadLength=this.consume(2).readUInt16BE(0),this.haveLength(Z)}getPayloadLength64(Z){if(this._bufferedBytes<8){this._loop=!1;return}let $=this.consume(8),Q=$.readUInt32BE(0);if(Q>Math.pow(2,21)-1){let X=this.createError(RangeError,"Unsupported WebSocket frame: payload length > 2^53 - 1",!1,1009,"WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH");Z(X);return}this._payloadLength=Q*Math.pow(2,32)+$.readUInt32BE(4),this.haveLength(Z)}haveLength(Z){if(this._payloadLength&&this._opcode<8){if(this._totalPayloadLength+=this._payloadLength,this._totalPayloadLength>this._maxPayload&&this._maxPayload>0){let $=this.createError(RangeError,"Max payload size exceeded",!1,1009,"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH");Z($);return}}if(this._masked)this._state=3;else this._state=4}getMask(){if(this._bufferedBytes<4){this._loop=!1;return}this._mask=this.consume(4),this._state=4}getData(Z){let $=Y5;if(this._payloadLength){if(this._bufferedBytes<this._payloadLength){this._loop=!1;return}if($=this.consume(this._payloadLength),this._masked&&(this._mask[0]|this._mask[1]|this._mask[2]|this._mask[3])!==0)GX($,this._mask)}if(this._opcode>7){this.controlMessage($,Z);return}if(this._compressed){this._state=5,this.decompress($,Z);return}if($.length){if(this._maxFragments>0&&this._fragments.length>=this._maxFragments){let Q=this.createError(RangeError,"Too many message fragments",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS");Z(Q);return}this._messageLength=this._totalPayloadLength,this._fragments.push($)}this.dataMessage(Z)}decompress(Z,$){this._extensions[X5.extensionName].decompress(Z,this._fin,(X,Y)=>{if(X)return $(X);if(Y.length){if(this._messageLength+=Y.length,this._messageLength>this._maxPayload&&this._maxPayload>0){let K=this.createError(RangeError,"Max payload size exceeded",!1,1009,"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH");$(K);return}if(this._maxFragments>0&&this._fragments.length>=this._maxFragments){let K=this.createError(RangeError,"Too many message fragments",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS");$(K);return}this._fragments.push(Y)}if(this.dataMessage($),this._state===0)this.startLoop($)})}dataMessage(Z){if(!this._fin){this._state=0;return}let $=this._messageLength,Q=this._fragments;if(this._totalPayloadLength=0,this._messageLength=0,this._fragmented=0,this._fragments=[],this._opcode===2){let X;if(this._binaryType==="nodebuffer")X=c4(Q,$);else if(this._binaryType==="arraybuffer")X=zX(c4(Q,$));else if(this._binaryType==="blob")X=new Blob(Q);else X=Q;if(this._allowSynchronousEvents)this.emit("message",X,!0),this._state=0;else this._state=6,setImmediate(()=>{this.emit("message",X,!0),this._state=0,this.startLoop(Z)})}else{let X=c4(Q,$);if(!this._skipUTF8Validation&&!K5(X)){let Y=this.createError(Error,"invalid UTF-8 sequence",!0,1007,"WS_ERR_INVALID_UTF8");Z(Y);return}if(this._state===5||this._allowSynchronousEvents)this.emit("message",X,!1),this._state=0;else this._state=6,setImmediate(()=>{this.emit("message",X,!1),this._state=0,this.startLoop(Z)})}}controlMessage(Z,$){if(this._opcode===8){if(Z.length===0)this._loop=!1,this.emit("conclude",1005,Y5),this.end();else{let Q=Z.readUInt16BE(0);if(!JX(Q)){let Y=this.createError(RangeError,`invalid status code ${Q}`,!0,1002,"WS_ERR_INVALID_CLOSE_CODE");$(Y);return}let X=new v$(Z.buffer,Z.byteOffset+2,Z.length-2);if(!this._skipUTF8Validation&&!K5(X)){let Y=this.createError(Error,"invalid UTF-8 sequence",!0,1007,"WS_ERR_INVALID_UTF8");$(Y);return}this._loop=!1,this.emit("conclude",Q,X),this.end()}this._state=0;return}if(this._allowSynchronousEvents)this.emit(this._opcode===9?"ping":"pong",Z),this._state=0;else this._state=6,setImmediate(()=>{this.emit(this._opcode===9?"ping":"pong",Z),this._state=0,this.startLoop($)})}createError(Z,$,Q,X,Y){this._loop=!1,this._errored=!0;let K=new Z(Q?`Invalid WebSocket frame: ${$}`:$);return Error.captureStackTrace(K,this.createError),K.code=Y,K[KX]=X,K}}z5.exports=H5});var o4=V0((XH,W5)=>{var{Duplex:ZH}=r("stream"),{randomFillSync:WX}=r("crypto"),{types:{isUint8Array:jX}}=r("util"),G5=Z1(),{EMPTY_BUFFER:NX,kWebSocket:BX,NOOP:DX}=a0(),{isBlob:Y1,isValidStatusCode:FX}=X1(),{mask:J5,toBuffer:x8}=E1(),U0=Symbol("kByteLength"),VX=Buffer.alloc(4),R8,K1=8192,v0=0,OX=1,MX=2;class j8{constructor(Z,$,Q){if(this._extensions=$||{},Q)this._generateMask=Q,this._maskBuffer=Buffer.alloc(4);this._socket=Z,this._firstFragment=!0,this._compress=!1,this._bufferedBytes=0,this._queue=[],this._state=v0,this.onerror=DX,this[BX]=void 0}static frame(Z,$){let Q,X=!1,Y=2,K=!1;if($.mask){if(Q=$.maskBuffer||VX,$.generateMask)$.generateMask(Q);else{if(K1===8192){if(R8===void 0)R8=Buffer.alloc(8192);WX(R8,0,8192),K1=0}Q[0]=R8[K1++],Q[1]=R8[K1++],Q[2]=R8[K1++],Q[3]=R8[K1++]}K=(Q[0]|Q[1]|Q[2]|Q[3])===0,Y=6}let H;if(typeof Z==="string")if((!$.mask||K)&&$[U0]!==void 0)H=$[U0];else Z=Buffer.from(Z),H=Z.length;else H=Z.length,X=$.mask&&$.readOnly&&!K;let J=H;if(H>=65536)Y+=8,J=127;else if(H>125)Y+=2,J=126;let G=Buffer.allocUnsafe(X?H+Y:Y);if(G[0]=$.fin?$.opcode|128:$.opcode,$.rsv1)G[0]|=64;if(G[1]=J,J===126)G.writeUInt16BE(H,2);else if(J===127)G[2]=G[3]=0,G.writeUIntBE(H,4,6);if(!$.mask)return[G,Z];if(G[1]|=128,G[Y-4]=Q[0],G[Y-3]=Q[1],G[Y-2]=Q[2],G[Y-1]=Q[3],K)return[G,Z];if(X)return J5(Z,Q,G,Y,H),[G];return J5(Z,Q,Z,0,H),[G,Z]}close(Z,$,Q,X){let Y;if(Z===void 0)Y=NX;else if(typeof Z!=="number"||!FX(Z))throw TypeError("First argument must be a valid error code number");else if($===void 0||!$.length)Y=Buffer.allocUnsafe(2),Y.writeUInt16BE(Z,0);else{let H=Buffer.byteLength($);if(H>123)throw RangeError("The message must not be greater than 123 bytes");if(Y=Buffer.allocUnsafe(2+H),Y.writeUInt16BE(Z,0),typeof $==="string")Y.write($,2);else if(jX($))Y.set($,2);else throw TypeError("Second argument must be a string or a Uint8Array")}let K={[U0]:Y.length,fin:!0,generateMask:this._generateMask,mask:Q,maskBuffer:this._maskBuffer,opcode:8,readOnly:!1,rsv1:!1};if(this._state!==v0)this.enqueue([this.dispatch,Y,!1,K,X]);else this.sendFrame(j8.frame(Y,K),X)}ping(Z,$,Q){let X,Y;if(typeof Z==="string")X=Buffer.byteLength(Z),Y=!1;else if(Y1(Z))X=Z.size,Y=!1;else Z=x8(Z),X=Z.length,Y=x8.readOnly;if(X>125)throw RangeError("The data size must not be greater than 125 bytes");let K={[U0]:X,fin:!0,generateMask:this._generateMask,mask:$,maskBuffer:this._maskBuffer,opcode:9,readOnly:Y,rsv1:!1};if(Y1(Z))if(this._state!==v0)this.enqueue([this.getBlobData,Z,!1,K,Q]);else this.getBlobData(Z,!1,K,Q);else if(this._state!==v0)this.enqueue([this.dispatch,Z,!1,K,Q]);else this.sendFrame(j8.frame(Z,K),Q)}pong(Z,$,Q){let X,Y;if(typeof Z==="string")X=Buffer.byteLength(Z),Y=!1;else if(Y1(Z))X=Z.size,Y=!1;else Z=x8(Z),X=Z.length,Y=x8.readOnly;if(X>125)throw RangeError("The data size must not be greater than 125 bytes");let K={[U0]:X,fin:!0,generateMask:this._generateMask,mask:$,maskBuffer:this._maskBuffer,opcode:10,readOnly:Y,rsv1:!1};if(Y1(Z))if(this._state!==v0)this.enqueue([this.getBlobData,Z,!1,K,Q]);else this.getBlobData(Z,!1,K,Q);else if(this._state!==v0)this.enqueue([this.dispatch,Z,!1,K,Q]);else this.sendFrame(j8.frame(Z,K),Q)}send(Z,$,Q){let X=this._extensions[G5.extensionName],Y=$.binary?2:1,K=$.compress,H,J;if(typeof Z==="string")H=Buffer.byteLength(Z),J=!1;else if(Y1(Z))H=Z.size,J=!1;else Z=x8(Z),H=Z.length,J=x8.readOnly;if(this._firstFragment){if(this._firstFragment=!1,K&&X&&X.params[X._isServer?"server_no_context_takeover":"client_no_context_takeover"])K=H>=X._threshold;this._compress=K}else K=!1,Y=0;if($.fin)this._firstFragment=!0;let G={[U0]:H,fin:$.fin,generateMask:this._generateMask,mask:$.mask,maskBuffer:this._maskBuffer,opcode:Y,readOnly:J,rsv1:K};if(Y1(Z))if(this._state!==v0)this.enqueue([this.getBlobData,Z,this._compress,G,Q]);else this.getBlobData(Z,this._compress,G,Q);else if(this._state!==v0)this.enqueue([this.dispatch,Z,this._compress,G,Q]);else this.dispatch(Z,this._compress,G,Q)}getBlobData(Z,$,Q,X){this._bufferedBytes+=Q[U0],this._state=MX,Z.arrayBuffer().then((Y)=>{if(this._socket.destroyed){let H=Error("The socket was closed while the blob was being read");process.nextTick(n4,this,H,X);return}this._bufferedBytes-=Q[U0];let K=x8(Y);if(!$)this._state=v0,this.sendFrame(j8.frame(K,Q),X),this.dequeue();else this.dispatch(K,$,Q,X)}).catch((Y)=>{process.nextTick(LX,this,Y,X)})}dispatch(Z,$,Q,X){if(!$){this.sendFrame(j8.frame(Z,Q),X);return}let Y=this._extensions[G5.extensionName];this._bufferedBytes+=Q[U0],this._state=OX,Y.compress(Z,Q.fin,(K,H)=>{if(this._socket.destroyed){let J=Error("The socket was closed while data was being compressed");n4(this,J,X);return}this._bufferedBytes-=Q[U0],this._state=v0,Q.readOnly=!1,this.sendFrame(j8.frame(H,Q),X),this.dequeue()})}dequeue(){while(this._state===v0&&this._queue.length){let Z=this._queue.shift();this._bufferedBytes-=Z[3][U0],Reflect.apply(Z[0],this,Z.slice(1))}}enqueue(Z){this._bufferedBytes+=Z[3][U0],this._queue.push(Z)}sendFrame(Z,$){if(Z.length===2)this._socket.cork(),this._socket.write(Z[0]),this._socket.write(Z[1],$),this._socket.uncork();else this._socket.write(Z[0],$)}}W5.exports=j8;function n4(Z,$,Q){if(typeof Q==="function")Q($);for(let X=0;X<Z._queue.length;X++){let Y=Z._queue[X],K=Y[Y.length-1];if(typeof K==="function")K($)}}function LX(Z,$,Q){n4(Z,$,Q),Z.onerror($)}});var L5=V0((YH,M5)=>{var{kForOnEventAttribute:v1,kListener:a4}=a0(),j5=Symbol("kCode"),N5=Symbol("kData"),B5=Symbol("kError"),D5=Symbol("kMessage"),F5=Symbol("kReason"),H1=Symbol("kTarget"),V5=Symbol("kType"),O5=Symbol("kWasClean");class N8{constructor(Z){this[H1]=null,this[V5]=Z}get target(){return this[H1]}get type(){return this[V5]}}Object.defineProperty(N8.prototype,"target",{enumerable:!0});Object.defineProperty(N8.prototype,"type",{enumerable:!0});class z1 extends N8{constructor(Z,$={}){super(Z);this[j5]=$.code===void 0?0:$.code,this[F5]=$.reason===void 0?"":$.reason,this[O5]=$.wasClean===void 0?!1:$.wasClean}get code(){return this[j5]}get reason(){return this[F5]}get wasClean(){return this[O5]}}Object.defineProperty(z1.prototype,"code",{enumerable:!0});Object.defineProperty(z1.prototype,"reason",{enumerable:!0});Object.defineProperty(z1.prototype,"wasClean",{enumerable:!0});class y1 extends N8{constructor(Z,$={}){super(Z);this[B5]=$.error===void 0?null:$.error,this[D5]=$.message===void 0?"":$.message}get error(){return this[B5]}get message(){return this[D5]}}Object.defineProperty(y1.prototype,"error",{enumerable:!0});Object.defineProperty(y1.prototype,"message",{enumerable:!0});class b$ extends N8{constructor(Z,$={}){super(Z);this[N5]=$.data===void 0?null:$.data}get data(){return this[N5]}}Object.defineProperty(b$.prototype,"data",{enumerable:!0});var _X={addEventListener(Z,$,Q={}){for(let Y of this.listeners(Z))if(!Q[v1]&&Y[a4]===$&&!Y[v1])return;let X;if(Z==="message")X=function(K,H){let J=new b$("message",{data:H?K:K.toString()});J[H1]=this,y$($,this,J)};else if(Z==="close")X=function(K,H){let J=new z1("close",{code:K,reason:H.toString(),wasClean:this._closeFrameReceived&&this._closeFrameSent});J[H1]=this,y$($,this,J)};else if(Z==="error")X=function(K){let H=new y1("error",{error:K,message:K.message});H[H1]=this,y$($,this,H)};else if(Z==="open")X=function(){let K=new N8("open");K[H1]=this,y$($,this,K)};else return;if(X[v1]=!!Q[v1],X[a4]=$,Q.once)this.once(Z,X);else this.on(Z,X)},removeEventListener(Z,$){for(let Q of this.listeners(Z))if(Q[a4]===$&&!Q[v1]){this.removeListener(Z,Q);break}}};M5.exports={CloseEvent:z1,ErrorEvent:y1,Event:N8,EventTarget:_X,MessageEvent:b$};function y$(Z,$,Q){if(typeof Z==="object"&&Z.handleEvent)Z.handleEvent.call(Z,Q);else Z.call($,Q)}});var h$=V0((KH,_5)=>{var{tokenChars:b1}=X1();function f0(Z,$,Q){if(Z[$]===void 0)Z[$]=[Q];else Z[$].push(Q)}function PX(Z){let $=Object.create(null),Q=Object.create(null),X=!1,Y=!1,K=!1,H,J,G=-1,W=-1,j=-1,N=0;for(;N<Z.length;N++)if(W=Z.charCodeAt(N),H===void 0)if(j===-1&&b1[W]===1){if(G===-1)G=N}else if(N!==0&&(W===32||W===9)){if(j===-1&&G!==-1)j=N}else if(W===59||W===44){if(G===-1)throw SyntaxError(`Unexpected character at index ${N}`);if(j===-1)j=N;let F=Z.slice(G,j);if(W===44)f0($,F,Q),Q=Object.create(null);else H=F;G=j=-1}else throw SyntaxError(`Unexpected character at index ${N}`);else if(J===void 0)if(j===-1&&b1[W]===1){if(G===-1)G=N}else if(W===32||W===9){if(j===-1&&G!==-1)j=N}else if(W===59||W===44){if(G===-1)throw SyntaxError(`Unexpected character at index ${N}`);if(j===-1)j=N;if(f0(Q,Z.slice(G,j),!0),W===44)f0($,H,Q),Q=Object.create(null),H=void 0;G=j=-1}else if(W===61&&G!==-1&&j===-1)J=Z.slice(G,N),G=j=-1;else throw SyntaxError(`Unexpected character at index ${N}`);else if(Y){if(b1[W]!==1)throw SyntaxError(`Unexpected character at index ${N}`);if(G===-1)G=N;else if(!X)X=!0;Y=!1}else if(K)if(b1[W]===1){if(G===-1)G=N}else if(W===34&&G!==-1)K=!1,j=N;else if(W===92)Y=!0;else throw SyntaxError(`Unexpected character at index ${N}`);else if(W===34&&Z.charCodeAt(N-1)===61)K=!0;else if(j===-1&&b1[W]===1){if(G===-1)G=N}else if(G!==-1&&(W===32||W===9)){if(j===-1)j=N}else if(W===59||W===44){if(G===-1)throw SyntaxError(`Unexpected character at index ${N}`);if(j===-1)j=N;let F=Z.slice(G,j);if(X)F=F.replace(/\\/g,""),X=!1;if(f0(Q,J,F),W===44)f0($,H,Q),Q=Object.create(null),H=void 0;J=void 0,G=j=-1}else throw SyntaxError(`Unexpected character at index ${N}`);if(G===-1||K||W===32||W===9)throw SyntaxError("Unexpected end of input");if(j===-1)j=N;let D=Z.slice(G,j);if(H===void 0)f0($,D,Q);else{if(J===void 0)f0(Q,D,!0);else if(X)f0(Q,J,D.replace(/\\/g,""));else f0(Q,J,D);f0($,H,Q)}return $}function xX(Z){return Object.keys(Z).map(($)=>{let Q=Z[$];if(!Array.isArray(Q))Q=[Q];return Q.map((X)=>{return[$].concat(Object.keys(X).map((Y)=>{let K=X[Y];if(!Array.isArray(K))K=[K];return K.map((H)=>H===!0?Y:`${Y}=${H}`).join("; ")})).join("; ")}).join(", ")}).join(", ")}_5.exports={format:xX,parse:PX}});var g$=V0((GH,S5)=>{var RX=r("events"),qX=r("https"),TX=r("http"),R5=r("net"),UX=r("tls"),{randomBytes:wX,createHash:AX}=r("crypto"),{Duplex:HH,Readable:zH}=r("stream"),{URL:r4}=r("url"),B8=Z1(),IX=i4(),CX=o4(),{isBlob:EX}=X1(),{BINARY_TYPES:P5,CLOSE_TIMEOUT:SX,EMPTY_BUFFER:m$,GUID:vX,kForOnEventAttribute:t4,kListener:yX,kStatusCode:bX,kWebSocket:Z0,NOOP:q5}=a0(),{EventTarget:{addEventListener:hX,removeEventListener:mX}}=L5(),{format:kX,parse:fX}=h$(),{toBuffer:gX}=E1(),T5=Symbol("kAborted"),s4=[8,13],t0=["CONNECTING","OPEN","CLOSING","CLOSED"],pX=/^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;class U extends RX{constructor(Z,$,Q){super();if(this._binaryType=P5[0],this._closeCode=1006,this._closeFrameReceived=!1,this._closeFrameSent=!1,this._closeMessage=m$,this._closeTimer=null,this._errorEmitted=!1,this._extensions={},this._paused=!1,this._protocol="",this._readyState=U.CONNECTING,this._receiver=null,this._sender=null,this._socket=null,Z!==null){if(this._bufferedAmount=0,this._isServer=!1,this._redirects=0,$===void 0)$=[];else if(!Array.isArray($))if(typeof $==="object"&&$!==null)Q=$,$=[];else $=[$];U5(this,Z,$,Q)}else this._autoPong=Q.autoPong,this._closeTimeout=Q.closeTimeout,this._isServer=!0}get binaryType(){return this._binaryType}set binaryType(Z){if(!P5.includes(Z))return;if(this._binaryType=Z,this._receiver)this._receiver._binaryType=Z}get bufferedAmount(){if(!this._socket)return this._bufferedAmount;return this._socket._writableState.length+this._sender._bufferedBytes}get extensions(){return Object.keys(this._extensions).join()}get isPaused(){return this._paused}get onclose(){return null}get onerror(){return null}get onopen(){return null}get onmessage(){return null}get protocol(){return this._protocol}get readyState(){return this._readyState}get url(){return this._url}setSocket(Z,$,Q){let X=new IX({allowSynchronousEvents:Q.allowSynchronousEvents,binaryType:this.binaryType,extensions:this._extensions,isServer:this._isServer,maxBufferedChunks:Q.maxBufferedChunks,maxFragments:Q.maxFragments,maxPayload:Q.maxPayload,skipUTF8Validation:Q.skipUTF8Validation}),Y=new CX(Z,this._extensions,Q.generateMask);if(this._receiver=X,this._sender=Y,this._socket=Z,X[Z0]=this,Y[Z0]=this,Z[Z0]=this,X.on("conclude",dX),X.on("drain",cX),X.on("error",iX),X.on("message",nX),X.on("ping",oX),X.on("pong",aX),Y.onerror=rX,Z.setTimeout)Z.setTimeout(0);if(Z.setNoDelay)Z.setNoDelay();if($.length>0)Z.unshift($);Z.on("close",I5),Z.on("data",f$),Z.on("end",C5),Z.on("error",E5),this._readyState=U.OPEN,this.emit("open")}emitClose(){if(!this._socket){this._readyState=U.CLOSED,this.emit("close",this._closeCode,this._closeMessage);return}if(this._extensions[B8.extensionName])this._extensions[B8.extensionName].cleanup();this._receiver.removeAllListeners(),this._readyState=U.CLOSED,this.emit("close",this._closeCode,this._closeMessage)}close(Z,$){if(this.readyState===U.CLOSED)return;if(this.readyState===U.CONNECTING){R0(this,this._req,"WebSocket was closed before the connection was established");return}if(this.readyState===U.CLOSING){if(this._closeFrameSent&&(this._closeFrameReceived||this._receiver._writableState.errorEmitted))this._socket.end();return}this._readyState=U.CLOSING,this._sender.close(Z,$,!this._isServer,(Q)=>{if(Q)return;if(this._closeFrameSent=!0,this._closeFrameReceived||this._receiver._writableState.errorEmitted)this._socket.end()}),A5(this)}pause(){if(this.readyState===U.CONNECTING||this.readyState===U.CLOSED)return;this._paused=!0,this._socket.pause()}ping(Z,$,Q){if(this.readyState===U.CONNECTING)throw Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof Z==="function")Q=Z,Z=$=void 0;else if(typeof $==="function")Q=$,$=void 0;if(typeof Z==="number")Z=Z.toString();if(this.readyState!==U.OPEN){e4(this,Z,Q);return}if($===void 0)$=!this._isServer;this._sender.ping(Z||m$,$,Q)}pong(Z,$,Q){if(this.readyState===U.CONNECTING)throw Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof Z==="function")Q=Z,Z=$=void 0;else if(typeof $==="function")Q=$,$=void 0;if(typeof Z==="number")Z=Z.toString();if(this.readyState!==U.OPEN){e4(this,Z,Q);return}if($===void 0)$=!this._isServer;this._sender.pong(Z||m$,$,Q)}resume(){if(this.readyState===U.CONNECTING||this.readyState===U.CLOSED)return;if(this._paused=!1,!this._receiver._writableState.needDrain)this._socket.resume()}send(Z,$,Q){if(this.readyState===U.CONNECTING)throw Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof $==="function")Q=$,$={};if(typeof Z==="number")Z=Z.toString();if(this.readyState!==U.OPEN){e4(this,Z,Q);return}let X={binary:typeof Z!=="string",mask:!this._isServer,compress:!0,fin:!0,...$};if(!this._extensions[B8.extensionName])X.compress=!1;this._sender.send(Z||m$,X,Q)}terminate(){if(this.readyState===U.CLOSED)return;if(this.readyState===U.CONNECTING){R0(this,this._req,"WebSocket was closed before the connection was established");return}if(this._socket)this._readyState=U.CLOSING,this._socket.destroy()}}Object.defineProperty(U,"CONNECTING",{enumerable:!0,value:t0.indexOf("CONNECTING")});Object.defineProperty(U.prototype,"CONNECTING",{enumerable:!0,value:t0.indexOf("CONNECTING")});Object.defineProperty(U,"OPEN",{enumerable:!0,value:t0.indexOf("OPEN")});Object.defineProperty(U.prototype,"OPEN",{enumerable:!0,value:t0.indexOf("OPEN")});Object.defineProperty(U,"CLOSING",{enumerable:!0,value:t0.indexOf("CLOSING")});Object.defineProperty(U.prototype,"CLOSING",{enumerable:!0,value:t0.indexOf("CLOSING")});Object.defineProperty(U,"CLOSED",{enumerable:!0,value:t0.indexOf("CLOSED")});Object.defineProperty(U.prototype,"CLOSED",{enumerable:!0,value:t0.indexOf("CLOSED")});["binaryType","bufferedAmount","extensions","isPaused","protocol","readyState","url"].forEach((Z)=>{Object.defineProperty(U.prototype,Z,{enumerable:!0})});["open","error","close","message"].forEach((Z)=>{Object.defineProperty(U.prototype,`on${Z}`,{enumerable:!0,get(){for(let $ of this.listeners(Z))if($[t4])return $[yX];return null},set($){for(let Q of this.listeners(Z))if(Q[t4]){this.removeListener(Z,Q);break}if(typeof $!=="function")return;this.addEventListener(Z,$,{[t4]:!0})}})});U.prototype.addEventListener=hX;U.prototype.removeEventListener=mX;S5.exports=U;function U5(Z,$,Q,X){let Y={allowSynchronousEvents:!0,autoPong:!0,closeTimeout:SX,protocolVersion:s4[1],maxBufferedChunks:1048576,maxFragments:131072,maxPayload:104857600,skipUTF8Validation:!1,perMessageDeflate:!0,followRedirects:!1,maxRedirects:10,...X,socketPath:void 0,hostname:void 0,protocol:void 0,timeout:void 0,method:"GET",host:void 0,path:void 0,port:void 0};if(Z._autoPong=Y.autoPong,Z._closeTimeout=Y.closeTimeout,!s4.includes(Y.protocolVersion))throw RangeError(`Unsupported protocol version: ${Y.protocolVersion} (supported versions: ${s4.join(", ")})`);let K;if($ instanceof r4)K=$;else try{K=new r4($)}catch{throw SyntaxError(`Invalid URL: ${$}`)}if(K.protocol==="http:")K.protocol="ws:";else if(K.protocol==="https:")K.protocol="wss:";Z._url=K.href;let H=K.protocol==="wss:",J=K.protocol==="ws+unix:",G;if(K.protocol!=="ws:"&&!H&&!J)G=`The URL's protocol must be one of "ws:", "wss:", "http:", "https:", or "ws+unix:"`;else if(J&&!K.pathname)G="The URL's pathname is empty";else if(K.hash)G="The URL contains a fragment identifier";if(G){let O=SyntaxError(G);if(Z._redirects===0)throw O;else{k$(Z,O);return}}let W=H?443:80,j=wX(16).toString("base64"),N=H?qX.request:TX.request,D=new Set,F;if(Y.createConnection=Y.createConnection||(H?lX:uX),Y.defaultPort=Y.defaultPort||W,Y.port=K.port||W,Y.host=K.hostname.startsWith("[")?K.hostname.slice(1,-1):K.hostname,Y.headers={...Y.headers,"Sec-WebSocket-Version":Y.protocolVersion,"Sec-WebSocket-Key":j,Connection:"Upgrade",Upgrade:"websocket"},Y.path=K.pathname+K.search,Y.timeout=Y.handshakeTimeout,Y.perMessageDeflate)F=new B8({...Y.perMessageDeflate,isServer:!1,maxPayload:Y.maxPayload}),Y.headers["Sec-WebSocket-Extensions"]=kX({[B8.extensionName]:F.offer()});if(Q.length){for(let O of Q){if(typeof O!=="string"||!pX.test(O)||D.has(O))throw SyntaxError("An invalid or duplicated subprotocol was specified");D.add(O)}Y.headers["Sec-WebSocket-Protocol"]=Q.join(",")}if(Y.origin)if(Y.protocolVersion<13)Y.headers["Sec-WebSocket-Origin"]=Y.origin;else Y.headers.Origin=Y.origin;if(K.username||K.password)Y.auth=`${K.username}:${K.password}`;if(J){let O=Y.path.split(":");Y.socketPath=O[0],Y.path=O[1]}let M;if(Y.followRedirects){if(Z._redirects===0){Z._originalIpc=J,Z._originalSecure=H,Z._originalHostOrSocketPath=J?Y.socketPath:K.host;let O=X&&X.headers;if(X={...X,headers:{}},O)for(let[P,R]of Object.entries(O))X.headers[P.toLowerCase()]=R}else if(Z.listenerCount("redirect")===0){let O=J?Z._originalIpc?Y.socketPath===Z._originalHostOrSocketPath:!1:Z._originalIpc?!1:K.host===Z._originalHostOrSocketPath;if(!O||Z._originalSecure&&!H){if(delete Y.headers.authorization,delete Y.headers.cookie,!O)delete Y.headers.host;Y.auth=void 0}}if(Y.auth&&!X.headers.authorization)X.headers.authorization="Basic "+Buffer.from(Y.auth).toString("base64");if(M=Z._req=N(Y),Z._redirects)Z.emit("redirect",Z.url,M)}else M=Z._req=N(Y);if(Y.timeout)M.on("timeout",()=>{R0(Z,M,"Opening handshake has timed out")});if(M.on("error",(O)=>{if(M===null||M[T5])return;M=Z._req=null,k$(Z,O)}),M.on("response",(O)=>{let P=O.headers.location,R=O.statusCode;if(P&&Y.followRedirects&&R>=300&&R<400){if(++Z._redirects>Y.maxRedirects){R0(Z,M,"Maximum redirects exceeded");return}M.abort();let x;try{x=new r4(P,$)}catch(V){let S=SyntaxError(`Invalid URL: ${P}`);k$(Z,S);return}U5(Z,x,Q,X)}else if(!Z.emit("unexpected-response",M,O))R0(Z,M,`Unexpected server response: ${O.statusCode}`)}),M.on("upgrade",(O,P,R)=>{if(Z.emit("upgrade",O),Z.readyState!==U.CONNECTING)return;M=Z._req=null;let x=O.headers.upgrade;if(x===void 0||x.toLowerCase()!=="websocket"){R0(Z,P,"Invalid Upgrade header");return}let V=AX("sha1").update(j+vX).digest("base64");if(O.headers["sec-websocket-accept"]!==V){R0(Z,P,"Invalid Sec-WebSocket-Accept header");return}let S=O.headers["sec-websocket-protocol"],u;if(S!==void 0){if(!D.size)u="Server sent a subprotocol but none was requested";else if(!D.has(S))u="Server sent an invalid subprotocol"}else if(D.size)u="Server sent no subprotocol";if(u){R0(Z,P,u);return}if(S)Z._protocol=S;let b=O.headers["sec-websocket-extensions"];if(b!==void 0){if(!F){R0(Z,P,"Server sent a Sec-WebSocket-Extensions header but no extension was requested");return}let X0;try{X0=fX(b)}catch(A0){R0(Z,P,"Invalid Sec-WebSocket-Extensions header");return}let J0=Object.keys(X0);if(J0.length!==1||J0[0]!==B8.extensionName){R0(Z,P,"Server indicated an extension that was not requested");return}try{F.accept(X0[B8.extensionName])}catch(A0){R0(Z,P,"Invalid Sec-WebSocket-Extensions header");return}Z._extensions[B8.extensionName]=F}Z.setSocket(P,R,{allowSynchronousEvents:Y.allowSynchronousEvents,generateMask:Y.generateMask,maxBufferedChunks:Y.maxBufferedChunks,maxFragments:Y.maxFragments,maxPayload:Y.maxPayload,skipUTF8Validation:Y.skipUTF8Validation})}),Y.finishRequest)Y.finishRequest(M,Z);else M.end()}function k$(Z,$){Z._readyState=U.CLOSING,Z._errorEmitted=!0,Z.emit("error",$),Z.emitClose()}function uX(Z){return Z.path=Z.socketPath,R5.connect(Z)}function lX(Z){if(Z.path=void 0,!Z.servername&&Z.servername!=="")Z.servername=R5.isIP(Z.host)?"":Z.host;return UX.connect(Z)}function R0(Z,$,Q){Z._readyState=U.CLOSING;let X=Error(Q);if(Error.captureStackTrace(X,R0),$.setHeader){if($[T5]=!0,$.abort(),$.socket&&!$.socket.destroyed)$.socket.destroy();process.nextTick(k$,Z,X)}else $.destroy(X),$.once("error",Z.emit.bind(Z,"error")),$.once("close",Z.emitClose.bind(Z))}function e4(Z,$,Q){if($){let X=EX($)?$.size:gX($).length;if(Z._socket)Z._sender._bufferedBytes+=X;else Z._bufferedAmount+=X}if(Q){let X=Error(`WebSocket is not open: readyState ${Z.readyState} (${t0[Z.readyState]})`);process.nextTick(Q,X)}}function dX(Z,$){let Q=this[Z0];if(Q._closeFrameReceived=!0,Q._closeMessage=$,Q._closeCode=Z,Q._socket[Z0]===void 0)return;if(Q._socket.removeListener("data",f$),process.nextTick(w5,Q._socket),Z===1005)Q.close();else Q.close(Z,$)}function cX(){let Z=this[Z0];if(!Z.isPaused)Z._socket.resume()}function iX(Z){let $=this[Z0];if($._socket[Z0]!==void 0)$._socket.removeListener("data",f$),process.nextTick(w5,$._socket),$.close(Z[bX]);if(!$._errorEmitted)$._errorEmitted=!0,$.emit("error",Z)}function x5(){this[Z0].emitClose()}function nX(Z,$){this[Z0].emit("message",Z,$)}function oX(Z){let $=this[Z0];if($._autoPong)$.pong(Z,!this._isServer,q5);$.emit("ping",Z)}function aX(Z){this[Z0].emit("pong",Z)}function w5(Z){Z.resume()}function rX(Z){let $=this[Z0];if($.readyState===U.CLOSED)return;if($.readyState===U.OPEN)$._readyState=U.CLOSING,A5($);if(this._socket.end(),!$._errorEmitted)$._errorEmitted=!0,$.emit("error",Z)}function A5(Z){Z._closeTimer=setTimeout(Z._socket.destroy.bind(Z._socket),Z._closeTimeout)}function I5(){let Z=this[Z0];if(this.removeListener("close",I5),this.removeListener("data",f$),this.removeListener("end",C5),Z._readyState=U.CLOSING,!this._readableState.endEmitted&&!Z._closeFrameReceived&&!Z._receiver._writableState.errorEmitted&&this._readableState.length!==0){let $=this.read(this._readableState.length);Z._receiver.write($)}if(Z._receiver.end(),this[Z0]=void 0,clearTimeout(Z._closeTimer),Z._receiver._writableState.finished||Z._receiver._writableState.errorEmitted)Z.emitClose();else Z._receiver.on("error",x5),Z._receiver.on("finish",x5)}function f$(Z){if(!this[Z0]._receiver.write(Z))this.pause()}function C5(){let Z=this[Z0];Z._readyState=U.CLOSING,Z._receiver.end(),this.end()}function E5(){let Z=this[Z0];if(this.removeListener("error",E5),this.on("error",q5),Z)Z._readyState=U.CLOSING,this.destroy()}});var h5=V0((WH,b5)=>{var JH=g$(),{Duplex:tX}=r("stream");function v5(Z){Z.emit("close")}function sX(){if(!this.destroyed&&this._writableState.finished)this.destroy()}function y5(Z){if(this.removeListener("error",y5),this.destroy(),this.listenerCount("error")===0)this.emit("error",Z)}function eX(Z,$){let Q=!0,X=new tX({...$,autoDestroy:!1,emitClose:!1,objectMode:!1,writableObjectMode:!1});return Z.on("message",function(K,H){let J=!H&&X._readableState.objectMode?K.toString():K;if(!X.push(J))Z.pause()}),Z.once("error",function(K){if(X.destroyed)return;Q=!1,X.destroy(K)}),Z.once("close",function(){if(X.destroyed)return;X.push(null)}),X._destroy=function(Y,K){if(Z.readyState===Z.CLOSED){K(Y),process.nextTick(v5,X);return}let H=!1;if(Z.once("error",function(G){H=!0,K(G)}),Z.once("close",function(){if(!H)K(Y);process.nextTick(v5,X)}),Q)Z.terminate()},X._final=function(Y){if(Z.readyState===Z.CONNECTING){Z.once("open",function(){X._final(Y)});return}if(Z._socket===null)return;if(Z._socket._writableState.finished){if(Y(),X._readableState.endEmitted)X.destroy()}else Z._socket.once("finish",function(){Y()}),Z.close()},X._read=function(){if(Z.isPaused)Z.resume()},X._write=function(Y,K,H){if(Z.readyState===Z.CONNECTING){Z.once("open",function(){X._write(Y,K,H)});return}Z.send(Y,H)},X.on("end",sX),X.on("error",y5),X}b5.exports=eX});var $Q=V0((jH,m5)=>{var{tokenChars:$Y}=X1();function QY(Z){let $=new Set,Q=-1,X=-1,Y=0;for(Y;Y<Z.length;Y++){let H=Z.charCodeAt(Y);if(X===-1&&$Y[H]===1){if(Q===-1)Q=Y}else if(Y!==0&&(H===32||H===9)){if(X===-1&&Q!==-1)X=Y}else if(H===44){if(Q===-1)throw SyntaxError(`Unexpected character at index ${Y}`);if(X===-1)X=Y;let J=Z.slice(Q,X);if($.has(J))throw SyntaxError(`The "${J}" subprotocol is duplicated`);$.add(J),Q=X=-1}else throw SyntaxError(`Unexpected character at index ${Y}`)}if(Q===-1||X!==-1)throw SyntaxError("Unexpected end of input");let K=Z.slice(Q,Y);if($.has(K))throw SyntaxError(`The "${K}" subprotocol is duplicated`);return $.add(K),$}m5.exports={parse:QY}});var u5=V0((BH,p5)=>{var ZY=r("events"),p$=r("http"),{Duplex:NH}=r("stream"),{createHash:XY}=r("crypto"),k5=h$(),q8=Z1(),YY=$Q(),KY=g$(),{CLOSE_TIMEOUT:HY,GUID:zY,kWebSocket:GY}=a0(),JY=/^[+/0-9A-Za-z]{22}==$/;class g5 extends ZY{constructor(Z,$){super();if(Z={allowSynchronousEvents:!0,autoPong:!0,maxBufferedChunks:1048576,maxFragments:131072,maxPayload:104857600,skipUTF8Validation:!1,perMessageDeflate:!1,handleProtocols:null,clientTracking:!0,closeTimeout:HY,verifyClient:null,noServer:!1,backlog:null,server:null,host:null,path:null,port:null,WebSocket:KY,...Z},Z.port==null&&!Z.server&&!Z.noServer||Z.port!=null&&(Z.server||Z.noServer)||Z.server&&Z.noServer)throw TypeError('One and only one of the "port", "server", or "noServer" options must be specified');if(Z.port!=null)this._server=p$.createServer((Q,X)=>{let Y=p$.STATUS_CODES[426];X.writeHead(426,{"Content-Length":Y.length,"Content-Type":"text/plain"}),X.end(Y)}),this._server.listen(Z.port,Z.host,Z.backlog,$);else if(Z.server)this._server=Z.server;if(this._server){let Q=this.emit.bind(this,"connection");this._removeListeners=WY(this._server,{listening:this.emit.bind(this,"listening"),error:this.emit.bind(this,"error"),upgrade:(X,Y,K)=>{this.handleUpgrade(X,Y,K,Q)}})}if(Z.perMessageDeflate===!0)Z.perMessageDeflate={};if(Z.clientTracking)this.clients=new Set,this._shouldEmitClose=!1;this.options=Z,this._state=0}address(){if(this.options.noServer)throw Error('The server is operating in "noServer" mode');if(!this._server)return null;return this._server.address()}close(Z){if(this._state===2){if(Z)this.once("close",()=>{Z(Error("The server is not running"))});process.nextTick(h1,this);return}if(Z)this.once("close",Z);if(this._state===1)return;if(this._state=1,this.options.noServer||this.options.server){if(this._server)this._removeListeners(),this._removeListeners=this._server=null;if(this.clients)if(!this.clients.size)process.nextTick(h1,this);else this._shouldEmitClose=!0;else process.nextTick(h1,this)}else{let $=this._server;this._removeListeners(),this._removeListeners=this._server=null,$.close(()=>{h1(this)})}}shouldHandle(Z){if(this.options.path){let $=Z.url.indexOf("?");if(($!==-1?Z.url.slice(0,$):Z.url)!==this.options.path)return!1}return!0}handleUpgrade(Z,$,Q,X){$.on("error",f5);let Y=Z.headers["sec-websocket-key"],K=Z.headers.upgrade,H=+Z.headers["sec-websocket-version"];if(Z.method!=="GET"){T8(this,Z,$,405,"Invalid HTTP method");return}if(K===void 0||K.toLowerCase()!=="websocket"){T8(this,Z,$,400,"Invalid Upgrade header");return}if(Y===void 0||!JY.test(Y)){T8(this,Z,$,400,"Missing or invalid Sec-WebSocket-Key header");return}if(H!==13&&H!==8){T8(this,Z,$,400,"Missing or invalid Sec-WebSocket-Version header",{"Sec-WebSocket-Version":"13, 8"});return}if(!this.shouldHandle(Z)){m1($,400);return}let J=Z.headers["sec-websocket-protocol"],G=new Set;if(J!==void 0)try{G=YY.parse(J)}catch(N){T8(this,Z,$,400,"Invalid Sec-WebSocket-Protocol header");return}let W=Z.headers["sec-websocket-extensions"],j={};if(this.options.perMessageDeflate&&W!==void 0){let N=new q8({...this.options.perMessageDeflate,isServer:!0,maxPayload:this.options.maxPayload});try{let D=k5.parse(W);if(D[q8.extensionName])N.accept(D[q8.extensionName]),j[q8.extensionName]=N}catch(D){T8(this,Z,$,400,"Invalid or unacceptable Sec-WebSocket-Extensions header");return}}if(this.options.verifyClient){let N={origin:Z.headers[`${H===8?"sec-websocket-origin":"origin"}`],secure:!!(Z.socket.authorized||Z.socket.encrypted),req:Z};if(this.options.verifyClient.length===2){this.options.verifyClient(N,(D,F,M,O)=>{if(!D)return m1($,F||401,M,O);this.completeUpgrade(j,Y,G,Z,$,Q,X)});return}if(!this.options.verifyClient(N))return m1($,401)}this.completeUpgrade(j,Y,G,Z,$,Q,X)}completeUpgrade(Z,$,Q,X,Y,K,H){if(!Y.readable||!Y.writable)return Y.destroy();if(Y[GY])throw Error("server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration");if(this._state>0)return m1(Y,503);let G=["HTTP/1.1 101 Switching Protocols","Upgrade: websocket","Connection: Upgrade",`Sec-WebSocket-Accept: ${XY("sha1").update($+zY).digest("base64")}`],W=new this.options.WebSocket(null,void 0,this.options);if(Q.size){let j=this.options.handleProtocols?this.options.handleProtocols(Q,X):Q.values().next().value;if(j)G.push(`Sec-WebSocket-Protocol: ${j}`),W._protocol=j}if(Z[q8.extensionName]){let j=Z[q8.extensionName].params,N=k5.format({[q8.extensionName]:[j]});G.push(`Sec-WebSocket-Extensions: ${N}`),W._extensions=Z}if(this.emit("headers",G,X),Y.write(G.concat(`\r
|
|
2
|
+
`).join(`\r
|
|
3
|
+
`)),Y.removeListener("error",f5),W.setSocket(Y,K,{allowSynchronousEvents:this.options.allowSynchronousEvents,maxBufferedChunks:this.options.maxBufferedChunks,maxFragments:this.options.maxFragments,maxPayload:this.options.maxPayload,skipUTF8Validation:this.options.skipUTF8Validation}),this.clients)this.clients.add(W),W.on("close",()=>{if(this.clients.delete(W),this._shouldEmitClose&&!this.clients.size)process.nextTick(h1,this)});H(W,X)}}p5.exports=g5;function WY(Z,$){for(let Q of Object.keys($))Z.on(Q,$[Q]);return function(){for(let X of Object.keys($))Z.removeListener(X,$[X])}}function h1(Z){Z._state=2,Z.emit("close")}function f5(){this.destroy()}function m1(Z,$,Q,X){Q=Q||p$.STATUS_CODES[$],X={Connection:"close","Content-Type":"text/html","Content-Length":Buffer.byteLength(Q),...X},Z.once("finish",Z.destroy),Z.end(`HTTP/1.1 ${$} ${p$.STATUS_CODES[$]}\r
|
|
4
|
+
`+Object.keys(X).map((Y)=>`${Y}: ${X[Y]}`).join(`\r
|
|
5
|
+
`)+`\r
|
|
6
|
+
\r
|
|
7
|
+
`+Q)}function T8(Z,$,Q,X,Y,K){if(Z.listenerCount("wsClientError")){let H=Error(Y);Error.captureStackTrace(H,T8),Z.emit("wsClientError",H,Q,$)}else m1(Q,X,Y,K)}});import{spawn as Q7}from"node:child_process";import{trimTrailingSlash as Z7}from"@ionite/core";function X7(Z){return new Promise(($)=>setTimeout($,Z))}var Y7=5000,K7=2000;function XQ(Z,$){if(Z.exitCode!==null||Z.signalCode!==null)return Promise.resolve(!0);return new Promise((Q)=>{let X=!1,Y=()=>{if(X)return;X=!0,clearTimeout(K),Q(!0)},K=setTimeout(()=>{if(X)return;X=!0,Z.removeListener("exit",Y),Q(!1)},$);Z.once("exit",Y)})}var H7="/api/ionite";function U8(Z){if(Z.readyPath)return Z.readyPath;return`${Z7(Z.basePath??H7)}/readyz`}async function YQ(Z,$={}){let{livenessPath:Q,maxRetries:X=30,retryDelayMs:Y=1000,requestTimeoutMs:K=3000}=$,H=U8($),J;for(let W=0;W<X;W++){try{if(Q){let N=await fetch(`${Z}${Q}`,{signal:AbortSignal.timeout(K)});if(!N.ok)throw Error(`liveness returned ${N.status}`)}let j=await fetch(`${Z}${H}`,{signal:AbortSignal.timeout(K)});if(j.ok)return;J=Error(`readiness returned ${j.status}`)}catch(j){J=j}if(W<X-1)await X7(Y)}let G=J instanceof Error?J.message:String(J);throw Error(`App not ready at ${Z}${H} after ${X} attempts: ${G}`)}async function c$(Z){let{command:$,args:Q=[],cwd:X,env:Y,baseUrl:K,...H}=Z,J=process.platform!=="win32",G=Q7($,Q,{cwd:X,env:{...process.env,...Y},stdio:"inherit",detached:J});G.on("error",(D)=>{console.error(`Failed to start app under test (${$}):`,D)});let W=(D)=>{try{if(J&&G.pid!==void 0)process.kill(-G.pid,D);else G.kill(D)}catch{}},j=()=>W("SIGKILL");process.once("exit",j);let N=async()=>{if(G.exitCode===null&&G.signalCode===null){if(W("SIGTERM"),!await XQ(G,Y7))W("SIGKILL"),await XQ(G,K7)}W("SIGKILL"),process.removeListener("exit",j)};try{await YQ(K,H)}catch(D){throw await N(),D}return{baseUrl:K,process:G,stop:N}}import{accessRefId as uQ,accessRootId as T0,CatalogAdjacencyCache as lQ,deepEqualPlain as r1,defineCatalog as t1,escapeScimFilterValue as N1,expandRoot as y0,hasAllScope as H4,hasAnyScope as oQ,hasPermission as B0,hasScope as z4,InMemoryAccessAssignmentStore as w,InMemoryAccessCatalogStore as S0,InMemoryAccessIndex as A,InMemoryResourceStore as G4,InMemoryUserStore as N6,ION_AUTHZ_OAA_SYNC as Y4,ION_IAM_GROUPS_WRITE as Y8,ION_IAM_READ as s1,ION_IAM_USERS_WRITE as aQ,ION_TENANT_MANAGE as V1,ionite as q,stableConfigHash as E8}from"@ionite/server";import{createValidators as T}from"@ionite/valibot";import*as O0 from"node:crypto";var HQ,i$,n$;function zQ(){let{publicKey:Z,privateKey:$}=O0.generateKeyPairSync("rsa",{modulusLength:2048});HQ=$,i$=Z,n$=O0.randomBytes(8).toString("hex")}function k1(Z){return(typeof Z==="string"?Buffer.from(Z):Z).toString("base64url")}function KQ(Z){return Buffer.from(Z,"base64url")}function D8(Z,$=3600){let Q=Math.floor(Date.now()/1000),X={alg:"RS256",typ:"JWT",kid:n$},Y={...Z,iat:Q,exp:Q+$},K=k1(JSON.stringify(X)),H=k1(JSON.stringify(Y)),J=`${K}.${H}`,G=O0.createSign("RSA-SHA256");G.update(J);let W=G.sign(HQ);return`${J}.${k1(W)}`}function G1(Z){try{let $=Z.split(".");if($.length!==3)return{valid:!1,error:"Invalid JWT format"};let[Q,X,Y]=$,K=`${Q}.${X}`,H=KQ(Y),J=O0.createVerify("RSA-SHA256");if(J.update(K),!J.verify(i$,H))return{valid:!1,error:"Invalid signature"};let W=JSON.parse(KQ(X).toString("utf-8")),j=Math.floor(Date.now()/1000);if(W.exp&&W.exp<j)return{valid:!1,error:"Token expired"};return{valid:!0,payload:W}}catch($){return{valid:!1,error:$ instanceof Error?$.message:"Unknown error"}}}function J1(){let Z=i$.export({format:"jwk"});return{keys:[{kty:Z.kty,n:Z.n,e:Z.e,kid:n$,use:"sig",alg:"RS256"}]}}function o$(Z=32){return O0.randomBytes(Z).toString("hex").substring(0,Z)}function W1(){return O0.randomUUID()}function GQ(Z,$){let Q=O0.createHash("sha256").update(Z).digest();return k1(Q)===$}import{gunzipSync as G7}from"node:zlib";var F8=new Map,V8=new Map,g1=new Map,w8=new Map,a$=new Map,p1=[],A8=new Map,f1,JQ={id:"test-user-001",userName:"testuser@example.com",email:"testuser@example.com",name:"Test User",givenName:"Test",familyName:"User"};function WQ(){return Array.from(F8.values())}function u1(Z){return F8.get(Z)}function jQ(Z){let $=new Date().toISOString();return Z.meta={resourceType:"User",created:$,lastModified:$,location:`/Users/${Z.id}`},F8.set(Z.id,Z),Z}function r$(Z,$){let Q=F8.get(Z);if(!Q)return;let X={...Q,...$};if(X.meta)X.meta.lastModified=new Date().toISOString();return F8.set(Z,X),X}function NQ(Z){return F8.delete(Z)}function BQ(){return Array.from(V8.values())}function l1(Z){return V8.get(Z)}function DQ(Z){let $=new Date().toISOString();return Z.meta={resourceType:"Group",created:$,lastModified:$,location:`/Groups/${Z.id}`},V8.set(Z.id,Z),Z}function t$(Z,$){let Q=V8.get(Z);if(!Q)return;let X={...Q,...$};if(X.meta)X.meta.lastModified=new Date().toISOString();return V8.set(Z,X),X}function FQ(Z){return V8.delete(Z)}function VQ(Z){g1.set(Z.code,Z)}function OQ(Z){return g1.get(Z)}function MQ(Z){return g1.delete(Z)}function LQ(Z){w8.set(Z.token,Z)}function _Q(Z){return w8.get(Z)}function s$(Z){return w8.delete(Z)}function z7(Z){for(let[$,Q]of w8.entries())if(Q.sessionId===Z)w8.delete($)}function PQ(Z){a$.set(Z.id,Z)}function xQ(Z){return z7(Z),a$.delete(Z)}function j1(){return JQ}function d1(Z){f1=Z?.trim()||void 0}function c1(){if(f1)return f1;return process.env.REDIS_URL?.trim()||void 0}function RQ(Z){p1.push(Z)}function k(){return[...p1]}function l(){p1.length=0,A8.clear()}function qQ(Z,$){A8.set(Z,$)}function TQ(Z,$,Q){let X=A8.get(Z);if(!X)return!1;return X.parts.set($,Q),!0}function UQ(Z){let $=A8.get(Z);if(!$)return;return A8.delete(Z),$}function e$(){f1=void 0,F8.clear(),V8.clear(),g1.clear(),w8.clear(),a$.clear(),p1.length=0,A8.clear(),JQ={id:"test-user-001",userName:"testuser@example.com",email:"testuser@example.com",name:"Test User",givenName:"Test",familyName:"User"}}var Q8="ecv-oaa-provider",Z8="ecv-oaa-datasource",p0="local-oaa-key",J7=/^\/api\/v1\/providers\/custom\/([^/]+)\/datasources\/([^/:]+):push$/,W7=/^\/api\/v1\/providers\/custom\/([^/]+)\/datasources\/([^/:]+):multipart\/start$/,j7=/^\/api\/v1\/providers\/custom\/([^/]+)\/datasources\/([^/:]+):multipart\/([^/]+)\/parts\/(\d+)$/,N7=/^\/api\/v1\/providers\/custom\/([^/]+)\/datasources\/([^/:]+):multipart\/([^/]+)\/complete$/;function $4(Z){return Z.startsWith("/api/v1/providers/custom/")}function i1(Z){return new Promise(($,Q)=>{let X="";Z.on("data",(Y)=>{X+=Y.toString()}),Z.on("end",()=>$(X)),Z.on("error",Q)})}function B7(Z){return Z.headers.authorization===`Bearer ${p0}`}function D7(Z){Z.writeHead(401,{"Content-Type":"application/json"}),Z.end(JSON.stringify({code:"unauthorized",message:"Invalid OAA API key"}))}function $8(Z,$){Z.writeHead(400,{"Content-Type":"application/json"}),Z.end(JSON.stringify({code:"invalid_request",message:$}))}function n1(Z,$){Z.writeHead(200,{"Content-Type":"application/json"}),Z.end(JSON.stringify($))}function wQ(Z,$){let Q=$==="GZIP"?G7(Buffer.from(Z,"base64")).toString("utf8"):Z,X=JSON.parse(Q);if(!X||typeof X!=="object"||Array.isArray(X))throw Error("OAA payload must be a JSON object");return X}function AQ(Z){return Z==="GZIP"?"GZIP":"NONE"}function IQ(Z,$,Q,X,Y){RQ({providerId:Z,datasourceId:$,compressionType:Q,isIncremental:X.is_incremental===!0,payload:X,via:Y,receivedAt:new Date().toISOString()})}async function CQ(Z,$,Q){if(!$4(Q))return!1;if(!B7(Z))return D7($),!0;let X=Q.match(J7);if(X&&Z.method==="POST")return await F7(Z,$,X[1]??"",X[2]??""),!0;let Y=Q.match(W7);if(Y&&Z.method==="POST")return await V7(Z,$,Y[1]??"",Y[2]??""),!0;let K=Q.match(j7);if(K&&Z.method==="PUT")return await O7(Z,$,K[3]??"",Number(K[4]??"0")),!0;let H=Q.match(N7);if(H&&Z.method==="POST")return await M7(Z,$,H[1]??"",H[2]??"",H[3]??""),!0;return $.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({code:"not_found",message:"Unknown OAA endpoint"})),!0}async function F7(Z,$,Q,X){let Y=await i1(Z),K;try{K=JSON.parse(Y)}catch{$8($,"OAA push body must be valid JSON");return}let H=K.json_data;if(typeof H!=="string"){$8($,"OAA push envelope must include json_data:string");return}let J=AQ(K.compression_type);try{let G=wQ(H,J);IQ(Q,X,J,G,"single")}catch(G){$8($,G instanceof Error?G.message:"Failed to decode OAA payload");return}n1($,{success:!0,sync_token:`ecv-${Date.now()}`,data_source_id:X})}async function V7(Z,$,Q,X){let Y=await i1(Z),K={};try{K=Y?JSON.parse(Y):{}}catch{$8($,"OAA multipart start body must be valid JSON");return}let H=`ecv-upload-${Date.now()}-${Math.random().toString(36).slice(2)}`;qQ(H,{providerId:Q,datasourceId:X,compressionType:AQ(K.compression_type),parts:new Map}),n1($,{upload_id:H})}async function O7(Z,$,Q,X){let Y=await i1(Z),K;try{K=JSON.parse(Y)}catch{$8($,"OAA multipart part body must be valid JSON");return}let H=typeof K.data==="string"?K.data:"";if(!TQ(Q,X,H)){$8($,`Unknown OAA multipart upload ${Q}`);return}n1($,{upload_id:Q,sequence:X})}async function M7(Z,$,Q,X,Y){await i1(Z);let K=UQ(Y);if(!K){$8($,`Unknown OAA multipart upload ${Y}`);return}let H=[...K.parts.entries()].sort(([G],[W])=>G-W).map(([,G])=>G),J=H.join("");try{let G=wQ(J,K.compressionType);IQ(Q,X,K.compressionType,G,"multipart")}catch(G){$8($,G instanceof Error?G.message:"Failed to decode OAA multipart payload");return}n1($,{success:!0,sync_token:`ecv-${Date.now()}`,upload_id:Y,parts:H.length})}import{createServer as X6}from"node:http";var I0="application/scim+json";async function I8(Z){return new Promise(($,Q)=>{let X="";Z.on("data",(Y)=>{X+=Y.toString()}),Z.on("end",()=>{try{$(X?JSON.parse(X):{})}catch(Y){Q(Y)}}),Z.on("error",Q)})}function L7(Z,$){if(!Z.headers.authorization?.startsWith("Bearer "))return $.writeHead(401,{"Content-Type":I0}),$.end(JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:Error"],status:"401",detail:"Authorization required"})),!1;return!0}function f(Z,$,Q,X){Z.writeHead($,{"Content-Type":I0}),Z.end(JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:Error"],status:String($),scimType:X,detail:Q}))}function EQ(Z,$,Q){Z.writeHead(200,{"Content-Type":I0}),Z.end(JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:ListResponse"],totalResults:Q??$.length,startIndex:1,itemsPerPage:$.length,Resources:$}))}async function SQ(Z,$,Q){let X=Q.replace(/^\/iam/,"");if(!L7(Z,$))return;let Y=X.match(/^\/Users(?:\/([^/]+))?$/),K=X.match(/^\/Groups(?:\/([^/]+))?$/);if(Y){let H=Y[1];await _7(Z,$,H);return}if(K){let H=K[1];await P7(Z,$,H);return}f($,404,"Resource not found")}async function _7(Z,$,Q){if(Q)switch(Z.method){case"GET":R7($,Q);break;case"PUT":await T7(Z,$,Q);break;case"PATCH":await U7(Z,$,Q);break;case"DELETE":w7($,Q);break;default:f($,405,"Method not allowed")}else switch(Z.method){case"GET":x7($);break;case"POST":await q7(Z,$);break;default:f($,405,"Method not allowed")}}async function P7(Z,$,Q){if(Q)switch(Z.method){case"GET":I7($,Q);break;case"PUT":await E7(Z,$,Q);break;case"PATCH":await S7(Z,$,Q);break;case"DELETE":v7($,Q);break;default:f($,405,"Method not allowed")}else switch(Z.method){case"GET":A7($);break;case"POST":await C7(Z,$);break;default:f($,405,"Method not allowed")}}function x7(Z){let $=WQ();EQ(Z,$)}function R7(Z,$){let Q=u1($);if(!Q){f(Z,404,`User ${$} not found`,"invalidValue");return}Z.writeHead(200,{"Content-Type":I0}),Z.end(JSON.stringify(Q))}async function q7(Z,$){try{let Q=await I8(Z);if(!Q.userName){f($,400,"userName is required","invalidValue");return}let X={id:W1(),schemas:Q.schemas||["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],userName:Q.userName,displayName:Q.displayName,name:Q.name,emails:Q.emails,active:Q.active??!0,externalId:Q.externalId,groups:[],"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":Q["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"]},Y=jQ(X);$.writeHead(201,{"Content-Type":I0}),$.end(JSON.stringify(Y))}catch(Q){f($,400,"Invalid request body")}}async function T7(Z,$,Q){if(!u1(Q)){f($,404,`User ${Q} not found`,"invalidValue");return}try{let Y=await I8(Z),K=r$(Q,{...Y,id:Q});if(K)$.writeHead(200,{"Content-Type":I0}),$.end(JSON.stringify(K));else f($,404,`User ${Q} not found`)}catch(Y){f($,400,"Invalid request body")}}async function U7(Z,$,Q){let X=u1(Q);if(!X){f($,404,`User ${Q} not found`,"invalidValue");return}try{let K=(await I8(Z)).Operations||[],H={...X};for(let G of K)if(G.op==="replace"&&G.path&&G.value!==void 0){if(G.path==="displayName")H.displayName=G.value;else if(G.path==="active")H.active=G.value;else if(G.path.startsWith("name.")){let W=G.path.split(".")[1];H.name={...H.name,[W]:G.value}}}else if(G.op==="add"&&G.path&&G.value!==void 0){if(G.path==="emails")H.emails=[...H.emails||[],...G.value||[]]}else if(G.op==="remove"&&G.path){if(G.path==="displayName")H.displayName=void 0}let J=r$(Q,H);if(J)$.writeHead(200,{"Content-Type":I0}),$.end(JSON.stringify(J));else f($,404,`User ${Q} not found`)}catch(Y){f($,400,"Invalid request body")}}function w7(Z,$){if(!NQ($)){f(Z,404,`User ${$} not found`,"invalidValue");return}Z.writeHead(204),Z.end()}function A7(Z){let $=BQ();EQ(Z,$)}function I7(Z,$){let Q=l1($);if(!Q){f(Z,404,`Group ${$} not found`,"invalidValue");return}Z.writeHead(200,{"Content-Type":I0}),Z.end(JSON.stringify(Q))}async function C7(Z,$){try{let Q=await I8(Z);if(!Q.displayName){f($,400,"displayName is required","invalidValue");return}let X={id:W1(),schemas:Q.schemas||["urn:ietf:params:scim:schemas:core:2.0:Group"],displayName:Q.displayName,externalId:Q.externalId,members:Q.members||[]},Y=DQ(X);$.writeHead(201,{"Content-Type":I0}),$.end(JSON.stringify(Y))}catch(Q){f($,400,"Invalid request body")}}async function E7(Z,$,Q){if(!l1(Q)){f($,404,`Group ${Q} not found`,"invalidValue");return}try{let Y=await I8(Z),K=t$(Q,{...Y,id:Q});if(K)$.writeHead(200,{"Content-Type":I0}),$.end(JSON.stringify(K));else f($,404,`Group ${Q} not found`)}catch(Y){f($,400,"Invalid request body")}}async function S7(Z,$,Q){let X=l1(Q);if(!X){f($,404,`Group ${Q} not found`,"invalidValue");return}try{let K=(await I8(Z)).Operations||[],H={...X};for(let G of K)if(G.op==="replace"&&G.path&&G.value!==void 0){if(G.path==="displayName")H.displayName=G.value}else if(G.op==="add"&&G.path&&G.value!==void 0){if(G.path==="members")H.members=[...H.members||[],...G.value||[]]}else if(G.op==="remove"&&G.path){if(G.path.startsWith("members[")){let W=G.path.match(/members\[value eq "([^"]+)"\]/);if(W)H.members=(H.members||[]).filter((j)=>j.value!==W[1])}}let J=t$(Q,H);if(J)$.writeHead(200,{"Content-Type":I0}),$.end(JSON.stringify(J));else f($,404,`Group ${Q} not found`)}catch(Y){f($,400,"Invalid request body")}}function v7(Z,$){if(!FQ($)){f(Z,404,`Group ${$} not found`,"invalidValue");return}Z.writeHead(204),Z.end()}var y7="http://localhost:3515/sso",Q4=3600,vQ=86400,b7=300;async function Z4(Z){return new Promise(($,Q)=>{let X="";Z.on("data",(Y)=>{X+=Y.toString()}),Z.on("end",()=>{$(new URLSearchParams(X))}),Z.on("error",Q)})}async function yQ(Z,$,Q,X){let Y=Q.replace(/^\/sso/,""),K=X?`${X}/sso`:y7;if(Z.method==="GET"&&Y==="/authorize"){await h7(Z,$);return}if(Z.method==="POST"&&Y==="/token"){await m7(Z,$,K);return}if(Z.method==="GET"&&Y==="/certs"){g7($);return}if(Z.method==="POST"&&Y==="/revoke"){await p7(Z,$);return}if(Z.method==="GET"&&Y==="/userinfo"){u7(Z,$);return}if(Z.method==="GET"&&Y==="/logout"){l7(Z,$);return}if(Z.method==="POST"&&Y==="/logout/backchannel"){await d7(Z,$);return}if(Z.method==="GET"&&Y==="/.well-known/openid-configuration"){c7($,K);return}$.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"not_found"}))}async function h7(Z,$){let X=new URL(Z.url||"",`http://${Z.headers.host}`).searchParams,Y=X.get("client_id"),K=X.get("redirect_uri"),H=X.get("response_type"),J=X.get("scope")||"openid",G=X.get("state"),W=X.get("nonce"),j=X.get("code_challenge"),N=X.get("code_challenge_method");if(!Y||!K||!H){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_request",error_description:"Missing required parameters"}));return}if(H!=="code"){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"unsupported_response_type",error_description:"Only code response type is supported"}));return}let D=j1(),F=o$(32),M={code:F,userId:D.id,clientId:Y,redirectUri:K,scope:J,codeChallenge:j||void 0,codeChallengeMethod:N||void 0,state:G||void 0,nonce:W||void 0,createdAt:new Date,expiresAt:new Date(Date.now()+b7*1000)};VQ(M);let O=new URL(K);if(O.searchParams.set("code",F),G)O.searchParams.set("state",G);$.writeHead(302,{Location:O.toString()}),$.end()}async function m7(Z,$,Q){let X=await Z4(Z),Y=X.get("grant_type");if(Y==="authorization_code")await k7(X,$,Q);else if(Y==="refresh_token")await f7(X,$,Q);else $.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"unsupported_grant_type",error_description:"Only authorization_code and refresh_token are supported"}))}async function k7(Z,$,Q){let X=Z.get("code"),Y=Z.get("redirect_uri"),K=Z.get("code_verifier");if(!X||!Y){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_request",error_description:"Missing code or redirect_uri"}));return}let H=OQ(X);if(!H){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_grant",error_description:"Invalid or expired authorization code"}));return}if(H.redirectUri!==Y){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_grant",error_description:"Redirect URI mismatch"}));return}if(H.codeChallenge&&H.codeChallengeMethod==="S256"){if(!K||!GQ(K,H.codeChallenge)){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_grant",error_description:"Invalid code verifier"}));return}}MQ(X);let J=W1();PQ({id:J,userId:H.userId,createdAt:new Date});let G=j1(),W=bQ(G,H.clientId,H.scope,J,Q,H.nonce);$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify(W))}async function f7(Z,$,Q){let X=Z.get("refresh_token");if(!X){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_request",error_description:"Missing refresh_token"}));return}let Y=_Q(X);if(!Y||Y.expiresAt<new Date){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_grant",error_description:"Invalid or expired refresh token"}));return}s$(X);let K=j1(),H=bQ(K,Y.clientId,Y.scope,Y.sessionId,Q);$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify(H))}function bQ(Z,$,Q,X,Y,K){let H=Math.floor(Date.now()/1000),J={iss:Y,sub:Z.id,aud:$,...K?{nonce:K}:{},sid:X,auth_time:H,email:Z.email,email_verified:!0,name:Z.name,given_name:Z.givenName,family_name:Z.familyName,preferred_username:Z.userName,picture:Z.picture},G={iss:Y,sub:Z.id,aud:$,scope:Q,sid:X},W=D8(J,Q4),j=D8(G,Q4),N=o$(64),D={token:N,userId:Z.id,clientId:$,scope:Q,sessionId:X,createdAt:new Date,expiresAt:new Date(Date.now()+vQ*1000)};return LQ(D),{access_token:j,token_type:"Bearer",expires_in:Q4,refresh_token:N,refresh_expires_in:vQ,id_token:W,scope:Q,session_state:X}}function g7(Z){let $=J1();Z.writeHead(200,{"Content-Type":"application/json"}),Z.end(JSON.stringify($))}async function p7(Z,$){let Q=await Z4(Z),X=Q.get("token"),Y=Q.get("token_type_hint");if(X){if(Y==="refresh_token"||!Y)s$(X)}$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({success:!0}))}function u7(Z,$){if(!Z.headers.authorization?.startsWith("Bearer ")){$.writeHead(401,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_token"}));return}let X=j1();$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({sub:X.id,email:X.email,email_verified:!0,name:X.name,given_name:X.givenName,family_name:X.familyName,preferred_username:X.userName,picture:X.picture}))}function l7(Z,$){let X=new URL(Z.url||"",`http://${Z.headers.host}`).searchParams.get("post_logout_redirect_uri");if(X)$.writeHead(302,{Location:X}),$.end();else $.writeHead(200,{"Content-Type":"text/html"}),$.end("<html><body><h1>Logged out</h1></body></html>")}async function d7(Z,$){let X=(await Z4(Z)).get("logout_token");if(!X){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_request",error_description:"Missing logout_token"}));return}try{let Y=X.split(".");if(Y.length>=2){let K=JSON.parse(Buffer.from(Y[1],"base64url").toString("utf-8"));if(K.sid)xQ(K.sid)}}catch{}$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({success:!0}))}function c7(Z,$){Z.writeHead(200,{"Content-Type":"application/json"}),Z.end(JSON.stringify({issuer:$,authorization_endpoint:`${$}/authorize`,token_endpoint:`${$}/token`,userinfo_endpoint:`${$}/userinfo`,jwks_uri:`${$}/certs`,end_session_endpoint:`${$}/logout`,revocation_endpoint:`${$}/revoke`,response_types_supported:["code"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],scopes_supported:["openid","profile","email"],token_endpoint_auth_methods_supported:["client_secret_post","client_secret_basic"],claims_supported:["sub","iss","aud","exp","iat","nonce","email","email_verified","name","given_name","family_name","preferred_username","picture","sid"],code_challenge_methods_supported:["S256"],backchannel_logout_supported:!0,backchannel_logout_session_supported:!0}))}function i7(Z="http://localhost:3515"){let $="ecv-validation-app";try{$=`ecv-validation-app-${new URL(Z).port||"3515"}`}catch{}return{id:$,capabilitiez:!0,sso:{authority:`${Z}/sso`,tokenUrl:`${Z}/sso/token`,authorizationUrl:`${Z}/sso/authorize`,jwksUri:`${Z}/sso/certs`,clientId:"local-test-client",clientSecret:"local-test-secret",scope:"openid profile email",revocationEndpoint:`${Z}/sso/revoke`,endSessionEndpoint:`${Z}/sso/logout`},iam:{url:`${Z}/iam`},workload:{idpTokenUrl:`${Z}/workload/token`,jwksUri:`${Z}/workload/certs`,clientId:"local-workload-client",clientSecret:"local-workload-secret",issuer:`${Z}/workload`,audience:`${Z}/workload`},ciam:{},authz:{scopesAsEntitlements:!0,oaa:{url:Z,apiKey:p0,providerId:Q8,datasourceId:Z8,applicationName:"Enterprise Control Validation"}}}}function n7(Z){if(!Z.startsWith("/v1/secret/data/"))return;return Z.slice(16)}function o7(Z){return Z.endsWith("/redis")||Z==="redis"}function hQ(Z,$){Z.writeHead(200,{"Content-Type":"application/json"}),Z.end(JSON.stringify({request_id:"local-ecv-request",lease_id:"",renewable:!1,lease_duration:0,data:{data:$,metadata:{created_time:new Date().toISOString(),deletion_time:"",destroyed:!1,version:1}},wrap_info:null,warnings:null,auth:null}))}function a7(Z,$,Q,X){if(o7(Q)){let Y=c1();if(!Y){$.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({errors:["redis secret not configured; set REDIS_URL or setVaultRedisUrl()"]}));return}hQ($,{redisUrl:Y});return}hQ($,i7(X))}function mQ(Z,$,Q,X){let Y=Q.replace(/^\/vault/,"");if(Z.method==="GET"&&Y.startsWith("/v1/secret/data/")){if(Z.headers["x-vault-token"]!==X4){$.writeHead(403,{"Content-Type":"application/json"}),$.end(JSON.stringify({errors:["permission denied"]}));return}let H=n7(Y);if(!H){$.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({errors:["path not found"]}));return}a7(Z,$,H,X);return}$.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({errors:["path not found"]}))}var o1=3600,r7={"local-workload-client":"local-workload-secret"};async function t7(Z){return new Promise(($,Q)=>{let X="";Z.on("data",(Y)=>{X+=Y.toString()}),Z.on("end",()=>{$(new URLSearchParams(X))}),Z.on("error",Q)})}async function kQ(Z,$,Q,X){let Y=`${X.replace(/\/$/,"")}/workload`,K=Q.replace(/^\/workload/,"");if(Z.method==="POST"&&K==="/token"){await s7(Z,$,Y);return}if(Z.method==="GET"&&K==="/certs"){Q6($);return}if(Z.method==="POST"&&K==="/validate"){Z6(Z,$);return}$.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"not_found"}))}async function s7(Z,$,Q){let X=await t7(Z),Y=X.get("grant_type");if(Y==="client_credentials")e7(X,$,Q);else if(Y==="urn:ietf:params:oauth:grant-type:jwt-bearer")$6(X,$,Q);else $.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"unsupported_grant_type",error_description:"Only client_credentials and jwt-bearer are supported"}))}function e7(Z,$,Q){let X=Z.get("client_id"),Y=Z.get("client_secret"),K=Z.get("scope")||"";if(!X||!Y){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_request",error_description:"Missing client_id or client_secret"}));return}let H=r7[X];if(!H||H!==Y){$.writeHead(401,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_client",error_description:"Invalid client credentials"}));return}let G=D8({iss:Q,sub:X,aud:Q,client_id:X,scope:K},o1);$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({access_token:G,token_type:"Bearer",expires_in:o1,scope:K}))}function $6(Z,$,Q){let X=Z.get("assertion"),Y=Z.get("scope")||"";if(!X){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_request",error_description:"Missing assertion"}));return}let K=G1(X);if(!K.valid){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"invalid_grant",error_description:K.error||"Invalid JWT assertion"}));return}let H=K.payload?.sub,J={iss:Q,sub:H,aud:Q,workload_id:H,scope:Y||K.payload?.scope||""},G=D8(J,o1);$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({access_token:G,token_type:"Bearer",expires_in:o1,scope:J.scope}))}function Q6(Z){let $=J1();Z.writeHead(200,{"Content-Type":"application/json"}),Z.end(JSON.stringify($))}function Z6(Z,$){let Q=Z.headers.authorization;if(!Q?.startsWith("Bearer ")){$.writeHead(401,{"Content-Type":"application/json"}),$.end(JSON.stringify({valid:!1,error:"Missing Authorization header"}));return}let X=Q.substring(7),Y=G1(X);if(!Y.valid){$.writeHead(401,{"Content-Type":"application/json"}),$.end(JSON.stringify({valid:!1,error:Y.error}));return}$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({valid:!0,claims:Y.payload,expiresAt:Y.payload?.exp?new Date(Y.payload.exp*1000).toISOString():void 0}))}function fQ(Z,$){let Q=Z.headers.authorization;if(!Q?.startsWith("Bearer ")){$.writeHead(401,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Unauthorized"}));return}let X=Q.substring(7),Y=G1(X);if(!Y.valid){$.writeHead(401,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Invalid token",details:Y.error}));return}$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({user:null,workload:{workloadId:Y.payload?.sub,clientId:Y.payload?.client_id,scope:Y.payload?.scope}}))}async function Y6(Z,$){if(Z.method!=="POST"){$.writeHead(405,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Method not allowed"}));return}let Q="";Z.on("data",(X)=>{Q+=X.toString()}),Z.on("end",()=>{try{let X=JSON.parse(Q);$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({received:!0,payload:X}))}catch(X){$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Invalid JSON"}))}})}var gQ=3515,pQ="localhost",X4="local-ecv-token",K6="ecv/config",H6="/vault/v1/secret/data",C0=`http://${pQ}:${gQ}`,u0=null,C8=!1,a1=new Set;async function z6(Z,$){try{let Q=await fetch(`http://${Z}:${$}/readyz`,{signal:AbortSignal.timeout(1500)});if(!Q.ok)return!1;return(await Q.json())?.service==="ecv-mock-server"}catch{return!1}}function G6(Z,$,Q){let X=`http://${$}:${Z}`,Y=`${X}${H6}`,K=`${K6}-${Z}`,H={ION_CONFIG_TYPE:"vault",ION_VAULT_URL:Y,ION_VAULT_TOKEN:X4,ION_VAULT_PATH:K};if(Q)H.REDIS_URL=Q;return{baseUrl:X,port:Z,host:$,env:H,configLocator:{path:K}}}async function J6(Z,$,Q){let X=new URL(Z.url||"/",`http://${Z.headers.host}`),Y=X.pathname;if(Q)console.log(`[ECV Server] ${Z.method} ${Y}`);if($.setHeader("Access-Control-Allow-Origin","*"),$.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, OPTIONS"),$.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization, X-Vault-Token"),Z.method==="OPTIONS"){$.writeHead(204),$.end();return}try{if(Y.startsWith("/vault"))mQ(Z,$,Y,X.origin);else if(Y.startsWith("/sso"))await yQ(Z,$,Y,X.origin);else if(Y.startsWith("/iam"))await SQ(Z,$,Y);else if(Y.startsWith("/workload"))await kQ(Z,$,Y,X.origin);else if($4(Y))await CQ(Z,$,Y);else if(Y==="/api/whoami")fQ(Z,$);else if(Y==="/webhook")Y6(Z,$);else if(Y==="/livez"||Y==="/readyz"||Y==="/")$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({status:"ok",service:"ecv-mock-server"}));else $.writeHead(404,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Not found"}))}catch(K){console.error("[ECV Server] Error handling request:",K),$.writeHead(500,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Internal server error"}))}}function W6(Z={}){let{port:$=gQ,host:Q=pQ,verbose:X=!1,redisUrl:Y}=Z,K=G6($,Q,Y);if(C8&&u0){if(X)console.log("[ECV Server] Server already running");return Promise.resolve(K)}return new Promise((H,J)=>{try{if(zQ(),e$(),Y)d1(Y);u0=X6((G,W)=>{J6(G,W,X).catch((j)=>{if(console.error("[ECV Server] Unhandled error:",j),!W.headersSent)W.writeHead(500,{"Content-Type":"application/json"}),W.end(JSON.stringify({error:"Internal server error"}))})}),u0.on("connection",(G)=>{a1.add(G),G.on("close",()=>a1.delete(G))}),u0.on("error",(G)=>{if(G.code==="EADDRINUSE")z6(Q,$).then((W)=>{if(W){if(X)console.log(`[ECV Server] Port ${$} already serving the ECV mock server; reusing it`);C8=!0,H(K)}else J(Error(`[ECV Server] Port ${$} is in use by a non-ECV process; refusing to assume ownership`))});else J(G)}),u0.listen($,Q,()=>{C8=!0,console.log(`[ECV Server] Running at http://${Q}:${$}/`),console.log("[ECV Server] Endpoints:"),console.log(` - Vault: ${K.env.ION_VAULT_URL}/${K.env.ION_VAULT_PATH}`),console.log(` - SSO: http://${Q}:${$}/sso/*`),console.log(` - IAM: http://${Q}:${$}/iam/*`),console.log(` - Workload: http://${Q}:${$}/workload/*`),console.log(` - OAA: http://${Q}:${$}/api/v1/providers/custom/*`),console.log(` - Whoami: http://${Q}:${$}/api/whoami`),console.log(` - Webhook: http://${Q}:${$}/webhook`),H(K)})}catch(G){J(G)}})}function j6(){return new Promise((Z,$)=>{if(!u0){C8=!1,Z();return}u0.close((Q)=>{if(Q){if(Q.code==="ERR_SERVER_NOT_RUNNING"){u0=null,C8=!1,Z();return}$(Q);return}u0=null,C8=!1,console.log("[ECV Server] Stopped"),Z()});for(let Q of a1)Q.destroy();a1.clear()})}function X8(Z){return typeof Z.baseUrl==="function"?Z.baseUrl():Z.baseUrl}function c(Z){let $=Z.timeout??5000;return async function(X,Y={}){let K=X8(Z),H=X.startsWith("http://")||X.startsWith("https://")?X:new URL(X,K).href,J=new AbortController,G=setTimeout(()=>J.abort(),$),W=typeof Z.headers==="function"?await Z.headers():Z.headers??{};try{return await fetch(H,{...Y,signal:J.signal,headers:{...W,...Y.headers},redirect:"manual"})}finally{clearTimeout(G)}}}async function L(Z,$){let Q=performance.now();try{let X=await $();return{name:Z,passed:!0,duration:performance.now()-Q,details:X?.details}}catch(X){return{name:Z,passed:!1,error:X instanceof Error?X.message:String(X),duration:performance.now()-Q}}}function O8(Z,$){return{name:Z,passed:!0,duration:0,details:{skipped:!0,reason:$}}}function z(Z,$){if(!Z)throw Error($)}function B(Z,$,Q){if(Z!==$)throw Error(Q??`Expected ${JSON.stringify($)}, got ${JSON.stringify(Z)}`)}function l0(Z,$,Q){let X=$["~standard"].validate(Z);if(X instanceof Promise)throw Error(Q??"Async validators are not supported in assertValid. Use the validator directly with await.");if("issues"in X){let K=X.issues.map((H)=>{let J=H.path?H.path.map(String).join("."):"";return J?`${J}: ${H.message}`:H.message});throw Error(Q??`Validation failed: ${K.join("; ")}`)}}function M0(Z){let $=new Map,Q=Z.getSetCookie?.()??[];for(let X of Q){let[Y]=X.split(";"),[K,H]=Y.split("=");if(K&&H!==void 0)$.set(K.trim(),H.trim())}return $}function d0(Z){return Array.from(Z.entries()).map(([$,Q])=>`${$}=${Q}`).join("; ")}function E(Z){return{async load(){return Z},async subscribe(){return},secret:{}}}async function C(){let Z=new S0;await Z.permissions.set({id:"read",name:"Read",description:"Read"}).ready(),await Z.permissions.set({id:"write",name:"Write",description:"Write"}).ready(),await Z.permissions.set({id:"manage",name:"Manage",description:"Manage (cascades to sub-resources)",applyToSubResources:!0}).ready(),await Z.roles.set({id:"editor",name:"Editor",description:"Editor",permissions:["read","write"]}).ready(),await Z.roles.set({id:"viewer",name:"Viewer",description:"Viewer",permissions:["read"]}).ready(),await Z.roles.set({id:"ops",name:"Ops",description:"Ops",permissions:["manage"]}).ready(),await Z.groups.set({id:"staff",name:"Staff",description:"Staff",roles:["editor"]}).ready();let $=new G4;return $.set({id:"project-a",type:"project",name:"Project A",description:"A",resources:["module-1"]}),$.set({id:"module-1",type:"module",name:"Module 1",description:"M1",parent:"project-a"}),$.set({id:"region:north",type:"region",name:"North Region",description:"North"}),$.set({id:"region:south",type:"region",name:"South Region",description:"South"}),await Z.groups.set({id:"region_north_librarian",name:"North Region Librarian",description:"Requestable entitlement scoped to the North region",requestable:!0,roles:["editor"],resources:["region:north"]}).ready(),{catalog:Z,resources:$}}function O1(){return t1({permissions:[{id:"code_read",name:"Code Read",description:"Read (code-declared)"},{id:"code_write",name:"Code Write",description:"Write (code-declared)"}],roles:[{id:"code_editor",name:"Code Editor",description:"Editor (code-declared)",permissions:["code_read","code_write"]}],groups:[{id:"code_staff",name:"Code Staff",description:"Staff (code-declared)",requestable:!0,roles:["code_editor"]}]})}async function y(Z,$,Q,X){let Y={...X,id:T0(X),status:"active"};await Z.grant(Y);let K=await y0(Y,Q);return await $.materialize(Y.id,K),Y.id}class e1 extends A{failNext=!0;materialize(Z,$){if(this.failNext)return this.failNext=!1,Promise.reject(Error("simulated transient index failure"));return super.materialize(Z,$)}}class rQ extends N6{upsert(Z){return Promise.reject(Error("simulated user metadata write failure"))}}function y8(){return{tests:[{name:"grant a group root materializes the full closure (group -> role -> permissions)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"alice",targetType:"group",target:"staff"}),z((await X.hasGroup("alice","staff")).allow,"alice should be in group staff"),z((await X.hasRole("alice","editor")).allow,"staff confers role editor"),z((await X.hasPermission("alice","read")).allow,"editor confers permission read"),z((await X.hasPermission("alice","write")).allow,"editor confers permission write"),z(!(await X.hasPermission("alice","manage")).allow,"alice should not have manage")}},{name:"diamond survival: revoking one role root keeps permissions reachable via another root",fn:async()=>{let $=await C(),Q=new w,X=new A,Y=await y(Q,X,$,{subject:"bob",targetType:"role",target:"editor"}),K=await y(Q,X,$,{subject:"bob",targetType:"role",target:"viewer"});z((await X.hasPermission("bob","read")).allow,"read present via both roles"),await Q.revoke({subject:"bob",targetType:"role",target:"editor"}),await X.removeByRoot(Y),z((await X.hasPermission("bob","read")).allow,"read survives via viewer root"),z(!(await X.hasPermission("bob","write")).allow,"write removed with editor root"),await Q.revoke({subject:"bob",targetType:"role",target:"viewer"}),await X.removeByRoot(K),z(!(await X.hasPermission("bob","read")).allow,"read gone once last root removed")}},{name:"applyToSubResources expands a resource-scoped permission across the sub-resource tree",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"carol",targetType:"permission",target:"manage",resources:["project-a"]}),z((await X.hasPermission("carol","manage","project-a")).allow,"manage on scoped resource"),z((await X.hasPermission("carol","manage","module-1")).allow,"manage cascades to sub-resource"),z(!(await X.hasPermission("carol","manage","other")).allow,"manage not on unrelated resource"),z((await X.hasPermission("carol","manage")).allow,"manage present for some resource")}},{name:"clear(subject) removes only that subject derived edges",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"dave",targetType:"role",target:"viewer"}),await y(Q,X,$,{subject:"erin",targetType:"role",target:"viewer"}),await X.clear("dave"),z(!(await X.hasPermission("dave","read")).allow,"dave edges cleared"),z((await X.hasPermission("erin","read")).allow,"erin edges retained")}},{name:"AccessAssignmentStore.list returns granted active roots",fn:async()=>{let $=new w;await $.grant({subject:"frank",targetType:"role",target:"viewer",status:"active"}),await $.grant({subject:"frank",targetType:"role",target:"editor",status:"active"});let Q=await $.list();z(Q.items.length===2,"both granted roots should be listed"),z(Q.items.every((X)=>X.targetType==="role"),"listed roots are roles")}},{name:"listByTarget returns active members of an entitlement (SCIM member projection source)",fn:async()=>{let $=new w;await $.grant({subject:"alice",targetType:"group",target:"staff",status:"active"}),await $.grant({subject:"bob",targetType:"group",target:"staff",status:"active"}),await $.grant({subject:"carol",targetType:"role",target:"viewer",status:"active"});let Q=await $.listByTarget("group","staff"),X=Q.items.map((K)=>K.subject).sort();z(Q.items.length===2,"both staff members are projected"),z(X[0]==="alice"&&X[1]==="bob","staff members are alice and bob"),z(Q.items.every((K)=>K.target==="staff"),"every projected root targets staff");let Y=await $.listByTarget("role","viewer");z(Y.items.length===1&&Y.items[0]?.subject==="carol","viewer has only carol")}},{name:"group delete semantics: revoking each listByTarget root removes membership from the closure",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"grace",targetType:"group",target:"staff"}),await y(Q,X,$,{subject:"heidi",targetType:"group",target:"staff"}),z((await X.hasPermission("grace","read")).allow,"grace inherits read via staff"),z((await X.hasPermission("heidi","read")).allow,"heidi inherits read via staff");let Y=await Q.listByTarget("group","staff");for(let K of Y.items)await Q.revoke(K),await X.removeByRoot(K.id??T0(K));z(!(await X.hasGroup("grace","staff")).allow,"grace no longer in staff"),z(!(await X.hasPermission("heidi","read")).allow,"heidi loses inherited read"),z((await Q.listByTarget("group","staff")).items.length===0,"no staff membership roots remain")}},{name:"role/permission entitlement roots round-trip through listByTarget by type",fn:async()=>{let $=new w;await $.grant({subject:"ivan",targetType:"role",target:"admin",status:"active"}),await $.grant({subject:"judy",targetType:"permission",target:"admin",status:"active"});let Q=await $.listByTarget("role","admin"),X=await $.listByTarget("permission","admin");z(Q.items.length===1&&Q.items[0]?.subject==="ivan","role admin has ivan"),z(X.items.length===1&&X.items[0]?.subject==="judy","permission admin has judy")}},{name:"resource-bound group entitlement scopes the conferred role/permissions to its resources (member-add)",fn:async()=>{let $=await C(),Q=new w,X=new A,K=((await $.catalog.groups.get("region_north_librarian"))?.resources??[]).map((H)=>typeof H==="string"?H:H.id);z(K.length===1&&K[0]==="region:north","entitlement carries the region binding"),await y(Q,X,$,{subject:"nina",targetType:"group",target:"region_north_librarian",resources:K}),z((await X.hasRole("nina","editor","region:north")).allow,"editor scoped to region:north"),z((await X.hasPermission("nina","read","region:north")).allow,"read scoped to region:north"),z((await X.hasPermission("nina","write","region:north")).allow,"write scoped to region:north"),z(!(await X.hasPermission("nina","read","region:south")).allow,"access does not leak to a different region"),z(!(await X.hasRole("nina","editor","region:south")).allow,"role does not leak to region:south")}},{name:"AccessCatalogStore list honors the requestable filter (SCIM assignable set)",fn:async()=>{let{catalog:$}=await C(),Q=await $.groups.list({filter:{requestable:!0}});z(Q.items.every((Y)=>Y.requestable===!0),"requestable filter returns only requestable groups"),z(Q.items.some((Y)=>Y.id==="region_north_librarian"),"the requestable region entitlement is included"),z(!Q.items.some((Y)=>Y.id==="staff"),"the non-requestable staff group is excluded");let X=await $.groups.list({filter:{requestable:!1}});z(X.items.some((Y)=>Y.id==="staff"),"staff (requestable unset) is in the non-requestable set"),z(!X.items.some((Y)=>Y.id==="region_north_librarian"),"the requestable entitlement is excluded from the non-requestable set")}},{name:"ion.authz.oaa becomes ready once OAA config (url/providerId/datasourceId) is applied",fn:async()=>{let $=q(E({id:"oaa-ready-test",authz:{oaa:{url:"https://veza.example",providerId:"prov",datasourceId:"ds"}}}),{validators:T()});z(!$.authz.oaa.isReady(),"OAA should not be ready before RemoteConfig is applied"),await $.ready(1000),z($.authz.oaa.isReady(),"OAA should be ready once url/providerId/datasourceId are present"),await $.authz.oaa.ready(1000),z($.readiness().modules.authz.ready===!0,"authz is ready once OAA is configured and applied"),$.close?.()}},{name:"ion.authz.oaa stays not ready and sync throws not_configured without OAA config",fn:async()=>{let $=q(E({id:"no-oaa-test"}),{validators:T()});await $.ready(1000),z(!$.authz.oaa.isReady(),"OAA should not be ready when no OAA config is present");let Q=$.readiness();z(Q.modules.authz.active===!0,"authz module is always active"),z(Q.modules.authz.ready===!0,"authz is ready when OAA is not configured"),z($.requireReady(["authz"]).ok===!0,"requireReady([authz]) is ok without OAA configured");let X;try{await $.authz.oaa.sync({})}catch(Y){X=Y.code}z(X==="not_configured","sync should throw OaaError with code not_configured when unconfigured"),$.close?.()}},{name:"ion.authz.has* delegate to the wired AccessIndex (allow / deny)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"alice",targetType:"role",target:"editor"});let Y=q(E({id:"authz-has-test"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000),z((await Y.authz.hasRole("alice","editor")).allow,"ion.authz.hasRole resolves a granted role"),z((await Y.authz.hasPermission("alice","read")).allow,"ion.authz.hasPermission resolves a permission conferred by the role"),z(!(await Y.authz.hasPermission("alice","manage")).allow,"ion.authz.hasPermission denies a permission the subject was never granted"),Y.close?.()}},{name:"ion.authz.has* deny by default (not_configured) when no AccessIndex is wired",fn:async()=>{let $=q(E({id:"authz-noindex-test"}),{validators:T()});await $.ready(1000);let Q=await $.authz.hasPermission("alice","read");z(!Q.allow,"denies when no AccessIndex is configured"),z(Q.metadata?.reason==="access_index_not_configured","decision metadata explains the missing AccessIndex"),$.close?.()}},{name:"standalone hasPermission(request, ion) returns unauthenticated -> 401 without a session",fn:async()=>{let $=q(E({id:"authz-helper-test"}),{validators:T()});await $.ready(1000);let Q=await B0(new Request("https://example.test/"),$,"read");z(!Q.allow,"no SSO session -> not allowed"),z(Q.reason==="unauthenticated","reason is unauthenticated when no user is resolved"),z(Q.response().status===401,"response() is 401 for the unauthenticated case"),$.close?.()}},{name:"subjectType isolates principals that share an id (user:123 vs customer:123)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"123",subjectType:"user",targetType:"role",target:"editor"}),await y(Q,X,$,{subject:"123",subjectType:"customer",targetType:"role",target:"viewer"}),z((await X.hasRole("123","editor",void 0,"user")).allow,"user:123 has editor"),z((await X.hasPermission("123","write",void 0,"user")).allow,"user:123 has write via editor"),z(!(await X.hasRole("123","editor",void 0,"customer")).allow,"customer:123 does NOT have editor"),z((await X.hasRole("123","viewer",void 0,"customer")).allow,"customer:123 has viewer"),z(!(await X.hasRole("123","viewer",void 0,"user")).allow,"user:123 does NOT have viewer"),z(!(await X.hasPermission("123","write",void 0,"customer")).allow,"customer:123 has no write"),await X.clear("123","user"),z(!(await X.hasRole("123","editor",void 0,"user")).allow,"user:123 edges cleared"),z((await X.hasRole("123","viewer",void 0,"customer")).allow,"customer:123 edges retained")}},{name:"ion.authz.has* honor subjectType (a customer grant is invisible to the default user scope)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"cust-1",subjectType:"customer",targetType:"role",target:"viewer"});let Y=q(E({id:"authz-subjecttype-test"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000),z((await Y.authz.hasRole("cust-1","viewer",void 0,"customer")).allow,"customer grant resolves under the customer subjectType"),z((await Y.authz.hasPermission("cust-1","read",void 0,"customer")).allow,"customer inherits read via viewer"),z(!(await Y.authz.hasRole("cust-1","viewer")).allow,"same id under default (user) subjectType is denied"),z(!(await Y.authz.hasRole("cust-1","viewer",void 0,"user")).allow,"same id under explicit user subjectType is denied"),Y.close?.()}},{name:"standalone has* default 'any' resolves no principals without a session -> unauthenticated/401",fn:async()=>{let $=await C(),Q=new w,X=new A,Y=q(E({id:"authz-any-test"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000);let K=await B0(new Request("https://example.test/"),Y,"read");z(!K.allow,"default 'any' with no session is not allowed"),z(K.reason==="unauthenticated","no principal resolved -> unauthenticated"),z(K.subject===void 0,"no subject is reported when nothing resolved"),z(K.subjectType===void 0,"no subjectType is reported when nothing resolved"),z(K.response().status===401,"unauthenticated -> 401"),Y.close?.()}},{name:"subjectType isolates a workload from a user that shares an id (workload:abc vs user:abc)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"abc",subjectType:"workload",targetType:"role",target:"ops"}),await y(Q,X,$,{subject:"abc",subjectType:"user",targetType:"role",target:"viewer"}),z((await X.hasRole("abc","ops",void 0,"workload")).allow,"workload:abc has ops"),z((await X.hasPermission("abc","manage",void 0,"workload")).allow,"workload:abc has manage via ops"),z(!(await X.hasRole("abc","ops",void 0,"user")).allow,"user:abc does NOT have ops"),z(!(await X.hasPermission("abc","manage",void 0,"user")).allow,"user:abc does NOT inherit manage"),z((await X.hasRole("abc","viewer",void 0,"user")).allow,"user:abc has viewer"),z(!(await X.hasRole("abc","viewer",void 0,"workload")).allow,"workload:abc does NOT have viewer"),await X.clear("abc","workload"),z(!(await X.hasRole("abc","ops",void 0,"workload")).allow,"workload:abc edges cleared"),z((await X.hasRole("abc","viewer",void 0,"user")).allow,"user:abc edges retained")}},{name:"ion.authz.has* honor the workload subjectType (a workload grant is invisible to the default user scope)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"svc-1",subjectType:"workload",targetType:"role",target:"ops"});let Y=q(E({id:"authz-workload-test"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000),z((await Y.authz.hasRole("svc-1","ops",void 0,"workload")).allow,"workload grant resolves under the workload subjectType"),z((await Y.authz.hasPermission("svc-1","manage",void 0,"workload")).allow,"workload inherits manage via ops"),z(!(await Y.authz.hasRole("svc-1","ops")).allow,"same id under default (user) subjectType is denied"),Y.close?.()}},{name:"standalone hasScope* return unauthenticated -> 401 when no workload token is present",fn:async()=>{let $=q(E({id:"authz-scope-noauth-test"}),{validators:T()});await $.ready(1000);let Q=new Request("https://example.test/"),X=await z4(Q,$,"oaa:sync","workload");z(!X.allow&&X.reason==="unauthenticated","hasScope with no token -> unauthenticated"),z(X.response().status===401,"hasScope unauthenticated -> 401");let Y=await oQ(Q,$,["a","b"],"workload");z(!Y.allow&&Y.reason==="unauthenticated","hasAnyScope with no token -> unauthenticated");let K=await H4(Q,$,["a","b"],"workload");z(!K.allow&&K.reason==="unauthenticated","hasAllScope with no token -> unauthenticated"),$.close?.()}},{name:"RemoteConfig.authz.scopesAsEntitlements is read into ion.config (gates has* scope auto-honor)",fn:async()=>{let $=q(E({id:"scopes-flag-on",authz:{scopesAsEntitlements:!0}}),{validators:T()});await $.ready(1000),z($.config?.authz?.scopesAsEntitlements===!0,"flag true is applied to ion.config"),$.close?.();let Q=q(E({id:"scopes-flag-off"}),{validators:T()});await Q.ready(1000),z(!Q.config?.authz?.scopesAsEntitlements,"flag defaults to falsy when unset (secure default)"),Q.close?.()}},{name:"ion.authz.reconcile() re-derives the closure after a role->permission catalog edit (§1.2a)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"drift",targetType:"role",target:"viewer"});let Y=q(E({id:"reconcile-roleedit"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000),z((await Y.authz.hasPermission("drift","read")).allow,"viewer confers read"),z(!(await Y.authz.hasPermission("drift","write")).allow,"viewer does not confer write yet"),await $.catalog.roles.set({id:"viewer",name:"Viewer",description:"Viewer",permissions:["read","write"]}).ready();let K=await Y.authz.reconcile();z(K.roots===1,"one live root re-expanded (only subject in the system)"),z((await Y.authz.hasPermission("drift","write")).allow,"reconcile materializes the new write edge"),z((await Y.authz.hasPermission("drift","read")).allow,"read still present after reconcile"),Y.close?.()}},{name:"ion.authz.reconcile() extends an applyToSubResources closure to a newly added child resource (§1.2a)",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"tree",targetType:"permission",target:"manage",resources:["project-a"]});let Y=q(E({id:"reconcile-tree"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000),z((await Y.authz.hasPermission("tree","manage","module-1")).allow,"manage cascades to the existing child module-1"),z(!(await Y.authz.hasPermission("tree","manage","module-2")).allow,"module-2 does not exist yet"),$.resources.set({id:"module-2",type:"module",name:"Module 2",description:"M2",parent:"project-a"}),$.resources.set({id:"project-a",type:"project",name:"Project A",description:"A",resources:["module-1","module-2"]}),await Y.authz.reconcile(),z((await Y.authz.hasPermission("tree","manage","module-2")).allow,"reconcile cascades manage to the newly added child"),Y.close?.()}},{name:"ion.authz.reconcile() rebuilds set-attributed edges so an overlapping permission survives one revoke",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"olap",targetType:"role",target:"editor"}),await y(Q,X,$,{subject:"olap",targetType:"role",target:"viewer"});let Y=q(E({id:"reconcile-overlap"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await Y.ready(1000),await Q.revoke({subject:"olap",targetType:"role",target:"editor"});let K=await Y.authz.reconcile();z(K.roots===1,"only the surviving viewer root remains live"),z((await Y.authz.hasPermission("olap","read")).allow,"read survives via the viewer root"),z(!(await Y.authz.hasPermission("olap","write")).allow,"write is removed with the editor root"),Y.close?.()}},{name:"partial-failure retry converges: a failed index materialize is repaired by re-grant + re-materialize (replaces §1.2b)",fn:async()=>{let $=await C(),Q=new w,X=new e1,Y={subject:"retry",targetType:"role",target:"viewer",status:"active"};Y.id=T0(Y),await Q.grant(Y);let K=!1;try{let J=await y0(Y,$);await X.materialize(Y.id,J)}catch{K=!0}z(K,"the first index write fails (simulated transient error)"),z(!(await X.hasPermission("retry","read")).allow,"no derived access after the failed attempt"),await Q.grant(Y);let H=await y0(Y,$);await X.materialize(Y.id,H),z((await X.hasPermission("retry","read")).allow,"retry re-materializes the closure"),z((await Q.listBySubject("retry","user")).items.length===1,"exactly one root persisted (grant is idempotent; no duplicate)"),await X.removeByRoot(Y.id),z(!(await X.hasPermission("retry","read")).allow,"access cleanly removed; no orphan rows linger")}},{name:"revoke idempotency: revoking an absent root is a no-op and a retried revoke still denies",fn:async()=>{let $=await C(),Q=new w,X=new A,Y=await y(Q,X,$,{subject:"idem",targetType:"role",target:"viewer"});z((await X.hasPermission("idem","read")).allow,"granted read is present"),await Q.revoke({subject:"idem",targetType:"role",target:"viewer"}),await X.removeByRoot(Y),z(!(await X.hasPermission("idem","read")).allow,"read denied after revoke"),await Q.revoke({subject:"idem",targetType:"role",target:"viewer"}),await X.removeByRoot(Y),z((await Q.listBySubject("idem","user")).items.length===0,"no roots remain after idempotent revoke"),z(!(await X.hasPermission("idem","read")).allow,"retry of an absent revoke keeps access denied")}},{name:"user delete semantics (§1.19): revoking each listBySubject root removes the subject from the closure",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"leaver",targetType:"group",target:"staff"}),await y(Q,X,$,{subject:"leaver",targetType:"role",target:"ops"}),z((await X.hasPermission("leaver","read")).allow,"leaver inherits read via staff"),z((await X.hasPermission("leaver","manage")).allow,"leaver holds manage via ops");let Y=await Q.listBySubject("leaver","user");z(Y.items.length===2,"both of the leaver roots are enumerated by listBySubject");for(let K of Y.items)await Q.revoke(K),await X.removeByRoot(K.id??T0(K));z(!(await X.hasGroup("leaver","staff")).allow,"group membership revoked on delete"),z(!(await X.hasPermission("leaver","read")).allow,"inherited read denied on delete"),z(!(await X.hasPermission("leaver","manage")).allow,"role permission denied on delete"),z((await Q.listBySubject("leaver","user")).items.length===0,"no assignment roots remain")}},{name:"ion.authz.reconcile() rebuilds live subjects from roots and sweeps orphan index rows",fn:async()=>{let $=await C(),Q=new w,X=new A,Y={subject:"live",targetType:"role",target:"editor",status:"active"};Y.id=T0(Y),await Q.grant(Y),z(!(await X.hasPermission("live","read")).allow,"live subject has no index rows pre-reconcile"),await X.materialize("orphan-root",[{subject:"orphan",targetType:"role",target:"viewer",rootId:"orphan-root"},{subject:"orphan",targetType:"permission",target:"read",rootId:"orphan-root"}]),z((await X.hasPermission("orphan","read")).allow,"orphan rows are present pre-reconcile");let K=q(E({id:"reconcile-full"}),{validators:T(),stores:{accessStore:Q,accessIndex:X,catalogStore:$.catalog,resources:$.resources}});await K.ready(1000);let H=await K.authz.reconcile();z(H.subjects>=2,"the full reconcile visited both the live and orphan subjects"),z((await K.authz.hasPermission("live","read")).allow,"full reconcile materializes the live root"),z((await K.authz.hasPermission("live","write")).allow,"editor closure rebuilt for the live subject"),z(!(await K.authz.hasPermission("orphan","read")).allow,"orphan index rows are swept"),z(H.cleared>=1,"the orphan subject is reported as cleared"),K.close?.()}},{name:"§8.4 id parity: two roots with the same subject/target but different resources do NOT collapse",fn:async()=>{let $={subject:"pat",targetType:"role",target:"editor",resources:["region:north"]},Q={subject:"pat",targetType:"role",target:"editor",resources:["region:south"]};z(T0($)!==T0(Q),"different resource scopes yield different root ids"),z(T0($)===T0({...$}),"same inputs yield a stable root id");let X=new w;await X.grant({...$,id:T0($),status:"active"}),await X.grant({...Q,id:T0(Q),status:"active"});let Y=await X.listByTarget("role","editor");z(Y.items.length===2,"both resource-scoped roots coexist (no collapse)"),z(uQ(" editor ")==="editor","accessRefId trims string ids"),z(uQ({id:"editor"})==="editor","accessRefId resolves an object ref by id")}},{name:"§7.4 listByTargets batches a page of member projections into one store call (no N+1)",fn:async()=>{let $=0,Q=0;class X extends w{async listByTarget(...H){return $+=1,super.listByTarget(...H)}async listByTargets(...H){return Q+=1,super.listByTargets(...H)}}let Y=new X;await Y.grant({subject:"m1",targetType:"group",target:"g1",status:"active"}),await Y.grant({subject:"m2",targetType:"group",target:"g1",status:"active"}),await Y.grant({subject:"m3",targetType:"role",target:"r1",status:"active"});let K=await Y.listByTargets([{targetType:"group",target:"g1"},{targetType:"role",target:"r1"},{targetType:"permission",target:"p1"}]);z(Q===1,"one batched call covers the whole page"),z($===0,"the batched path does not fan out to per-item listByTarget"),z((K.get("group:g1")??[]).length===2,"g1 members batched"),z((K.get("role:r1")??[]).length===1,"r1 members batched"),z((K.get("permission:p1")??[]).length===0,"an empty target returns an empty list")}},{name:"§7.4 hasAll over N targets resolves in one pass (bounded) and denies if any is missing",fn:async()=>{let $=await C(),Q=new w,X=new A;await y(Q,X,$,{subject:"multi",targetType:"role",target:"editor"}),await y(Q,X,$,{subject:"multi",targetType:"role",target:"viewer"}),await y(Q,X,$,{subject:"multi",targetType:"role",target:"ops"});let Y=["editor","viewer","ops"].map((H)=>({targetType:"role",target:H}));z((await X.hasAll("multi",Y)).allow,"hasAll is satisfied when every target is held");let K=[...Y,{targetType:"role",target:"admin"}];z(!(await X.hasAll("multi",K)).allow,"hasAll denies when one target is missing")}},{name:"cross-tenant isolation: grants in one store keyspace are invisible to another (distinct stores)",fn:async()=>{let $=await C(),Q={store:new w,index:new A},X={store:new w,index:new A};await y(Q.store,Q.index,$,{subject:"shared-123",targetType:"role",target:"ops"}),z((await Q.index.hasRole("shared-123","ops")).allow,"tenant A granted ops to shared-123"),z(!(await X.index.hasRole("shared-123","ops")).allow,"tenant B (separate keyspace) does NOT see tenant A grant for the same id"),z((await X.store.listBySubject("shared-123","user")).items.length===0,"tenant B store has no roots for the shared id")}},{name:"§7.4 externalId secondary index resolves a catalog entity and tracks externalId changes",fn:async()=>{let $=new S0;await $.groups.set({id:"g-ext",name:"Group Ext",description:"with external id",externalId:"EXT-1"}).ready(),z(typeof $.groups.getByExternalId==="function","in-memory entity store exposes getByExternalId"),z((await $.groups.getByExternalId?.("EXT-1"))?.id==="g-ext","externalId resolves the entity"),z(await $.groups.getByExternalId?.("NOPE")===void 0,"an unknown externalId resolves nothing"),await $.groups.set({id:"g-ext",name:"Group Ext",description:"rekeyed",externalId:"EXT-2"}).ready(),z(await $.groups.getByExternalId?.("EXT-1")===void 0,"stale externalId no longer resolves"),z((await $.groups.getByExternalId?.("EXT-2"))?.id==="g-ext","the new externalId resolves"),await $.groups.delete("g-ext"),z(await $.groups.getByExternalId?.("EXT-2")===void 0,"delete clears the externalId index")}},{name:"§7.4 CatalogAdjacencyCache memoizes catalog reads and re-reads after invalidation (same edges)",fn:async()=>{let $=await C(),Q={subject:"memo",targetType:"group",target:"staff"};Q.id=T0(Q);let X=await y0(Q,$),Y=new lQ($.catalog,$.resources),K={...$,cache:Y},H=await y0(Q,K),J=await y0(Q,K);z(r1(X,H)&&r1(H,J),"cached expansion produces the same edges as the uncached read"),await $.catalog.groups.set({id:"staff",name:"Staff",description:"Staff",roles:["editor","ops"]}).ready();let G=await y0(Q,K);z(r1(G,H),"a not-yet-invalidated memo still returns the old closure"),Y.invalidate();let W=await y0(Q,K);z(W.some((j)=>j.targetType==="permission"&&j.target==="manage"),"after invalidation the cache re-reads the catalog and reflects the new ops->manage edge")}},{name:"code-declared catalog (defineCatalog) expands a grant with NO catalogStore wired (zero store reads)",fn:async()=>{let $=O1(),Q=new w,X=new A;await y(Q,X,{code:$},{subject:"carol",targetType:"group",target:"code_staff"}),z((await X.hasGroup("carol","code_staff")).allow,"carol is in the code-declared group"),z((await X.hasRole("carol","code_editor")).allow,"the code group confers the code role"),z((await X.hasPermission("carol","code_read")).allow,"the code role confers code_read"),z((await X.hasPermission("carol","code_write")).allow,"the code role confers code_write"),z($.groups.get("code_staff")?.externalId==="code_staff","externalId is SDK-managed and defaults to the entity id")}},{name:"defineCatalog rejects duplicate ids, dangling references, and group cycles at module evaluation",fn:async()=>{let $=(Q)=>{try{return Q(),!1}catch{return!0}};z($(()=>t1({roles:[{id:"dup",name:"Dup",description:"dup"}],permissions:[{id:"dup",name:"Dup",description:"dup"}]})),"a duplicate id across kinds throws (one SCIM Groups id space)"),z($(()=>t1({groups:[{id:"g",name:"G",description:"g",roles:["missing_role"]}]})),"a dangling group->role reference throws"),z($(()=>t1({groups:[{id:"a",name:"A",description:"a",groups:["b"]},{id:"b",name:"B",description:"b",groups:["a"]}]})),"a nested-group cycle throws")}},{name:"§7.2 stableConfigHash is order-independent and agrees with deepEqualPlain equality",fn:async()=>{let $={id:"t",authz:{scopesAsEntitlements:!0},workload:{jwksUri:"https://x/certs"}},Q={workload:{jwksUri:"https://x/certs"},authz:{scopesAsEntitlements:!0},id:"t"},X={...$,extra:void 0},Y={...$,id:"t2"};z(r1($,Q),"precondition: the two objects are deep-equal"),z(E8($)===E8(Q),"key order does not change the hash"),z(E8($)===E8(X),"undefined-valued keys do not change the hash"),z(E8($)!==E8(Y),"a changed value changes the hash")}},{name:'§10.3 escapeScimFilterValue backslash-escapes `"` and `\\` for RFC 7644 filters',fn:async()=>{B(N1('a"b'),"a\\\"b","a double quote is backslash-escaped so it stays inside the filter literal"),B(N1("a\\b"),"a\\\\b","a backslash is doubled so the escape is unambiguous"),B(N1("x\\\"y"),"x\\\\\\\"y","ordering escapes backslash first, then quote"),B(N1("plain-id"),"plain-id","an ordinary id is returned unchanged");let $=`externalId eq "${N1('ev"il')}"`;B($,'externalId eq "ev\\"il"',"the composed filter carries the escaped literal")}},{name:"§7.3 a shared CatalogAdjacencyCache collapses N member expansions to O(graph) catalog reads",fn:async()=>{let $=new S0;await $.permissions.set({id:"read",name:"Read",description:"Read"}).ready(),await $.permissions.set({id:"write",name:"Write",description:"Write"}).ready(),await $.roles.set({id:"editor",name:"Editor",description:"Editor",permissions:["read","write"]}).ready(),await $.groups.set({id:"staff",name:"Staff",description:"Staff",roles:["editor"]}).ready();let Q=0,X=(j)=>({...j,get:async(N)=>{return Q+=1,j.get(N)}}),Y={groups:X($.groups),roles:X($.roles),permissions:X($.permissions)},K=Array.from({length:100},(j,N)=>`m${N}`),H=(j)=>({subject:j,targetType:"group",target:"staff",status:"active",id:`r-${j}`});Q=0;for(let j of K)await y0(H(j),{catalog:Y});let J=Q,G=new lQ(Y);Q=0;for(let j of K)await y0(H(j),{catalog:Y,cache:G});let W=Q;z(J>=K.length,`uncached expansion re-reads the graph per member (saw ${J})`),z(W<J,"a shared adjacency cache must collapse repeated node reads"),z(W<=8,`cached expansion reads the shared graph O(graph) once, not O(members×graph) (saw ${W})`)}}]}}function tQ(Z){let $=typeof Z==="function"?Z():Z;if(!$)throw Error("AuthzValidationConfig.ecv is required to run OAA projection tests");return $}function J4(Z,$){return E({id:"ecv-oaa-projection",authz:{oaa:{url:Z,apiKey:p0,providerId:Q8,datasourceId:Z8,applicationName:"ECV OAA Projection"},...$?{trustedProvisioningClients:$}:{}}})}async function E0(Z,$,Q,X){let Y=q(J4(Z,X),{validators:T()});try{return await Y.ready($),await Q(Y)}finally{Y.close?.()}}async function dQ(Z,$,Q,X,Y){let K=q(J4(Z),{validators:T(),stores:{catalogStore:Q,...Y?{resources:Y}:{}}});try{return await K.ready($),await X(K)}finally{K.close?.()}}function B1(Z,$){return E({id:"ecv-oaa-fullsync",workload:{jwksUri:`${Z}/workload/certs`,audience:`${Z}/workload`},authz:{scopesAsEntitlements:!0,oaa:{url:Z,apiKey:p0,providerId:Q8,datasourceId:Z8,applicationName:"ECV OAA Full Sync",fullSync:{enabled:$}}}})}function cQ(Z,$){return E({id:$,workload:{jwksUri:`${Z}/workload/certs`,audience:`${Z}/workload`},iam:{},authz:{scopesAsEntitlements:!0,oaa:{url:Z,apiKey:p0,providerId:Q8,datasourceId:Z8,applicationName:"ECV SCIM OAA Guard"}}})}var S8="https://ecv.test/api/ionite/authz/oaa/sync";function Y0(Z){let $=Z.applications;if(!Array.isArray($)||$.length===0)return;let Q=$[0];return Q&&typeof Q==="object"?Q:void 0}function I(Z){if(!Array.isArray(Z))return[];return Z.filter(($)=>Boolean($)&&typeof $==="object")}function M8(Z){let $=Z.timeout??5000,Q=()=>tQ(Z.ecv);return[{name:"ion.authz.oaa is ready when pointed at the mock receiver and projects an incremental user push",fn:async()=>{l(),await E0(Q().baseUrl,$,async(J)=>{z(J.authz.oaa.isReady(),"OAA should be ready with mock receiver config"),await J.authz.oaa.ready($),await J.authz.oaa.pushUser({id:"ecv-user-1",userName:"ecv-user-1@example.com",displayName:"ECV User One",email:"ecv-user-1@example.com"})});let X=k();z(X.length>=1,"expected at least one OAA push to be received");let Y=X[X.length-1];z(Y!==void 0,"expected a recorded OAA push"),z(Y.via==="single","an incremental user push should use the single :push endpoint"),z(Y.isIncremental===!0,"a mutation-scoped push should be marked incremental"),z(Y.compressionType==="GZIP","OAA client compresses payloads with gzip by default");let K=Y0(Y.payload);z(K!==void 0,"OAA payload must contain an application");let H=I(K?.local_users);z(H.some((J)=>J.unique_id==="ecv-user-1"),"the pushed user should appear in local_users")}},{name:"ion.authz.oaa projects a group definition with its role/permission assignments",fn:async()=>{l(),await E0(Q().baseUrl,$,async(G)=>{await G.authz.oaa.pushGroup({id:"ecv-group-1",name:"ECV Group One",description:"ECV projected group",roles:["ecv-role-1"],permissions:["ecv-perm-1"]})});let X=k().at(-1);z(X!==void 0,"expected a recorded OAA group push");let Y=Y0(X.payload),K=I(Y?.local_groups);z(K.some((G)=>G.unique_id==="ecv-group-1"),"the pushed group should appear in local_groups");let J=I(X.payload.identity_to_permissions).find((G)=>G.identity==="ecv-group-1");z(J!==void 0,"group role/permission assignments should project an identity entry"),z(J?.identity_type==="local_group","a catalog group is projected as a local_group principal")}},{name:"ion.authz.oaa scopes a resource-bound group role assignment (apply_to_application:false + resources)",fn:async()=>{l(),await E0(Q().baseUrl,$,async(N)=>{await N.authz.oaa.pushGroup({id:"ecv-region-librarian",name:"ECV Region Librarian",description:"resource-bound requestable entitlement",requestable:!0,roles:["ecv-librarian"],resources:["region:ecv-north"]})});let X=k().at(-1);z(X!==void 0,"expected a recorded OAA group push");let K=I(X.payload.identity_to_permissions).find((N)=>N.identity==="ecv-region-librarian");z(K!==void 0,"resource-bound group should project an identity entry");let J=I(K?.role_assignments).find((N)=>N.role==="ecv-librarian");z(J!==void 0,"the group should confer the librarian role"),z(J?.apply_to_application===!1,"a resource-bound assignment must not be application-wide"),z(Array.isArray(J?.resources)&&(J?.resources).includes("region:ecv-north"),"the role assignment carries the bound resource");let G=Y0(X.payload),j=I(G?.local_groups).find((N)=>N.unique_id==="ecv-region-librarian")?.custom_properties??{};z(Array.isArray(j.resources)&&j.resources.includes("region:ecv-north"),"group custom_properties expose the resource scope")}},{name:"ion.authz.oaa projects a permission with canonical permission types",fn:async()=>{l(),await E0(Q().baseUrl,$,async(H)=>{await H.authz.oaa.pushPermission({id:"ecv-perm-1",name:"ECV Read",description:"ECV read permission",permissionTypes:["DataRead"]})});let X=k().at(-1);z(X!==void 0,"expected a recorded OAA permission push");let K=I(X.payload.permissions).find((H)=>H.name==="ECV Read");z(K!==void 0,"the pushed permission should appear in the permissions stream"),z(Array.isArray(K?.permission_type)&&(K?.permission_type).includes("DataRead"),"permission_type should carry the canonical OAA privilege")}},{name:"ion.authz.oaa projects grant then revoke of a group membership",fn:async()=>{l();let X={subject:"ecv-user-2",targetType:"group",target:"ecv-group-1"};await E0(Q().baseUrl,$,async(W)=>{await W.authz.oaa.grant(X),await W.authz.oaa.revoke(X)});let Y=k();z(Y.length>=2,"grant and revoke should each produce a push");let H=I(Y0(Y.at(-2)?.payload??{})?.local_users).find((W)=>W.unique_id==="ecv-user-2");z(H!==void 0,"grant should project the subject as a local_user"),z(Array.isArray(H?.groups)&&(H?.groups).includes("ecv-group-1"),"granted membership should list the target group"),z(H?.operation==="add","grant projection uses the add operation");let G=I(Y0(Y.at(-1)?.payload??{})?.local_users).find((W)=>W.unique_id==="ecv-user-2");z(G?.operation==="delete","revoke projection uses the delete operation")}},{name:"ion.authz.oaa.sync uploads a full (non-incremental) snapshot",fn:async()=>{l();let X=await E0(Q().baseUrl,$,async(H)=>H.authz.oaa.sync({users:[{id:"ecv-sync-user",userName:"ecv-sync-user@example.com",displayName:"ECV Sync User",email:"ecv-sync-user@example.com"}],groups:[{id:"ecv-sync-group",name:"ECV Sync Group",description:"ECV sync group"}]}));z(X.success===!0,"full sync should report success from the receiver");let Y=k().at(-1);z(Y!==void 0,"expected a recorded OAA sync push"),z(Y.isIncremental===!1,"a full snapshot sync must not be marked incremental");let K=Y0(Y.payload);z(K!==void 0,"sync payload must contain an application"),z(I(K?.local_users).some((H)=>H.unique_id==="ecv-sync-user"),"sync should include the snapshot users"),z(I(K?.local_groups).some((H)=>H.unique_id==="ecv-sync-group"),"sync should include the snapshot groups")}},{name:"ion.authz.oaa.sync surfaces config trusted provisioning clients with eci-trusted provenance",fn:async()=>{l();let X={issuer:"https://idp.example/realms/ops",audience:["ecv-app"],clientId:"ecv-provisioner",permissions:[V1.id]};await E0(Q().baseUrl,$,async(j)=>j.authz.oaa.sync({}),[X]);let Y=k().at(-1);z(Y!==void 0,"expected a recorded OAA sync push"),z(Y.isIncremental===!1,"trusted-client union rides the full snapshot, not an incremental push");let K=Y0(Y.payload),H=I(K?.local_users).find((j)=>j.unique_id==="ecv-provisioner");z(H!==void 0,"the trusted provisioning client appears as a local_user");let J=H?.custom_properties??{};z(J.source==="eci-trusted","the trusted client carries eci-trusted provenance");let W=I(Y.payload.identity_to_permissions).find((j)=>j.identity==="ecv-provisioner");z(W!==void 0,"the trusted client projects its declared permission assignment")}},{name:"incremental group push is referentially closed (conferred role + its permission definitions ride along)",fn:async()=>{l();let X=new S0;await X.permissions.set({id:"ecv-status",name:"System Status Checks",description:"status"}).ready(),await X.roles.set({id:"ecv-admin",name:"Admin",description:"admin",permissions:["ecv-status"]}).ready();let Y={id:"ecv-supers",name:"Super Admins",description:"closure test",roles:["ecv-admin"]};await X.groups.set(Y).ready(),l(),await dQ(Q().baseUrl,$,X,async(j)=>{await j.authz.oaa.pushGroup(Y)});let K=k().at(-1);z(K!==void 0,"expected a recorded OAA group push");let H=Y0(K.payload),J=I(H?.local_roles);z(J.some((j)=>j.unique_id==="ecv-admin"),"the conferred role definition must ride along so the reference is not dangling");let W=I(K.payload.permissions).find((j)=>j.unique_id==="ecv-status");z(W!==void 0,"the permission the conferred role confers must also be included"),z(W?.name==="System Status Checks","the closed-over permission definition carries its display name (not just an id)")}},{name:"full sync nests Resource.resources as OAA sub_resources (team owns project)",fn:async()=>{l();let X={id:"team:acme",type:"team",name:"Acme",description:"Acme team",resources:["project:phoenix"]},Y={id:"project:phoenix",type:"project",name:"Phoenix",description:"Phoenix project",parent:"team:acme"};await E0(Q().baseUrl,$,async(j)=>{await j.authz.oaa.sync({resources:[X,Y]})});let K=k().at(-1);z(K!==void 0,"expected a recorded OAA sync push"),z(K.isIncremental===!1,"full sync must not be marked incremental");let H=Y0(K.payload),J=I(H?.resources);z(J.some((j)=>j.id==="team:acme"),"the team resource is a top-level OAA resource"),z(!J.some((j)=>j.id==="project:phoenix"),"the child project must not also appear as a top-level sibling");let G=J.find((j)=>j.id==="team:acme"),W=I(G?.sub_resources);z(W.some((j)=>j.id==="project:phoenix"),"the project nests under the team as sub_resources")}},{name:"incremental pushResource re-emits the top-level ancestor subtree with nested sub_resources",fn:async()=>{l();let X=new S0,Y=new G4,K={id:"team:acme",type:"team",name:"Acme",description:"Acme team",resources:["project:phoenix"]},H={id:"project:phoenix",type:"project",name:"Phoenix",description:"Phoenix project",parent:"team:acme"};Y.set(K),Y.set(H),l(),await dQ(Q().baseUrl,$,X,async(D)=>{await D.authz.oaa.pushResource(H)},Y);let J=k().at(-1);z(J!==void 0,"expected a recorded OAA resource push"),z(J.isIncremental===!0,"pushResource is an incremental push");let G=Y0(J.payload),W=I(G?.resources);z(W.some((D)=>D.id==="team:acme"),"incremental push walks parent to the top-level ancestor"),z(!W.some((D)=>D.id==="project:phoenix"),"the child is nested, not duplicated at top level");let j=W.find((D)=>D.id==="team:acme"),N=I(j?.sub_resources);z(N.some((D)=>D.id==="project:phoenix"),"the pushed project appears under the ancestor as sub_resources")}},{name:"ion.authz.oaa entity deletes emit incremental delete operations for each entity kind",fn:async()=>{l(),await E0(Q().baseUrl,$,async(K)=>{await K.authz.oaa.deleteUser("ecv-del-user"),await K.authz.oaa.deleteGroup("ecv-del-group"),await K.authz.oaa.deleteRole("ecv-del-role"),await K.authz.oaa.deletePermission("ecv-del-perm"),await K.authz.oaa.deleteResource("ecv-del-resource")});let X=k();z(X.length>=5,"each entity delete should produce a push");let Y=(K,H,J)=>X.some((G)=>{let W=Y0(G.payload)??{};return K(W,G.payload).some((j)=>j[H]===J&&j.operation==="delete")});z(Y((K)=>I(K.local_users),"unique_id","ecv-del-user"),"deleteUser marks the user with operation:delete"),z(Y((K)=>I(K.local_groups),"unique_id","ecv-del-group"),"deleteGroup marks the group with operation:delete"),z(Y((K)=>I(K.local_roles),"unique_id","ecv-del-role"),"deleteRole marks the role with operation:delete"),z(Y((K,H)=>I(H.permissions),"unique_id","ecv-del-perm"),"deletePermission marks the permission with operation:delete"),z(Y((K)=>I(K.resources),"id","ecv-del-resource"),"deleteResource marks the resource with operation:delete")}},{name:"OAA full-sync endpoint returns 404 when RemoteConfig.authz.oaa.fullSync is disabled",fn:async()=>{let X=q(B1(Q().baseUrl,!1),{validators:T(),stores:{catalogStore:new S0}});try{await X.ready($);let Y=await X.handler(new Request(S8,{method:"POST"}));z(Y.status===404,`disabled full-sync endpoint should 404, got ${Y.status}`)}finally{X.close?.()}}},{name:"OAA full-sync endpoint requires workload auth when enabled (401 without a token)",fn:async()=>{let X=q(B1(Q().baseUrl,!0),{validators:T(),stores:{catalogStore:new S0}});try{await X.ready($);let Y=await X.handler(new Request(S8,{method:"POST"}));z(Y.status!==404,"an enabled endpoint must not 404"),z(Y.status!==200,"an unauthenticated caller must not be allowed to trigger a sync"),z(Y.status===401||Y.status===403,`unauthenticated full-sync should be denied (401/403), got ${Y.status}`)}finally{X.close?.()}}},{name:"OAA full-sync endpoint runs a snapshot for an authorized workload and returns 405 for non-POST",fn:async()=>{l();let X=await i(Q().baseUrl,Y4.id),Y=q(B1(Q().baseUrl,!0),{validators:T(),stores:{catalogStore:new S0}});try{await Y.ready($);let K=await Y.handler(new Request(S8,{method:"GET"}));z(K.status===405,`full-sync endpoint is POST-only, got ${K.status} for GET`);let H=await Y.handler(new Request(S8,{method:"POST",headers:{Authorization:`Bearer ${X}`}}));z(H.status===200,`authorized full-sync should succeed, got ${H.status}`);let J=await H.json();z(J.reconcile!==void 0&&typeof J.reconcile.subjects==="number"&&typeof J.reconcile.roots==="number"&&typeof J.reconcile.cleared==="number","the endpoint bundles a full-reconcile report alongside the OAA sync result");let G=k().at(-1);z(G!==void 0,"the endpoint should drive an OAA sync push"),z(G.isIncremental===!1,"the endpoint runs a full (snapshot) sync, not an incremental push")}finally{Y.close?.()}}},{name:"routing.resolve replacing iam.users inherits the requiredPermission gate unless it opts out with null (§16.1)",fn:async()=>{let X=await C(),Y="custom-iam-users-handler",K=q(E({id:"ecv-resolve-inherit",workload:{jwksUri:`${Q().baseUrl}/workload/certs`,audience:`${Q().baseUrl}/workload`},authz:{scopesAsEntitlements:!0},iam:{}}),{validators:T(),stores:{catalogStore:X.catalog,accessStore:new w,accessIndex:new A,resources:X.resources},routing:{resolve:(H,J,G)=>{if(!G||new URL(H.url).pathname!=="/api/ionite/iam/Users")return G;let W={module:G.module,kind:"iam.users.custom",handler:async()=>new Response("custom-iam-users-handler",{status:200})};if(H.headers.get("x-ion-resolve-mode")==="public")return{...W,requiredPermission:null};return W}}});try{await K.ready($);let H=await K.handler(new Request("https://ecv.test/api/ionite/iam/Users",{headers:{"x-ion-resolve-mode":"inherit"}}));z(H.status===401||H.status===403,`a replaced route omitting requiredPermission must inherit the gate (401/403), got ${H.status}`),z(await H.text()!=="custom-iam-users-handler","the custom handler must not run when the inherited gate denies");let J=await K.handler(new Request("https://ecv.test/api/ionite/iam/Users",{headers:{"x-ion-resolve-mode":"public"}}));z(J.status===200,`requiredPermission: null must reach the handler, got ${J.status}`),z(await J.text()==="custom-iam-users-handler","the explicit public opt-out reaches the custom handler");let G=await K.handler(new Request("https://ecv.test/api/ionite/does-not-exist"));z(G.status===404,`an unmatched route returns 404, got ${G.status}`);let W=await G.json();B(W.error,"not_found",'the router 404 uses the unified JSON shape { error: "not_found" }')}finally{K.close?.()}}},{name:"a failed SCIM Group membership mutation is auto-repaired by the SDK (awaited subject reconcile within the request, no IDP retry)",fn:async()=>{let X=await i(Q().baseUrl,Y8.id),Y=await C(),K=new w,H=new e1,J=q(E({id:"ecv-scim-repair",workload:{jwksUri:`${Q().baseUrl}/workload/certs`,audience:`${Q().baseUrl}/workload`},authz:{scopesAsEntitlements:!0},iam:{}}),{validators:T(),stores:{catalogStore:Y.catalog,accessStore:K,accessIndex:H,resources:Y.resources}});try{await J.ready($);let G=await J.handler(new Request("https://ecv.test/api/ionite/iam/Groups/staff",{method:"PATCH",headers:{Authorization:`Bearer ${X}`,"Content-Type":"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"repair-user"}]}]})}));z(G.status>=400,`a failed access mutation should return a SCIM error, got ${G.status}`),z((await K.listBySubject("repair-user","user")).items.length===1,"the grant root is durably committed even though the index write failed"),z((await H.hasGroup("repair-user","staff")).allow,"the SDK awaited-repair re-materializes the subject index within the request (no IDP retry)"),z((await H.hasPermission("repair-user","read")).allow,"the repaired closure includes the role/permission edges (editor -> read via staff)")}finally{J.close?.()}}},{name:"settlement: onAccessMutation fires exactly once with summary.egress on every exit path",fn:async()=>{let X=Q().baseUrl,Y=await i(X,Y8.id),K=await i(X,aQ.id),H={jwksUri:`${X}/workload/certs`,audience:`${X}/workload`},J=(G,W)=>new Request("https://ecv.test/api/ionite/iam/Groups/staff",{method:"PATCH",headers:{Authorization:`Bearer ${W}`,"Content-Type":"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:G}]}]})});{let G=await C(),W=[],j=q(E({id:"settle-success",workload:H,authz:{scopesAsEntitlements:!0},iam:{}}),{validators:T(),authz:{onAccessMutation:(N)=>W.push(N)},stores:{catalogStore:G.catalog,accessStore:new w,accessIndex:new A,resources:G.resources}});try{await j.ready($);let N=await j.handler(J("settle-ok",Y));z(N.status>=200&&N.status<300,`success exit should be 2xx, got ${N.status}`),B(W.length,1,"onAccessMutation fires exactly once on the success exit"),z(W[0]?.egress!==void 0,"summary.egress is populated on the success exit"),z(W[0]?.access.some((D)=>D.status==="applied"),"success summary records the applied grant")}finally{j.close?.()}}{let G=await C(),W=[],j=q(E({id:"settle-access-fail",workload:H,authz:{scopesAsEntitlements:!0},iam:{}}),{validators:T(),authz:{onAccessMutation:(N)=>W.push(N)},stores:{catalogStore:G.catalog,accessStore:new w,accessIndex:new e1,resources:G.resources}});try{await j.ready($);let N=await j.handler(J("settle-fail",Y));z(N.status>=400,`access-failure exit should return a SCIM error, got ${N.status}`),B(W.length,1,"onAccessMutation fires exactly once on the access-failure exit"),z(W[0]?.egress!==void 0,"summary.egress is populated on the access-failure exit"),z(W[0]?.access.some((D)=>D.status==="failed"),"access-failure summary records the failed access mutation")}finally{j.close?.()}}{let G=[],W=q(E({id:"settle-metadata-fail",workload:H,authz:{scopesAsEntitlements:!0},iam:{}}),{validators:T(),authz:{onAccessMutation:(j)=>G.push(j)},stores:{userStore:new rQ}});try{await W.ready($);let j=await W.handler(new Request("https://ecv.test/api/ionite/iam/Users",{method:"POST",headers:{Authorization:`Bearer ${K}`,"Content-Type":"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:User"],userName:"settle-metadata-fail@example.com",externalId:"settle-metadata-fail",name:{givenName:"Settle",familyName:"Fail"},emails:[{value:"settle-metadata-fail@example.com",primary:!0}]})}));z(j.status>=400,`metadata-failure exit should return a SCIM error, got ${j.status}`),B(G.length,1,"onAccessMutation fires exactly once on the metadata-failure exit"),z(G[0]?.egress!==void 0,"summary.egress is populated on the metadata-failure exit"),z(G[0]?.metadataApplied===!1,"metadata-failure summary marks metadataApplied false")}finally{W.close?.()}}}},{name:"settlement: a degraded OAA push is non-fatal (SCIM stays 2xx, summary.egress.oaa is degraded)",fn:async()=>{let X=Q().baseUrl,Y=await i(X,Y8.id),K=await C(),H=[],J=q(E({id:"settle-oaa-degraded",workload:{jwksUri:`${X}/workload/certs`,audience:`${X}/workload`},authz:{scopesAsEntitlements:!0,oaa:{url:`${X}/__oaa_missing__`,apiKey:p0,providerId:Q8,datasourceId:Z8,applicationName:"ECV Settlement Degraded"}},iam:{}}),{validators:T(),authz:{onAccessMutation:(G)=>H.push(G)},stores:{catalogStore:K.catalog,accessStore:new w,accessIndex:new A,resources:K.resources}});try{await J.ready($),z(J.authz.oaa.isReady(),"OAA must be configured/ready so the push is actually attempted");let G=await J.handler(new Request("https://ecv.test/api/ionite/iam/Groups/staff",{method:"PATCH",headers:{Authorization:`Bearer ${Y}`,"Content-Type":"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"oaa-degraded-member"}]}]})}));z(G.status>=200&&G.status<300,`a degraded OAA push must not fail the SCIM response, got ${G.status}`),B(H.length,1,"onAccessMutation fires exactly once"),B(H[0]?.egress?.oaa.status,"degraded","the failed OAA push is recorded as degraded egress"),z(H[0]?.access.some((W)=>W.status==="applied"),"the underlying store mutation still committed (applied)")}finally{J.close?.()}}},{name:"resource-derived entitlements appear in SCIM Groups list/get and honor requestable + externalId filters",fn:async()=>{let X=await i(Q().baseUrl,s1.id),Y=await C(),K=D1(),H=q(v8(Q().baseUrl,"ecv-derived-read"),{validators:T(),stores:{catalogStore:Y.catalog,accessStore:new w,accessIndex:new A,resources:K}});try{await H.ready($);let J={Authorization:`Bearer ${X}`},G=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups",{headers:J}));z(G.status===200,`Groups list should succeed, got ${G.status}`);let W=await G.json(),j=(W.Resources??[]).map((x)=>x.id);z(j.includes("acme_team_admin"),"the derived entitlement is listed"),z(j.includes("staff"),"stored catalog entitlements are still listed"),z(W.totalResults>=j.length,"totalResults counts stored + derived entries");let F=((await(await H.handler(new Request(`https://ecv.test/api/ionite/iam/Groups?filter=${encodeURIComponent("requestable eq true")}`,{headers:J}))).json()).Resources??[]).map((x)=>x.id);z(F.includes("acme_team_admin"),"the derived entitlement passes the requestable filter"),z(!F.includes("staff"),"the non-requestable stored group is excluded");let O=await(await H.handler(new Request(`https://ecv.test/api/ionite/iam/Groups?filter=${encodeURIComponent('externalId eq "ext-acme-team-admin"')}`,{headers:J}))).json();z((O.Resources??[]).some((x)=>x.id==="acme_team_admin"),"externalId filtering matches the derived entitlement");let P=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups/acme_team_admin",{headers:J}));z(P.status===200,`derived Group GET should succeed, got ${P.status}`);let R=await P.json();z(R.id==="acme_team_admin"&&R.displayName==="Acme Admin","derived metadata projected")}finally{H.close?.()}}},{name:"a member grant on a resource-derived entitlement materializes resource-scoped access; metadata writes and delete are 403 mutability",fn:async()=>{let X=await i(Q().baseUrl,Y8.id),Y=await C(),K=D1(),H=new w,J=new A,G=q(v8(Q().baseUrl,"ecv-derived-write"),{validators:T(),stores:{catalogStore:Y.catalog,accessStore:H,accessIndex:J,resources:K}});try{await G.ready($);let W={Authorization:`Bearer ${X}`,"Content-Type":"application/scim+json"},j=await G.handler(new Request("https://ecv.test/api/ionite/iam/Groups/acme_team_admin",{method:"PATCH",headers:W,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"derived-member"}]}]})}));z(j.status<300,`member add on a derived entitlement should succeed, got ${j.status}`),z((await J.hasRole("derived-member","editor","team:acme")).allow,"the grant confers the derived role scoped to the backing resource"),z(!(await J.hasRole("derived-member","editor","region:north")).allow,"the derived scope does not leak to unrelated resources");let N=await G.handler(new Request("https://ecv.test/api/ionite/iam/Groups/acme_team_admin",{method:"PATCH",headers:W,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"displayName",value:"Renamed"}]})}));z(N.status===403,`derived metadata PATCH must be 403, got ${N.status}`);let D=await N.json();z(D.scimType==="mutability","the rejection is a SCIM mutability error");let F=await G.handler(new Request("https://ecv.test/api/ionite/iam/Groups/acme_team_admin",{method:"DELETE",headers:W}));z(F.status===403,`derived Group DELETE must be 403, got ${F.status}`)}finally{G.close?.()}}},{name:"code-declared entitlements appear in SCIM Groups list/get with SDK-managed externalId (= id) and honor filters",fn:async()=>{let X=await i(Q().baseUrl,s1.id),Y=await C(),K=q(v8(Q().baseUrl,"ecv-code-read"),{validators:T(),stores:{catalog:O1(),catalogStore:Y.catalog,accessStore:new w,accessIndex:new A}});try{await K.ready($);let H={Authorization:`Bearer ${X}`},J=await K.handler(new Request("https://ecv.test/api/ionite/iam/Groups",{headers:H}));z(J.status===200,`Groups list should succeed, got ${J.status}`);let G=await J.json(),W=(G.Resources??[]).map((x)=>x.id);z(W.includes("code_staff"),"the code-declared group is listed"),z(W.includes("code_editor"),"the code-declared role is listed"),z(W.includes("staff"),"store catalog entitlements are still listed"),z(W.indexOf("code_staff")<W.indexOf("staff"),"code entities precede store rows (deterministic pagination order)"),z(G.totalResults>=W.length,"totalResults counts code + store entries");let D=((await(await K.handler(new Request(`https://ecv.test/api/ionite/iam/Groups?filter=${encodeURIComponent("requestable eq true")}`,{headers:H}))).json()).Resources??[]).map((x)=>x.id);z(D.includes("code_staff"),"the code entitlement passes the requestable filter"),z(!D.includes("staff"),"the non-requestable store group is excluded");let O=((await(await K.handler(new Request(`https://ecv.test/api/ionite/iam/Groups?filter=${encodeURIComponent('externalId eq "code_editor"')}`,{headers:H}))).json()).Resources??[]).find((x)=>x.id==="code_editor");z(O!==void 0,"externalId filtering matches the code entitlement by its id"),z(O?.externalId==="code_editor","the projected externalId equals the id");let P=await K.handler(new Request("https://ecv.test/api/ionite/iam/Groups/code_staff",{headers:H}));z(P.status===200,`code Group GET should succeed, got ${P.status}`);let R=await P.json();z(R.id==="code_staff"&&R.externalId==="code_staff","code metadata projected")}finally{K.close?.()}}},{name:"store-less app (code catalog only): member grant/revoke works; metadata writes (incl. externalId) and delete are 403; create is 501",fn:async()=>{let X=await i(Q().baseUrl,Y8.id),Y=new w,K=new A,H=q(v8(Q().baseUrl,"ecv-code-write"),{validators:T(),stores:{catalog:O1(),accessStore:Y,accessIndex:K}});try{await H.ready($);let J={Authorization:`Bearer ${X}`,"Content-Type":"application/scim+json"},G=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups/code_staff",{method:"PATCH",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"code-member"}]}]})}));z(G.status<300,`member add on a code entitlement should succeed, got ${G.status}`),z((await K.hasRole("code-member","code_editor")).allow,"the grant confers the code role"),z((await K.hasPermission("code-member","code_read")).allow,"the code role confers its permissions");let W=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups/code_staff",{method:"PATCH",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"remove",path:'members[value eq "code-member"]'}]})}));z(W.status<300,`member remove should succeed, got ${W.status}`),z(!(await K.hasGroup("code-member","code_staff")).allow,"the revoke converges the closure");let j=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups/code_staff",{method:"PATCH",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"displayName",value:"Renamed"}]})}));z(j.status===403,`code metadata PATCH must be 403, got ${j.status}`);let N=await j.json();z(N.scimType==="mutability","the rejection is a SCIM mutability error");let D=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups/code_staff",{method:"PUT",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:Group"],id:"code_staff",externalId:"EXT-HIJACK",displayName:"Code Staff"})}));z(D.status===403,`code externalId PUT must be 403, got ${D.status}`);let F=await D.json();z(F.scimType==="mutability","the externalId rejection is a SCIM mutability error");let M=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups/code_staff",{method:"DELETE",headers:J}));z(M.status===403,`code Group DELETE must be 403, got ${M.status}`);let O=await H.handler(new Request("https://ecv.test/api/ionite/iam/Groups",{method:"POST",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:Group"],displayName:"New Group"})}));z(O.status===501,`Group create without a catalogStore must be 501, got ${O.status}`)}finally{H.close?.()}}},{name:"a SCIM create whose externalId collides with a code-declared entitlement is a 409 uniqueness error",fn:async()=>{let X=await i(Q().baseUrl,Y8.id),Y=q(v8(Q().baseUrl,"ecv-code-collision"),{validators:T(),stores:{catalog:O1(),catalogStore:new S0,accessStore:new w,accessIndex:new A}});try{await Y.ready($);let K=await Y.handler(new Request("https://ecv.test/api/ionite/iam/Groups",{method:"POST",headers:{Authorization:`Bearer ${X}`,"Content-Type":"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:Group"],displayName:"Impostor",externalId:"code_staff"})}));z(K.status===409,`a colliding externalId create must be 409, got ${K.status}`);let H=await K.json();z(H.scimType==="uniqueness","the rejection is a SCIM uniqueness error")}finally{Y.close?.()}}},{name:"OAA full sync unions code-declared entitlements with store rows and resource-derived groups",fn:async()=>{l();let X=await i(Q().baseUrl,Y4.id),Y=new S0;await Y.groups.set({id:"stored-group",name:"Stored Group",description:"stored"}).ready();let K=q(B1(Q().baseUrl,!0),{validators:T(),stores:{catalog:O1(),catalogStore:Y,resources:D1()}});try{await K.ready($);let H=await K.handler(new Request(S8,{method:"POST",headers:{Authorization:`Bearer ${X}`}}));z(H.status===200,`authorized full-sync should succeed, got ${H.status}`);let J=k().at(-1);z(J!==void 0&&J.isIncremental===!1,"the endpoint runs a full (snapshot) sync");let G=Y0(J.payload),W=I(G?.local_groups).map((D)=>D.unique_id);z(W.includes("code_staff"),"code-declared groups are unioned into the projection"),z(W.includes("stored-group"),"store catalog groups remain in the projection"),z(W.includes("acme_team_admin"),"resource-derived groups remain in the projection");let j=I(G?.local_roles).map((D)=>D.unique_id);z(j.includes("code_editor"),"code-declared roles are unioned into the projection");let N=I(J.payload.permissions).map((D)=>D.unique_id);z(N.includes("code_read"),"code-declared permissions are unioned into the projection")}finally{K.close?.()}}},{name:"ResourceProvider.changed triggers an off-request-path reconcile and an incremental OAA push",fn:async()=>{l();let X=await C(),Y=new w,K=new A;X.resources.set({id:"team:a",type:"team",name:"Team A",description:"A team"}),await y(Y,K,X,{subject:"lead",targetType:"permission",target:"manage",resources:["team:a"]});let H=q(J4(Q().baseUrl),{validators:T(),stores:{catalogStore:X.catalog,accessStore:Y,accessIndex:K,resources:X.resources}});try{await H.ready($),z(!(await K.hasPermission("lead","manage","proj:a1")).allow,"no child access before the change"),l(),X.resources.set({id:"proj:a1",type:"project",name:"Project A1",description:"A1",parent:"team:a"});let J=!1;for(let W=0;W<100&&!J;W++)await new Promise((j)=>setTimeout(j,10)),J=(await K.hasPermission("lead","manage","proj:a1")).allow;z(J,"the changed signal drives a reconcile that cascades manage to the new child");let G=!1;for(let W=0;W<100&&!G;W++)await new Promise((j)=>setTimeout(j,10)),G=k().some((j)=>{let N=Y0(j.payload),D=(F)=>F.flatMap((M)=>[M,...D(I(M.sub_resources))]);return j.isIncremental===!0&&D(I(N?.resources)).some((F)=>F.id==="proj:a1")});z(G,"the changed signal drives an incremental OAA resource push")}finally{H.close?.()}}},{name:"OAA full sync (built-in endpoint) includes ResourceProvider resources and resource-derived entitlement groups",fn:async()=>{l();let X=await i(Q().baseUrl,Y4.id),Y=new S0;await Y.groups.set({id:"stored-group",name:"Stored Group",description:"stored"}).ready();let K=q(B1(Q().baseUrl,!0),{validators:T(),stores:{catalogStore:Y,resources:D1()}});try{await K.ready($);let H=await K.handler(new Request(S8,{method:"POST",headers:{Authorization:`Bearer ${X}`}}));z(H.status===200,`authorized full-sync should succeed, got ${H.status}`);let J=k().at(-1);z(J!==void 0,"the endpoint should drive an OAA sync push"),z(J.isIncremental===!1,"the endpoint runs a full (snapshot) sync");let G=Y0(J.payload);z(I(G?.resources).some((j)=>j.id==="team:acme"),"the snapshot streams resources from the ResourceProvider");let W=I(G?.local_groups).map((j)=>j.unique_id);z(W.includes("acme_team_admin"),"derived entitlement groups are unioned into the projection"),z(W.includes("stored-group"),"stored catalog groups remain in the projection")}finally{K.close?.()}}},{name:"the SDK write path (SCIM access mutation + ResourceProvider.changed) emits only incremental OAA pushes, never a full snapshot",fn:async()=>{l();let X=await i(Q().baseUrl,Y8.id),Y=await C(),K=D1(),H=new w,J=new A,G=q(cQ(Q().baseUrl,"ecv-scim-oaa-guard"),{validators:T(),stores:{catalogStore:Y.catalog,accessStore:H,accessIndex:J,resources:K}});try{await G.ready($),z(G.authz.oaa.isReady(),"OAA must be ready so the write path actually projects");let W={Authorization:`Bearer ${X}`,"Content-Type":"application/scim+json"},j=()=>k().every((O)=>O.isIncremental===!0),N=await G.handler(new Request("https://ecv.test/api/ionite/iam/Groups/staff",{method:"PATCH",headers:W,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"guard-user-1"}]}]})}));z(N.status<300,`stored group member add should succeed, got ${N.status}`);let D=await G.handler(new Request("https://ecv.test/api/ionite/iam/Groups/acme_team_admin",{method:"PATCH",headers:W,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"guard-user-2"}]}]})}));z(D.status<300,`derived group member add should succeed, got ${D.status}`),z(k().length>=2,"each SCIM membership grant should project an incremental push"),z(j(),"a SCIM access mutation must not trigger a full OAA snapshot sync"),K.set({id:"team:beta",type:"team",name:"Beta",description:"Beta team"});let F=!1;for(let O=0;O<100&&!F;O++)await new Promise((P)=>setTimeout(P,10)),F=k().some((P)=>{let R=Y0(P.payload);return P.isIncremental===!0&&I(R?.resources).some((x)=>x.id==="team:beta")});z(F,"a resource change should project the resource to OAA incrementally"),await new Promise((O)=>setTimeout(O,50));let M=k().filter((O)=>O.isIncremental===!1);z(M.length===0,`the SDK write path emitted ${M.length} full snapshot push(es); full sync must only run via the Ops-gated endpoint`)}finally{G.close?.()}}},{name:"§7.1 pushAccessChanges batches N membership changes into ONE incremental push (O(1), not O(N))",fn:async()=>{l();let X=Array.from({length:100},(G,W)=>({change:{subject:`batch-user-${W}`,targetType:"group",target:"batch-group"},op:"add"}));await E0(Q().baseUrl,$,async(G)=>{await G.authz.oaa.pushAccessChanges(X)});let Y=k();B(Y.length,1,"a 100-change batch must issue exactly one OAA push, not one per change");let K=Y[0];z(K!==void 0,"expected the single batched push to be recorded"),z(K?.isIncremental===!0,"a batched membership push is incremental, never a full snapshot");let H=I(Y0(K?.payload??{})?.local_users);for(let G=0;G<100;G+=1){let W=H.find((j)=>j.unique_id===`batch-user-${G}`);z(W!==void 0,`subject batch-user-${G} must appear in the single batched payload`),z(Array.isArray(W?.groups)&&(W?.groups).includes("batch-group"),`subject batch-user-${G} must carry the granted group membership`)}let J=H.find((G)=>G.unique_id==="batch-user-0");z(J?.operation==="add","a batched add projects each membership with the add operation")}},{name:"§7.1 pushAccessChanges([]) is a no-op — an empty batch never contacts the OAA receiver",fn:async()=>{l(),await E0(Q().baseUrl,$,async(X)=>{await X.authz.oaa.pushAccessChanges([])}),B(k().length,0,"an empty change batch must not issue any OAA push")}},{name:"re-pushing the SAME SCIM membership grant keeps IAM entitlement members and OAA edges stable (idempotent, no accumulation)",fn:async()=>{l();let X=await i(Q().baseUrl,`${Y8.id} ${s1.id}`),Y=await C(),K=new w,H=new A,J=q(cQ(Q().baseUrl,"ecv-scim-oaa-idempotent"),{validators:T(),stores:{catalogStore:Y.catalog,accessStore:K,accessIndex:H}});try{await J.ready($),z(J.authz.oaa.isReady(),"OAA must be ready so the write path projects each grant");let G={Authorization:`Bearer ${X}`,"Content-Type":"application/scim+json"},W=()=>new Request("https://ecv.test/api/ionite/iam/Groups/staff",{method:"PATCH",headers:G,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:"idem-user"}]}]})}),j=()=>{let b=new Set;for(let X0 of k())for(let J0 of I(Y0(X0.payload)?.local_users)){if(J0.operation==="delete")continue;let A0=typeof J0.unique_id==="string"?J0.unique_id:"";for(let s0 of Array.isArray(J0.groups)?J0.groups:[])if(typeof s0==="string")b.add(`${A0}:member_of:group:${s0}`)}return b},N=await J.handler(W());z(N.status<300,`first membership grant should succeed, got ${N.status}`);let D=(await K.listByTarget("group","staff")).items.length,F=j(),M=k().length;z(D===1,"the first grant persists exactly one membership root"),z(F.has("idem-user:member_of:group:staff"),"the first grant projects the membership edge"),z((await H.hasPermission("idem-user","read")).allow,"the granted staff membership confers the editor role closure (read)");let O=await J.handler(W());z(O.status<300,`re-granting the same membership should still succeed, got ${O.status}`);let P=(await K.listByTarget("group","staff")).items.length,R=j(),x=k().length;z(P===D,`re-pushing the same grant must not add IAM entitlement roots (was ${D}, now ${P})`),z(R.size===F.size,`re-pushing the same grant must not add OAA edges (was ${F.size}, now ${R.size})`);for(let b of F)z(R.has(b),`OAA edge ${b} must remain stable across an identical re-push`);z(x>M,"the SDK re-projects on the second grant (the receiver, not the SDK, dedupes the edge)");let V=await J.handler(new Request("https://ecv.test/api/ionite/iam/Groups/staff",{headers:{Authorization:`Bearer ${X}`}}));z(V.status===200,`Groups/staff read should succeed, got ${V.status}`);let u=((await V.json()).members??[]).filter((b)=>b.value==="idem-user");z(u.length===1,`the re-granted member must appear exactly once in the SCIM projection, saw ${u.length}`)}finally{J.close?.()}}}]}function iQ(Z){let $=Z.id.replace(/^team:/,"");return{id:`${$}_team_admin`,name:`${Z.name} Admin`,displayName:`${Z.name} Admin`,description:`Derived admin entitlement for ${Z.name}`,requestable:!0,roles:["editor"],resources:[Z.id],externalId:`ext-${$}-team-admin`}}function D1(){let Z=new G4({deriveEntitlements:($)=>$.type==="team"?[iQ($)]:[],resolveEntitlement:($,Q)=>{if(!$.endsWith("_team_admin"))return;let X=Q(`team:${$.slice(0,-11)}`);return X?iQ(X):void 0}});return Z.set({id:"team:acme",type:"team",name:"Acme",description:"Acme team"}),Z}function v8(Z,$){return E({id:$,workload:{jwksUri:`${Z}/workload/certs`,audience:`${Z}/workload`},authz:{scopesAsEntitlements:!0},iam:{}})}async function i(Z,$){let Q=new URLSearchParams({grant_type:"client_credentials",client_id:"local-workload-client",client_secret:"local-workload-secret",scope:$}),Y=await(await fetch(`${Z}/workload/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:Q})).json();return z(typeof Y.access_token==="string","mock workload token endpoint must return an access_token"),Y.access_token}function F1(Z,$){return q(E({id:"ecv-workload-scope",workload:{jwksUri:`${Z}/workload/certs`,audience:`${Z}/workload`},...$?{authz:$}:{}}),{validators:T()})}function nQ(Z,$,Q){return q(E({id:"ecv-workload-incoming",workload:{incoming:$},...Q?{authz:Q}:{}}),{validators:T()})}function K8(Z){return new Request("https://example.test/api/task",{headers:{Authorization:`Bearer ${Z}`}})}async function K4(Z,$,Q){try{await Z()}catch(X){let Y=X instanceof Error?X.message:String(X);if(!Y.includes($))throw Error(`${Q}: expected error containing "${$}", got "${Y}"`);return}throw Error(`${Q}: expected rejection, but the call resolved`)}function $$(Z){let $=Z.timeout??5000,Q=()=>tQ(Z.ecv);return[{name:"hasScope* authorize a workload from its token scope claim (allow / deny + 403 mapping)",fn:async()=>{let X=await i(Q().baseUrl,"oaa:sync reports:read"),Y=F1(Q().baseUrl);try{await Y.ready($);let K=K8(X),H=await z4(K,Y,"oaa:sync","workload");z(H.allow,"token scope oaa:sync satisfies hasScope"),z(H.subjectType==="workload","the matched principal is the workload"),z(H.subject?.id==="local-workload-client","workload id is the client_id");let J=await z4(K,Y,"admin:all","workload");z(!J.allow&&J.reason==="forbidden","a missing scope is forbidden"),z(J.response().status===403,"forbidden scope -> 403"),z((await oQ(K,Y,["nope","reports:read"],"workload")).allow,"hasAnyScope matches one"),z(!(await H4(K,Y,["oaa:sync","nope"],"workload")).allow,"hasAllScope needs all"),z((await H4(K,Y,["oaa:sync","reports:read"],"workload")).allow,"hasAllScope all present")}finally{Y.close?.()}}},{name:"scopesAsEntitlements gates whether has* auto-honors a workload token scope (app-wide only)",fn:async()=>{let X=await i(Q().baseUrl,"run_oaa_sync"),Y=K8(X),K=F1(Q().baseUrl,{scopesAsEntitlements:!1});try{await K.ready($);let J=await B0(Y,K,"run_oaa_sync",void 0,"workload");z(!J.allow,"scopes do not satisfy has* when scopesAsEntitlements is false")}finally{K.close?.()}let H=F1(Q().baseUrl,{scopesAsEntitlements:!0});try{await H.ready($);let J=await B0(Y,H,"run_oaa_sync",void 0,"workload");z(J.allow,"app-wide has* auto-honors the matching token scope when enabled");let G=await B0(Y,H,"run_oaa_sync","project-a","workload");z(!G.allow,"a token scope cannot satisfy a resource-scoped has* check")}finally{H.close?.()}}},{name:"trustedProvisioningClients overlay authorizes a matching workload without a store grant or scope",fn:async()=>{let X=Q().baseUrl,Y=`${X}/workload`,K=await i(X,""),H={issuer:Y,audience:[Y],clientId:"local-workload-client",permissions:[V1.id]},J=F1(X,{trustedProvisioningClients:[H]});try{await J.ready($);let G=K8(K),W=await B0(G,J,V1.id,void 0,"workload");z(W.allow,"trusted client is authorized for its declared permission via the overlay"),z(W.decision?.metadata?.reason==="trusted-client","allow reason is trusted-client");let j=await B0(G,J,"ion:iam:read",void 0,"workload");z(!j.allow,"a permission outside the trusted client declaration is denied")}finally{J.close?.()}}},{name:"trustedProvisioningClients overlay is audience-bound (a mismatched audience does not authorize)",fn:async()=>{let X=Q().baseUrl,Y=`${X}/workload`,K=await i(X,""),H={issuer:Y,audience:["https://some-other-app.example/api"],clientId:"local-workload-client",permissions:[V1.id]},J=F1(X,{trustedProvisioningClients:[H]});try{await J.ready($);let G=await B0(K8(K),J,V1.id,void 0,"workload");z(!G.allow,"audience mismatch means the trusted-client overlay does not apply")}finally{J.close?.()}}},{name:"per-IdP scopesAsEntitlements honors token scope even when the global flag is off",fn:async()=>{let X=Q().baseUrl,Y=`${X}/workload`,K=await i(X,"run_oaa_sync"),H=nQ(X,[{issuer:Y,jwksUri:`${X}/workload/certs`,audience:Y,scopesAsEntitlements:!0}]);try{await H.ready($);let J=await B0(K8(K),H,"run_oaa_sync",void 0,"workload");z(J.allow,"the matched IdP scopesAsEntitlements honors the token scope"),z(J.decision?.metadata?.reason==="scope","allow reason is scope")}finally{H.close?.()}}},{name:"multi-IdP incoming enforces audience (cross-app token reuse is rejected at validation)",fn:async()=>{let X=Q().baseUrl,Y=`${X}/workload`,K=await i(X,"run_oaa_sync"),H=nQ(X,[{issuer:Y,jwksUri:`${X}/workload/certs`,audience:"https://other-app.example/api"}],{scopesAsEntitlements:!0});try{await H.ready($);let J=await B0(K8(K),H,"run_oaa_sync",void 0,"workload");z(!J.allow,"a token whose audience does not match the trusted IdP is rejected"),z(J.reason==="unauthenticated","audience mismatch fails validation -> unauthenticated")}finally{H.close?.()}}},{name:"trustedProvisioningClients without an audience is rejected at config apply (§1.4)",fn:async()=>{let X=Q().baseUrl,Y={issuer:`${X}/workload`,audience:[],clientId:"eci-provisioning",permissions:["ion:iam:read"]},K=q(E({id:"ecv-1-4-empty-audience",workload:{jwksUri:`${X}/workload/certs`,audience:`${X}/workload`},authz:{trustedProvisioningClients:[Y]}}),{validators:T()});try{await K4(()=>K.ready($),"audience","an audience-less trusted provisioning client must fail config apply")}finally{K.close?.()}}},{name:"incoming workload trust without an audience is rejected at config apply (§1.5)",fn:async()=>{let X=Q().baseUrl,Y=q(E({id:"ecv-1-5-flat-no-audience",workload:{jwksUri:`${X}/workload/certs`}}),{validators:T()});try{await K4(()=>Y.ready($),"audience","a server-only workload trust without an audience must fail config apply")}finally{Y.close?.()}let K=q(E({id:"ecv-1-5-incoming-no-audience",workload:{incoming:[{issuer:`${X}/workload`,jwksUri:`${X}/workload/certs`}]}}),{validators:T()});try{await K4(()=>K.ready($),"audience","an audience-less TrustedIdp incoming entry must fail config apply")}finally{K.close?.()}}},{name:"flat server-only workload trust enforces its configured audience at runtime (§1.5)",fn:async()=>{let X=Q().baseUrl,Y=await i(X,"run_oaa_sync"),K=q(E({id:"ecv-1-5-flat-wrong-audience",workload:{jwksUri:`${X}/workload/certs`,audience:"https://other-app.example/api"},authz:{scopesAsEntitlements:!0}}),{validators:T()});try{await K.ready($);let H=await B0(K8(Y),K,"run_oaa_sync",void 0,"workload");z(!H.allow,"a token minted for another audience must not authenticate"),z(H.reason==="unauthenticated","flat audience mismatch -> unauthenticated")}finally{K.close?.()}}},{name:"subject-disabled marker vetoes the scope overlay; re-enable restores access",fn:async()=>{let X=Q().baseUrl,Y=await i(X,"run_oaa_sync"),K=new A,H=q(E({id:"ecv-subject-disabled-overlay",workload:{jwksUri:`${X}/workload/certs`,audience:`${X}/workload`},authz:{scopesAsEntitlements:!0}}),{validators:T(),stores:{accessIndex:K}});try{await H.ready($);let J=K8(Y),G=await B0(J,H,"run_oaa_sync",void 0,"workload");z(G.allow,"the scope overlay allows before the subject is disabled"),await K.setSubjectDisabled("local-workload-client","workload",!0);let W=await B0(J,H,"run_oaa_sync",void 0,"workload");z(!W.allow,"a disabled subject is denied even when a scope overlay matches (deny wins)"),await K.setSubjectDisabled("local-workload-client","workload",!1);let j=await B0(J,H,"run_oaa_sync",void 0,"workload");z(j.allow,"re-enabling the subject restores overlay-based access")}finally{H.close?.()}}},{name:"SCIM active flip sets/clears the AccessIndex subject-disabled marker (deactivate, re-activate, delete)",fn:async()=>{let X=Q().baseUrl,Y=await i(X,`${aQ.id} ${s1.id}`),K=new A,H=q(v8(X,"ecv-scim-subject-disabled"),{validators:T(),stores:{accessIndex:K}});try{await H.ready($);let J={Authorization:`Bearer ${Y}`,"Content-Type":"application/scim+json"},G=await H.handler(new Request("https://ecv.test/api/ionite/iam/Users",{method:"POST",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:User"],userName:"disable-flow@example.test",active:!0})}));z(G.status===201,`SCIM user create should succeed, got ${G.status}`);let W=await G.json();z(!await K.isSubjectDisabled(W.id,"user"),"an active user starts enabled");let j=await H.handler(new Request(`https://ecv.test/api/ionite/iam/Users/${W.id}`,{method:"PATCH",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"active",value:!1}]})}));z(j.status===200,`SCIM deactivate should succeed, got ${j.status}`),z(await K.isSubjectDisabled(W.id,"user"),"PATCH active:false sets the disabled marker");let N=await K.hasPermission(W.id,"read");z(!N.allow,"a disabled subject is denied by the index"),z(N.metadata?.reason==="subject_disabled","the index denial carries reason subject_disabled");let D=await H.handler(new Request(`https://ecv.test/api/ionite/iam/Users/${W.id}`,{method:"PUT",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:User"],id:W.id,userName:"disable-flow@example.test",active:!0})}));z(D.status===200,`SCIM reactivate should succeed, got ${D.status}`),z(!await K.isSubjectDisabled(W.id,"user"),"PUT active:true clears the disabled marker"),await H.handler(new Request(`https://ecv.test/api/ionite/iam/Users/${W.id}`,{method:"PATCH",headers:J,body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"active",value:!1}]})})),z(await K.isSubjectDisabled(W.id,"user"),"the marker is set again before delete");let F=await H.handler(new Request(`https://ecv.test/api/ionite/iam/Users/${W.id}`,{method:"DELETE",headers:J}));z(F.status===204,`SCIM user delete should succeed, got ${F.status}`),z(!await K.isSubjectDisabled(W.id,"user"),"user delete clears the disabled marker")}finally{H.close?.()}}}]}async function B6(Z={}){let $=performance.now(),Q=[...y8().tests];if(Z.ecv)Q.push(...$$(Z)),Q.push(...M8(Z));let X=[];for(let J of Q)X.push(await L(J.name,async()=>{await J.fn();return}));let Y=performance.now()-$,K=X.filter((J)=>!J.passed).length,H=X.filter((J)=>J.details?.skipped).length;return{suite:"Authz",passed:K===0,tests:X,duration:Y,summary:{total:X.length,passed:X.length-K-H,failed:K,skipped:H}}}import{ION_CAPABILITY_CATALOG as _6}from"@ionite/server";import{ION_CAPABILITIEZ_READ as D6,ION_IAM_GROUPS_WRITE as F6,ION_IAM_READ as V6,ION_IAM_USERS_WRITE as O6,ION_TENANT_MANAGE as M6}from"@ionite/server";var W4=`${V6.id} ${O6.id} ${F6.id}`,M1=D6.id,L6=M6.id;async function b0(Z=C0,$,Q="local-workload-client",X="local-workload-secret"){let Y=`${Z.replace(/\/$/,"")}/workload/token`,K=await fetch(Y,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:Q,client_secret:X,...$?{scope:$}:{}}).toString()});if(!K.ok){let J=await K.text();throw Error(`Failed to get tenant auth token: ${K.status} ${J}`)}let H=await K.json();if(!H.access_token)throw Error("Tenant auth token response missing access_token");return H.access_token}var sQ={readyzPath:"/api/ionite/readyz",capabilitiezPath:"/api/ionite/capabilitiez"},L1=["sso","ciam","iam","workload","otel","secrets","authz"];function eQ(Z,$){return z(typeof Z==="string"&&Z.length>0,`${$} must be configured`),Z}function $9(Z,$){z(typeof Z==="object"&&Z!==null,`modules.${$} must be an object`);let Q=Z;return z(typeof Q.active==="boolean",`modules.${$}.active must be a boolean`),z(typeof Q.ready==="boolean",`modules.${$}.ready must be a boolean`),Q}async function Q9(Z,$){let Q=await $(eQ(Z.readyzPath,"readyzPath"));z(Q.status===200||Q.status===503,`Expected 200 or 503 from readyz endpoint, got ${Q.status} (404 means readyz is disabled via RemoteConfig)`);let X=await Q.json();return z(typeof X.applied==="boolean","Readiness report must include applied:boolean"),z(typeof X.ok==="boolean","Readiness report must include ok:boolean"),z(typeof X.modules==="object"&&X.modules!==null,"Readiness report must include a modules map"),X}function P6(Z,$){z(typeof Z==="object"&&Z!==null,`capabilities.${$} must be an object`);let Q=Z;return z(typeof Q.testable==="boolean",`capabilities.${$}.testable must be a boolean`),z(typeof Q.active==="boolean",`capabilities.${$}.active must be a boolean`),z(typeof Q.ready==="boolean",`capabilities.${$}.ready must be a boolean`),Q}async function Z9(Z,$){let Q=await b0(Z.ecvUrl,M1),X=await $(eQ(Z.capabilitiezPath,"capabilitiezPath"),{headers:{Authorization:`Bearer ${Q}`}});z(X.status===200,`Expected 200 from capabilitiez endpoint, got ${X.status}. Ensure RemoteConfig includes capabilitiez: true and workload auth is configured.`);let Y=await X.json();return z(typeof Y.basePath==="string","Capabilitiez manifest must include basePath:string"),z(typeof Y.urls==="object"&&Y.urls!==null,"Capabilitiez manifest must include urls"),z(typeof Y.capabilities==="object"&&Y.capabilities!==null,"Capabilitiez manifest must include capabilities"),Y}async function X9(Z,$){return L("Readiness Endpoint Reachable",async()=>{let Q=await Q9(Z,$),X=await Z9(Z,$);for(let Y of L1)$9(Q.modules[Y],Y);return{details:{applied:Q.applied,ok:Q.ok,config:Q.config}}})}async function Y9(Z,$){return L("Capability Catalog",async()=>{let Q=await Q9(Z,$),X=await Z9(Z,$),Y={};for(let W of L1)Y[W]=$9(Q.modules[W],W);let K=L1.every((W)=>{let j=Y[W];return!j.active||j.ready});z(Q.ok===(Q.applied&&K),"Readiness report `ok` must equal (applied && every active module ready)");let H={};for(let W of _6){let j=Y[W.module];z(j!==void 0,`Capability ${W.key} maps to unknown module ${W.module}`);let N=P6(X.capabilities[W.key],W.key);H[W.key]={label:W.label,module:W.module,active:N.active,ready:N.ready,testable:N.testable}}let J=L1.filter((W)=>Y[W].ready),G=L1.filter((W)=>Y[W].active);return{details:{catalog:H,active:G,ready:J,ok:Q.ok,applied:Q.applied}}})}async function x6(Z){let $=performance.now(),Q={...sQ,...Z},X=c(Q),Y=[];Y.push(await X9(Q,X)),Y.push(await Y9(Q,X));let K=performance.now()-$,H=Y.filter((W)=>W.passed).length,J=Y.filter((W)=>!W.passed).length,G=Y.filter((W)=>W.details?.skipped).length;return{suite:"Capabilities",passed:J===0,tests:Y,duration:K,summary:{total:Y.length,passed:H-G,failed:J,skipped:G}}}function j4(Z){let $={...sQ,...Z},Q=c($);return[{name:"readiness endpoint is reachable and well-formed",fn:async()=>{let X=await X9($,Q);if(!X.passed)throw Error(X.error)}},{name:"capability catalog covers all known capabilities",fn:async()=>{let X=await Y9($,Q);if(!X.passed)throw Error(X.error)}}]}import{silentLogger as R6}from"@ionite/core";import{ciam as q6}from"@ionite/core/server";import{InMemoryMagicLinkStore as T6,InMemorySessionStore as U6}from"@ionite/server";import{createValidators as w6}from"@ionite/valibot";var K9={customerPath:"/api/ionite/auth/customer",magicLinkPath:"/api/ionite/magic-link",magicLinkLoginPath:"/api/ionite/magic-link/login",logoutPath:"/api/ionite/auth/customer/logout",backChannelLogoutPath:"/api/ionite/auth/customer/logout/backchannel",customerProvidersPath:"/api/ionite/auth/customer/providers",customerLoginPath:"/api/ionite/auth/customer/login"},A6=["clientId","clientSecret","authority","privateKey","keyId","teamId"];function I6(Z){return Z.endsWith(".access")||Z.endsWith(".id")||Z.endsWith(".control")}function C6(Z){for(let $ of Z.keys()){if($.endsWith(".session"))return $.slice(0,-8);let Q=$.indexOf(".cstate");if(Q>0)return $.slice(0,Q)}return}async function H9(Z,$){return L("CIAM Customer Endpoint (Unauthenticated)",async()=>{let Q=Z.customerPath??"/api/ionite/auth/customer",X=await $(Q);return z(X.status===401,`Expected 401 Unauthorized, got ${X.status}`),{details:{status:X.status}}})}async function z9(Z,$){return L("CIAM Magic Link Requires Workload Auth",async()=>{let Q=Z.magicLinkPath??"/api/ionite/magic-link",X=await $(Q,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userName:"jdoe",name:"Jane Doe",email:"jane@example.com"})});return z(X.status===401||X.status===404,`Expected 401 Unauthorized (or 404 if disabled), got ${X.status}`),{details:{status:X.status,endpointExists:X.status!==404}}})}async function G9(Z,$){z($.endsWith("/logout"),`CIAM logoutPath must end with /logout, got: ${$}`);let Q={headers:{Accept:"application/json"}},X=await Z($,Q);if(X.status!==200&&X.status!==302)X=await Z($,Q);if(X.status!==200&&X.status!==302){let Y=await X.text().catch(()=>"");throw Error(`Expected 302 or 200, got ${X.status} for ${$} (content-type=${X.headers.get("content-type")??"none"}): ${Y.slice(0,500)}`)}return X}async function J9(Z,$){return L("CIAM Customer Logout Endpoint",async()=>{let Q=Z.logoutPath??"/api/ionite/auth/customer/logout",X=await G9($,Q),Y=X.headers.getSetCookie?.()??[],K=Y.some((G)=>G.includes("Max-Age=0")||G.includes("expires=Thu, 01 Jan 1970")),H=Y.some((G)=>G.startsWith("ion.sso")&&(G.includes("Max-Age=0")||G.includes("expires=")));z(!H,"CIAM logout must not clear SSO (ion.sso.*) cookies");let J=[...M0(X.headers).entries()].filter(([,G])=>G===""||G.includes("Max-Age=0")).map(([G])=>G);return z(K||J.length>0||Y.length===0,"CIAM logout should clear customer session cookies"),{details:{status:X.status,clearedCookies:J}}})}async function W9(Z,$){return L("CIAM Back-Channel Logout Removed",async()=>{let Q=Z.backChannelLogoutPath??"/api/ionite/auth/customer/logout/backchannel",X=await $(Q,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:""});return z(X.status===404,`Expected 404 (route removed), got ${X.status}`),{details:{status:X.status,endpointRemoved:!0}}})}async function j9(Z,$){return L("CIAM Provider Discovery (Sanitized + Ordered)",async()=>{let Q=Z.customerProvidersPath??"/api/ionite/auth/customer/providers",X=await $(Q);if(X.status===404)return{details:{status:404,configured:!1,skipped:!0}};z(X.status===200,`Expected 200, got ${X.status}`);let Y=await X.json();z(Array.isArray(Y.providers),"Discovery response must contain a `providers` array");let K=Y.providers,H=[];for(let G of K){z(typeof G.name==="string","Each provider must have a string `name`"),z(typeof G.loginUrl==="string","Each provider must include a derived `loginUrl`"),z(typeof G.displayName==="string","Each provider must include a `displayName`");for(let W of A6)z(!(W in G),`Discovery must not expose secret field "${W}"`);H.push(G.name)}let J=await $(Q);if(J.status===200){let W=((await J.json()).providers??[]).map((j)=>j.name);z(JSON.stringify(H)===JSON.stringify(W),"Provider discovery order must be stable (recommended order preserved)")}return{details:{status:200,configured:!0,count:K.length,order:H}}})}async function N9(Z,$){return L("CIAM Third-Party Login Redirect",async()=>{let Q=Z.customerLoginPath??"/api/ionite/auth/customer/login",X=await $(`${Q}?provider=__esv_unknown__`,{redirect:"manual"});z(X.status===404||X.status===400||X.status===500,`Unknown provider should be rejected (4xx/5xx), got ${X.status}`);let Y=Z.customerProvidersPath??"/api/ionite/auth/customer/providers",K=await $(Y);if(K.status!==200)return{details:{unknownStatus:X.status,configured:!1,skipped:!0}};let J=((await K.json()).providers??[]).find((D)=>D.type&&D.type!=="magic-link");if(!J?.name)return{details:{unknownStatus:X.status,configured:!1,skipped:!0}};let G=await $(`${Q}?provider=${encodeURIComponent(J.name)}`,{redirect:"manual"});z(G.status===302,`Expected 302 redirect to IdP, got ${G.status}`);let W=G.headers.get("location");z(Boolean(W),"Login redirect must include a Location header"),z((W??"").includes("state=")&&(W??"").includes("client_id="),"Authorization redirect must include OAuth2 state and client_id parameters");let N=(G.headers.getSetCookie?.()??[]).find((D)=>D.includes(".cstate_"));return z(Boolean(N),"Login must set a CIAM state cookie (ion.ciam.cstate_<state>)"),z(/SameSite=Lax/i.test(N),`CIAM state cookie must be SameSite=Lax for the cross-site provider callback, got: ${N}`),{details:{status:302,provider:J.name,unknownStatus:X.status,cstateCookie:N}}})}async function B9(Z,$){return L("CIAM Logout Opaque-Only (no token cookies)",async()=>{let Q=Z.logoutPath??"/api/ionite/auth/customer/logout",Y=(await G9($,Q)).headers.getSetCookie?.()??[],K=Y.find((H)=>I6(H.split("=")[0]?.trim()??""));return z(!K,`CIAM logout must not touch token cookies (opaque-only), found: ${K}`),{details:{setCookieCount:Y.length}}})}async function D9(Z,$){return L("CIAM Legacy Token Cookies Rejected",async()=>{let Q=Z.logoutPath??"/api/ionite/auth/customer/logout",X=Z.customerPath??"/api/ionite/auth/customer",Y=await $(Q,{headers:{Accept:"application/json"}}),K=C6(M0(Y.headers));if(!K)return{details:{skipped:!0,reason:"Could not derive CIAM cookie prefix from logout response"}};let H=new Map([[`${K}.access`,"legacy-access"],[`${K}.id`,"legacy-id"],[`${K}.control`,btoa(JSON.stringify({token_type:"Bearer",expires_in:3600,expires:new Date(Date.now()+3600000).toISOString()}))]]),J=await $(X,{headers:{Cookie:d0(H)}});return z(J.status===401,`Legacy CIAM token cookies must not authenticate (expected 401), got ${J.status}`),{details:{status:J.status,prefix:K}}})}async function E6(Z){let $=performance.now(),Q={...K9,...Z},X=c(Q),Y=[];Y.push(await H9(Q,X)),Y.push(await z9(Q,X)),Y.push(await J9(Q,X)),Y.push(await B9(Q,X)),Y.push(await D9(Q,X)),Y.push(await W9(Q,X)),Y.push(await j9(Q,X)),Y.push(await N9(Q,X));let K=performance.now()-$,H=Y.filter((W)=>W.passed).length,J=Y.filter((W)=>!W.passed).length,G=Y.filter((W)=>W.details?.skipped).length;return{suite:"CIAM",passed:J===0,tests:Y,duration:K,summary:{total:Y.length,passed:H-G,failed:J,skipped:G}}}async function S6(){return L("CIAM magic-link session stores no token material (§1.10)",async()=>{let Z=new T6,$=new U6,Q=w6(),X=q6(Q.ciam,R6,void 0,{magicLinkUrl:"/magic-link",magicLinkLoginUrl:"/magic-link/login",sessionStore:$,magicLinkStore:Z,cookiesSecure:!1});z(!!X,"ciam() must return a handle when configured");let Y={id:"cust-1",userName:"cust1",email:"c1@example.test",name:"Customer One"};await Z.create("tok-1",Y,new Date(Date.now()+60000));let K=await X?.handler(new Request("https://app.ecv.local/magic-link/login?token=tok-1"));z(!!K,"magic-link login must produce a response");let J=(K?.headers.getSetCookie?.()??[]).find((j)=>(j.split("=")[0]?.trim()??"").endsWith(".session"));z(!!J,"magic-link login must set an opaque session cookie");let G=J.split("=")[1]?.split(";")[0]??"";z(G.length>0,"session cookie must carry an opaque sid");let W=await $.get(G);return z(W!=null,"the magic-link session must be persisted"),z(!("tokens"in W),"a CIAM session must NOT store any signed token material (§1.10)"),z(!!W?.claims,"the opaque session still carries identity claims"),{details:{sid:G,hasTokens:W!=null&&"tokens"in W}}})}function b8(Z){let $={...K9,...Z},Q=c($);return{tests:[{name:"customer endpoint returns 401 when unauthenticated",fn:async()=>{let Y=await H9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"magic-link generation requires workload auth",fn:async()=>{let Y=await z9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"customer logout endpoint clears CIAM cookies (not SSO)",fn:async()=>{let Y=await J9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"customer logout is opaque-only (no token cookies)",fn:async()=>{let Y=await B9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"legacy CIAM token cookies do not authenticate (opaque-only)",fn:async()=>{let Y=await D9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"customer back-channel logout endpoint is removed (404)",fn:async()=>{let Y=await W9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"provider discovery returns sanitized list in stable order",fn:async()=>{let Y=await j9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"third-party login redirects to IdP with state",fn:async()=>{let Y=await N9($,Q);if(!Y.passed)throw Error(Y.error)}},{name:"magic-link session stores no token material (§1.10)",fn:async()=>{let Y=await S6();if(!Y.passed)throw Error(Y.error)}}]}}import{ionite as s,secretConfigSource as F9,vaultConfig as N4}from"@ionite/server";import{createValidators as e}from"@ionite/valibot";function _1(Z,$={}){return Object.assign(Error(Z),$)}function h8(Z){if(Z.length===0)throw Error("sequenceConfigSource requires at least one step");let $=0;return{attempts:()=>$,async load(){$+=1;let Q=Z[Math.min($-1,Z.length-1)];if(Q===void 0)throw Error("sequenceConfigSource step unexpectedly missing");if(Q instanceof Error)throw Q;if(typeof Q==="function")return await Q();return Q},async subscribe(){return},secret:{}}}async function W0(Z){await new Promise(($)=>setTimeout($,Z))}function L0(Z){let $=Z,Q;return{async load(){return $},async subscribe(X){return Q=X,()=>{Q=void 0}},push(X){$=X,Q?.(X)},secret:{}}}function v6(Z,$){let Q;return{async load(){return await W0($),Z},async subscribe(X){return Q=X,()=>{Q=void 0}},push(X){Q?.(X)},secret:{}}}var K0={jwksUri:"http://localhost/workload/certs",issuer:"http://localhost/workload",audience:"http://localhost/workload"};function D4(){return[{name:"module readiness re-arms when a module is de-configured and re-configured (§5.1)",fn:async()=>{let Z=L0({id:"rearm",workload:K0}),$=s(Z,{validators:e()});await $.ready(500),z($.workload?.isReady?.()===!0,"Expected workload ready after initial config"),Z.push({id:"rearm"}),await W0(20),z($.workload?.isReady?.()===!1,"Expected workload.isReady() false after de-configuration");let Q=!1;try{await $.workload?.ready?.(50)}catch{Q=!0}z(Q,"Expected re-armed workload.ready(50) to reject while de-configured"),Z.push({id:"rearm",workload:K0}),await W0(20),z($.workload?.isReady?.()===!0,"Expected workload.isReady() true after re-configuration"),await $.workload?.ready?.(200),$.close?.()}},{name:"async beforeChange override is awaited and applied (§5.2)",fn:async()=>{let Z=L0({id:"bc-1"}),$=s(Z,{validators:e(),beforeChange:async(Q)=>{return await W0(5),{config:{...Q,id:"bc-1-overridden"}}}});await $.ready(500),B($.config?.id,"bc-1-overridden","Expected async beforeChange override to be applied"),$.close?.()}},{name:"awaiting ion.ready() inside beforeChange on initial load fails fast (§5.2)",fn:async()=>{let Z=L0({id:"deadlock-1"}),$=!1,Q=s(Z,{validators:e(),beforeChange:async()=>{try{await Q.ready(100)}catch{$=!0}return}});await Q.ready(500),z($,"Expected ion.ready() inside beforeChange to reject (deadlock guard)"),z(Q.isReady(),"Expected instance to still become ready after beforeChange returns"),Q.close?.()}},{name:"throwing afterChange on initial load rejects ready() (§5.4)",fn:async()=>{let Z=L0({id:"ac-1",workload:K0}),$=s(Z,{validators:e(),afterChange:()=>{throw Error("afterChange boom (initial)")}}),Q=!1;try{await $.ready(500)}catch{Q=!0}z(Q,"Expected a throwing initial afterChange to reject ready()"),$.close?.()}},{name:"setStores() re-applies config and runs afterChange (§16.2)",fn:async()=>{let Z=0,$=!1,Q=L0({id:"setstores-1",workload:K0}),X=s(Q,{validators:e(),afterChange:(K,H,J)=>{Z+=1,$=typeof J.setStores==="function"}});await X.ready(500);let Y=Z;z(Y>=1,"Expected afterChange to run on the initial apply"),await X.setStores?.({}),z(Z>Y,"Expected afterChange to run again after setStores() re-applies config"),z($,"Expected afterChange to receive a modifiable framework config (with setStores) on the store swap"),X.close?.()}},{name:"throwing afterChange on a live push keeps last-known-good applied (§5.4)",fn:async()=>{let Z=0,$=L0({id:"aclkg"}),Q=s($,{validators:e(),afterChange:()=>{if(Z+=1,Z>=2)throw Error("afterChange boom (live)")}});await Q.ready(500),z(Q.isReady(),"Expected ready after first apply"),$.push({id:"aclkg",workload:K0}),await W0(20),z(Q.isReady(),"Expected instance to stay ready (LKG) after a throwing live afterChange"),z(Q.config?.workload!==void 0,"Expected the pushed config to still apply despite afterChange throwing"),Q.close?.()}},{name:"a live update that changes the immutable RemoteConfig.id is rejected and keeps last-known-good (§2.4)",fn:async()=>{let Z=L0({id:"immutable-id",workload:K0}),$=s(Z,{validators:e()});await $.ready(500),B($.config?.id,"immutable-id","Expected the initial id to apply"),Z.push({id:"different-id",workload:K0}),await W0(20),z($.isReady(),"Expected the instance to stay ready after a rejected id change"),B($.config?.id,"immutable-id","Expected the immutable id change to be rejected (LKG retained)"),z($.configStatus?.lastError!==void 0,"Expected configStatus.lastError to record the rejected immutable-id change"),$.close?.()}},{name:"setStores() and a concurrent live push are serialized; the push is reflected (§5.1)",fn:async()=>{let Z=L0({id:"s51",workload:K0}),$=s(Z,{validators:e(),beforeChange:async(X)=>{return await W0(50),{config:X}}});await $.ready(1000),z($.config?.capabilitiez!==!0,"Expected capabilitiez unset before the push");let Q=$.setStores?.({});Z.push({id:"s51",workload:K0,capabilitiez:!0}),await Q,await W0(150),B($.config?.capabilitiez,!0,"Expected the concurrent live push to be reflected after setStores (serialized, not reverted)"),$.close?.()}},{name:"a wait-mode push that lands during a slow initial load wins over the stale load (§5.2)",fn:async()=>{let Z=v6({id:"race",workload:K0},80),$=s(Z,{validators:e()});Z.push({id:"race",workload:K0,capabilitiez:!0}),await $.ready(500),B($.config?.capabilitiez,!0,"Expected the wait-mode push (v2) to be the applied config"),await W0(120),B($.config?.capabilitiez,!0,"Expected the stale initial load to be skipped (push retained)"),$.close?.()}},{name:"a secrets source config unchanged across an sso-only re-apply keeps a stable, ready source (§7.9)",fn:async()=>{let Z=L0({id:"memo",workload:K0,secrets:{primary:{type:"vault",url:"http://localhost/vault"}}}),$=s(Z,{validators:e()});await $.ready(500),z($.secrets?.isReady?.()===!0,"Expected the secrets module ready after initial apply");let Q=$.secrets?.getSecretsSource("primary");Z.push({id:"memo",workload:K0,sso:{clientId:"c",clientSecret:"s",authority:"http://localhost/idp"},secrets:{primary:{type:"vault",url:"http://localhost/vault"}}}),await W0(30),z($.secrets?.isReady?.()===!0,"Expected the secrets module to stay ready after an sso-only change");let X=$.secrets?.getSecretsSource("primary");z(Q===X,"Expected the unchanged secrets source handle to be reused (memoized) across apply"),$.close?.()}}]}function B4(Z){return{type:"vault",getFullSecret:async()=>({data:Z,metadata:{version:1}})}}function V9(Z){return{type:"vault",getSecret:async()=>Z}}async function Q$(Z,$){try{await Z()}catch(Q){if($&&!$(Q))throw Error(`Threw an unexpected error: ${Q instanceof Error?Q.message:String(Q)}`);return Q}throw Error("Expected the operation to throw, but it resolved")}function F4(){return[{name:"RemoteConfig credential wins over a framework-set secrets credential (§2.1)",fn:async()=>{let Z=e(),$={},Q={...Z,secrets:{...Z.secrets,validateSourceConfig:(K,H)=>{$[K]=H}}},X=L0({id:"merge-1",secrets:{primary:{type:"vault",url:"http://localhost/vault",token:"REMOTE-TOKEN"}}}),Y=s(X,{validators:Q,secrets:{primary:{token:"CODE-TOKEN"}}});await Y.ready(500),B($.primary?.token,"REMOTE-TOKEN","Expected the merged secrets config to use the RemoteConfig token, not the framework token"),Y.close?.()}},{name:"unimplemented secrets source types (aws/gcp/dev) fail at config apply, not first access (§4.3)",fn:async()=>{for(let Z of["aws","gcp","dev"]){let $=L0({id:`unimpl-${Z}`,secrets:{x:{type:Z}}}),Q=s($,{validators:e()}),X=!1;try{await Q.ready(300)}catch{X=!0}z(X,`Expected secrets source type "${Z}" to fail readiness at config apply`),Q.close?.()}}},{name:"vault config-source rejects an empty payload as recoverable and a wrong-shape payload as invalid (§2.2)",fn:async()=>{let Z=N4({vault:B4({}),path:"ion/config"});await Q$(()=>Z.load(),(Y)=>Y instanceof Error&&(Y.name==="VaultConfigWaitingForSecretError"||Y.code==="not_found"||/empty|missing/i.test(Y.message)));let $=N4({vault:B4({foo:1}),path:"ion/config"});await Q$(()=>$.load(),(Y)=>Y instanceof Error&&/invalid payload|not a remoteconfig/i.test(Y.message));let X=await N4({vault:B4({data:{id:"cfg-ok"}}),path:"ion/config"}).load();B(X.id,"cfg-ok","Expected a valid wrapped RemoteConfig payload to be unwrapped")}},{name:"secret config-source rejects an invalid ConfigLocator payload (§2.2)",fn:async()=>{let Z=F9(V9({type:"not-a-real-type"}),"boot/locator");await Q$(()=>Z.load(),(Q)=>Q instanceof Error&&/invalid payload|unknown type/i.test(Q.message));let $=F9(V9("vault"),"boot/locator");await Q$(()=>$.load(),(Q)=>Q instanceof Error&&/invalid payload|expected an object/i.test(Q.message))}},{name:"a configured beforeChange re-applies even when the effective config is unchanged (§2.3)",fn:async()=>{let Z=0,$=L0({id:"normalize-me",workload:K0}),Q=s($,{validators:e(),beforeChange:(Y)=>({config:{...Y,id:"normalized"}}),afterChange:()=>{Z+=1}});await Q.ready(500);let X=Z;z(X>=1,"Expected at least one apply on the initial load"),$.push({id:"normalize-me-2",workload:K0}),await W0(30),z(Z>X,"Expected beforeChange-present pipeline to re-apply on a normalized push"),Q.close?.()}},{name:"secrets module stays not-ready while a declared source lacks a ConfigSource type (§4.5)",fn:async()=>{let Z=L0({id:"sec-ready",workload:K0}),$=s(Z,{validators:e(),secrets:{needsRemote:{}}});await $.ready(500),z($.isReady(),"Expected the instance to be ready (config applied) on initial load"),z($.secrets?.isReady?.()===!1,"Expected the secrets module to be not-ready while a declared source has no ConfigSource type"),Z.push({id:"sec-ready",workload:K0,secrets:{needsRemote:{type:"vault",url:"http://localhost/vault"}}}),await W0(30),z($.secrets?.isReady?.()===!0,"Expected the secrets module to become ready once the ConfigSource supplies the source type"),$.close?.()}}]}function V4(){return[{name:"retries recoverable missing config and then becomes ready",fn:async()=>{let Z=h8([_1("Waiting for vault config secret at ion/test",{code:"not_found",status:404,sourceType:"vault"}),{id:"retry-ok"}]),$=s(Z,{validators:e(),configRetry:{initialDelayMs:1,maxDelayMs:1,jitterRatio:0,loadTimeoutMs:50}});await $.ready(250),B(Z.attempts(),2,"Expected one retry after recoverable missing config"),$.close?.()}},{name:"fails fast for unrecoverable auth errors",fn:async()=>{let Z=h8([_1("Vault returned 403 Forbidden (auth error)",{status:403,sourceType:"vault"}),{id:"should-not-load"}]),$=s(Z,{validators:e(),configRetry:{initialDelayMs:1,maxDelayMs:1,jitterRatio:0,loadTimeoutMs:50}}),Q=!1;try{await $.ready(250)}catch{Q=!0}await W0(10),z(Q,"Expected auth failure to reject readiness"),B(Z.attempts(),1,"Expected auth failure not to retry"),$.close?.()}},{name:"retries unclassified (unknown) errors instead of failing fast (§2.1)",fn:async()=>{let Z=h8([Error("unexpected config loader bug"),{id:"unknown-retry-ok"}]),$=s(Z,{validators:e(),configRetry:{initialDelayMs:1,maxDelayMs:1,jitterRatio:0,loadTimeoutMs:50}});await $.ready(250),z($.isReady(),"Expected an unknown error to retry and then become ready"),B(Z.attempts(),2,"Expected one retry after an unknown (retry-by-default) error"),$.close?.()}},{name:"retries transient transport codes (EAI_AGAIN) then becomes ready (§2.1)",fn:async()=>{let Z=h8([_1("getaddrinfo EAI_AGAIN vault.internal",{code:"EAI_AGAIN",sourceType:"vault"}),_1("getaddrinfo EAI_AGAIN vault.internal",{code:"EAI_AGAIN",sourceType:"vault"}),{id:"eai-ok"}]),$=s(Z,{validators:e(),configRetry:{initialDelayMs:1,maxDelayMs:1,jitterRatio:0,loadTimeoutMs:50}});await $.ready(500),z($.isReady(),"Expected EAI_AGAIN to retry and then become ready"),B(Z.attempts(),3,"Expected two retries after EAI_AGAIN before success"),$.close?.()}},{name:"fails fast for a structured 4xx (403) load error (§2.1)",fn:async()=>{let Z=h8([_1("Vault returned 403 Forbidden",{status:403,sourceType:"vault"}),{id:"should-not-load"}]),$=s(Z,{validators:e(),configRetry:{initialDelayMs:1,maxDelayMs:1,jitterRatio:0,loadTimeoutMs:50}}),Q=!1;try{await $.ready(250)}catch{Q=!0}await W0(10),z(Q,"Expected a 403 load to fail fast"),B(Z.attempts(),1,"Expected a provably-unrecoverable 403 not to retry"),$.close?.()}},{name:"handler returns 503 immediately while the instance is not ready",fn:async()=>{let Z=h8([async()=>{return await W0(100),{id:"slow-config"}}]),$=s(Z,{validators:e()});z(!$.isReady(),"Expected instance to start not ready");let Q=await $.handler(new Request("http://localhost/api/ionite/auth/user"));B(Q.status,503,"Expected handler to return 503 immediately when not ready"),await $.ready(250),z($.isReady(),"Expected instance to be ready after config applied"),$.close?.()}}]}function O9(Z){return{capabilities:Z}}function P1(){return[{name:"readyPath defaults to basePath + /readyz (/api/ionite/readyz) (§17.3)",fn:async()=>{B(U8({}),"/api/ionite/readyz","Expected the default readyPath to derive from /api/ionite"),B(U8({basePath:"/api/ionite/"}),"/api/ionite/readyz","Expected a trailing-slash basePath to normalize")}},{name:"readyPath derives from a custom basePath (§17.3)",fn:async()=>{B(U8({basePath:"/custom/mount"}),"/custom/mount/readyz","Expected readyPath to derive from the provided basePath")}},{name:"an explicit readyPath always wins over basePath (§17.3)",fn:async()=>{B(U8({readyPath:"/api/readyz",basePath:"/api/ionite"}),"/api/readyz","Expected an explicit readyPath to override basePath derivation")}},{name:"capabilitySkipReason surfaces a reason for a non-testable capability and undefined for a testable one (§4.10)",fn:async()=>{let Z=O9({"iam.users":{module:"iam",active:!0,ready:!0,testable:!0},"sso.login":{module:"sso",active:!1,ready:!1,testable:!1,reason:"SSO is not configured"}});B(H8(Z,"iam.users"),void 0,"Expected a testable capability to produce no skip reason (it runs)");let $=H8(Z,"sso.login");z(typeof $==="string"&&$.length>0,"Expected a non-testable capability to carry a skip reason"),z($?.includes("SSO is not configured")===!0,"Expected the manifest reason to be surfaced in the skip")}},{name:"capabilitySkipReason passes when ANY key in an array is testable (§4.10)",fn:async()=>{let Z=O9({"ciam.magic_link":{module:"ciam",active:!1,ready:!1,testable:!1,reason:"magic-link disabled"},"ciam.oidc_login":{module:"ciam",active:!0,ready:!0,testable:!0}});B(H8(Z,["ciam.magic_link","ciam.oidc_login"]),void 0,"Expected an array gate to run when at least one capability is testable")}}]}import{createValidators as y6}from"@ionite/valibot";var O4=y6(),b6="urn:ionite:params:scim:schemas:extension:authz:2.0:Group";function j0(Z,$){return z(typeof Z==="object"&&Z!==null&&!Array.isArray(Z),`${$} must be an object`),Z}function h0(Z,$){return z(typeof Z==="string",`${$} must be a string`),Z}function M9(Z,$){return z(typeof Z==="number",`${$} must be a number`),Z}function Z$(Z,$){return z(Array.isArray(Z)&&Z.every((Q)=>typeof Q==="string"),`${$} must be a string array`),Z}function M4(Z,$){return z(Array.isArray(Z),`${$} must be an array`),Z}function L9(Z,$){let Q=Z[b6];if(Q===void 0)return;let X=j0(Q,`${$} authz extension`);if(X.type!==void 0)z(X.type==="group"||X.type==="role"||X.type==="permission",`${$} authz extension type must be group, role, or permission`);if(X.requestable!==void 0)z(typeof X.requestable==="boolean",`${$} authz extension requestable must be a boolean`);if(X.description!==void 0)z(typeof X.description==="string",`${$} authz extension description must be a string`)}var _9={scimPath:"/api/iam",testUser:{userName:"ecv-test-user@example.com",displayName:"ECV Test User",emails:[{value:"ecv-test-user@example.com",primary:!0}],name:{givenName:"ECV Test",familyName:"User"},"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{department:"Platform",costCenter:"ENG-001",organization:"ionite"}},testGroup:{displayName:"ECV Test Group",externalId:"ecv-test-group-001"}};async function D0(Z){let $=typeof Z.ion==="function"?Z.ion():Z.ion;if($!==null&&$!==void 0){if(await $.ready(Z.timeout??15000),!$.workload)throw Error("Workload management is required for IAM tests. Ionite instance provided but workload is not configured.");let X=$.workload;return`Bearer ${typeof X.getServerToken==="function"?await X.getServerToken():await X.getToken()}`}return`Bearer ${await b0(Z.ecvUrl??C0,W4)}`}async function h6(Z){return`Bearer ${await b0(Z.ecvUrl??C0)}`}async function P9(Z,$){return L("IAM Users Endpoint Schema",async()=>{let Q=await D0(Z),X=await $(`${Z.scimPath}/Users`,{headers:{Authorization:Q,Accept:"application/scim+json"}});z(X.status===200,`Expected 200 OK, got ${X.status}`);let Y=j0(await X.json(),"SCIM users response"),K=Z$(Y.schemas,"Response schemas"),H=M9(Y.totalResults,"Response totalResults"),J=M4(Y.Resources,"Response Resources");return z(K.includes("urn:ietf:params:scim:api:messages:2.0:ListResponse"),"Response missing SCIM ListResponse schema"),{details:{totalResults:H,resourceCount:J.length}}})}async function m6(Z,$){return L("IAM Groups Endpoint Schema",async()=>{let Q=await D0(Z),X=await $(`${Z.scimPath}/Groups`,{headers:{Authorization:Q,Accept:"application/scim+json"}});z(X.status===200,`Expected 200 OK, got ${X.status}`);let Y=j0(await X.json(),"SCIM groups response"),K=Z$(Y.schemas,"Response schemas"),H=M9(Y.totalResults,"Response totalResults"),J=Y.Resources===void 0?[]:M4(Y.Resources,"Response Resources");for(let G of J)L9(j0(G,"SCIM group resource"),"SCIM group resource");return z(K.includes("urn:ietf:params:scim:api:messages:2.0:ListResponse"),"Response missing SCIM ListResponse schema"),{details:{totalResults:H,resourceCount:J.length}}})}async function x9(Z,$){if(!Z.testUser)return{...O8("IAM Create User","No test user configured"),userId:void 0};let Q=await L("IAM Create User",async()=>{let X=await D0(Z),Y={schemas:["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],...Z.testUser},K=await $(`${Z.scimPath}/Users`,{method:"POST",headers:{Authorization:X,"Content-Type":"application/scim+json",Accept:"application/scim+json"},body:JSON.stringify(Y)});z(K.status===201,`Expected 201 Created, got ${K.status}`);let H=j0(await K.json(),"Created SCIM user");l0(H,O4.iam.user);let J=Z$(H.schemas,"Created user schemas"),G=h0(H.id,"Created user id"),W=h0(H.userName,"Created user userName");if(z(J.includes("urn:ietf:params:scim:schemas:core:2.0:User"),"Created user missing SCIM User schema"),Z.testUser?.["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"])z(J.includes("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"),"Created user missing enterprise extension schema");return{details:{userId:G,userName:W}}});return{...Q,userId:Q.details?.userId}}async function R9(Z,$,Q){return L("IAM Get User",async()=>{let X=await D0(Z),Y=await $(`${Z.scimPath}/Users/${Q}`,{headers:{Authorization:X,Accept:"application/scim+json"}});z(Y.status===200,`Expected 200 OK, got ${Y.status}`);let K=j0(await Y.json(),"Fetched SCIM user");l0(K,O4.iam.user);let H=h0(K.id,"Fetched user id"),J=h0(K.userName,"Fetched user userName");z(H===Q,`User ID mismatch: expected ${Q}, got ${H}`);let G=Z.testUser?.["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"];if(G){let W=j0(K["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"Fetched user enterprise extension");if(G.department!==void 0)z(W.department===G.department,`Expected department ${G.department}, got ${String(W.department)}`);if(G.costCenter!==void 0)z(W.costCenter===G.costCenter,`Expected costCenter ${G.costCenter}, got ${String(W.costCenter)}`)}return{details:{userId:H,userName:J}}})}async function q9(Z,$,Q){return L("IAM Update User",async()=>{let X=await D0(Z),Y={schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"displayName",value:"ECV Test User (Updated)"}]},K=await $(`${Z.scimPath}/Users/${Q}`,{method:"PATCH",headers:{Authorization:X,"Content-Type":"application/scim+json",Accept:"application/scim+json"},body:JSON.stringify(Y)});z(K.status===200,`Expected 200 OK, got ${K.status}`);let H=j0(await K.json(),"Updated SCIM user"),J=h0(H.id,"Updated user id"),G=h0(H.displayName,"Updated user displayName");return z(G==="ECV Test User (Updated)","displayName was not updated"),{details:{userId:J,displayName:G}}})}async function T9(Z,$,Q){return L("IAM Delete User",async()=>{let X=await D0(Z),Y=await $(`${Z.scimPath}/Users/${Q}`,{method:"DELETE",headers:{Authorization:X}});z(Y.status===204,`Expected 204 No Content, got ${Y.status}`);let K=await $(`${Z.scimPath}/Users/${Q}`,{headers:{Authorization:X,Accept:"application/scim+json"}});return z(K.status===404,`Expected 404 after deletion, got ${K.status}`),{details:{userId:Q,deleted:!0}}})}async function U9(Z,$){if(!Z.testGroup)return{...O8("IAM Create Group","No test group configured"),groupId:void 0};let Q=await L("IAM Create Group",async()=>{let X=await D0(Z),Y={schemas:["urn:ietf:params:scim:schemas:core:2.0:Group"],...Z.testGroup},K=await $(`${Z.scimPath}/Groups`,{method:"POST",headers:{Authorization:X,"Content-Type":"application/scim+json",Accept:"application/scim+json"},body:JSON.stringify(Y)});z(K.status===201,`Expected 201 Created, got ${K.status}`);let H=j0(await K.json(),"Created SCIM group");l0(H,O4.iam.group);let J=h0(H.id,"Created group id"),G=h0(H.displayName,"Created group displayName");return L9(H,"Created SCIM group"),{details:{groupId:J,displayName:G}}});return{...Q,groupId:Q.details?.groupId}}async function w9(Z,$,Q){return L("IAM Delete Group",async()=>{let X=await D0(Z),Y=await $(`${Z.scimPath}/Groups/${Q}`,{method:"DELETE",headers:{Authorization:X}});return z(Y.status===204,`Expected 204 No Content, got ${Y.status}`),{details:{groupId:Q,deleted:!0}}})}async function A9(Z,$){return L("IAM Users Endpoint Requires Authentication",async()=>{let Q=await $(`${Z.scimPath}/Users`,{headers:{Accept:"application/scim+json"}});return z(Q.status===401,`Expected 401 Unauthorized for Users, got ${Q.status}`),{details:{usersRequiresAuth:!0}}})}async function k6(Z,$){return L("IAM Groups Endpoint Requires Authentication",async()=>{let Q=await $(`${Z.scimPath}/Groups`,{headers:{Accept:"application/scim+json"}});return z(Q.status===401,`Expected 401 Unauthorized for Groups, got ${Q.status}`),{details:{groupsRequiresAuth:!0}}})}async function I9(Z,$){return L("IAM Users Endpoint Denies Unauthorized (403)",async()=>{let Q=await h6(Z),X=await $(`${Z.scimPath}/Users`,{headers:{Authorization:Q,Accept:"application/scim+json"}});return z(X.status===403,`Expected 403 Forbidden for an authenticated-but-unauthorized token, got ${X.status}`),{details:{deniedByDefault:!0}}})}async function f6(Z){let $=performance.now(),Q={..._9,...Z},X=c(Q),Y=[],K=typeof Z.ion==="function"?Z.ion():Z.ion;if((K===null||K===void 0)&&!Z.ecvUrl)return{suite:"IAM",passed:!1,tests:[{name:"IAM Configuration",passed:!1,error:"IAM tests require either es (Ionite instance) or ecvUrl (ECV mock server URL)",duration:0}],duration:0,summary:{total:1,passed:0,failed:1,skipped:0}};if(K&&!K.workload)return{suite:"IAM",passed:!1,tests:[{name:"IAM Configuration",passed:!1,error:"Workload management is required for IAM tests. Ionite instance provided but workload is not configured.",duration:0}],duration:0,summary:{total:1,passed:0,failed:1,skipped:0}};Y.push(await A9(Q,X)),Y.push(await I9(Q,X)),Y.push(await P9(Q,X));let H=await x9(Q,X);if(Y.push(H),H.passed&&H.userId)Y.push(await R9(Q,X,H.userId)),Y.push(await q9(Q,X,H.userId)),Y.push(await T9(Q,X,H.userId));if(Z.testGroup){Y.push(await k6(Q,X)),Y.push(await m6(Q,X));let N=await U9(Q,X);if(Y.push(N),N.passed&&N.groupId)Y.push(await w9(Q,X,N.groupId))}let J=performance.now()-$,G=Y.filter((N)=>N.passed).length,W=Y.filter((N)=>!N.passed).length,j=Y.filter((N)=>N.details?.skipped).length;return{suite:"IAM",passed:W===0,tests:Y,duration:J,summary:{total:Y.length,passed:G-j,failed:W,skipped:j}}}function m8(Z){let $={..._9,...Z},Q=c($),X,Y,K;return{tests:[{name:"users endpoint requires authentication",fn:async()=>{let G=await A9($,Q);if(!G.passed)throw Error(G.error)}},{name:"users endpoint denies an authenticated-but-unauthorized token (403)",fn:async()=>{let G=await I9($,Q);if(!G.passed)throw Error(G.error)}},{name:"users endpoint returns proper SCIM schema",fn:async()=>{let G=await P9($,Q);if(!G.passed)throw Error(G.error)}},{name:"can create a user",fn:async()=>{let G=await x9($,Q);if(!G.passed)throw Error(G.error);X=G.userId}},{name:"can get a user by ID",fn:async()=>{if(!X)throw Error("No user ID from create test");let G=await R9($,Q,X);if(!G.passed)throw Error(G.error)}},{name:"can update a user",fn:async()=>{if(!X)throw Error("No user ID from create test");let G=await q9($,Q,X);if(!G.passed)throw Error(G.error)}},{name:"can delete a user",fn:async()=>{if(!X)throw Error("No user ID from create test");let G=await T9($,Q,X);if(!G.passed)throw Error(G.error)}}],ext:{createGroupsOutboundTests:()=>[{name:"can create a group in external IAM",fn:async()=>{let G=await U9($,Q);if(!G.passed)throw Error(G.error);Y=G.groupId}},{name:"can delete a group in external IAM",fn:async()=>{if(!Y)throw Error("No group ID from create test");let G=await w9($,Q,Y);if(!G.passed)throw Error(G.error)}}],createGroupsInboundTests:()=>{let G=Z.groupsInboundPath??`${Z.scimPath}/Groups`;return[{name:"inbound endpoint requires authentication",fn:async()=>{let W=await Q(G,{headers:{Accept:"application/scim+json"}});if(W.status!==401)throw Error(`Expected 401 Unauthorized, got ${W.status}`)}},{name:"inbound endpoint returns proper SCIM schema",fn:async()=>{let W=await D0($),j=await Q(G,{headers:{Authorization:W,Accept:"application/scim+json"}});if(j.status!==200)throw Error(`Expected 200 OK, got ${j.status}`);let N=j0(await j.json(),"Inbound groups response");if(!Z$(N.schemas,"Inbound response schemas").includes("urn:ietf:params:scim:api:messages:2.0:ListResponse"))throw Error("Response missing SCIM ListResponse schema")}},{name:"can receive group creation from IAM provider",fn:async()=>{let W=await D0($),N=await Q(G,{method:"POST",headers:{Authorization:W,"Content-Type":"application/scim+json",Accept:"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:schemas:core:2.0:Group"],displayName:"ECV Inbound Test Group",externalId:"ecv-inbound-test-group-001"})});if(N.status!==201)throw Error(`Expected 201 Created, got ${N.status}`);let D=j0(await N.json(),"Created inbound SCIM group"),F=h0(D.id,"Created inbound group id");if(h0(D.displayName,"Created inbound group displayName"),!F)throw Error("Created group missing id or displayName");K=F}},{name:"ServiceProviderConfig advertises filter support (§11.3)",fn:async()=>{let W=await D0($),j=G.replace(/\/Groups$/,""),N=await Q(`${j}/ServiceProviderConfig`,{headers:{Authorization:W,Accept:"application/scim+json"}});if(N.status!==200)throw Error(`Expected 200 OK from ServiceProviderConfig, got ${N.status}`);let D=j0(await N.json(),"ServiceProviderConfig");if(j0(D.filter,"ServiceProviderConfig.filter").supported!==!0)throw Error("ServiceProviderConfig must advertise filter.supported: true (filtering IS implemented)")}},{name:"externalId eq filter round-trips the created group (§11.3)",fn:async()=>{let W=await D0($),j=encodeURIComponent('externalId eq "ecv-inbound-test-group-001"'),N=await Q(`${G}?filter=${j}`,{headers:{Authorization:W,Accept:"application/scim+json"}});if(N.status!==200)throw Error(`Expected 200 OK from filtered Groups list, got ${N.status}`);let D=j0(await N.json(),"Filtered groups response");if(!M4(D.Resources??[],"Filtered Resources").map((O)=>j0(O,"Filtered resource")).find((O)=>O.externalId==="ecv-inbound-test-group-001"))throw Error("externalId eq filter did not return the group created with that externalId")}},{name:"rejects a malformed PATCH body with 400 (§10.2)",fn:async()=>{if(!K)throw Error("No group ID from create test");let W=await D0($),j=await Q(`${G}/${K}`,{method:"PATCH",headers:{Authorization:W,"Content-Type":"application/scim+json",Accept:"application/scim+json"},body:JSON.stringify({schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"displayName",value:12345}]})});if(j.status!==400)throw Error(`Expected 400 for a malformed group PATCH body, got ${j.status}`)}},{name:"can receive group deletion from IAM provider",fn:async()=>{if(!K)throw Error("No group ID from create test");let W=await D0($),j=await Q(`${G}/${K}`,{method:"DELETE",headers:{Authorization:W}});if(j.status!==204)throw Error(`Expected 204 No Content, got ${j.status}`)}}]}}}}import{createManagedIamDelta as L4,InMemoryIamDeltaIdMapStore as Y$,ScimOutboundClient as g6,silentLogger as K$}from"@ionite/server";var p6="urn:ietf:params:scim:api:messages:2.0:ListResponse";function c0(Z="https://iam.example.test/scim/v2"){let $=new Map,Q=new Set,X=new Map,Y=new Set,K=[],H=0,J,G,W=(D,F)=>new Response(JSON.stringify(F),{status:D,headers:{"Content-Type":"application/scim+json"}}),j=(D)=>W(200,{schemas:[p6],totalResults:D?1:0,Resources:D?[{id:D}]:[]});return{baseUrl:Z,fetchImpl:async(D,F)=>{let M=new URL(D),O=(F?.method??"GET").toUpperCase(),P=M.pathname.split("/").filter(Boolean),R=P[P.length-2]==="Groups"?"Groups":P[P.length-1],x=M.pathname.endsWith("/Users"),V=M.pathname.endsWith("/Groups"),S=M.searchParams.get("filter")??void 0,u=F?.body?JSON.parse(String(F.body)):void 0;if(K.push({method:O,path:M.pathname,filter:S,body:u}),O==="GET"&&(x||V)){if(G!==void 0)return W(G,{detail:"lookup rejected"});let b=S?.match(/externalId eq "([^"]*)"/i)?.[1];z(b!==void 0,"destination lookups must be by externalId filter only");let X0=(x?$:X).get(b);return j(X0)}if(O==="POST"&&(x||V)){let b=String(u?.externalId??"");H+=1;let X0=`${x?"u":"g"}_${H}`;if(x)$.set(b,X0),Q.add(X0);else X.set(b,X0),Y.add(X0);return W(201,{id:X0,externalId:b})}if(O==="PATCH"&&R==="Groups"){let b=decodeURIComponent(P[P.length-1]??"");if(!Y.has(b))return W(404,{detail:"group not found"});if(J!==void 0)return W(J,{detail:"patch rejected"});return W(200,{id:b})}return W(404,{detail:"not found"})},calls:K,seedUser(D,F){$.set(D,F),Q.add(F)},seedGroup(D,F){X.set(D,F),Y.add(F)},countCalls(D){return K.filter(D).length},failGroupPatchWith(D){J=D},failLookupWith(D){G=D}}}function z8(Z){let $=new Y$;return{client:new g6({baseUrl:Z.baseUrl,getToken:async()=>"test-token",idMapStore:$,tenantId:"acme",log:K$,fetchImpl:Z.fetchImpl}),idMapStore:$}}var X$={getServerToken:async()=>"test-token",getToken:async(Z)=>`token-for-${Z??"server"}`};function k8(){return{tests:[{name:"resolveProviderId resolves a destination id STRICTLY by externalId filter",fn:async()=>{let $=c0();$.seedUser("u-alice","u_42");let{client:Q,idMapStore:X}=z8($),Y=await Q.resolveProviderId("user","u-alice");z(typeof Y==="object"&&Y.found==="u_42","resolves the destination id returned by the externalId filter lookup");let K=$.calls.find((J)=>J.method==="GET"&&J.path.endsWith("/Users"));z(K?.filter?.includes('externalId eq "u-alice"')===!0,"lookup used the externalId filter");let H=await X.get({tenantId:"acme",destination:$.baseUrl,resourceType:"user",externalId:"u-alice"});z(H==="u_42","the resolved id is written through to the mapping cache")}},{name:"create-on-empty: an unknown externalId is created at the destination",fn:async()=>{let $=c0(),{client:Q}=z8($),X=await Q.resolveProviderId("group","g-new",{displayName:"New Group"});z(typeof X==="object"&&X.found.startsWith("g_"),"a missing resource is created and its id returned"),z($.countCalls((Y)=>Y.method==="POST"&&Y.path.endsWith("/Groups"))===1,"exactly one create is issued when the destination has no match")}},{name:"mapping hit: a cached id avoids a second filter lookup",fn:async()=>{let $=c0();$.seedGroup("g-eng","g_7");let{client:Q}=z8($);await Q.resolveProviderId("group","g-eng"),await Q.resolveProviderId("group","g-eng"),z($.countCalls((X)=>X.method==="GET"&&X.path.endsWith("/Groups"))===1,"the second resolve is served from the mapping cache (no extra filter GET)")}},{name:"patchGroupMembership(add) resolves both sides by externalId and PATCHes membership",fn:async()=>{let $=c0();$.seedUser("u-alice","u_1"),$.seedGroup("g-eng","g_1");let{client:Q}=z8($);await Q.patchGroupMembership({groupExternalId:"g-eng",userExternalId:"u-alice",op:"add"});let X=$.calls.find((K)=>K.method==="PATCH"&&K.path.endsWith("/Groups/g_1"));z(X!==void 0,"a membership PATCH is issued against the resolved group id");let Y=X?.body?.Operations;z(Y?.[0]?.op==="add","the patch adds membership"),z(Y?.[0]?.value?.[0]?.value==="u_1","membership references the resolved user provider id")}},{name:"stale cached group id self-heals on a destination 404 (invalidate -> re-resolve -> retry)",fn:async()=>{let $=c0();$.seedUser("u-alice","u_1"),$.seedGroup("g-eng","g_real");let{client:Q,idMapStore:X}=z8($);await X.set({tenantId:"acme",destination:$.baseUrl,resourceType:"group",externalId:"g-eng"},"g_stale"),await Q.patchGroupMembership({groupExternalId:"g-eng",userExternalId:"u-alice",op:"add"}),z($.calls.some((K)=>K.method==="PATCH"&&K.path.endsWith("/Groups/g_stale")),"the first PATCH targets the stale id and is rejected (404)"),z($.calls.some((K)=>K.method==="PATCH"&&K.path.endsWith("/Groups/g_real")),"after self-heal the retry targets the freshly re-resolved id");let Y=await X.get({tenantId:"acme",destination:$.baseUrl,resourceType:"group",externalId:"g-eng"});z(Y==="g_real","the mapping cache is repaired to the correct provider id")}},{name:"idempotency: repeated adds do not create duplicate destination resources",fn:async()=>{let $=c0(),{client:Q}=z8($),X={groupExternalId:"g-eng",userExternalId:"u-alice",op:"add",groupCreate:{displayName:"Eng"},userCreate:{userName:"alice"}};await Q.patchGroupMembership(X),await Q.patchGroupMembership(X),z($.countCalls((Y)=>Y.method==="POST"&&Y.path.endsWith("/Users"))===1,"the user is created at most once across repeated emits"),z($.countCalls((Y)=>Y.method==="POST"&&Y.path.endsWith("/Groups"))===1,"the group is created at most once across repeated emits")}},{name:"readiness: patch needs url + workload + id map",fn:async()=>{let $=L4(K$),Q=new Y$,X=$.update({mode:"patch"},{workload:X$,idMapStore:Q});z(!X.isReady(),"'patch' without a destination url is not ready");let Y=$.update({mode:"patch",url:"https://iam.example.test/scim/v2"},{idMapStore:Q});z(!Y.isReady(),"'patch' without a workload identity is not ready");let K=$.update({mode:"patch",url:"https://iam.example.test/scim/v2",tenantId:"acme"},{workload:X$,idMapStore:Q});z(K.isReady(),"'patch' with url + workload + id map is ready"),await K.ready(1000)}},{name:"externalId-only contract: emitter skips a grant when subject/target lack an externalId",fn:async()=>{let Q=L4(K$).update({mode:"patch",url:"https://iam.example.test/scim/v2",tenantId:"acme"},{workload:X$,idMapStore:new Y$});z(Q.isReady(),"emitter is ready for the reconcile path");async function*X(){yield{subject:"alice",targetType:"group",target:"eng",status:"active"}}let Y=await Q.reconcile(X());z(Y.pushed===0,"an unresolvable subject/target is not pushed"),z(Y.skipped===1,"it is skipped per the externalId-only contract"),z(Y.errors.length===0,"a skip is not an error")}},{name:'patchGroupMembership reports "error" when the destination rejects the PATCH (no false success)',fn:async()=>{let $=c0();$.seedUser("u-alice","u_1"),$.seedGroup("g-eng","g_1"),$.failGroupPatchWith(500);let{client:Q}=z8($),X=await Q.patchGroupMembership({groupExternalId:"g-eng",userExternalId:"u-alice",op:"add"});z(X==="error",'a non-2xx destination PATCH is reported as an error, not "pushed"')}},{name:'patchGroupMembership reports "error" (never a create) when the externalId lookup fails',fn:async()=>{let $=c0();$.failLookupWith(500);let{client:Q}=z8($),X=await Q.patchGroupMembership({groupExternalId:"g-eng",userExternalId:"u-alice",op:"add",groupCreate:{displayName:"Eng"},userCreate:{userName:"alice"}});z(X==="error",'a failed lookup is reported as an error, not "skipped" or "pushed"'),z($.countCalls((Y)=>Y.method==="POST")===0,"a failed lookup must NOT fall through to create-on-empty (no POST issued)")}},{name:"reconcile surfaces a failing destination PATCH as a reconcile error",fn:async()=>{let $=c0();$.seedUser("u-alice","u_1"),$.seedGroup("g-eng","g_1"),$.failGroupPatchWith(503);let X=L4(K$).update({mode:"patch",url:$.baseUrl,tenantId:"acme"},{workload:X$,idMapStore:new Y$,fetchImpl:$.fetchImpl});async function*Y(){yield{subject:{id:"alice",externalId:"u-alice",userName:"alice"},targetType:"group",target:{id:"eng",externalId:"g-eng",displayName:"Eng"},status:"active"}}let K=await X.reconcile(Y());z(K.pushed===0,"a rejected destination push is not counted as pushed"),z(K.errors.length>0,"a rejected destination PATCH is recorded as a reconcile error")}}]}}async function u6(){let Z=performance.now(),$=[],Q=[...k8().tests];for(let H of Q)$.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=$.filter((H)=>!H.passed).length,K=$.filter((H)=>H.details?.skipped).length;return{suite:"IAM Delta",passed:Y===0,tests:$,duration:X,summary:{total:$.length,passed:$.length-Y-K,failed:Y,skipped:K}}}import{generateLfvEd25519KeyPair as H0,signLfvPayload as C9,stringifyLfvJwks as G0}from"@ionite/core/server";import{ionite as l6,lfvVault as N0}from"@ionite/server";import{createValidators as d6}from"@ionite/valibot";var _={lfvServerUrl:"http://localhost:3592/api/lfv",clientId:"ecv-lfv-client",path:"/ecv/lfv/config",deliveryEndpoint:"/api/ionite/lfv/delivery",eventsEndpoint:"/api/ionite/lfv/events"};async function $0(Z,$,Q,X,Y){let K=String($.request_id),H="issued_at"in $?$:{...$,issued_at:Date.now()},J=JSON.stringify(H),G=Y??await C9({privateKeyJwk:Q,requestId:K,rawBody:J});return new Request(`http://localhost${Z}`,{method:"POST",headers:{"Content-Type":"application/json","X-LFV-Request-Id":K,"X-LFV-Key-Id":X,"X-LFV-Signature":G},body:J})}function c6(Z){return{async load(){return Z},async subscribe(){return},secret:{}}}function f8(){return{tests:[{name:"correctly signed delivery callback is accepted",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-delivery-kid"),X=G0(Q),Y=N0({..._,verifyJwks:X}),H={request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"signed-ok"},metadata:{path:_.path,version:1}}},J=await Y.handleDelivery(await $0(_.deliveryEndpoint,H,$,"ecv-delivery-kid"));B(J.status,200,"signed delivery should return 200");let G=await Y.getSecret(_.path);B(G.marker,"signed-ok","delivery payload should be cached")}},{name:"pushed update delivery is applied (and propagated to subscribers) without a client re-read",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-update-kid"),X=G0(Q),Y=N0({..._,verifyJwks:X}),K={request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"v1"},metadata:{path:_.path,version:1}}},H=await Y.handleDelivery(await $0(_.deliveryEndpoint,K,$,"ecv-update-kid"));B(H.status,200,"initial delivery should return 200");let J=await Y.getSecret(_.path);B(J.marker,"v1","initial pushed value should be cached");let G=[],W=await Y.subscribe(_.path,(F)=>{G.push(F.data.marker)});B(G[G.length-1],"v1","subscriber should receive the cached v1 value");let j={request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"v2"},metadata:{path:_.path,version:2}}},N=await Y.handleDelivery(await $0(_.deliveryEndpoint,j,$,"ecv-update-kid"));B(N.status,200,"update delivery should return 200");let D=await Y.getSecret(_.path);B(D.marker,"v2","updated value should be applied directly from the push (no re-read)"),B(G[G.length-1],"v2","subscriber should be notified of the pushed update without issuing a re-read"),W()}},{name:"forged delivery signature is rejected with 401 invalid_signature",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-forged-kid"),X=G0(Q),Y=N0({..._,verifyJwks:X}),H={request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"forged"},metadata:{path:_.path,version:1}}},J=await Y.handleDelivery(await $0(_.deliveryEndpoint,H,$,"ecv-forged-kid","AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));B(J.status,401,"forged delivery should return 401");let G=await J.json();B(G.error,"invalid_signature","error code should be invalid_signature")}},{name:"delivery without signature headers is rejected",fn:async()=>{let{publicJwks:$}=await H0("ecv-missing-kid"),X=await N0({..._,verifyJwks:G0($)}).handleDelivery(new Request(`http://localhost${_.deliveryEndpoint}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({request_id:crypto.randomUUID(),path:_.path,data:{data:{},metadata:{path:_.path,version:1}}})}));B(X.status,401,"unsigned delivery should return 401")}},{name:"delivery rejects request_id header/body mismatch",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-mismatch-kid"),X=G0(Q),Y=N0({..._,verifyJwks:X}),K=crypto.randomUUID(),H=JSON.stringify({request_id:K,path:_.path,data:{data:{},metadata:{path:_.path,version:1}}}),J=await C9({privateKeyJwk:$,requestId:K,rawBody:H}),G=await Y.handleDelivery(new Request(`http://localhost${_.deliveryEndpoint}`,{method:"POST",headers:{"Content-Type":"application/json","X-LFV-Request-Id":crypto.randomUUID(),"X-LFV-Key-Id":"ecv-mismatch-kid","X-LFV-Signature":J},body:H}));B(G.status,401,"mismatched request_id should return 401")}},{name:"delivery is rejected when verifyJwks is not configured",fn:async()=>{let{privateJwk:$}=await H0("ecv-no-jwks-kid"),Q=N0({..._}),Y={request_id:crypto.randomUUID(),path:_.path,data:{data:{},metadata:{path:_.path,version:1}}},K=await Q.handleDelivery(await $0(_.deliveryEndpoint,Y,$,"ecv-no-jwks-kid"));B(K.status,401,"delivery without verifyJwks should return 401")}},{name:"correctly signed events callback is accepted",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-events-kid"),X=N0({..._,verifyJwks:G0(Q)}),K={request_id:crypto.randomUUID(),path:_.path,event:"updated"},H=await X.handleEvents(await $0(_.eventsEndpoint,K,$,"ecv-events-kid"));B(H.status,200,"signed events should return 200")}},{name:"forged events signature is rejected with 401",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-events-forged-kid"),X=N0({..._,verifyJwks:G0(Q)}),K={request_id:crypto.randomUUID(),path:_.path,event:"revoked"},H=await X.handleEvents(await $0(_.eventsEndpoint,K,$,"ecv-events-forged-kid","BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"));B(H.status,401,"forged events should return 401")}},{name:"replayed delivery callback (same signature) is rejected as a replay",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-replay-kid"),X=N0({..._,verifyJwks:G0(Q)}),Y={request_id:crypto.randomUUID(),path:_.path,issued_at:Date.now(),data:{data:{marker:"replay-once"},metadata:{path:_.path,version:1}}},K=await X.handleDelivery(await $0(_.deliveryEndpoint,Y,$,"ecv-replay-kid"));B(K.status,200,"first delivery should be accepted");let H=await X.handleDelivery(await $0(_.deliveryEndpoint,Y,$,"ecv-replay-kid"));B(H.status,401,"an exact replay of the same signed callback must be rejected");let J=await H.json();B(J.error,"invalid_signature","replay rejection uses the invalid_signature code")}},{name:"stale delivery callback (issued_at outside freshness window) is rejected",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-stale-kid"),X=N0({..._,verifyJwks:G0(Q)}),Y={request_id:crypto.randomUUID(),path:_.path,issued_at:Date.now()-600000,data:{data:{marker:"stale"},metadata:{path:_.path,version:1}}},K=await X.handleDelivery(await $0(_.deliveryEndpoint,Y,$,"ecv-stale-kid"));B(K.status,401,"a stale callback must be rejected")}},{name:"older-version delivery does not roll the cache backward",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-monotonic-kid"),X=N0({..._,verifyJwks:G0(Q)}),Y=async(J,G)=>X.handleDelivery(await $0(_.deliveryEndpoint,{request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:J},metadata:{path:_.path,version:G}}},$,"ecv-monotonic-kid"));B((await Y("v2",2)).status,200,"v2 delivery accepted");let K=await X.getSecret(_.path);B(K.marker,"v2","v2 is cached"),B((await Y("v1-old",1)).status,200,"older delivery is acknowledged (200) but ignored");let H=await X.getSecret(_.path);B(H.marker,"v2","an older-version delivery must NOT overwrite the newer cached value")}},{name:"callback fan-out never crosses a trust boundary (delivery for A never updates B)",fn:async()=>{let $=await H0("ecv-trust-a"),Q=await H0("ecv-trust-b"),X={lfvServerUrl:_.lfvServerUrl,clientId:_.clientId,path:"/ecv/lfv/trust-shared",deliveryEndpoint:_.deliveryEndpoint,eventsEndpoint:_.eventsEndpoint},Y=N0({...X,verifyJwks:G0($.publicJwks)}),K=N0({...X,verifyJwks:G0(Q.publicJwks)});B(Y.callbackKey,K.callbackKey,"both sources register under one callback key"),z(Y.trustId!==K.trustId,"the two sources carry distinct trust identities");let H=async(D,F,M,O)=>{await D.handleDelivery(await $0(X.deliveryEndpoint,{request_id:crypto.randomUUID(),path:X.path,data:{data:{marker:O},metadata:{path:X.path,version:1}}},F.privateJwk,M))};await H(Y,$,"ecv-trust-a","A1"),await H(K,Q,"ecv-trust-b","B1");let J=[],G=[],W=await Y.subscribe(X.path,(D)=>J.push(D.data.marker)),j=await K.subscribe(X.path,(D)=>G.push(D.data.marker));B(J[J.length-1],"A1","A observes its own seeded value"),B(G[G.length-1],"B1","B observes its own seeded value"),await Y.handleDelivery(await $0(X.deliveryEndpoint,{request_id:crypto.randomUUID(),path:X.path,data:{data:{marker:"A2"},metadata:{path:X.path,version:2}}},$.privateJwk,"ecv-trust-a")),B(J[J.length-1],"A2","A applies its own trusted update"),z(!G.includes("A2"),"B must never receive A's trust-scoped delivery via fan-out");let N=await K.getSecret(X.path);B(N.marker,"B1","B's cache is untouched by A's delivery"),W(),j(),Y.close?.(),K.close?.()}},{name:"failed post-event refresh marks the source degraded (isAvailable() false), not a stale serve",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-degraded-kid"),X=G0(Q),Y=N0({..._,verifyJwks:X,deliveryTimeout:150,retryInterval:40});await Y.handleDelivery(await $0(_.deliveryEndpoint,{request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"v1"},metadata:{path:_.path,version:1}}},$,"ecv-degraded-kid")),B(Y.isAvailable?.(),!0,"a freshly-seeded source is available"),await Y.handleEvents(await $0(_.eventsEndpoint,{request_id:crypto.randomUUID(),path:_.path,event:"revoked"},$,"ecv-degraded-kid"));let K=Date.now()+3000;while(Y.isAvailable?.()!==!1&&Date.now()<K)await new Promise((H)=>setTimeout(H,25));B(Y.isAvailable?.(),!1,"a failed post-event refresh must mark the source degraded via isAvailable()"),await Y.handleDelivery(await $0(_.deliveryEndpoint,{request_id:crypto.randomUUID(),path:`${_.path}/unrelated`,data:{data:{marker:"other"},metadata:{path:`${_.path}/unrelated`,version:1}}},$,"ecv-degraded-kid")),B(Y.isAvailable?.(),!1,"an unrelated path's delivery must not clear another path's degradation"),await Y.handleDelivery(await $0(_.deliveryEndpoint,{request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"v2"},metadata:{path:_.path,version:2}}},$,"ecv-degraded-kid")),B(Y.isAvailable?.(),!0,"fresh material for the degraded path recovers isAvailable()"),Y.close?.()}},{name:"cacheMaxAgeSeconds bounds cache hits: a stale entry forces a fresh read",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await H0("ecv-maxage-kid"),X=G0(Q),Y=N0({..._,verifyJwks:X,cacheMaxAgeSeconds:0.05,deliveryTimeout:150,retryInterval:40});await Y.handleDelivery(await $0(_.deliveryEndpoint,{request_id:crypto.randomUUID(),path:_.path,data:{data:{marker:"fresh"},metadata:{path:_.path,version:1}}},$,"ecv-maxage-kid"));let K=await Y.getSecret(_.path);B(K.marker,"fresh","a fresh cache hit is served directly"),await new Promise((J)=>setTimeout(J,120));let H=!1;try{await Y.getSecret(_.path),H=!0}catch{}z(!H,"a cache entry older than cacheMaxAgeSeconds must not be served from cache"),Y.close?.()}},{name:"two LFV sources on one serverUrl|path with different verifyJwks are rejected at apply",fn:async()=>{let $=G0((await H0("ecv-apply-a")).publicJwks),Q=G0((await H0("ecv-apply-b")).publicJwks),X="/ecv/lfv/apply-shared",Y={id:"ecv-lfv-dual-trust",secrets:{SRC_A:{type:"vault",lfv:{lfvServerUrl:_.lfvServerUrl,clientId:"ecv-apply-client",path:"/ecv/lfv/apply-shared",deliveryEndpoint:"/api/a/lfv/delivery",eventsEndpoint:"/api/a/lfv/events",verifyJwks:$}},SRC_B:{type:"vault",lfv:{lfvServerUrl:_.lfvServerUrl,clientId:"ecv-apply-client",path:"/ecv/lfv/apply-shared",deliveryEndpoint:"/api/b/lfv/delivery",eventsEndpoint:"/api/b/lfv/events",verifyJwks:Q}}}},K=l6(c6(Y),{validators:d6()}),H=!1,J="";try{await K.ready(2000)}catch(G){H=!0,J=G instanceof Error?G.message:String(G)}K.close?.(),z(H,"a dual-trust LFV config must reject ion.ready() at apply time"),z(J.toLowerCase().includes("trust key"),`apply rejection should explain the single-trust-key-per-path invariant (got: ${J})`)}}]}}async function i6(){let Z=performance.now(),{tests:$}=f8(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"LFV callbacks",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}import{ionite as _4}from"@ionite/server";import{createValidators as P4}from"@ionite/valibot";var v9=(Z)=>new Promise(($)=>setTimeout($,Z));async function E9(Z,$=1500,Q=20){let X=Date.now()+$;while(Date.now()<X){if(Z())return!0;await v9(Q)}return Z()}function x4(Z){let $=Z,Q;return{async load(){return $},async subscribe(X){return Q=X,()=>{Q=void 0}},push(X){$=X,Q?.(X)},secret:{}}}var S9={name:"ecv-otel",traces:!1,metrics:!1,logs:!1};function g8(){return{tests:[{name:"otel module is inactive (not-ready) when RemoteConfig omits otel (§22.2)",fn:async()=>{let $=x4({id:"otel-off"}),Q=_4($,{validators:P4()});try{await Q.ready(500),z(Q.otel?.isReady?.()!==!0,"Expected ion.otel to be not-ready with no RemoteConfig.otel");let X=Q.readiness();B(X.modules.otel.active,!1,"Expected modules.otel.active === false when unconfigured");let Y=Q.requireReady(["otel"]);z(Y.ok===!1,'Expected requireReady(["otel"]) to fail when otel is unconfigured'),z(Y.notReady.some((K)=>K.name==="otel"),"Expected otel to be listed among the not-ready dependencies")}finally{Q.close?.()}}},{name:"RemoteConfig.otel activates the module; de-configuration re-arms readiness (§22.2)",fn:async()=>{let $=x4({id:"otel-rearm",otel:{...S9}}),Q=_4($,{validators:P4()});try{await Q.ready(1000),z(Q.otel?.isReady?.()===!0,"Expected ion.otel.isReady() true after RemoteConfig.otel applied");let X=Q.readiness();B(X.modules.otel.active,!0,"Expected modules.otel.active === true once configured"),B(X.modules.otel.ready,!0,"Expected modules.otel.ready === true once started"),z(Q.requireReady(["otel"]).ok===!0,'Expected requireReady(["otel"]) to pass when active+ready'),$.push({id:"otel-rearm"}),await E9(()=>Q.otel?.isReady?.()!==!0),z(Q.otel?.isReady?.()!==!0,"Expected ion.otel.isReady() false after de-configuration");let Y=Q.readiness();B(Y.modules.otel.active,!1,"Expected modules.otel.active === false after de-config"),z(Q.requireReady(["otel"]).ok===!1,'Expected requireReady(["otel"]) to fail once otel is de-configured');let K=!1;try{await Q.otel?.ready?.(50)}catch{K=!0}z(K,"Expected a re-armed ion.otel.ready(50) to reject while de-configured");let H={resolved:!1},J=Q.otel?.ready?.().then(()=>{H.resolved=!0}).catch(()=>{});await v9(60);let G=H.resolved===!1;z(G,"Expected a fresh no-timeout ready() to stay pending while inactive"),$.push({id:"otel-rearm",otel:{...S9}}),await E9(()=>Q.otel?.isReady?.()===!0),await J;let W=H.resolved===!0;z(W,"Expected the re-armed ready() waiter to resolve after re-activation"),z(Q.otel?.isReady?.()===!0,"Expected ion.otel.isReady() true after re-activation"),z(Q.requireReady(["otel"]).ok===!0,'Expected requireReady(["otel"]) to pass again after re-activation')}finally{Q.close?.()}}},{name:"an invalid RemoteConfig.otel provider config fails the config apply (§22.2)",fn:async()=>{let $=x4({id:"otel-invalid",otel:{type:"datadog",name:"ecv-otel",traces:!1,metrics:!1,logs:!1}}),Q=_4($,{validators:P4()});try{let X=!1;try{await Q.ready(500)}catch{X=!0}z(X,"Expected an invalid otel provider config to reject readiness at apply"),z(Q.configStatus?.lastError!==void 0,"Expected configStatus.lastError to record the failed otel apply")}finally{Q.close?.()}}}]}}async function n6(){let Z=performance.now(),{tests:$}=g8(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"OpenTelemetry",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}import{ionite as p8}from"@ionite/server";import{createValidators as u8}from"@ionite/valibot";var l8="/api/ionite",d8="http://localhost";function c8(Z){return{async load(){return Z},async subscribe(){return},secret:{}}}async function x1(Z){let $=await Z.text();try{return JSON.parse($)}catch{throw Error(`Expected a JSON body, got: ${$.slice(0,120)}`)}}function i8(){return{tests:[{name:"POST {basePath}/readyz returns a structured 405 with an Allow header (§11.2)",fn:async()=>{let Z=p8(c8({id:"protocol-readyz-405"}),{validators:u8()});await Z.ready(1000);let $=await Z.handler(new Request(`${d8}${l8}/readyz`,{method:"POST"}));B($.status,405,"POST to readyz must be 405 Method Not Allowed"),z($.headers.get("Allow")?.includes("GET")??!1,"Allow header must advertise GET");let Q=await x1($);B(Q.error,"method_not_allowed","body carries the stable method_not_allowed code"),Z.close?.()}},{name:"GET {basePath}/readyz is reachable and reports config applied",fn:async()=>{let Z=p8(c8({id:"protocol-readyz-get"}),{validators:u8()});await Z.ready(1000);let $=await Z.handler(new Request(`${d8}${l8}/readyz`));z($.status===200||$.status===503,"readyz GET returns a readiness status");let Q=await x1($);B(Q.applied,!0,"readyz reports config applied after ion.ready()"),Z.close?.()}},{name:"POST {basePath}/capabilitiez returns 405 before authentication (§11.2)",fn:async()=>{let Z=p8(c8({id:"protocol-cap-405",capabilitiez:!0}),{validators:u8()});await Z.ready(1000);let $=await Z.handler(new Request(`${d8}${l8}/capabilitiez`,{method:"POST"}));B($.status,405,"POST to capabilitiez must be 405");let Q=await x1($);B(Q.error,"method_not_allowed","capabilitiez 405 carries the stable code"),Z.close?.()}},{name:"GET {basePath}/capabilitiez returns JSON 404 when disabled (§11.2)",fn:async()=>{let Z=p8(c8({id:"protocol-cap-disabled"}),{validators:u8()});await Z.ready(1000);let $=await Z.handler(new Request(`${d8}${l8}/capabilitiez`));B($.status,404,"disabled capabilitiez is 404");let Q=await x1($);B(Q.error,"not_found","disabled capabilitiez returns the stable JSON not_found code"),Z.close?.()}},{name:"an unknown ionite route returns JSON 404 { error: not_found } (§9.2)",fn:async()=>{let Z=p8(c8({id:"protocol-unknown-404"}),{validators:u8()});await Z.ready(1000);let $=await Z.handler(new Request(`${d8}${l8}/does-not-exist`));B($.status,404,"unknown route is 404");let Q=await x1($);B(Q.error,"not_found","unknown route returns the stable JSON not_found code"),Z.close?.()}},{name:"a throwing handler is caught and sanitized to 500 { error: internal_error } (§9.2)",fn:async()=>{let $=p8(c8({id:"protocol-throw-500"}),{validators:u8(),routing:{resolve:()=>({module:"sso",kind:"sso.handler",handler:async()=>{throw Error("LEAKED_INTERNAL_DETAIL_42")}})}});await $.ready(1000);let Q=await $.handler(new Request(`${d8}${l8}/anything`));B(Q.status,500,"an unhandled handler throw becomes a 500");let X=await Q.text();z(!X.includes("LEAKED_INTERNAL_DETAIL_42"),"the raw error message must not leak to the caller");let Y=JSON.parse(X);B(Y.error,"internal_error","a thrown error collapses to the stable internal_error code"),$.close?.()}}]}}async function o6(){let{tests:Z}=i8(),$=[];for(let Q of Z)try{await Q.fn(),$.push({name:Q.name,passed:!0})}catch(X){$.push({name:Q.name,passed:!1,error:X instanceof Error?X.message:String(X)})}return $}var h9={loginPath:"/api/auth/login",callbackPath:"/api/auth/callback",userPath:"/api/auth/user",logoutPath:"/api/auth/logout",backChannelLogoutPath:"/api/auth/logout/backchannel",tokenPath:"/api/auth/token",refreshPath:"/api/auth/refresh"};async function m9(Z,$){return L("SSO Login Endpoint",async()=>{let Q=Z.loginPath??"/api/auth/login",X=await $(Q);z(X.status===302,`Expected 302 redirect, got ${X.status}`);let Y=X.headers.get("Location");if(z(Y!==null,"Missing Location header in redirect"),Z.expectedAuthorizationUrlPattern)z(Z.expectedAuthorizationUrlPattern.test(Y),`Authorization URL does not match expected pattern: ${Y}`);let K=new URL(Y);z(K.searchParams.has("client_id"),"Missing client_id in authorization URL"),z(K.searchParams.has("redirect_uri"),"Missing redirect_uri in authorization URL"),z(K.searchParams.has("response_type"),"Missing response_type in authorization URL"),z(K.searchParams.has("scope"),"Missing scope in authorization URL"),z(K.searchParams.has("state"),"Missing state in authorization URL"),z(K.searchParams.has("code_challenge"),"Missing code_challenge (PKCE) in authorization URL"),z(K.searchParams.has("code_challenge_method"),"Missing code_challenge_method (PKCE) in authorization URL");let H=M0(X.headers),J=Array.from(H.keys()).some((G)=>G.includes(".state"));return z(J,"Missing state cookie in response"),{details:{authorizationUrl:Y,pkceEnabled:!0,stateParameter:K.searchParams.get("state")}}})}async function k9(Z,$){return L("SSO User Endpoint (Unauthenticated)",async()=>{let Q=Z.userPath??"/api/auth/user",X=await $(Q);return z(X.status===401,`Expected 401 Unauthorized, got ${X.status}`),{details:{status:X.status}}})}async function f9(Z,$){return L("SSO Logout Endpoint",async()=>{let Q=Z.logoutPath??"/api/auth/logout",X=await $(`${Q}?redirect=/`);z(X.status===302||X.status===200,`Expected 302 or 200, got ${X.status}`);let Y=M0(X.headers),K=[];for(let[G,W]of Y.entries())if(W===""||W.includes("Max-Age=0"))K.push(G);let H=X.headers.getSetCookie?.()??[],J=H.some((G)=>G.includes("Max-Age=0")||G.includes("expires=Thu, 01 Jan 1970"));return z(J||K.length>0||H.length===0,"Logout should clear session cookies"),{details:{status:X.status,clearedCookies:K}}})}async function g9(Z,$){return L("SSO Back-Channel Logout Endpoint",async()=>{let Q=Z.backChannelLogoutPath??"/api/auth/logout/backchannel",X=await $(Q,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:""});return z(X.status===400||X.status===200||X.status===404,`Expected 400 or 200, got ${X.status}`),{details:{status:X.status,endpointExists:X.status!==404}}})}async function p9(Z,$){return L("SSO Callback Endpoint (Invalid)",async()=>{let Q=Z.callbackPath??"/api/auth/callback",X=await $(`${Q}?code=invalid&state=invalid`);z(X.status===302,`Expected redirect for invalid callback, got ${X.status}`);let Y=X.headers.get("Location")??"";if(!Y.startsWith("/")){let K=new URL(`${X8(Z)}/`).origin;z(new URL(Y).origin===K,`Failure redirect must stay same-origin: ${Y}`)}if(Z.expectedCallbackErrorPattern)z(Z.expectedCallbackErrorPattern.test(Y),`Failure redirect does not surface an error code (expected ${Z.expectedCallbackErrorPattern}): ${Y}`);return{details:{status:X.status,location:Y,handlesInvalidCallback:!0}}})}async function u9(Z,$){if(!Z.tokenPath)return O8("SSO Token Endpoint (Unauthenticated)","Token path not configured");let Q=Z.tokenPath;return L("SSO Token Endpoint (Unauthenticated)",async()=>{let X=await $(Q);return z(X.status===401||X.status===404,`Expected 401 Unauthorized or 404, got ${X.status}`),{details:{status:X.status}}})}async function l9(Z,$){if(!Z.refreshPath)return O8("SSO Refresh Endpoint (Unauthenticated)","Refresh path not configured");let Q=Z.refreshPath;return L("SSO Refresh Endpoint (Unauthenticated)",async()=>{let X=await $(Q);return z(X.status===401||X.status===404,`Expected 401 Unauthorized or 404, got ${X.status}`),{details:{status:X.status}}})}function y9(Z){return Array.from(Z.keys()).filter(($)=>$.includes(".state"))}function b9(Z){return Z.endsWith(".access")||Z.endsWith(".id")||Z.endsWith(".refresh")||Z.endsWith(".control")}function a6(Z){for(let $ of Z.keys()){let Q=$.indexOf(".state");if(Q>0)return $.slice(0,Q);if($.endsWith(".session"))return $.slice(0,-8)}return}function r6(Z,$){return(Z.getSetCookie?.()??[]).some((X)=>{let Y=X.split("=")[0]?.trim()??"";if(!$(Y))return!1;return X.includes("Max-Age=0")||/expires=Thu, 01 Jan 1970/i.test(X)})}async function d9(Z,$,Q={}){let X=Z.loginPath;if(!X)throw Error("Login path not configured");let Y=Q.redirectParam?`?redirect=${encodeURIComponent(Q.redirectParam)}`:"",K=await $(`${X}${Y}`);if(K.status!==302)throw Error(`Login did not redirect: ${K.status}`);let H=K.headers.get("Location");if(!H)throw Error("No redirect location from login");let J=M0(K.headers),G=new URL(H).searchParams.get("state");if(Q.bogusCode){let F=new URL(H).searchParams.get("redirect_uri");if(!F||!G)throw Error("Missing redirect_uri/state on authorization URL");let M=new URL(F).pathname;return{callback:await $(`${M}?code=bogus-code&state=${encodeURIComponent(G)}`,{headers:{Cookie:d0(J)}}),state:G}}let W=await globalThis.fetch(H,{redirect:"manual"});if(W.status!==302)throw Error(`Auth did not redirect: ${W.status}`);let j=W.headers.get("Location");if(!j)throw Error("No redirect location from auth");let N=new URL(j).pathname+new URL(j).search;return{callback:await $(N,{headers:{Cookie:d0(J)}}),state:G}}async function c9(Z,$){return L("SSO State Cookie Multi-Tab Isolation",async()=>{let Q=Z.loginPath??"/api/auth/login",[X,Y]=await Promise.all([$(Q),$(Q)]);z(X.status===302&&Y.status===302,"Both login attempts should redirect");let K=new URL(X.headers.get("Location")??"").searchParams.get("state"),H=new URL(Y.headers.get("Location")??"").searchParams.get("state");z(!!K&&!!H&&K!==H,"Each login attempt must mint a distinct state");let J=y9(M0(X.headers)),G=y9(M0(Y.headers));z(J.length===1&&G.length===1,"Each login should set exactly one state cookie"),z(J[0]!==G[0],"Concurrent logins must use isolated (distinct-name) state cookies"),z(J[0].includes(K)&&G[0].includes(H),"The state value must be encoded in the state cookie name");let W=(X.headers.getSetCookie?.()??[]).find((j)=>j.split("=")[0]?.includes(".state"));return z(!!W,"Login must emit a Set-Cookie for the state cookie"),z(/SameSite=Lax/i.test(W),`State cookie must be SameSite=Lax for the cross-site IdP callback, got: ${W}`),{details:{state1:K,state2:H,cookie1:J[0],cookie2:G[0],stateSetCookie:W}}})}async function i9(Z,$){return L("SSO State Cookie Cleared On Error",async()=>{let Q;try{Q=await d9(Z,$,{bogusCode:!0})}catch(K){return{details:{skipped:!0,reason:K instanceof Error?K.message:String(K)}}}let{callback:X,state:Y}=Q;if(z(X.status===302,`Failed callback should redirect, got ${X.status}`),z(r6(X.headers,(K)=>K.includes(".state")&&(Y?K.includes(Y):!0)),"Callback error path must clear the per-attempt state cookie"),Z.expectedCallbackErrorPattern){let K=X.headers.get("Location")??"";z(Z.expectedCallbackErrorPattern.test(K),`Failure redirect does not surface an error code (expected ${Z.expectedCallbackErrorPattern}): ${K}`)}return{details:{status:X.status,state:Y}}})}async function n9(Z,$){return L("SSO Open-Redirect Denied (same-origin floor)",async()=>{let Q;try{Q=await d9(Z,$,{redirectParam:"https://evil.example/pwned"})}catch(K){return{details:{skipped:!0,reason:K instanceof Error?K.message:String(K)}}}let{callback:X}=Q;z(X.status===302,`Successful callback should redirect, got ${X.status}`);let Y=X.headers.get("Location")??"";if(z(!Y.includes("evil.example"),`Open redirect leaked to external origin: ${Y}`),!Y.startsWith("/")){let K=new URL(`${X8(Z)}/`).origin;z(new URL(Y).origin===K,`Post-login redirect must stay same-origin: ${Y}`)}return{details:{location:Y}}})}async function o9(Z,$){return L("SSO Opaque Session Cookie Only",async()=>{let{cookies:Q,success:X,error:Y}=await r9(Z,$);z(X,Y||"Login flow failed");let K=Array.from(Q.keys());z(K.some((j)=>j.includes(".session")),"Opaque login must set a `.session` cookie");let H=K.find(b9);z(!H,`Opaque-only login must not set token cookies, found: ${H}`);let J=Z.userPath;if(!J)throw Error("User path not configured");let G=await $(J,{headers:{Cookie:d0(Q)}});z(G.status===200,`User endpoint returned ${G.status} after login, expected 200`);let W=Array.from(M0(G.headers).keys()).find(b9);return z(!W,`Session resolution must not set token cookies, found: ${W}`),{details:{sessionCookieOnly:!0}}})}async function a9(Z,$){return L("SSO Legacy Token Cookies Rejected",async()=>{let Q=Z.loginPath??"/api/auth/login",X=Z.userPath??"/api/auth/user",Y=await $(Q);z(Y.status===302,`Login should redirect, got ${Y.status}`);let K=a6(M0(Y.headers));if(!K)return{details:{skipped:!0,reason:"Could not derive cookie prefix from login response"}};let H=new Map([[`${K}.access`,"legacy-access"],[`${K}.id`,"legacy-id"],[`${K}.refresh`,"legacy-refresh"],[`${K}.control`,btoa(JSON.stringify({token_type:"Bearer",expires_in:3600}))]]),J=await $(X,{headers:{Cookie:d0(H)}});return z(J.status===401,`Legacy token cookies must not authenticate (expected 401), got ${J.status}`),{details:{status:J.status,prefix:K}}})}async function r9(Z,$){let Q=Z.loginPath;if(!Q)return{cookies:new Map,success:!1,error:"Login path not configured"};try{let X=await $(Q);if(X.status!==302)return{cookies:new Map,success:!1,error:`Login did not redirect: ${X.status}`};let Y=X.headers.get("Location");if(!Y)return{cookies:new Map,success:!1,error:"No redirect location from login"};let K=M0(X.headers),H=await globalThis.fetch(Y,{redirect:"manual"});if(H.status!==302)return{cookies:new Map,success:!1,error:`Auth did not redirect: ${H.status}`};let J=H.headers.get("Location");if(!J)return{cookies:new Map,success:!1,error:"No redirect location from auth"};let G=new URL(J).pathname+new URL(J).search,W=await $(G,{headers:{Cookie:d0(K)}});if(W.status!==302)return{cookies:new Map,success:!1,error:`Callback did not redirect: ${W.status}`};return{cookies:M0(W.headers),success:!0}}catch(X){return{cookies:new Map,success:!1,error:X instanceof Error?X.message:String(X)}}}async function t9(Z,$){return L("SSO JIT User Provisioning Flow",async()=>{let{cookies:Q,success:X,error:Y}=await r9(Z,$);z(X,Y||"Login flow failed");let K=Array.from(Q.keys()).some((W)=>W.includes(".session"));z(K,"No opaque session cookie set after login");let H=Z.userPath;if(!H)throw Error("User path not configured");let J=await $(H,{headers:{Cookie:d0(Q)}});z(J.status===200,`User endpoint returned ${J.status} after login, expected 200`);let G=await J.json();return z(G.id!==void 0,"User data missing id field"),t6(Z,G),{details:{userId:G.id,userName:G.userName,name:G.name,email:G.email,avatar:G.avatar,jitFlowCompleted:!0}}})}function t6(Z,$){let Q=Z.expectedUser;if(!Q)return;if(Q.id!==void 0)z($.id===Q.id,`Expected user id ${Q.id}, got ${String($.id)}`);if(Q.userName!==void 0)z($.userName===Q.userName,`Expected username ${Q.userName}, got ${String($.userName)}`);if(Q.name!==void 0)z($.name===Q.name,`Expected name ${Q.name}, got ${String($.name)}`);if(Q.email!==void 0)z($.email===Q.email,`Expected email ${Q.email}, got ${String($.email)}`);if(Q.avatar!==void 0)z($.avatar===Q.avatar,`Expected avatar ${Q.avatar}, got ${String($.avatar)}`);if(Q.requireSso)z($.sso!==void 0,"Expected SSO auth envelope to be preserved on user response")}async function s6(Z){let $=performance.now(),Q={...h9,...Z},X=c(Q),Y=[];Y.push(await m9(Q,X)),Y.push(await k9(Q,X)),Y.push(await f9(Q,X)),Y.push(await g9(Q,X)),Y.push(await p9(Q,X)),Y.push(await u9(Q,X)),Y.push(await l9(Q,X)),Y.push(await c9(Q,X)),Y.push(await i9(Q,X)),Y.push(await n9(Q,X)),Y.push(await a9(Q,X)),Y.push(await t9(Q,X)),Y.push(await o9(Q,X));let K=performance.now()-$,H=Y.filter((W)=>W.passed).length,J=Y.filter((W)=>!W.passed).length,G=Y.filter((W)=>W.details?.skipped).length;return{suite:"SSO",passed:J===0,tests:Y,duration:K,summary:{total:Y.length,passed:H-G,failed:J,skipped:G}}}function n8(Z){let $={...h9,...Z},Q=c($);return{tests:[{name:"login endpoint redirects to authorization URL",fn:async()=>{let K=await m9($,Q);if(!K.passed)throw Error(K.error)}},{name:"user endpoint returns 401 when unauthenticated",fn:async()=>{let K=await k9($,Q);if(!K.passed)throw Error(K.error)}},{name:"callback endpoint redirects on invalid requests",fn:async()=>{let K=await p9($,Q);if(!K.passed)throw Error(K.error)}},{name:"token endpoint returns 401 when unauthenticated",fn:async()=>{let K=await u9($,Q);if(!K.passed)throw Error(K.error)}},{name:"refresh endpoint returns 401 when unauthenticated",fn:async()=>{let K=await l9($,Q);if(!K.passed)throw Error(K.error)}},{name:"state cookie is isolated per login attempt (multi-tab)",fn:async()=>{let K=await c9($,Q);if(!K.passed)throw Error(K.error)}},{name:"state cookie is cleared on callback error",fn:async()=>{let K=await i9($,Q);if(!K.passed)throw Error(K.error)}},{name:"cross-origin post-login redirect is floored to same-origin",fn:async()=>{let K=await n9($,Q);if(!K.passed)throw Error(K.error)}},{name:"legacy token cookies do not authenticate (opaque-only)",fn:async()=>{let K=await a9($,Q);if(!K.passed)throw Error(K.error)}}],ext:{createJITTests:()=>[{name:"user provisioning flow completes successfully",fn:async()=>{let K=await t9($,Q);if(!K.passed)throw Error(K.error)}},{name:"login sets only an opaque session cookie (no token cookies)",fn:async()=>{let K=await o9($,Q);if(!K.passed)throw Error(K.error)}}],createLogoutTests:()=>[{name:"logout endpoint clears cookies",fn:async()=>{let K=await f9($,Q);if(!K.passed)throw Error(K.error)}}],createBackChannelLogoutTests:()=>[{name:"endpoint exists",fn:async()=>{let K=await g9($,Q);if(!K.passed)throw Error(K.error)}}]}}}import*as L8 from"node:crypto";import{clearJwksCache as G$,setJwksClock as T4,setJwksRetry as s9,verifyIdToken as t,verifyLogoutToken as H$}from"@ionite/core/server";import{createValidators as e6}from"@ionite/valibot";var h="https://idp.ecv.local/realms/ecv",v="ecv-oidc-client",$3="http://schemas.openid.net/event/backchannel-logout";function d(Z){let{publicKey:$,privateKey:Q}=L8.generateKeyPairSync("rsa",{modulusLength:2048}),X=$.export({format:"jwk"});return{privateKey:Q,kid:Z,jwk:{kty:X.kty,n:X.n,e:X.e,kid:Z,use:"sig",alg:"RS256"}}}function R4(Z){return Buffer.from(Z).toString("base64url")}function g(Z){let $={alg:Z.headerAlg??"RS256",typ:"JWT"};if(Z.headerKid!==null)$.kid=Z.headerKid??Z.key.kid;let Q=`${R4(JSON.stringify($))}.${R4(JSON.stringify(Z.claims))}`,X=L8.createSign("RSA-SHA256");return X.update(Q),`${Q}.${R4(X.sign(Z.key.privateKey))}`}function n(Z={}){let $=Math.floor(Date.now()/1000);return{iss:h,aud:v,sub:"ecv-user",iat:$,exp:$+3600,...Z}}async function Q0(Z,$,Q){let X=globalThis.fetch;G$(Z),globalThis.fetch=async(Y,K)=>{if((typeof Y==="string"?Y:Y.toString())===Z)return new Response(JSON.stringify($),{status:200,headers:{"Content-Type":"application/json"}});return X(Y,K)};try{return await Q()}finally{globalThis.fetch=X,G$(Z)}}async function F0(Z,$,Q){try{await Z()}catch(X){let Y=X instanceof Error?X.message:String(X);if(!Y.includes($))throw Error(`${Q}: expected error containing "${$}", got "${Y}"`);return}throw Error(`${Q}: expected rejection, but the call resolved`)}function o(){return`https://jwks.ecv.local/${L8.randomUUID()}`}function q4(Z={}){let $=Math.floor(Date.now()/1000);return{iss:h,aud:v,iat:$,jti:L8.randomUUID(),sid:"ecv-session-1",events:{[$3]:{}},...Z}}async function z$(Z,$,Q){let X=globalThis.fetch;G$(Z),s9({maxRetries:0}),globalThis.fetch=async(Y,K)=>{if((typeof Y==="string"?Y:Y.toString())===Z){let J=$();if(J==="fail")return new Response("unavailable",{status:503});return new Response(JSON.stringify(J),{status:200,headers:{"Content-Type":"application/json"}})}return X(Y,K)};try{return await Q()}finally{globalThis.fetch=X,G$(Z),T4(),s9()}}function o8(){return{tests:[{name:"valid ID token (correct iss/aud/exp/kid) resolves claims",fn:async()=>{let $=d("ecv-valid-kid"),Q=o(),X=g({key:$,claims:n()}),Y=await Q0(Q,{keys:[$.jwk]},()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}));if(Y.sub!=="ecv-user")throw Error(`expected sub "ecv-user", got "${String(Y.sub)}"`)}},{name:"wrong aud and wrong azp throws audience mismatch",fn:async()=>{let $=d("ecv-aud-kid"),Q=o(),X=g({key:$,claims:n({aud:"someone-else",azp:"someone-else"})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"audience mismatch","wrong aud/azp"))}},{name:"matching azp only (client absent from aud) passes",fn:async()=>{let $=d("ecv-azp-kid"),Q=o(),X=g({key:$,claims:n({aud:"api://resource-server",azp:v})}),Y=await Q0(Q,{keys:[$.jwk]},()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}));if(Y.azp!==v)throw Error(`expected azp "${v}", got "${String(Y.azp)}"`)}},{name:"wrong iss throws issuer mismatch",fn:async()=>{let $=d("ecv-iss-kid"),Q=o(),X=g({key:$,claims:n({iss:"https://attacker.example/realms/evil"})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"issuer mismatch","wrong iss"))}},{name:"expired exp throws (beyond clock skew)",fn:async()=>{let $=d("ecv-exp-kid"),Q=o(),X=Math.floor(Date.now()/1000)-3600,Y=g({key:$,claims:n({exp:X})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:Y,jwksUri:Q,issuer:h,audience:v}),"expired","expired token"))}},{name:"missing kid in header throws",fn:async()=>{let $=d("ecv-nokid-kid"),Q=o(),X=g({key:$,headerKid:null,claims:n()});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"missing kid","missing kid"))}},{name:"unknown header kid fails closed after JWKS refresh (no first-key fallback)",fn:async()=>{let $=d("ecv-realkid"),Q=o(),X=g({key:$,headerKid:"kid-not-in-jwks",claims:n()});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"Public key not found","unknown kid"))}},{name:"JWKS rotation: a token signed with a newly published kid verifies after refresh (§1.10)",fn:async()=>{let $=d("kid-1"),Q=d("kid-2"),X=o(),Y=[$.jwk];await z$(X,()=>({keys:Y}),async()=>{if(await t({idToken:g({key:$,claims:n()}),jwksUri:X,issuer:h,audience:v}),Y=[$.jwk,Q.jwk],(await t({idToken:g({key:Q,claims:n()}),jwksUri:X,issuer:h,audience:v})).sub!=="ecv-user")throw Error("rotated-key token did not verify")})}},{name:"JWKS 503 on a cold cache fails closed (§1.29)",fn:async()=>{let $=d("kid-cold"),Q=o(),X=g({key:$,claims:n()});await z$(Q,()=>"fail",()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"Failed to fetch JWKS","cold JWKS 503"))}},{name:"JWKS TTL expiry triggers a refresh that picks up rotation (§1.10)",fn:async()=>{let $=d("kid-ttl-1"),Q=d("kid-ttl-2"),X=o(),Y=Date.UTC(2026,0,1);T4(()=>Y);let K=[$.jwk];await z$(X,()=>({keys:K}),async()=>{if(await t({idToken:g({key:$,claims:n()}),jwksUri:X,issuer:h,audience:v}),K=[Q.jwk],Y+=660000,(await t({idToken:g({key:Q,claims:n()}),jwksUri:X,issuer:h,audience:v})).sub!=="ecv-user")throw Error("post-TTL rotated token did not verify")})}},{name:"JWKS warm refresh failure serves last-known-good keys (§1.10)",fn:async()=>{let $=d("kid-warm"),Q=o(),X=Date.UTC(2026,0,1);T4(()=>X);let Y="ok";await z$(Q,()=>Y==="fail"?"fail":{keys:[$.jwk]},async()=>{if(await t({idToken:g({key:$,claims:n()}),jwksUri:Q,issuer:h,audience:v}),X+=660000,Y="fail",(await t({idToken:g({key:$,claims:n()}),jwksUri:Q,issuer:h,audience:v})).sub!=="ecv-user")throw Error("last-known-good keys were not served on refresh failure")})}},{name:"logout_token with events + sid (no nonce) verifies (§1.6)",fn:async()=>{let $=d("kid-logout-ok"),Q=o(),X=g({key:$,claims:q4()}),Y=await Q0(Q,{keys:[$.jwk]},()=>H$({logoutToken:X,jwksUri:Q,issuer:h,audience:v}));if(Y.sid!=="ecv-session-1")throw Error(`expected sid, got "${String(Y.sid)}"`)}},{name:"logout_token containing a nonce is rejected (§1.6)",fn:async()=>{let $=d("kid-logout-nonce"),Q=o(),X=g({key:$,claims:q4({nonce:"must-not-be-here"})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>H$({logoutToken:X,jwksUri:Q,issuer:h,audience:v}),"must not contain a nonce","logout_token nonce"))}},{name:"plain ID token (no events) rejected as a logout_token (§1.6)",fn:async()=>{let $=d("kid-logout-idtoken"),Q=o(),X=g({key:$,claims:n({sid:"ecv-session-1"})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>H$({logoutToken:X,jwksUri:Q,issuer:h,audience:v}),"missing back-channel logout event","id token as logout_token"))}},{name:"logout_token missing both sid and sub is rejected (§1.6)",fn:async()=>{let $=d("kid-logout-nosub"),Q=o(),X=g({key:$,claims:q4({sid:void 0,sub:void 0})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>H$({logoutToken:X,jwksUri:Q,issuer:h,audience:v}),"missing sid and sub","logout_token no sid/sub"))}},{name:"nonce: matching expectedNonce at the callback exchange passes (§1.8)",fn:async()=>{let $=d("ecv-nonce-ok"),Q=o(),X=g({key:$,claims:n({nonce:"ecv-nonce-123"})});if((await Q0(Q,{keys:[$.jwk]},()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v,expectedNonce:"ecv-nonce-123"}))).nonce!=="ecv-nonce-123")throw Error("expected the nonce claim to round-trip")}},{name:"nonce: a different nonce claim is rejected when expectedNonce is set (§1.8)",fn:async()=>{let $=d("ecv-nonce-bad"),Q=o(),X=g({key:$,claims:n({nonce:"attacker-nonce"})});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v,expectedNonce:"ecv-nonce-123"}),"nonce mismatch","nonce mismatch"))}},{name:"nonce: a MISSING nonce claim is rejected when expectedNonce is set (§1.8)",fn:async()=>{let $=d("ecv-nonce-missing"),Q=o(),X=g({key:$,claims:n()});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v,expectedNonce:"ecv-nonce-123"}),"nonce mismatch","missing nonce claim"))}},{name:"nonce: refresh-path verification (no expectedNonce) accepts a token without a nonce (§1.8)",fn:async()=>{let $=d("ecv-nonce-refresh"),Q=o(),X=g({key:$,claims:n()});if((await Q0(Q,{keys:[$.jwk]},()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}))).sub!=="ecv-user")throw Error("nonce-less refresh verification failed")}},{name:'alg pinning: header alg "none" is rejected before key resolution (§1.9)',fn:async()=>{let $=d("ecv-alg-none"),Q=o(),X=g({key:$,headerAlg:"none",claims:n()});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"Unsupported JWT alg","alg none"))}},{name:"alg pinning: HMAC header alg (HS256) is rejected (§1.9)",fn:async()=>{let $=d("ecv-alg-hs"),Q=o(),X=g({key:$,headerAlg:"HS256",claims:n()});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"Unsupported JWT alg","alg HS256"))}},{name:"alg pinning: an RSA JWK cannot verify a token claiming ES256 (§1.9)",fn:async()=>{let $=d("ecv-alg-cross"),Q=o(),X=g({key:$,headerAlg:"ES256",claims:n()});await Q0(Q,{keys:[$.jwk]},()=>F0(()=>t({idToken:X,jwksUri:Q,issuer:h,audience:v}),"does not match token alg","alg/JWK-type cross"))}},{name:"token response validator rejects a body missing access_token (§1.11 refresh fails closed)",fn:async()=>{let $=e6();if(!(await $.sso.tokenResponse.validate({token_type:"Bearer"})).issues)throw Error("expected validation issues for a token response missing access_token");if((await $.sso.tokenResponse.validate({access_token:"a.b.c",id_token:"d.e.f",token_type:"Bearer"})).issues)throw Error("expected a minimal valid token response to pass validation")}}]}}async function Q3(){let Z=performance.now(),{tests:$}=o8(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"SSO ID token verification",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}function J$(Z,$){return z(typeof Z==="string"&&Z.length>0,`${$} must be configured`),Z}function a8(Z,$){return z(typeof Z==="object"&&Z!==null&&!Array.isArray(Z),`${$} must be an object`),Z}function e9(Z,$){return z(typeof Z==="boolean",`${$} must be a boolean`),Z}function Z3(Z,$){return z(Array.isArray(Z),`${$} must be an array`),Z}function X3(){return{"~standard":{validate:(Z)=>{if(typeof Z!=="object"||Z===null)return{issues:[{message:`Expected object, got ${typeof Z}`}]};let $=Z,Q=[];if(typeof $.kty!=="string")Q.push({message:"Expected kty to be string",path:["kty"]});if(typeof $.kid!=="string")Q.push({message:"Expected kid to be string",path:["kid"]});if(Q.length>0)return{issues:Q};return{value:Z}}}}}function $Z(){return{"~standard":{validate:(Z)=>{if(typeof Z!=="object"||Z===null)return{issues:[{message:`Expected object, got ${typeof Z}`}]};let $=Z,Q=[];if(typeof $.valid!=="boolean")Q.push({message:"Expected valid to be boolean",path:["valid"]});if(Q.length>0)return{issues:Q};return{value:Z}}}}}var QZ={validatePath:"/api/workload/validate",jwksPath:"/api/workload/jwks",ecvUrl:C0};async function U4(Z){if(Z.validToken)return Z.validToken;return b0(Z.ecvUrl??C0)}async function ZZ(Z,$){return L("Workload JWKS Endpoint",async()=>{let Q=await $(J$(Z.jwksPath,"jwksPath"));z(Q.status===200,`Expected 200 OK, got ${Q.status}`);let X=a8(await Q.json(),"JWKS response"),Y=Z3(X.keys,"JWKS keys"),K=X3();for(let H of Y)l0(H,K);return{details:{keyCount:Y.length,algorithms:Y.map((H)=>a8(H,"JWKS key").alg).filter((H)=>typeof H==="string")}}})}async function XZ(Z,$,Q){return L("Workload Validate Endpoint",async()=>{let X=await $(J$(Z.validatePath,"validatePath"),{method:"POST",headers:{Authorization:`Bearer ${Q}`}});z(X.status===200,`Expected 200 OK for valid token, got ${X.status}`);let Y=a8(await X.json(),"Token validation response"),K=$Z();l0(Y,K);let H=e9(Y.valid,"Validation result valid");z(H===!0,"Token should be valid");let J=new Set(["valid","expiresAt"]),G=Object.keys(Y).filter((W)=>!J.has(W));return z(G.length===0,`Validate response leaked fields: ${G.join(", ")}`),{details:{valid:H,expiresAt:Y.expiresAt}}})}async function YZ(Z,$){return L("Workload Validate Endpoint (Invalid Token)",async()=>{let Q=await $(J$(Z.validatePath,"validatePath"),{method:"POST",headers:{Authorization:"Bearer invalid.token.here"}});z(Q.status===401,`Expected 401 Unauthorized for invalid token, got ${Q.status}`);let X=a8(await Q.json(),"Invalid token validation response"),Y=$Z();l0(X,Y);let K=e9(X.valid,"Validation result valid");z(K===!1,"Invalid token should not validate"),z(X.error==="invalid_token",`Expected generic 'invalid_token' error, got ${String(X.error)}`);let H=new Set(["valid","error"]),J=Object.keys(X).filter((G)=>!H.has(G));return z(J.length===0,`Invalid-token response leaked fields: ${J.join(", ")}`),{details:{valid:K,error:X.error}}})}async function KZ(Z,$){return L("Workload Validate Endpoint (No Auth)",async()=>{let Q=await $(J$(Z.validatePath,"validatePath"),{method:"POST"});return z(Q.status===401,`Expected 401 Unauthorized for missing auth, got ${Q.status}`),{details:{status:Q.status,requiresAuth:!0}}})}async function HZ(Z,$){return L("Workload Authentication (Whoami)",async()=>{let Q=`${Z.ecvUrl}/api/whoami`,X=await globalThis.fetch(Q,{headers:{Authorization:`Bearer ${$}`}});if(X.status===404)return{details:{skipped:!0,reason:"ECV whoami endpoint not available"}};z(X.status===200,`Expected 200 OK, got ${X.status}`);let Y=a8(await X.json(),"Whoami response"),K=a8(Y.workload,"Whoami workload");return{details:{workloadId:K.workloadId,clientId:K.clientId}}})}async function Y3(Z){let $=performance.now(),Q={...QZ,...Z},X=c(Q),Y=[];Y.push(await ZZ(Q,X)),Y.push(await KZ(Q,X)),Y.push(await YZ(Q,X));let K=await U4(Q);Y.push(await XZ(Q,X,K)),Y.push(await HZ(Q,K));let H=performance.now()-$,J=Y.filter((j)=>j.passed).length,G=Y.filter((j)=>!j.passed).length,W=Y.filter((j)=>j.details?.skipped).length;return{suite:"Workload",passed:G===0,tests:Y,duration:H,summary:{total:Y.length,passed:J-W,failed:G,skipped:W}}}function r8(Z){let $={...QZ,...Z},Q=c($);return[{name:"JWKS endpoint returns valid keys",fn:async()=>{let X=await ZZ($,Q);if(!X.passed)throw Error(X.error)}},{name:"validate endpoint rejects missing auth",fn:async()=>{let X=await KZ($,Q);if(!X.passed)throw Error(X.error)}},{name:"validate endpoint rejects invalid tokens",fn:async()=>{let X=await YZ($,Q);if(!X.passed)throw Error(X.error)}},{name:"validate endpoint accepts valid token",fn:async()=>{let X=await U4($),Y=await XZ($,Q,X);if(!Y.passed)throw Error(Y.error)}},{name:"whoami endpoint works with workload authentication",fn:async()=>{let X=await U4($),Y=await HZ($,X);if(!Y.passed&&!Y.details?.skipped)throw Error(Y.error)}}]}var K3="/api/ionite";function H3(Z){return Z.capabilitiezPath??`${Z.basePath??K3}/capabilitiez`}function R1(Z){try{return(typeof Z.ecv==="function"?Z.ecv():Z.ecv)?.baseUrl??Z.ecvUrl??C0}catch{return Z.ecvUrl??C0}}function _0(Z,$){z(typeof Z==="object"&&Z!==null,`capabilities.${$} must be an object`);let Q=Z;if(z(typeof Q.module==="string",`capabilities.${$}.module must be a string`),z(typeof Q.active==="boolean",`capabilities.${$}.active must be a boolean`),z(typeof Q.ready==="boolean",`capabilities.${$}.ready must be a boolean`),z(typeof Q.testable==="boolean",`capabilities.${$}.testable must be a boolean`),Q.urls!==void 0)z(Array.isArray(Q.urls)&&Q.urls.every((X)=>typeof X==="string"),`capabilities.${$}.urls must be a string array when provided`);if(Q.reason!==void 0)z(typeof Q.reason==="string",`capabilities.${$}.reason must be a string when provided`);return Q}async function q1(Z){let $=c(Z),Q=await b0(R1(Z),M1),X=await $(H3(Z),{headers:{Authorization:`Bearer ${Q}`}});z(X.status===200,`Expected 200 from capabilitiez endpoint, got ${X.status}. Ensure RemoteConfig includes capabilitiez: true and workload auth is configured.`);let Y=await X.json();return z(typeof Y.basePath==="string","capabilitiez manifest must include basePath:string"),z(typeof Y.urls==="object"&&Y.urls!==null,"capabilitiez manifest must include urls"),z(typeof Y.capabilities==="object"&&Y.capabilities!==null,"capabilitiez manifest must include capabilities"),_0(Y.capabilities["sso.login"],"sso.login"),_0(Y.capabilities["sso.jit"],"sso.jit"),_0(Y.capabilities["sso.logout"],"sso.logout"),_0(Y.capabilities["sso.back_channel_logout"],"sso.back_channel_logout"),_0(Y.capabilities["ciam.magic_link"],"ciam.magic_link"),_0(Y.capabilities["ciam.oidc_login"],"ciam.oidc_login"),_0(Y.capabilities["ciam.customer"],"ciam.customer"),_0(Y.capabilities["ciam.logout"],"ciam.logout"),_0(Y.capabilities["iam.users"],"iam.users"),_0(Y.capabilities["iam.groups"],"iam.groups"),_0(Y.capabilities["workload.incoming"],"workload.incoming"),_0(Y.capabilities["workload.outgoing"],"workload.outgoing"),_0(Y.capabilities["authz.oaa"],"authz.oaa"),Y}function z3(Z,$){return Z.capabilities[$]?.testable===!0}function P0(Z){return Z.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function m0(Z){let $,Q=!1;return(X)=>{if(!Q)$=Z(X),Q=!0;return $}}function H8(Z,$){if(!$)return;let Q=Array.isArray($)?$:[$];if(Q.some((Y)=>z3(Z,Y)))return;return`not testable: ${Q.map((Y)=>{return Z.capabilities[Y]?.reason??`${Y} is not testable`}).join("; ")}`}function i0(Z,$,Q,X,Y){X.forEach((K,H)=>{Z.push({id:`${P0($)}.${P0(K.name)}`,suite:$,name:K.name,capability:Q,run:async(J)=>{z(J!==void 0,`${$} entry requires a capabilitiez manifest`);let G=Y(J)[H];z(G!==void 0,`${$} test "${K.name}" could not be resolved`),await G.fn()}})})}function W$(Z,$={}){let Q=[];if(Q.push({id:"discovery.capabilitiez-manifest",suite:"Discovery",name:"capabilitiez manifest is reachable and well-formed",alwaysRun:!0,run:async()=>{await q1(Z)}}),$.configRetry)for(let V of V4())Q.push({id:`config-retry.${P0(V.name)}`,suite:"Config retry policy",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of D4())Q.push({id:`config-lifecycle.${P0(V.name)}`,suite:"Config lifecycle",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of F4())Q.push({id:`config-boundary.${P0(V.name)}`,suite:"Config boundary",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of P1())Q.push({id:`harness.${P0(V.name)}`,suite:"Harness",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of y8().tests)Q.push({id:`authz-closure.${P0(V.name)}`,suite:"Authz closure",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of f8().tests)Q.push({id:`lfv-callbacks.${P0(V.name)}`,suite:"LFV callbacks",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of k8().tests)Q.push({id:`iam-delta.${P0(V.name)}`,suite:"IAM delta",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of g8().tests)Q.push({id:`otel.${P0(V.name)}`,suite:"OpenTelemetry",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of i8().tests)Q.push({id:`protocol-surface.${P0(V.name)}`,suite:"Protocol surface",name:V.name,alwaysRun:!0,run:()=>V.fn()});for(let V of o8().tests)Q.push({id:`sso-id-token-verification.${P0(V.name)}`,suite:"SSO ID token verification",name:V.name,alwaysRun:!0,run:()=>V.fn()});let X=n8({...Z}),Y=m0((V)=>n8({...Z,loginPath:V.urls.sso.loginUrl,callbackPath:V.urls.sso.callbackUrl,userPath:V.urls.sso.userUrl,logoutPath:V.urls.sso.logoutUrl,backChannelLogoutPath:V.urls.sso.logoutBackChannelUrl,tokenPath:V.urls.sso.loginUrl.replace(/\/login$/,"/token"),refreshPath:V.urls.sso.refreshUrl})),K=m0((V)=>Y(V).ext.createJITTests()),H=m0((V)=>Y(V).ext.createLogoutTests()),J=m0((V)=>Y(V).ext.createBackChannelLogoutTests());i0(Q,"SSO","sso.login",X.tests,(V)=>Y(V).tests),i0(Q,"SSO","sso.jit",X.ext.createJITTests(),K),i0(Q,"SSO","sso.logout",X.ext.createLogoutTests(),H),i0(Q,"SSO","sso.back_channel_logout",X.ext.createBackChannelLogoutTests(),J);let G=b8({...Z}),W=m0((V)=>b8({...Z,customerPath:V.urls.ciam.customerUrl,magicLinkPath:V.urls.ciam.magicLinkUrl,magicLinkLoginPath:V.urls.ciam.magicLinkLoginUrl,logoutPath:V.urls.ciam.logoutUrl??"/api/ionite/auth/customer/logout",customerProvidersPath:V.urls.ciam.customerProvidersUrl,customerLoginPath:V.urls.ciam.customerLoginUrl}));i0(Q,"CIAM",["ciam.magic_link","ciam.oidc_login"],G.tests,(V)=>W(V).tests);let j=r8({...Z,ecvUrl:R1(Z)}),N=m0((V)=>r8({...Z,ecvUrl:R1(Z),validatePath:V.urls.workload.validateUrl,jwksPath:V.urls.workload.jwksUrl}));i0(Q,"Workload",["workload.incoming","workload.outgoing"],j,N);let D=m8({...Z,ecvUrl:R1(Z),ion:Z.ion}),F=m0((V)=>m8({...Z,ecvUrl:R1(Z),scimPath:V.urls.iam.basePath,groupsInboundPath:V.urls.iam.groupsUrl,ion:Z.ion})),M=m0((V)=>F(V).ext.createGroupsOutboundTests()),O=m0((V)=>F(V).ext.createGroupsInboundTests());i0(Q,"IAM","iam.users",D.tests,(V)=>F(V).tests),i0(Q,"IAM","iam.groups",D.ext.createGroupsOutboundTests(),M),i0(Q,"IAM","iam.groups",D.ext.createGroupsInboundTests(),O);let P={ecv:Z.ecv,timeout:Z.timeout},R=M8(P),x=m0(()=>M8(P));return R.forEach((V,S)=>{Q.push({id:`authz-oaa.${P0(V.name)}`,suite:"Authz/OAA",name:V.name,capability:"authz.oaa",run:async(u)=>{await x(u)[S].fn()}})}),Q}function G3(Z){let $=W$(Z),Q,X=async()=>{return Q??=await q1(Z),Q};return $.map((Y)=>({name:`${Y.suite}: ${Y.name}`,fn:async()=>{if(Y.alwaysRun){await Y.run(void 0);return}let K=await X(),H=H8(K,Y.capability);if(H){console.warn(`[ECV] SKIP ${Y.suite}: ${Y.name} — ${H}`);return}await Y.run(K)}}))}import*as s8 from"node:crypto";import{defaultLogger as WZ}from"@ionite/core";import{CACHE_BOUNDS as G8,clearJwksCache as N$,resolveCacheConfig as w4,sso as jZ,verifyIdToken as zZ}from"@ionite/core/server";import{createSessionReadCache as GZ,InMemorySessionStore as t8}from"@ionite/server";import{createValidators as NZ}from"@ionite/valibot";var B$="https://idp.ecv.local/realms/ecv",D$="ecv-oidc-client",S4="ion.sso";function A4(Z){let{publicKey:$,privateKey:Q}=s8.generateKeyPairSync("rsa",{modulusLength:2048}),X=$.export({format:"jwk"});return{privateKey:Q,kid:Z,jwk:{kty:X.kty,n:X.n,e:X.e,kid:Z,use:"sig",alg:"RS256"}}}function I4(Z){return Buffer.from(Z).toString("base64url")}function C4(Z,$){let Q={alg:"RS256",typ:"JWT",kid:Z.kid},X=`${I4(JSON.stringify(Q))}.${I4(JSON.stringify($))}`,Y=s8.createSign("RSA-SHA256");return Y.update(X),`${X}.${I4(Y.sign(Z.privateKey))}`}function w1(Z={}){let $=Math.floor(Date.now()/1000);return{iss:B$,aud:D$,sub:"ecv-user",iat:$,exp:$+3600,...Z}}function JZ(){return`https://jwks.ecv.local/${s8.randomUUID()}`}function T1(Z){let $={get:0,update:0,delete:0};return{store:{async create(X){await Z.create(X)},async get(X){return $.get++,Z.get(X)},async update(X,Y){$.update++,await Z.update(X,Y)},async delete(X){$.delete++,await Z.delete(X)}},counts:$}}function U1(Z){let $=new Date(Date.now()+3600000);return{sid:Z,sub:"ecv-user",authType:"sso",tokens:{access_token:"opaque-access",id_token:"dummy.not.verified",token_type:"Bearer",scope:"openid",expires:new Date(Date.now()+3600000).toISOString()},claims:w1({preferred_username:"ecv-user",name:"ECV User",email:"ecv@example.com"}),expiresAt:$,createdAt:new Date}}function j$(Z){return new Request("https://app.ecv.local/",{headers:{cookie:`${S4}.session=${Z}`}})}function E4(Z){let $=NZ(),Q=jZ($.sso,WZ,void 0,{sessionStore:Z,cookiesPrefix:S4},{sessionCache:{read:{enabled:!1,ttlMs:30000,maxEntries:1024}}});if(!Q)throw Error("sso() returned undefined");return Q}function J3(Z,$,Q){let X=NZ(),Y=jZ(X.sso,WZ,void 0,{authority:B$,tokenUrl:$,authorizationUrl:"https://idp.ecv.local/authorize",clientId:D$,redirectUri:"https://app.ecv.local/callback",scope:"openid",jwksUri:Q,sessionStore:Z,cookiesPrefix:S4,cookiesSecure:!1},{sessionCache:{read:{enabled:!1,ttlMs:30000,maxEntries:1024}}});if(!Y)throw Error("sso() returned undefined");return Y}function W3(Z){return{sid:Z,sub:"ecv-user",authType:"sso",tokens:{access_token:"old-access",id_token:"dummy.not.verified",token_type:"Bearer",scope:"openid",refresh_token:"old-refresh",expires:new Date(Date.now()-1000).toISOString()},claims:w1({preferred_username:"ecv-user",name:"ECV User",email:"ecv@example.com"}),expiresAt:new Date(Date.now()+86400000),createdAt:new Date}}async function j3(Z,$,Q){let X=globalThis.fetch,Y=0;N$(Z),globalThis.fetch=async(K,H)=>{if((typeof K==="string"?K:K.toString())===Z)return Y++,new Response(JSON.stringify({keys:$()}),{status:200,headers:{"Content-Type":"application/json"}});return X(K,H)};try{return await Q(()=>Y)}finally{globalThis.fetch=X,N$(Z)}}function p(Z,$){if(!Z)throw Error($)}function k0(Z,$,Q){if(Z!==$)throw Error(`${Q} (expected ${$}, got ${Z})`)}var N3=(Z)=>new Promise(($)=>setTimeout($,Z));function F$(){return{tests:[{name:"SSO read path builds user from stored claims with NO JWKS fetch",fn:async()=>{let{store:$,counts:Q}=T1(new t8);await $.create(U1("sid-read"));let X=E4($),Y=globalThis.fetch,K=0;globalThis.fetch=async(H,J)=>{return K++,Y(H,J)};try{let H=await X.resolveUser(j$("sid-read"));p(H.status==="authenticated",`expected authenticated, got ${H.status}`),p(H.value.id==="ecv-user",`expected id ecv-user, got ${String(H.value.id)}`),p(K===0,`read path performed ${K} fetch(es); expected 0`),k0(Q.get,1,`expected 1 store.get, got ${Q.get}`)}finally{globalThis.fetch=Y}}},{name:"authenticated SSO read path performs ZERO session-store writes (§7.5)",fn:async()=>{let{store:$,counts:Q}=T1(new t8);await $.create(U1("sid-nowrite"));let X=E4($);for(let Y=0;Y<3;Y++){let K=await X.resolveUser(j$("sid-nowrite"));p(K.status==="authenticated",`expected authenticated, got ${K.status}`)}k0(Q.update,0,`read path must not write (got ${Q.update} update(s))`),k0(Q.delete,0,`read path must not delete a live session (got ${Q.delete})`)}},{name:"SSO refresh rotates the sid, strips the old refresh_token, and never 503s (§1.2)",fn:async()=>{let $=A4("kid-rotate"),Q=JZ(),X="https://idp.ecv.local/token",Y=new t8,K=[],H={async create(G){K.push(G.sid),await Y.create(G)},get:(G)=>Y.get(G),update:(G,W)=>Y.update(G,W),delete:(G)=>Y.delete(G)};await H.create(W3("sid-old"));let J=globalThis.fetch;N$(Q),globalThis.fetch=async(G,W)=>{let j=typeof G==="string"?G:G.toString();if(j===Q)return new Response(JSON.stringify({keys:[$.jwk]}),{status:200,headers:{"Content-Type":"application/json"}});if(j==="https://idp.ecv.local/token"){let N=Math.floor(Date.now()/1000),D=C4($,w1({preferred_username:"ecv-user",name:"ECV User",email:"ecv@example.com",iat:N}));return new Response(JSON.stringify({access_token:"new-access",id_token:D,token_type:"Bearer",refresh_token:"new-refresh",expires_in:3600,refresh_expires_in:86400,scope:"openid"}),{status:200,headers:{"Content-Type":"application/json"}})}return J(G,W)};try{let W=await J3(H,"https://idp.ecv.local/token",Q).resolveUser(j$("sid-old"));p(W.status==="authenticated",`an expired token with a valid refresh token must refresh, never 503 — got ${W.status}`);let j=K.find((M)=>M!=="sid-old");p(!!j,"refresh must rotate to a new sid (new record created)");let N=await H.get(j);p(N?.tokens.access_token==="new-access","the rotated record holds the refreshed access token"),p(N?.tokens.refresh_token==="new-refresh","the rotated record holds the new refresh token");let D=await H.get("sid-old");p(D!=null,"old record survives during the rotation grace window"),p(D?.tokens.refresh_token===void 0,"old record must have its refresh_token stripped during grace");let F=new Date(D?.expiresAt).getTime();p(F<=Date.now()+60000+2000,`old record window must shrink to the grace (<=~60s), got ${F-Date.now()}ms`)}finally{globalThis.fetch=J,N$(Q)}}},{name:"resolveUser is memoized per Request object (single store round-trip)",fn:async()=>{let{store:$,counts:Q}=T1(new t8);await $.create(U1("sid-memo"));let X=E4($),Y=j$("sid-memo");await Promise.all([X.resolveUser(Y),X.resolveUser(Y),X.resolveUser(Y)]),k0(Q.get,1,`same-request resolveUser should hit the store once, got ${Q.get}`)}},{name:"session read cache: repeat get is served locally; miss is not cached; update/delete invalidate",fn:async()=>{let{store:$,counts:Q}=T1(new t8);await $.create(U1("sid-cache"));let X=GZ($,{ttlMs:30000,maxEntries:1024});await X.get("sid-cache"),await X.get("sid-cache"),k0(Q.get,1,`second get within TTL should be local, underlying got ${Q.get}`),await X.get("missing"),await X.get("missing"),k0(Q.get,3,`misses must not be cached (expected 3 underlying gets, got ${Q.get})`),await X.update("sid-cache",{expiresAt:new Date(Date.now()+3600000)}),await X.get("sid-cache"),k0(Q.get,4,`get after update should re-read (expected 4, got ${Q.get})`),await X.delete("sid-cache");let Y=await X.get("sid-cache");p(Y===null,"session should be gone after delete"),k0(Q.get,5,`get after delete should re-read (expected 5, got ${Q.get})`)}},{name:"session read cache: entry expires after ttlMs and re-reads",fn:async()=>{let{store:$,counts:Q}=T1(new t8);await $.create(U1("sid-ttl"));let X=GZ($,{ttlMs:20,maxEntries:1024});await X.get("sid-ttl"),k0(Q.get,1,`first get reaches store, got ${Q.get}`),await N3(35),await X.get("sid-ttl"),k0(Q.get,2,`get after TTL expiry should re-read (expected 2, got ${Q.get})`)}},{name:"RemoteConfig.cache values are clamped, not honored, when out of range",fn:async()=>{let $=w4({jwks:{ttlSeconds:1,maxEntries:1},session:{read:{enabled:!0,ttlSeconds:0,maxEntries:1}}});p($.jwks.ttlMs===G8.jwksTtlSeconds.min*1000,`jwks.ttl should clamp to min, got ${$.jwks.ttlMs}`),p($.jwks.maxEntries===G8.jwksMaxEntries.min,"jwks.maxEntries should clamp to min"),p($.session.read.ttlMs===G8.sessionReadTtlSeconds.min*1000,"session.read.ttl should clamp to min"),p($.session.read.maxEntries===G8.sessionReadMaxEntries.min,"read.maxEntries clamp to min");let Q=w4({jwks:{ttlSeconds:1e7,maxEntries:1e6},session:{read:{enabled:!0,ttlSeconds:1e4,maxEntries:1e7}}});p(Q.jwks.ttlMs===G8.jwksTtlSeconds.max*1000,"jwks.ttl should clamp to max"),p(Q.jwks.maxEntries===G8.jwksMaxEntries.max,"jwks.maxEntries should clamp to max"),p(Q.session.read.ttlMs===G8.sessionReadTtlSeconds.max*1000,"session.read.ttl should clamp to max");let X=w4(void 0);p(X.jwks.ttlMs===G8.jwksTtlSeconds.default*1000,"jwks.ttl default"),p(X.session.read.enabled===!1,"read cache must default to disabled")}},{name:"JWKS/CryptoKey caching: repeated verifications = one fetch; verifies after key rotation",fn:async()=>{let $=A4("kid-perf-1"),Q=A4("kid-perf-2"),X=JZ(),Y=[$.jwk];await j3(X,()=>Y,async(K)=>{for(let J=0;J<3;J++){let G=await zZ({idToken:C4($,w1()),jwksUri:X,issuer:B$,audience:D$});p(G.sub==="ecv-user","repeated verify should resolve claims")}p(K()===1,`3 verifications should share one JWKS fetch, got ${K()}`),Y=[$.jwk,Q.jwk];let H=await zZ({idToken:C4(Q,w1()),jwksUri:X,issuer:B$,audience:D$});p(H.sub==="ecv-user","rotated-key token must verify after refresh"),p(K()===2,`rotation should trigger exactly one refresh, got ${K()}`)})}}]}}async function B3(){let Z=performance.now(),{tests:$}=F$(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"Performance tuning",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}import{isStoreConflictError as v4,silentLogger as a}from"@ionite/core";import{chunkedMget as D3,createEvalshaRunner as F3,createReactiveRedis as BZ,DEFAULT_LIST_LIMIT as V3,DEFAULT_MGET_CHUNK_SIZE as O3,encodeKeyId as M3,MAX_LIST_LIMIT as DZ,RedisAccessAssignmentStore as L3,RedisAccessIndex as n0,RedisIamDeltaIdMapStore as _3,RedisMagicLinkStore as FZ,RedisSessionStore as A1,RedisUserStore as V$,scriptSha1 as y4,supportsRunScript as I1}from"@ionite/redis-core";var m="ecv:redis:";function z0(){let Z=new Map,$=new Map,Q=new Map,X={get:0,set:0,del:0,sadd:0,srem:0,smembers:0,mget:0,runScript:0},Y={raw:Z,sets:$,ttls:Q,mgetCalls:0,commands:X,roundTrips:0,resetCounts(){Y.mgetCalls=0,Y.roundTrips=0;for(let K of Object.keys(X))X[K]=0},async get(K){return X.get+=1,Y.roundTrips+=1,Z.has(K)?Z.get(K):null},async set(K,H){X.set+=1,Y.roundTrips+=1,Z.set(K,H),Q.delete(K)},async setex(K,H,J){Y.roundTrips+=1,Z.set(K,J),Q.set(K,Date.now()+H*1000)},async del(K){X.del+=1,Y.roundTrips+=1,Z.delete(K),$.delete(K),Q.delete(K)},async ttl(K){if(!Z.has(K))return-2;let H=Q.get(K);if(H==null)return-1;return Math.max(0,Math.ceil((H-Date.now())/1000))},async getdel(K){Y.roundTrips+=1;let H=Z.has(K)?Z.get(K):null;return Z.delete(K),Q.delete(K),H},async sadd(K,...H){X.sadd+=1,Y.roundTrips+=1;let J=$.get(K)??new Set;for(let G of H)J.add(G);$.set(K,J)},async srem(K,...H){X.srem+=1,Y.roundTrips+=1;let J=$.get(K);if(J)for(let G of H)J.delete(G)},async smembers(K){return X.smembers+=1,Y.roundTrips+=1,Array.from($.get(K)??[])},async mget(K){return X.mget+=1,Y.mgetCalls+=1,Y.roundTrips+=1,K.map((H)=>Z.has(H)?Z.get(H):null)}};return Y}function O$(){let Z=z0(),$=(J)=>Z.sets.get(J)?.size??0,Q=(J,G)=>{let W=Z.sets.get(J)??new Set;W.add(G),Z.sets.set(J,W)},X=(J,G)=>{Z.sets.get(J)?.delete(G)},Y=(J)=>Array.from(Z.sets.get(J)??[]),K=(J)=>{Z.raw.set(J,"1")},H=(J)=>{Z.raw.delete(J),Z.sets.delete(J)};return Z.runScript=async(J,G,W)=>{if(Z.commands.runScript+=1,Z.roundTrips+=1,J.includes("local A=tonumber(ARGV[2])")){let j=W[0],N=Number(W[1]),D=Number(W[2]),F=G[0],M=G[1];for(let R=0;R<N;R+=1){let x=2+R*5,[V,S,u,b,X0]=[G[x],G[x+1],G[x+2],G[x+3],G[x+4]],J0=3+R*3,[A0,s0,i5]=[W[J0],W[J0+1],W[J0+2]];Q(V,j),K(S),Q(u,s0),K(b),Q(F,A0),Q(X0,A0),Q(M,i5)}let O=2+N*5,P=3+N*3;for(let R=0;R<D;R+=1){let x=O+R*5,[V,S,u,b,X0]=[G[x],G[x+1],G[x+2],G[x+3],G[x+4]],J0=P+R*2,[A0,s0]=[W[J0],W[J0+1]];if(X(V,j),$(V)===0){if(H(V),H(S),X(X0,A0),X(u,s0),$(u)===0)H(u),H(b)}X(F,A0)}return"ok"}if(J.includes("local n=tonumber(ARGV[2])")){let j=W[0],N=Number(W[1]),D=G[0];for(let F=0;F<N;F+=1){let M=1+F*5,[O,P,R,x,V]=[G[M],G[M+1],G[M+2],G[M+3],G[M+4]],[S,u]=[W[2+F*2],W[3+F*2]];if(X(O,j),$(O)===0){if(H(O),H(P),X(V,S),X(R,u),$(R)===0)H(R),H(x)}}return H(D),"ok"}if(J.includes("local roots={}")){let j=Number(W[0]),N=G[0],D=[],F=new Set;for(let M=0;M<j;M+=1){let O=1+M*4,[P,R,x,V]=[G[O],G[O+1],G[O+2],G[O+3]];for(let S of Y(P))if(!F.has(S))F.add(S),D.push(S);H(P),H(R),H(x),H(V)}return H(N),D}if(J.includes("for i=1,#KEYS do")){for(let j of G)H(j);return"ok"}throw Error("authz scripted adapter: unsupported script")},Z}function M$(){let Z=z0();return Z.runScript=async($,Q,X)=>{if($.includes("return 'created'")){let Y=Q[0],K=X[0],H=Z.raw.has(Y)?Z.raw.get(Y):null;if(H!=null)return H===K?"exists_identical":"conflict";if(X.length>=2)await Z.setex(Y,Number(X[1]),K);else await Z.set(Y,K);return"created"}if($.includes("return 'retry'")){let Y=Q[0],K=X[0],H=X[1],J=Z.raw.has(Y)?Z.raw.get(Y):null;if(J==null)return"missing";if(J!==K)return"retry";if(X.length>=3)await Z.setex(Y,Number(X[2]),H);else{let G=Z.ttls.get(Y);if(Z.raw.set(Y,H),G!=null)Z.ttls.set(Y,G)}return"ok"}throw Error("scripted memory adapter: unsupported script")},Z}function e8(Z,$,Q){return{sid:Z,sub:`user-${$}`,user:{id:`user-${$}`},createdAt:new Date(0),...Q?{expiresAt:Q}:{}}}function L$(){return{tests:[{name:"corrupt JSON payloads are discarded (get -> undefined, list skips) instead of throwing (§1.27)",fn:async()=>{let $=z0(),Q=new V$({redis:$,keyPrefix:m,logger:a});await Q.upsert({id:"u-ok",userName:"ok",name:"OK",email:"ok@example.test"}),$.raw.set(`${m}user:${M3("u-bad")}`,"{ this is : not valid json");let X=$.sets.get(`${m}user:ids`);z(X!=null,"user id set should exist after the first upsert"),X?.add("u-bad");let Y=await Q.get("u-bad");B(Y,void 0,"a corrupt record must read back as undefined, not throw");let K=await Q.get("u-ok");z(K?.id==="u-ok","a valid record beside a corrupt one must still read");let H=await Q.list();B(H.items.length,1,"list must skip the corrupt record and return only valid ones"),B(H.items[0]?.id,"u-ok","the surviving record should be the valid one")}},{name:"list() bounds the page: omitted limit -> default cap, oversized limit -> max cap (§7.3)",fn:async()=>{let $=z0(),Q=new V$({redis:$,keyPrefix:m,logger:a}),X=DZ*2+50;for(let H=0;H<X;H+=1)await Q.upsert({id:`u-${H}`,userName:`u-${H}`,name:`User ${H}`,email:`u${H}@example.test`});let Y=await Q.list();B(Y.total,X,"total must reflect every matching row (before pagination)"),B(Y.items.length,V3,"an omitted limit must default-cap the page");let K=await Q.list({limit:X});B(K.items.length,DZ,"an oversized explicit limit must clamp to the max page"),B(K.total,X,"total stays the true count even when the page is clamped")}},{name:"chunkedMget batches a large key set (no unbounded MGET) and preserves order + nulls (§7.3)",fn:async()=>{let $=Array.from({length:5000},(K,H)=>`k${H}`),Q=0,X=0,Y=await D3($,async(K)=>{return X+=1,Q=Math.max(Q,K.length),K.map((H)=>Number(H.slice(1))%2===0?H:null)});z(X>1,"a 5000-key MGET must be split into multiple bounded batches"),z(Q<=O3,`each MGET batch must stay within the chunk size (saw ${Q})`),B(Y.length,$.length,"chunked result length must match input length"),B(Y[0],"k0","order must be preserved across chunks (first even key)"),B(Y[1],null,"missing values must normalize to null (first odd key)"),B(Y[4998],"k4998","last even key preserved"),B(Y[4999],null,"last odd key normalizes to null")}},{name:"create-if-absent: identical replay succeeds; a different value is a typed conflict (§5.9)",fn:async()=>{let $=z0(),Q=new A1({redis:$,keyPrefix:m,logger:a}),X=e8("s1","a");await Q.create(X),await Q.create(e8("s1","a"));let Y;try{await Q.create(e8("s1","b"))}catch(W){Y=W}z(v4(Y),"a different value at the same sid must throw a typed StoreConflictError");let K=new FZ({redis:$,keyPrefix:m,logger:a}),H={id:"c1",userName:"c1"},J=new Date(Date.now()+60000);await K.create("tok",H,J);let G;try{await K.create("tok",{id:"c2",userName:"c2"},J)}catch(W){G=W}z(v4(G),"a magic-link token collision must throw a typed StoreConflictError")}},{name:"unique secondary index: a second id claiming an in-use userName conflicts and writes nothing (§5.9)",fn:async()=>{let $=z0(),Q=new V$({redis:$,keyPrefix:m,logger:a});await Q.upsert({id:"u1",userName:"alice",name:"Alice",email:"a@example.test"}),await Q.upsert({id:"u1",userName:"alice",name:"Alice II",email:"a@example.test"});let X;try{await Q.upsert({id:"u2",userName:"alice",name:"Mallory",email:"m@example.test"})}catch(Y){X=Y}z(v4(X),"claiming an in-use userName must throw a typed conflict"),B(await Q.get("u2"),void 0,"a conflicting upsert must not persist the record (checks-first)"),B((await Q.get("u1"))?.name,"Alice II","the original owner record is untouched by the conflict")}},{name:"session update merges fields (no clobber) and a missing session throws (§5.9)",fn:async()=>{let $=z0(),Q=new A1({redis:$,keyPrefix:m,logger:a});await Q.create(e8("s1","a"));let X=new Date(Date.now()+1e6);await Q.update("s1",{expiresAt:X,tenantId:"tnt-1"});let Y=await Q.get("s1");B(new Date(Y?.expiresAt).getTime(),X.getTime(),"the patched field is applied"),B(Y?.createdAt?.getTime(),0,"unpatched fields are preserved (shallow merge, no clobber)");let K=!1;try{await Q.update("missing",{tenantId:"x"})}catch{K=!0}z(K,"updating a missing session must throw")}},{name:"session create derives a bounded TTL from expiresAt; a rotation-grace update shrinks it (§1.3)",fn:async()=>{let $=M$(),Q=new A1({redis:$,keyPrefix:m,logger:a});await Q.create(e8("s-ttl","ttl",new Date(Date.now()+1e6)));let X=Array.from($.raw.keys()).find((J)=>J.includes("session"));z(!!X,"a session key must exist after create");let Y=await $.ttl?.(X)??-1;z(Y>0&&Y<=1000,`create must set a bounded TTL from expiresAt (got ${Y})`),await Q.update("s-ttl",{tenantId:"tnt"});let K=await $.ttl?.(X)??-1;z(K>0,`an update without expiresAt must KEEPTTL (got ${K})`),await Q.update("s-ttl",{expiresAt:new Date(Date.now()+60000)});let H=await $.ttl?.(X)??-1;z(H>0&&H<=60,`grace re-TTL must shrink the key TTL to the grace (got ${H})`)}},{name:"session create with no runScript (fallback path) still applies a TTL via setex (§1.3)",fn:async()=>{let $=z0();await new A1({redis:$,keyPrefix:m,logger:a}).create(e8("s-ttl-fb","fb",new Date(Date.now()+500000)));let X=Array.from($.raw.keys()).find((K)=>K.includes("session"));z(!!X,"a session key must exist after fallback create");let Y=await $.ttl?.(X)??-1;z(Y>0&&Y<=500,`fallback create must setex a bounded TTL (got ${Y})`)}},{name:"hasAll/hasAny evaluate many targets with a single batched MGET (§7.4)",fn:async()=>{let $=z0(),Q=new n0({redis:$,keyPrefix:m,logger:a}),X=[{subject:"u1",subjectType:"user",targetType:"permission",target:"p0"}];await Q.materialize("root1",X);let Y=Array.from({length:50},(K,H)=>({targetType:"permission",target:`p${H}`,resource:`r${H}`}));$.mgetCalls=0,await Q.hasAll("u1",Y),B($.mgetCalls,1,"hasAll over 50 targets must issue exactly one batched MGET"),$.mgetCalls=0,await Q.hasAny("u1",Y),B($.mgetCalls,1,"hasAny over 50 targets must issue exactly one batched MGET")}},{name:"access list total stays accurate after revoke (§7.5)",fn:async()=>{let $=z0(),Q=new L3({redis:$,keyPrefix:m,logger:a});await Q.grant({subject:"u1",subjectType:"user",targetType:"role",target:"r1"}),await Q.grant({subject:"u1",subjectType:"user",targetType:"role",target:"r2"});let X=await Q.list();B(X.total,2,"both active grants are counted"),await Q.revoke({subject:"u1",subjectType:"user",targetType:"role",target:"r1"});let Y=await Q.list();B(Y.total,1,"revoke removes the row from the id set so the total is accurate"),B(Y.items.length,1,"only the surviving grant is returned"),B(Y.items[0]?.target,"r2","the surviving grant is the non-revoked one")}},{name:"list() returns a stable deterministic order with disjoint, complete offset pages",fn:async()=>{let $=z0(),Q=new V$({redis:$,keyPrefix:m,logger:a}),X=["u3","u1","u5","u2","u4"];for(let W of X)await Q.upsert({id:W,userName:`${W}@example.com`});let Y=await Q.list({start:0,limit:2}),K=await Q.list({start:2,limit:2}),H=await Q.list({start:4,limit:2});B(Y.total,5,"total reflects the full set on every page");let J=[...Y.items,...K.items,...H.items].map((W)=>W.id);B(J.join(","),"u1,u2,u3,u4,u5","pages are disjoint, complete, and in stable ascending id order");let G=await Q.list({start:0,limit:2});B(G.items.map((W)=>W.id).join(","),"u1,u2","the same offset window is byte-stable across repeated list() calls")}},{name:"reactive adapter close() before first readiness rejects ready() instead of hanging (§5.8)",fn:async()=>{let Q=BZ({source:{async subscribe(){return()=>{}}},path:"redis/main",createClient:async()=>({adapter:z0()})});await Q.close();let X=!1;try{await Q.ready(50)}catch{X=!0}z(X,"ready() must reject promptly after close() before first readiness, not hang")}},{name:"reactive adapter forwards runScript + getdel from the active client (§10.1)",fn:async()=>{let $=M$(),X=BZ({source:{async subscribe(Y,K){return K({value:{},metadata:{version:1}}),()=>{}}},path:"redis/main",createClient:async()=>({adapter:$})});await X.ready(1000),z(I1(X),"reactive adapter must forward runScript once the client supports it"),z(typeof X.getdel==="function","reactive adapter must forward getdel once the client supports it"),await X.set(`${m}fwd:k`,"v"),B(await X.getdel?.(`${m}fwd:k`),"v","forwarded getdel returns and deletes the value"),B(await X.get(`${m}fwd:k`),null,"forwarded getdel deleted the key"),await X.close()}},{name:"single-use consume over runScript/getdel yields exactly one winner (§10.1)",fn:async()=>{let $=M$();z(I1($),"scripted adapter must advertise runScript for this test");let Q=new FZ({redis:$,keyPrefix:m,logger:a}),X={id:"c1",userName:"c1"};await Q.create("one-time",X,new Date(Date.now()+60000));let K=(await Promise.all(Array.from({length:8},()=>Q.getAndDelete("one-time")))).filter((H)=>H!=null);B(K.length,1,"exactly one concurrent consume may win the single-use token")}},{name:"session update over the runScript CAS path preserves arrays and large ints (§8.2)",fn:async()=>{let $=M$();z(I1($),"scripted adapter must advertise runScript for this test");let Q=new A1({redis:$,keyPrefix:m,logger:a}),X=Number.MAX_SAFE_INTEGER,Y={sid:"s-merge",sub:"user-merge",user:{id:"user-merge"},claims:{groups:[],seq:X},createdAt:new Date(0),expiresAt:new Date(Date.now()+1e6)};await Q.create(Y),await Q.update("s-merge",{tenantId:"tnt-merge"});let K=await Q.get("s-merge");z(K!=null,"session must exist after update"),B(K?.tenantId,"tnt-merge","the patched field is applied"),z(Array.isArray(K?.claims.groups),"an empty-array claim must stay an array (not become {})"),B((K?.claims.groups).length,0,"the empty-array claim stays empty"),B(K?.claims.seq,X,"a large-integer claim survives the merge unchanged")}},{name:"key encoding: injected `:` cannot alias another edge in the access index (§10.2)",fn:async()=>{let $=z0(),Q=new n0({redis:$,keyPrefix:m,logger:a});await Q.materialize("root-a",[{subject:"u",subjectType:"user",targetType:"permission",target:"p",resource:"x:y"}]),z((await Q.has("u","permission","p","x:y")).allow,"the materialized edge (target p, resource x:y) must be allowed"),z(!(await Q.has("u","permission","p:x","y")).allow,"a colon-shifted edge must NOT alias the real edge — it must be denied")}},{name:"key encoding: subject-disabled marker is keyed to the exact id, not a `:`-prefix (§10.2)",fn:async()=>{let $=z0(),Q=new n0({redis:$,keyPrefix:m,logger:a});await Q.setSubjectDisabled("team:a","user",!0),z(await Q.isSubjectDisabled("team:a","user"),"the disabled marker round-trips for a `:`-bearing id"),z(!await Q.isSubjectDisabled("team","user"),"a prefix subject id must not alias another subject’s disabled marker"),z(!await Q.isSubjectDisabled("team:a","customer"),"the veto is namespaced by subjectType")}},{name:"key encoding: iam-delta id-map keys do not cross-map on `:` boundaries (§10.2)",fn:async()=>{let $=z0(),Q=new _3({redis:$,keyPrefix:m,logger:a});await Q.set({resourceType:"user",destination:"a",externalId:"b:c"},"provider-1"),await Q.set({resourceType:"user",destination:"a:b",externalId:"c"},"provider-2"),B(await Q.get({resourceType:"user",destination:"a",externalId:"b:c"}),"provider-1","first mapping is preserved (no cross-map)"),B(await Q.get({resourceType:"user",destination:"a:b",externalId:"c"}),"provider-2","second mapping is preserved (no cross-map)")}},{name:"key encoding: a pathologically long id produces a bounded key (§10.2)",fn:async()=>{let $=z0(),Q=new n0({redis:$,keyPrefix:m,logger:a}),X="x".repeat(1e4);await Q.materialize("root-huge",[{subject:X,subjectType:"user",targetType:"permission",target:X}]);for(let Y of $.raw.keys())z(Y.length<512,`key must stay bounded even for a 10k-char id (saw ${Y.length})`),z(!Y.includes(X),"the raw oversized id must never appear verbatim in a key");for(let Y of $.sets.keys())z(Y.length<512,`set key must stay bounded even for a 10k-char id (saw ${Y.length})`)}},{name:"Lua teardown: removeByRoot of a 1000-edge root is O(1) client round trips, not O(edges) (§7.2)",fn:async()=>{let $=O$();z(I1($),"scripted authz adapter must advertise runScript for the Lua path");let Q=new n0({redis:$,keyPrefix:m,logger:a}),X=Array.from({length:1000},(Y,K)=>({subject:"big",subjectType:"user",targetType:"permission",target:`p${K}`}));await Q.materialize("root-big",X),z((await Q.hasPermission("big","p0")).allow,"a materialized edge resolves before teardown"),z((await Q.hasPermission("big","p999")).allow,"the last materialized edge resolves too"),$.resetCounts(),await Q.removeByRoot("root-big"),B($.commands.smembers,1,"teardown reads the root descriptor set with exactly one SMEMBERS"),B($.commands.runScript,1,"teardown applies every edge removal in exactly one runScript"),z($.roundTrips<=3,`a 1000-edge teardown must be O(1) client round trips, not O(edges) (saw ${$.roundTrips})`),z(!(await Q.hasPermission("big","p0")).allow,"edges are gone after teardown"),z(!(await Q.hasPermission("big","p999")).allow,"the last edge is gone after teardown")}},{name:"diff-materialize: re-materializing identical edges is a no-op (no write, no §5.3 deny window)",fn:async()=>{let $=O$(),Q=new n0({redis:$,keyPrefix:m,logger:a}),X=[{subject:"u",subjectType:"user",targetType:"role",target:"a"},{subject:"u",subjectType:"user",targetType:"role",target:"b"}];await Q.materialize("root-x",X),$.resetCounts(),await Q.materialize("root-x",X),B($.commands.runScript,0,"an empty diff must not invoke the write script at all"),z((await Q.hasRole("u","a")).allow,"edge a stays present across a no-op re-materialize"),z((await Q.hasRole("u","b")).allow,"edge b stays present across a no-op re-materialize")}},{name:"diff-materialize: removing one edge leaves the unchanged sibling continuously present (§5.3)",fn:async()=>{let $=O$(),Q=new n0({redis:$,keyPrefix:m,logger:a});await Q.materialize("root-y",[{subject:"u",subjectType:"user",targetType:"role",target:"keep"},{subject:"u",subjectType:"user",targetType:"role",target:"drop"}]),await Q.materialize("root-y",[{subject:"u",subjectType:"user",targetType:"role",target:"keep"}]),z((await Q.hasRole("u","keep")).allow,"the unchanged edge survives a sibling removal"),z(!(await Q.hasRole("u","drop")).allow,"the removed edge is gone")}},{name:"teardown never touches idx:disabled: a deactivated subject stays vetoed after removeByRoot (§7.2)",fn:async()=>{let $=O$(),Q=new n0({redis:$,keyPrefix:m,logger:a});await Q.setSubjectDisabled("u","user",!0),await Q.materialize("root-z",[{subject:"u",subjectType:"user",targetType:"role",target:"a"}]),await Q.removeByRoot("root-z"),z(await Q.isSubjectDisabled("u","user"),"the subject-disabled marker is independent of edge materialization and survives teardown")}},{name:"fallback teardown (no runScript): removeByRoot issues O(edges) commands, not ~5×edges",fn:async()=>{let $=z0();z(!I1($),"this test intends to exercise the no-runScript fallback");let Q=new n0({redis:$,keyPrefix:m,logger:a}),X=Array.from({length:100},(Y,K)=>({subject:"f",subjectType:"user",targetType:"permission",target:`p${K}`}));await Q.materialize("root-fb",X),$.resetCounts(),await Q.removeByRoot("root-fb"),z($.roundTrips<=1200,`fallback teardown must be O(edges) with a small constant (saw ${$.roundTrips})`),z($.roundTrips<1e4,`fallback teardown must not be quadratic in edges (saw ${$.roundTrips})`),z(!(await Q.hasPermission("f","p0")).allow,"fallback teardown removes edges"),z(!(await Q.hasPermission("f","p99")).allow,"fallback teardown removes the last edge")}},{name:"EVALSHA memoization: stable script body → one sha, EVALSHA hits, NOSCRIPT falls back to EVAL (§7.8)",fn:async()=>{let $=0,Q=0,X=new Set,K=F3({async eval(G,W,j){return $+=1,X.add(y4(G)),"ok"},async evalsha(G,W,j){if(Q+=1,!X.has(G))throw Error("NOSCRIPT No matching script. Please use EVAL.");return"ok"}}),H=`redis.call('SET', KEYS[1], ARGV[1])
|
|
8
|
+
return 'ok'`;await K(H,["k"],["v1"]),B(Q,1,"the runner always tries EVALSHA first"),B($,1,"a cold NOSCRIPT falls back to EVAL exactly once"),await K(H,["k"],["v2"]),await K(H,["k"],["v3"]),B($,1,"a stable script body never re-transmits the source after the first prime"),B(Q,3,"steady-state calls all go over EVALSHA (the 40-byte sha)");let J=`redis.call('DEL', KEYS[1])
|
|
9
|
+
return 'ok'`;z(y4(H)!==y4(J),"distinct script bodies hash to distinct shas")}}]}}async function P3(){let Z=performance.now(),{tests:$}=L$(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"Redis stores",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}import{waitOn as x3}from"@ionite/core";import{generateLfvEd25519KeyPair as VZ,signLfvPayload as R3,stringifyLfvJwks as OZ}from"@ionite/core/server";import{lfvVault as MZ,openbaoVault as q3,subscribeSecretPathWithHttpPolling as T3}from"@ionite/server";var o0={lfvServerUrl:"http://localhost:3599/api/lfv",clientId:"ecv-hardening-client",path:"/ecv/hardening/config",deliveryEndpoint:"/api/ionite/lfv/delivery",eventsEndpoint:"/api/ionite/lfv/events"},J8=(Z)=>new Promise(($)=>setTimeout($,Z));async function LZ(Z,$,Q,X){let Y=String($.request_id),K="issued_at"in $?$:{...$,issued_at:Date.now()},H=JSON.stringify(K),J=await R3({privateKeyJwk:Q,requestId:Y,rawBody:H});return new Request(`http://localhost${Z}`,{method:"POST",headers:{"Content-Type":"application/json","X-LFV-Request-Id":Y,"X-LFV-Key-Id":X,"X-LFV-Signature":J},body:H})}function U3(Z){if(typeof Z==="string")return Z;if(Z instanceof URL)return Z.toString();return Z.url}function w3(Z,$=5){let Q=[],X=Z;for(let Y=0;Y<$&&X!=null;Y+=1)if(X instanceof Error)Q.push(X.message),X=X.cause;else{Q.push(String(X));break}return Q.join(" <- ")}function A3(Z,$){let Q=Z?.headers;if(!Q)return null;if(Q instanceof Headers)return Q.get($);if(Array.isArray(Q)){let Y=Q.find(([K])=>K.toLowerCase()===$.toLowerCase());return Y?Y[1]:null}let X=Q;for(let[Y,K]of Object.entries(X))if(Y.toLowerCase()===$.toLowerCase())return K;return null}function _$(){return{tests:[{name:"upstream error bodies are redacted from thrown errors (no secret leaks into configStatus.lastError)",fn:async()=>{let Q=q3({url:"http://vault.test/v1/secret/data",token:"ecv-test-token"}),X=globalThis.fetch;globalThis.fetch=async()=>{return new Response(JSON.stringify({errors:["SECRET-DO-NOT-LEAK-7f3a91"],leaked:"SECRET-DO-NOT-LEAK-7f3a91"}),{status:500,statusText:"Internal Server Error"})};try{let Y=!1,K="";try{await Q.getSecret("app/config")}catch(H){Y=!0,K=w3(H)}z(Y,"a non-200 vault read must throw (fail closed), not return stale/empty data"),z(!K.includes("SECRET-DO-NOT-LEAK-7f3a91"),`thrown error must not include the upstream body (got: ${K})`),z(K.includes("500"),`redacted error should still carry the structured status (got: ${K})`)}finally{globalThis.fetch=X,Q.close?.()}}},{name:"http polling never overlaps and surfaces degraded/recovered health",fn:async()=>{let $=0,Q=0,X=0,Y=!1,K=[],J=await T3({getFullSecret:async(W)=>{if($+=1,Q=Math.max(Q,$),X+=1,await J8(25),$-=1,Y)throw Error("simulated upstream failure");return{data:{},metadata:{path:W,version:1,created:new Date}}}},"/p",()=>{},15,void 0,void 0,{onHealth:(W)=>K.push(W)});await J8(180),B(Q,1,"HTTP polls must never overlap (self-scheduling chain)"),Y=!0,await J8(120),z(K.includes(!1),"a failed poll must surface unhealthy (degraded isAvailable) instead of hammering"),Y=!1,await J8(120),B(K[K.length-1],!0,"recovery must surface healthy again"),J();let G=X;await J8(120),B(X,G,"no polls may run after unsubscribe (loop halts on teardown)")}},{name:"LFV OTP-as-possession-proof: no client secret, OTP arrives only via signed callback",fn:async()=>{let{privateJwk:$,publicJwks:Q}=await VZ("ecv-otp-kid"),X=MZ({...o0,verifyJwks:OZ(Q)}),Y=0,K=0,H=!1,J=7,G=globalThis.fetch;globalThis.fetch=async(W,j)=>{let N=U3(W);if(A3(j,"X-LFV-Signature")!==null)H=!0;let D=JSON.parse(String(j?.body??"{}"));if(N.endsWith("/otp"))return Y+=1,await X.handleDelivery(await LZ(o0.deliveryEndpoint,{request_id:D.request_id,type:"otp",otp:"otp-value",path:o0.path},$,"ecv-otp-kid")),new Response(JSON.stringify({request_id:D.request_id,status:"pending"}),{status:202});if(N.endsWith("/secrets/request"))return K+=1,await X.handleDelivery(await LZ(o0.deliveryEndpoint,{request_id:D.request_id,path:o0.path,data:{data:{marker:"possession-ok"},metadata:{path:o0.path,version:J}}},$,"ecv-otp-kid")),new Response(JSON.stringify({request_id:D.request_id,status:"accepted"}),{status:202});return new Response("{}",{status:200})};try{let W=await Promise.all(Array.from({length:5},()=>X.getSecret(o0.path)));for(let j of W)B(j.marker,"possession-ok","each concurrent read should resolve from the one cycle");B(Y,1,"concurrent reads must issue exactly one OTP request (single-flight)"),B(K,1,"concurrent reads must issue exactly one secret request (single-flight)"),z(!H,"client must not send X-LFV-Signature (no client-held secret)")}finally{globalThis.fetch=G,X.close?.()}}},{name:"LFV close() mid-outage settles pending waiters instead of leaking them",fn:async()=>{let{publicJwks:$}=await VZ("ecv-close-kid"),Q=MZ({...o0,verifyJwks:OZ($)}),X=globalThis.fetch;globalThis.fetch=async(Y,K)=>{let H=JSON.parse(String(K?.body??"{}"));return new Response(JSON.stringify({request_id:H.request_id,status:"pending"}),{status:202})};try{let Y=Q.getSecret(o0.path),K=!1;Y.catch(()=>{K=!0}),await J8(40),Q.close?.();let H=!1,J="";try{await Y}catch(G){H=!0,J=G instanceof Error?G.message:String(G)}z(H,"close() must settle the pending read (reject), not leak the waiter"),z(J.toLowerCase().includes("closed"),`rejection should explain the source closed (got: ${J})`),z(K,"pending waiter promise must settle after close()")}finally{globalThis.fetch=X}}},{name:"waitOn probes never overlap and stop after success (timers cleared)",fn:async()=>{let $=0,Q=0,X=0,Y=!1,K=globalThis.fetch;globalThis.fetch=async()=>{return $+=1,Q+=1,X=Math.max(X,Q),await J8(40),Q-=1,new Response("{}",{status:200})};let H=setTimeout(()=>{Y=!0},150);try{await x3("http://localhost/ecv/health",()=>Y,10,0,5000),B(X,1,"waitOn must never run overlapping probes (self-scheduling chain)"),z($>=2,"the probe chain should have iterated at least twice before becoming ready");let J=$;await J8(120),B($,J,"no probe may fire after waitOn resolves (retry timer cleared)")}finally{clearTimeout(H),globalThis.fetch=K}}}]}}async function I3(){let Z=performance.now(),{tests:$}=_$(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"Secrets hardening",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}var _Z={tenantPath:"/api/tenants"};function b4(Z,$){return`${(Z.tenantPath??"/api/tenants").replace(/\/+$/,"")}/${$}`}async function PZ(Z,$,Q,X){return L("Tenant Create",async()=>{if(!Q)return{details:{skipped:!0,reason:"Test tenant data not provided"}};let Y={id:Q.id,companyId:Q.companyId,companyName:Q.companyName,environmentType:Q.environmentType,configLocator:Q.configLocator,...Q.email?{email:Q.email}:{},...Q.webhookUrl||X?{webhookUrl:Q.webhookUrl??X}:{},...Q.callbackUrl?{callbackUrl:Q.callbackUrl}:{}},K=await $(Z.tenantPath??"/api/tenants",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Y)});z(K.status===200||K.status===201||K.status===202,`Expected 200, 201, or 202, got ${K.status}`);let H=await K.json();z(typeof H.status==="string","Expected response status");let J=H.status;if(z(["action_required","pending","processing","completed","failed"].includes(J),`Unexpected tenant status: ${J}`),J==="action_required")z(typeof H.actionUrl==="string","Expected actionUrl for action_required"),z(typeof H.requestToken==="string","Expected requestToken for action_required"),z(typeof H.expiresAt==="string","Expected expiresAt for action_required");else if(H.tenantUrl!=null)z(typeof H.tenantUrl==="string","Expected tenantUrl to be a string when provided");return{details:{httpStatus:K.status}}})}async function xZ(Z,$,Q){return L("Tenant Get",async()=>{if(!Q)return{details:{skipped:!0,reason:"Test tenant data not provided"}};let X=await $(b4(Z,Q.id),{method:"GET",headers:{"Content-Type":"application/json"}});z(X.status===200,`Expected 200, got ${X.status}`);let Y=await X.json();return z(Y.id===Q.id,"Expected id to match"),{details:{httpStatus:X.status}}})}async function RZ(Z,$,Q){return L("Tenant Delete",async()=>{if(!Q)return{details:{skipped:!0,reason:"Test tenant data not provided"}};let X=await $(b4(Z,Q.id),{method:"DELETE",headers:{"Content-Type":"application/json"}});z(X.status===204,`Expected 204, got ${X.status}`);let Y=await $(b4(Z,Q.id),{method:"GET"});return z(Y.status===404,`Expected 404 after delete, got ${Y.status}`),{details:{httpStatus:X.status}}})}async function qZ(Z,$){return L("Tenant Creation (Validation)",async()=>{let Q={companyId:"test-company"},X=Z.tenantPath??"/api/tenants",Y=await $(X,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Q)});return z(Y.status===400,`Expected 400 Bad Request for invalid payload, got ${Y.status}`),{details:{status:Y.status}}})}async function TZ(Z,$){return L("Tenant Creation Rejects Malformed JSON",async()=>{let Q=Z.tenantPath??"/api/tenants",X=await $(Q,{method:"POST",headers:{"Content-Type":"application/json"},body:'{"companyId":'});return z(X.status===400,`Expected 400 Bad Request for malformed JSON, got ${X.status}`),{details:{status:X.status}}})}async function UZ(Z,$){return L("Tenant Rejects Legacy user-tenants Route",async()=>{let Q=(Z.tenantPath??"/api/tenants").replace(/\/+$/,""),X=await $(`${Q}/user-tenants?email=test@example.com`,{method:"GET",headers:{"Content-Type":"application/json"}});return z(X.status===404,`Expected 404, got ${X.status}`),{details:{status:X.status}}})}async function C3(Z){let $=performance.now(),Q={..._Z,...Z},X=c(Q),Y=Q.profile??"tenantManager",K=[];if(K.push(await wZ(Q)),K.push(await AZ(Q)),K.push(await qZ(Q,X)),K.push(await TZ(Q,X)),Y==="tenantManager")K.push(await UZ(Q,X));if(K.push(await PZ(Q,X,Q.testTenantData,Q.webhookPath?`${X8(Q)}${Q.webhookPath}`:void 0)),Y==="tenantManager")K.push(await xZ(Q,X,Q.testTenantData)),K.push(await RZ(Q,X,Q.testTenantData));let H=performance.now()-$,J=K.filter((j)=>j.passed).length,G=K.filter((j)=>!j.passed).length,W=K.filter((j)=>j.details?.skipped).length;return{suite:"Tenant",passed:G===0,tests:K,duration:H,summary:{total:K.length,passed:J-W,failed:G,skipped:W}}}async function wZ(Z){return L("Tenant Rejects Without Workload Auth",async()=>{let $=Z.tenantPath??"/api/tenants",X=await c({baseUrl:Z.baseUrl,timeout:Z.timeout,headers:{}})($,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:"auth-test-app",companyId:"auth-test-company",companyName:"Auth Test Company",environmentType:"POC",configLocator:{path:"/ecv/test-config"}})});return z(X.status===401,`Expected 401 without workload auth, got ${X.status}`),{details:{httpStatus:X.status}}})}async function AZ(Z){return L("Tenant Rejects Invalid Workload Token",async()=>{let $=Z.tenantPath??"/api/tenants",X=await c({baseUrl:Z.baseUrl,timeout:Z.timeout,headers:{Authorization:"Bearer invalid-token"}})($,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:"auth-test-app",companyId:"auth-test-company",companyName:"Auth Test Company",environmentType:"POC",configLocator:{path:"/ecv/test-config"}})});return z(X.status===401,`Expected 401 with invalid token, got ${X.status}`),{details:{httpStatus:X.status}}})}function h4(Z){let $={..._Z,...Z},Q=c($),X=$.profile??"tenantManager",Y=[{name:"tenant creation endpoint rejects requests without workload authentication",fn:async()=>{let K=await wZ($);if(!K.passed)throw Error(K.error)}},{name:"tenant creation endpoint rejects requests with invalid workload token",fn:async()=>{let K=await AZ($);if(!K.passed)throw Error(K.error)}},{name:"tenant creation endpoint validates required fields",fn:async()=>{let K=await qZ($,Q);if(!K.passed)throw Error(K.error)}},{name:"tenant creation endpoint rejects malformed JSON",fn:async()=>{let K=await TZ($,Q);if(!K.passed)throw Error(K.error)}},{name:"tenant manager rejects legacy user-tenants route",fn:async()=>{let K=await UZ($,Q);if(!K.passed)throw Error(K.error)}},{name:"tenant upsert returns valid response structure",fn:async()=>{let K=await PZ($,Q,$.testTenantData,$.webhookPath?`${X8($)}${$.webhookPath}`:void 0);if(!K.passed&&!K.details?.skipped)throw Error(K.error)}},{name:"tenant get returns upserted tenant",fn:async()=>{let K=await xZ($,Q,$.testTenantData);if(!K.passed&&!K.details?.skipped)throw Error(K.error)}},{name:"tenant delete removes tenant",fn:async()=>{let K=await RZ($,Q,$.testTenantData);if(!K.passed&&!K.details?.skipped)throw Error(K.error)}}];if(X==="creation")return Y.filter((K)=>["tenant creation endpoint rejects requests without workload authentication","tenant creation endpoint rejects requests with invalid workload token","tenant creation endpoint validates required fields","tenant creation endpoint rejects malformed JSON","tenant upsert returns valid response structure"].includes(K.name));return Y}import{RedisTenantStore as C1}from"@ionite/redis-core";import{InMemoryAccessCatalogStore as E3,InMemoryTenantStore as P$,ionite as S3,silentLogger as IZ}from"@ionite/server";import{createValidators as v3}from"@ionite/valibot";import{LRUCache as x$}from"lru-cache";function y3(Z){return{async load(){return Z},async subscribe(){return},secret:{}}}function _8(Z,$=Z){return{id:Z,companyId:`co-${Z}`,companyName:$,environmentType:"DEV",status:"completed",configLocator:{path:`secret/data/tenants/${Z}`},createdAt:new Date,updatedAt:new Date}}function R$(Z,$){return{close:$,__marker:Z}}function P8(Z){return Z?.__marker}function CZ(){let Z=new Map,$=new Map;return{async get(Q){return Z.has(Q)?Z.get(Q):null},async set(Q,X){Z.set(Q,X)},async del(Q){Z.delete(Q)},async mget(Q){return Q.map((X)=>Z.has(X)?Z.get(X):null)},async sadd(Q,...X){let Y=$.get(Q)??new Set;for(let K of X)Y.add(K);return $.set(Q,Y),X.length},async srem(Q,...X){let Y=$.get(Q);if(!Y)return 0;let K=0;for(let H of X)if(Y.delete(H))K+=1;return K},async smembers(Q){return Array.from($.get(Q)??[])}}}function q$(){return{tests:[{name:"getIon() without an ionCache throws a guidance error — the SDK never constructs runtimes (§7.4)",fn:async()=>{let $=new P$;await $.upsert(_8("t1"));let Q;try{await $.getIon("t1")}catch(X){Q=X.message}z(Q!==void 0,"getIon() must throw when no ionCache is configured"),z(/ionCache/.test(Q??""),"the error must point at the required ionCache pattern")}},{name:"the app cache single-flights concurrent construction (two getIon for one id => one build) (§7.4)",fn:async()=>{let $={constructions:0,closeCount:0},Q,X=new x$({max:50,dispose:(H)=>H.close(),fetchMethod:async(H)=>{let J=await Q.get(H);if(!J)return;return await Promise.resolve(),$.constructions+=1,R$(J.companyName,()=>{$.closeCount+=1})}});Q=new P$({ionCache:X}),await Q.upsert(_8("t1"));let[Y,K]=await Promise.all([Q.getIon("t1"),Q.getIon("t1")]);B($.constructions,1,"concurrent getIon must coalesce into a single construction"),z(Y===K,"both callers must receive the same cached instance")}},{name:"upsert invalidates and closes the cached runtime; the next getIon constructs fresh (§7.4)",fn:async()=>{let $={constructions:0,closeCount:0},Q,X=new x$({max:50,dispose:(H)=>H.close(),fetchMethod:async(H)=>{let J=await Q.get(H);if(!J)return;return $.constructions+=1,R$(J.companyName,()=>{$.closeCount+=1})}});Q=new P$({ionCache:X}),await Q.upsert(_8("t1","v1"));let Y=await Q.getIon("t1");B(P8(Y),"v1","first build reflects the v1 record"),B($.constructions,1,"one construction after the first getIon"),await Q.upsert(_8("t1","v2")),B($.closeCount,1,"upsert must close the previously cached runtime");let K=await Q.getIon("t1");B(P8(K),"v2","the next getIon rebuilds from the updated record"),B($.constructions,2,"a fresh construction follows invalidation")}},{name:"delete closes the cached runtime (§7.4)",fn:async()=>{let $={constructions:0,closeCount:0},Q,X=new x$({max:50,dispose:(Y)=>Y.close(),fetchMethod:async(Y)=>{let K=await Q.get(Y);if(!K)return;return $.constructions+=1,R$(K.companyName,()=>{$.closeCount+=1})}});Q=new P$({ionCache:X}),await Q.upsert(_8("t1")),await Q.getIon("t1"),B($.closeCount,0,"no close before deletion"),await Q.delete("t1"),B($.closeCount,1,"delete must close the cached runtime")}},{name:"ion.close() detaches the authz catalog-store listener; a closed instance stops reacting (§6.1)",fn:async()=>{let $=new E3,Q=0,X=0,Y=$.afterChange.bind($);$.afterChange=(G)=>{Q+=1;let W=Y((j)=>{return X+=1,G(j)});return()=>{Q-=1,W()}};let K=(G)=>S3(y3({id:G}),{validators:v3(),stores:{catalogStore:$}}),H=K("batch6-authz-a"),J=K("batch6-authz-b");await H.ready(1000),await J.ready(1000),B(Q,2,"both instances subscribe exactly one catalog listener"),X=0,$.groups.set({id:"g1"}),B(X,2,"both instances react to the change while open"),H.close(),B(Q,1,"ion.close() detaches the closed instance’s catalog listener"),X=0,$.groups.set({id:"g2"}),B(X,1,"exactly one reconcile/projection fires after the other instance is closed"),J.close(),B(Q,0,"closing the last instance leaves no listeners on the shared store")}},{name:"cross-node staleness is TTL-bounded: upsert on node A does not invalidate node B (§3.1)",fn:async()=>{let $=CZ(),Q=`ion:t:batch6-node:${crypto.randomUUID()}:`,X=()=>{let H,J=new x$({max:50,dispose:(G)=>G.close(),fetchMethod:async(G)=>{let W=await H.get(G);if(!W)return;return R$(W.companyName,()=>{})}});return H=new C1({redis:$,keyPrefix:Q,logger:IZ,ionCache:J}),{store:H,ionCache:J}},Y=X(),K=X();await Y.store.upsert(_8("t","v1")),B(P8(await Y.store.getIon("t")),"v1","node A builds from v1"),B(P8(await K.store.getIon("t")),"v1","node B builds from v1"),await Y.store.upsert(_8("t","v2")),B(P8(await Y.store.getIon("t")),"v2","node A rebuilds from the new record"),B(P8(await K.store.getIon("t")),"v1","node B keeps serving the old runtime until its own cache evicts (documented staleness)"),K.ionCache.delete("t"),B(P8(await K.store.getIon("t")),"v2","after eviction node B rebuilds from the shared record")}},{name:'the Redis key-prefix "constructed twice" warning fires at most once per fingerprint (§6.2)',fn:async()=>{let $=CZ(),Q=[],X={...IZ,warn:(J)=>{Q.push(String(J))}},Y=`ion:t:batch6-dup:${crypto.randomUUID()}:`,K={redis:$,keyPrefix:Y,logger:X};new C1(K),new C1(K),new C1(K),new C1(K);let H=Q.filter((J)=>J.includes(Y));B(H.length,1,"the duplicate-prefix warning must fire at most once per fingerprint")}}]}}async function b3(){let Z=performance.now(),{tests:$}=q$(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"Tenant lifecycle",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}import{assertEcid as EZ,EcidFormatError as m4,formatEcid as SZ,ION_SCIM_USER_EXTENSION_URN as vZ,isEcid as yZ,parseEcid as bZ}from"@ionite/core";import{createControlValidators as h3,createValidators as m3,BaseUser as k3}from"@ionite/valibot";import{createControlValidators as f3,createValidators as g3,BaseUser as p3}from"@ionite/zod";var gZ=g3(),pZ=m3(),u3=f3(),l3=h3();async function hZ(Z,$){return!(await Z["~standard"].validate($)).issues}async function x0(Z,$,Q,X,Y){let K=await hZ(Z,Q),H=await hZ($,Q);z(K===H,`${Y}: zod and valibot disagree (zod ${K?"accepted":"rejected"}, valibot ${H?"accepted":"rejected"})`),z(K===X,`${Y}: expected both packages to ${X?"accept":"reject"}`)}function T$(){return{z:gZ.sso.idTokenClaims,v:pZ.sso.idTokenClaims}}function U$(){return{z:gZ.iam.user,v:pZ.iam.user}}function mZ(){return{z:p3,v:k3}}var k4="ecid:acme.com:01J9F3Z8QK7M2N4P6R8T0V2W4X",f4=["not-an-ecid","ecid:acme.com","ecid::opaque","ecid:acme.com:","urn:x:acme:1","ecid:acme com:abc"],kZ={userName:"u@example.com",name:"U",email:"u@example.com"},fZ={userName:"u@example.com"};function g4(){return{z:u3.sso.registerSSOAppPayload,v:l3.sso.registerSSOAppPayload}}var w$={sub:"user-1",iss:"https://idp.example/realm",aud:"client-1",exp:2000000000,iat:1};function A$(){return{tests:[{name:"§10.1 ID-token claims: complete claims accepted (both packages)",fn:async()=>{let{z:$,v:Q}=T$();await x0($,Q,w$,!0,"valid id-token claims")}},{name:"§10.1 ID-token claims: array aud accepted (both packages)",fn:async()=>{let{z:$,v:Q}=T$();await x0($,Q,{...w$,aud:["client-1","api://resource"]},!0,"array aud")}},{name:"§10.1 ID-token claims: missing sub/iss/aud/exp each rejected (both packages)",fn:async()=>{let{z:$,v:Q}=T$();for(let X of["sub","iss","aud","exp"]){let Y={...w$};delete Y[X],await x0($,Q,Y,!1,`id-token missing ${X}`)}}},{name:"§10.1 ID-token claims: empty aud array rejected (both packages)",fn:async()=>{let{z:$,v:Q}=T$();await x0($,Q,{...w$,aud:[]},!1,"empty aud array")}},{name:"§10.2 SCIM user: a present password is rejected (both packages)",fn:async()=>{let{z:$,v:Q}=U$();await x0($,Q,{userName:"u@example.com",password:"hunter2"},!1,"scim user with password")}},{name:"§10.2 SCIM user: no password accepted (both packages)",fn:async()=>{let{z:$,v:Q}=U$();await x0($,Q,{userName:"u@example.com"},!0,"scim user without password")}},{name:"§10.2 registerSSOApp: javascript:/data: and non-URL redirectUris rejected (both packages)",fn:async()=>{let{z:$,v:Q}=g4(),X=["javascript:alert(1)","data:text/html,<script>alert(1)</script>","not-a-url","ftp://example.com/cb","http://evil.example.com/cb",`java
|
|
10
|
+
script:alert(1)`];for(let Y of X)await x0($,Q,{redirectUris:[Y]},!1,`redirectUri ${JSON.stringify(Y)}`)}},{name:"§10.2 registerSSOApp: https and http://localhost redirectUris accepted (both packages)",fn:async()=>{let{z:$,v:Q}=g4(),X=["https://app.example.com/auth/callback","http://localhost:3000/cb","http://127.0.0.1:3000/cb"];for(let Y of X)await x0($,Q,{redirectUris:[Y]},!0,`redirectUri ${JSON.stringify(Y)}`)}},{name:"§10.2 registerSSOApp: empty redirectUris array rejected (both packages)",fn:async()=>{let{z:$,v:Q}=g4();await x0($,Q,{redirectUris:[]},!1,"empty redirectUris")}},{name:"ECID: formatEcid/parseEcid/isEcid round-trip a valid ECID",fn:async()=>{let $=SZ("acme.com","01J9F3Z8QK7M2N4P6R8T0V2W4X");z($===k4,`formatEcid produced ${$}`),z(yZ($),"isEcid should accept a formatted ECID");let Q=bZ($);z(Q.realm==="acme.com"&&Q.opaque==="01J9F3Z8QK7M2N4P6R8T0V2W4X","parseEcid segments"),z(EZ($)===$,"assertEcid returns the value")}},{name:"ECID: malformed values are rejected by isEcid and throw from parse/assert/format",fn:async()=>{for(let Q of f4){z(!yZ(Q),`isEcid should reject ${JSON.stringify(Q)}`);let X=!1;try{bZ(Q)}catch(K){X=K instanceof m4}z(X,`parseEcid should throw EcidFormatError for ${JSON.stringify(Q)}`);let Y=!1;try{EZ(Q)}catch(K){Y=K instanceof m4}z(Y,`assertEcid should throw EcidFormatError for ${JSON.stringify(Q)}`)}let $=!1;try{SZ("acme com","abc")}catch(Q){$=Q instanceof m4}z($,"formatEcid should throw EcidFormatError for an invalid realm")}},{name:"ECID: SCIM user with a valid ionite extension ecid/accountClass accepted (both packages)",fn:async()=>{let{z:$,v:Q}=U$(),X={...fZ,[vZ]:{ecid:k4,accountClass:"privileged"}};await x0($,Q,X,!0,"scim user with valid ionite extension")}},{name:"ECID: SCIM user with a malformed ecid rejected (both packages)",fn:async()=>{let{z:$,v:Q}=U$();for(let X of f4){let Y={...fZ,[vZ]:{ecid:X}};await x0($,Q,Y,!1,`scim user with malformed ecid ${JSON.stringify(X)}`)}}},{name:"ECID: BaseUser with a valid ecid accepted (both packages)",fn:async()=>{let{z:$,v:Q}=mZ();await x0($,Q,{...kZ,ecid:k4,accountClass:"daily"},!0,"base user valid ecid")}},{name:"ECID: BaseUser with a malformed ecid rejected (both packages)",fn:async()=>{let{z:$,v:Q}=mZ();for(let X of f4)await x0($,Q,{...kZ,ecid:X},!1,`base user malformed ecid ${JSON.stringify(X)}`)}}]}}async function d3(){let Z=performance.now(),{tests:$}=A$(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"Validator parity",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}import{closeAllVaultSockets as l5,openbaoVault as q0,setVaultWebSocketCommandTimeout as d5,vaultSocketDiagnostics as u$}from"@ionite/server";var jY=e0(h5(),1),NY=e0(h$(),1),BY=e0(Z1(),1),DY=e0(i4(),1),FY=e0(o4(),1),VY=e0($Q(),1),OY=e0(g$(),1),QQ=e0(u5(),1);var MY={"tok-a":"CLT-A","tok-b":"CLT-B","tok-solo":"CLT-SOLO"};function LY(Z){return typeof Z==="string"?MY[Z]:void 0}function l$(Z,$){return`${Z}::${$}`}function _Y(Z){let $=Z["x-vault-token"],Q=Array.isArray($)?$[0]:$;if(Q)return Q;let X=Z.authorization,Y=Array.isArray(X)?X[0]:X;if(Y)return Y.replace(/^Bearer\s+/i,"");return}async function PY(){let Z=new Map,$=new Set,Q=new Set,X=new Set,Y=!1,K=new Map,H=0,J=new QQ.default({port:0,verifyClient:(F,M)=>{let O=_Y(F.req.headers);if(O&&X.has(O)){M(!1,401,"revoked credential");return}M(!0)}});await new Promise((F)=>J.once("listening",F));let G=J.address();if(typeof G==="string"||!G)throw Error("failed to bind test vault ws server");let W=`ws://127.0.0.1:${G.port}`,j=(F,M)=>{if(F.readyState===F.OPEN)F.send(JSON.stringify(M))},N=(F,M,O,P)=>{let R=Z.get(l$(F,M));if(!R)return;return{type:"secret",subscription_id:O,client_id:F,event:P,path:M,subscription:M,data:R.data,metadata:Y?{path:M,created_time:new Date().toISOString()}:{path:M,version:R.version,created_time:new Date().toISOString()}}};return J.on("connection",(F)=>{H+=1,Q.add(F),K.set(F,new Map),F.on("message",(M)=>{let O;try{O=JSON.parse(M.toString())}catch{return}let P=O.type,R=LY(O.token);if(P==="subscribe"){let x=String(O.subscription_id??""),V=Array.isArray(O.paths)?String(O.paths[0]):"";if(!R){j(F,{type:"error",subscription_id:x,error:"invalid token"});return}K.get(F)?.set(x,{clientId:R,path:V});let S=N(R,V,x,"subscribed");if(S)j(F,S);return}if(P==="unsubscribe"){K.get(F)?.delete(String(O.subscription_id??""));return}if(P==="command"){let x=String(O.id??""),V=String(O.path??"");if($.has(V))return;if(!R){j(F,{type:"command_result",id:x,ok:!1,error:"invalid token",code:"auth",status:403});return}if(O.command==="read_secret"){let S=Z.get(l$(R,V));if(!S){j(F,{type:"command_result",id:x,ok:!1,error:"not found",code:"not_found",status:404});return}j(F,{type:"command_result",id:x,ok:!0,result:{path:V,data:S.data,metadata:Y?{path:V,created_time:new Date().toISOString()}:{path:V,version:S.version,created_time:new Date().toISOString()}}});return}j(F,{type:"command_result",id:x,ok:!0,result:{}});return}}),F.on("close",()=>{Q.delete(F),K.delete(F)}),j(F,{type:"connected"})}),{url:W,connectionCount:()=>H,openCount:()=>Q.size,setSecret:(F,M,O)=>{let P=l$(F,M),R=(Z.get(P)?.version??0)+1;Z.set(P,{data:O,version:R})},pushUpdate:(F,M,O)=>{let P=l$(F,M),R=(Z.get(P)?.version??0)+1;Z.set(P,{data:O,version:R});for(let[x,V]of K)for(let[S,u]of V){if(u.clientId!==F||u.path!==M)continue;let b=N(F,M,S,"updated");if(b)j(x,b)}},dropSockets:()=>{for(let F of Array.from(Q))F.terminate()},ignore:$,setOmitVersions:(F)=>{Y=F},revokeToken:(F)=>{X.add(F)},close:()=>new Promise((F)=>{for(let P of J.clients)P.terminate();let M=!1,O=()=>{if(M)return;M=!0,F()};J.close(()=>O()),setTimeout(O,250)})}}function c5(Z){return new Promise(($)=>setTimeout($,Z))}async function w0(Z,$=5000,Q=50){let X=Date.now();while(Date.now()-X<$){if(Z())return!0;await c5(Q)}return Z()}async function g0(Z){let $=await PY();try{await Z($)}finally{l5(),d5(30000),await $.close()}}function d$(){return{tests:[{name:"multiple sources on one URL share a single socket and demux by subscription_id",fn:async()=>{await g0(async($)=>{$.setSecret("CLT-A","/shared",{who:"A"}),$.setSecret("CLT-B","/shared",{who:"B"});let Q=q0({websocket:{url:$.url,token:"tok-a",clientId:"CLT-A"}}),X=q0({websocket:{url:$.url,token:"tok-b",clientId:"CLT-B"}}),Y=[],K=[],H=await Q.subscribe("/shared",(j)=>Y.push(j.data)),J=await X.subscribe("/shared",(j)=>K.push(j.data)),G=u$();B(G.sockets,1,"exactly one socket per URL across both sources"),B(G.subscriptions,2,"two registered subscriptions on the shared socket"),B($.connectionCount(),1,"server accepted exactly one connection"),z(Y.some((j)=>j?.who==="A"),"client A receives its own tenant-scoped secret"),z(K.some((j)=>j?.who==="B"),"client B receives a different secret on the same path"),H(),J();let W=await w0(()=>u$().sockets===0&&$.openCount()===0);z(W,"socket idle-closes after the last subscription is removed")})}},{name:"per-frame auth rejects an invalid token (fail-fast subscribe throws)",fn:async()=>{await g0(async($)=>{let Q=q0({websocket:{url:$.url,token:"not-valid",clientId:"CLT-A"}}),X=!1;try{await Q.subscribe("/shared",()=>{},{mode:"fail-fast"})}catch{X=!0}z(X,"subscribe with an invalid per-frame token must reject")})}},{name:"live update is delivered and deduplicated by version",fn:async()=>{await g0(async($)=>{$.setSecret("CLT-SOLO","/live",{v:1});let Q=q0({websocket:{url:$.url,token:"tok-solo",clientId:"CLT-SOLO"}}),X=[],Y=await Q.subscribe("/live",(K)=>X.push(K.data.v));z(await w0(()=>X.includes(1)),"initial value delivered"),$.pushUpdate("CLT-SOLO","/live",{v:2}),z(await w0(()=>X.includes(2)),"updated value delivered"),Y()})}},{name:"reconnect replays active subscriptions after an unexpected socket drop",fn:async()=>{await g0(async($)=>{$.setSecret("CLT-SOLO","/recon",{v:1});let Q=q0({websocket:{url:$.url,token:"tok-solo",clientId:"CLT-SOLO"}}),X=[],Y=await Q.subscribe("/recon",(H)=>X.push(H.data.v));z(await w0(()=>X.includes(1)),"initial value delivered before drop"),$.setSecret("CLT-SOLO","/recon",{v:2}),$.dropSockets();let K=await w0(()=>X.includes(2),8000);z(K,"subscription replayed and newer secret delivered after reconnect"),Y()})}},{name:"a command with no reply rejects after the per-command timeout",fn:async()=>{await g0(async($)=>{d5(250),$.ignore.add("/never");let Q=q0({websocket:{url:$.url,token:"tok-solo",clientId:"CLT-SOLO"}}),X=!1;try{await Q.getFullSecret("/never")}catch{X=!0}z(X,"an unanswered websocket command must reject on timeout")})}},{name:"close() releases subscriptions and idle-closes the shared socket",fn:async()=>{await g0(async($)=>{$.setSecret("CLT-SOLO","/closing",{v:1});let Q=q0({websocket:{url:$.url,token:"tok-solo",clientId:"CLT-SOLO"}});await Q.subscribe("/closing",()=>{}),B(u$().sockets,1,"socket open while subscribed"),await Q.close?.();let X=await w0(()=>u$().sockets===0&&$.openCount()===0);z(X,"close() releases the subscription and the socket idle-closes")})}},{name:"version-less deliveries are never deduplicated (server omits metadata.version)",fn:async()=>{await g0(async($)=>{$.setOmitVersions(!0),$.setSecret("CLT-SOLO","/nov",{n:1});let Q=q0({websocket:{url:$.url,token:"tok-solo",clientId:"CLT-SOLO"}}),X=[],Y=await Q.subscribe("/nov",(H)=>X.push(H.data.n));z(await w0(()=>X.includes(1)),"initial version-less value delivered");let K=X.length;$.pushUpdate("CLT-SOLO","/nov",{n:2}),$.pushUpdate("CLT-SOLO","/nov",{n:2}),z(await w0(()=>X.length>=K+2),"both version-less updates must be delivered (no version → no dedup)"),z(X.slice(K,K+2).every((H)=>H===2),"the delivered version-less updates carry the pushed value"),Y()})}},{name:"a throwing onChange is contained; sibling subscriptions still deliver",fn:async()=>{await g0(async($)=>{$.setSecret("CLT-A","/boom",{v:1}),$.setSecret("CLT-B","/safe",{v:1});let Q=q0({websocket:{url:$.url,token:"tok-a",clientId:"CLT-A"}}),X=q0({websocket:{url:$.url,token:"tok-b",clientId:"CLT-B"}}),Y=[],K=await Q.subscribe("/boom",()=>{throw Error("intentional onChange failure")}),H=await X.subscribe("/safe",(J)=>Y.push(J.data.v));z(await w0(()=>Y.includes(1)),"sibling receives initial value despite the throwing peer"),$.pushUpdate("CLT-A","/boom",{v:2}),$.pushUpdate("CLT-B","/safe",{v:2}),z(await w0(()=>Y.includes(2)),"sibling still receives updates after the throwing subscription fired"),K(),H()})}},{name:"reconnect rotates to a second credential when the first is revoked",fn:async()=>{await g0(async($)=>{$.setSecret("CLT-A","/rot-a",{v:1}),$.setSecret("CLT-B","/rot-b",{v:1});let Q=q0({websocket:{url:$.url,token:"tok-a",clientId:"CLT-A"}}),X=q0({websocket:{url:$.url,token:"tok-b",clientId:"CLT-B"}}),Y=[],K=await Q.subscribe("/rot-a",()=>{}),H=await X.subscribe("/rot-b",(G)=>Y.push(G.data.v));z(await w0(()=>Y.includes(1)),"initial delivery before the credential is revoked"),$.revokeToken("tok-a"),$.setSecret("CLT-B","/rot-b",{v:2}),$.dropSockets();let J=await w0(()=>Y.includes(2),20000);z(J,"reconnect recovered via the second (still-valid) credential and replayed subscriptions"),K(),H()})}},{name:"wait-mode subscribe rejects when the socket is disposed before the first secret",fn:async()=>{await g0(async($)=>{let X=q0({websocket:{url:$.url,token:"tok-solo",clientId:"CLT-SOLO"}}).subscribe("/never-arrives",()=>{},{mode:"wait"}),Y=!1,K="";X.then(()=>{},(H)=>{Y=!0,K=H instanceof Error?H.message:String(H)}),await c5(200),l5();try{await X}catch(H){Y=!0,K=H instanceof Error?H.message:String(H)}z(Y,"wait-mode subscribe must reject when the socket is disposed (no hang)"),z(K.toLowerCase().includes("disposed")||K.toLowerCase().includes("interrupted"),`rejection should explain the socket teardown (got: ${K})`)})}}]}}async function xY(){let Z=performance.now(),{tests:$}=d$(),Q=[];for(let H of $)Q.push(await L(H.name,async()=>{await H.fn();return}));let X=performance.now()-Z,Y=Q.filter((H)=>!H.passed).length,K=Q.filter((H)=>H.details?.skipped).length;return{suite:"Vault websocket transport",passed:Y===0,tests:Q,duration:X,summary:{total:Q.length,passed:Q.length-Y-K,failed:Y,skipped:K}}}function gH(Z,$,Q){Z("Enterprise Control Validation",()=>{if(Q.capabilities)Z("Capabilities",()=>{let X=typeof Q.capabilities==="object"?{...Q.capabilities,baseUrl:Q.baseUrl}:{baseUrl:Q.baseUrl};j4(X).forEach(({name:K,fn:H})=>{$(K,H)})});if(Q.sso)Z("SSO",()=>{let X=typeof Q.sso==="object"?{...Q.sso,baseUrl:Q.baseUrl}:{baseUrl:Q.baseUrl},{tests:Y}=n8(X);Y.forEach(({name:K,fn:H})=>{$(K,H)})});if(Q.ciam)Z("CIAM",()=>{let X=typeof Q.ciam==="object"?{...Q.ciam,baseUrl:Q.baseUrl}:{baseUrl:Q.baseUrl},{tests:Y}=b8(X);Y.forEach(({name:K,fn:H})=>{$(K,H)})});if(Q.lfv!==!1)Z("LFV callbacks",()=>{f8().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.otel===!0)Z("OpenTelemetry",()=>{g8().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.protocolSurface!==!1)Z("Protocol surface",()=>{i8().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.harness!==!1)Z("Harness",()=>{P1().forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.vaultWebsocket!==!1)Z("Vault websocket transport",()=>{d$().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.secretsHardening!==!1)Z("Secrets hardening",()=>{_$().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.redisStores!==!1)Z("Redis stores",()=>{L$().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.tenantLifecycle!==!1)Z("Tenant lifecycle",()=>{q$().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.idTokenVerification!==!1)Z("SSO ID token verification",()=>{o8().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.performanceTuning!==!1)Z("Performance tuning",()=>{F$().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.validatorParity!==!1)Z("Validator parity",()=>{A$().tests.forEach(({name:X,fn:Y})=>{$(X,Y)})});if(Q.iam)Z("IAM",()=>{let{tests:X,ext:Y}=m8({...Q.iam,baseUrl:Q.baseUrl});X.forEach(({name:K,fn:H})=>{$(K,H)}),Z("Groups Outbound",()=>{Y.createGroupsOutboundTests().forEach(({name:K,fn:H})=>{$(K,H)})}),Z("Groups Inbound",()=>{Y.createGroupsInboundTests().forEach(({name:K,fn:H})=>{$(K,H)})}),Z("Delta (patch)",()=>{k8().tests.forEach(({name:K,fn:H})=>{$(K,H)})})});if(Q.workload)Z("Workload",()=>{let X=typeof Q.workload==="object"?{...Q.workload,baseUrl:Q.baseUrl}:{baseUrl:Q.baseUrl};r8(X).map(({name:K,fn:H})=>$(K,H))});if(Q.tenant)Z("Tenant",()=>{let X=typeof Q.tenant==="object"?{...Q.tenant,baseUrl:Q.baseUrl}:{baseUrl:Q.baseUrl};h4(X).map(({name:K,fn:H})=>$(K,H))});if(Q.authz)Z("Authz",()=>{y8().tests.forEach(({name:Y,fn:K})=>$(Y,K));let X=typeof Q.authz==="object"?Q.authz:{};if(X.ecv)Z("Workload scopes",()=>{$$(X).forEach(({name:Y,fn:K})=>$(Y,K))}),Z("OAA projection",()=>{M8(X).forEach(({name:Y,fn:K})=>$(Y,K))})})})}function pH(Z,$){let{describe:Q,it:X,beforeAll:Y,afterAll:K}=Z,H=()=>typeof $.ecv==="function"?$.ecv():$.ecv,J=null,G={ecv:$.ecv,baseUrl:()=>$.app.baseUrl,basePath:$.basePath,timeout:$.timeout,ion:$.ion,expectedCallbackErrorPattern:$.expectedCallbackErrorPattern},W=W$(G,{configRetry:$.configRetry}),j={};Q("Enterprise Control Validation",()=>{Y(async()=>{let D=H();J=await c$({...$.app,basePath:$.app.basePath??$.basePath,env:{...D.env,...$.app.env}});try{j.manifest=await q1(G)}catch(F){j.error=F}}),K(async()=>{await J?.stop(),J=null});let N=new Map;for(let D of W){let F=N.get(D.suite)??[];F.push(D),N.set(D.suite,F)}for(let[D,F]of N)Q(D,()=>{for(let M of F)X(M.name,async(O)=>{if(!M.alwaysRun){if(!j.manifest){let R=j.error instanceof Error?`: ${j.error.message}`:"";O.skip(`capabilitiez manifest unavailable${R}`);return}let P=H8(j.manifest,M.capability);if(P){O.skip(P);return}}await M.run(j.manifest)})})})}function uH(Z){console.log(`
|
|
11
|
+
=`.repeat(60)),console.log("Enterprise Control Validation Report"),console.log("=".repeat(60)),console.log(`Timestamp: ${Z.timestamp.toISOString()}`),console.log(`Base URL: ${Z.baseUrl}`),console.log(`Duration: ${Z.duration.toFixed(2)}ms`),console.log(`Overall: ${Z.passed?"✅ PASSED":"❌ FAILED"}`),console.log("");for(let $ of Z.suites){console.log(`
|
|
12
|
+
${$.suite} Suite ${$.passed?"✅":"❌"}`),console.log("-".repeat(40)),console.log(` Passed: ${$.summary.passed}/${$.summary.total}`+($.summary.skipped>0?` (${$.summary.skipped} skipped)`:"")),console.log(` Duration: ${$.duration.toFixed(2)}ms`);for(let Q of $.tests){let X=Q.details?.skipped?"⏭️ ":Q.passed?"✅":"❌";if(console.log(` ${X} ${Q.name}`),Q.error)console.log(` Error: ${Q.error}`)}}console.log(`
|
|
13
|
+
${"=".repeat(60)}
|
|
14
|
+
`)}
|
|
15
|
+
export{r as a,YQ as b,c$ as c,d1 as d,c1 as e,W6 as f,j6 as g,X8 as h,c as i,L as j,O8 as k,z as l,B as m,l0 as n,y8 as o,M8 as p,$$ as q,B6 as r,W4 as s,M1 as t,L6 as u,b0 as v,x6 as w,j4 as x,E6 as y,b8 as z,D4 as A,F4 as B,V4 as C,P1 as D,f6 as E,m8 as F,k8 as G,u6 as H,f8 as I,i6 as J,g8 as K,n6 as L,i8 as M,o6 as N,s6 as O,n8 as P,o8 as Q,Q3 as R,Y3 as S,r8 as T,q1 as U,H8 as V,W$ as W,G3 as X,F$ as Y,B3 as Z,L$ as _,P3 as $,_$ as aa,I3 as ba,C3 as ca,h4 as da,q$ as ea,b3 as fa,A$ as ga,d3 as ha,d$ as ia,xY as ja,gH as ka,pH as la,uH as ma};
|