candor-ts 0.4.5 → 0.4.6

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scan.mjs +20 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "candor-ts",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "candor for TypeScript — per-function side effects, transitively, with a policy gate (candor-spec 0.4)",
5
5
  "type": "module",
6
6
  "dependencies": {
package/scan.mjs CHANGED
@@ -305,6 +305,21 @@ function firstStringLiteral(node) {
305
305
  }
306
306
  return null;
307
307
  }
308
+ // Refine the Exec cliff (spec §4 ⟨0.5⟩): the effects a literal, statically-known subprocess head
309
+ // implies, matched by basename. ADDED to a caller that already carries Exec (a subprocess is still
310
+ // spawned — Exec is never dropped); an unrecognised head returns [] and keeps the bare cliff (never
311
+ // guess). A candor engine reads Fs/Env only — spec §7 item 12 (the analyzer self-boundary) guarantees
312
+ // it, so that case is spec-supplied. Only UNAMBIGUOUS single-effect tools belong here: a multi-modal
313
+ // head (git status local vs git push Net; rsync local vs remote; make/npm run project code) would
314
+ // fabricate the effect for its common case. The reference engines share this table verbatim.
315
+ function commandHeadEffects(cmd) {
316
+ const base = cmd.trim().split(/\s+/)[0].split(/[/\\]/).pop();
317
+ if (["curl", "wget", "http", "ssh", "scp"].includes(base)) return ["Net"];
318
+ if (["psql", "mysql", "sqlite3", "mongosh", "redis-cli"].includes(base)) return ["Db"];
319
+ if (["candor", "candor-run.sh", "candor-scan", "candor-query", "candor-java",
320
+ "candor-classify", "candor-report", "cargo-candor"].includes(base)) return ["Env", "Fs"];
321
+ return [];
322
+ }
308
323
  // host[:port] from an address/URL literal; non-address strings yield nothing (never fabricate).
309
324
  function hostLiteral(s) {
310
325
  const m = s.match(/^[a-z][a-z0-9+.-]*:\/\/([^/]+)/i); // scheme://host[:port]/…
@@ -683,7 +698,11 @@ function visitCalls(node) {
683
698
  }
684
699
  if (eff === "Exec") {
685
700
  const lit = firstStringLiteral(node);
686
- if (lit) rec.cmds.add(lit.trim().split(/\s+/)[0]); // the program of a command line
701
+ if (lit) {
702
+ rec.cmds.add(lit.trim().split(/\s+/)[0]); // the program of a command line
703
+ // a known literal head refines the cliff (curl→Net, candor→Fs/Env); Exec stays
704
+ for (const e of commandHeadEffects(lit)) rec.direct.add(e);
705
+ }
687
706
  }
688
707
  if (eff === "Fs") {
689
708
  const lit = firstStringLiteral(node);