Cascade

A code-to-column change-impact knowledge graph for AI coding agents.

View the Project on GitHub alexsoft-hq/Cascade

What was measured, and what was not

Every number in this document was produced by running the engine, and every row names the command that produces it again. The second half of the page, the list of what is not verified, matters more than the first: a tool whose only durable difference is honesty has to be honest about its own evidence.

The generality gate

scripts/generality-gate.mjs runs this engine, unchanged and with no per-project configuration, over a pinned corpus of real repositories nobody here wrote it for, and prints what it reached.

The engine’s own tests prove that each rule does what it says on a fixture chosen to exercise it. They cannot tell you whether the SET of rules adds up to something that works on a repository nobody here has read. That question has one honest answer, which is to run it and count, and the answer is only worth anything if it repeats: the same commits, the same flags, the same numbers, so a change that quietly loses a DAO layer shows up as a smaller number rather than as nothing at all.

node scripts/generality-gate.mjs --fetch            # clone or check out every pin, then run
node scripts/generality-gate.mjs --fetch --no-run   # clone only
node scripts/generality-gate.mjs                    # run over whatever is cloned
node scripts/generality-gate.mjs --only mall,litemall
node scripts/generality-gate.mjs --accept           # rewrite the baseline, printing the diff

test/generality_gate.test.mjs compares the result against test/fixtures/generality-gate.baseline.json and fails when a repository reaches less than it did. A rise changes nothing until somebody runs --accept: a number that improves silently is a number nobody checked. The clones live under the cache directory, about 1 GB in all, never inside this repository, and each run gets its own registry so the gate cannot write into yours.

The corpus, and what it reached

Six counts are guarded. Five of them are floors, and a drop in any fails the suite.

Repository Pinned at Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs
jeecgboot/JeecgBoot 74364054 744 / 969 73 / 177 836 / 2092 562 / 963 25 / 181 15997
jishenghua/JSH_ERP ad6cf886 330 / 339 32 / 32 409 / 413 165 / 221 0 / 7 13179
apache/dolphinscheduler 499fd068 204 / 239 42 / 65 457 / 622 219 / 233 0 / 44 6216
macrozheng/mall + mall-admin-web 0504e86b + 81fc17e5 205 / 239 49 / 76 461 / 669 145 / 153 44 / 54 3868
linlinjava/litemall a1ef964a 198 / 219 34 / 34 376 / 376 172 / 191 40 / 89 4223
yangzongzhuan/RuoYi-Vue + RuoYi-Vue3 13db1fce + 838965c5 123 / 147 22 / 33 224 / 305 122 / 142 8 / 21 1613
jeequan/jeepay ba371119 126 / 134 22 / 23 302 / 314 0 / 0 0 / 5 3554
xuxueli/xxl-job e74c784f 31 / 42 7 / 8 70 / 71 24 / 31 6 / 11 528
mybatis/jpetstore-6 ebb36b39 11 / 22 12 / 13 77 / 86 52 / 53 16 / 16 243
spring-projects/spring-petclinic 818c4136 15 / 17 7 / 7 24 / 24 12 / 13 3 / 8 196
spring-petclinic-microservices 3858f9c6 13 / 15 7 / 7 24 / 24 14 / 14 8 / 9 99

The table and column counts above were taken before RM67, when a census still counted a table or a column reached only through a guessed edge. Four of them are lower now (jeecg-boot, dolphinscheduler, jeepay, spring-petclinic-microservices); the RM67 section has the numbers and the declaration that restores each.

Six more repositories joined this corpus in RM55 and are measured in their own section, which keeps the measurement taken BEFORE any rule was written for them beside the one after. Two more joined in RM63, a WebSquare client and a system whose logic sits in stored routines, and their section does the same.

The last column is the sixth, and it is guarded the other way up. It adds up, over every endpoint, how many distinct columns that one endpoint reaches, and a rise of more than five percent fails the suite. It is here because the five floors are all union counts over the whole project, and a union count cannot see reach that smears sideways: when one generic base method resolved in every subclass at once, jeecg carried 9,400 false endpoint-to-column pairs, not one of them naming a column some other endpoint did not already reach on its own. Every floor above stayed exactly where it is written, and the gate stayed green. The pair sum is the number that moved, 25,397 down to 15,997 when the rule was fixed, and it is guarded now so the next one fails instead.

The axes each entry declared

The other half of a gate run is what each pack said about itself before anybody asked it a question.

Repository catalog statements jpa mybatisPlus column code web screen
jeecg-boot shipped shipped not-shipped degraded shipped shipped shipped shipped
jsh-erp shipped shipped not-shipped not-shipped shipped shipped degraded shipped
dolphinscheduler shipped shipped not-shipped degraded shipped shipped shipped degraded
mall shipped shipped not-shipped not-shipped shipped shipped shipped shipped
litemall shipped shipped not-shipped not-shipped shipped shipped degraded shipped
ruoyi-vue shipped shipped not-shipped not-shipped shipped shipped shipped degraded
jeepay shipped shipped not-shipped degraded shipped shipped not-shipped not-shipped
xxl-job shipped shipped not-shipped not-shipped shipped shipped shipped degraded
jpetstore-6 shipped shipped not-shipped not-shipped shipped shipped shipped shipped
spring-petclinic shipped not-shipped degraded not-shipped degraded shipped shipped degraded
petclinic-ms shipped not-shipped degraded not-shipped degraded shipped shipped shipped

degraded on mybatisPlus there is the naming strategy: no profile in the corpus declares one, so the engine assumes MyBatis-Plus’s own default, grades every name it derived that way HEURISTIC, and says so. degraded on web is the frontend prefix: nothing in the source states it, so the engine matched candidates against the routes and took the best fit, which is a guess and is graded as one. degraded on screen is a router the server fills in at run time, which means the screens in the source are not the screens the product has.

How to read these numbers

“204 of 239 endpoints reach a statement” says the engine connected 204 chains. Whether the other 35 touch no database at all (a login, a file upload, a health check) or whether the engine lost one of their calls is a question only reading them answers, and the round that adds a repository to this table is the round that reads them. It is not a benchmark and it is not a claim of correctness.

Three of those frontend numbers are new in RM47, and none of them came from a flag. The petclinic gateway ships its frontend as <script src> tags with no package.json anywhere: 22 files, nine screens and thirteen $http calls that the lane refused to open until this round, so both of its frontend columns said “no frontend read”. cascade init now calls a directory of frontend sources the tree says it serves a web root and writes it into the profile. xxl-job gains a web lane over 19 loose scripts and reads two call sites out of them, neither resolving to a route it serves, which is why its web axis is degraded rather than shipped; jeecg-boot picks up five more call sites the same way, none of them resolving either. jeepay is the case that stays where it was on purpose: its static/cashier/js is a webpack build, and a .js with a .js.map beside it is output rather than a frontend somebody keeps here. See the web lane.

Four of them are new in RM48, and none of them came from a flag either. Three of these projects have no frontend router at all: a @Controller returns a view name, a template engine renders it, and the page’s own <form>, its links and its inline <script> are what talk to the backend. spring-petclinic, xxl-job and jpetstore-6 had zero screens for exactly that reason, and now have 8, 11 and 16. jpetstore’s 20 JSP pages yield 52 resolved calls out of 53 and every one of its 16 pages reaches a table, which is why it is the one project here whose screen axis reads shipped. jeecg-boot gains 15 pages beside the 166 screens its Vue frontend’s router declares, and jeepay 5 payment pages it had none of.

Ten of jpetstore’s sixteen page names, and five of petclinic’s eight, are not literals at the return statement at all: they are a static final String the controller declares (return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;) or a private method of the same class whose returns are literals (petclinic’s addPaginationModel). Both are read out of ONE file, so both are EXACT like any other literal. What is still not read is a name that would need another file or a data-flow answer: 2 of petclinic’s returns ("redirect:/owners/" + owner.getId()) and 2 of xxl-job’s, which is why those two read degraded.

One entry is worth reading as a limit rather than as a score. jeecg-boot declares 173 routes, 87 of them the framework’s own demo pages, and fetches every business screen at run time from a route named after permissions rather than after menus. No spelling in the server-menu rule matches it, so its screen axis reads shipped and “19 of 166 screens reach a table” reads as a shortfall rather than as a description of a product whose screens are mostly not in its source. Adding that project’s own spelling to the rule would be a rule that works on that project and nowhere else, so the table stays generic and this is written down instead.

The Korean market, held out first

The eleven repositories above are the frameworks the world writes in. They are not the market this product is for. So RM55 cloned the public repositories that are — eGovFrame, which every Korean public sector project is required to build on, a Nexacro client over an eGovFrame backend, and one modern Korean OSS as a control — and measured them before writing a line of code for them. That first measurement is kept here beside the one after, so anyone can see which numbers the round moved and which it did not.

The first measurement, before any rule was written

Repository Pinned at Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs
egovframe-common-components a88a3e31 0 / 1193 0 / 184 0 / 1956 0 / 13 0 / 1 0
egovframe-enterprise-business-template cfccbe89 0 / 219 0 / 35 0 / 288 0 / 4 0 / 0 0
egovframe-msa-edu 777f697c 90 / 163 20 / 25 191 / 270 2 / 217 0 / 0 394
egovframe-web-sample 8f37555e 5 / 6 1 / 1 5 / 5 0 / 0 0 / 0 5
nexacro-sample-egov deb90f90 6 / 21 2 / 5 9 / 46 0 / 0 0 / 0 27
naver/ngrinder 2a6da299 30 / 124 7 / 9 92 / 114 66 / 77 0 / 19 900

Two of those rows are the finding. 219 routes and 205 statements, 1,193 routes and 1,256 statements, and not one edge between them: nothing a route reached ended at a table. The product was useless on eGovFrame, and the causes were three, each verified by reading the trees:

  1. Statements are called by their string id. eGovFrame DAOs extend a session base and write selectList("CmmnDetailCodeManageDAO.selectCmmnDetailCodeList", vo). There is no mapper interface anywhere, and the engine bound a statement only through one.
  2. The view resolver is a bean in an XML. 92 and 747 JSPs, and no template root, because the prefix was in egov-com-servlet.xml rather than in application.yml.
  3. The DDL is one file per vendor. Seven of them, all read at once.

The same six, after

Repository Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs Wall
egovframe-common-components 999 / 1193 165 / 179 1640 / 1818 765 / 811 470 / 657 9568 50 s
egovframe-enterprise-business-template 163 / 219 30 / 35 210 / 288 122 / 135 55 / 84 1889 4 s
egovframe-msa-edu 90 / 163 20 / 25 191 / 270 2 / 217 0 / 0 394 1 s
egovframe-web-sample 5 / 6 1 / 1 5 / 5 0 / 0 0 / 2 5 1 s
nexacro-sample-egov 2 / 21 2 / 5 9 / 46 0 / 0 0 / 0 9 1 s
ngrinder 30 / 124 7 / 9 92 / 114 66 / 77 0 / 19 900 1 s

The one number that went DOWN is the one worth reading. nexacro-sample-egov fell from 6 endpoints reaching a statement to 2, and its pair count from 27 to 9, while the tables and columns it reaches did not move at all. That project declares one SampleService interface with two implementations — one over iBATIS, one over MyBatis — and one LargeDataService with three. Every controller reached all of them through the interface, so four routes claimed to run MyBatis statements that their own @Resource(name = "sampleService") says they never touch. Reading the bean name removed exactly those four, and the two that remain (/sampleMybatisSelectVO.do, /sampleMybatisLargeData.do) are the two routes wired to the MyBatis implementation. Verified by running the same commit with the rule switched off and listing both sets.

msa-edu and ngrinder did not move by one number, which is the other half of the result: neither has a mapper XML, an XML view resolver or a vendor DDL tree, so none of the three rules had anything to fire on. Their frontends (two Next.js applications and a Nexacro client) are still unread, and that is the next round.

The eleven repositories above this section did not move by one number either.

And the same six after the rest of the Korean stack (RM56)

RM55 fixed what a route reaches; it left the frontends and half the SQL. RM56 took those: one vendor’s mapper XML, iBATIS 2 in the SQL lane, a Nexacro client in the web lane, and Next.js file routing.

Repository Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs Wall
egovframe-common-components 999 / 1193 165 / 179 1680 / 1862 765 / 811 470 / 657 9647 22 s
egovframe-enterprise-business-template 163 / 219 30 / 35 218 / 288 122 / 135 55 / 84 2099 2 s
egovframe-msa-edu 90 / 163 20 / 25 191 / 270 2 / 217 0 / 56 394 1 s
egovframe-web-sample 5 / 6 1 / 1 5 / 5 0 / 0 0 / 2 5 0 s
nexacro-sample-egov 10 / 21 5 / 5 46 / 46 7 / 7 4 / 30 100 1 s
ngrinder 30 / 124 7 / 9 92 / 114 66 / 77 0 / 19 900 1 s

Read row by row:

The eleven repositories above this section do not move by one number, all sixteen pack digests are identical, and 3,028 recorded answers are byte-identical.

Two entries trip the ceiling the gate guards the other way up: endpointColumnPairs rises 1,889 → 2,099 on the business template and 9 → 100 on the Nexacro sample. Both are the same cause as the rises above — statements that now parse, and a client whose screens now reach tables — rather than reach smearing sideways, and both are on the held-out six.

What the after-run left on the table

And the same six after the two defects the MSA template exposed (RM57)

RM56 read msa-edu’s 56 screens and left its calls at 2 of 217. The two causes were both general, and neither was Korean: a gateway rewrite written the way Spring’s own reference writes it, and one hop between a page and the module its API calls live in.

Repository Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs Wall
egovframe-common-components 999 / 1193 165 / 179 1680 / 1862 765 / 811 470 / 657 9647 21 s
egovframe-enterprise-business-template 163 / 219 30 / 35 218 / 288 122 / 135 55 / 84 2099 2 s
egovframe-msa-edu 90 / 163 20 / 25 191 / 270 23 / 217 12 / 56 394 1 s
egovframe-web-sample 5 / 6 1 / 1 5 / 5 0 / 0 0 / 2 5 1 s
nexacro-sample-egov 10 / 21 5 / 5 46 / 46 7 / 7 4 / 30 100 1 s
ngrinder 30 / 124 7 / 9 92 / 114 66 / 77 0 / 19 900 1 s

Only msa-edu moves, and it moves twice.

The round trip, end to end, on the /content/{id} screen: RENDERS (EXACT, the route declaration names the component) to ContentItem, CALLS (SOUND_SET, the member contentService.save through an export * barrel) to Content.ts#save, CALLS_HTTP (SOUND_SET, axios, the declared gateway prefix taking /portal-service off the front) to POST /api/v1/contents, HANDLES (EXACT) to ContentApiController#save, two MAY_CALL hops to ContentRepository#save, IMPLEMENTS_STMT (EXACT), and the table content, written.

What this round left on the table

A URL built on a module constant is still nothing but holes. 73 of msa-edu’s 217 call sites resolve to a template like {*}/{*}, because axios.get(`${POSTS_URL}/${id}`) records the leading ${POSTS_URL} as a hole. The worker names the hole (base: "POSTS_URL") and knows the constant’s value in the same file, but nothing substitutes it, so the path names no route and the walk stops at the API function. That is why the portal’s own /board/{skin}/{board}/view/{id} reaches five service functions and no endpoint. It is a general gap in the URL reader, not a gateway or a member rule, and it is untouched here.

And the same corpus after a URL built on a constant is read (RM58)

The gap the round before left on the table: a frontend does not write its paths at the call site. It writes them once at the top of the file and every call is that name plus what the caller passes, so axios.get(`${POSTS_URL}/${id}`) read letter by letter is {*}/{*} and names no route. A hole that is a name the file can follow to text is now filled in, and the edge says what went in.

Repository Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs Wall
egovframe-msa-edu 90 / 163 20 / 25 191 / 270 137 / 217 33 / 56 394 1 s
jeecgboot/JeecgBoot 744 / 969 73 / 177 836 / 2092 584 / 963 25 / 181 15997 14 s

Two repositories move, both upward, and every other number in the corpus is where it was.

The round trip on the portal’s /board/{skin}/{board}/view/{id}, which reached no endpoint at all before: RENDERS (EXACT, the file-tree page declaration) to [id].tsx#BoardView, CALLS (SOUND_SET, the member boardService.getPostById through an @service import) to Board.ts#getPostById, CALLS_HTTP (SOUND_SET, axios, POSTS_URL put into `${POSTS_URL}/view/${boardNo}/${postsNo}` and the declared gateway prefix taking /board-service off the front) to GET /api/v1/posts/view/{boardNo}/{postsNo}, HANDLES (EXACT) to PostsApiController#findViewById, two MAY_CALL hops (SOUND_SET, field receiver) to PostsRepository.findById, IMPLEMENTS_STMT (EXACT), and the table posts. The page reaches four tables in all: board, posts, posts_read, comment.

What this round left on the table

A constant another module exports is followed only when it is a literal. The bridge resolves the specifier and reads the exported constant, and every unit test for it passes, but no repository in the corpus exercises it: the eight import-bound holes left in msa-edu all lead to export const X = process.env.X, which is a deployment value and is left alone on purpose. A constant whose own value is built out of another hole (const BANNER_URL = `/api/${SITE_ID}/banners`) is reported by what stopped it and is not followed further.

And the same corpus once a navigation stops counting as a request (RM59)

A single-page app changes the screen by asking its own router, and the browser sends nothing. Every rule in the web lane saw a path-shaped argument and called it a request, so router.push('/auth/login') came out as a call to a route nothing serves. The sinks are now declared per router and read as navigations.

The Frontend calls column is the end of all three fixes this round made (the navigation pack, the imported constant, the string method); the paragraphs below say which took which.

Repository Frontend calls Calls resolved Navigations To a screen Screens reaching a table Endpoint to column pairs
egovframe-msa-edu 217 -> 173 137 68 62 33 / 56 394
jsh-erp 221 -> 193 (the constant fix, not a navigation) 165 8 3 0 / 7 13179
litemall 191 -> 177 172 56 37 40 / 89 4223
jeecgboot/JeecgBoot 963 -> 938 584 30 15 25 / 181 15997
naver/ngrinder 77 -> 69 66 -> 63 14 7 0 / 19 900
macrozheng/mall 153 -> 151 145 32 32 44 / 54 3868
ruoyi-vue 142 -> 138 122 -> 121 5 4 8 / 21 1613
egovframe-common-components 811 -> 810 765 66 0 470 / 657 9647
egovframe-enterprise-business-template 135 -> 134 122 27 0 55 / 84 2099
dolphinscheduler 233 219 33 18 0 / 44 6216
xxl-job 31 24 5 3 6 / 11 528

A string method is never an HTTP sink. Five of the MSA template’s nineteen unanswered paths were pathname.startsWith('/auth/login/naver') and pathname.indexOf('/refresh'): the argument really is a path, so the URL rule cannot help, and the METHOD name is what settles it. Reading it that way takes the template’s call sites 178 to 173 and its unanswered paths 19 to 14, jeecg-boot 950 to 938, ruoyi-vue 141 to 138, and one call each off the two eGovFrame repositories, jsh-erp and litemall. No repository loses a resolved call, and endpointColumnPairs, screens reaching a table, tables, columns and endpoints reaching a statement are identical on all seventeen.

What this round left on the table. A <router-link to=…> is written in a single-file component’s <template>, and the worker parses a component’s <script> blocks and not its markup, so that sink is declared and never fires. Neither does router.push on an application’s OWN router module (import router from '@/router'), which would need the module traced to a createRouter(...) the way an axios instance is traced. And a location.href in a server-rendered page IS a request in a way a router push is not: one such site in ngrinder (a CSV download) is now counted as a navigation that names no screen rather than as a call.

A URL that IS an imported constant. RM58 filled the holes a template was left with and left a URL argument that is the imported name itself resolving to nothing, counted importedConstant. All 27 of jsh-erp’s importedConstant unresolved sites were that, and not one was a URL: they are browser-storage keys read through a verb-named method (Vue.ls.get(ACCESS_TOKEN), where ACCESS_TOKEN is 'Access-Token'). Reading the value settles the call in both directions: 221 call sites to 194, 56 unresolved to 29, the same 165 resolved, and 27 more counted under notUrlShaped, which is where a call that never was one belongs. sound stays 0 there because every one of jsh-erp’s resolved calls goes through a prefix chosen by match count, and an auto prefix grades the whole edge HEURISTIC.

Two shapes measured and deliberately not built. A MyBatis session fetched and used in one expression (getSqlSession().selectList("X.y", vo)) has zero sites in all seventeen repositories, and the two getSqlSession() calls that exist fetch a JDBC connection for table metadata. A Nexacro transaction’s in and out datasets (75 in the sample) are data flow inside the browser, and what impact needs is the call, which the transaction record already carries.

And the same corpus once a page’s own script is read (RM60)

The eGovFrame standard is a form whose address is assigned in JavaScript and submitted from JavaScript, and until this round the lane read none of it. Three of the seventeen entries are written that way, and one of them — the framework’s own sample — went from reaching nothing at all to reaching everything it has.

Repository Frontend calls Calls resolved Screens reaching a table Endpoint to column pairs
egovframe-web-sample 0 -> 8 0 -> 8 0 / 2 -> 2 / 2 5
egovframe-enterprise-business-template 134 -> 357 122 -> 333 55 / 84 -> 72 / 84 2099
egovframe-common-components 810 -> 2156 765 -> 2074 470 / 657 -> 550 / 657 9647
apache/dolphinscheduler 233 -> 234 219 0 / 44 6216
jeecgboot/JeecgBoot 938 -> 936 584 25 / 181 15997
the other twelve unchanged unchanged unchanged unchanged

The two gaps the page’s own script left (RM61)

RM60 named what it did not read, and both turned out to be spellings rather than missing ideas.

Checked against what ran: two eGovFrame applications (RM62)

Everything above is what the engine READ. This is what two eGovFrame applications DID, and how much of it the pack already said. Each was built from its pinned clone, deployed on Tomcat 10.1 with the OpenTelemetry Java agent 2.31.1 and its in-memory HSQLDB, and driven in a headless browser: the web sample through every button of its two screens, the enterprise business template signed in as its administrator, through 27 list screens and their search buttons, and then through every route without a path variable (130). Nothing in either application was changed apart from the business template’s Globals.DbType, which its own comment says to set to hsql to run without a database.

  web sample business template
trust level RUNTIME_PASS GOLDEN_FAIL
endpoint -> tables, cases a run labelled 6 of 6 routes, 6 right 107 of 238 routes, 106 right
method -> statements 6 of 6, all right 70, all right
screen-to-route pairs a browser recorded 7, each a form-submit edge the pack had 14, each an edge the pack had

The one route the business template got wrong is /EgovPageLink.do. Its handler returns a view name taken from a list a Spring XML declares (egovPageLinkWhitelist) by the index the request carries, so no literal says which page it renders, and the menu tables that page imports are not reached. One miss in 107 puts the Wilson lower bound at 0.9489 against a 0.95 target, and the verdict says FAIL rather than rounding.

What the runs found wrong in the engine, each fixed in this release and each shown on the numbers above:

What the runs left is honest and named: 116 hops a servlet filter wrapped around a controller (RUNTIME_ONLY, shown and never walked, the shape an aspect has), the schema scripts HSQLDB runs at start-up (no method above that SQL), and the requests for a web font on another host. Neither application’s column -> endpoints or statement -> columns can be labelled by a run, and both are listed as not shown rather than counted against the level.

On the corpus the three eGovFrame rows move up and nothing else does:

Repository Endpoints reaching a statement Tables reached Columns reached Screens reaching a table
egovframe-web-sample 5 / 6 1 / 1 -> 2 / 2 5 / 5 2 / 2
egovframe-enterprise-business-template 163 -> 188 / 219 30 / 35 -> 32 / 36 218 -> 223 / 288 73 -> 77 / 84
egovframe-common-components 999 / 1193 165 -> 166 / 179 1680 -> 1682 / 1862 555 / 657

endpointColumnPairs rose past the gate’s ceiling on the business template (2099 -> 4417) and the common components (9647 -> 10131), and both were accepted: the rise is the menu tables every page imports and the key table every insert advances, which are the relations the runs above recorded. The sixteen pack digests of the registered projects are identical.

WebSquare and stored routines (RM63)

Two things a Korean SI system is written in were missing from everything above: a WebSquare client, and business logic kept in stored routines that a mapper only calls. One public repository of each was cloned, pinned and measured with the engine as it stood, before a rule was written, and again after.

Repository Pinned at Endpoints reaching a statement Tables reached Columns reached Frontend calls resolved Screens reaching a table Endpoint to column pairs
inswave/WRM-Public 19e0c19c 82 / 96 27 / 27 205 / 222 0 / 0 -> 111 / 119 0 / 2 -> 40 / 159 277
sindohmes/mes4u ed57f3db 110 / 115 29 / 30 -> 41 / 43 307 / 387 -> 483 / 673 113 / 148 0 / 47 618 -> 1409

WRM-Public is WebSquare’s own sample application over an eGovFrame backend. Before, the lane read none of its .xml pages. The product had two screens, the two views a controller returns to host the WebSquare engine, and not one frontend call. After, every page is a screen at the address the application opens it by. Of the 119 calls that carry an address, 111 reach a route this pack serves, and the eight that do not are what they look like:

Three more calls name a submission the page never declares, or hand over an options object this lane cannot read, and they are counted, not dropped. 110 of the 159 pages send nothing, and 79 of those screens are page templates under cm/template. The repository ships no DDL, so a column is attributed only where a statement names it.

mes4u is a manufacturing execution system on Spring Boot, MyBatis and PostgreSQL. Its schema is one pg_dump of 33 tables and 45 PL/pgSQL functions, and 29 of its 135 statements call one of them. Before, the catalog was parsed as MySQL, because nothing told init otherwise, and not one of the 33 tables was read. After, the dialect is PostgreSQL, read off the schema’s own text, and a call reaches the tables of the function and of what the function calls: statements that reach a table go from 105 to 131 of 135, and 854 edges run through a routine, each SOUND_SET. The endpoint-to-column pairs more than double. That rise is the point: every column a function touches is now reached by every endpoint above the statement that calls it.

What the gate itself caught while these two were added, both fixed before the rows were recorded:

What is left, and named:

The seventeen rows already in the corpus did not move by one number, and the sixteen pack digests of the registered projects are identical.

RM67: a table is graded by the SQL edge that reaches it

Until RM67 a walk that reached a statement counted every table and column the statement touched, whatever the grade of the edge to it, while flow drew only the ones the mode admits. Now every census counts a statement’s tables and columns the way flow draws them: through the EXECUTES, READS and WRITES edges the mode’s floor admits. A name a rule only assumed (an entity with no @Table, a MyBatis-Plus field with no @TableField, under no declared naming strategy) is graded HEURISTIC, so the default conservative census no longer counts it. The pinned corpus moved in these counts and no others:

Repository Tables reached Columns reached
dolphinscheduler 42 (unchanged) 457 -> 444
egovframe-msa-edu 20 -> 0 191 -> 0
eladmin 26 (unchanged) 230 -> 123
jeecg-boot 73 -> 67 836 -> 467
jeepay 22 (unchanged) 302 -> 48
mes4u 41 (unchanged) 483 -> 415
ngrinder 7 (unchanged) 92 -> 78
spring-petclinic-microservices 7 (unchanged) 24 -> 20
ruoyi-vue-pro 548 (unchanged) 8010 -> 15

Nothing was lost. A mode=heuristic census counts them all again (jeecg-boot: 73 tables, 836 columns), and declaring the naming strategy the project runs with makes those names EXACT: jpa.namingStrategy for JPA entities, mybatisPlus.namingStrategy for MyBatis-Plus. The analysis names the one it assumed (PROFILE_DEFAULT_ASSUMED). egovframe-msa-edu is the plain case: every one of its 20 reached tables is an @Entity with no @Table, and the project configures no strategy anywhere.

Three more counts moved in this round. The baseline keeps the first two where they were, so a run with the corpus reports them until the declaration is made; it takes the third, which no declaration restores:

RM67: one depth rule for every walk

Until this round the census walked 8 hops from a handler and stopped, and a walk that had more to go counted it as cut. Now every walk goes as far as the graph goes, and a node cap of 4000 is the guard (concepts.md). On the pinned corpus no census walk reached the node cap, the slowest census took the same time uncapped (130 ms on ruoyi-vue-pro), and these counts rose:

Repository What rose
dolphinscheduler endpoints reaching a statement 204 -> 215, statements 259 -> 265, tables 42 -> 43, columns 444 -> 451
ruoyi-vue-pro endpoints reaching a statement 2638 -> 2671, statements 2525 -> 2548
egovframe-msa-edu endpoints reaching a statement 90 -> 91
egov-business-template screens reaching a table 77 -> 78
ruoyi-vue screens reaching a table 8 -> 9
jeecg-boot statements reached 748 -> 749

The same round reads every method and path a Spring mapping annotation names (java-lane.md), which moved jeecg-boot’s own test pins: routes with one handler 969 -> 1016, endpoints 970 -> 1017, endpoints reaching a statement 744 -> 790. When the gate baseline was recorded again after the round, three numbers moved for a reason other than a gain. Endpoint-to-column pairs rose past the 5% budget on jeepay (3554 -> 3824), jeecg-boot (15997 -> 17231) and ngrinder (900 -> 1082), because the routes a multi-value mapping names are routes of their own (endpoints 134 -> 138, 970 -> 1017, 124 -> 136) and each reaches what its sibling reaches; per route the pairs move 26.5 -> 27.7, 16.5 -> 16.9 and 7.3 -> 8.0. mes4u’s screens fell 50 -> 31: the 19 that went were children composed under a wildcard (/*/dashboard, /*/defect) because their parent was found by line only; found by line and column they compose to /mdm/defect and the rest, which were already there. Its screens reaching a table stay 0.

RM67: one question, one answer, and the fourth review

Trace walked up, endpoint_impact and screen_impact now read one walk up, and src/core/walk_agreement.mjs compares their rows, column by column and table by table, with each other and with the census. Before the walks were merged, spring-petclinic had 17 of its 24 columns, and jpetstore-6 49 of its 86, whose screens one answer named and another did not (forest-blog 77, jeecg-boot 42, xxl-job 31, jeepay 4). After it, no column of the 26 Java corpus packs or the 4 TypeScript ones does. The suite runs the check on hand-built graphs, on the three golden fixture trees in every mode, and on spring-petclinic, jpetstore-6 and the mall pack when they are checked out.

The gate baseline was recorded again after the round. Each number that moved past its budget was bisected to one commit and read against the source:

The goldens

Three real projects, each pinned to a commit and checked end to end.

node --test test/mall_demo.test.mjs        # the MyBatis + Spring MVC golden
node --test test/jpetstore.test.mjs        # the HSQLDB + MyBatis golden
node --test test/petclinic.test.mjs        # the JPA golden

Each skips out loud without its fixture, naming both the fixture and the command that produces it. CI clones all three at pinned commits and fails if any of those tests skips, because a permanent self-omission on a fresh clone is a defect rather than a pass.

mall

Built from a fresh clone at a new absolute path, through the documented no-flag path, with a profile cascade init wrote on the spot:

What The number
pack 12674 nodes, 19269 edges, lanes [sql,java], digest 99141d55e969
census 239 endpoints, 906 mapper statements, 76 tables, 669 columns, 10784 symbols
reach 205 of 239 endpoints reach a statement, 208 statements, 49 tables, 461 columns
pms_product.price 18 statements, 8 writing and 10 reading, all EXACT
the same column, upward 27 endpoints, 12 of them under /product/*, all SOUND_SET
ERD 27 relationships over 76 tables, joining 32 of them
transactions 35 @Transactional boundaries, the largest reaching 15 tables
coupling 32 API groups, 22 of them participating, 61 coupled pairs, 267 coupled columns

With its Vue admin frontend added by one --web-src flag, the same tree becomes 13032 nodes and 19797 edges, digest 7994e1a5fd22, and adds 54 screens, 145 resolved frontend calls out of 153, and 44 screens that reach a table.

jpetstore-6

What The number
census 22 endpoints, 25 mapper statements, 13 tables, 86 columns
reach 11 of 22 endpoints reach a statement, 12 tables, 77 columns
pages 20 JSP templates, 16 of them a controller names, 52 of 53 call sites resolved

Its schema is HSQLDB, which routes to sqlglot’s ANSI parser rather than to a dialect parser of its own, so this golden is what exercises that path. It is also the plainest server-rendered application in the corpus: not one line of JavaScript, and every screen it has is a JSP file a @Controller named.

spring-petclinic

What The number
census 17 endpoints, 6 statements, 7 tables, 24 columns
reach 15 of 17 endpoints reach a statement, 7 tables, 24 columns
pages 12 Thymeleaf templates, 8 of them a controller names

It has no mapper XML at all: every statement in it is one the JPA bridge derived from an entity mapping, a derived query name or a @Query.

It is also the project a real OpenTelemetry agent capture is kept for (test/fixtures/otel/petclinic-agent.log, ten routes), and that recording is what moved these numbers. Of the ten routes it observed, six read tables the pack did not name, for two reasons: the JPA lane stopped at the entity the repository method names instead of following the eager associations that come back with it, and nothing in the graph led to a @ModelAttribute method, which is where two of those routes load their owner. Both are read now, and all ten routes score. The two routes the pack still answers nothing for are the welcome page and the crash page, which run no SQL.

The incremental oracle

The claim that a reused analysis equals a fresh one is a test rather than a promise. test/incremental.test.mjs builds a synthetic Spring, MyBatis and MySQL project inside a git repository, mutates a random subset of its files each round with a seeded PRNG, and requires the incremental pack to equal a cold pack byte for byte.

node --test test/incremental.test.mjs
node --test test/overlay_integration.test.mjs

The second one holds the other half: the working-tree overlay may over-approximate, and it may not omit. It compares the overlay against a full re-analysis of the same bytes and fails on anything the overlay missed.

Determinism, tamper cases and credentials

Claim How it is held
the same commit, catalog, engine and profile produce the same pack digest on any machine test/mall_demo.test.mjs rebuilds mall from a fresh clone at a different absolute path and compares the digest
an edited pack, receipt, fact set or gate state is refused rather than used test/calibration_tamper.test.mjs, twelve cases including a dropped fact set, an expired receipt and a flipped golden
no credential is ever written to .cascade/, the pack, the receipt, the registry or a log test/catalog_fetch.test.mjs runs a fetch end to end and greps every written file and both captured streams
catalog fetch refuses to connect without --yes the same test
the JavaScript and Python identifier folds agree test/identifier_case.test.mjs, 20 names by 3 rules by 11 dialects, running both implementations

NOT verified, and why

These are not “probably fine”.

The changelog carries the same two lists round by round, with the command behind every number: CHANGELOG.md.