Skip to content

Add IdeaOS portfolio graph domain and hydration seam - #262

Merged
cryptoxdog merged 32 commits into
mainfrom
feat/idea-portfolio-domain
Sep 19, 2026
Merged

cryptoxdog merged 32 commits into
mainfrom
feat/idea-portfolio-domain

Conversation

@cryptoxdog

@cryptoxdog cryptoxdog commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Objective

Add the CEG-owned idea-portfolio domain and the first owner-native hydration seam for IdeaOS semantic IdeaGraphProjection artifacts.

Authority boundary

  • IdeaOS remains authoritative for idea identity, source lineage, and lifecycle truth.
  • CEG owns projection persistence, graph intersections, leverage matching, and portfolio ranking evidence.
  • Hydration consumes semantic projections only. It never parses raw Ideas/ filenames, ZIP names, or invents Idea-to-Idea source truth.
  • No new graph client, queue, service, transport authority, or ranking engine.

Runtime design

  • idea_portfolio_enabled defaults false.
  • DomainPackLoader hides/rejects the domain while disabled, so normal match reads fail closed.
  • The hydration CLI checks the same flag before connecting to Neo4j, and IdeaPortfolioHydrator has a second fail-closed admission guard.
  • Generic SyncGenerator is deliberately not exposed for this domain because it cannot preserve variable relationship types, relationship-level provenance, replacement semantics, tombstones, or corpus revision authority.
  • One hydration envelope executes inside one managed Neo4j write transaction: lock revision state -> compare parent -> apply all upserts/tombstones -> advance revision -> commit once.
  • Exact revision replay returns reused without a second semantic mutation.

Epistemic contract

All source assertions are preserved on the graph with evidence_state and source references. Ranking admits only source-backed VERIFIED and SUPPORTED_INFERENCE assertions. HYPOTHESIS and UNKNOWN remain visible graph context but cannot silently become numeric ranking evidence.

The Python boundary models use internal schema_id fields aliased to wire key schema; digests serialize with aliases so wire compatibility is retained without shadowing Pydantic BaseModel.schema.

Files

  • domains/idea-portfolio/spec.yaml
  • engine/config/loader.py
  • engine/config/settings.py
  • engine/sync/idea_portfolio.py
  • tools/hydrate_idea_portfolio.py
  • tests/unit/test_idea_portfolio.py

Safety / consistency

  • explicit feature gating for reads and writes
  • strict CEG admission validation
  • evidence-state preservation and rank admission
  • content-addressed shared facets
  • explicit tombstones
  • singleton hydration-state identity in ontology
  • parent-linked deterministic graph revisions
  • one-transaction batch atomicity
  • self/inactive candidate gates
  • scoring weights sum to 1.0
  • no generic sync bypass
  • no fake PageRank/centrality capability
  • reviewable additions kept below repository blocking threshold

Validation target

  • python tools/validate_domain.py domains/idea-portfolio/spec.yaml --strict
  • unit coverage for dormant/enabled domain behavior, gate/scoring compilation, wire aliases, epistemic rank filtering, assertion preservation, deterministic revisions, exact replay, and revision conflicts
  • repository Ruff, mypy, pre-commit, contract, security, and PR-policy gates

Copilot AI lite review requested due to automatic review settings September 8, 2026 23:38
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T23:44:34.362911Z 207a25b PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

PR Too Large
Reviewable additions: 1253
Limit: 1000 added lines
(deletions: 246; total churn 1499 is warn-only)
Action Required: Break into smaller, atomic PRs

📋 Best Practices for Large Changes

  1. Refactoring + Features: Separate into 2 PRs
  2. Multiple Features: One PR per feature
  3. Database + Code: Separate migration from logic
  4. Generated Code: Exclude it from reviewable-size accounting

🚫 This PR is blocked until reviewable size limits are met.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

L9 Audit Harness Report

  • Generated: 2026-09-19T00:30:03.091770+00:00
  • Repo root: /home/runner/work/Cognitive.Engine.Graphs/Cognitive.Engine.Graphs
  • Overall result: ✅ PASSED
  • Exit code: 0

Step Results

Step Status Exit Code Notes
Architecture Audit ✅ Passed 0
Spec Coverage ✅ Passed 0
Contract Wiring ✅ Passed 0

Architecture Audit Findings

Severity Count
🔴 CRITICAL 0
🟠 HIGH 0
🟡 MEDIUM 17
🔵 LOW 0

See artifacts/audit_report.md for full details.

Spec Coverage

  • ✅ Implemented: 37
  • ⚠️ Partial: 9
  • ❌ Missing: 0
  • Total features: 46
Category Implemented Partial Missing Total
gates 10 0 0 10
scoring 7 0 0 7
v1.1_node 2 0 0 2
v1.1_edge 2 0 0 2
v1.1_action 0 2 0 2
v1.1_scoring 1 1 0 2
action_handler 0 6 0 6
gds_algorithm 5 0 0 5
research_pattern 10 0 0 10

See artifacts/coverage_report.md for full details.

Next Steps

All checks passed. Safe to merge.

Comment thread engine/sync/idea_portfolio.py Fixed
Comment thread engine/sync/idea_portfolio.py Fixed
Comment thread engine/sync/idea_portfolio.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The domain spec currently exposes a generic sync endpoint that can bypass the intended hydration validation/write seam, and there are a couple of correctness/quality issues (CLI path robustness, unused constant) that should be addressed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces the new CEG-owned idea-portfolio domain pack and a dedicated hydration seam that ingests IdeaOS IdeaGraphProjection artifacts into Neo4j with revision-chained, resumable semantics. It extends the engine with a domain-specific persistence compiler while keeping matching on the existing generic match path.

Changes:

  • Add the idea-portfolio domain spec with ontology, gates, and scoring dimensions for cross-idea portfolio matching.
  • Implement a dedicated hydrator/compiler (engine/sync/idea_portfolio.py) to validate projections, compile facet IDs, and apply revision-chained hydration batches.
  • Add a CLI tool, unit tests, and documentation for validating/applying hydration envelopes and explaining the authority boundary.
File summaries
File Description
tools/hydrate_idea_portfolio.py Adds a CLI to validate (dry-run) or apply IdeaOS hydration envelopes to the idea-portfolio graph.
tests/unit/test_idea_portfolio.py Adds unit coverage for domain loading/compilation, projection validation, query compilation, and revision chaining behavior.
engine/sync/idea_portfolio.py Implements the IdeaOS projection boundary models, deterministic digests, and the domain-specific hydration writer.
domains/idea-portfolio/spec.yaml Introduces the idea-portfolio domain pack (ontology, gates, scoring, compliance/capabilities).
docs/idea-portfolio.md Documents the authority boundary, hydration contract, revision chain, and matching semantics for the new domain.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread domains/idea-portfolio/spec.yaml Outdated
Comment thread tools/hydrate_idea_portfolio.py Outdated
Comment thread engine/sync/idea_portfolio.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 207a25b8f2

ℹ️ 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".

Comment thread domains/idea-portfolio/spec.yaml Outdated
Comment thread domains/idea-portfolio/spec.yaml Outdated
Comment thread engine/sync/idea_portfolio.py Outdated
Comment thread engine/sync/idea_portfolio.py Outdated
Comment thread tools/hydrate_idea_portfolio.py Outdated
@sonarqubecloud

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

Comment thread .github/workflows/l9-analysis.yml Fixed
Remediates CEG-262-002 (RELIABILITY, High) and CEG-262-003 (REVIEW, Low)
from audit ceg-open-prs-2026-09-17, against PR #262 source head
0058b0e.

CEG-262-002 — the --apply path checked the feature flag, loaded the domain,
connected the driver and called IdeaPortfolioHydrator without ever running
init_schema or verifying the state-id uniqueness constraint (E007). The
hydrator serialises revisions with MERGE on IdeaPortfolioHydrationState
{state_id} (E008), and MERGE alone is not single-node-safe under concurrent
load without a uniqueness constraint on the merged property (E010). So two
concurrent initial hydrations could each create a canonical state node, each
read current_revision=null, and each commit a child revision.

The apply path now runs the repository's existing schema-init contract — the
same _init_schema the admin/init_schema subaction invokes, which provisions
the constraint from the ontology's required state_id (E009) — and then
verifies the constraint actually exists, failing closed if it does not.
Verification is not redundant: _init_schema logs and swallows per-constraint
failures, so calling it proves nothing on its own.

The label, property and SHOW CONSTRAINTS query now have one definition in
engine/sync/idea_portfolio.py, alongside a pure predicate over the returned
rows. Neo4j 5 names node uniqueness NODE_PROPERTY_UNIQUENESS (4.x called it
UNIQUENESS); NODE_KEY also satisfies the precondition.

The check is deliberately outside the write transaction: the hydrator's
transaction statement sequence is unchanged, so the existing atomicity and
replay tests keep their exact tx.run counts.

CEG-262-003 — GraphWriter.execute_write carried a bare ellipsis body, the
subject of the one current unresolved review thread (E012). Replaced with the
repository-accepted Protocol form used by engine/hoprag/indexer.py GraphStore:
class docstring, method docstring with Args/Returns, ellipsis on its own line.
No behaviour change.

Scope: strictly the two write surfaces the handoff allows. The bundle grants
no publish, merge or policy authority, and PRESERVE-L9-ANALYSIS-OWNER remains
VIOLATED on this branch — that is CEG-262-001, CI_PIPELINE-owned, untouched here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

Copy link
Copy Markdown
Collaborator Author

Pushed audit remediation: 0058b0e..ef4c5f4

Heads up — I pushed one commit to this branch at the repository owner's direction, remediating two work units from audit ceg-open-prs-2026-09-17. Fast-forward only; no rebase, amend, or force-push, so existing checkouts stay valid.

ef4c5f4fix(idea-portfolio): gate hydration on state_id uniqueness, touching only tools/hydrate_idea_portfolio.py and engine/sync/idea_portfolio.py (the audit's write allowlist for these units).

CEG-262-002 — RELIABILITY, High, merge-blocking

_apply checked the feature flag, loaded the domain, connected the driver and called IdeaPortfolioHydrator without ever running init_schema or verifying the state-id uniqueness constraint. The hydrator serialises revisions with MERGE (state:IdeaPortfolioHydrationState {state_id: ...}), and MERGE is not single-node-safe under concurrent load unless a uniqueness constraint covers the merged property. Two concurrent initial hydrations could therefore each create a canonical state node, each read current_revision = null, and each commit a child revision — forking the chain.

The apply path now runs the repository's existing schema-init contract (the same _init_schema the admin/init_schema subaction invokes, which provisions the constraint from this domain's required state_id), then verifies the constraint actually exists and fails closed otherwise. The verification is not redundant: _init_schema logs and swallows per-constraint failures, so calling it proves nothing on its own.

Two details worth a reviewer's eye:

  • The check runs outside the write transaction, so the hydrator's tx.run sequence is unchanged and the existing atomicity/replay tests keep their exact call counts. tests/unit/test_idea_portfolio.py still passes untouched.
  • Neo4j 5 names node uniqueness NODE_PROPERTY_UNIQUENESS (4.x called it UNIQUENESS); NODE_KEY also satisfies the precondition. Both are accepted; the 4.x spelling is deliberately not, since it would be a false positive on a v5 server.

This finding is not fully closed. Its second closing validation — a real-Neo4j concurrency test at tests/integration/test_idea_portfolio_hydration.py — was not executed: that file does not exist, and tests/integration/ was a read-only surface under the handoff, so creating it needs a widened allowlist. The schema-precondition half is proven; the concurrency half is not.

CEG-262-003 — REVIEW, Low

GraphWriter.execute_write carried a bare ellipsis body, the subject of the "Statement has no effect" thread. Replaced with the form already accepted in this repository (engine/hoprag/indexer.py::GraphStore): class docstring, method docstring with Args/Returns, ellipsis on its own line. No behaviour change. I have not resolved the thread — that is the author's call.

Validation on ef4c5f4

ruff check .          All checks passed!
ruff format --check   366 files already formatted
mypy engine/          Success: no issues found in 134 source files
pytest tests/unit/    1425 passed, 7 skipped
pytest tests/ -n auto 2010 passed, 10 skipped, 56 xfailed, 25 errors

The 25 errors are tests/integration/ and tests/performance/ fixtures that need a Docker daemon for testcontainers-neo4j; none is a code failure.

Not touched, deliberately

CEG-262-001 remains open and is not addressed here. This branch still deletes .github/workflows/l9-analysis.yml (186 lines), so PRESERVE-L9-ANALYSIS-OWNER is still VIOLATED. That work unit is CI_PIPELINE-owned with no mutation authority, and the workflow is a non-authorizing surface — restoring it is the owner's call, not something to fold into this push.


Generated by Claude Code

Comment thread engine/sync/idea_portfolio.py Fixed
…tocol

github-code-quality re-flagged "Statement has no effect" on the ellipsis at
engine/sync/idea_portfolio.py after the previous push, proposing
`raise NotImplementedError`. The earlier fix matched the form used by
engine/hoprag/indexer.py GraphStore (docstring plus a bare `...`), which the
bot still counts as a no-op statement — so CEG-262-003's closure condition,
that the review thread actually closes, was not met.

Protocols are structural: IdeaPortfolioHydrator calls execute_write on the
concrete object it is given, never on the protocol class, so raising here
changes no behaviour and gives a clear error if it is ever invoked directly.
The 1425-test unit suite passes unchanged, including the test doubles that
implement this protocol.

Also tightens prose that duplicated the previous commit message, cutting this
remediation from +98 to +63 added lines. No logic changed: the constants, the
SHOW CONSTRAINTS predicate, the schema-init call and the fail-closed check are
byte-identical in behaviour, and the discriminating proof still shows the apply
path reaching IdeaPortfolioHydrator iff state_id uniqueness is present.

This does not clear `Enforce PR Policies`: #262 stood at +969 additions against
a 1000 limit before any of this work, so the arithmetic is reported on the PR
rather than solved by deleting the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

Copy link
Copy Markdown
Collaborator Author

6404959 pushed; and the honest position on Enforce PR Policies

Fixed — github-code-quality "Statement has no effect" (engine/sync/idea_portfolio.py, GraphWriter.execute_write). My previous push used the form already in this repo at engine/hoprag/indexer.py::GraphStore — docstring plus a bare ... — and the bot still counts that as a no-op statement. So CEG-262-003's closure condition, that the thread actually closes, was not met. Now raise NotImplementedError, as the bot proposed.

Protocols are structural, so this changes no behaviour: IdeaPortfolioHydrator calls execute_write on the concrete object it is handed, never on the protocol class. The 1425-test unit suite passes unchanged, including the test doubles that implement this protocol.

Not fixed — Enforce PR Policies, and I want to be exact about why.

My first push flipped this check from green to red. That is on me, and here is the arithmetic:

additions
#262 before any of my work (0058b0e) 969
Block threshold (additions only; deletions excluded) 1000
Headroom the branch had 31
My remediation, first push +98 → 1067 ❌
My remediation, after trimming prose +63 → 1032

I cut my footprint from 98 to 63 added lines by removing prose that duplicated the commit message. Getting to ≤31 would mean deleting the SHOW CONSTRAINTS predicate or the fail-closed verification — that is, deleting the CEG-262-002 fix itself to make a size gate pass. I am not doing that.

I also checked a hypothesis I had and it was wrong, so I am correcting it rather than leaving it implied: removing the unrelated CI/governance changes would not get this under the limit. On additions they are almost free — ci.yml +1, supply-chain.yml +1, .github/governance/quality-thresholds.yaml +6, and l9-analysis.yml is +0/−186 (the gate ignores deletions by design). The volume is the feature itself:

587 +  engine/sync/idea_portfolio.py
183 +  tests/unit/test_idea_portfolio.py
148 +  domains/idea-portfolio/spec.yaml
108 +  tools/hydrate_idea_portfolio.py

So this branch was always going to sit at ~97% of its additions budget, and any substantive remediation tips it over. Two ways out, both yours, not mine: split the feature per the policy's own advice, or have the owner adjust the threshold for this PR. Tell me which and I will do the work.

Not mine, for the record — SonarCloud Quality Gate, C Security Rating on New Code. I queried the analysis rather than guessing. None of the 10 open issues is attributable to my commits:

  • tools/hydrate_idea_portfolio.py:34 pythonsecurity:S8707 path traversal in _load() — pre-existing content of this PR; my hunks in that file are elsewhere. Worth a look regardless: it is MAJOR and is one of the two things driving the C rating. For an operator CLI whose argument is the file to read, it may be a false positive — your call.
  • .github/workflows/ci.yml ×5 and supply-chain.yml ×1 githubactions:S8541 (--only-binary :all:) — the CI baseline edits, the other driver of the C rating.
  • engine/sync/idea_portfolio.py:494 python:S3776, cognitive complexity 18 > 15 in apply() — I did not touch apply().
  • tests/unit/test_idea_portfolio.py ×2 python:S5778.

Sonar also did not have a clean baseline here: the previous run on 0058b0e reported "The last analysis has failed", so 2026-09-18 is the first completed verdict on this PR.

CEG-262-001 is still untouched and still VIOLATED — this branch deletes .github/workflows/l9-analysis.yml.


Generated by Claude Code

…ns the raise

Reverts the `raise NotImplementedError` from 6404959, which turned
`Scan for Contract Violations` and `compliance` red on this PR.

tools/contract_scanner.py rejects it:

  [STUB-001] engine/sync/idea_portfolio.py:222 (CRITICAL)
    Stub in engine/ - unimplemented code path

The two bots want opposite things. github-code-quality reads the ellipsis as
a no-op statement and proposes `raise NotImplementedError`; this repository's
own contract scanner classifies that raise as a stub in engine/ and blocks
the merge on it. The repository's contract is the authority — it is a blocking
CI gate and repository law, where the bot comment is advisory. This is also
what the audit's closure condition meant by "the repository-accepted no-op
Protocol body": the form already used by engine/hoprag/indexer.py GraphStore,
which is a docstring followed by an ellipsis, and which the scanner accepts.

Verified before pushing this time, including the gates the previous push
skipped: contract scanner clean (no violations), verify_contracts 27/27
present and wired, ruff, ruff format, mypy engine/ 134 files, 1425 unit tests,
2010 tests in CI shape.

The github-code-quality thread cannot be satisfied as proposed; answering it
on the PR rather than breaking a blocking contract to silence it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX
Comment thread engine/sync/idea_portfolio.py
…l Neo4j

Adds tests/integration/test_idea_portfolio_hydration.py, the closing
validation audit ceg-open-prs-2026-09-17 names for CEG-262-002, and fixes a
fail-open bug in the precondition that only a real server could reveal.

The bug: state_uniqueness_constraint_present accepted NODE_PROPERTY_UNIQUENESS
but not UNIQUENESS. Neo4j 5.18 — the server tests/conftest.py pins — reports
`type: 'UNIQUENESS'`; the rename to NODE_PROPERTY_UNIQUENESS is later. So on
the pinned server the predicate returned False even with the constraint
correctly created by _init_schema, and _require_state_uniqueness would have
refused every hydration permanently. Verified directly against 5.18.1:

  u1  type='UNIQUENESS'            props=['a']   # IS UNIQUE
  k1  type='NODE_KEY'              props=['b']   # IS NODE KEY
  e1  type='NODE_PROPERTY_EXISTENCE' props=['c'] # IS NOT NULL

Both spellings are now accepted, so the check works on the pinned server and
on later ones. The hand-written rows in the unit-level proof could not catch
this — they encoded the same wrong assumption as the code. That is precisely
why the audit required a real-Neo4j validation rather than a mocked one.

The tests:
- the predicate agrees with a real server before and after the constraint exists
- two concurrent initial envelopes yield exactly one commit, one rejection, one
  canonical state node, and a committed revision matching the winner's receipt
- replaying the committed envelope is reused, not a second revision
- a child naming the wrong parent revision is rejected

Run against neo4j:5.18-enterprise via the existing testcontainers fixtures:
4 passed. Contract scanner clean, ruff, ruff format, mypy engine/ 134 files,
1425 unit tests green.

Note for reviewers: this adds ~193 lines and pushes #262 further past the
1000-addition size policy, which was already failing at 1032. That gate needs
an owner decision either way; it is not a reason to omit the validation the
audit requires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

Copy link
Copy Markdown
Collaborator Author

3b16333 — CEG-262-002 concurrency validation executed, and it caught a real bug in my own fix

The audit's second closing validation for CEG-262-002 was previously unexecuted: tests/integration/test_idea_portfolio_hydration.py did not exist and no real Neo4j was available. Both are now resolved — the test exists and runs against neo4j:5.18-enterprise through the existing testcontainers fixtures. 4 passed.

It immediately found a fail-open defect in a4c5817

state_uniqueness_constraint_present accepted NODE_PROPERTY_UNIQUENESS but not UNIQUENESS. Verified directly against the server this suite pins:

server: 5.18.1
  u1  type='UNIQUENESS'              props=['a']   # IS UNIQUE
  k1  type='NODE_KEY'                props=['b']   # IS NODE KEY
  e1  type='NODE_PROPERTY_EXISTENCE' props=['c']   # IS NOT NULL

5.18 reports UNIQUENESS; the rename to NODE_PROPERTY_UNIQUENESS is later. So on the pinned server the predicate returned False even when _init_schema had correctly created the constraint, and _require_state_uniqueness would have refused every hydration permanently — a fix that fails closed into a deadlock. Both spellings are now accepted.

This is exactly why the audit demanded a real-database validation. My unit-level proof passed throughout, because its hand-written rows encoded the same wrong assumption as the code. Mocks cannot catch a contract you have mis-transcribed.

What the tests establish

  • the predicate agrees with a real server before and after the constraint exists — the regression guard for that rename
  • two concurrent initial envelopes produce exactly one commit, one rejection, one canonical state node, and a committed revision matching the winning receipt — the closure condition, discriminated rather than asserted
  • replaying the committed envelope is reused, not a second revision
  • a child naming the wrong parent revision is rejected, keeping the chain linear

Verification

pytest tests/integration/test_idea_portfolio_hydration.py   4 passed
tools/contract_scanner.py                                   no violations
ruff check . / ruff format --check .                        clean, 367 files
mypy engine/                                                134 files, no issues
pytest tests/unit/                                          1425 passed, 7 skipped

Status of the audit against this PR

Finding State
CEG-262-002 Both closing validations now executed and passing
CEG-262-003 Code closed; thread cannot be satisfied — see #267
CEG-262-001 Untouched, CI_PIPELINE-owned — #265

One consequence you should weigh

This adds ~193 lines, taking the PR to +1239 against the 1000-addition policy. That gate was already failing at 1032 and the branch arrived at 969, so it needs a decision either way — #268 lays out the options, including excluding test files from the accounting. I did not omit the validation to protect a line count.


Generated by Claude Code

SonarCloud python:S5778 on the file added in 3b16333: the `pytest.raises`
block held three calls that could throw — `_digest`, `_envelope` and the
`apply` under test — so a failure in the fixture builders would have been
indistinguishable from the rejection the test asserts.

Only `apply` belongs inside the block. Same shape as the surrounding suite.
Behaviour unchanged: 4 passed against neo4j:5.18-enterprise, ruff and format
clean, contract scanner clean.

This was the only finding SonarCloud raised against the new file; the other
ten open issues on this PR are pre-existing and attributed in earlier comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX
…refusal

Found by running the CLI end-to-end against a live Neo4j as a least-privilege
user. A database user may be denied SHOW CONSTRAINTS itself:

  Neo.ClientError.Security.Forbidden: Show constraints on database
  'idea-portfolio' is not allowed for user 'limited'

The precondition already failed closed in that case — nothing was written, the
graph stayed empty — but it surfaced as a raw neo4j driver traceback. An
operator could not tell "the constraint is missing" from "this user may not
read constraints", and those have different remedies: one is a schema init,
the other is a grant.

The driver error is now caught and re-raised as IdeaPortfolioHydrationError
naming the cause and the grant needed. Unverifiable is treated exactly like
absent — not provably safe, so it still refuses to mutate.

Verified against neo4j:5.18-enterprise, all four paths of the real CLI:
  restricted user, cannot read constraints -> refuses, 0 nodes written
  admin, constraint absent   -> _init_schema creates it, applies, 1 state node
  admin, replay same envelope -> "reused", still 1 state node
  no --apply                  -> "validated", no mutation

Contract scanner clean, ruff, ruff format, mypy engine/ 134 files, and the
full suite with a real database: 2039 passed, 10 skipped, 56 xfailed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

Copy link
Copy Markdown
Collaborator Author

cbaa90c — end-to-end verification against a live Neo4j, and one more defect found

Docker became available in my session, so I stopped reporting the integration suite as unverifiable and ran it.

The 25 "environmental" errors were genuinely environmental

I have been reporting all along that the integration and performance failures were a missing Docker daemon rather than broken code. That is now measured, not asserted:

pytest tests/ (real neo4j:5.18-enterprise)
  2039 passed, 10 skipped, 56 xfailed, 0 failed, 0 errors

tests/integration    54 passed
tests/performance     4 passed

Every one of those tests executes and passes. Nothing was skipped into a false green.

Running the real CLI found a diagnosability defect in my own fix

The audit's CHECK validation for CEG-262-002 asks that the apply path cannot reach IdeaPortfolioHydrator without state_id uniqueness. I had proven that with mocks. Against a live database, through tools/hydrate_idea_portfolio.py --apply, all four paths behave correctly:

Scenario Result
admin, constraint absent _init_schema creates it → applied, 1 state node
admin, replay same envelope reused, still 1 state node
no --apply validated, no mutation
least-privilege user refuses, 0 nodes written

The last one exposed a flaw. A restricted database user is denied SHOW CONSTRAINTS itself:

Neo.ClientError.Security.Forbidden: Show constraints on database
'idea-portfolio' is not allowed for user 'limited'

The precondition still failed closed — the graph stayed empty, so the safety property held — but it surfaced as a raw neo4j traceback. An operator could not distinguish "the constraint is missing" from "this user may not read constraints", and the remedies differ: a schema init versus a grant. cbaa90c catches the driver error and re-raises it as IdeaPortfolioHydrationError naming the cause and the grant required. Unverifiable is treated exactly like absent — not provably safe, so still no mutation.

This is the second defect that only a real database could reveal; the first was the UNIQUENESS vs NODE_PROPERTY_UNIQUENESS spelling in 3b16333. Worth noting for the record that the live run confirmed the constraint _init_schema actually creates is typed UNIQUENESS — the spelling my original code missed, which would have deadlocked hydration on this exact server.

Verification on cbaa90c

tools/contract_scanner.py   no violations
ruff check . / format       clean, 367 files
mypy engine/                134 files, no issues
pytest tests/               2039 passed, 10 skipped, 56 xfailed

Size note: this adds 11 lines, so the reviewable-size gate moves from 1240 to ~1251. That gate needs a decision either way (#268); I am not leaving a known defect in the fail-closed path to protect a line count.


Generated by Claude Code

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

cryptoxdog pushed a commit that referenced this pull request Sep 19, 2026
The enrichment assertion is red on main and takes seven checks down with it on
this PR: Test Suite, Pre-commit Hooks (via the pytest hook), Coverage (Codecov),
Quality Gate, Baseline Ratchet (Required Tests + Verdict, which records it as an
unledgered finding), and the CI Gate rollup that blocks merge.

The fix exists in open PRs #264 and #262, which make byte-identical changes to
this hunk. Porting it rather than waiting: it no-ops once main carries it.

This is the exact hunk both PRs carry, deliberately with no edits of my own.
An earlier attempt here rewrote the same assertion with an explanatory comment
inside the dict, which made it textually different and so a genuine add/add
conflict — that is what the overlap gate caught, and it was right to. Identical
text on both sides merges cleanly in a three-way merge, so this port conflicts
with neither PR.

engine/gate_egress.py documents the contract this restores: "retry is the
caller's decision and requires the idempotency key returned in the result".
All three return paths of request_enrichment honour it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nwd3DgnqkpEuaBMPKYLhF3
@cryptoxdog

Copy link
Copy Markdown
Collaborator Author

PR Remediation — Cycle 1 Summary

Commit: none | Findings processed: 9 | CI gates: NOT_RUN (no source edit; digest UNKNOWN; consumer Makefile has no precommit-repo target)

Fixed (0)

none

Deferred (0)

none

Acknowledged (8)

Finding Response
generic sync endpoint already sync.endpoints empty on cbaa90c
DomainPackLoader CWD path CLI now passes ROOT/domains
unused _RELATION_TYPES symbol removed
feature_catalog list form catalog is a YAML list
block generic sync endpoints empty
atomic hydration batch one execute_write transaction
state_id uniqueness ontology + runtime check + integration tests
idea_portfolio_enabled flag defaults False; loader and CLI both check

Disagreed (1)

Finding Reason
Protocol ellipsis typing.Protocol body; tracked in #267

Local verify: NOT_RUN (no source edit; digest UNKNOWN; consumer Makefile has no precommit-repo target) | Threads resolved: 9/9

@cryptoxdog
cryptoxdog merged commit d26194d into main Sep 19, 2026
64 of 66 checks passed
@cryptoxdog
cryptoxdog deleted the feat/idea-portfolio-domain branch September 19, 2026 14:10
cryptoxdog added a commit that referenced this pull request Sep 19, 2026
… main (#264)

* fix(test): assert idempotency_key in fail-closed enrichment result

`request_enrichment` mints the idempotency key before the GATE_URL check
and returns it on every path, including the fail-closed
`gate_not_configured` branch. The test for that branch asserted an exact
dict without the key, so it has failed since the seam-audit commit that
introduced both (#259) — CI's Test Suite is red on main HEAD for this one
assertion and nothing else.

Classified as a test defect, not a product defect: the success-path test
in the same file already asserts `result["idempotency_key"]`, so the
contract is that the key is always returned. The assertion now names the
key via `enrichment_idempotency_key(...)`, keeping the exact-dict form so
no extra field can leak in unnoticed.

Verified: tests/unit 1421 passed, 7 skipped; full suite (no Docker)
2006 passed, 56 xfailed; ruff check + ruff format + mypy engine/ clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

* test(gate-egress): assert idempotency_key on the SDK-error return

Validate & Repair surfaced that the "always replayable" contract was
asserted on the success and fail-closed returns but not on the SDK-error
one, though request_enrichment returns the key there too. Adds the
missing assertion so every failing return of request_enrichment is
covered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

* test(gate-egress): match PR #262's fail-closed hunk byte-for-byte

Open PR #262 (feat/idea-portfolio-domain) already carries the identical
fail-closed assertion fix. The only difference was an explanatory comment
on this branch, which made the same region conflict textually and blocked
the overlap gate. Dropping the comment makes both sides of the hunk
identical, so whichever PR lands first the other merges clean. The
rationale lives in the PR body and in this branch's first commit message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX

---------

Co-authored-by: Claude <noreply@anthropic.com>
cryptoxdog added a commit that referenced this pull request Sep 19, 2026
…entifiers (#271)

* fix(tests): assert the full fail-closed enrich envelope in gate_egress test

test_request_enrichment_fails_closed_without_gate_url compared the whole
result dict against a three-key literal that omitted idempotency_key, so it
failed against the implementation it was introduced alongside (#259).

engine/gate_egress.py documents the authoritative contract: "one attempt per
call; retry is the caller's decision and requires the idempotency key returned
in the result". All three return paths of request_enrichment honour that, the
sibling success-path test already asserts result["idempotency_key"], and
engine/health/enrichment_trigger.py forwards the whole envelope to its caller,
so the key is load-bearing on the fail-closed path too.

The test encoded the wrong envelope, so the test is corrected. Strict whole-dict
equality is kept — the envelope stays exactly pinned, with the expected key
derived from the module's public enrichment_idempotency_key helper rather than a
hardcoded digest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nwd3DgnqkpEuaBMPKYLhF3

* fix(compliance): stop the phone pattern matching inside opaque identifiers

handle_match builds query_id as `q_` + uuid4().hex[:12]. The PHONE value
pattern was the only one in _PII_PATTERNS without boundary guards, so any
10-digit run inside that token matched it. ComplianceEngine.redact_response
then removed the field outright (PIIHandler.redact pops the key), so the match
response silently lost query_id.

Measured on 200,000 generated ids: 1.572% were classified as a phone number.
That is a live response-shape defect, and it is what made
tests/test_handlers.py::test_match_returns_structure fail intermittently — the
assertion is correct, the engine was dropping the key.

`\b` is not usable here: the pattern can start with `+` or `(`, which are not
word characters, so a leading `\b` would break `+1 (555) 123-4567`. SSN and
IP_ADDRESS can use `\b` because they start with a digit. Lookarounds for
[0-9A-Za-z_] give the same protection without that constraint.

Verified: 0/200,000 false positives after the change, while `555-123-4567`,
`+1 (555) 123-4567`, `5551234567` and `call 555.123.4567 now` are all still
detected, as is detection by field name. Both directions are pinned by new
regression tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nwd3DgnqkpEuaBMPKYLhF3

* revert(tests): drop the gate_egress fix already carried by open PR #264

The overlap gate blocked publication: tests/unit/test_gate_egress.py
textually conflicts with PR #264 ("fix(test): repair the fail-closed
enrichment assertion that is red on main") and PR #262.

PR #264 already contains the same correction, derived independently and
byte-identical in the block it touches, and additionally asserts the
idempotency key on the SDK-error path. That PR owns this file, so carrying a
duplicate here would only add an add/add conflict for whichever landed second.

This branch keeps only the compliance repair, which neither #264 nor #262
touches. This PR therefore stacks on #264's head so the enrichment assertion
is inherited rather than duplicated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nwd3DgnqkpEuaBMPKYLhF3

* fix(tests): port the fail-closed enrich assertion fix from #264

The enrichment assertion is red on main and takes seven checks down with it on
this PR: Test Suite, Pre-commit Hooks (via the pytest hook), Coverage (Codecov),
Quality Gate, Baseline Ratchet (Required Tests + Verdict, which records it as an
unledgered finding), and the CI Gate rollup that blocks merge.

The fix exists in open PRs #264 and #262, which make byte-identical changes to
this hunk. Porting it rather than waiting: it no-ops once main carries it.

This is the exact hunk both PRs carry, deliberately with no edits of my own.
An earlier attempt here rewrote the same assertion with an explanatory comment
inside the dict, which made it textually different and so a genuine add/add
conflict — that is what the overlap gate caught, and it was right to. Identical
text on both sides merges cleanly in a three-way merge, so this port conflicts
with neither PR.

engine/gate_egress.py documents the contract this restores: "retry is the
caller's decision and requires the idempotency key returned in the result".
All three return paths of request_enrichment honour it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nwd3DgnqkpEuaBMPKYLhF3

---------

Co-authored-by: Claude <noreply@anthropic.com>
cryptoxdog added a commit that referenced this pull request Sep 21, 2026
…ersedes #280, #281, #283, #284) (#285)

* fix(c-009): add make cypher-lint and close live interpolation holes

C-009 named make cypher-lint as automated enforcement but the target did
not exist. Add the scanner, wire it to Make and pre-commit, and
parameterize the live quoted interpolations it now finds.

Issue-Remediation-Cycle: #272/cycle-1
Co-authored-by: Cursor <cursoragent@cursor.com>

* style: commit gate writer rewrites so make pr finishes once

* fix(c-009): return label-safety check as a boolean

Ruff SIM103 failed the PR writer wave on the two-return form.

Issue-Remediation-Cycle: #272/cycle-1
Co-authored-by: Cursor <cursoragent@cursor.com>

* test(c-009): cover cypher_quoted_ident wrap and reject

Carry the isolate follow-up tests onto the open PR so identifier quoting
has the same unit coverage as sanitize_label.

Issue-Remediation-Cycle: #272/cycle-1
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): declare consumer repo_class python for org-ci language detect

CEG is Python-only but had no .l9/ci.json, so REPO_CLASS=auto left Analyze (central Core) fail-closed on ambiguous SDK language detect. Consumer metadata is the documented escape hatch and does not touch workflows.

Issue-Remediation-Cycle: #273/cycle-1
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ceg): close CEG-001..009 from the Constellation E2E action log

CEG-001 supply chain. The release set kept the moving @v1 tag and closed the
finding on the lock instead: poetry.lock records resolved_reference e9f829f,
which is what every deployed image installs, and validate_sdk_pin.py now prints
that commit on PASS so a build log says which SDK object was taken. EIE's
validator enforces the same contract over its requirements.lock. The trade-off
the moving tag accepts — a live-resolving build takes whatever v1 points at
that minute — is now stated in the script rather than left to be discovered.

CEG-002 configuration. Both compose files set L9_REQUIRE_SIGNATURE=true and
neither set L9_VERIFYING_KEYS_JSON. That flag also verifies the responses Gate
signs, and Gate signs with its own key id, so as shipped CEG rejected every
signed Gate response — the E2E passed only because its harness supplied the
map. Dev carries the shared HMAC secret; prod requires the variable rather than
defaulting it, because a wrong key here is a silent trust failure.

CEG-003 configuration. Compose set L9_NODE_SPEC_PATH, which nothing reads. The
SDK reads GATE_NODE_SPEC_PATH. Registration worked only because the SDK default
happens to be engine/spec.yaml and WORKDIR is /app, so relocating the spec
would have silently had no effect until the default path stopped existing — at
which point register_with_gate swallows FileNotFoundError and returns False.

CEG-005 architecture. engine/spec.yaml now declares owner: ceg. Registration
previously succeeded only because Gate's _OWNER_ALIASES maps the node name
"graph" to "ceg": correct today, and an alias rather than a declaration.
Renaming the node would have broken registration for every canonical action at
once. Pairs with the resolve ownership entry added in Constellation.Gate.

CEG-006 + EIE-008 architecture. Two halves of one loop, neither connected.
engine/health/api.py implemented three handlers and was imported by nothing, so
request_enrichment — the entire CEG -> Gate -> EIE direction — had no trigger an
inbound packet could reach; it now hangs off `admin` alongside trigger_gds,
where operator surfaces belong. In the other direction EIE advertises and fully
implements `graph-inference-result` and no code here ever produced one, so the
feedback loop had a consumer and no producer. emit_graph_inference_result sends
it through Gate, filtering at the same 0.55 confidence floor EIE drops below —
the two sides were built to the same number and never joined. Flag-gated
(GRAPH_INFERENCE_FEEDBACK_ENABLED), because each emission spends EIE budget.

CEG-007 dependency hygiene. No import of redis exists anywhere under engine/ or
chassis/, yet redis was a requirement, a service in both compose files with
depends_on: service_healthy (so the api service could not start without it), a
CI service container in two workflows, a required env var in the env contract,
and a dependency contract claiming "Scoring result caching" and "Domain pack
cache" that were never implemented. Four contract tests asserted that
description continuously, which is what made an unused dependency look
load-bearing. All of it removed together.

CEG-008 operability. match and sync route to a Neo4j database named after the
domain id and nothing created it, so on a fresh instance every one failed until
an operator ran CREATE DATABASE by hand. GraphDriver now provisions it on first
use under AUTO_CREATE_DOMAIN_DATABASE (once per database per process,
Enterprise-only), and either way an absent database raises
DatabaseNotProvisionedError naming the database and the exact command. The name
is validated before it is quoted into the statement: CREATE DATABASE takes no
query parameter, and sanitize_label cannot be used because domain ids
legitimately contain dashes.

CEG-009 configuration. Nine specs used a flat <name>_domain_spec.yaml shape the
loader never reads — it resolves domains/<domain_id>/spec.yaml — so they looked
like available verticals and no tenant id could select them. Migrated. Two were
additionally invalid, with edges to undeclared Skill and ZipCode nodes; nothing
caught that, because a file the loader never opens is never validated either.
Loadable domains go from one to ten, idea-portfolio still behind its flag.

Verification: 1818 passed, 12 skipped, 56 xfailed across unit, contracts,
compliance, architecture and invariants. Contract 21 caught the new feature
flag as undocumented and FEATURE_GATES.md now carries both new gates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoomAnwQt9gxGhHAJJSDz2

* fix(ceg): repair kernel-surfaced drift from the CEG-007 removal

Recursive Alignment found the redis removal stopped at the declarations. The
Makefile still drove the service: `health` exec'd l9-graph-redis, `redis-shell`
opened a CLI on a container that no longer exists, `local-dbs` ran
`docker compose up -d neo4j redis` against a service absent from both compose
files, and two local-api targets exported PLASTICOS_REDIS_URL for a setting
that was deleted. docs/CI_PIPELINE.md still listed the CI redis service.

Also registered the three capabilities this branch adds in
.claude/rules/capability-registry.md. CLAUDE.md's first "Always" is to check
that registry before building, so a capability missing from it is how a second
implementation gets written — which is exactly what EIE-006 turned out to be.

Apply report: .l9/autonomy/kernel-apply.md. Suites unchanged at 1818 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoomAnwQt9gxGhHAJJSDz2

* fix(ceg): remediate PR #283 review findings and mypy CI failure

Nine findings from the PR #283 review plus the single mypy error that
turned all four CEG lint jobs red.

CI fix
- handlers.py: the inference-rule walrus reused `result`, already bound to
  dict[str, Any] earlier in handle_admin. Renamed to `inferred`.
  mypy: "Success: no issues found in 133 source files".

Correctness
- graph/driver.py: ensure_database() claimed the database name AFTER the
  await, so concurrent first-use requests each issued their own CREATE
  DATABASE. The claim now precedes the suspension point and is released on
  failure so a later attempt can retry.
- handlers.py: `rules: []` in emit_inference_feedback silently became "run
  every rule"; a bare string was iterated character by character. Absent
  key still means all rules; a non-list of non-strings is now rejected.
- gate_egress.py: get_gate_client() raises ValueError on missing or invalid
  SDK environment material. Both egress paths caught only GateClientError,
  so a configuration fault crashed the caller instead of returning the
  typed failure result every other path returns.
- health/health_report.py: _conversion_events was an unbounded list fed by
  every Seed-tier assess/report call — a leak CEG-006 made reachable, and
  one CLAUDE.md's "never create unbounded caches" covers. Now a
  deque(maxlen=10_000).
- scripts/validate_sdk_pin.py: printed "PASS ... -> None" when the tree has
  no poetry.lock. Says what is true instead.

Feature-flag discipline (C-21)
- health_* admin subactions now gate on health_api_enabled. auto_enrich_via_gate
  gates only the outbound Gate request, not assessment, reporting or
  conversion tracking, so the surface needed its own flag.
- admin feature_status reports all five Constellation-seam flags.

Domain packs — correction to the PR body
- Five of the nine packs CEG-009 made readable compile to Cypher that
  cannot execute: `type: traversal` gates written with pattern/condition
  fall back to a RELATES_TO edge no ontology declares, and scalar
  queryparams (85.0, 5, 1) are emitted as parameter NAMES, producing $85.0.
  Readable is not correct. They are gated behind
  unvalidated_domain_packs_enabled until the compiler or query schema grows
  the support they assume.
- The PR's "loadable domains 1 -> 10" is therefore
  "1 -> 5 discoverable-and-executable, plus 5 migrated-but-dormant".
- test_domain_pack_shape.py compiles every discoverable pack's gates and
  fails on either defect shape; a shrink-only test fails if a gated pack
  starts compiling cleanly, so the flag cannot become a place defects go to
  be forgotten.

Verification
- pytest tests/unit tests/contracts tests/compliance tests/architecture
  tests/invariants: exit 0 (jsonschema installed so the two previously
  skipped contract modules actually ran: 34 passed).
- mypy engine/: clean. ruff check/format clean on changed files under the
  CI-pinned rule set.
- tools/contract_scanner.py: no violations. tools/verify_contracts.py: 27/27.
- scripts/validate_sdk_pin.py: PASS Quantum-L9/Gate_SDK@v1 -> e9f829f.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoomAnwQt9gxGhHAJJSDz2

* fix(ci): restore L9 analysis and settle Protocol body + Scorecard GHCR

Restore the owner-managed l9-analysis.yml deleted in #262. Keep Scorecard
on GHCR-backed v2.4.4 (v2.4.0 still pulls gcr.io/openssf). Protocol
methods are docstring-only so STUB-001, ruff, and code-quality agree.

Issue-Remediation-Cycle: #265/cycle-1
Issue-Remediation-Cycle: #267/cycle-1
Issue-Remediation-Cycle: #269/cycle-1
Co-authored-by: Cursor <cursoragent@cursor.com>

* style: commit gate writer rewrites so make pr finishes once

* fix(c-009): AST cypher-lint scanner and value parameterization (F280-1, F280-2)

Audit findings F280-1/F280-2 on PR #280:

- tools/cypher_lint.py is now an AST scanner. Every f-string that builds
  Cypher (by text, by sink, or by compiler context) has each interpolation
  classified by syntactic role: quoted value and LIMIT/SKIP always fail;
  label, property, parameter-name and back-quoted positions must be
  validated; a bare fragment fails when it reads a raw spec value. No
  keyword precondition, multi-line f-strings analysed as one unit, and
  waivers are explicit, reasoned, and printed on every run.
- cypher_quoted_ident() is removed: it sanitized data as if it were a
  label. EnumMapGate mapping keys/values and GDS equipment type names now
  travel as $parameters via BaseGate._bind_param() / query_params.
- ExclusionGate sanitizes edge/node identifiers; _prop_ref/_param_ref
  validate identifiers; operators and composite logic pass through literal
  allow-lists aligned with the schema's validated set.
- GateCompiler validates query parameter names with sanitize_label() and
  casts numeric spec values; scoring builders validate dimension aliases
  and property/parameter names.
- sanitize_database_name() added for CREATE DATABASE quoting.

Tests cover both directions of the scanner (including the audit's
false-negative NOT EXISTS shape and false-positive module-constant MERGE),
non-identifier and injection-shaped enum values, and the live engine tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dcSADmR2nDESm69Szd1fT

* fix(driver): make concurrent first-use callers await in-flight provisioning (F283-1)

ensure_database() claimed the name before awaiting CREATE DATABASE, so a
second request arriving mid-flight saw the name, returned True, and ran its
domain query against a database that did not exist yet. In-flight
provisioning is now a per-database asyncio.Task kept apart from the ensured
set; every concurrent caller awaits (shielded) the same task and receives
its result, exactly one CREATE is issued, a failed CREATE is retried on the
next call, and cancelling one waiter does not cancel the CREATE.

Tests prove one CREATE and no domain query before provisioning completes
under concurrency, shared outcome, retry after failure, and cancellation
isolation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dcSADmR2nDESm69Szd1fT

* ci: keep L9 analysis central; test: enforce docstring-only Protocol bodies (F284-1, F284-2)

- Drop the restored consumer-local .github/workflows/l9-analysis.yml.
  Current l9-ci-core owner law (consumer_copy_required=false) prohibits
  copied L9 workflows in consumers; Analyze (central Core) already passes
  on this repository, and the local duplicate only produced a
  startup_failure plus Scorecard/Sonar alerts.
- test_protocol_bodies.py now strips the optional leading docstring and
  fails on any remaining statement with its location, instead of only
  recognising pass / raise NotImplementedError / ellipsis. Negative fixtures
  cover return, assignment, arbitrary raise, calls, expressions and
  compound statements.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dcSADmR2nDESm69Szd1fT

* fix(ci): validated numeric casts, logger docstring examples, no bare awaits in tests

- Semgrep Policy Check (float-requires-try-except): numeric spec values now
  pass through engine.utils.security.cypher_number(), which does the float()
  conversion inside try/except and raises ValueError with the offending
  value; cypher-lint treats it as a validator alongside sanitize_label.
- Check Terminology Consistency (print( on changed files): docstring usage
  examples in hoprag/indexer, scoring/helpfulness, scoring/importance,
  traversal/multihop and traversal/pseudo_query use logger.info; the
  Protocol-body fixture uses len("x") as its call statement.
- github-code-quality "statement has no effect": the provisioning tests
  assert on the awaited task results instead of bare await statements.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dcSADmR2nDESm69Szd1fT

* feat(deps): detect a stale Gate_SDK lock, and enforce the pin in CI

CEG already declares the moving major channel correctly — pyproject
rev = "v1", requirements.txt @v1, poetry.lock reference = "v1" — and
none of that changes here. What was missing is the one failure a moving
tag makes possible and structural checks cannot see: Gate_SDK advances
`v1`, and this repository's lock silently stops matching it without a
single local file changing.

- scripts/validate_sdk_pin.py: adds --verify-tag. It resolves
  Quantum-L9/Gate_SDK@v1 at the canonical remote and requires
  poetry.lock's resolved_reference to equal it, failing closed when the
  remote cannot be resolved — an unverifiable lock has not been
  verified. check_text() and check_tree() are untouched, so the existing
  offline contract and its tests are unchanged; the new logic is three
  additive pure functions plus an argparse front door.
- tests/unit/test_validate_sdk_pin.py: covers the comparison directly —
  current lock, stale lock, unresolvable channel, lock with no
  resolved_reference. Pure logic, no network in the test suite.
- ci.yml: the validator was never actually wired into CI; it was only
  reachable through a unit test. Both modes now run in the
  merge-blocking validate job.

poetry.lock is NOT regenerated: resolved_reference is already
e9f829f982110be13752da8f18c7a9692e8ed908, which is what v1 resolves to
today, so there is nothing to refresh and no reason to churn the file.

Verified: ruff check, ruff format --check, 9 passed in
tests/unit/test_validate_sdk_pin.py, and both validator modes against
the live remote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq

* fix: address Codex and Copilot review on PR #285

- cypher-lint (Codex P1, Copilot): name facts are now lexically scoped — a
  sanitizer binding in one function no longer certifies a same-named
  variable in a sibling function; nested functions see enclosing scopes;
  a helper counts as validated only by its own return statements, never
  by returns inside a nested def. Scoping exposed five private helpers
  (resolution/similarity, feedback/drift_detector, feedback/signal_weights)
  that interpolated a label parameter relying on their callers to have
  sanitized it; each now validates the parameter itself.
- GraphDriver (Codex P2): execute_write provisions the domain database on
  first use and translates a missing-database error exactly like
  execute_query, via shared _provision_on_first_use /
  _translate_absent_database helpers.
- domain_extractor (Codex P2): writes domains/<domain-id>/spec.yaml, the
  only layout DomainPackLoader discovers.
- strictwhen (Copilot): declared by GateSpec, consumed by no compiler, so
  legal-discovery and research-agent are withheld behind
  unvalidated_domain_packs_enabled; the pack-shape test records strictwhen
  as a defect so the shrink-only guard still holds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dcSADmR2nDESm69Szd1fT

* docs(contracts): point the SDK install contract at the canonical channel

SHARED_MODELS.md is status: active and CLAUDE.md tells agents to load it
before touching engine/packet/. Under a heading reading "Installation
(pyproject.toml)" it prescribed:

    constellation-node-sdk = {git = "https://github.com/cryptoxdog/Gate_SDK.git"}

which is wrong twice over — the forbidden fork, and no ref at all, so it
floats on that fork's default branch. scripts/validate_sdk_pin.py fails
closed on both, meaning the doc instructed the next agent to write
something the repository's own gate rejects.

This is not the historical seam-audit evidence the release-identity
campaign deliberately leaves alone; it is a live instruction. Corrected
to the canonical repo on the v1 compatibility channel, with a note on
why the resolved object belongs in poetry.lock instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153NHEJmFreprQ5tHLztSGq

* fix(security): refuse a Gate_SDK remote git would parse as an option

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

* fix(security): the canonical remote is the contract, not a CLI option

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants