fix(pipeline): stop CI/tooling config URLs from minting infra Route nodes

URLs in .pre-commit-config.yaml, .github/workflows and similar CI
configs are repository/action/registry references for the development
toolchain, never endpoints the indexed service exposes. They matched
is_infra_file (.yaml) and passed the #521 value guards, so each minted
a __route__infra__<url> Route node; the route matcher's root-service
heuristic then attached every handler of an ambiguous '/' route to each
junk URL - 6-7 junk HANDLES targets per handler on plain pallets/flask,
with tie-break-dependent winners feeding HANDLES churn between builds.

Deny by file identity (well-known CI/tooling basenames plus .github/,
.gitlab/, .circleci/ trees) in cbm_pipeline_extract_infra_routes, not
by URL shape: deployment configs (Cloud Scheduler push endpoints,
compose) keep minting their genuine endpoints, guarded by an inverse
test.

Closes #999

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-07-12 14:12:26 +02:00
1 parent 8c9ac98e30
commit f7b5a856f5
2 files changed
+139 -1

No files matched your search

+43 -1
View File
@@ -613,6 +613,47 @@ static bool is_infra_file(const char *fp) {
strstr(fp, ".tf") != NULL || strstr(fp, ".hcl") != NULL || strstr(fp, ".toml") != NULL);
}
/* CI/tooling configs describe the development TOOLCHAIN — their URLs are
* repository/action/registry references, never endpoints this service
* exposes. Minting infra Route nodes from them lets the route matcher's
* root-service heuristic attach every handler of an ambiguous "/" route to
* each tooling URL (junk HANDLES churn on plain pallets/flask, #999).
* Deny by file identity, not URL shape: deployment configs (Cloud
* Scheduler, compose) keep minting their genuine endpoints. */
static bool is_ci_tooling_config(const char *fp) {
if (!fp) {
return false;
}
if (strstr(fp, ".github/") != NULL || strstr(fp, ".gitlab/") != NULL ||
strstr(fp, ".circleci/") != NULL) {
return true;
}
const char *slash = strrchr(fp, '/');
const char *base = slash ? slash + 1 : fp;
static const char *const tooling[] = {".pre-commit-config.yaml",
".pre-commit-hooks.yaml",
".gitlab-ci.yml",
".travis.yml",
"azure-pipelines.yml",
"appveyor.yml",
"bitbucket-pipelines.yml",
".readthedocs.yaml",
".readthedocs.yml",
"codecov.yml",
".codecov.yml",
".goreleaser.yaml",
".goreleaser.yml",
".golangci.yml",
".golangci.yaml",
NULL};
for (int i = 0; tooling[i]; i++) {
if (strcmp(base, tooling[i]) == 0) {
return true;
}
}
return false;
}
/* True when a YAML key path denotes an UPSTREAM dependency, CONFIG value, or
* HEALTHCHECK target rather than an endpoint this service exposes. Such URLs
* (auth JWKS, downstream service base URLs, package-registry URLs, healthcheck
@@ -681,7 +722,8 @@ static void cbm_pipeline_extract_infra_routes(cbm_gbuf_t *gbuf, const cbm_file_i
CBMHashTable *denied = cbm_ht_create(16);
for (int pass = 0; pass < 2; pass++) {
for (int i = 0; i < file_count; i++) {
if (!result_cache[i] || !is_infra_file(files[i].rel_path)) {
if (!result_cache[i] || !is_infra_file(files[i].rel_path) ||
is_ci_tooling_config(files[i].rel_path)) {
continue;
}
for (int si = 0; si < result_cache[i]->string_refs.count; si++) {
+96
View File
@@ -1202,6 +1202,100 @@ TEST(contract_edge_imports_alias_resolves_real_file_issue767) {
PASS();
}
/* #999: URLs in CI/tooling configs (.pre-commit-config.yaml, .github/
* workflows) are repository references for TOOLING, not endpoints this
* service exposes. They must not mint __route__infra__ Route nodes — the
* route matcher's root-service heuristic otherwise attaches every handler
* of an ambiguous "/" route to each junk URL (HANDLES churn on plain
* pallets/flask). */
static int count_infra_routes_matching(cbm_store_t *store, const char *project,
const char *substr) {
cbm_node_t *nodes = NULL;
int count = 0;
if (cbm_store_find_nodes_by_label(store, project, "Route", &nodes, &count) != CBM_STORE_OK)
return -1;
int hits = 0;
for (int i = 0; i < count; i++) {
const char *qn = nodes[i].qualified_name;
if (qn && strncmp(qn, "__route__infra__", (sizeof("__route__infra__") - 1)) == 0 &&
strstr(qn, substr))
hits++;
}
cbm_store_free_nodes(nodes, count);
return hits;
}
TEST(contract_edge_no_infra_routes_from_ci_configs_issue999) {
LangProj lp;
static const LangFile f[] = {
{"app.py", "from flask import Flask\n"
"app = Flask(__name__)\n"
"\n"
"@app.route(\"/\")\n"
"def index():\n"
" return \"hi\"\n"
"\n"
"@app.route(\"/auth/login\")\n"
"def login():\n"
" return \"login\"\n"},
{".pre-commit-config.yaml", "repos:\n"
" - repo: https://github.com/astral-sh/ruff-pre-commit\n"
" rev: v0.1.0\n"
" hooks:\n"
" - id: ruff\n"
" - repo: https://github.com/pre-commit/pre-commit-hooks\n"
" rev: v4.5.0\n"
" hooks:\n"
" - id: trailing-whitespace\n"},
{".github/workflows/ci.yml", "name: CI\n"
"on: [push]\n"
"jobs:\n"
" build:\n"
" runs-on: ubuntu-latest\n"
" steps:\n"
" - uses: actions/checkout@v4\n"
" - run: curl https://coverage.example.io/upload\n"}};
cbm_store_t *store = lang_index_files(&lp, f, 3);
ASSERT_NOT_NULL(store);
/* No tooling URL may materialize as an infra Route node. */
int gh_routes = count_infra_routes_matching(store, lp.project, "github.com");
int wf_routes = count_infra_routes_matching(store, lp.project, "coverage.example.io");
if (gh_routes != 0 || wf_routes != 0) {
fprintf(stderr,
" [999] FAIL infra routes from CI configs: github.com=%d workflows=%d "
"(expected 0/0)\n",
gh_routes, wf_routes);
}
ASSERT_EQ(gh_routes, 0);
ASSERT_EQ(wf_routes, 0);
lang_cleanup(&lp, store);
PASS();
}
/* #999 inverse guard: genuine infra endpoint URLs (Cloud Scheduler push
* targets and similar deployment configs) must STILL mint infra Route
* nodes — the CI/tooling deny must be file-scoped, not a blanket URL kill. */
TEST(contract_edge_infra_routes_from_deploy_configs_still_minted) {
LangProj lp;
static const LangFile f[] = {
{"scheduler.yaml",
"jobs:\n"
" - name: nightly-sync\n"
" schedule: \"0 3 * * *\"\n"
" push_endpoint: https://sync.internal.example/api/v1/sync\n"}};
cbm_store_t *store = lang_index_files(&lp, f, 1);
ASSERT_NOT_NULL(store);
int routes = count_infra_routes_matching(store, lp.project, "sync.internal.example");
if (routes < 1) {
fprintf(stderr, " [999] FAIL deploy-config endpoint minted %d infra routes "
"(expected >=1)\n",
routes);
}
ASSERT_TRUE(routes >= 1);
lang_cleanup(&lp, store);
PASS();
}
/* DEPENDS_ON — Helm Chart.yaml `dependencies:` -> per-dependency Chart node.
* Basename must be exactly "Chart.yaml"; pass_k8s runs in both pipeline paths. */
TEST(contract_edge_depends_on) {
@@ -1411,6 +1505,8 @@ SUITE(lang_contract) {
RUN_TEST(contract_edge_workspaces_imports_issue408);
RUN_TEST(contract_edge_imports_alias_no_phantom_folder_edge_issue767);
RUN_TEST(contract_edge_imports_alias_resolves_real_file_issue767);
RUN_TEST(contract_edge_no_infra_routes_from_ci_configs_issue999);
RUN_TEST(contract_edge_infra_routes_from_deploy_configs_still_minted);
RUN_TEST(contract_edge_depends_on);
RUN_TEST(contract_edge_parallel_service_edges);
RUN_TEST(contract_edge_file_changes_with);