candor-ts 0.8.12 → 0.8.13

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/query-core.mjs +9 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "candor-ts",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
4
4
  "description": "candor for TypeScript — per-function side effects, transitively, with a policy gate (candor-spec 0.8)",
5
5
  "type": "module",
6
6
  "dependencies": {
package/query-core.mjs CHANGED
@@ -540,14 +540,21 @@ function computeRemedy(start, eff, layer, cg, rev, byName, policyParsed, scopeMa
540
540
  // higher hoist options: allowed-layer transitive callers of the minimal frontier that also route the
541
541
  // effect — hoisting higher keeps the frontier pure too, at the cost of threading through more signatures
542
542
  // (FIX-SPEC: the trade-off, disclosed not hidden).
543
+ // The SANDWICHED-layer check (/code-review): a hoist is CLEAN only if no forbidden fn sits ABOVE the
544
+ // frontier. If a denied fn calls into a hoist target, hoisting the effect there leaves that caller
545
+ // violating. Detected in the same climb that gathers `hoistHigher` (the allowed ancestors).
543
546
  const hoistHigher = new Set();
547
+ let sandwiched = false;
544
548
  const hseen = new Set(hoistTo);
545
549
  const hq = [...hoistTo];
546
550
  while (hq.length) {
547
551
  const cur = hq.shift();
548
552
  for (const caller of rev.get(cur) ?? []) {
549
553
  const ce = byName.get(caller);
550
- if (ce && (ce.inferred ?? []).includes(eff) && deniedLayer(caller, eff, policyParsed, scopeMatches) === null && !hseen.has(caller)) {
554
+ if (!ce || !(ce.inferred ?? []).includes(eff)) continue;
555
+ if (deniedLayer(caller, eff, policyParsed, scopeMatches) !== null) {
556
+ sandwiched = true;
557
+ } else if (!hseen.has(caller)) {
551
558
  hseen.add(caller);
552
559
  hoistHigher.add(caller);
553
560
  hq.push(caller);
@@ -556,7 +563,7 @@ function computeRemedy(start, eff, layer, cg, rev, byName, policyParsed, scopeMa
556
563
  }
557
564
  return {
558
565
  fn: start, effect: eff, layer,
559
- cleanHoist: hoistTo.size > 0,
566
+ cleanHoist: hoistTo.size > 0 && !sandwiched,
560
567
  site: [...sites].sort(),
561
568
  deniedSpan: [...deniedSpan].sort(),
562
569
  hoistTo: [...hoistTo].sort(),