test(acl): pin the fail-closed rule for non-condition events on folder items (#838 item 3)
v2-ci / unit-tests (push) Waiting to run

A folder event item under enforcement withholds an event that names no live
condition; flag off, the SDK's own type + SourceNode check applies. Positive
controls: the same event reaches the item flag-off and a condition event
reaches it flag-on. Mutation-checked: IsNotifierEventVisibleTo returning true
for a non-condition event fails the test.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
Joseph Doherty
2026-11-12 08:54:43 -05:00
parent a28adba2fd
commit e5a03e3cce
@@ -167,6 +167,55 @@ public sealed class FolderEventVerdictInvalidationTests
"MonitoredNode2.InvalidatePermissionCaches() is gone — re-check InvalidateFolderEventVerdicts against the new SDK");
}
/// <summary>
/// Gitea #838 item 3: an event on a folder that names no live condition (none is produced today — model-change and
/// rejected-write audits go to the Server object) is WITHHELD under enforcement, fail closed, and delivered with
/// the flag off (the SDK's own type + SourceNode check only). Positive controls: the same event reaches the same
/// item with enforcement off, and a condition event reaches it with enforcement on.
/// </summary>
[Fact]
public async Task A_non_condition_event_on_a_folder_is_withheld_under_enforcement_and_delivered_without_it()
{
var ct = TestContext.Current.CancellationToken;
await using var rig = await ServerEventRig.StartAsync("Folder838NonCondition", encrypted: false, ct);
rig.KeyRoles = [];
rig.MaterialiseNative();
rig.Enforce(false, [], ServerEventRig.EquipmentGrant(EqA, NodePermissions.ReadOnly));
using var session = await rig.ConnectAsync(anonymous: true, ct);
var events = new RecordedEvents();
var subscription = await RecordedEvents.SubscribeAsync(session, events, ct);
var onA = RecordedEvents.AddEventItem(subscription, ServerEventRig.UnsNodeId(session, EqA));
await subscription.ApplyChangesAsync(ct);
onA.Status.Error.ShouldBeNull();
var folder = rig.Nm.FindPredefinedNode<FolderState>(ServerEventRig.UnsNodeId(session, EqA)).ShouldNotBeNull();
void ReportPlainEvent(string message)
{
var e = new BaseEventState(null);
lock (rig.Nm.Lock)
{
e.Initialize(rig.Nm.SystemContext, folder, EventSeverity.Low, new LocalizedText(message));
folder.ReportEvent(rig.Nm.SystemContext, e);
}
}
ReportPlainEvent("plain-off");
await RecordedEvents.WaitUntilAsync(() => events.Has(onA, "plain-off"));
rig.Enforce(true, [], ServerEventRig.EquipmentGrant(EqA, NodePermissions.ReadOnly));
rig.Project(native: true, State("cond-on"));
await RecordedEvents.WaitUntilAsync(() => events.Has(onA, "cond-on"));
ReportPlainEvent("plain-on");
rig.Project(native: true, State("cond-on-2"));
await RecordedEvents.WaitUntilAsync(() => events.Has(onA, "cond-on-2"));
await Task.Delay(TimeSpan.FromMilliseconds(700), ct);
events.Has(onA, "plain-on").ShouldBeFalse("a non-condition event on a folder fails closed under enforcement");
await session.CloseAsync(ct);
}
private static AlarmConditionSnapshot State(string message) =>
new(true, false, false, true, AlarmShelvingKind.Unshelved, 700, message);