diff --git a/docs/guides/shacl-validation.md b/docs/guides/shacl-validation.md index 8ee40f7e..df81df04 100644 --- a/docs/guides/shacl-validation.md +++ b/docs/guides/shacl-validation.md @@ -707,6 +707,22 @@ report_dict = report.to_dict() --- +## Resource limits + +Live SHACL validation in the Explorer enforces four resource limits, all configurable +through environment variables. When a limit trips, the error message names the +variable that controls it. + +| Environment variable | Default | What it bounds | +| --- | --- | --- | +| `SEMANTICA_MAX_SHACL_TURTLE_BYTES` | `262144` (256 KB) | Size of the submitted SHACL Turtle | +| `SEMANTICA_MAX_SHACL_TRIPLES` | `1000` | Triple count of the parsed shapes graph | +| `SEMANTICA_MAX_SHACL_TIMEOUT` | `15.0` | Validation timeout in seconds | +| `SEMANTICA_MAX_SHACL_CONCURRENCY` | `4` | Concurrent validations per process | + +The first three are surfaced in the validation error message when exceeded; the +concurrency limit applies as a semaphore and does not appear in responses. + ## Using SHACL validation as a CI/CD gate Call this function as a pre-publish gate; exit code 1 blocks the pipeline. diff --git a/semantica/explorer/routes/ontology.py b/semantica/explorer/routes/ontology.py index e723d166..7fa547af 100644 --- a/semantica/explorer/routes/ontology.py +++ b/semantica/explorer/routes/ontology.py @@ -2877,7 +2877,8 @@ async def validate_shacl( status="error", message=( f"SHACL Turtle size ({len(_shacl_bytes)} bytes) " - f"exceeds maximum allowed size ({_MAX_SHACL_TURTLE_BYTES} bytes)." + f"exceeds maximum allowed size ({_MAX_SHACL_TURTLE_BYTES} bytes); " + f"set SEMANTICA_MAX_SHACL_TURTLE_BYTES to raise the limit." ), violations=[], ) @@ -2894,7 +2895,8 @@ async def validate_shacl( status="error", message=( f"SHACL graph triple count ({len(g)}) " - f"exceeds maximum allowed limit ({_MAX_SHACL_TRIPLES})." + f"exceeds maximum allowed limit ({_MAX_SHACL_TRIPLES}); " + f"set SEMANTICA_MAX_SHACL_TRIPLES to raise the limit." ), violations=[], ) @@ -2945,7 +2947,8 @@ async def validate_shacl( conforms=False, status="error", message=( - f"SHACL validation timed out after {_MAX_SHACL_TIMEOUT_SECONDS} seconds." + f"SHACL validation timed out after {_MAX_SHACL_TIMEOUT_SECONDS} seconds; " + f"set SEMANTICA_MAX_SHACL_TIMEOUT to raise the timeout." ), violations=[], ) diff --git a/tests/explorer/test_ontology_subissue3.py b/tests/explorer/test_ontology_subissue3.py index b24d596a..23192b96 100644 --- a/tests/explorer/test_ontology_subissue3.py +++ b/tests/explorer/test_ontology_subissue3.py @@ -735,6 +735,7 @@ onto:PersonShape a sh:NodeShape ; payload = response.json() assert payload["status"] == "error" assert "exceeds maximum allowed size" in payload["message"] + assert "SEMANTICA_MAX_SHACL_TURTLE_BYTES" in payload["message"] def test_validate_shacl_rejects_too_many_triples(client): @@ -757,6 +758,7 @@ onto:PersonShape a sh:NodeShape ; payload = response.json() assert payload["status"] == "error" assert "exceeds maximum allowed limit" in payload["message"] + assert "SEMANTICA_MAX_SHACL_TRIPLES" in payload["message"] def test_validate_shacl_handles_timeout(client): @@ -786,6 +788,7 @@ onto:PersonShape a sh:NodeShape ; payload = response.json() assert payload["status"] == "error" assert "timed out" in payload["message"] + assert "SEMANTICA_MAX_SHACL_TIMEOUT" in payload["message"] def test_validate_shacl_returns_unavailable_for_truncated_graph(client):