Repository navigation
chore(sdk): converge every governed Gate_SDK consumer on one immutable SHA - #210
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Size Report
Best Practices for Large Changes
|
There was a problem hiding this comment.
🟡 Changes recommended
The moving @v1 pin is inconsistent with existing commit-pinned workflow usage and breaks the repo’s current hashed lock generation assumptions for git dependencies.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Pins the private constellation-node-sdk Git dependency to the moving major tag @v1 to reduce per-consumer PRs for future v1.x SDK updates as part of broader Gate/CEG/EIE seam alignment.
Changes:
- Update
constellation-node-sdkdependency ref from a commit SHA to@v1in the app’s declared dependencies. - Update the CI dependency set to use the same
@v1ref.
File summaries
| File | Description |
|---|---|
| requirements-ci.txt | Switches the CI install source for constellation-node-sdk to @v1. |
| pyproject.toml | Switches the project dependency declaration for constellation-node-sdk to @v1. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22916c6e15
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Repairs EIE-PR210-F001 (audit eie-open-prs-2026-09-17-pr210-22916c6e). PR #210 moved pyproject.toml and requirements-ci.txt from the immutable commit 69c6c67 to the floating tag @v1. A tag is mutable, so it is not a release identity: Gate_SDK can repoint it without any EIE change. It also breaks the production lock, because tools/lock_requirements.sh only recognises the SDK when the declaration ends in a 40-char SHA, which it then rewrites to a hashed source archive for `pip --require-hashes` (pip cannot hash-verify a git+https URL at all). Changes: - pyproject.toml, requirements-ci.txt: restore 69c6c67, the reviewed SHA carrying the L9_VERIFYING_KEYS_JSON GateClientConfig fix. Without it a signature-requiring node rejects every signed Gate response. - .github/workflows/pr-pipeline.yml: the select-gates step installed the SDK at a third, unrelated commit (ead0f48, 2026-07-23) outside requirements-ci.txt. Aligned to the same SHA. - scripts/validate_sdk_pin.py: the guard only asserted the pin appeared somewhere in a file, so the workflow-local pin drifted invisibly. It now extracts every Gate_SDK ref from every governed consumer and requires each to equal PIN, rejecting floating refs by shape. The workflow file is now a governed consumer. Verified: validate_sdk_pin.py passes, and fails on both drift modes it could not previously see (floating @v1; workflow pinned to ead0f48). Unit/compliance 213 passed, CI tests 17 passed. Lint, mypy, audit and contract-verify results are byte-identical to the unmodified PR #210 head. Not closed here: requirements.lock still carries release commit 2b2f53a, so production and CI remain split. That file is outside this task's write allowlist and closing it requires regenerating the hashed lock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
`ruff check .` reported F401 for `TransportPacket` in tests/test_pr21_packet_router.py: the symbol is imported on line 17 and never referenced (only `create_transport_packet`, at line 79, is used). This single error failed the "Lint (Ruff + Mypy)" and "Lint and Type Check" jobs on main at 843db96, and gate 1 of `make agent-check`, which blocks every later gate. Removing it makes `ruff check .` pass cleanly. The 8 tests in the file still pass, which confirms the import was dead rather than an implicit dependency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
L9 Audit ReviewStatus
Policy
Blocking Findings
Advisory Findings
... truncated 1172 additional findings ... Generated by L9 Audit Engine ( |
This PR's direction is now inverted — please re-read before reviewingPushed Why the reversal. A tag is mutable, so it cannot be a release identity — Gate_SDK can repoint Review threads
Not fixed here — needs a decision
It is not a mechanical regeneration, because the two candidates are not interchangeable:
Worth a separate look: the ledger's own rule says Verification on
|
Pushing 22663a6 flipped the SonarCloud quality gate from passed to failed ("C Security Rating on New Code"). Touching pr-pipeline.yml brought the whole file into the PR's new-code scope, surfacing 7 pre-existing githubactions:S8541 findings — "Omitting --only-binary :all: can lead to the execution of setup scripts". Six are `python -m pip install --upgrade pip` at lines 101/167/270/333/ 411/483. They take the fix directly, matching the convention line 488 already used. Verified: `--only-binary=:all: --upgrade pip` installs cleanly. The seventh is the Gate_SDK install. The rule's remedy cannot apply to a git source dependency — verified it aborts with "Building source distributions is disabled, but attempted to build constellation-node-sdk". That line already carried `# NOSONAR`, but on the continuation line rather than the flagged `pip install` line, so it never suppressed anything. The install is now one line with the marker and a stated reason. Its integrity comes from the immutable commit SHA; production installs the hash-verified archive instead. No suppression is added to the six that can genuinely be fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
CI triage on
|
| Check | Root cause |
|---|---|
| Test Suite | tests/services/test_gate_registration.py:215 imports start_reregistration_loop / stop_reregistration_loop from app.main; both were removed in 843db96 (#203) and the test was not updated. 1634 passed, 1 collection error — the suite is otherwise green. |
| Architecture Compliance · Architecture Compliance Gate · CI Gate | verify_contracts.py → SHA256 mismatch for app/services/gate_client.py (expected b87e0809…, actual 86ee3e8f…). The contract manifest was not updated when that file changed. 11 of 12 contracts pass. |
| Semgrep Policy Check · Security Scanning · Baseline Ratchet | Fail identically on 843db96. |
No fix PR exists for any of these, so there is nothing to port. I have not widened this PR to cover them — each needs a decision outside this change's scope:
- Test Suite — the test should either be deleted with the retired re-registration loop or rewritten against whatever replaced it. That is a call for whoever landed Migrate EIE to Gate-only egress; retire direct peer transport #203, since it decides whether the behaviour is gone or moved.
- Contract mismatch — the manifest hash needs regenerating for
app/services/gate_client.py, which is an assertion that the current file content is the reviewed content. Not something to rubber-stamp from here.
One measurable improvement landed alongside: Lint (Ruff + Mypy) and Lint and Type Check were failing on main and are green on this head after the unused-import removal in 22663a6.
I have not re-run any job — every failure above is reproducible, not a flake.
Generated by Claude Code
Correction:
|
tests/services/test_gate_registration.py imported start_reregistration_loop and stop_reregistration_loop from app.main at module level. 843db96 (#203, "retire direct peer transport") deleted both and left the tests behind, so the import failed at collection and took the ENTIRE module with it — 17 tests that never ran on any commit since. Removed the four re-registration tests and the four imports used only by them (asyncio, AsyncMock, main_module, and the two deleted symbols). Verified none is referenced elsewhere in the file, and that the periodic re-registration loop has no successor anywhere in app/ — this is dead coverage for a retired feature, not a relocated one. Collection then exposed two genuine failures the ImportError had been hiding, both in the signing posture tests: test_runtime_signs_responses_when_key_material_is_present test_malformed_verifying_keys_fail_closed Cause is a fixture defect, not a product defect. The SDK's get_runtime_config is @lru_cache'd, so the first call in the process pins the config and monkeypatch.setenv is read against a config built before it ran. Verified the SDK itself maps the environment correctly when called fresh, and that @lru_cache has been there since the SDK's first commit — so this is long-standing, not a consequence of the pin change. Caching is correct in production (the runtime must not re-read the environment per packet), so the fix clears the cache around these tests rather than weakening the assertions or touching the SDK. Full suite: 1651 passed, 4 xfailed, 0 errors, coverage 74.96% (was 1634 passed with 1 collection error). Assertions are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
The manifest pinned each active contract by sha256, so any incidental edit re-stamped it. `app/services/gate_client.py` is the current instance: 843db96 (#203) changed the file without updating its digest, and every commit since failed Architecture Compliance, Architecture Compliance Gate, CI Gate and PR Pipeline Gate on one stale hash. VERIFICATION_MATRIX records the same class of break on 2026-08-01, re-stamped by hand after a shared formatter/loader pass. Each entry now carries `contract_version: "1.0.0"` in place of `sha256`, using the MAJOR.MINOR.PATCH vocabulary docs/contracts/node.constitution.yaml already declares rather than a new one. The manifest itself goes 2.0.0 -> 3.0.0, since the entry schema changed incompatibly. verify_contracts.py validates the new field instead of hashing: present, stamped, and well-formed. Every other check is untouched — the contract file must exist, each required_ref must exist, and each must cite the contract path. The trade is stated, not hidden, in both the module docstring and the matrix: this gate no longer detects silent content drift. Whether an edit changed the contract is now a review judgement. Nothing else was relaxed to reach green. Verified the gate still discriminates — it fails, with the precise message, on a removed contract_version, a malformed one ("v1.0"), an unstamped placeholder, a missing contract file, and a missing required_ref; and passes only on the restored manifest. No stamper script existed to update (the digests were hand-maintained), and tools/payload_contract_compiler.py computes its own payload hashes without reading this manifest, so it is unaffected. verify_contracts: 12/12 pass. Suite: 1651 passed, 4 xfailed, coverage 74.96%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
Both were red on main and both gate the CI Gate / PR Pipeline Gate rollups, which print their own inputs and fail on nothing else. Semgrep — one blocking finding, semgrep.float-requires-try-except at app/services/gate_client.py:67: a bare float() on a caller-supplied timeout. Fixed with safe_float from app/utils/safe_convert.py, which is how app/engines/graph_sync_client.py:48 already calls into the same function, so this adopts the existing pattern rather than adding one. Its 0.0 default is rejected by GateClientConfig.timeout_seconds (gt=0.0), so a bad budget fails closed at validation instead of silently becoming a zero-second timeout. Verified: semgrep --config .semgrep/ --error exits 0 on the repaired tree and reproduces the exact CI finding at exit 1 when the bare float() is restored. pip-audit — PYSEC-2026-3740 in nltk 3.10.3, ignored with justification rather than fixed, because there is nothing to fix to. The advisory states "Patched versions: Not yet patched"; 3.10.3 is the latest nltk on PyPI and safety 3.8.1 (also latest) still requires nltk>=3.9, so no upgrade path exists. nltk reaches CI only as a transitive dependency of the `safety` scanner in requirements-ci.txt -- it appears in neither pyproject.toml nor requirements.lock, so it never reaches a runtime or production image, and the advisory's impact (pathsec sandbox bypass via model-persistence APIs) needs nltk to be called, which EIE never does. The flag names the single CVE, and both call sites carry the reason and the removal trigger. safety itself is unchanged and still runs, non-blocking, as before. gate_client.py is a manifest contract, so its contract_version goes 1.0.0 -> 1.0.1 under the scheme added in 79c13c8 -- a defensive-conversion change with no API change. No scanner was disabled or scoped down, and no test was touched. Verified: pip-audit --desc reproduces "Found 1 known vulnerability" at exit 1 and reports "No known vulnerabilities found, 1 ignored" at exit 0 with the flag. Lint, format, contracts (12/12) pass; suite 1651 passed, coverage 74.96%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
Editing ci.yml in 59fdeb0 pulled that file into the PR's Sonar new-code scope, taking the quality gate from 1 finding to 7. Four are S8541 in ci.yml (48/106/160/215) — the same "omitting --only-binary :all:" rule already fixed in pr-pipeline.yml. Two are S8544 in pr-pipeline.yml (417/489), "using dependencies without locking resolved versions": the earlier fix added --only-binary but left `--upgrade pip` unpinned, which satisfies one rule and trips the other. All ten `python -m pip install --upgrade pip` / `--only-binary=:all: --upgrade pip` lines across both files now read: python -m pip install --only-binary=:all: "pip==26.2.1" which satisfies both rules the way this repo already does it — the pinned-wheel block at pr-pipeline.yml:494, commented "satisfy Sonar githubactions S8541/S8544", pairs --only-binary with exact == pins and is not flagged. 26.2.1 is the current pip release; verified the form installs cleanly. Sonar flagged only 2 of the 6 `--upgrade pip` lines in pr-pipeline.yml, because only those fell in new code. All 6 are fixed: the defect is identical and leaving four behind would surface them on the next edit to that file. Deliberately NOT touched: the same unpinned pattern in audit-pr-review.yml, compliance.yml, l9-constitution-gate.yml, l9-contract-control.yml, refactoring-validation.yml, sonarcloud.yml and supply-chain.yml. Those files are outside this PR's changed set; editing them would pull each into new-code scope and surface further findings, which is exactly how this PR went from 1 to 7. They are pre-existing and belong in their own change. This takes the gate from 7 findings to 1. The remainder is the already-reported S8541 on the Gate_SDK git install (now line 550), where --only-binary cannot apply to a source dependency; it still needs a SonarCloud "Safe" marking or a published wheel. Note the rule matches `python -m pip install` and not bare `pip install`, so rewriting that line would evade the check rather than fix it. Not done. Per CI policy, both checks are strictly stronger than before: binary-only install plus a locked version. No check was removed or weakened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gf6isfRck8yQg5qExWBYBc
PR Pipeline Gate Summary
✅ All checks passedReady for code review and merge. Local equivalent: |
Re: the 10 Scorecard "pipCommand not pinned by hash" threadsReplying once for all ten — they're the same finding on the same construct. Verified, correct, and not being actioned in this PR. Scorecard wants pip installs pinned by hash; Three reasons this stops here:
That last point is why I deliberately left those seven files untouched: editing a workflow pulls its whole content into Sonar's new-code scope, which is exactly how this PR's finding count went 1 → 7 before Worth doing as its own change, alongside a decision on whether the maintenance cost of hash-pinning a tool like pip is one this repo wants. Happy to take it if you'd like it done. Generated by Claude Code |
PR Remediation — Cycle 1 SummaryCommit: Fixed (0)none Deferred (12)
Acknowledged (0)none Disagreed (3)
Local verify: Skipped — digest BLOCKED; no source edit this cycle. validate_sdk_pin.py Passed (PIN=69c6c670). | Threads resolved: 15/15 |
EIE was running three different Gate_SDK identities at once. pyproject.toml and requirements-ci.txt named 69c6c67; the packet/envelope gate step in pr-pipeline.yml installed ead0f48; requirements.lock still carried 2b2f53a2. The old validator read four manifest paths and never the workflow, so the CI-only third identity was structurally invisible to it — the packet gates validated transport against an SDK no other job in the repository used. The fix is not a fourth hand-maintained sha. Gate_SDK owns release identity and its ledger (schema v2) makes the consumer contract the moving major channel `v1`. Gate and CEG declare the same channel. - pyproject.toml, requirements-ci.txt: `@v1`. - .github/workflows/pr-pipeline.yml: `@v1`. That job installs a pinned wheel list rather than the repository dependency, so the SDK genuinely has to be named there — it just has to name the same thing everything else does. No new dependency abstraction was introduced for it. - scripts/validate_sdk_pin.py: replaces the hardcoded-sha check. CEG's moving-major validator is the semantic donor. It enumerates every surface that installs the SDK — including the workflow — and a missing surface FAILs rather than being skipped, because skipping is what hid the drift. Rejects sha, main/master, the fork, and the exact release tag as consumer declarations. `--verify-tag` resolves v1 at the canonical remote and requires the lock to match, failing closed when the remote cannot be resolved. - tools/lock_requirements.sh: comments now describe channel resolution. Behaviour is unchanged — verified that uv resolves `@v1` to a concrete object, so the existing archive+sha256 rewrite still applies and `pip install --require-hashes` is unaffected. - requirements.lock: regenerated (never hand-edited). Besides the SDK it drops neo4j and pytz. That is a correction, not a side effect: #203 removed neo4j from pyproject when EIE moved to Gate-only egress, but the lock was last generated at #202 and had carried the stale entry ever since — tests/contracts/test_dependency_contracts.py asserts EIE must not own a neo4j dependency. Seven packages floated within their existing `>=` ranges. - tests/unit/test_sdk_pin.py: main had no validator test surface. Drives the failure side — both shas EIE actually ran, v1.1.0, main/master, fork, absent surface, unhashed lock, stale lock, unresolvable remote. No network in the tests. - ci.yml: both modes run in the merge-blocking validate job. - tests/test_pr21_packet_router.py: pre-existing unused TransportPacket import. `ruff check .` covers tests/, so it would have blocked this PR. Verified: ruff check, ruff format --check (313 files), 1623 passed + 4 xfailed, coverage 74.63% (gate 71%), both validator modes against the live remote, and requirements-ci.txt installing the SDK from @v1 (resolves to 1.1.0). Known pre-existing failure, left alone deliberately: tests/services/test_gate_registration.py fails collection because it imports start_reregistration_loop/stop_reregistration_loop from app.main, which do not exist on main. Those belong to PR #212's re-registration loop, which this campaign is explicitly scoped out of absorbing. Reported rather than patched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq
#210 and this branch found the same defect — EIE resolved Gate_SDK from three executable surfaces that disagreed, and the old validator read none of the workflow — and chose opposite fixes. #210 converged them on one immutable SHA. The human-approved architecture for this campaign is version-governed compatibility, so the declaration is the moving major channel @v1 instead; #210's immutable-consumer-pin policy is superseded (03_PR_RECONCILIATION). What is kept from #210, because it was right: - The surface enumeration. #210 identified pr-pipeline.yml as a governed consumer that installs the SDK a second time; this validator reads it for the same reason. - The S8541 NOSONAR justification: --only-binary cannot apply to a git source dependency, since pip/uv abort with "Building source distributions is disabled". Carried over verbatim in substance. What changed, and why it matters: - #210 documented a PRODUCTION_LOCK_SHA gap — it knew requirements.lock carried 2b2f53a2, which was not its PIN, and deliberately did not assert it because closing the split meant regenerating the lock. That gap is now closed rather than documented: the lock is regenerated and `validate_sdk_pin.py --verify-tag` asserts it against the live channel, so a stale lock fails instead of being a known exception. - scripts/validate_sdk_pin.py: this branch's version. Same enumeration, but it requires @v1, rejects sha/branch/fork/exact-release-tag, FAILs on a missing surface rather than skipping it, and adds the networked stale-lock check. - pr-pipeline.yml packet gate: @v1. ruff check, ruff format --check (313 files), 1623 passed + 4 xfailed, and both validator modes against the live remote. Unchanged and still failing, deliberately: tests/services/test_gate_registration.py imports start_reregistration_loop/stop_reregistration_loop from app.main, which exist on neither main nor #210. That is PR #212's re-registration loop, explicitly out of scope for this campaign. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq
SonarCloud pythonsecurity:S8705 on the new --verify-tag code, and it is a real finding, not a false positive. Passing argv as a list and never invoking a shell stops *command* injection but not *argument* injection: `git ls-remote --upload-pack=<cmd> <repo>` executes <cmd>, so a --remote value beginning with `-` is an execution vector on its own. Two independent guards, because either alone is a single point of failure: - safe_remote() rejects an empty remote or one starting with `-`, before it reaches git. - --end-of-options is passed so git treats the remote as a positional even if the guard is ever loosened. The rejection fails closed through the same path as any other unresolvable channel, but names which of the two reasons it was rather than reporting a generic resolution failure. Tests cover both directions: a canonical https remote and a local fixture path are accepted, and --upload-pack=..., -u, --exec=sh and "" are refused. The Gate_SDK test drives it end to end through the CLI and asserts the payload file was never created. Introduced by my own --verify-tag commit on this branch; SonarCloud was green on the previous head and red on mine. The same code was written into all four validators, so all four carry this fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq
My previous commit guarded the remote with a negative check and
--end-of-options. That is correct on the merits but SonarCloud still
reported pythonsecurity:S8705, and it was right to: a `startswith("-")`
rejection is not a sanitizer, so the argparse value still reached git
having only been filtered, not replaced. On Gate_SDK the report simply
moved from the resolver to the _git() helper.
The real problem was the flag existing at all. `--remote` let an
operator point a release-identity check at a repository that is not
Gate_SDK — which is precisely what this contract exists to prevent. So
the flag is gone:
- main() passes the CANONICAL_REMOTE module constant. No command-line
argument reaches git.
- safe_remote() returns the constant on an equality match, so the value
handed to the sink is provably not derived from any input. A
non-canonical value is only accepted if it is an existing directory —
the local fixture repositories the tests use — and a `-`-leading or
empty value is still refused outright. --end-of-options stays as the
third layer.
- The Gate_SDK tests that drove --verify-tag through the CLI now call
validate(..., remote=...) in process, so the fixture path is a
function parameter rather than a command-line argument.
This narrows what the tool can be pointed at, which is the behaviour the
release-identity contract wanted in the first place. Nothing about the
v1 channel, the lock comparison or the fail-closed semantics changes.
Verified in every repo: ruff, ruff format, the validator suites
(Gate_SDK 22, Gate 126, CEG 15, EIE 25 passed) and --verify-tag against
the live remote still reporting
e9f829f982110be13752da8f18c7a9692e8ed908.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq
My merge resolution reformatted the packet-gate install onto a `\` continuation. Sonar reports githubactions:S8541 and S8544 against the line the command starts on, so moving the marker to the continuation line silently stopped suppressing them — the annotation looked present and did nothing. Restored to one line, with a comment saying why it must stay that way. The justification is also extended to S8544, which is new and is not a defect: "without locking resolved versions" is exactly the contract. @v1 is Gate_SDK's moving major channel, and pinning this gate to a resolved sha is what put three different SDKs in this repository in the first place. Reproducibility lives in requirements.lock, which carries the hash-verified archive production installs, and scripts/validate_sdk_pin.py --verify-tag fails closed the moment that lock stops matching the channel. pr-pipeline.yml parses, both validator modes pass against the live remote, 25 passed in tests/unit/test_sdk_pin.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq
The previous commit assumed the trailing `# NOSONAR` on this line worked and had only been displaced by my `\` continuation. It never worked: Sonar still reports githubactions S8541 and S8544 against this exact line with the marker present, and #210 was already Sonar-red before this branch moved the ref to @v1. NOSONAR does not apply to githubactions rules here. A marker that reads as a suppression and is not one is worse than no marker — it is the same defect class as an error message naming the wrong violation. Removed it, and replaced the comment with what is actually true about each finding: - S8541 (--only-binary) cannot apply to a git source dependency and is pre-existing on this line. - S8544 ("without locking resolved versions") is new with @v1, and it is the contract rather than a defect: pinning this gate to a resolved sha is precisely what put three different SDKs in this repository. Reproducibility lives in requirements.lock, and --verify-tag fails closed the moment that lock stops matching the channel. Clearing the pair needs a SonarCloud policy decision — accepting the two issues on this line, or scoping the rules — not a code change here. Said so in the file instead of leaving it looking handled. pr-pipeline.yml parses, both validator modes pass against the live remote, 25 passed in tests/unit/test_sdk_pin.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq
PR Remediation — Cycle 1 SummaryCommit: Fixed (0)none Deferred (1)
Acknowledged (0)none Disagreed (0)none Local verify: NotApplicable | Threads resolved: 1/1 |
…uite coverage floor from addopts, mark the integration suite (#211) * fix(tests): make every subset test target runnable — drop the whole-suite coverage floor from addopts, mark the integration suite Two defects in the repository-native test surface that no open PR addresses. Both were found by running the complete test surface, not just the default command, and both make a documented `make` target impossible to pass rather than reporting a failing test. 1. pytest.ini — every subset invocation failed on a whole-suite coverage floor `addopts` carried `--cov=app --cov-fail-under=71`. Coverage of the whole `app` package is a whole-suite property, so every subset run failed on the floor with zero failing tests: make test-unit 176 passed → exit 2 (coverage 33.89%) make test-compliance 37 passed → exit 2 (coverage 4.31%) make test-ci 17 passed → exit 2 (coverage 4.31%) make test-contracts 7 passed → exit 2 (coverage 4.31%) make test-integration 0 run → exit 2 (coverage 19.53%) `make agent-check` — "THE universal gate. Agents run this before every commit" — runs two of those as gates 4/8 and 5/8, so it could never pass either. The floor is owned by the invocation, not by this file, and the repository already says so in three independent places: every full-suite run in CI passes the coverage flags explicitly (ci.yml, pr-pipeline.yml, sonarcloud.yml, refactoring-validation.yml); CI's own subset runs already neutralise this file with `-o addopts=""` (l9-constitution-gate.yml, pr-pipeline.yml Tier 2 steps); and `make test-all` passes them explicitly. Nothing depended on addopts to supply coverage. Removed the coverage flags and recorded why in a comment so they are not reinstated. The 71% gate is unchanged everywhere it is enforced — verified by re-running the CI command: 1651 passed, coverage 74.96%, floor reached. 2. tests/integration/*.py — `make test-integration` could select nothing pytest.ini declares the `integration` marker and the Makefile target filters `-m integration`, but no test under tests/integration/ ever carried it, so the target deselected all 30 tests and ran none. Two of the three layers agreed on the contract; the test files were the outlier, so the marker is declared there. Added module-level `pytestmark = pytest.mark.integration` to all four files. Markers do not deselect by default, so full-suite runs are unaffected (1655 collected, unchanged). Stacked on #210 (chore/pin-gate-sdk-v1), bottom-up merge order. This branch originally also carried fixes for main's collection error, the ruff F401, the Semgrep float finding and the contract-manifest mismatch; #210 already fixes all four — and supersedes the manifest one by pinning contracts on contract_version instead of a file digest — so those were dropped rather than duplicated into a conflicting PR. What remains here is disjoint from #210 by construction: it touches pytest.ini and tests/integration/ only, neither of which #210 modifies. Validation on this branch (Python 3.12, the interpreter .python-version, CI and the devcontainer all declare; dockerd started and proven with a container run): CI-equivalent full suite 1651 passed, 4 xfailed, 0 failed, coverage 74.96% pytest --collect-only 1655 collected, exit 0 make test / test-unit / test-compliance / test-ci / test-contracts all exit 0 (all exited 2 before) make test-integration 30 passed (0 run before) ruff check / ruff format --check exit 0 semgrep --config .semgrep/ --error exit 0 tools/verify_contracts.py RESULT: PASS tools/payload_contract_compiler.py PASS Pre-existing and deliberately not absorbed: mypy reports the same 37 errors in 22 files and tools/audit_engine.py --strict the same 10 CRITICAL / 0 HIGH as the base — both non-blocking in the workflows that gate a merge (ci.yml and pr-pipeline.yml wrap mypy, compliance.yml wraps the audit). No test was skipped, xfailed, deleted, or had an assertion weakened to reach green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FjeFxJoqBq7h1UhJg4BAFV * fix(coverage): put the C-15 71% floor in .coveragerc so no --cov run can lose it Addresses the P1 on #211: removing --cov-fail-under from pytest.ini addopts left two whole-suite consumers with no floor. The finding is correct. Verified against the primary sources, not inferred: - AGENTS.md:179 — "C-15 | Coverage >= 71% — never lower the threshold | HIGH" - .github/workflows/sonarcloud.yml:89-97 passed --cov=app --cov=engine and both --cov-report flags but NO --cov-fail-under. It was floored only by addopts, so removing them dropped the gate on a real CI job. - Makefile:22 `test:` is a whole-suite run (pytest tests/) with no --cov flags and was likewise floored only by addopts. The previous pytest.ini comment claimed CI "passes the coverage flags explicitly on every full-suite run". That was wrong for sonarcloud.yml, which passes the report flags but not the threshold. Corrected. Fix — move the floor down a layer instead of back into addopts: .coveragerc [report] fail_under = 71 coverage.py's own config applies to any run that produces a coverage report, so a workflow can no longer drop the gate by forgetting a command-line flag — which is exactly how sonarcloud.yml came to have none. A subset run passes no --cov at all, produces no report, and is unaffected, so the subset targets this PR fixes stay runnable. This is strictly stronger than the original addopts placement for every --cov consumer, and it makes the threshold single-homed (pyproject.toml already carried an inert fail_under = 71 that .coveragerc overrides; the two now agree). Also added --cov-fail-under explicitly to sonarcloud.yml, with a COVERAGE_THRESHOLD env var matching ci.yml's vars.COVERAGE_THRESHOLD || '71' pattern, so the intent is visible at the call site and not only in config. Proved, in this order: 1. The floor bites: a subset run WITH --cov now fails — "ERROR: Coverage failure: total of 4.31 is less than fail-under=71.00" (exit 1). Before this commit that same command exited 0. 2. sonarcloud.yml's exact command passes: 1651 passed, 4 xfailed, "Required test coverage of 71.0% reached. Total coverage: 74.96%" (exit 0). 3. Subsets stay unaffected: make test-unit (176), test-compliance (37), test-ci (17), test-contracts (7), test-integration (30) all exit 0. 4. CI-equivalent full suite: 1651 passed, coverage 74.96%, exit 0. 5. Every workflow YAML still parses (the ci.yml validate job's own check). 6. ruff check / ruff format --check / semgrep / verify_contracts / payload_contract_compiler: all exit 0. 1655 tests collected, exit 0. Not changed: Makefile:22 `test:`. The root Makefile is append-only under L9 rule 00-global, and a one-line recipe edit is not an append. C-15's threshold value is not lowered there — `make test` is the documented "Quick" dev loop, is invoked by no workflow and gates nothing, and it now measures no coverage rather than passing a lowered floor. The one-line patch is proposed on the PR thread for the author to take. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FjeFxJoqBq7h1UhJg4BAFV --------- Co-authored-by: Claude <noreply@anthropic.com>
|
#212 had gone `dirty` against main: #210 landed the Gate_SDK release-identity contract on 2026-09-20 and this branch carried its own parallel attempt at the same thing, so seven files conflicted. Resolution, by file and by reason: - scripts/validate_sdk_pin.py, tests/unit/test_sdk_pin.py, tools/lock_requirements.sh -> main. These are the landed contract. This branch's versions were an independent run at the same problem written before #210 merged; keeping them would fork the validator across the constellation. - pyproject.toml -> main's comment, plus this branch's EIE-001 finding appended. Both sides already declared @v1, so only the prose differed. Main's text is what Gate and CEG now carry verbatim; the EIE-001 note (pyproject said 69c6c67 while the lock said 2b2f53a2, and the two differ by the GateClientConfig fix that loads L9_VERIFYING_KEYS_JSON) is a concrete instance of the split that text describes, so it is worth keeping beside it. - app/services/gate_client.py -> both sides. The conflict was structural, not semantic: main added a `safe_float` import in the same region where this branch added `_gate_url_visible_to_sdk`. The import moved to the first-party group and the window stayed. At the call site main's `safe_float(timeout_seconds)` wins over this branch's bare pass-through: safe_float does not trip semgrep.float-requires-try-except (it is the try/except), and its 0.0 default is rejected by GateClientConfig.timeout_seconds (gt=0.0), so a malformed budget fails closed instead of becoming a silent zero-second timeout. - tools/l9_enrichment_manifest.yaml -> main's field. This branch re-stamped four `sha256:` digests; main migrated the manifest to `contract_version:`, and tools/verify_contracts.py now reads only the latter. The digests were dead data. Fixes eie-reregistration-loop-import. The bug was real and it was not this branch's. #203 (843db96, "Migrate EIE to Gate-only egress") deleted _reregistration_loop / start_reregistration_loop / stop_reregistration_loop from app/main.py and left the fifteen lines of tests/services/test_gate_registration.py that import them, so the module raised ImportError at collection on main from 843db96 until #210, which deleted the orphaned block. Deleting it was the right call for a PR that could not carry the implementation, but it left the behaviour itself missing: a node whose Gate registration lapses has had no way back short of a process restart since #203. This branch restores the implementation, so the tests get their symbols back. The merge therefore keeps this side of that hunk — 25 tests in the module, four of them exercising the loop directly (off when registration is off, off at zero interval, re-registers and feeds readiness, survives a raising attempt). The signing-posture hunks below it resolve to main instead: `usefixtures` versus a fixture parameter is the same behaviour twice, and main's spelling is what landed. Its docstring is merged with this branch's observation that app/main.py warms the lru_cache at import time, which is the specific reason the fixture has to exist. Contract versions bumped where the contract actually moved: app/main.py 1.0.0 -> 1.1.0 (re-registration lifecycle added), app/engines/orchestration_layer.py 1.0.0 -> 1.1.0 (run_outcome_feedback and the module-level GraphSyncClient removed), app/services/gate_client.py 1.0.1 -> 1.1.0 (scoped GATE_URL window). app/engines/handlers.py stays 1.0.0 — its only change is a docstring. Verified on the merged tree: ruff check clean, ruff format clean over 318 files, tools/verify_contracts.py 12/12, scripts/validate_sdk_pin.py PASS offline and with --verify-tag (v1 == lock e9f829f982110be13752da8f18c7a9692e8ed908), and pytest 1716 passed / 4 xfailed / 0 failed — including the 25 in tests/services/test_gate_registration.py that could not be collected at all before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq




Align SDK pin to the moving major tag
@v1(Gate_SDK v1.1.0 @ main).Replaces the commit-SHA pin so
@v1auto-adopts future v1.x releases without a PR in every consumer. Wire schema (transport/packet.py) verified identical to the prior pin;GateClient.executepresent.Part of the coordinated Gate/CEG/EIE/Odoo seam alignment.