Skip to content

Commit f7b5a85

Browse files
committed
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 DeusData#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 DeusData#999 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 8c9ac98 commit f7b5a85

2 files changed

Lines changed: 139 additions & 1 deletion

File tree

‎src/pipeline/pipeline.c‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,47 @@ static bool is_infra_file(const char *fp) {
613613
strstr(fp, ".tf") != NULL || strstr(fp, ".hcl") != NULL || strstr(fp, ".toml") != NULL);
614614
}
615615

616+
/* CI/tooling configs describe the development TOOLCHAIN — their URLs are
617+
* repository/action/registry references, never endpoints this service
618+
* exposes. Minting infra Route nodes from them lets the route matcher's
619+
* root-service heuristic attach every handler of an ambiguous "/" route to
620+
* each tooling URL (junk HANDLES churn on plain pallets/flask, #999).
621+
* Deny by file identity, not URL shape: deployment configs (Cloud
622+
* Scheduler, compose) keep minting their genuine endpoints. */
623+
static bool is_ci_tooling_config(const char *fp) {
624+
if (!fp) {
625+
return false;
626+
}
627+
if (strstr(fp, ".github/") != NULL || strstr(fp, ".gitlab/") != NULL ||
628+
strstr(fp, ".circleci/") != NULL) {
629+
return true;
630+
}
631+
const char *slash = strrchr(fp, '/');
632+
const char *base = slash ? slash + 1 : fp;
633+
static const char *const tooling[] = {".pre-commit-config.yaml",
634+
".pre-commit-hooks.yaml",
635+
".gitlab-ci.yml",
636+
".travis.yml",
637+
"azure-pipelines.yml",
638+
"appveyor.yml",
639+
"bitbucket-pipelines.yml",
640+
".readthedocs.yaml",
641+
".readthedocs.yml",
642+
"codecov.yml",
643+
".codecov.yml",
644+
".goreleaser.yaml",
645+
".goreleaser.yml",
646+
".golangci.yml",
647+
".golangci.yaml",
648+
NULL};
649+
for (int i = 0; tooling[i]; i++) {
650+
if (strcmp(base, tooling[i]) == 0) {
651+
return true;
652+
}
653+
}
654+
return false;
655+
}
656+
616657
/* True when a YAML key path denotes an UPSTREAM dependency, CONFIG value, or
617658
* HEALTHCHECK target rather than an endpoint this service exposes. Such URLs
618659
* (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
681722
CBMHashTable *denied = cbm_ht_create(16);
682723
for (int pass = 0; pass < 2; pass++) {
683724
for (int i = 0; i < file_count; i++) {
684-
if (!result_cache[i] || !is_infra_file(files[i].rel_path)) {
725+
if (!result_cache[i] || !is_infra_file(files[i].rel_path) ||
726+
is_ci_tooling_config(files[i].rel_path)) {
685727
continue;
686728
}
687729
for (int si = 0; si < result_cache[i]->string_refs.count; si++) {

‎tests/test_lang_contract.c‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,100 @@ TEST(contract_edge_imports_alias_resolves_real_file_issue767) {
12021202
PASS();
12031203
}
12041204

1205+
/* #999: URLs in CI/tooling configs (.pre-commit-config.yaml, .github/
1206+
* workflows) are repository references for TOOLING, not endpoints this
1207+
* service exposes. They must not mint __route__infra__ Route nodes — the
1208+
* route matcher's root-service heuristic otherwise attaches every handler
1209+
* of an ambiguous "/" route to each junk URL (HANDLES churn on plain
1210+
* pallets/flask). */
1211+
static int count_infra_routes_matching(cbm_store_t *store, const char *project,
1212+
const char *substr) {
1213+
cbm_node_t *nodes = NULL;
1214+
int count = 0;
1215+
if (cbm_store_find_nodes_by_label(store, project, "Route", &nodes, &count) != CBM_STORE_OK)
1216+
return -1;
1217+
int hits = 0;
1218+
for (int i = 0; i < count; i++) {
1219+
const char *qn = nodes[i].qualified_name;
1220+
if (qn && strncmp(qn, "__route__infra__", (sizeof("__route__infra__") - 1)) == 0 &&
1221+
strstr(qn, substr))
1222+
hits++;
1223+
}
1224+
cbm_store_free_nodes(nodes, count);
1225+
return hits;
1226+
}
1227+
1228+
TEST(contract_edge_no_infra_routes_from_ci_configs_issue999) {
1229+
LangProj lp;
1230+
static const LangFile f[] = {
1231+
{"app.py", "from flask import Flask\n"
1232+
"app = Flask(__name__)\n"
1233+
"\n"
1234+
"@app.route(\"/\")\n"
1235+
"def index():\n"
1236+
" return \"hi\"\n"
1237+
"\n"
1238+
"@app.route(\"/auth/login\")\n"
1239+
"def login():\n"
1240+
" return \"login\"\n"},
1241+
{".pre-commit-config.yaml", "repos:\n"
1242+
" - repo: https://github.com/astral-sh/ruff-pre-commit\n"
1243+
" rev: v0.1.0\n"
1244+
" hooks:\n"
1245+
" - id: ruff\n"
1246+
" - repo: https://github.com/pre-commit/pre-commit-hooks\n"
1247+
" rev: v4.5.0\n"
1248+
" hooks:\n"
1249+
" - id: trailing-whitespace\n"},
1250+
{".github/workflows/ci.yml", "name: CI\n"
1251+
"on: [push]\n"
1252+
"jobs:\n"
1253+
" build:\n"
1254+
" runs-on: ubuntu-latest\n"
1255+
" steps:\n"
1256+
" - uses: actions/checkout@v4\n"
1257+
" - run: curl https://coverage.example.io/upload\n"}};
1258+
cbm_store_t *store = lang_index_files(&lp, f, 3);
1259+
ASSERT_NOT_NULL(store);
1260+
/* No tooling URL may materialize as an infra Route node. */
1261+
int gh_routes = count_infra_routes_matching(store, lp.project, "github.com");
1262+
int wf_routes = count_infra_routes_matching(store, lp.project, "coverage.example.io");
1263+
if (gh_routes != 0 || wf_routes != 0) {
1264+
fprintf(stderr,
1265+
" [999] FAIL infra routes from CI configs: github.com=%d workflows=%d "
1266+
"(expected 0/0)\n",
1267+
gh_routes, wf_routes);
1268+
}
1269+
ASSERT_EQ(gh_routes, 0);
1270+
ASSERT_EQ(wf_routes, 0);
1271+
lang_cleanup(&lp, store);
1272+
PASS();
1273+
}
1274+
1275+
/* #999 inverse guard: genuine infra endpoint URLs (Cloud Scheduler push
1276+
* targets and similar deployment configs) must STILL mint infra Route
1277+
* nodes — the CI/tooling deny must be file-scoped, not a blanket URL kill. */
1278+
TEST(contract_edge_infra_routes_from_deploy_configs_still_minted) {
1279+
LangProj lp;
1280+
static const LangFile f[] = {
1281+
{"scheduler.yaml",
1282+
"jobs:\n"
1283+
" - name: nightly-sync\n"
1284+
" schedule: \"0 3 * * *\"\n"
1285+
" push_endpoint: https://sync.internal.example/api/v1/sync\n"}};
1286+
cbm_store_t *store = lang_index_files(&lp, f, 1);
1287+
ASSERT_NOT_NULL(store);
1288+
int routes = count_infra_routes_matching(store, lp.project, "sync.internal.example");
1289+
if (routes < 1) {
1290+
fprintf(stderr, " [999] FAIL deploy-config endpoint minted %d infra routes "
1291+
"(expected >=1)\n",
1292+
routes);
1293+
}
1294+
ASSERT_TRUE(routes >= 1);
1295+
lang_cleanup(&lp, store);
1296+
PASS();
1297+
}
1298+
12051299
/* DEPENDS_ON — Helm Chart.yaml `dependencies:` -> per-dependency Chart node.
12061300
* Basename must be exactly "Chart.yaml"; pass_k8s runs in both pipeline paths. */
12071301
TEST(contract_edge_depends_on) {
@@ -1411,6 +1505,8 @@ SUITE(lang_contract) {
14111505
RUN_TEST(contract_edge_workspaces_imports_issue408);
14121506
RUN_TEST(contract_edge_imports_alias_no_phantom_folder_edge_issue767);
14131507
RUN_TEST(contract_edge_imports_alias_resolves_real_file_issue767);
1508+
RUN_TEST(contract_edge_no_infra_routes_from_ci_configs_issue999);
1509+
RUN_TEST(contract_edge_infra_routes_from_deploy_configs_still_minted);
14141510
RUN_TEST(contract_edge_depends_on);
14151511
RUN_TEST(contract_edge_parallel_service_edges);
14161512
RUN_TEST(contract_edge_file_changes_with);

0 commit comments

Comments
 (0)