Skip to content

fix: BROKEN-001/002/003/004 — workflow loader, verifying_keys env parsing, admin token unification, pre-commit contract hooks#1

Open
cryptoxdog wants to merge 1 commit into
mainfrom
fix/alignment-remediation
Open

fix: BROKEN-001/002/003/004 — workflow loader, verifying_keys env parsing, admin token unification, pre-commit contract hooks#1
cryptoxdog wants to merge 1 commit into
mainfrom
fix/alignment-remediation

Conversation

@cryptoxdog

@cryptoxdog cryptoxdog commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes all HIGH and MEDIUM alignment gaps identified in the cross-surface misalignment report.


Fixes

BROKEN-001 — WorkflowEngine not wired to config (HIGH)

Problem: WorkflowEngine was instantiated with no definitions; GATE_WORKFLOW_CONFIG_PATH env var had no effect.

Fix:

  • Added workflow_config_path: str | None field to GateSettings (read from GATE_WORKFLOW_CONFIG_PATH).
  • Added _load_workflow_definitions(path) in api/dependencies.py — fails fast if path is set but file is missing or malformed YAML.
  • get_workflow_engine() now loads definitions at startup; logs warning when path is unset (workflows disabled, not broken).

Validation: tests/orchestration/test_workflow_loader.py — valid load, empty workflows, null file, missing file raises, invalid YAML raises, invalid step raises.


BROKEN-002 — verifying_keys not populated from env (HIGH)

Problem: L9_VERIFYING_KEYS_JSON was silently ignored. Multi-key signature verification was impossible at runtime.

Fix:

  • Added _env_verifying_keys(name) — parses JSON from env, fails fast on malformed JSON, non-object, or blank values.
  • get_settings() now calls _env_verifying_keys("L9_VERIFYING_KEYS_JSON").
  • GateSettings.resolve_verifying_key() now returns from populated verifying_keys dict.

Validation: 6 new tests in tests/config/test_settings.py — valid JSON, empty, missing, invalid JSON, non-object, blank value.


BROKEN-003 — Admin token env var mismatch: L9_GATE_ADMIN_TOKEN vs GATE_ADMIN_TOKEN (MEDIUM)

Problem: Gate used L9_GATE_ADMIN_TOKEN; SDK uses GATE_ADMIN_TOKEN. Node registration auth silently failed with mismatched env vars.

Fix:

  • get_settings() reads GATE_ADMIN_TOKEN first (canonical, matches SDK), falls back to L9_GATE_ADMIN_TOKEN for backward compatibility.
  • .env.example updated to use GATE_ADMIN_TOKEN as canonical with comment.

Validation: 3 tests — canonical takes precedence, legacy fallback works, canonical overwrites legacy.


BROKEN-004 — No pre-commit contract validation hooks (MEDIUM)

Problem: Contract drift could not be detected before merge. scripts/validate_contracts.py did not exist.

Fix:

  • Added scripts/validate_contracts.py — validates schema JSON, required fields, $id, additionalProperties, action pattern, signature_algorithm enum, hop.status enum, hop_signature_algorithm enum.
  • Added validate-contracts local pre-commit hook to .pre-commit-config.yaml.

Fail condition: Hook exits 1 and blocks commit on any check failure.


Pre-Merge Checklist

  • python scripts/validate_contracts.py passes
  • All new tests have failing-before/passing-after semantics
  • No validation defaults weakened
  • Backward compatibility preserved (L9_GATE_ADMIN_TOKEN alias still accepted)
  • No silent fail-open paths remain for the fixed items

Risk Notes

  • R2 (config parsing breaks deployments): Mitigated — L9_GATE_ADMIN_TOKEN alias preserved.
  • R3 (fail-fast blocks incomplete envs): Documented in .env.example with GATE_WORKFLOW_CONFIG_PATH commented out by default.
  • R4 (activating dormant config exposes invalid definitions): Mitigated — _load_workflow_definitions validates Pydantic models before engine receives them.

Summary by CodeRabbit

  • New Features

    • Added support for multi-key signature verification via JSON configuration.
    • Introduced optional YAML-based workflow configuration loading.
  • Configuration

    • Updated admin token environment variable naming (GATE_ADMIN_TOKEN), with backward compatibility maintained.
    • Added contract validation to ensure schema integrity during development.

…min token unification, pre-commit contract hooks

- BROKEN-001: Wire workflow definitions from GATE_WORKFLOW_CONFIG_PATH env var at startup.
  WorkflowEngine now receives loaded definitions or fails safely if path is set but file is invalid.
  Startup logs warning when path is unset (workflows disabled by default, not broken).

- BROKEN-002: Parse L9_VERIFYING_KEYS_JSON in get_settings(). Multi-key signature
  verification is now functional when require_signature=True.

- BROKEN-003: Rename L9_GATE_ADMIN_TOKEN -> GATE_ADMIN_TOKEN in settings.py and .env.example
  to match SDK GATE_ADMIN_TOKEN. Backward-compat alias: L9_GATE_ADMIN_TOKEN still read
  as fallback so existing deployments are not broken.

- BROKEN-004: Add validate-contracts and schema-drift pre-commit hooks. Add
  scripts/validate_contracts.py with schema parity checks.
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Configuration management enhancements for signature verification keys via JSON environment variable, admin token naming standardization with backward compatibility, and new YAML-based workflow definitions loading with pre-commit contract validation.

Changes

Cohort / File(s) Summary
Configuration & Settings
constellation-gate/.env.example, src/constellation_gate/config/settings.py
Added L9_VERIFYING_KEYS_JSON for multi-key signature verification, replaced L9_GATE_ADMIN_TOKEN with GATE_ADMIN_TOKEN (retaining fallback support), and introduced optional GATE_WORKFLOW_CONFIG_PATH for loading workflow definitions from YAML file.
Workflow Definitions Loading
src/constellation_gate/api/dependencies.py, tests/orchestration/test_workflow_loader.py
New _load_workflow_definitions() helper dynamically loads and validates workflow definitions from YAML, with error handling for missing/invalid files and malformed YAML. Workflow engine now populates definitions from config or remains empty with conditional logging.
Contract Validation
scripts/validate_contracts.py, .pre-commit-config.yaml
New pre-commit hook and validation script that checks contracts/transport-packet.schema.json for schema ID, required fields, and enum patterns in signature-related fields. Script exits with status code reflecting validation results.
Test Coverage
tests/config/test_settings.py
Expanded test suite for environment-driven configuration: JSON key parsing, admin token fallback precedence, verifying keys sourcing, and workflow config path defaulting behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Application Start
    participant Settings as GateSettings
    participant DepWire as Dependency Wiring
    participant YAML as YAML Parser
    participant Validator as WorkflowDefinition
    participant Engine as WorkflowEngine

    Client->>Settings: Load settings from env vars
    Settings->>Settings: Parse GATE_WORKFLOW_CONFIG_PATH
    Settings-->>DepWire: Return settings with config path

    DepWire->>DepWire: get_workflow_engine() called
    DepWire->>YAML: _load_workflow_definitions(config_path)
    
    alt Config path set
        YAML->>YAML: Read and parse YAML file
        YAML->>Validator: Validate each workflow definition
        Validator-->>YAML: Validation result
        YAML->>YAML: Normalize keys, inject name field
        YAML-->>DepWire: Return populated definitions dict
        DepWire->>DepWire: Log info: definitions loaded
    else Config path not set
        DepWire->>DepWire: Log warning: no config path
        DepWire-->>DepWire: Use empty definitions dict
    end
    
    DepWire->>Engine: Instantiate with definitions
    Engine-->>Client: Workflow engine ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Keys and workflows intertwined,
JSON parsed, YAML refined,
Contracts validated with care,
Configuration's answer to prayer,
Gate's new config takes its flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing four alignment issues (BROKEN-001/002/003/004) involving workflow loader, verifying keys parsing, admin token unification, and pre-commit hooks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/alignment-remediation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@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: 42640616de

ℹ️ 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 on lines +103 to +106
defn_raw = {"name": name, **defn_raw}
try:
definitions[name.strip().lower()] = WorkflowDefinition.model_validate(defn_raw)
except Exception as exc:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Support current workflow YAML step schema

_load_workflow_definitions() now validates each workflow against WorkflowDefinition directly, but that model requires step fields (name, merge_strategy) that are not present in the repository’s shipped workflow config (src/constellation_gate/config/workflows.yaml uses action + payload_transform). In this configuration, setting GATE_WORKFLOW_CONFIG_PATH to the bundled file causes validation to fail and prevents workflow-based routing from being enabled; add compatibility mapping (e.g., payload_transform -> merge_strategy and generated step names) or validate against the existing schema type first.

Useful? React with 👍 / 👎.

Comment on lines +100 to +103
has_algo_constraint = (
"enum" in sig_alg_def
or any("enum" in branch for branch in sig_alg_def.get("anyOf", []))
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce expected enum members in contract hook

The new validate_contracts check only verifies that an enum key exists for security.signature_algorithm (and similarly for hop fields), but it never verifies the allowed values. A schema drift like changing the enum to unsupported values would still pass pre-commit, so this hook does not actually protect the contract invariants it claims to enforce.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (9)
constellation-gate/src/constellation_gate/api/dependencies.py (2)

104-108: Narrow the except Exception to Pydantic's ValidationError.

WorkflowDefinition.model_validate raises pydantic.ValidationError. Catching the base Exception here also wraps unrelated programming errors (e.g., a TypeError from a future refactor) as "failed validation: …", which obscures real bugs. Importing from pydantic import ValidationError and catching that explicitly keeps the intent precise.

♻️ Suggested change
+from pydantic import ValidationError
@@
-        try:
-            definitions[name.strip().lower()] = WorkflowDefinition.model_validate(defn_raw)
-        except Exception as exc:
-            raise ValueError(f"'{path}': workflow '{name}' failed validation: {exc}") from exc
+        try:
+            definitions[name.strip().lower()] = WorkflowDefinition.model_validate(defn_raw)
+        except ValidationError as exc:
+            raise ValueError(f"'{path}': workflow '{name}' failed validation: {exc}") from exc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/src/constellation_gate/api/dependencies.py` around lines
104 - 108, The except block around WorkflowDefinition.model_validate is too
broad; import ValidationError from pydantic and replace "except Exception as
exc:" with "except ValidationError as exc:" so only pydantic validation failures
are caught and re-raised as the ValueError (preserving "from exc"); keep the
existing raise ValueError(f"'{path}': workflow '{name}' failed validation:
{exc}") from exc to retain the original error chain.

98-108: name field can desync from the dict key.

The injected name (line 103) only fills in when missing. If the YAML provides an explicit name that differs from the workflow key (e.g., workflows: { full_pipeline: { name: alt_name, ... } }), definitions["full_pipeline"] will hold a WorkflowDefinition whose .name is alt_name. Later code that pulls the name off the definition (logs, error messages, comparisons) will diverge from the lookup key. Either always overwrite with the YAML key or reject mismatches with a clear ValueError.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/src/constellation_gate/api/dependencies.py` around lines
98 - 108, The workflow `name` in the parsed mapping can diverge from the mapping
key because you only inject `name` when missing; instead, always set/overwrite
the definition's "name" to the mapping key before validation to keep the stored
WorkflowDefinition in sync with the lookup key: in the loop over workflows_raw,
assign defn_raw = { "name": name, **defn_raw } (or set defn_raw["name"] = name)
unconditionally prior to calling WorkflowDefinition.model_validate, so
definitions[name.strip().lower()] will always hold a .name matching the mapping
key.
constellation-gate/src/constellation_gate/config/settings.py (2)

33-35: Nit: the raw == "{}" short-circuit is redundant.

json.loads("{}") already returns {}, which would then iterate zero items and return an empty result. The special case adds a branch without changing behavior — feel free to drop it for simplicity.

♻️ Suggested simplification
-    raw = os.getenv(name, "").strip()
-    if not raw or raw == "{}":
-        return {}
+    raw = os.getenv(name, "").strip()
+    if not raw:
+        return {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/src/constellation_gate/config/settings.py` around lines 33
- 35, The check for raw == "{}" is redundant; update the conditional that
currently reads `if not raw or raw == "{}":` to simply check `if not raw:` (keep
the existing use of `raw = os.getenv(name, "").strip()`), and remove the
special-case branch so the subsequent json.loads handling (which returns {} for
"{}") handles that input normally.

156-192: Consider explicit handling when the raw env value is set but blank.

os.getenv("GATE_ADMIN_TOKEN") or os.getenv("L9_GATE_ADMIN_TOKEN") or None quietly treats GATE_ADMIN_TOKEN="" as "fall back to legacy", which is convenient but masks operator misconfiguration (e.g., a deployment pipeline that intends to require canonical-only). Not a defect given the documented fallback contract, just flagging that the empty-string-as-unset behavior is implicit. If you want to enforce stricter semantics later, a small helper that distinguishes "unset" from "set-but-blank" would do it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/src/constellation_gate/config/settings.py` around lines
156 - 192, The code in get_settings treats empty strings as unset when computing
admin_token (admin_token = os.getenv("GATE_ADMIN_TOKEN") or
os.getenv("L9_GATE_ADMIN_TOKEN") or None), which can silently fall back to the
legacy var; change to explicitly detect blank-but-set values by using a small
helper or explicit checks so you can distinguish unset (None) from set-but-empty
(""), then pass the resulting value into GateSettings.admin_token; update the
logic around get_settings / admin_token (and any helper you add) to preserve
current fallback behavior but allow detecting and handling set-but-blank cases
if needed.
constellation-gate/.env.example (1)

13-15: Minor wording: "Must be valid JSON" doesn't reflect the blank-value branch.

_env_verifying_keys treats a missing or blank value as an empty dict (no JSON parsing required), so the absolute "Must be valid JSON" reads stricter than the actual behavior. Clarifying to "If set, must be a valid JSON object" matches the code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/.env.example` around lines 13 - 15, The documentation line
for L9_VERIFYING_KEYS_JSON is too strict: change the wording to reflect that an
empty or missing value is allowed and that JSON parsing only applies when set;
update the example comment for L9_VERIFYING_KEYS_JSON and/or the description
near _env_verifying_keys to read something like "If set, must be a valid JSON
object (e.g. {\"key-1\": \"abc123\"}); blank or unset is treated as an empty
dict" so readers know blank is acceptable and parsing only occurs when the
variable is populated.
constellation-gate/scripts/validate_contracts.py (2)

38-148: Wrap top-level logic in a main() guarded by __name__ == "__main__".

All side-effecting logic (mutating module-global failures, printing, sys.exit) executes at import time. This makes the script unimportable from tests or other tooling without triggering checks/exits, and prevents incremental coverage of individual checks. Wrapping in def main() -> int: plus an if __name__ == "__main__": sys.exit(main()) guard is a small change that keeps the same CLI behavior while enabling reuse.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/scripts/validate_contracts.py` around lines 38 - 148, The
module runs side-effecting validation at import time (mutating the module-global
failures list, printing, and calling sys.exit), so wrap all top-level logic into
a function def main() -> int: that performs the current checks and returns 0 on
success or a non-zero int on failure, move the existing global helpers (fail,
ok) and the schema-checking code into main (referencing failures, fail, ok,
schema, REQUIRED_TOP_LEVEL_FIELDS, CANONICAL_ID, GATE_SCHEMA_PATH), and replace
the current top-level sys.exit calls with returning appropriate exit codes;
finally add the standard guard if __name__ == "__main__": sys.exit(main()) so
importing the module no longer executes the checks.

98-141: Enum presence checks don't verify the actual allowed values.

The three enum checks only assert that an enum key exists somewhere (directly or in any anyOf branch). The failure message at line 107 mentions the expected values [hmac-sha256, ed25519], but a schema with enum: ["foo", "bar"] would still pass. Same for hop_trace.items.status and hop_trace.items.hop_signature_algorithm. If the goal is to catch contract drift, consider asserting the exact expected enum sets (matching the canonical contract) like you do for header.action's pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/scripts/validate_contracts.py` around lines 98 - 141, The
checks for signature_algorithm, hop_trace.items.status, and
hop_trace.items.hop_signature_algorithm only verify an "enum" exists but not its
contents; update the logic around sig_alg_def / has_algo_constraint, status_def
/ has_status_enum, and hop_sig_alg_def / has_hop_sig_alg_constraint to extract
the actual enum values (from the direct "enum" or by collecting "enum" from
anyOf branches), and compare those sets to the canonical expected sets (e.g.,
for signature_algorithm assert exactly ["hmac-sha256","ed25519"]; for the hop
fields either assert the exact canonical lists used by the contract or pull them
from a single source-of-truth variable/constants), and call fail() when the sets
differ (ok() only when they match exactly). Ensure you normalize ordering and
types when comparing sets so a different order doesn't bypass the check.
constellation-gate/tests/orchestration/test_workflow_loader.py (1)

12-72: Optional: add coverage for the non-mapping error branches.

_load_workflow_definitions raises ValueError in three additional structural cases that aren't exercised here:

  • Top-level YAML not a mapping (e.g., - a\n- b) → line 91 in dependencies.py.
  • workflows key not a mapping (e.g., workflows: [1,2,3]) → line 95.
  • A single workflow not a mapping (e.g., workflows: {bad_flow: "string"}) → line 100.

Adding a parametrized test for these would round out the loader's contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/tests/orchestration/test_workflow_loader.py` around lines
12 - 72, Add a parametrized test in test_workflow_loader.py that calls
_load_workflow_definitions and asserts it raises ValueError for the three
structural non-mapping cases: (1) top-level YAML as a sequence (e.g., "- a\n-
b"), (2) workflows key as a sequence (e.g., "workflows: [1,2,3]"), and (3) a
single workflow value that is not a mapping (e.g., 'workflows: {bad_flow:
"string"}'); implement it using pytest.mark.parametrize and
pytest.raises(ValueError) to exercise the branches in _load_workflow_definitions
so the loader's non-mapping error paths are covered.
constellation-gate/tests/config/test_settings.py (1)

87-149: Optional: hoist get_settings.cache_clear() into an autouse fixture.

Every get_settings-touching test repeats get_settings.cache_clear() at start and end. An autouse fixture would centralize this and prevent a future test from forgetting it (which would silently leak cached settings between tests via lru_cache).

♻️ Suggested fixture
+@pytest.fixture(autouse=True)
+def _clear_settings_cache() -> None:
+    get_settings.cache_clear()
+    yield
+    get_settings.cache_clear()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/tests/config/test_settings.py` around lines 87 - 149, The
tests repeatedly call get_settings.cache_clear() at their start and end; add an
autouse pytest fixture (e.g., def clear_get_settings_cache(): with
pytest.fixture(autouse=True)) that calls get_settings.cache_clear() before the
test (and again after via yield teardown) so the cache is always cleared for
each test, and then remove the per-test get_settings.cache_clear() calls from
the individual test functions (references: get_settings.cache_clear(), tests
that call it such as test_get_settings_reads_gate_admin_token,
test_get_settings_workflow_config_path_reads_env, etc.).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@constellation-gate/.pre-commit-config.yaml`:
- Around line 32-39: The pre-commit hook entry for the local hook
"validate-contracts" currently uses a relative path "python
scripts/validate_contracts.py" which will be resolved from the repository root;
update the entry to reference the correct repo-root-relative path "python
constellation-gate/scripts/validate_contracts.py" (or alternatively move the
.pre-commit-config.yaml to the git root) so the validate-contracts hook can find
and run scripts/validate_contracts.py correctly.

In `@constellation-gate/scripts/validate_contracts.py`:
- Around line 4-14: The docstring promises an SDK-schema parity check that isn't
implemented; add a function (e.g., check_sdk_schema_parity(schema_path)) that
looks for a sibling SDK schema file for the same contract, loads it if present,
and compares the Gate schema to the SDK schema: ensure every required array
entry in the Gate schema also appears in the SDK schema for the same object, and
ensure additionalProperties is false in the SDK schema where it is false in the
Gate schema; call this function from the main validation flow and make it return
a nonzero failure status when parity violations are found so the script's exit
codes remain correct.

In `@constellation-gate/src/constellation_gate/api/dependencies.py`:
- Around line 111-134: Add a FastAPI lifespan startup hook that calls
get_workflow_engine() to force the `@lru_cache-backed` factory to run at
application boot so _load_workflow_definitions() errors surface immediately;
implement a context manager for the app lifespan (or use
`@app.on_event`("startup")) and invoke get_workflow_engine() inside it, letting
exceptions propagate to abort startup, then continue to use
get_workflow_engine() as the dependency elsewhere so the cached WorkflowEngine
(including definitions loaded by _load_workflow_definitions and constructed via
WorkflowEngine with dispatcher=get_dispatcher() and
local_node=settings.local_node) is preloaded.

---

Nitpick comments:
In `@constellation-gate/.env.example`:
- Around line 13-15: The documentation line for L9_VERIFYING_KEYS_JSON is too
strict: change the wording to reflect that an empty or missing value is allowed
and that JSON parsing only applies when set; update the example comment for
L9_VERIFYING_KEYS_JSON and/or the description near _env_verifying_keys to read
something like "If set, must be a valid JSON object (e.g. {\"key-1\":
\"abc123\"}); blank or unset is treated as an empty dict" so readers know blank
is acceptable and parsing only occurs when the variable is populated.

In `@constellation-gate/scripts/validate_contracts.py`:
- Around line 38-148: The module runs side-effecting validation at import time
(mutating the module-global failures list, printing, and calling sys.exit), so
wrap all top-level logic into a function def main() -> int: that performs the
current checks and returns 0 on success or a non-zero int on failure, move the
existing global helpers (fail, ok) and the schema-checking code into main
(referencing failures, fail, ok, schema, REQUIRED_TOP_LEVEL_FIELDS,
CANONICAL_ID, GATE_SCHEMA_PATH), and replace the current top-level sys.exit
calls with returning appropriate exit codes; finally add the standard guard if
__name__ == "__main__": sys.exit(main()) so importing the module no longer
executes the checks.
- Around line 98-141: The checks for signature_algorithm,
hop_trace.items.status, and hop_trace.items.hop_signature_algorithm only verify
an "enum" exists but not its contents; update the logic around sig_alg_def /
has_algo_constraint, status_def / has_status_enum, and hop_sig_alg_def /
has_hop_sig_alg_constraint to extract the actual enum values (from the direct
"enum" or by collecting "enum" from anyOf branches), and compare those sets to
the canonical expected sets (e.g., for signature_algorithm assert exactly
["hmac-sha256","ed25519"]; for the hop fields either assert the exact canonical
lists used by the contract or pull them from a single source-of-truth
variable/constants), and call fail() when the sets differ (ok() only when they
match exactly). Ensure you normalize ordering and types when comparing sets so a
different order doesn't bypass the check.

In `@constellation-gate/src/constellation_gate/api/dependencies.py`:
- Around line 104-108: The except block around WorkflowDefinition.model_validate
is too broad; import ValidationError from pydantic and replace "except Exception
as exc:" with "except ValidationError as exc:" so only pydantic validation
failures are caught and re-raised as the ValueError (preserving "from exc");
keep the existing raise ValueError(f"'{path}': workflow '{name}' failed
validation: {exc}") from exc to retain the original error chain.
- Around line 98-108: The workflow `name` in the parsed mapping can diverge from
the mapping key because you only inject `name` when missing; instead, always
set/overwrite the definition's "name" to the mapping key before validation to
keep the stored WorkflowDefinition in sync with the lookup key: in the loop over
workflows_raw, assign defn_raw = { "name": name, **defn_raw } (or set
defn_raw["name"] = name) unconditionally prior to calling
WorkflowDefinition.model_validate, so definitions[name.strip().lower()] will
always hold a .name matching the mapping key.

In `@constellation-gate/src/constellation_gate/config/settings.py`:
- Around line 33-35: The check for raw == "{}" is redundant; update the
conditional that currently reads `if not raw or raw == "{}":` to simply check
`if not raw:` (keep the existing use of `raw = os.getenv(name, "").strip()`),
and remove the special-case branch so the subsequent json.loads handling (which
returns {} for "{}") handles that input normally.
- Around line 156-192: The code in get_settings treats empty strings as unset
when computing admin_token (admin_token = os.getenv("GATE_ADMIN_TOKEN") or
os.getenv("L9_GATE_ADMIN_TOKEN") or None), which can silently fall back to the
legacy var; change to explicitly detect blank-but-set values by using a small
helper or explicit checks so you can distinguish unset (None) from set-but-empty
(""), then pass the resulting value into GateSettings.admin_token; update the
logic around get_settings / admin_token (and any helper you add) to preserve
current fallback behavior but allow detecting and handling set-but-blank cases
if needed.

In `@constellation-gate/tests/config/test_settings.py`:
- Around line 87-149: The tests repeatedly call get_settings.cache_clear() at
their start and end; add an autouse pytest fixture (e.g., def
clear_get_settings_cache(): with pytest.fixture(autouse=True)) that calls
get_settings.cache_clear() before the test (and again after via yield teardown)
so the cache is always cleared for each test, and then remove the per-test
get_settings.cache_clear() calls from the individual test functions (references:
get_settings.cache_clear(), tests that call it such as
test_get_settings_reads_gate_admin_token,
test_get_settings_workflow_config_path_reads_env, etc.).

In `@constellation-gate/tests/orchestration/test_workflow_loader.py`:
- Around line 12-72: Add a parametrized test in test_workflow_loader.py that
calls _load_workflow_definitions and asserts it raises ValueError for the three
structural non-mapping cases: (1) top-level YAML as a sequence (e.g., "- a\n-
b"), (2) workflows key as a sequence (e.g., "workflows: [1,2,3]"), and (3) a
single workflow value that is not a mapping (e.g., 'workflows: {bad_flow:
"string"}'); implement it using pytest.mark.parametrize and
pytest.raises(ValueError) to exercise the branches in _load_workflow_definitions
so the loader's non-mapping error paths are covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d873d63-e888-4fb1-943c-886332774668

📥 Commits

Reviewing files that changed from the base of the PR and between 25a3e56 and 4264061.

📒 Files selected for processing (7)
  • constellation-gate/.env.example
  • constellation-gate/.pre-commit-config.yaml
  • constellation-gate/scripts/validate_contracts.py
  • constellation-gate/src/constellation_gate/api/dependencies.py
  • constellation-gate/src/constellation_gate/config/settings.py
  • constellation-gate/tests/config/test_settings.py
  • constellation-gate/tests/orchestration/test_workflow_loader.py

Comment on lines +32 to +39
- repo: local
hooks:
- id: validate-contracts
name: validate-contracts
language: python
entry: python scripts/validate_contracts.py
pass_filenames: false
always_run: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm where the git root sits relative to the pre-commit config and the script.
fd -H -t d -d 3 '^\.git$'
fd -t f -d 3 '^\.pre-commit-config\.yaml$'
fd -t f 'validate_contracts.py'
fd -t f 'transport-packet.schema.json'

Repository: cryptoxdog/Constellation.Gate

Length of output: 412


Fix the relative entry path to account for the git root location.

The .pre-commit-config.yaml is in the constellation-gate/ subdirectory, but pre-commit invokes hooks from the repository root (where .git is located). The entry python scripts/validate_contracts.py will fail because it looks for <repo_root>/scripts/validate_contracts.py, not <repo_root>/constellation-gate/scripts/validate_contracts.py.

Change entry to python constellation-gate/scripts/validate_contracts.py or move the config file to the repository root.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/.pre-commit-config.yaml` around lines 32 - 39, The
pre-commit hook entry for the local hook "validate-contracts" currently uses a
relative path "python scripts/validate_contracts.py" which will be resolved from
the repository root; update the entry to reference the correct
repo-root-relative path "python
constellation-gate/scripts/validate_contracts.py" (or alternatively move the
.pre-commit-config.yaml to the git root) so the validate-contracts hook can find
and run scripts/validate_contracts.py correctly.

Comment on lines +4 to +14
Checks:
1. contracts/transport-packet.schema.json is valid JSON.
2. All required top-level fields are present in the schema.
3. The $id matches the canonical contract URL.
4. SDK schema parity: if SDK schema is available at a sibling path, verify
that all required-array entries and additionalProperties=false are present.

Exit codes:
0 — all checks pass
1 — one or more checks failed
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Docstring advertises an "SDK schema parity" check that isn't implemented.

The docstring lists check #4 as "SDK schema parity: if SDK schema is available at a sibling path, verify that all required-array entries and additionalProperties=false are present." However, no SDK-side schema lookup or parity check appears below — the script only validates the Gate-side schema. Either implement the SDK-parity check or trim the docstring to match what runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/scripts/validate_contracts.py` around lines 4 - 14, The
docstring promises an SDK-schema parity check that isn't implemented; add a
function (e.g., check_sdk_schema_parity(schema_path)) that looks for a sibling
SDK schema file for the same contract, loads it if present, and compares the
Gate schema to the SDK schema: ensure every required array entry in the Gate
schema also appears in the SDK schema for the same object, and ensure
additionalProperties is false in the SDK schema where it is false in the Gate
schema; call this function from the main validation flow and make it return a
nonzero failure status when parity violations are found so the script's exit
codes remain correct.

Comment on lines 111 to 134
@lru_cache
def get_workflow_engine() -> WorkflowEngine:
settings = get_gate_settings()
definitions: dict[str, WorkflowDefinition] = {}

if settings.workflow_config_path:
# Fails fast on invalid path or malformed file — startup aborts cleanly.
definitions = _load_workflow_definitions(settings.workflow_config_path)
logger.info(
"workflow engine loaded %d definitions from '%s'",
len(definitions),
settings.workflow_config_path,
)
else:
logger.warning(
"GATE_WORKFLOW_CONFIG_PATH is not set; workflow engine is active but has no definitions. "
"Packets routed to workflow actions will fall through to direct dispatch."
)

return WorkflowEngine(
definitions={},
definitions=definitions,
dispatcher=get_dispatcher(),
local_node=settings.local_node,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for an app/lifespan/startup wiring that could preload workflow_engine.
rg -nP -C3 '(lifespan|on_event\(["\']startup|@app\.on_event)' --type=py
rg -nP -C2 '\bget_workflow_engine\b' --type=py

Repository: cryptoxdog/Constellation.Gate

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Search for startup/lifespan handlers with proper escaping
rg -nP -C3 'lifespan|on_event.*startup|@app\.on_event' --type=py

# Search for get_workflow_engine usage
rg -nP -C2 'get_workflow_engine' --type=py

# Find the FastAPI app initialization
rg -nP -C5 'FastAPI|app\s*=' --type=py | head -100

Repository: cryptoxdog/Constellation.Gate

Length of output: 11748


🏁 Script executed:

#!/bin/bash
# Look at the main app file structure
fd -e py -path '*/main.py' -o -path '*/app.py' -o -path '*/factory.py' | xargs -I {} sh -c 'echo "=== {} ===" && head -50 {}'

Repository: cryptoxdog/Constellation.Gate

Length of output: 242


🏁 Script executed:

#!/bin/bash
# Check the dependencies.py file more closely around get_workflow_engine
cat -n constellation-gate/src/constellation_gate/api/dependencies.py | sed -n '100,145p'

Repository: cryptoxdog/Constellation.Gate

Length of output: 2099


🏁 Script executed:

#!/bin/bash
# Find and display the main.py file where create_app is defined
fd -name 'main.py' -path '*/api/*' -type f

Repository: cryptoxdog/Constellation.Gate

Length of output: 304


🏁 Script executed:

#!/bin/bash
# Search for create_app function definition
rg -n 'def create_app' --type=py

Repository: cryptoxdog/Constellation.Gate

Length of output: 159


🏁 Script executed:

#!/bin/bash
# Look for lifespan context manager in FastAPI setup
rg -n 'lifespan|@asynccontextmanager' --type=py -A 10

Repository: cryptoxdog/Constellation.Gate

Length of output: 1211


🏁 Script executed:

#!/bin/bash
# Display the create_app function and surrounding code
cat -n constellation-gate/src/constellation_gate/api/main.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 2895


🏁 Script executed:

#!/bin/bash
# Search for where app.state.runtime is set
rg -n 'app\.state\.runtime|state\.runtime' --type=py -B 3 -A 3

Repository: cryptoxdog/Constellation.Gate

Length of output: 759


🏁 Script executed:

#!/bin/bash
# Search for any lifespan context managers or startup handlers
rg -n '@asynccontextmanager|lifespan.*=|Lifespan' --type=py -B 2 -A 5

Repository: cryptoxdog/Constellation.Gate

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Check if there's any middleware or initialization in main.py that we might have missed
wc -l constellation-gate/src/constellation_gate/api/main.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 130


🏁 Script executed:

#!/bin/bash
# Search for where runtime is created and assigned to app.state
rg -n 'runtime\s*=' --type=py | head -30

Repository: cryptoxdog/Constellation.Gate

Length of output: 183


🏁 Script executed:

#!/bin/bash
# Look in the entire api directory for any other setup files
find constellation-gate/src/constellation_gate/api -name '*.py' -type f | sort

Repository: cryptoxdog/Constellation.Gate

Length of output: 363


🏁 Script executed:

#!/bin/bash
# Check if there's a __init__.py in api directory with setup logic
cat -n constellation-gate/src/constellation_gate/api/__init__.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 1038


🏁 Script executed:

#!/bin/bash
# Check the middleware.py file for runtime initialization
cat -n constellation-gate/src/constellation_gate/api/middleware.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 816


🏁 Script executed:

#!/bin/bash
# Broader search for Runtime class and where it's instantiated
rg -n 'class Runtime|Runtime(' --type=py -B 2 -A 5

Repository: cryptoxdog/Constellation.Gate

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Search for where state.runtime could be assigned
rg -n '\.state\.runtime\s*=' --type=py -B 5 -A 5

Repository: cryptoxdog/Constellation.Gate

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Check if there's any conftest or setup in tests
find constellation-gate/tests -name 'conftest.py' -type f

Repository: cryptoxdog/Constellation.Gate

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Search for Runtime class definition with proper escaping
rg -n 'class Runtime' --type=py -B 2 -A 5

Repository: cryptoxdog/Constellation.Gate

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Display conftest.py
cat -n constellation-gate/tests/conftest.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 1074


🏁 Script executed:

#!/bin/bash
# Search for Runtime in entire codebase
rg -n 'Runtime' --type=py | grep -E 'class|import|from' | head -20

Repository: cryptoxdog/Constellation.Gate

Length of output: 801


🏁 Script executed:

#!/bin/bash
# Check the full test file to understand context better
cat -n constellation-gate/tests/integration/test_production_startup.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 798


🏁 Script executed:

#!/bin/bash
# Search for http_client_manager which might give us a clue
rg -n 'http_client_manager' --type=py -B 3 -A 3

Repository: cryptoxdog/Constellation.Gate

Length of output: 2021


🏁 Script executed:

#!/bin/bash
# Search for app.state or app.lifespan assignments
rg -n 'app\.' --type=py constellation-gate/src/constellation_gate/api/main.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 206


🏁 Script executed:

#!/bin/bash
# Look for any Python files that might define lifespan or app initialization
rg -n 'lifespan|@asynccontextmanager' --type=py -B 3 -A 10 | head -50

Repository: cryptoxdog/Constellation.Gate

Length of output: 1462


🏁 Script executed:

#!/bin/bash
# Search for any assignment to app state or where Runtime might be instantiated
rg -n '\.state\s*=' --type=py -B 3 -A 3 | head -60

Repository: cryptoxdog/Constellation.Gate

Length of output: 3269


🏁 Script executed:

#!/bin/bash
# Look for AsyncHttpClientManager which seems to be part of the runtime
rg -n 'class AsyncHttpClientManager' --type=py -B 2 -A 15

Repository: cryptoxdog/Constellation.Gate

Length of output: 1765


🏁 Script executed:

#!/bin/bash
# Check if there's any setup in the main app initialization file or a wsgi/asgi wrapper
find constellation-gate -name '*.py' -path '*/src/*' | xargs grep -l 'FastAPI\|create_app' | head -10

Repository: cryptoxdog/Constellation.Gate

Length of output: 239


🏁 Script executed:

#!/bin/bash
# Check the package-level __init__.py
cat -n constellation-gate/src/constellation_gate/__init__.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 2315


🏁 Script executed:

#!/bin/bash
# Search for where state.runtime might be set by looking for 'runtime' assignment
rg -n 'runtime\s*=' --type=py -B 5 -A 2 | head -80

Repository: cryptoxdog/Constellation.Gate

Length of output: 891


🏁 Script executed:

#!/bin/bash
# Look at the entire runtime module structure
find constellation-gate/src/constellation_gate/runtime -name '*.py' | sort

Repository: cryptoxdog/Constellation.Gate

Length of output: 590


🏁 Script executed:

#!/bin/bash
# Check the lifecycle.py file - this is likely where startup happens
cat -n constellation-gate/src/constellation_gate/runtime/lifecycle.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 971


🏁 Script executed:

#!/bin/bash
# Check the app_state.py file
cat -n constellation-gate/src/constellation_gate/runtime/app_state.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 644


🏁 Script executed:

#!/bin/bash
# Check if these are imported and used in main.py or create_app
rg -n 'lifecycle|app_state|AppState' --type=py

Repository: cryptoxdog/Constellation.Gate

Length of output: 1514


🏁 Script executed:

#!/bin/bash
# Search more thoroughly for where app.state is used
rg -n '\.state\.runtime|app\.state' --type=py -B 5 -A 5

Repository: cryptoxdog/Constellation.Gate

Length of output: 1180


🏁 Script executed:

#!/bin/bash
# Check the runtime __init__ file for exports
cat -n constellation-gate/src/constellation_gate/runtime/__init__.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 671


🏁 Script executed:

#!/bin/bash
# Look for any ASGI/WSGI wrappers or additional app configuration
find constellation-gate -name '*.py' | xargs grep -l 'state\.runtime\|lifespan' | head -10

Repository: cryptoxdog/Constellation.Gate

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Let's run the actual test to see if it passes or fails
cd constellation-gate && python -m pytest tests/integration/test_production_startup.py::test_app_lifespan_starts_runtime_and_health_surface -v 2>&1 | head -100

Repository: cryptoxdog/Constellation.Gate

Length of output: 350


🏁 Script executed:

#!/bin/bash
# Check the git log for recent changes to main.py
cd constellation-gate && git log --oneline -10 -- src/constellation_gate/api/main.py 2>/dev/null || echo "Git not available"

Repository: cryptoxdog/Constellation.Gate

Length of output: 199


🏁 Script executed:

#!/bin/bash
# Double-check main.py to ensure we have the complete file and nothing was missed
wc -l constellation-gate/src/constellation_gate/api/main.py
tail -20 constellation-gate/src/constellation_gate/api/main.py

Repository: cryptoxdog/Constellation.Gate

Length of output: 755


🏁 Script executed:

#!/bin/bash
# Search all Python files for where runtime is actually instantiated or set as state
rg -n 'state\.runtime\s*=|runtime\s*=.*AsyncHttpClientManager|state\[.runtime' --type=py

Repository: cryptoxdog/Constellation.Gate

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Check if there are any recent additions to main.py by looking at the full context
cd constellation-gate && git show HEAD:src/constellation_gate/api/main.py 2>/dev/null | head -100

Repository: cryptoxdog/Constellation.Gate

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Search for any class that has http_client_manager as a property
rg -n 'http_client_manager' --type=py -B 10 | head -60

Repository: cryptoxdog/Constellation.Gate

Length of output: 2833


Add FastAPI lifespan handler to preload get_workflow_engine() at startup.

The @lru_cache decorator on get_workflow_engine() defers execution until the dependency is first requested—typically the first call to /v1/execute—not at application startup. Validation errors in _load_workflow_definitions() (missing file, invalid YAML, or model validation failures) will surface as a 500 error on that first request rather than aborting boot, contradicting the PR's "fails fast on missing or malformed YAML" guarantee.

Use a FastAPI lifespan context manager to invoke get_workflow_engine() during startup so configuration errors fail immediately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@constellation-gate/src/constellation_gate/api/dependencies.py` around lines
111 - 134, Add a FastAPI lifespan startup hook that calls get_workflow_engine()
to force the `@lru_cache-backed` factory to run at application boot so
_load_workflow_definitions() errors surface immediately; implement a context
manager for the app lifespan (or use `@app.on_event`("startup")) and invoke
get_workflow_engine() inside it, letting exceptions propagate to abort startup,
then continue to use get_workflow_engine() as the dependency elsewhere so the
cached WorkflowEngine (including definitions loaded by
_load_workflow_definitions and constructed via WorkflowEngine with
dispatcher=get_dispatcher() and local_node=settings.local_node) is preloaded.

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.

1 participant