Skip to content

fix(governance): RED-1 — gate governed close on current-code-vs-attach drift (filigree-owned)#71

Merged
tachyon-beep merged 1 commit into
mainfrom
fix/red1-closure-gate-drift
Jun 29, 2026
Merged

fix(governance): RED-1 — gate governed close on current-code-vs-attach drift (filigree-owned)#71
tachyon-beep merged 1 commit into
mainfrom
fix/red1-closure-gate-drift

Conversation

@tachyon-beep

Copy link
Copy Markdown
Collaborator

Closes the RED-1 closure-gate drift hole (hub ticket weft-0678843f13): a governed issue could close allowed:true while the bound entity's CURRENT code had drifted from the content at attach — nobody compared current-code-vs-attach.

Hub seam ruling (2026-06-29): Filigree owns the drift comparison (ADR-029 Decision 3); the Legis gate stays binding+signature.

What it does: at governed close, for each governed (signed) binding, resolve the entity's CURRENT content_hash from Loomweave and compare to content_hash_at_attach → drift → GateOutcome.STALE (reuses the existing outcome, no new wiring). Fixes the gap where _signed_row_is_stale only caught a re-attach, never the bound code drifting.

Enrich-only degrade (verified): Loomweave unreachable/unsupported/no-resolver/orphaned → discriminated UNKNOWN (logged, never silently fresh); the core close is NOT hard-blocked. Ungoverned closes short-circuit before any resolution.

Tests: full uv run pytest exit 0 (+17 tests); mypy/ruff clean; wardline scan 0 findings.

Release mode: additive / consumer-side — ships solo (no Loomweave change required today).

Federation blast-radius (filed): weft-dbd0584a4d (Loomweave: bless content_hash on resolve_sei in the oracle — the SEI-form path works today but rests on an un-blessed field); weft-aee5769607 (cascade loomweave_known_down bound + live e2e seam test).

🤖 Generated with Claude Code

…(RED-1)

Hub ticket: weft-0678843f13. Hub ruling 2026-06-29: Filigree OWNS the
current-code-vs-attach drift comparison (aligns ADR-029 Decision 3 — drift
detection is the consumer's job); Legis's closure-gate stays binding+signature.

The bug: a *governed* issue closed `allowed:true` even when the bound entity's
CURRENT content had drifted from the content at attach. The existing STALE check
(`_signed_row_is_stale`) only catches a re-attach that advanced
content_hash_at_attach past the signed snapshot; it could not catch the bound
CODE drifting while nobody re-attaches — then content_hash_at_attach stays frozen
at (and equal to) signed_content_hash, the snapshot check is False, and the close
was waved through. Nobody compared current-code vs attach.

The fix, at governed close (governance.evaluate_closure_gate), for each governed
(signed) binding:
- Resolve the bound entity's CURRENT content_hash via the Loomweave registry
  consumer (new LoomweaveRegistry.resolve_entity_content_hashes). Form-dispatched
  on the frozen SEI prefix (the one sanctioned grammar peek, mirroring
  sei_backfill): SEIs -> GET /api/v1/identity/sei/{sei}; locators -> POST
  /api/v1/identity/resolve:batch. Both Loomweave endpoints carry content_hash for
  an ALIVE entity (verified against loomweave http_read/identity.rs + the blessed
  weft-sei-conformance-oracle).
- Compare to content_hash_at_attach. Drift -> fail closed as GateOutcome.STALE
  (reuses the existing outcome, so every close surface renders it CONFLICT+reason
  with no new wiring). Ordered before the legis_known_down short-circuit, same
  load-bearing ordering as the snapshot-STALE check.

Enrich-only degrade (REQUIRED): when Loomweave is unreachable / unsupported / the
registry can't resolve (no resolver, availability error, orphaned/not_found/
invalid entity), the binding's freshness is a discriminated UNKNOWN — logged,
never silently treated as fresh, and NOT a hard block. The core close must not
become load-bearing on Loomweave.

Scope: only GOVERNED closes are affected; ungoverned closes short-circuit before
any drift resolution (registry never consulted). Local-mode / registry-less dbs
degrade to UNKNOWN (no-op), so existing behavior is unchanged.

Bonus (same machinery): list_entity_associations now accepts an optional
current_content_hashes map so a caller with resolved hashes can populate
freshness_status (fresh/stale) instead of the hardwired "unknown"; default
preserves the prior behavior. The data layer still performs no network resolve.

Also: _LoomweaveLocalFallbackRegistry delegates the new resolver to its primary
(else fallback-enabled loomweave mode would false-green to UNKNOWN while Loomweave
is up); db_base.py DBMixinProtocol stub updated to the new signature.

Tests: gate-level drift/match/unreachable/unresolved/ungoverned/mixed/no-registry
(tests/core/test_governance_gate.py); wire-level resolver against a stub mirroring
Loomweave's real identity-resolve response shapes for BOTH locator and SEI forms
(tests/unit/test_entity_content_hash_resolve.py); forward-list freshness bonus
(tests/core/test_entity_associations.py). Full suite green; mypy/ruff/wardline clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

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

ℹ️ 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 thread src/filigree/registry.py
else:
unresolved.append(loc)
for sei in seis:
current = self._resolve_sei_content_hash(sei)

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 Preserve known drift when later SEI lookups fail

When an issue has multiple governed bindings, this per-SEI request can raise after earlier locator or SEI hashes have already been resolved into resolved; evaluate_closure_gate catches that RegistryUnavailableError and treats the entire drift check as UNKNOWN, so a binding whose current hash already differs from content_hash_at_attach can still proceed to the Legis gate merely because a later SEI lookup timed out or returned 5xx. This contradicts the mixed “drift wins over unknown” behavior and can let a stale governed close through; return partial per-entity failures as unresolved or evaluate/block on known drift before discarding partial results.

Useful? React with 👍 / 👎.

Comment thread src/filigree/registry.py
Comment on lines +1161 to +1163
content_hash = record.get("content_hash")
if isinstance(content_hash, str) and content_hash:
out[locator] = content_hash

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 Validate returned content hashes before comparing drift

When Loomweave returns a malformed but non-empty content_hash for a resolved locator, this accepts it as a real current hash and the closure gate may block as STALE simply because the malformed token differs from content_hash_at_attach. The registry already validates content hashes elsewhere with make_content_hash, and this new gate is documented as enrich-only where malformed identity responses degrade to UNKNOWN rather than hard-blocking, so malformed tokens (for locators and the analogous SEI path) should be rejected/degraded instead of compared directly.

Useful? React with 👍 / 👎.

@tachyon-beep tachyon-beep merged commit 9b50143 into main Jun 29, 2026
8 checks passed
@tachyon-beep tachyon-beep deleted the fix/red1-closure-gate-drift branch June 29, 2026 09:25
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