@milpa/comal 0.2.0 → 0.2.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/README.md CHANGED
@@ -28,7 +28,7 @@ comal --help
28
28
 
29
29
  Keep the PATH line in your shell startup file if you use that prefix. If your Node manager already provides a user prefix, you can omit `--prefix "$HOME/.local"`. The package registers the `comal` executable; you do not need to create a shell alias.
30
30
 
31
- You can pin a version with `npx @milpa/comal@0.2.0` or `npm install --global @milpa/comal@0.2.0`. The local release manifest preserves SHA-256 and SHA-512 integrity is checked against npm at publish time. No independent publisher signature is claimed.
31
+ You can pin a version with `npx @milpa/comal@0.2.1` or `npm install --global @milpa/comal@0.2.1`. The local release manifest preserves SHA-256 and SHA-512 integrity is checked against npm at publish time. No independent publisher signature is claimed.
32
32
 
33
33
  ## Test without account or model
34
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milpa/comal",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Comal: governed operations, execution receipts, and programming assistance in any language",
5
5
  "type": "module",
6
6
  "private": false,
@@ -5,21 +5,27 @@ import path from 'node:path';
5
5
 
6
6
  const fail=message=>Object.assign(new Error(message),{code:'PRIVATE_STATE_UNSAFE'});
7
7
  let broker,starting,sequence=0;
8
+ // PowerShell puede tardar en arrancar (antivirus, runners en frío): su arranque tiene su propio plazo.
9
+ const STARTUP_MS=60000,REQUEST_MS=15000;
8
10
  async function windowsBroker(){
9
11
  if(broker)return broker;if(starting)return starting;
10
12
  starting=(async()=>{
11
13
  const script=await readFile(new URL('./windows-private.ps1',import.meta.url),'utf8'),program=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
12
14
  const child=spawn(program,['-NoLogo','-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-EncodedCommand',Buffer.from(script,'utf16le').toString('base64')],{stdio:['pipe','pipe','pipe'],windowsHide:true});
13
- const pending=new Map(),lines=createInterface({input:child.stdout});let diagnostic='';
15
+ const pending=new Map(),lines=createInterface({input:child.stdout});let diagnostic='',ready;
16
+ const readiness=new Promise((resolve,reject)=>{ready={resolve,reject};});
17
+ const startup=setTimeout(()=>{ready.reject(fail('Windows ACL helper did not start within its deadline. '+diagnostic.slice(0,300)));child.kill();},STARTUP_MS);
14
18
  const references=active=>{const method=active?'ref':'unref';child[method]();for(const stream of[child.stdin,child.stdout,child.stderr])stream[method]?.();};
15
19
  child.stderr.on('data',data=>{if(diagnostic.length<2000)diagnostic+=data;});child.stdin.on('error',()=>{});
16
- const stopped=()=>{for(const job of pending.values()){clearTimeout(job.timer);job.reject(fail('Windows private-state inspection stopped. '+diagnostic.slice(0,300)));}pending.clear();if(broker?.child===child)broker=undefined;};
20
+ const stopped=()=>{clearTimeout(startup);ready.reject(fail('Windows private-state inspection stopped. '+diagnostic.slice(0,300)));for(const job of pending.values()){clearTimeout(job.timer);job.reject(fail('Windows private-state inspection stopped. '+diagnostic.slice(0,300)));}pending.clear();if(broker?.child===child)broker=undefined;};
17
21
  child.on('error',stopped);child.on('close',stopped);
18
- lines.on('line',line=>{let result;try{result=JSON.parse(line);}catch{stopped();child.kill();return;}const job=pending.get(result.id);if(!job)return;clearTimeout(job.timer);pending.delete(result.id);if(!pending.size)references(false);result.ok?job.resolve():job.reject(fail(result.error||'Windows private-state ACL does not verify.'));});
22
+ lines.on('line',line=>{let result;try{result=JSON.parse(line);}catch{stopped();child.kill();return;}if(result?.ready===true){clearTimeout(startup);ready.resolve();return;}const job=pending.get(result.id);if(!job)return;clearTimeout(job.timer);pending.delete(result.id);if(!pending.size)references(false);result.ok?job.resolve():job.reject(fail(result.error||'Windows private-state ACL does not verify.'));});
19
23
  const result={child,request:(action,file)=>new Promise((resolve,reject)=>{
20
- const id=++sequence;references(true);const timer=setTimeout(()=>{pending.delete(id);reject(fail('Windows ACL inspection exceeded its deadline.'));if(!pending.size)references(false);},15000);pending.set(id,{resolve,reject,timer});
24
+ const id=++sequence;references(true);const timer=setTimeout(()=>{pending.delete(id);reject(fail('Windows ACL inspection exceeded its deadline.'));if(!pending.size)references(false);},REQUEST_MS);pending.set(id,{resolve,reject,timer});
21
25
  child.stdin.write(JSON.stringify({id,action,path:path.resolve(file)})+'\n',error=>{if(error){clearTimeout(timer);pending.delete(id);reject(fail('Windows ACL inspection could not be sent.'));if(!pending.size)references(false);}});
22
- })};references(false);broker=result;return result;
26
+ })};
27
+ try{await readiness;}catch(error){if(broker?.child===child)broker=undefined;throw error;}
28
+ references(false);broker=result;return result;
23
29
  })();try{return await starting;}finally{starting=undefined;}
24
30
  }
25
31
  export async function ensurePrivateDirectory(directory){
@@ -22,8 +22,11 @@ export class SourceOwnership {
22
22
  }
23
23
  contains(file){const r=path.relative(this.directory,file);return r===''||r!=='..'&&!r.startsWith('..'+path.sep)&&!path.isAbsolute(r);}
24
24
  async prepareDirectory(){
25
+ await mkdir(path.dirname(this.directory),{recursive:true,mode:0o700});
26
+ // El HOME puede llegar por un enlace del sistema (macOS: /tmp, /var → /private/…).
27
+ // Se resuelve el antecesor del registro; el padre (~/.comal) sigue sin poder ser enlace.
28
+ if(!this.canonical){const grand=await realpath(path.dirname(path.dirname(this.directory)));this.directory=path.join(grand,path.basename(path.dirname(this.directory)),path.basename(this.directory));this.canonical=true;}
25
29
  const parent=path.dirname(this.directory);
26
- await mkdir(parent,{recursive:true,mode:0o700});
27
30
  if(await realpath(parent)!==parent)throw fail('Native source lock registry has a noncanonical parent.');
28
31
  try{await ensurePrivateDirectory(this.directory);}catch(error){if(error.code==='PRIVATE_STATE_UNSAFE')throw fail('Native source lock registry is not private.');throw error;}
29
32
  const info=await lstat(this.directory);
@@ -3,6 +3,8 @@ $ErrorActionPreference = 'Stop'
3
3
  [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
4
4
  $currentSid = [Security.Principal.WindowsIdentity]::GetCurrent().User
5
5
  $trusted = @($currentSid.Value, 'S-1-5-18', 'S-1-5-32-544')
6
+ [Console]::Out.WriteLine('{"ready":true}')
7
+ [Console]::Out.Flush()
6
8
 
7
9
  function Assert-Private([string] $path, [bool] $directory) {
8
10
  $attributes = [IO.File]::GetAttributes($path)