Skip to content

fix(OMN-15645): bind DELEGATION_ROUTING_TIERS_PATH on .201 compose lanes - #2620

Merged
jonahgabriel merged 4 commits into
devfrom
jonah/omn-15645-build-lane
Aug 2, 2026
Merged

jonahgabriel merged 4 commits into
devfrom
jonah/omn-15645-build-lane

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Aug 2, 2026 •

Copy link
Copy Markdown
Collaborator

OMN-15645

Binds DELEGATION_ROUTING_TIERS_PATH on all four .201 compose lane projects (omnibase-infra, omnibase-infra-stability-test, omnibase-infra-judge, omnibase-infra-prod). omnimarket#2000 (OMN-15628) removed the packaged-default fallback for this key in the delegation routing reducer's _get_config() singleton; an unbound key now raises ProtocolConfigurationError at first config read. The key was bound on zero .201 compose lanes before this PR — the next cold rebuild of dev/stability-test/judge (and the next prod release promotion carrying #2000) refuses boot.

Fix

  • docker/docker-compose.infra.yml: bind DELEGATION_ROUTING_TIERS_PATH in the shared x-runtime-env anchor (same locus/reason BIFROST_CONTRACT_PATH is hardcoded, OMN-12864: no ${VAR:-} ambient-override footgun). All four lane projects inherit this anchor (verified live, see below). projection-api and omninode-contract-resolver explicitly opt out with "" (no delegation-routing surface), mirroring the existing BIFROST_CONTRACT_PATH opt-out for the same two services.
  • docker/Dockerfile.runtime: bakes the packaged omnimarket routing_tiers.yaml into the fixed path /app/contracts/delegation/routing_tiers.yaml at build time via COPY --from=builder .../lib/python*/site-packages/omnimarket/configs/routing_tiers.yaml. The python* glob is the same technique already used for the omnibase_core runtime-contracts COPY a few lines above — never a literal python3.X site-packages path, which is exactly the trap OMN-15628's k8s-side entrypoint self-heal (merged just before this PR, docker/entrypoint-runtime.sh) exists to correct for a stale pin. That self-heal is untouched and remains defense-in-depth.

Seam definition (omnimarket consumer, field-by-field)

Field Value
env-var key name DELEGATION_ROUTING_TIERS_PATH (exact string)
consumer omnimarket src/omnimarket/nodes/node_delegation_routing_reducer/handlers/handler_delegation_routing.py:392-393, _get_config() singleton
resolver resolve_required_path_config("DELEGATION_ROUTING_TIERS_PATH") — omnimarket/src/omnimarket/inference/delegation_config_provenance.py:126-158
blank-is-absent os.environ.get(key, "").strip() (delegation_config_provenance.py:225) — "" and unset are equivalent, which the projection-api/contract-resolver opt-outs rely on
required-vs-optional required, no packaged fallback (resolve_required_path_config, not resolve_path_config) — raises ValueError (wrapped to ProtocolConfigurationError) when blank/unset
error on failure ProtocolConfigurationError naming the key, ONEX_CORE_041_INVALID_CONFIGURATION
success provenance ModelDelegationConfigProvenance.log_line() INFO log: config_provenance surface=delegation config_key=DELEGATION_ROUTING_TIERS_PATH source=contract_overlay_env override_present=true resolved_path=<path>

BIFROST_CONTRACT_PATH is explicitly not touched — different resolution semantics (resolve_optional_path_config), already bound everywhere; out of scope per the ticket.

Acceptance criteria -> proof

AC1 (RED first). Captured against the real merged-dev code (omnibase_infra@94a3a11c1, omnimarket@3d60ae73 — both pulled fresh on .201), driving the real _get_config() singleton (installed via uv pip install --no-deps -e into a venv, not mocked), DELEGATION_ROUTING_TIERS_PATH unset:

$ env -u DELEGATION_ROUTING_TIERS_PATH .venv/bin/python -c '...; _get_config()'
EXIT_CODE=1
EXC_TYPE=ProtocolConfigurationError
EXC_MSG=[ONEX_CORE_041_INVALID_CONFIGURATION] DELEGATION_ROUTING_TIERS_PATH is not bound. This delegation-path config key has no packaged-default fallback (CLAUDE.md rule 8 -- no silent config fallback, OMN-15628): set DELEGATION_ROUTING_TIERS_PATH explicitly via the contract overlay/deployment env binding for this surface.

AC2 (all four lanes bind the key to an existing in-image path, readback not assertion). docker compose -p <project> config on .201 (operator env sourced, set -a) for all four lanes:

Project omninode-runtime runtime-effects runtime-worker projection-api contract-resolver
omnibase-infra (dev) /app/contracts/delegation/routing_tiers.yaml same same "" ""
omnibase-infra-stability-test same same same "" n/a (not in this profile render)
omnibase-infra-judge same same n/a (judge profile) "" n/a (judge-profile-excluded)
omnibase-infra-prod (config-level only, no recreate) same same same "" n/a (not in this profile render)

Plus, on the real image built by this PR's Dockerfile change (docker compose ... build, workspace mode, OMNIMARKET_REF=3d60ae73...):

$ docker run --rm --entrypoint sh omnibase-infra-omninode-runtime:latest -c 'test -f /app/contracts/delegation/routing_tiers.yaml && echo FILE_EXISTS && sha256sum /app/contracts/delegation/routing_tiers.yaml /app/.venv/lib/python*/site-packages/omnimarket/configs/routing_tiers.yaml'
FILE_EXISTS
7e04bd0eb916c8cf4d12295c81203b45f6b16129399df116815ce6630d41e094  /app/contracts/delegation/routing_tiers.yaml
7e04bd0eb916c8cf4d12295c81203b45f6b16129399df116815ce6630d41e094  /app/.venv/lib/python3.12/site-packages/omnimarket/configs/routing_tiers.yaml

Exact sha256 match, in-container test -f exits 0.

AC3 (GREEN). deploy-runtime.sh --execute --cold on the dev lane (omnibase-infra) built cleanly from merged dev (all 8 images built, my Dockerfile COPY step included) — but the full orchestrated bring-up did not reach a clean end-to-end boot, blocked by two pre-existing, unrelated defects already on merged dev independent of this change:

  1. forward-migration fails: unresolved migration domain for node:node_projection_delegation:0016_delegation_judge_verdict_events.sql (OMN-15423: delegation_judge_verdict_events domain unresolved) — a tracked, cited ticket, unrelated to env-var binding.
  2. Starting omninode-runtime directly (bypassing the broken one-shot, migration-gate already healthy) crash-loops on a separate boot-time error: ONEX_DATABASE_TOPOLOGY_PROFILE is not set for a list of newly-discovered projection contracts — this matches the class of work in OMN-15418 ("P0: Replace raw db_io loading with typed declarations and exactly-one-owner validation", In Review) / OMN-15417 (Done); not filing a duplicate, citing as the likely tracking ticket.

Given those two blockers, GREEN is proven at the mechanism level instead, on the actual image this PR's Dockerfile builds:

$ docker run --rm -e DELEGATION_ROUTING_TIERS_PATH=/app/contracts/delegation/routing_tiers.yaml --entrypoint sh omnibase-infra-omninode-runtime:latest -c 'python -c "from omnimarket.nodes.node_delegation_routing_reducer.handlers.handler_delegation_routing import _get_config; print(_get_config())"'
INFO:omnimarket.inference.delegation_config_provenance:config_provenance surface=delegation config_key=DELEGATION_ROUTING_TIERS_PATH source=contract_overlay_env override_present=true resolved_path=/app/contracts/delegation/routing_tiers.yaml
CONFIG_LOAD_SUCCESS ModelDelegationConfig

The exact ModelDelegationConfigProvenance INFO log line the ticket asks for, the loader reading the real (sha256-matched) file, and the config object constructing successfully — on the real built image, real omnimarket wheel, real routing-reducer code path. Honest gap: the literal "boots clean end-to-end" wording of AC3 is not met on the dev lane today, for reasons outside this PR's diff. Dev lane's omninode-runtime container is left stopped (not crash-looping) after this investigation; the old pre-build image tag was not recoverable (deploy-runtime.sh's own rollback failed to restore it — logged, unrelated to this PR).

AC4 (mechanism, same PR). The existing scripts/check_required_env_vars.py walks compose->env-file only and is structurally blind to a code-required key missing from compose entirely (as the ticket states). This PR:

  • Extends tests/ci/test_runtime_env_anchor.py's REQUIRED_RUNTIME_KEYS frozenset (an existing, already-CI-wired reverse-direction check: declared-required-list -> anchor-coverage) with DELEGATION_ROUTING_TIERS_PATH, citing the omnimarket call site inline. Reuses existing machinery per the net-negative-surface rule rather than building a parallel check.
  • Adds one regression test per lane render fixture (tests/integration/infra/test_{dev,stability_test,judge,prod}*compose_render*.py) asserting the resolved docker compose config shows the key bound correctly, docker-gated (skips cleanly without Docker, matching the existing convention).
  • Adds tests/unit/docker/test_delegation_routing_tiers_path_binding_omn15645.py: static (no-Docker) guards that the compose default and Dockerfile bake stay at the same fixed path, never regress to a version-embedded literal, and documents the omnimarket-side seam contract (omnimarket is not a pyproject dependency of omnibase_infra, so a literal cross-repo import test cannot run in this repo's own CI — the RED/GREEN evidence above is the actual cross-boundary proof).
  • Deferred: the fully generic "arbitrary code-declared-required-key -> lane-compose-coverage" scanner (would need to introspect the installed omnimarket package's source for resolve_required_path_config(...) call sites, since omnimarket is not vendored in this repo and is not a pyproject dependency here) is deferred to OMN-14951 (gate: env-surface-as-contract, In Progress, named in the ticket as the natural home). Building a robust, non-gameable version of that scanner is a materially larger, separate piece of work than this ticket's registry-based extension.

Prod boundary

Committing the compose/Dockerfile change is in scope. Recreating the live omnibase-infra-prod runtime is explicitly not in scope and was not done — prod verification above is config-level readback only (docker compose config, no up/--force-recreate). Applying this to live prod routes through the gated redeploy-start -> prod-promotion gate -> deploy-agent path with a fresh grant, per CLAUDE.md rules 2a/12, at the next release promotion.

Gates

Ran on stickybeatz-studio (.200) via patch-transfer + sha256 verification of every changed file (local Edit/Write never touch .200 directly): ruff format --check, ruff check, focused pytest (static seam tests + test_runtime_env_anchor.py + existing self-heal tests, all green; the four new integration render tests skip cleanly on .200 — no Docker daemon there — and were verified directly against real docker compose config/docker run on .201 instead, see AC2/AC3 above), mypy (3 pre-existing dict-without-type-args errors in code this PR did not touch, confirmed identical on the unmodified base), pre-commit run --all-files on the changed set (all pass; one pre-existing host-env gap on .200's local ~/.omnibase/.env unrelated to this diff, fixed at the host level so the gate host itself is usable going forward).

OMN-15645

Summary by CodeRabbit

  • New Features
    • Runtime services now include the packaged delegation routing configuration at a consistent container path.
    • Supported services automatically receive the routing configuration, while unsupported services explicitly opt out.
  • Bug Fixes
    • Improved compatibility across Python versions by removing version-specific configuration paths.
  • Tests
    • Added coverage validating configuration bindings across development, judge, production, and stability environments.

Evidence-Source: 1eda09714b927494a06d0fd17672503c287eeaf1
Evidence-Ticket: OMN-15645

omnimarket#2000 (OMN-15628) removed the packaged-default fallback for
DELEGATION_ROUTING_TIERS_PATH in the delegation routing reducer's
_get_config() singleton -- an unbound key now raises
ProtocolConfigurationError at first config read instead of silently
defaulting. The key was bound on zero .201 compose lanes, so the next
cold rebuild of dev/stability-test/judge (and the next prod release
promotion carrying #2000) refuses boot.

Bind the key at the shared x-runtime-env anchor in
docker/docker-compose.infra.yml (all four lane projects inherit it),
same locus/reason BIFROST_CONTRACT_PATH is already hardcoded
(OMN-12864: no ${VAR:-} ambient-override footgun). The bound value is
a fixed, non-version-embedded in-image path
(/app/contracts/delegation/routing_tiers.yaml) that
docker/Dockerfile.runtime bakes at build time via a glob-derived COPY
(python*, matching the existing omnibase_core runtime contracts COPY
precedent) -- never a literal python3.X site-packages path. Services
with no delegation-routing surface (projection-api,
omninode-contract-resolver) explicitly opt out with "", mirroring the
BIFROST_CONTRACT_PATH opt-out.

Mechanism: extends tests/ci/test_runtime_env_anchor.py's
REQUIRED_RUNTIME_KEYS registry (code-declared-required-key ->
compose-anchor-coverage), adds compose-render regression tests to all
four lane fixtures under tests/integration/infra/, and adds a static
seam-consistency test file documenting the omnimarket contract. Fully
generic code-scanning mechanism deferred to OMN-14951 (PR body).

OMN-15645
@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The runtime image now packages routing_tiers.yaml at a stable path. Compose binds this path for supported runtime services and explicitly disables it for projection and contract-resolver services. Unit and integration tests validate the configuration.

Changes

Delegation routing tiers configuration

Layer / File(s) Summary
Runtime image path
docker/Dockerfile.runtime, tests/unit/docker/test_delegation_routing_tiers_path_binding_omn15645.py
The image copies the packaged routing tiers file to a fixed path. Unit tests reject Python-version-specific paths and verify copy ordering.
Compose environment bindings
docker/docker-compose.infra.yml, tests/ci/test_runtime_env_anchor.py, tests/unit/docker/test_delegation_routing_tiers_path_binding_omn15645.py
The shared runtime environment binds DELEGATION_ROUTING_TIERS_PATH. Projection API and contract resolver set it to an empty value.
Rendered Compose validation
tests/integration/infra/test_dev_runtime_compose_render.py, tests/integration/infra/test_judge_compose_render.py, tests/integration/infra/test_prod_runtime_compose_render.py, tests/integration/infra/test_stability_test_runtime_compose_render.py
Compose rendering tests verify the fixed path for supported services and the absence of the setting for unsupported services.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: binding DELEGATION_ROUTING_TIERS_PATH across the .201 Compose lanes.
✨ 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 jonah/omn-15645-build-lane

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

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker/docker-compose.infra.yml`:
- Around line 169-183: Move the baked routing tiers artifact to a stable path
outside the /app/contracts mount and update DELEGATION_ROUTING_TIERS_PATH in
docker/docker-compose.infra.yml to reference it. Update
docker/Dockerfile.runtime to copy routing_tiers.yaml to that unmounted path. In
tests/unit/docker/test_delegation_routing_tiers_path_binding_omn15645.py:51-53,
tests/integration/infra/test_dev_runtime_compose_render.py:283-306,
tests/integration/infra/test_judge_compose_render.py:368-385,
tests/integration/infra/test_prod_runtime_compose_render.py:161-175, and
tests/integration/infra/test_stability_test_runtime_compose_render.py:451-465,
replace the expected path; additionally ensure the dev runtime test verifies the
/app/contracts mount does not mask the new path.
🪄 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: 086b40f7-789d-4a65-9acb-30319709fa1d

📥 Commits

Reviewing files that changed from the base of the PR and between 805aa24 and ac4817c.

📒 Files selected for processing (8)
  • docker/Dockerfile.runtime
  • docker/docker-compose.infra.yml
  • tests/ci/test_runtime_env_anchor.py
  • tests/integration/infra/test_dev_runtime_compose_render.py
  • tests/integration/infra/test_judge_compose_render.py
  • tests/integration/infra/test_prod_runtime_compose_render.py
  • tests/integration/infra/test_stability_test_runtime_compose_render.py
  • tests/unit/docker/test_delegation_routing_tiers_path_binding_omn15645.py

Comment thread docker/docker-compose.infra.yml Outdated
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 2, 2026
#5917)

* evidence: OCC companion pass 1 for OmniNode-ai/omnibase_infra#2620

* evidence: OCC companion self-bind for #5917

---------

Co-authored-by: node-occ-companion-effect <occ-companion-effect@omninode.ai>
…parity debt

CI's Env Parity gate (tests/ci/test_env_parity.py) requires every
x-runtime-env key to be accounted for in the k8s ConfigMap, SECRET_KEYS,
LOCAL_ONLY_KEYS, or CONFIGMAP_DEBT_KEYS. The new binding surfaced this
gap. Add it to CONFIGMAP_DEBT_KEYS mirroring the existing
BIFROST_CONTRACT_PATH precedent (OMN-10943) -- the k8s ConfigMap side is
OMN-15628's own k8s-manifest-scoped acceptance criterion (In Progress,
sibling omninode_infra checkout), not this ticket's scope.

OMN-15645
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Blocking findings (critical): 0
Total findings: 0
Models succeeded: none

Note: All reviewer models failed or were unavailable. Degraded results are informational during the pilot phase (OMN-8468/OMN-8524) and do not block merge. Error: all review endpoints [192.168.86.201:8000 192.168.86.201:8001 ] unreachable — preflight short-circuit (no models available)


Gate semantics (pilot phase)

Verdict Meaning Blocks merge?
passed No critical findings No
blocked CRITICAL findings found Yes
degraded All models unavailable (infra) No (pilot)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8468/OMN-8524)

…ts mount

CodeRabbit catch (PR #2620): the runtime services bind-mount
../contracts:/app/contracts:ro (host content). Baking
routing_tiers.yaml into /app/contracts/delegation/ was silently
shadowed by that mount at container start -- the image-baked file was
invisible, defeating the whole fix and leaving the entrypoint's
OMN-15628 self-heal to paper over it via internal re-export (which
would NOT satisfy the OMN-15645 AC2 in-container test -f requirement
against the compose-declared, static path).

Relocate the bake destination (and the compose binding) to
/app/config/delegation/routing_tiers.yaml -- a path with zero
volume/bind-mount entries anywhere in the compose lane files. Verified
on .201 with a real docker run reproducing the exact bind-mount shape
(-v $(pwd)/contracts:/app/contracts:ro): the file now exists and
resolves correctly under the mount, sha256-matches the packaged
omnimarket copy, and _get_config() loads successfully end-to-end.

Adds a permanent static regression test
(test_expected_path_is_never_shadowed_by_a_volume_mount) that parses
every runtime service's volumes: list and fails if the bound path ever
falls under a mounted target again.

OMN-15645
onexbot-occ-writer Bot pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 2, 2026
…-test/prod profile

CI (which has real Docker, unlike .200) caught what manual .201
verification already showed but the test code didn't encode:
omninode-contract-resolver is not part of the --profile runtime render
for the stability-test and prod lanes (unlike dev and judge, where it
either renders or is judge-profile-excluded via a different path). The
new delegation-routing-tiers-path tests hard-indexed
services['omninode-contract-resolver'], raising KeyError instead of
skipping the absent service.

Guard both assertions with services.get(...) + skip-if-absent,
matching the pattern already used in the judge lane's version of this
test.

OMN-15645
@jonahgabriel
jonahgabriel merged commit 381fccc into dev Aug 2, 2026
116 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-15645-build-lane branch August 2, 2026 03:21
jonahgabriel added a commit that referenced this pull request Aug 2, 2026
…ut dev red (#2622)

omnibase_infra dev has been RED on its own committed env-parity gate since
merge 04b4b43. Two individually-green PRs, merged 14 minutes apart, each
added DELEGATION_ROUTING_TIERS_PATH to the SAME docker-compose.infra.yml
x-runtime-env anchor with a DIFFERENT value:

  #2620 (OMN-15645, 03:21:51Z) -> /app/config/delegation/routing_tiers.yaml
  #2621 (OMN-15628, 03:35:23Z) -> /app/.venv/lib/python3.12/site-packages/...

YAML keeps the last occurrence, so the earlier declaration became silently
dead and the elected value contradicted judge/e2e and the k8s Deployment pin.
Neither PR could have gone red on its own; the failure exists only in the
merge product.

FIX
- Delete the duplicate declaration. One canonical compose value survives:
  /app/config/delegation/routing_tiers.yaml -- the version-independent path
  docker/Dockerfile.runtime:851-867 bakes from the installed omnimarket
  package, and the one that is not shadowed by the ../contracts:/app/contracts
  bind-mount.
- Align docker-compose.judge.yml and docker-compose.e2e.yml to the same
  literal. All three lanes build the same Dockerfile.runtime image.

GATES ADDED (each proven RED against the merged-dev tree first)
- test_no_duplicate_keys_in_compose_files: node-level duplicate-key scan over
  every docker-compose*.yml. This is the check that would have caught the
  collision. Loader-based checks structurally cannot -- yaml.safe_load
  collapses the duplicate before any assertion runs.
- test_delegation_routing_tiers_path_matches_k8s_pin: extended from
  infra.yml-only to every lane that stands up a runtime container, and now
  requires all lanes to agree on one literal. Accepts the k8s pin or a path
  Dockerfile.runtime bakes it to -- aliases are PARSED from the Dockerfile
  COPY, never hardcoded, so a typo still fails.

VERIFIER REMEDIATION (round 1 findings)
- Forward parity surface no longer widened to a plain union. It is now the
  runtime-FAMILY surface: ConfigMap keys plus keys inline on ALL THREE runtime
  Deployments, so a per-workload binding cannot satisfy an anchor-wide claim.
  test_k8s_family_surface_requires_all_runtime_deployments locks it against
  the real manifests with the binding removed from exactly one Deployment.
- Lane-overlay inheritance check is structural over the parsed service graph
  instead of a raw-text regex. The old regex matched only the literal string
  "environment: !override" and was blind to !reset, which severs inheritance
  identically.
- Module docstring now states each direction's scope explicitly, including
  that the reverse walk is deliberately infra.yml-only.

ALSO FIXED (second live dev breakage, pre-existing, same blast radius)
tests/unit/infra/test_compose_no_silent_fallbacks.py scanned raw file text, so
the #2620 comment that explains the ban by quoting it registered as a
violation on a variable literally named VAR. The matcher now strips comments,
quote-aware. test_comment_stripping_does_not_defang_the_ban proves a real
empty-default in live YAML still fails, that a hash inside a quoted scalar is
not eaten, and that only the prose case is exempt.

KNOWN, NOT ASSERTED: onex-dev configmap.yaml carries this key pinned to a
third path (/app/contracts/delegation/routing_tiers.yaml). All three runtime
Deployments override it inline and inline env beats envFrom, so the runtime
family is unaffected -- but a workload that envFroms without an inline
override would get the stale path. That is an omninode_infra fix; asserting it
here would leave a permanently-red gate on a defect this repo cannot resolve.

Ticket: OMN-15628

Co-authored-by: Jonah Gray <jonah.g.gray@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant