fix: BROKEN-001/002/003/004 — workflow loader, verifying_keys env parsing, admin token unification, pre-commit contract hooks#1
Conversation
…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.
📝 WalkthroughWalkthroughConfiguration 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 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".
| defn_raw = {"name": name, **defn_raw} | ||
| try: | ||
| definitions[name.strip().lower()] = WorkflowDefinition.model_validate(defn_raw) | ||
| except Exception as exc: |
There was a problem hiding this comment.
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 👍 / 👎.
| has_algo_constraint = ( | ||
| "enum" in sig_alg_def | ||
| or any("enum" in branch for branch in sig_alg_def.get("anyOf", [])) | ||
| ) |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (9)
constellation-gate/src/constellation_gate/api/dependencies.py (2)
104-108: Narrow theexcept Exceptionto Pydantic'sValidationError.
WorkflowDefinition.model_validateraisespydantic.ValidationError. Catching the baseExceptionhere also wraps unrelated programming errors (e.g., aTypeErrorfrom a future refactor) as "failed validation: …", which obscures real bugs. Importingfrom pydantic import ValidationErrorand 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:namefield can desync from the dict key.The injected
name(line 103) only fills in when missing. If the YAML provides an explicitnamethat differs from the workflow key (e.g.,workflows: { full_pipeline: { name: alt_name, ... } }),definitions["full_pipeline"]will hold aWorkflowDefinitionwhose.nameisalt_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 clearValueError.🤖 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: theraw == "{}"short-circuit is redundant.
json.loads("{}")already returns{}, which would then iterate zero items and return an emptyresult. 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 Nonequietly treatsGATE_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_keystreats 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 amain()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 indef main() -> int:plus anif __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
enumkey exists somewhere (directly or in anyanyOfbranch). The failure message at line 107 mentions the expected values[hmac-sha256, ed25519], but a schema withenum: ["foo", "bar"]would still pass. Same forhop_trace.items.statusandhop_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 forheader.action'spattern.🤖 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_definitionsraisesValueErrorin three additional structural cases that aren't exercised here:
- Top-level YAML not a mapping (e.g.,
- a\n- b) → line 91 independencies.py.workflowskey 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: hoistget_settings.cache_clear()into an autouse fixture.Every
get_settings-touching test repeatsget_settings.cache_clear()at start and end. Anautousefixture would centralize this and prevent a future test from forgetting it (which would silently leak cached settings between tests vialru_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
📒 Files selected for processing (7)
constellation-gate/.env.exampleconstellation-gate/.pre-commit-config.yamlconstellation-gate/scripts/validate_contracts.pyconstellation-gate/src/constellation_gate/api/dependencies.pyconstellation-gate/src/constellation_gate/config/settings.pyconstellation-gate/tests/config/test_settings.pyconstellation-gate/tests/orchestration/test_workflow_loader.py
| - repo: local | ||
| hooks: | ||
| - id: validate-contracts | ||
| name: validate-contracts | ||
| language: python | ||
| entry: python scripts/validate_contracts.py | ||
| pass_filenames: false | ||
| always_run: true |
There was a problem hiding this comment.
🧩 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.
| 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 | ||
| """ |
There was a problem hiding this comment.
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.
| @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, | ||
| ) |
There was a problem hiding this comment.
🧩 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=pyRepository: 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 -100Repository: 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 fRepository: cryptoxdog/Constellation.Gate
Length of output: 304
🏁 Script executed:
#!/bin/bash
# Search for create_app function definition
rg -n 'def create_app' --type=pyRepository: 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 10Repository: 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.pyRepository: 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 3Repository: 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 5Repository: 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.pyRepository: 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 -30Repository: 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 | sortRepository: 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__.pyRepository: 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.pyRepository: 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 5Repository: 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 5Repository: 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 fRepository: 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 5Repository: cryptoxdog/Constellation.Gate
Length of output: 55
🏁 Script executed:
#!/bin/bash
# Display conftest.py
cat -n constellation-gate/tests/conftest.pyRepository: 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 -20Repository: 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.pyRepository: 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 3Repository: 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.pyRepository: 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 -50Repository: 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 -60Repository: 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 15Repository: 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 -10Repository: 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__.pyRepository: 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 -80Repository: 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' | sortRepository: 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.pyRepository: 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.pyRepository: 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=pyRepository: 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 5Repository: 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__.pyRepository: 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 -10Repository: 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 -100Repository: 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.pyRepository: 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=pyRepository: 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 -100Repository: 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 -60Repository: 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.
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:
WorkflowEnginewas instantiated with no definitions;GATE_WORKFLOW_CONFIG_PATHenv var had no effect.Fix:
workflow_config_path: str | Nonefield toGateSettings(read fromGATE_WORKFLOW_CONFIG_PATH)._load_workflow_definitions(path)inapi/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_keysnot populated from env (HIGH)Problem:
L9_VERIFYING_KEYS_JSONwas silently ignored. Multi-key signature verification was impossible at runtime.Fix:
_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 populatedverifying_keysdict.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_TOKENvsGATE_ADMIN_TOKEN(MEDIUM)Problem: Gate used
L9_GATE_ADMIN_TOKEN; SDK usesGATE_ADMIN_TOKEN. Node registration auth silently failed with mismatched env vars.Fix:
get_settings()readsGATE_ADMIN_TOKENfirst (canonical, matches SDK), falls back toL9_GATE_ADMIN_TOKENfor backward compatibility..env.exampleupdated to useGATE_ADMIN_TOKENas 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.pydid not exist.Fix:
scripts/validate_contracts.py— validates schema JSON, required fields,$id,additionalProperties,actionpattern,signature_algorithmenum,hop.statusenum,hop_signature_algorithmenum.validate-contractslocal 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.pypassesL9_GATE_ADMIN_TOKENalias still accepted)Risk Notes
L9_GATE_ADMIN_TOKENalias preserved..env.examplewithGATE_WORKFLOW_CONFIG_PATHcommented out by default._load_workflow_definitionsvalidates Pydantic models before engine receives them.Summary by CodeRabbit
New Features
Configuration
GATE_ADMIN_TOKEN), with backward compatibility maintained.