Problem/Motivation

High-assurance governed actions require durable evidence. Treating logging as best-effort permits a mutation to succeed without the decision/execution record needed to prove or reconcile it.

Proposed resolution

Define an evidence-required assurance class and make the governed action fail closed when the required evidence precommit or durable commit cannot be completed. Consume Audit Chain through a versioned evidence contract without transferring product ownership.

Acceptance criteria

  • Policy identifies which action classes require durable evidence.
  • Required evidence is keyed, integrity-protected, and correlated to principal, delegation, policy
  • digest, decision, target, and request identifiers.

  • If the required precommit cannot be written, the mutation does not execute.
  • If post-execution receipt persistence is uncertain, the system records and reconciles an explicit
  • uncertain state; it never reports an unproven success.

  • Tests cover evidence-store outage, timeout, retry, duplicate delivery, partial failure, and recovery.
  • No fallback to unkeyed integrity or best-effort logging can satisfy the high-assurance class.

API changes

Governed action results gain explicit evidence-required failure and uncertain-execution states.

Comments

jmcerda created an issue. See original summary.

jmcerda’s picture

Public working mirror: GitHub #110.

Drupal.org remains the authority for this work item; implementation discussion and pull-request linkage may occur in the mirror.

jmcerda’s picture

Status: Active » Needs review

Implemented; working PR on the public mirror: https://github.com/Wilkes-Liberty/mcp_sentinel/pull/132 (TDD; every acceptance criterion has a both-directions kernel test).

What it does:

- Policy profiles gain evidence_required_actions (opt-in, empty by default; entity_write, entity_delete; update 10020 backfills empty on existing profiles so an upgrade changes no behavior and exported configuration round-trips without drift).

- Veto before mutation: a new evidence guard refuses an evidence-required governed action — with a rollback-surviving evidence_veto row and stable reason codes evidence_chain_missing / evidence_audit_disabled / evidence_unkeyed — whenever the keyed chain cannot take the evidence. No fallback to unkeyed integrity or best-effort logging satisfies the class. The signing key is re-checked after the append, so signing that degrades mid-request aborts the transaction; the residual race and its complete fix (a strict keyed-append + signing-status API on audit_chain) are filed upstream.

- Atomic co-commit: the evidence_precommit row (correlation id, principal, validated OAuth consumer client id, caller X-Request-Id, policy digest, decision, target) is appended inside the mutation's own transaction — both become durable together or neither does. Proven in both directions: an evidence-store outage or append timeout aborts the save; a save that fails after its precommit takes the precommit down with it, leaving no orphaned evidence. This is a deliberate, documented reading of "durable precommit": there is no reachable state where the mutation persists without its evidence, which is the invariant the criterion exists for.

- Receipts and explicit uncertainty: the post-save entity_save / entity_delete row completes the correlation (via logAlways, so a mid-flight audit_enabled flip cannot silently drop a receipt). A receipt that fails once the mutation is already durable is recorded once per correlation id in a reconciliation ledger, refused to the caller as evidence_uncertain — never reported as a proven success — retried on cron, appended marked reconciled on recovery, and surfaced as a status-report error that stays visible even with governance disabled.

Scope, stated rather than implied: the gate covers governed content-entity mutations (create/update/delete; bulk and workflow tools flow through entity save and are covered transitively). The config-write plane has its own governance seam and gets its evidence-required leg as a separate slice. Failure states surface as stable reason codes in the refusal message on every channel; a structured code field on Tool results is a candidate follow-up rather than part of this change.

Gates: 447 kernel / 92 unit / 57 submodule tests green, module PHPCS exit 0, PHPStan clean. Moving to Needs review.

  • jmcerda committed 0a5542f4 on 1.x
    Issue #3616539: veto evidence-required governed actions when evidence...
jmcerda’s picture

Status: Needs review » Fixed

Merged to 1.x (merge f740375, working PR #132 on the public mirror). The review round adopted two automated-review findings before merge: refusal rows for the audit-disabled veto now write through the always path (a refusal suppressed by the very flag it reports on would be best-effort logging by another name), and the uncertain-receipt ledger moved to per-correlation key-value entries so concurrent failures cannot overwrite each other. Final coverage: 11 kernel tests, both-directions proofs; full matrix green including the Drupal 10.6 leg.

Ships in the next release; the upstream contract improvement that removes the guard's remaining check-then-append window (a strict keyed-append / signing-status API on audit_chain) is tracked separately. Marking Fixed.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.