Repository navigation
feat(omnigraph): hand the cluster lock over in the server entrypoint - #479
Open
blarghmatey wants to merge 3 commits into
Open
blarghmatey wants to merge 3 commits into
blarghmatey wants to merge 3 commits into
Conversation
omnigraph 0.13 has no `cluster import` or `cluster refresh`, and every writer of a cluster root (the server, `cluster apply`, `optimize`, `cleanup`) leaves an exclusive lock behind when it exits. The shell entrypoint would fail on its first command, and a pod restart would be refused with `state_lock_held` until someone force-unlocked by hand. This replaces the shell entrypoint with a Python one that takes a Kubernetes Lease, waits until no other live pod matches the Deployment's selector, renders group membership, plans, releases the previous owner's `serve` or `deployment` lock, applies, releases its own apply's lock and execs the server. It exits 1 on a `graph_operation` lock, on an outstanding deployment, and on a cluster.yaml that drops a graph outside OMNIGRAPH_GRAPH_REMOVAL_ALLOWLIST, because 0.13 deletes a dropped graph's storage. Losing the Lease stops the running child and unlocks nothing. The policy fixture graph `code_example` becomes `code-example`: 0.13's `cluster apply` accepts the underscore but omnigraph-server refuses to start on it. policy/check.sh drops `cluster import`. This only works on 0.13. On 0.11 `cluster apply` needs the ledger that `import` created, so policy/check.sh fails there, and the real-binary tests skip until the pin reports storage format 14. It has to merge with the pin bump. Design: docs/internals/design/omnigraph-0-13-upgrade-spec.md (D3, D4), #461 Claude-Session: https://claude.ai/code/session_011nyp32f2v7efTTBMqnmEuG
Three things from review of the entrypoint rewrite. A renewal that hit a 409 and found the Lease still naming this holder returned failure, so a confirm() that landed on a relabelled Lease refused the boot. It now retries once on the new resourceVersion. A truncated Kubernetes API response raises http.client.HTTPException, which is neither OSError nor ValueError. It escaped _request and ended the renew thread, and the running apply was then stopped 20 seconds later for no reason. `--cluster=<value>` was not recognised, so a storage-URI root passed that way would have converged the default directory. The Dockerfile comment said python3-minimal lacks json and ssl. Both import there; urllib.request and secrets are the ones that do not (checked in debian:trixie-slim). Claude-Session: https://claude.ai/code/session_011nyp32f2v7efTTBMqnmEuG
Outside Kubernetes the entrypoint has no Lease and no pod list, and it still force-unlocked whatever `serve` or `deployment` lock it found. A `docker run` whose cluster.yaml points at a served storage root would have removed the live server's lock and applied over it. It now refuses to force-unlock there unless OMNIGRAPH_UNLOCK_WITHOUT_PEERS=1 is set. `cluster apply` always leaves a lock, so a local run needs the variable on an empty root too. Claude-Session: https://claude.ai/code/session_011nyp32f2v7efTTBMqnmEuG
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The safety-critical Kubernetes client lacks direct coverage, and deployment still depends on the 0.13 pin and unfinished infrastructure configuration.
1 open finding
What changed in this PR
Replaces the Omnigraph server’s shell bootstrap with a Kubernetes-aware Python entrypoint for safe lock handoff during the 0.13 upgrade.
Changes:
- Adds Lease-based coordination, peer detection, guarded planning/apply, and lock release.
- Adds extensive entrypoint tests and CI coverage.
- Updates policy fixtures and documentation for Omnigraph 0.13.
| File | Description |
|---|---|
docker/omnigraph_server_entrypoint.py |
Implements coordinated boot and lock handoff. |
docker/omnigraph-server-entrypoint.sh |
Removes the legacy shell bootstrap. |
docker/omnigraph-server.Dockerfile |
Installs and invokes the Python entrypoint. |
docker/README.md |
Documents boot behavior and environment variables. |
.github/workflows/witan-tests.yml |
Triggers tests for entrypoint changes. |
.gitignore |
Updates cluster-state generation notes. |
mcp/servers/witan/tests/test_omnigraph_server_entrypoint.py |
Tests Lease, planning, locking, and real-binary behavior. |
mcp/servers/witan/policy/check.sh |
Adopts the 0.13 direct-apply workflow. |
mcp/servers/witan/policy/cluster.yaml |
Renames the fixture graph. |
mcp/servers/witan/policy/code-graph.policy.yaml |
Updates the fixture graph reference. |
mcp/servers/witan/policy/tests/code-graph.tests.yaml |
Updates the policy-test command. |
mcp/servers/witan/policy/schema/stub.pg |
Updates fixture documentation. |
mcp/servers/witan/policy/render_groups.py |
References the new entrypoint. |
mcp/servers/witan/policy/README.md |
Documents renamed fixtures and entrypoint. |
mcp/servers/witan/docs/adr/0002-witan-cedar-authorization-bundle.md |
Updates the ADR fixture name. |
docs/explanation/decisions/0002-witan-cedar-authorization-bundle.md |
Updates the mirrored decision document. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| self.status = status | ||
|
|
||
|
|
||
| class KubernetesApi: |
This was referenced Oct 13, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What are the relevant tickets?
N/A (witan task tk-rewrite-the-omnigraph-server-entrypoint-and-poli-39b7a8, part of the omnigraph 0.13 upgrade, spec in #461 D3 and D4)
Description (What does it do?)
omnigraph 0.13 has no
cluster importorcluster refresh, and every writer of a cluster root (the server,cluster apply,optimize,cleanup) leaves an exclusive lock in the storage root when it exits. The shell entrypoint fails on its first command under 0.13, and without an unlock every pod restart would be refused withstate_lock_held.This replaces
docker/omnigraph-server-entrypoint.shwithdocker/omnigraph_server_entrypoint.py. It is Python because it has to talk to the Kubernetes API and renew a Lease while a child runs. The boot sequence:coordination.k8s.ioLease named byOMNIGRAPH_UNLOCK_LEASE, or exit 1 if another holder's is still valid. A holder left by an earlier container of the same pod is taken over.OMNIGRAPH_POD_SELECTOR(Terminating pods count,FailedandSucceededdo not), or exit 1 afterOMNIGRAPH_PEER_WAIT_SECONDS(default 50).render_groups.py, unchanged).cluster plan. Exit 1 on an outstanding deployment, on a graph removal not listed inOMNIGRAPH_GRAPH_REMOVAL_ALLOWLIST(0.13 deletes a dropped graph's storage and history), or on any other plan error.cluster status. Force-unlock aserveordeploymentlock, and exit 1 on anything else (e.g.graph_operation, which belongs to a maintenance run).cluster apply --as $OMNIGRAPH_APPLY_ACTOR, then force-unlock thedeploymentlock it left.exec omnigraph-server.Every force-unlock first proves the Lease with a conditional write and checks that enough of it is left. If no renewal has succeeded for 20s the running child is stopped and the boot exits without unlocking.
Outside Kubernetes (no
KUBERNETES_SERVICE_HOST) there is no Lease or pod list to consult, so the entrypoint refuses to force-unlock unlessOMNIGRAPH_UNLOCK_WITHOUT_PEERS=1is set.cluster applyalways leaves a lock, so a localdocker runneeds it on an empty root too. Without the opt-in, adocker runwhosecluster.yamlpoints at a served root would unlock the live server.Two differences from the sequence written in the spec:
cluster planreports it as acluster_deployment_outstandingdiagnostic, and it does so while the dead apply'sdeploymentlock is still held. I did not look for an equivalent field incluster status.test_real_interrupted_apply_is_reported_as_outstandingpins the plan diagnostic by killing an apply mid-run.OMNIGRAPH_APPLY_ACTORis required when the image carries policy bundles. A no-op apply passes without an actor and the first schema change does not.Also here: the policy fixture graph
code_exampleis renamed tocode-example(0.13cluster applyaccepts the underscore butomnigraph-serverrefuses to start on it),policy/check.shdropscluster import, and the image installspython3in place ofpython3-minimal, under whichurllib.requestandsecretsdo not import (checked indebian:trixie-slim).This only works on 0.13 and has to merge with the pin bump, not before it. On the pinned 0.11.0
policy/check.shfails at the converge step, since 0.11'scluster applyneeds the ledger thatimportcreated, so thewitan (Cedar policy bundle)check will be red here until the pin moves.The ol-infrastructure side is not done yet and the image cannot boot in the cluster without it: the pre-created Lease,
get/updateon it andliston pods for the server's ServiceAccount, and theOMNIGRAPH_UNLOCK_LEASE,OMNIGRAPH_POD_SELECTORandOMNIGRAPH_APPLY_ACTORenv vars (tk-ol-infrastructure-omnigraph-stack-for-0-12-clust-2dee94).docker/README.mdlinks to the spec file, which arrives with #461.How can this be tested?
PATH,--noconftestbecause the hermetic conftest puts the pinned binary back in front):test_omnigraph_server_entrypoint.py54 passed, none skipped. Five of those run the real sequence on afile://copy of the policy fixture: first boot, boot/serve/SIGTERM/boot again, a schema change, a dropped graph with and without the allow-list, and an apply killed mid-run.policy/check.shwith the 0.13.0 binary passes (80 policy test cases, and the rendered bundles validate).test_omnigraph_server_entrypoint.py49 passed, 5 skipped (the real-binary cases skip until the pin reports storage format 14).just test-witan-councilwas 1497 passed, 6 skipped on the first commit, and I did not rerun the full suite after the two fix commits, which touch only the entrypoint, its test file anddocker/.policy/check.shfails there withstate_missing ... run cluster import to bootstrap state, as described above.KubernetesApiitself (URL building, TLS, error mapping),main, a Lease lost between the first and second unlock, or the renew thread running concurrently withconfirm().https://claude.ai/code/session_011nyp32f2v7efTTBMqnmEuG