candor-ts 0.25.0 → 0.26.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/AGENTS.md +1 -1
- package/README.md +2 -2
- package/package.json +2 -2
- package/query-core.mjs +29 -5
- package/query.mjs +1 -1
- package/scan.mjs +15 -5
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ chains by hand.
|
|
|
12
12
|
> **Already installed? Report the version and ask before upgrading — before you scan.** If this
|
|
13
13
|
> project already has candor (a `.candor/` report directory, or `candor-ts` reachable via `npx`/a
|
|
14
14
|
> global install), do this *first*: run `npx -y candor-ts --version` (offline) and **tell the user
|
|
15
|
-
> plainly which version they're on** — e.g. *"This project is on candor-ts `<version>` (spec 0.
|
|
15
|
+
> plainly which version they're on** — e.g. *"This project is on candor-ts `<version>` (spec 0.26)."*
|
|
16
16
|
> On a build too old for the flag, read `candor.version` / `candor.spec` from an existing
|
|
17
17
|
> `.candor/report*.json`, or `npm ls -g candor-ts`.
|
|
18
18
|
>
|
package/README.md
CHANGED
|
@@ -192,7 +192,7 @@ pure-vs-Unknown ruling (PART 16) — the engines must answer identically, on eve
|
|
|
192
192
|
| A call resolving to a *type* (function-typed field/param) → `Unknown`, never silent-pure | SPEC §4 |
|
|
193
193
|
| Unmatched external calls contribute nothing (curated-classifier caveat) | SEMANTICS §8 C1 |
|
|
194
194
|
| The literal surfaces `hosts`/`cmds`/`paths`/`tables`, literal-read only | SPEC §2 |
|
|
195
|
-
| `{ candor: { version, toolchain, spec: "0.
|
|
195
|
+
| `{ candor: { version, toolchain, spec: "0.26" }, functions }` envelope; pure fns omitted | SPEC §2/§2.1 |
|
|
196
196
|
| Call-graph sidecar with **every** analyzed function a key | SPEC §2.2 |
|
|
197
197
|
| The gate: AS-EFF-006 / 008 / 009, loud on an unreadable policy | SPEC §6.2 |
|
|
198
198
|
|
|
@@ -210,7 +210,7 @@ read the Rust source".
|
|
|
210
210
|
|
|
211
211
|
## Status
|
|
212
212
|
|
|
213
|
-
0.19.x, speaking candor-spec 0.
|
|
213
|
+
0.19.x, speaking candor-spec 0.26: the analysis core, the gate (`--policy` / `--gate-json` /
|
|
214
214
|
`.candor/config`), the full §3.1 query surface (including `containment`, `blindspots`, the
|
|
215
215
|
`--include-unknown` dispatch frontier, and ⟨0.24⟩ `gate --report` — the gate applied to an EXISTING
|
|
216
216
|
report, byte-equivalent to `scan --policy`'s verdict), the MCP server, the LSP server, and the watch loop are
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "candor-ts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "candor for TypeScript \u2014 per-function side effects, transitively, with a policy gate (candor-spec 0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
|
+
"description": "candor for TypeScript \u2014 per-function side effects, transitively, with a policy gate (candor-spec 0.26)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@types/node": "^25.9.2",
|
package/query-core.mjs
CHANGED
|
@@ -696,13 +696,37 @@ export const byCodePoint = (a, b) => {
|
|
|
696
696
|
};
|
|
697
697
|
|
|
698
698
|
// Reflexive+transitive subtype test over the hierarchy sidecar.
|
|
699
|
-
|
|
700
|
-
|
|
699
|
+
//
|
|
700
|
+
// ⟨0.26⟩ THREE-VALUED, because the format now distinguishes what it could not before. SPEC §2.2 makes the
|
|
701
|
+
// KEY SET the manifest: a producer emits a key for every type it indexed, `[]` included, so a type with NO
|
|
702
|
+
// key is one the pass never looked at. `hierarchy[t] ?? []` read those two cases alike and answered
|
|
703
|
+
// `false` — a positive claim about a type nobody analysed.
|
|
704
|
+
//
|
|
705
|
+
// MEASURED before the rung, doctoring only the sidecar of a real scan: removing the REACHING implementor's
|
|
706
|
+
// entry silently dropped the dispatcher from `possibleViaUnknownDispatch` (`[]` where the control gives
|
|
707
|
+
// `[Dispatcher.run]`), while removing the sidecar ENTIRELY left it correct — the ⟨0.24⟩ per-file rule
|
|
708
|
+
// over-lists. LESS information was SAFER than partial information. candor-java behaved identically, which
|
|
709
|
+
// is what said the defect was the FORMAT rather than either consumer.
|
|
710
|
+
//
|
|
711
|
+
// A POSITIVE DOMINATES: if a known path reaches `owner` the answer is YES even when another branch ran
|
|
712
|
+
// into an unindexed type — the relation is established and an unknown branch cannot un-establish it. NO is
|
|
713
|
+
// reserved for a walk that stayed entirely inside types the sidecar answers for.
|
|
714
|
+
function subtypeOf(type, owner, hierarchy) {
|
|
715
|
+
if (type === owner) return "YES";
|
|
716
|
+
let sawUnindexed = false;
|
|
701
717
|
const seen = new Set(), stack = [type];
|
|
702
718
|
while (stack.length) {
|
|
703
|
-
|
|
719
|
+
const cur = stack.pop();
|
|
720
|
+
if (!Object.prototype.hasOwnProperty.call(hierarchy, cur)) { sawUnindexed = true; continue; }
|
|
721
|
+
for (const s of hierarchy[cur]) { if (s === owner) return "YES"; if (!seen.has(s)) { seen.add(s); stack.push(s); } }
|
|
704
722
|
}
|
|
705
|
-
return
|
|
723
|
+
return sawUnindexed ? "UNANSWERABLE" : "NO";
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// The two-valued form. UNANSWERABLE collapses to TRUE — disclose, never drop — which is the direction
|
|
727
|
+
// §2.2 ⟨0.26⟩ requires and the opposite of what absence used to do.
|
|
728
|
+
function isSubtypeOf(type, owner, hierarchy) {
|
|
729
|
+
return subtypeOf(type, owner, hierarchy) !== "NO";
|
|
706
730
|
}
|
|
707
731
|
|
|
708
732
|
// callers + the unresolved-dispatch frontier (--include-unknown, SPEC §3.1/§4 0.7): the CONFIRMED set,
|
|
@@ -1236,7 +1260,7 @@ export function narrowingContext(fns, cg = {}, policyParsed = null) {
|
|
|
1236
1260
|
const u = byRaw.get(r.raw);
|
|
1237
1261
|
if (!u) continue; // unreachable: every held triple has a group
|
|
1238
1262
|
const cur = heldByFn.get(f.fn) ?? new Map();
|
|
1239
|
-
cur.set(`${r.raw}
|
|
1263
|
+
cur.set(`${r.raw}\0${eff}`, { fn: f.fn, rule: r.raw, effect: eff, why: u.why });
|
|
1240
1264
|
heldByFn.set(f.fn, cur);
|
|
1241
1265
|
}
|
|
1242
1266
|
return {
|
package/query.mjs
CHANGED
|
@@ -232,7 +232,7 @@ function renderPathHuman(fns, cg, fnQ, eff) {
|
|
|
232
232
|
// package.json; SPEC_VERSION is the spec contract this build speaks. Reused, never re-littered.
|
|
233
233
|
const QUERY_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
234
234
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(QUERY_DIR, "package.json"), "utf8")).version;
|
|
235
|
-
const SPEC_VERSION = "0.
|
|
235
|
+
const SPEC_VERSION = "0.26";
|
|
236
236
|
|
|
237
237
|
// ---- the §3.3.1 canonical query grammar (⟨0.10⟩, additive over 0.9) --------------------------------
|
|
238
238
|
// One shape for every verb: `<verb> <verb-args…> [--report <locator>] [--policy <file>] [--json]
|
package/scan.mjs
CHANGED
|
@@ -44,7 +44,7 @@ const ENGINE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
44
44
|
// literal stamped into the envelope's `spec` field, so the doc lines and the report can never drift.
|
|
45
45
|
// Reused, never re-littered.
|
|
46
46
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(ENGINE_DIR, "package.json"), "utf8")).version;
|
|
47
|
-
const SPEC_VERSION = "0.
|
|
47
|
+
const SPEC_VERSION = "0.26";
|
|
48
48
|
|
|
49
49
|
// --version: a print-and-exit MODE, handled before the main arg walk so it never depends on a target.
|
|
50
50
|
// Fully OFFLINE — candor never phones home. Staying current is the AGENT's job: read the installed
|
|
@@ -4371,9 +4371,13 @@ for (const [name, rec] of fns) {
|
|
|
4371
4371
|
hash: `${pkgName}#${rec.local}`, // SPEC §2: the cross-package join key (package + local tail)
|
|
4372
4372
|
inferred: inf,
|
|
4373
4373
|
direct: [...rec.direct].sort(),
|
|
4374
|
-
declared
|
|
4375
|
-
|
|
4376
|
-
|
|
4374
|
+
// ⟨0.26⟩ `declared`/`undeclared`/`overdeclared` are DELIBERATELY ABSENT. They are the §5
|
|
4375
|
+
// capability-reconciliation outputs and this engine runs no such pass, so emitting `[]` would be a
|
|
4376
|
+
// positive claim — `undeclared: []` reads as "this function performs no undeclared effect", an
|
|
4377
|
+
// AS-EFF-001 all-clear from a check that never ran. SPEC §2 ⟨0.26⟩: present means the pass ran,
|
|
4378
|
+
// absent means it did not, and `[]` from an engine that computed nothing is forbidden.
|
|
4379
|
+
// They were emitted as hardcoded constants "for cross-engine schema parity" — a schema-parity check
|
|
4380
|
+
// is exactly what made that look conforming, which is why the rule now forbids requiring them.
|
|
4377
4381
|
unresolved: inf.includes("Unknown"),
|
|
4378
4382
|
};
|
|
4379
4383
|
// Inline call edges (§2 `calls`) — the SAME edges the callgraph sidecar carries, embedded per entry so a
|
|
@@ -4909,7 +4913,13 @@ for (const sf of sources) {
|
|
|
4909
4913
|
supers.push(d && d.name ? `${moduleOf(d.getSourceFile())}.${namespacePrefixOf(d)}${d.name.getText()}` : t.expression.getText());
|
|
4910
4914
|
}
|
|
4911
4915
|
}
|
|
4912
|
-
|
|
4916
|
+
// ⟨0.26⟩ A KEY FOR EVERY TYPE INDEXED, `[]` INCLUDED — the key set IS the manifest (SPEC §2.2).
|
|
4917
|
+
// This was `if (supers.length)`, so a type with no supertypes was OMITTED and absence meant BOTH
|
|
4918
|
+
// "no supertypes" and "never indexed". Measured: removing one entry from a real sidecar silently
|
|
4919
|
+
// dropped a `callers --include-unknown` frontier row, while removing the sidecar ENTIRELY left it
|
|
4920
|
+
// correct — so LESS information was SAFER than partial information. §2.2's own example has always
|
|
4921
|
+
// shown `"app.Base": []`; this producer contradicted it.
|
|
4922
|
+
hierarchy[`${mod}.${namespacePrefixOf(node)}${node.name.getText()}`] = supers;
|
|
4913
4923
|
}
|
|
4914
4924
|
ts.forEachChild(node, walk);
|
|
4915
4925
|
})(sf);
|