Camera permission and review fixes (openpanel C3, C4)

C4: a device's `viewers` (superusers by default, or members) covers the
image and video messages its ports publish. Anyone else gets none of their
values on the socket, in the catalogue or a flow's state, 403 for their
history, for the frames they hold (the last 8 included) and for TURN
credentials, no bytes when asking for them, and `bye: forbidden` for a live
view. A device credential stays bounded by its dashboards. The device page
sets it in its Camera section, and a tile the viewer may not see says
"No access to this camera" and asks the camera for nothing.

C3:
- A volatile upload is held to MAX_FRAME_BYTES (4 MiB, 413 above it, read
  without extra copies), and so is a frame on a device's socket.
- A frame the engine holds in memory is written to the ring directory
  before a local worker is handed it, so load_artifact finds it.
- A socket the bus dropped events for catches up with a snapshot, demand
  included, and a device's credential is checked again per frame, cached by
  the flow store revision and its nonce, so a lost event never keeps an
  unpaired device's frames coming.
- A report is at most 64 KB once its named config_schemas are put in place.
- A device away for 60 s has its true bool messages set to false.
- Refused frames are logged by fixed keys, a frame is named after its
  message, `fluksio serve` waits 5 s for open connections, a tile with a
  stream URL creates no demand, the 8-name cap keeps the newest names.
- Every container's log rotates (json-file, 3 x 10 MB).
This commit is contained in:
Melvin Strobl
2026-10-17 09:20:04 +02:00
parent 6a98dcd23f
commit 72c1d73e96
36 changed files with 1268 additions and 152 deletions
+40
View File
@@ -3,6 +3,8 @@
import asyncio
from pathlib import Path
import pytest
from fluksio.flow.alerts import Alert, AlertManager, AlertsConfig, Channel, Rule
from fluksio.flow.controller import HEALTH_FLOOR, FlowController
from fluksio.flow.devices import presence
@@ -165,3 +167,41 @@ def test_a_device_away_after_a_restart_alerts_nobody(tmp_path: Path) -> None:
]
assert sent == []
presence.forget("wall")
def test_a_device_gone_for_the_grace_reads_false(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A flag such as `person` stops saying someone is there once the device
has been away for the grace, and a device back in time keeps it."""
from fluksio.flow.nodes import device as device_node
monkeypatch.setattr(device_node, "OFFLINE_GRACE_S", 0.1)
controller = _controller(tmp_path)
def bell() -> object:
return controller.values("hall")["hall.bell"]["value"]
async def scenario() -> list[object]:
await controller.reload()
presence.opened("wall")
controller.publish_message("hall.bell", True)
seen = [bell()]
# Back within the grace: nothing changes.
presence.closed("wall")
presence.opened("wall")
await asyncio.sleep(0.25)
seen.append(bell())
# Away for good.
presence.closed("wall")
await asyncio.sleep(0.05)
seen.append(bell())
await asyncio.sleep(0.25)
seen.append(bell())
# What reads it hears so too.
seen.append(controller.values("hall")["hall.rung"]["value"])
await controller._teardown()
presence.forget("wall")
return seen
assert asyncio.run(scenario()) == [True, True, True, False, False]