You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An unnamed arrow or function expression that is not a variable initializer is never modelled as a callable, so its contents are invisible above L1. computeSignatureForDecl (src/schema/signatures.ts:46) returns null for it — documented at signatures.ts:43-44 as "not a nameable declaration" — so indexCallableDecls (src/dataflow/extract.ts:52) never sees it and no CFG/CDG/DDG is built. Meanwhile namedBoundary (src/syntactic_analysis/builders.ts:330-335) treats only named nested callables as boundaries, so the arrow's calls are attributed to the enclosing function, and captureScan (src/dataflow/defuse.ts:305) then skips the arrow's own parameters because they are declared inside it. Jelly's side mints a plaque instead: homeSynthesized (src/schema/v2/emit.ts:317-333) emits a flat application-scope V2Node with no body, no graphs, and bytes: [0, 0], addressed by an ordinal id. The repo therefore holds two contradictory positions at once, and for the Express handler idiom — return (req, res, next) => { … query(\… ${req.body.email} …`) }— noreq.body.emailfact exists anywhere on the native DDG. Measured on OWASP Juice Shop: 24req-rooted TS_DDGedges application-wide, none onroutes/login.ts:34`, across 883 anonymous handlers.
Note the asymmetry that is the whole change surface: isCallableDecl (signatures.ts:29-40) already lists ArrowFunction and FunctionExpression; contributorName (signatures.ts:11-27) has no case for either.
Scope boundary
Materializes unnamed function-like nodes only, in this repo only.
Does not fix #57 or #85 — this.x = fn and named object-literal methods are the named-but-unmaterialized family, same symptom, different root cause. Does not add argument-level or expression-level DDG granularity (V2Callable.ddg stays unknown[]); that is roadmap candidate 2, collision group A. Does not coin canonical cross-language vocabulary — the <anon@L:C> production is TypeScript-local and provisional, pending roadmap candidate 4 ratification; sibling analyzers should not adopt it yet. Does not define entrypoints or taint sources from unbound formals (roadmap candidate 6, #72). Does not touch the <service> segment (#91, now schema 2.2.0).
Goals
contributorName (src/schema/signatures.ts:11-27) contributes an <anon@L:C> segment for unnamed ArrowFunction / FunctionExpression; computeSignatureForDecl stops returning null for them
namedBoundary / walkBody (src/syntactic_analysis/builders.ts:330-344) treat unnamed function-like nodes as callable boundaries; they populate inner_callables
indexCallableDecls (src/dataflow/extract.ts:52) indexes them, so CFG/CDG/DDG are built per arrow
captureScan (src/dataflow/defuse.ts:305) boundary follows; arrow parameters become @formal_in:N at L4
homeSynthesized (src/schema/v2/emit.ts:317-333) emits a signature → can:// id index; no bytes: [0, 0] spans remain
Neo4j: one node carrying :TSCallable:TSAnonymousCallable, reached by a containment relationship from the enclosing callable (src/build/neo4j/{schema,project}.ts)
SCHEMA_VERSION → 2.1.0 in bothsrc/schema/v2/emit.ts:33 and src/build/neo4j/schema.ts:22; bun run gen:schema regenerated
test/schema-v2.test.ts monotonicity gates hold at L1 ⊆ L2 ⊆ L3 ⊆ L4 with anonymous callables populated
Migration note in CHANGELOG.md stating the call-site re-anchoring plainly
Caveats and known risks
Callable count grows sharply — Juice Shop gains on the order of 883 callables, each with its own CFG/CDG/DDG at -a 3. L3/L4 runtime, .codeanalyzer/ cache and graph.cypher all grow. Mitigation: measure before/after on Juice Shop at -a 4 and record the numbers in the PR; the per-callable parallel worker pool already absorbs some of it.
Instance-level drift is real even though the bump is MINOR. Any consumer asking "what does login call" gets a different answer — call sites re-anchor to the arrow. The version rule at src/build/neo4j/schema.ts:19 classifies this as MINOR because no label, relationship or key is removed, but the CHANGELOG must state the behavioural change plainly rather than hide behind the version number.
Position-based ids are not stable across edits. Inserting a line above an arrow changes its id. This matches the keystone's posture for ordinal ids but is a new property for a durable-tier id, and is the most likely thing group A amends at ratification.
Deeply nested closures produce long dotted chains — a callback inside a callback inside a handler yields three <anon@L:C> segments. Accepted; watch id length in the Neo4j projection.
Juice Shop at -a 4 yields at least one req.body.email-rooted DDG path reaching the models.sequelize.query call in routes/login.ts — the EXP-001 acceptance check, demonstrated in the PR, not asserted
The arrow at routes/login.ts:34 appears in login's callables{} with a non-empty body, cfg, cdg, ddg at -a 3 and @formal_in:0 at -a 4
synthesized_callables contains only signature → can:// id pairs; a grep for "bytes": [0, 0] in analysis.json returns nothing
Plan (optional)
Spec:
docs/design/specs/anonymous-callable-materialization.mdProblem
An unnamed arrow or function expression that is not a variable initializer is never modelled as a callable, so its contents are invisible above L1.
computeSignatureForDecl(src/schema/signatures.ts:46) returnsnullfor it — documented atsignatures.ts:43-44as "not a nameable declaration" — soindexCallableDecls(src/dataflow/extract.ts:52) never sees it and no CFG/CDG/DDG is built. MeanwhilenamedBoundary(src/syntactic_analysis/builders.ts:330-335) treats only named nested callables as boundaries, so the arrow's calls are attributed to the enclosing function, andcaptureScan(src/dataflow/defuse.ts:305) then skips the arrow's own parameters because they are declared inside it. Jelly's side mints a plaque instead:homeSynthesized(src/schema/v2/emit.ts:317-333) emits a flat application-scopeV2Nodewith nobody, no graphs, andbytes: [0, 0], addressed by an ordinal id. The repo therefore holds two contradictory positions at once, and for the Express handler idiom —return (req, res, next) => { … query(\… ${req.body.email} …`) }— noreq.body.emailfact exists anywhere on the native DDG. Measured on OWASP Juice Shop: 24req-rootedTS_DDGedges application-wide, none onroutes/login.ts:34`, across 883 anonymous handlers.Note the asymmetry that is the whole change surface:
isCallableDecl(signatures.ts:29-40) already listsArrowFunctionandFunctionExpression;contributorName(signatures.ts:11-27) has no case for either.Scope boundary
Materializes unnamed function-like nodes only, in this repo only.
Does not fix #57 or #85 —
this.x = fnand named object-literal methods are the named-but-unmaterialized family, same symptom, different root cause. Does not add argument-level or expression-level DDG granularity (V2Callable.ddgstaysunknown[]); that is roadmap candidate 2, collision group A. Does not coin canonical cross-language vocabulary — the<anon@L:C>production is TypeScript-local and provisional, pending roadmap candidate 4 ratification; sibling analyzers should not adopt it yet. Does not define entrypoints or taint sources from unbound formals (roadmap candidate 6, #72). Does not touch the<service>segment (#91, now schema 2.2.0).Goals
contributorName(src/schema/signatures.ts:11-27) contributes an<anon@L:C>segment for unnamedArrowFunction/FunctionExpression;computeSignatureForDeclstops returningnullfor themnamedBoundary/walkBody(src/syntactic_analysis/builders.ts:330-344) treat unnamed function-like nodes as callable boundaries; they populateinner_callablesindexCallableDecls(src/dataflow/extract.ts:52) indexes them, so CFG/CDG/DDG are built per arrowcaptureScan(src/dataflow/defuse.ts:305) boundary follows; arrow parameters become@formal_in:Nat L4homeSynthesized(src/schema/v2/emit.ts:317-333) emits a signature →can://id index; nobytes: [0, 0]spans remain:TSCallable:TSAnonymousCallable, reached by a containment relationship from the enclosing callable (src/build/neo4j/{schema,project}.ts)SCHEMA_VERSION→2.1.0in bothsrc/schema/v2/emit.ts:33andsrc/build/neo4j/schema.ts:22;bun run gen:schemaregeneratedtest/schema-v2.test.tsmonotonicity gates hold at L1 ⊆ L2 ⊆ L3 ⊆ L4 with anonymous callables populatedCHANGELOG.mdstating the call-site re-anchoring plainlyCaveats and known risks
-a 3. L3/L4 runtime,.codeanalyzer/cache andgraph.cypherall grow. Mitigation: measure before/after on Juice Shop at-a 4and record the numbers in the PR; the per-callable parallel worker pool already absorbs some of it.logincall" gets a different answer — call sites re-anchor to the arrow. The version rule atsrc/build/neo4j/schema.ts:19classifies this as MINOR because no label, relationship or key is removed, but the CHANGELOG must state the behavioural change plainly rather than hide behind the version number.<anon@L:C>segments. Accepted; watch id length in the Neo4j projection..claude/SCHEMA_DECISIONS.mdrows 9-11 record these decisions but the file is gitignored (~/.gitignore_global:9), so they do not travel. Tracked separately as chore: track .claude/SCHEMA_DECISIONS.md (un-ignore the schema-decisions doc) #47; the committed spec is the durable record until it lands.Definition of done
-a 4yields at least onereq.body.email-rooted DDG path reaching themodels.sequelize.querycall inroutes/login.ts— the EXP-001 acceptance check, demonstrated in the PR, not assertedroutes/login.ts:34appears inlogin'scallables{}with a non-emptybody,cfg,cdg,ddgat-a 3and@formal_in:0at-a 4synthesized_callablescontains only signature →can://id pairs; a grep for"bytes": [0, 0]inanalysis.jsonreturns nothinggraph.cypherleaves zero:TSAnonymousCallableorphans — closes fix(neo4j): cypher snapshot wipe misses :TSAnonymousCallable orphans #75analysis.jsonandschema.neo4j.jsonboth report2.1.0bun test,bun run typecheckandbun run test:containerpass; the Neo4j conformance test passes against the regenerated schemaL1 ⊆ L2 ⊆ L3 ⊆ L4holds on bothtest/fixtures/sample-appandtest/fixtures/dataflow-appwith anonymous callables present at every levelCloses #75.