1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one
3+ * or more contributor license agreements. See the NOTICE file
4+ * distributed with this work for additional information
5+ * regarding copyright ownership. The ASF licenses this file
6+ * to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance
8+ * with the License. You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing,
13+ * software distributed under the License is distributed on an
14+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+ * KIND, either express or implied. See the License for the
16+ * specific language governing permissions and limitations
17+ * under the License.
18+ */
19+
120import assert from 'node:assert/strict' ;
221import {
22+ copyFile ,
323 mkdir ,
424 mkdtemp ,
525 readFile ,
@@ -31,8 +51,8 @@ afterEach(async () => {
3151
3252test ( 'prepares state and workspace under one quiescence boundary, then releases live writers' , async ( ) => {
3353 const fixture = await createFixture ( ) ;
34- let liveState = 'state-at-boundary' ;
35- let liveWorkspace = 'workspace-at-boundary' ;
54+ await writeFile ( join ( fixture . liveStateRoot , 'runtime.sqlite' ) , 'state-at-boundary' , 'utf8' ) ;
55+ await writeFile ( join ( fixture . liveWorkspaceRoot , 'main.ts' ) , 'workspace-at-boundary' , 'utf8' ) ;
3656 const events : string [ ] = [ ] ;
3757 let quiescent = false ;
3858
@@ -57,7 +77,10 @@ test('prepares state and workspace under one quiescence boundary, then releases
5777 assert . equal ( quiescent , true ) ;
5878 events . push ( 'state' ) ;
5979 await mkdir ( input . destinationRoot ) ;
60- await writeFile ( join ( input . destinationRoot , 'runtime.sqlite' ) , liveState , 'utf8' ) ;
80+ await copyFile (
81+ join ( fixture . liveStateRoot , 'runtime.sqlite' ) ,
82+ join ( input . destinationRoot , 'runtime.sqlite' ) ,
83+ ) ;
6184 return {
6285 mediaType : 'application/vnd.maka.session-state-identity+json;version=1' ,
6386 bytes : Buffer . from ( '{"makaSessionId":"session-1"}' , 'utf8' ) ,
@@ -70,7 +93,10 @@ test('prepares state and workspace under one quiescence boundary, then releases
7093 assert . equal ( input . policy , SESSION_SNAPSHOT_WORKSPACE_POLICY_V1 ) ;
7194 events . push ( 'workspace' ) ;
7295 await mkdir ( input . destinationRoot ) ;
73- await writeFile ( join ( input . destinationRoot , 'main.ts' ) , liveWorkspace , 'utf8' ) ;
96+ await copyFile (
97+ join ( fixture . liveWorkspaceRoot , 'main.ts' ) ,
98+ join ( input . destinationRoot , 'main.ts' ) ,
99+ ) ;
74100 return workspaceResult ( { includedEntries : 1 } ) ;
75101 } ,
76102 } ,
@@ -82,15 +108,15 @@ test('prepares state and workspace under one quiescence boundary, then releases
82108 assert . notEqual ( handle . snapshot . workspaceRoot , fixture . liveWorkspaceRoot ) ;
83109 assert . equal (
84110 await readFile ( join ( handle . snapshot . stateRoot , 'runtime.sqlite' ) , 'utf8' ) ,
85- liveState ,
111+ 'state-at-boundary' ,
86112 ) ;
87113 assert . equal (
88114 await readFile ( join ( handle . snapshot . workspaceRoot , 'main.ts' ) , 'utf8' ) ,
89- liveWorkspace ,
115+ 'workspace-at-boundary' ,
90116 ) ;
91117
92- liveState = ' later-state';
93- liveWorkspace = ' later-workspace';
118+ await writeFile ( join ( fixture . liveStateRoot , 'runtime.sqlite' ) , ' later-state', 'utf8' ) ;
119+ await writeFile ( join ( fixture . liveWorkspaceRoot , 'main.ts' ) , ' later-workspace', 'utf8' ) ;
94120 assert . equal (
95121 await readFile ( join ( handle . snapshot . stateRoot , 'runtime.sqlite' ) , 'utf8' ) ,
96122 'state-at-boundary' ,
@@ -308,6 +334,41 @@ test('cancellation and an expired deadline stop before staging begins', async ()
308334 assert . deepEqual ( await readdir ( fixture . stagingParent ) , [ ] ) ;
309335} ) ;
310336
337+ test ( 'a deadline beyond the Node timer limit is rescheduled until the absolute time' , async ( t ) => {
338+ t . mock . timers . enable ( { apis : [ 'Date' , 'setTimeout' ] , now : 0 } ) ;
339+ const fixture = await createFixture ( ) ;
340+ const authorityEntered = deferred < void > ( ) ;
341+ let cancellationSignal : AbortSignal | undefined ;
342+ const coordinator = createFileQuiescentSessionSnapshotCoordinator ( {
343+ stagingParent : fixture . stagingParent ,
344+ privateStagingRootAuthority,
345+ now : Date . now ,
346+ quiescence : {
347+ async runQuiescent ( input ) {
348+ cancellationSignal = input . cancellation . signal ;
349+ authorityEntered . resolve ( ) ;
350+ await waitForAbort ( input . cancellation ) ;
351+ throw Object . assign ( new Error ( 'aborted' ) , { name : 'AbortError' } ) ;
352+ } ,
353+ } ,
354+ state : directoryStatePreparer ,
355+ workspace : directoryWorkspacePreparer ,
356+ } ) ;
357+ const timerLimit = 2_147_483_647 ;
358+ const preparation = coordinator . prepare ( {
359+ makaSessionId : 'long-deadline' ,
360+ deadlineAt : timerLimit + 1_000 ,
361+ } ) ;
362+ await authorityEntered . promise ;
363+
364+ t . mock . timers . tick ( timerLimit ) ;
365+ assert . equal ( cancellationSignal ?. aborted , false ) ;
366+ t . mock . timers . tick ( 999 ) ;
367+ assert . equal ( cancellationSignal ?. aborted , false ) ;
368+ t . mock . timers . tick ( 1 ) ;
369+ await assert . rejects ( preparation , isSnapshotError ( 'snapshot_cancelled' ) ) ;
370+ } ) ;
371+
311372test ( 'cancellation while waiting for quiescence is propagated as snapshot_cancelled' , async ( ) => {
312373 const fixture = await createFixture ( ) ;
313374 const authorityEntered = deferred < void > ( ) ;
@@ -547,12 +608,21 @@ test('V1 workspace policy includes portable inputs, excludes rebuildable data, a
547608 [ '.turbo/cache.bin' , 'file' , { kind : 'exclude' , category : 'cache' } ] ,
548609 [ 'logs/agent.txt' , 'file' , { kind : 'exclude' , category : 'log' } ] ,
549610 [ 'debug.log' , 'file' , { kind : 'exclude' , category : 'log' } ] ,
550- [ 'secrets.log' , 'file' , { kind : 'exclude' , category : 'log' } ] ,
611+ [ 'secrets.log' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
612+ [ '.env.log' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
613+ [ 'keys/private-key.log' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
551614 [ '.maka-runtime/input.json' , 'file' , { kind : 'exclude' , category : 'runtime_scratch' } ] ,
552615 [ '.env.local' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
616+ [ '.env.example' , 'file' , { kind : 'include' } ] ,
617+ [ '.env.template' , 'file' , { kind : 'include' } ] ,
618+ [ '.env.example.local' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
553619 [ 'keys/id_ed25519' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
620+ [ 'keys/id_ed25519.pub' , 'file' , { kind : 'include' } ] ,
621+ [ 'keys/id_rsa.pub' , 'file' , { kind : 'include' } ] ,
554622 [ 'credentials.yaml' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
555623 [ 'secrets.json' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
624+ [ 'src/secrets.ts' , 'file' , { kind : 'include' } ] ,
625+ [ 'docs/secrets.md' , 'file' , { kind : 'include' } ] ,
556626 [ '.terraformrc' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
557627 [ '.git-credentials.lock' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
558628 [ 'keys/client-private-key.pem' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
@@ -562,7 +632,13 @@ test('V1 workspace policy includes portable inputs, excludes rebuildable data, a
562632 [ 'certs/client.csr' , 'file' , { kind : 'include' } ] ,
563633 [ 'certs/client.pem' , 'file' , { kind : 'include' } ] ,
564634 [ 'certs/client.der' , 'file' , { kind : 'include' } ] ,
635+ [ 'privkey.pem' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
636+ [ 'private.pem' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
565637 [ 'keys/service-account.json' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
638+ [ 'secrets' , 'directory' , { kind : 'reject' , category : 'known_secret_file' } ] ,
639+ [ 'secrets/token' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
640+ [ 'credentials/oauth.json' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
641+ [ 'private/token' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
566642 [ '.ssh/config' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
567643 [ '.aws/credentials' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
568644 [ '.cargo/credentials' , 'file' , { kind : 'reject' , category : 'known_secret_file' } ] ,
@@ -575,6 +651,11 @@ test('V1 workspace policy includes portable inputs, excludes rebuildable data, a
575651 ] ,
576652 [ '../escape' , 'file' , { kind : 'reject' , category : 'unsafe_path' } ] ,
577653 [ 'a\\b' , 'file' , { kind : 'reject' , category : 'unsafe_path' } ] ,
654+ [ 'CON' , 'file' , { kind : 'reject' , category : 'unsafe_path' } ] ,
655+ [ 'nested/LPT1.txt' , 'file' , { kind : 'reject' , category : 'unsafe_path' } ] ,
656+ [ 'foo:bar' , 'file' , { kind : 'reject' , category : 'unsafe_path' } ] ,
657+ [ 'name.' , 'file' , { kind : 'reject' , category : 'unsafe_path' } ] ,
658+ [ 'name ' , 'directory' , { kind : 'reject' , category : 'unsafe_path' } ] ,
578659 ] as const ;
579660
580661 for ( const [ relativePath , kind , expected ] of cases ) {
0 commit comments