Skip to content

fix: resolve three lacuna-dogfood defects (posture crash, env mint-drop, fingerprint fragility) — v1.1.0#14

Merged
tachyon-beep merged 1 commit into
mainfrom
fix/dogfood-posture-fingerprint-bugs
Jun 19, 2026
Merged

fix: resolve three lacuna-dogfood defects (posture crash, env mint-drop, fingerprint fragility) — v1.1.0#14
tachyon-beep merged 1 commit into
mainfrom
fix/dogfood-posture-fingerprint-bugs

Conversation

@tachyon-beep

Copy link
Copy Markdown
Collaborator

Resolves three defects surfaced by a lacuna dogfooding pass, each confirmed against the shipped 1.0.0 surface (investigation + adversarial verification) and fixed test-first via /systematic-debugging.

# Bug Fix
legis-5fd3b257c3 posture_get/policy_list crash on an unprovisioned (empty/no-table) posture DB → non-recoverable INTERNAL_ERROR leaking SQL, fail-closed structured degrade never delivered AuditStore reads treat an absent audit_log table as an empty store (same as a missing file)
legis-1844bf8ac9 install --posture --insecure-key-in-env mint-drops the operator key (GENESIS stamped with a throwaway; EnvSigner refuses fingerprint_mismatch; floor read-only) env backend adopts + validates LEGIS_OPERATOR_KEY as the epoch key, fails loud if absent/malformed
legis-13b4e97bf4 test_fingerprint = raw ast.dump hash, interpreter-fragile across Python minors version-stable _stable_ast_repr serializer (emits every field of every node), pinned by a cross-interpreter 3.12↔3.13 test

⚠️ Breaking (for pinned fingerprints)

Bug 3 changes the fingerprint value for all sources. Consumers with pinned @policy_boundary test_fingerprints (e.g. lacuna's specimen) will report POLICY_BOUNDARY_TEST_FINGERPRINT_MISMATCH until 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

  • Full suite: 1160 passed, 5 skipped (10 new tests, all of which fail on main and pass here)
  • ruff check src ✅ · mypy src/legis
  • Coverage 92.36% (floor 88%); all per-package floors hold (posture 97.4%)

🤖 Generated with Claude Code

…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>
@tachyon-beep tachyon-beep merged commit 38b1c58 into main Jun 19, 2026
2 checks passed
@tachyon-beep tachyon-beep deleted the fix/dogfood-posture-fingerprint-bugs branch June 19, 2026 10:58

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +328 to +329
if not self._has_log_table(conn):
return []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +119 to +122
fields = ", ".join(
f"{name}={_stable_ast_repr(getattr(node, name, None))}"
for name in node._fields
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

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