fix(governance): RED-1 — gate governed close on current-code-vs-attach drift (filigree-owned)#71
Conversation
…(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>
There was a problem hiding this comment.
💡 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".
| else: | ||
| unresolved.append(loc) | ||
| for sei in seis: | ||
| current = self._resolve_sei_content_hash(sei) |
There was a problem hiding this comment.
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 👍 / 👎.
| content_hash = record.get("content_hash") | ||
| if isinstance(content_hash, str) and content_hash: | ||
| out[locator] = content_hash |
There was a problem hiding this comment.
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 👍 / 👎.
Closes the RED-1 closure-gate drift hole (hub ticket
weft-0678843f13): a governed issue could closeallowed:truewhile 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_staleonly 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 pytestexit 0 (+17 tests); mypy/ruff clean;wardline scan0 findings.Release mode: additive / consumer-side — ships solo (no Loomweave change required today).
Federation blast-radius (filed):
weft-dbd0584a4d(Loomweave: blesscontent_hashonresolve_seiin the oracle — the SEI-form path works today but rests on an un-blessed field);weft-aee5769607(cascadeloomweave_known_downbound + live e2e seam test).🤖 Generated with Claude Code