Prepare Legis 1.1.1 hardening release#16
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17a370c8a6
ℹ️ 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".
| return 1 | ||
|
|
||
| ledger = PostureLedger(posture_db_url(), initialize=False) | ||
| epoch_fp = ledger.current_epoch_fingerprint() |
There was a problem hiding this comment.
Refuse cleanly when the posture DB directory is absent
When legis operator enable --insecure-key-in-env is run before any install has created .weft/legis/, this new current_epoch_fingerprint() read opens an initialize=False SQLite store without creating the parent directory or checking whether the DB path exists. SQLite will raise unable to open database file before the intended epoch_fp is None refusal branch runs, so a fresh repo gets a traceback instead of the friendly fail-closed message and exit code. Check the posture DB path (as read_floor() does) or catch the store read failure before this call.
Useful? React with 👍 / 👎.
| run: uv sync --dev | ||
|
|
||
| - name: Verify lockfile | ||
| run: uv lock --check |
There was a problem hiding this comment.
Check the lock before mutating sync
In this workflow, the lock check runs after uv sync --dev. I checked uv sync --help: --locked is the option that asserts uv.lock remains unchanged, while uv lock --check only checks the current lockfile state. If pyproject.toml and uv.lock drift on a release, the preceding sync can refresh uv.lock in the runner and this check then passes, letting publish proceed from an unreviewed lock; run the check before syncing or use uv sync --locked --dev here.
Useful? React with 👍 / 👎.
| backend=backend, | ||
| ) | ||
| except Exception as exc: # noqa: BLE001 — custody gap is a fail-closed refusal | ||
| except (OperatorKeyCustodyError, RuntimeError, ValueError) as exc: |
There was a problem hiding this comment.
Preserve fail-closed refusal for custody write errors
When rekeying to age-file and the custody path cannot be written (for example .weft/legis is unwritable, the disk is full, or os.replace fails), _default_key_sink can raise OSError from mkdir, mkstemp, or the replace. This handler used to catch all ledger.rekey(...) failures, but now those OS custody failures escape as a traceback instead of the intended posture rekey: refused response with no KEY_RESET written. Include OSError or restore a broader fail-closed catch around the custody handoff.
Useful? React with 👍 / 👎.
| from legis.config import governance_db_url | ||
| from legis.enforcement.judge_factory import configured_judge_from_env | ||
|
|
||
| judge = configured_judge_from_env("MCP") |
There was a problem hiding this comment.
Handle incomplete judge env as not enabled
For a coached policy when LEGIS_JUDGE_PROVIDER=openrouter is set but the required credentials or token settings are missing/invalid, configured_judge_from_env() raises LLMTransportError here. That bypasses the judge is None disabled path, so policy_explain, policy_list, or override_submit become an internal MCP error instead of the documented actionable “coached not enabled; set provider credentials” response. Catch the configuration error and map it to no coached engine (or NotEnabledError) so partial judge configuration fails closed as an enablement problem.
Useful? React with 👍 / 👎.
Summary
Review
Local verification