@@ -66,6 +66,65 @@ function cloneBigIntStatWith(
6666}
6767
6868describe ( "embedded attempt session lock lifecycle" , ( ) => {
69+ it ( "recognizes an unchanged session file trusted by the previous prompt release" , async ( ) => {
70+ const sessionFile = await createTempSessionFile ( ) ;
71+ const options = { ...lockOptions , sessionFile } ;
72+ const first = await createEmbeddedAttemptSessionLockController ( {
73+ acquireSessionWriteLock,
74+ lockOptions : options ,
75+ } ) ;
76+
77+ expect ( await first . readTrustedCurrentSessionFileSnapshot ( ) ) . toBeUndefined ( ) ;
78+ await first . releaseForPrompt ( ) ;
79+ await first . dispose ( ) ;
80+
81+ const second = await createEmbeddedAttemptSessionLockController ( {
82+ acquireSessionWriteLock,
83+ lockOptions : options ,
84+ } ) ;
85+ expect ( await second . readTrustedCurrentSessionFileSnapshot ( ) ) . toBeDefined ( ) ;
86+ await second . dispose ( ) ;
87+ } ) ;
88+
89+ it ( "does not trust a session file changed after the previous prompt release" , async ( ) => {
90+ const sessionFile = await createTempSessionFile ( ) ;
91+ const options = { ...lockOptions , sessionFile } ;
92+ const first = await createEmbeddedAttemptSessionLockController ( {
93+ acquireSessionWriteLock,
94+ lockOptions : options ,
95+ } ) ;
96+ await first . releaseForPrompt ( ) ;
97+ await first . dispose ( ) ;
98+ await fs . appendFile ( sessionFile , '{"type":"message","id":"external"}\n' , "utf8" ) ;
99+
100+ const second = await createEmbeddedAttemptSessionLockController ( {
101+ acquireSessionWriteLock,
102+ lockOptions : options ,
103+ } ) ;
104+ expect ( await second . readTrustedCurrentSessionFileSnapshot ( ) ) . toBeUndefined ( ) ;
105+ await second . dispose ( ) ;
106+ } ) ;
107+
108+ it ( "publishes a known owned append for the next attempt" , async ( ) => {
109+ const sessionFile = await createTempSessionFile ( ) ;
110+ const options = { ...lockOptions , sessionFile } ;
111+ const first = await createEmbeddedAttemptSessionLockController ( {
112+ acquireSessionWriteLock,
113+ lockOptions : options ,
114+ } ) ;
115+ await fs . appendFile ( sessionFile , '{"type":"message","id":"owned"}\n' , "utf8" ) ;
116+ first . refreshAfterOwnedSessionWrite ( ) ;
117+ await first . releaseForPrompt ( ) ;
118+ await first . dispose ( ) ;
119+
120+ const second = await createEmbeddedAttemptSessionLockController ( {
121+ acquireSessionWriteLock,
122+ lockOptions : options ,
123+ } ) ;
124+ expect ( await second . readTrustedCurrentSessionFileSnapshot ( ) ) . toBeDefined ( ) ;
125+ await second . dispose ( ) ;
126+ } ) ;
127+
69128 it ( "serializes embedded attempts that share a session file owner" , async ( ) => {
70129 const sessionFile = await createTempSessionFile ( ) ;
71130 const firstOwner = await acquireEmbeddedAttemptSessionFileOwner ( { sessionFile } ) ;
@@ -1045,6 +1104,90 @@ describe("embedded attempt session lock lifecycle", () => {
10451104 expect ( release ) . toHaveBeenCalledTimes ( 3 ) ;
10461105 } ) ;
10471106
1107+ it ( "authorizes entry-cache advancement only under the exact trusted file lock" , async ( ) => {
1108+ const sessionFile = await createTempSessionFile ( ) ;
1109+ const release = vi . fn ( async ( ) => { } ) ;
1110+ const acquireSessionWriteLockLocal = vi . fn ( async ( ) => ( { release } ) ) ;
1111+ const controller = await createEmbeddedAttemptSessionLockController ( {
1112+ acquireSessionWriteLock : acquireSessionWriteLockLocal ,
1113+ lockOptions : { ...lockOptions , sessionFile } ,
1114+ } ) ;
1115+
1116+ await controller . releaseForPrompt ( ) ;
1117+ const stat = await fs . stat ( sessionFile , { bigint : true } ) ;
1118+ const snapshot = {
1119+ dev : stat . dev ,
1120+ ino : stat . ino ,
1121+ size : stat . size ,
1122+ mtimeNs : stat . mtimeNs ,
1123+ ctimeNs : stat . ctimeNs ,
1124+ } ;
1125+
1126+ expect ( controller . canAdvanceSessionEntryCache ( snapshot ) ) . toBe ( false ) ;
1127+ await expect (
1128+ controller . withSessionWriteLock ( ( ) => {
1129+ expect ( controller . canAdvanceSessionEntryCache ( snapshot ) ) . toBe ( true ) ;
1130+ return "locked" ;
1131+ } ) ,
1132+ ) . resolves . toBe ( "locked" ) ;
1133+ expect ( controller . canAdvanceSessionEntryCache ( snapshot ) ) . toBe ( false ) ;
1134+ } ) ;
1135+
1136+ it ( "publishes an exact owned snapshot only while the write lock is active" , async ( ) => {
1137+ const sessionFile = await createTempSessionFile ( ) ;
1138+ const controller = await createEmbeddedAttemptSessionLockController ( {
1139+ acquireSessionWriteLock : vi . fn ( async ( ) => ( {
1140+ release : vi . fn ( async ( ) => { } ) ,
1141+ } ) ) ,
1142+ lockOptions : { ...lockOptions , sessionFile } ,
1143+ } ) ;
1144+ const readSnapshot = async ( ) => {
1145+ const stat = await fs . stat ( sessionFile , { bigint : true } ) ;
1146+ return {
1147+ dev : stat . dev ,
1148+ ino : stat . ino ,
1149+ size : stat . size ,
1150+ mtimeNs : stat . mtimeNs ,
1151+ ctimeNs : stat . ctimeNs ,
1152+ } ;
1153+ } ;
1154+ const initialSnapshot = await readSnapshot ( ) ;
1155+
1156+ expect ( controller . publishOwnedSessionFileSnapshot ( initialSnapshot ) ) . toBe ( false ) ;
1157+ await controller . withSessionWriteLock ( async ( ) => {
1158+ expect ( controller . publishOwnedSessionFileSnapshot ( initialSnapshot ) ) . toBe ( true ) ;
1159+ await fs . appendFile ( sessionFile , '{"type":"message","id":"owned"}\n' , "utf8" ) ;
1160+ expect ( controller . publishOwnedSessionFileSnapshot ( initialSnapshot ) ) . toBe ( false ) ;
1161+ expect ( controller . publishOwnedSessionFileSnapshot ( await readSnapshot ( ) ) ) . toBe ( true ) ;
1162+ } ) ;
1163+ expect ( controller . publishOwnedSessionFileSnapshot ( await readSnapshot ( ) ) ) . toBe ( false ) ;
1164+ await controller . dispose ( ) ;
1165+ } ) ;
1166+
1167+ it ( "publishes only an unchanged repair-validated snapshot while retaining the lock" , async ( ) => {
1168+ const sessionFile = await createTempSessionFile ( ) ;
1169+ const acquireSessionWriteLockLocal = vi . fn ( async ( ) => ( {
1170+ release : vi . fn ( async ( ) => { } ) ,
1171+ } ) ) ;
1172+ const controller = await createEmbeddedAttemptSessionLockController ( {
1173+ acquireSessionWriteLock : acquireSessionWriteLockLocal ,
1174+ lockOptions : { ...lockOptions , sessionFile } ,
1175+ } ) ;
1176+ const stat = await fs . stat ( sessionFile , { bigint : true } ) ;
1177+ const snapshot = {
1178+ dev : stat . dev ,
1179+ ino : stat . ino ,
1180+ size : stat . size ,
1181+ mtimeNs : stat . mtimeNs ,
1182+ ctimeNs : stat . ctimeNs ,
1183+ } ;
1184+
1185+ expect ( controller . publishValidatedSessionFileSnapshot ( snapshot ) ) . toBe ( true ) ;
1186+ await fs . appendFile ( sessionFile , '{"type":"message","id":"external"}\n' , "utf8" ) ;
1187+ expect ( controller . publishValidatedSessionFileSnapshot ( snapshot ) ) . toBe ( false ) ;
1188+ await controller . dispose ( ) ;
1189+ } ) ;
1190+
10481191 it ( "refreshes the prompt fence after an owned session manager append" , async ( ) => {
10491192 const sessionFile = await createTempSessionFile ( ) ;
10501193 const release = vi . fn ( async ( ) => { } ) ;
0 commit comments