candor-ts 0.26.0 → 0.28.0

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/scan-core.mjs CHANGED
@@ -215,6 +215,45 @@ export const MODEL_SDK_RE =
215
215
  export function isModelSdkPackage(moduleName) {
216
216
  return MODEL_SDK_RE.test(moduleName);
217
217
  }
218
+ /// SPEC §2 `fs` — for a call ALREADY classified `Fs`, the read/write direction its verb implies.
219
+ /// Returns ["read"], ["write"], ["read","write"], or [] when the verb does not say.
220
+ ///
221
+ /// THE EMPTY CASE IS THE POINT. §2: "when `Fs` is reached but its kind is unknown … the field MUST be
222
+ /// omitted rather than guessed. An empty or partial `fs` would be read as a positive claim ('reads but
223
+ /// never writes'), which is the §4 trust contract's forbidden direction." So an unrecognised verb
224
+ /// contributes nothing and the field stays absent — absence means "kind undetermined", never "read-only".
225
+ ///
226
+ /// A syntactic refinement of an effect candor already proved, NOT a soundness claim: a wrong direction
227
+ /// misreports a detail, a wrong EFFECT is the cardinal sin, and those are different failures. Deliberately
228
+ /// the same vocabulary and shape as candor-java's `fsKind` and candor-swift's — the surface is spec'd
229
+ /// four-way, and three engines inventing three verb tables for one field is how a shared field stops
230
+ /// meaning one thing. Node's sync/promise variants are handled by stripping the `Sync` suffix rather than
231
+ /// by listing every pair.
232
+ export function fsKind(moduleName, member) {
233
+ if (!member) return [];
234
+ const m = member.endsWith("Sync") ? member.slice(0, -4) : member;
235
+ // Reads the source AND writes the destination, in one call.
236
+ if (m === "copyFile" || m === "cp") return ["read", "write"];
237
+ const WRITE = new Set([
238
+ "writeFile", "appendFile", "write", "writev", "mkdir", "mkdtemp", "rmdir", "rm", "unlink",
239
+ "rename", "truncate", "ftruncate", "chmod", "fchmod", "lchmod", "chown", "fchown", "lchown",
240
+ "utimes", "futimes", "lutimes", "symlink", "link", "createWriteStream", "outputFile", "ensureDir",
241
+ "ensureFile", "emptyDir", "remove", "move", "outputJson", "writeJson", "writeJSON",
242
+ ]);
243
+ const READ = new Set([
244
+ "readFile", "readdir", "read", "readv", "stat", "lstat", "fstat", "statfs", "access", "exists",
245
+ "realpath", "readlink", "createReadStream", "opendir", "watch", "watchFile", "readJson",
246
+ "readJSON", "pathExists", "lstatSync",
247
+ ]);
248
+ if (WRITE.has(m)) return ["write"];
249
+ if (READ.has(m)) return ["read"];
250
+ // `open`/`openSync` take a MODE — "r", "w", "a" — so the verb alone does not say. Deliberately no claim
251
+ // rather than a guess at the common case.
252
+ if (m.startsWith("write") || m.startsWith("append")) return ["write"];
253
+ if (m.startsWith("read")) return ["read"];
254
+ return [];
255
+ }
256
+
218
257
  export function kappa(moduleName, member) {
219
258
  for (const [mre, vre, eff] of KAPPA_RULES) {
220
259
  if (mre.test(moduleName) && (!vre || vre.test(member))) return eff;