candor-ts 0.40.0 → 0.40.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/policy.mjs +8 -0
  3. package/scan.mjs +961 -88
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "candor-ts",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "mcpName": "io.github.tombaldwin/candor",
5
5
  "description": "candor for TypeScript — per-function side effects, transitively, with a policy gate (candor-spec 0.40)",
6
6
  "type": "module",
package/policy.mjs CHANGED
@@ -1296,11 +1296,19 @@ export function evaluatePolicy(pol, functions, callgraph, incomplete = new Map()
1296
1296
  // a rule whose destinations all resolve while its `from` names nothing has bound nothing, and that is
1297
1297
  // exactly the typo that leaves an operator believing a leaf is protected.
1298
1298
  for (const r of pol.only ?? []) zeroCount.set(r.raw, 0);
1299
+ // ⟨SOUNDNESS R952⟩ …and a SCOPED `allow`. It was never enrolled, so `allow Net in nosuch.fn ok.example` — a
1300
+ // typo'd scope — bound nothing, said nothing, and exited 0: a gate that cannot fail, exactly the `deny` typo
1301
+ // this pass exists for, one rule kind over. A scoped allow makes a promise about the functions its scope names;
1302
+ // naming none is the same unanswerable rule. A SCOPELESS allow applies to every function and stays exempt, as a
1303
+ // scopeless `deny` does. Disclosure only: the line and `zeroMatch` entry `deny` already gets, exit unchanged
1304
+ // (candor-rust's half, same shape).
1305
+ for (const r of pol.allow ?? []) if (r.scope) zeroCount.set(r.raw, 0);
1299
1306
  if (zeroCount.size) {
1300
1307
  const names = new Set(functions.map((f) => f.fn));
1301
1308
  for (const k of Object.keys(callgraph ?? {})) names.add(k);
1302
1309
  for (const n of names) {
1303
1310
  for (const r of pol.deny) if (r.scope && scopeMatches(n, r.scope)) zeroCount.set(r.raw, zeroCount.get(r.raw) + 1);
1311
+ for (const r of pol.allow ?? []) if (r.scope && scopeMatches(n, r.scope)) zeroCount.set(r.raw, zeroCount.get(r.raw) + 1);
1304
1312
  for (const r of pol.forbid) {
1305
1313
  if (scopeMatches(n, r.from) || scopeMatches(n, r.to)) zeroCount.set(r.raw, zeroCount.get(r.raw) + 1);
1306
1314
  }