fix: resolve three lacuna-dogfood defects (posture crash, env mint-drop, fingerprint fragility) — v1.1.0#14
Conversation
…op, fingerprint fragility)
Three defects surfaced by a lacuna dogfooding pass, confirmed against the shipped
1.0.0 surface (investigation + adversarial verification) and fixed test-first.
1. Posture reads crash on an unprovisioned ledger (legis-5fd3b257c3).
Production opens the posture ledger initialize=False, so a pre-posture / empty
(no audit_log table) DB made posture_get and policy_list raise
OperationalError("no such table: audit_log"), reaching the agent as a
non-recoverable INTERNAL_ERROR leaking the SQL string — the intended
fail-closed `structured` degrade was never delivered. AuditStore reads now
treat an absent table as an empty store (same state as a missing file), so
posture reads fail closed to `structured` instead of crashing.
2. install --posture --insecure-key-in-env mint-drops the operator key
(legis-1844bf8ac9). install_posture always minted a fresh key and the env
custody sink was a no-op, so LEGIS_OPERATOR_KEY was ignored: GENESIS was
stamped with a throwaway the later EnvSigner never held, and every posture set
refused fingerprint_mismatch (the floor was a dead read-only affordance). The
env backend now adopts + validates LEGIS_OPERATOR_KEY (64 hex) as the epoch
key, and fails loud (no dead GENESIS) when it is absent or malformed.
3. BREAKING (for pinned fingerprints): policy-boundary test_fingerprint is now
interpreter-stable (legis-13b4e97bf4). The fingerprint hashed raw ast.dump
output, which is Python-version-dependent (3.13 omits default-empty AST fields
3.12 renders) → spurious POLICY_BOUNDARY_TEST_FINGERPRINT_MISMATCH across
interpreters. get_normalized_ast_str now serializes via _stable_ast_repr, which
emits every field of every node in a fixed order — version-stable, pinned by a
cross-interpreter 3.12<->3.13 test. This changes the fingerprint value for all
sources; consumers with pinned test_fingerprints must regenerate once.
Bumps version to 1.1.0. Full suite: 1160 passed, 5 skipped; ruff check src and
mypy src/legis clean; all per-package coverage floors hold.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af45413ca0
ℹ️ 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".
| if not self._has_log_table(conn): | ||
| return [] |
There was a problem hiding this comment.
Fail closed when the audit table is missing
When any initialized governance/signoff/binding audit database is pointed at an empty file or has its audit_log table dropped out of band, this now returns an empty record set; verify_integrity() then iterates over [] and returns True, so callers such as BindingLedger.verify() and the governance verified_records() path treat a missing trail as a valid, empty one. The posture unprovisioned-DB case needs special handling, but the generic audit store should not silently pass integrity verification when the audit table itself is gone.
Useful? React with 👍 / 👎.
| fields = ", ".join( | ||
| f"{name}={_stable_ast_repr(getattr(node, name, None))}" | ||
| for name in node._fields | ||
| ) |
There was a problem hiding this comment.
Normalize AST fields independently of runtime version
Because this walks the current interpreter's node._fields, fields added in newer Python minors still change the serialized text. I checked a source valid on the supported floor, def test_generic[T](): ...: Python 3.12's ast.TypeVar._fields is ('name', 'bound'), while 3.13 adds default_value, so this serializer emits default_value=None only on 3.13 and the fingerprints differ. Any pinned boundary evidence test using PEP-695 type parameters will still report drift after a 3.12→3.13 upgrade.
Useful? React with 👍 / 👎.
Resolves three defects surfaced by a
lacunadogfooding pass, each confirmed against the shipped 1.0.0 surface (investigation + adversarial verification) and fixed test-first via/systematic-debugging.posture_get/policy_listcrash on an unprovisioned (empty/no-table) posture DB → non-recoverableINTERNAL_ERRORleaking SQL, fail-closedstructureddegrade never deliveredAuditStorereads treat an absentaudit_logtable as an empty store (same as a missing file)install --posture --insecure-key-in-envmint-drops the operator key (GENESIS stamped with a throwaway;EnvSignerrefusesfingerprint_mismatch; floor read-only)envbackend adopts + validatesLEGIS_OPERATOR_KEYas the epoch key, fails loud if absent/malformedtest_fingerprint= rawast.dumphash, interpreter-fragile across Python minors_stable_ast_reprserializer (emits every field of every node), pinned by a cross-interpreter 3.12↔3.13 testBug 3 changes the fingerprint value for all sources. Consumers with pinned
@policy_boundarytest_fingerprints (e.g. lacuna's specimen) will reportPOLICY_BOUNDARY_TEST_FINGERPRINT_MISMATCHuntil they regenerate once against 1.1.0. After that they no longer drift across Python versions. → version bumped 1.0.0 → 1.1.0.Verification
mainand pass here)ruff check src✅ ·mypy src/legis✅🤖 Generated with Claude Code