Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions .github/workflows/l9-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# L9 Governed Analysis Pipeline — Python Preset (LOCKED)
#
# DO NOT EDIT — this file is managed by l9-ci-core presets/python.
# To update, pull the latest preset from Quantum-L9/l9-ci-core.
#
# This workflow runs the full L9 analysis pipeline:
# 1. Resolve governance config from .github/governance/
# 2. Run semgrep with Python rulesets
# 3. Provision the SDK (immutable, pinned)
# 4. Normalize → Validate → Project → Route → Manifest → Upload
# 5. Publish results as GitHub Checks
name: L9 Analysis
on:
pull_request:
push:
branches: [main]
workflow_dispatch:

env:
L9_CORE_REF: "f88116503430aa18992b70d8d31063e34ff97ef1"
L9_PROFILE: "pr_fast"
L9_MATRIX_ID: "pr-semgrep"

permissions:
contents: read
checks: write

Check warning on line 26 in .github/workflows/l9-analysis.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this write permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=Quantum-L9_Cognitive.Engine.Graphs&issues=AaC8smhEzZY4JqY3qNdE&open=AaC8smhEzZY4JqY3qNdE&pullRequest=284
Comment thread
cryptoxdog marked this conversation as resolved.

concurrency:
group: l9-analysis-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: Governed Semgrep Analysis
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
enabled: ${{ steps.gov.outputs.enabled }}
mode: ${{ steps.gov.outputs.mode }}
governance-digest: ${{ steps.gov.outputs.governance-digest }}
artifact-name: ${{ steps.names.outputs.artifact-name }}
permissions:
contents: read
checks: write
Comment thread
cryptoxdog marked this conversation as resolved.
steps:
- name: Checkout immutable event revision
env:
REPOSITORY: ${{ github.repository }}
REVISION: ${{ github.sha }}
TOKEN: ${{ github.token }}
run: |
set -euo pipefail
git init .
git remote add origin \
"https://x-access-token:${TOKEN}@github.com/${REPOSITORY}.git"
git -c protocol.version=2 fetch --depth=1 origin "${REVISION}"
git checkout --detach FETCH_HEAD
git remote set-url origin "https://github.com/${REPOSITORY}.git"

- id: gov
name: Resolve governance
uses: Quantum-L9/l9-ci-core/.github/actions/resolve-governance@555d577eb805851b624cf7b0b8fc4df75a225d9f
with:
profile: ${{ env.L9_PROFILE }}
provider: semgrep
event-name: ${{ github.event_name }}
repository: ${{ github.repository }}
ref: ${{ github.ref }}

- id: names
name: Compute artifact names
env:
MATRIX_ID: ${{ env.L9_MATRIX_ID }}
run: |
set -euo pipefail
echo "artifact-name=l9-semgrep-${MATRIX_ID}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"

- name: Run semgrep
if: steps.gov.outputs.enabled == 'true'
run: |
set -euo pipefail
pip install --upgrade pip semgrep

Check warning on line 82 in .github/workflows/l9-analysis.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=Quantum-L9_Cognitive.Engine.Graphs&issues=AaC8smhEzZY4JqY3qNdD&open=AaC8smhEzZY4JqY3qNdD&pullRequest=284

Check warning on line 82 in .github/workflows/l9-analysis.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=Quantum-L9_Cognitive.Engine.Graphs&issues=AaC8smhEzZY4JqY3qNdC&open=AaC8smhEzZY4JqY3qNdC&pullRequest=284
Comment thread
cryptoxdog marked this conversation as resolved.
mkdir -p "artifacts/raw/semgrep/${L9_MATRIX_ID}"
# No `|| true` (Baseline Ratchet rejects fail-open). Also no
# `--error`: findings must reach normalize/publish so governance
# can decide blocking vs advisory; `--error` exits 1 before that.
semgrep scan \
--config p/python \
--json \
--output "artifacts/raw/semgrep/${L9_MATRIX_ID}/report.json" \
--quiet
env:
L9_MATRIX_ID: ${{ env.L9_MATRIX_ID }}

- id: sdk
name: Provision immutable SDK
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/provision-sdk@0d28395428426853c44825c4645c23ee8ace23b1

- name: Normalize provider report
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/invoke-sdk@f88116503430aa18992b70d8d31063e34ff97ef1
with:
executable: ${{ steps.sdk.outputs.executable }}
operation: semgrep-normalize
input: artifacts/raw/semgrep/${{ env.L9_MATRIX_ID }}/report.json
output: .l9/runtime/${{ env.L9_MATRIX_ID }}/finding-bundle.json
root: .
snapshot-id: ${{ github.sha }}
revision: ${{ github.sha }}
strict: ${{ steps.gov.outputs.strict }}
required: ${{ steps.gov.outputs.required-provider }}
policy: ${{ steps.gov.outputs.sdk-policy }}
identity-map: .github/governance/semgrep-identity-map.yaml

- name: Validate canonical bundle
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/validate-bundle@84375ed2bc9e005048dfb6f74076fc420b4bc01c
with:
executable: ${{ steps.sdk.outputs.executable }}
bundle: .l9/runtime/${{ env.L9_MATRIX_ID }}/finding-bundle.json

- name: Project agent-review payload
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/invoke-sdk@f88116503430aa18992b70d8d31063e34ff97ef1
with:
executable: ${{ steps.sdk.outputs.executable }}
operation: bundle-project-agent-payload
input: .l9/runtime/${{ env.L9_MATRIX_ID }}/finding-bundle.json
output: .l9/runtime/${{ env.L9_MATRIX_ID }}/agent-review-payload.json
strict: ${{ steps.gov.outputs.strict }}

- id: route
name: Route artifacts
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/route-artifacts@84375ed2bc9e005048dfb6f74076fc420b4bc01c
with:
provider: semgrep
matrix-id: ${{ env.L9_MATRIX_ID }}
raw-report: artifacts/raw/semgrep/${{ env.L9_MATRIX_ID }}/report.json
bundle: .l9/runtime/${{ env.L9_MATRIX_ID }}/finding-bundle.json
agent-payload: .l9/runtime/${{ env.L9_MATRIX_ID }}/agent-review-payload.json
destination-root: artifacts

- name: Build artifact manifest
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/build-artifact-manifest@555d577eb805851b624cf7b0b8fc4df75a225d9f
with:
provider: semgrep
matrix-id: ${{ env.L9_MATRIX_ID }}
sdk-revision: ${{ steps.sdk.outputs.sdk-revision }}
bundle: ${{ steps.route.outputs.bundle }}
agent-payload: ${{ steps.route.outputs.agent-payload }}
raw-directory: ${{ steps.route.outputs.raw-directory }}
output: artifacts/metadata/${{ env.L9_MATRIX_ID }}/artifact-manifest.json

- name: Upload analysis artifact set
if: steps.gov.outputs.enabled == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.names.outputs.artifact-name }}
path: |
artifacts/raw/semgrep/${{ env.L9_MATRIX_ID }}/
artifacts/l9/${{ env.L9_MATRIX_ID }}/
artifacts/metadata/${{ env.L9_MATRIX_ID }}/
if-no-files-found: error
retention-days: 14

publish:
name: Publish analysis (Core)
needs: analyze
if: needs.analyze.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/workflows/publish-analysis.yml@0d28395428426853c44825c4645c23ee8ace23b1
permissions:
actions: read
checks: write
Comment thread
cryptoxdog marked this conversation as resolved.
contents: read
with:
artifact-name: ${{ needs.analyze.outputs.artifact-name }}
profile: pr_fast
mode: ${{ needs.analyze.outputs.mode }}
provider: semgrep
matrix-id: pr-semgrep
governance-digest: ${{ needs.analyze.outputs.governance-digest }}
repository-revision: ${{ github.sha }}
workflow-result: ${{ needs.analyze.result }}
2 changes: 2 additions & 0 deletions .github/workflows/supply-chain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ jobs:
persist-credentials: false

- name: Run OpenSSF Scorecard Analysis
# v2.4.4 action.yaml image is docker://ghcr.io/ossf/scorecard-action:v2.4.4
# Do not pin v2.4.0 — that tag still docker-pulls gcr.io/openssf (billing denied).
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: scorecard.sarif
Expand Down
24 changes: 24 additions & 0 deletions docs/contracts/BANNED_PATTERNS.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,27 @@ def compile_traversal_gate(spec: GateSpec) -> str:
**Scope note:** these rules apply to `engine/` only. Abstract base classes in `chassis/`
(for example `AuditSink.write_batch`) raise `NotImplementedError` as their defining
contract — that is the intended use, not a stub.

### `typing.Protocol` method bodies (CEG#267)

A Protocol method is a structural signature. It is never instantiated and never
executed. The only body that is valid Python *and* clean on every in-repo gate
is a docstring and nothing else:

| Body | `STUB-001` | ruff `PIE790` | github-code-quality |
|---|---|---|---|
| `raise NotImplementedError` | CRITICAL (blocks merge) | clean | clean |
| `pass` | clean | PIE790 | clean |
| `...` | clean | clean | “Statement has no effect” |
| docstring only | clean | clean | clean |

Do not “fix” a Protocol by raising. That is the opposite of a stub-free engine:
it trips the blocking scanner so a review bot can go quiet. Chassis ABCs may
still raise; `engine/` Protocols may not.

```python
# ✅ CORRECT — Protocol signature, no executable statement
class GraphWriter(Protocol):
async def execute_write(self, *args: Any, **kwargs: Any) -> Any:
"""Run one managed write transaction."""
```
3 changes: 0 additions & 3 deletions engine/hoprag/indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ async def fetch_passages(
Returns:
List of dicts with 'id' and 'text' keys.
"""
...

async def write_edges(
self,
Expand All @@ -83,7 +82,6 @@ async def write_edges(
Returns:
Number of edges written.
"""
...

async def get_vertex_count(self, label: str) -> int:
"""Count vertices with given label.
Expand All @@ -94,7 +92,6 @@ async def get_vertex_count(self, label: str) -> int:
Returns:
Number of vertices.
"""
...


@dataclass
Expand Down
1 change: 0 additions & 1 deletion engine/sync/idea_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ async def execute_write(
**kwargs: Any,
) -> dict[str, Any] | Any:
"""Run one managed write transaction, via a transaction function or `cypher`."""
...


@dataclass(frozen=True)
Expand Down
2 changes: 0 additions & 2 deletions engine/traversal/multihop.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ def evaluate_edges(
Returns:
Index of the selected edge in candidate_edges.
"""
...


@dataclass(frozen=True)
Expand Down Expand Up @@ -132,7 +131,6 @@ async def get_outgoing_edges(self, vertex_id: str) -> list[TraversalEdge]:
Returns:
List of TraversalEdge objects.
"""
...


class MultiHopTraverser:
Expand Down
3 changes: 0 additions & 3 deletions engine/traversal/pseudo_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ def generate(self, prompt: str) -> str:
Returns:
Generated text response.
"""
...


class KeywordExtractor(Protocol):
Expand All @@ -141,7 +140,6 @@ def extract(self, text: str) -> frozenset[str]:
Returns:
Set of extracted keyword strings.
"""
...


class EmbeddingEncoder(Protocol):
Expand All @@ -156,7 +154,6 @@ def encode(self, text: str) -> tuple[float, ...]:
Returns:
Embedding vector as tuple of floats.
"""
...


# ── Main Generator ───────────────────────────────────────────────────
Expand Down
66 changes: 66 additions & 0 deletions tests/unit/test_protocol_bodies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Protocol method bodies must stay lint-clean on every in-repo gate (CEG#267)."""

from __future__ import annotations

import ast
from pathlib import Path

import pytest

ENGINE = Path(__file__).resolve().parents[2] / "engine"


def _protocol_methods(path: Path) -> list[tuple[str, str, ast.AST]]:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
found: list[tuple[str, str, ast.AST]] = []
for node in ast.walk(tree):
if not isinstance(node, ast.ClassDef):
continue
if not any(
(isinstance(base, ast.Name) and base.id == "Protocol")
or (isinstance(base, ast.Attribute) and base.attr == "Protocol")
for base in node.bases
):
continue
for item in node.body:
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
found.append((path.as_posix(), f"{node.name}.{item.name}", item))
return found


def _engine_protocol_methods() -> list[tuple[str, str, ast.AST]]:
rows: list[tuple[str, str, ast.AST]] = []
for path in sorted(ENGINE.rglob("*.py")):
rows.extend(_protocol_methods(path))
return rows


@pytest.mark.unit
def test_engine_has_protocol_methods() -> None:
assert _engine_protocol_methods(), "expected at least one engine Protocol method"


@pytest.mark.unit
def test_protocol_methods_have_no_executable_stub() -> None:
failures: list[str] = []
for rel, qualname, fn in _engine_protocol_methods():
executable = []
for stmt in fn.body:
if (
isinstance(stmt, ast.Expr)
and isinstance(stmt.value, ast.Constant)
and isinstance(stmt.value.value, str)
):
continue
executable.append(stmt)
for stmt in executable:
if isinstance(stmt, ast.Pass):
failures.append(f"{rel}:{qualname} uses pass (ruff PIE790)")
elif isinstance(stmt, ast.Raise) and isinstance(stmt.exc, ast.Call):
func = stmt.exc.func
name = func.id if isinstance(func, ast.Name) else ""
if name == "NotImplementedError":
failures.append(f"{rel}:{qualname} raises NotImplementedError (STUB-001)")
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant) and stmt.value.value is Ellipsis:
failures.append(f"{rel}:{qualname} uses ... (github-code-quality no-op)")
Comment thread
cryptoxdog marked this conversation as resolved.
assert failures == [], "\n".join(failures)
Loading