feat(store): add Area admission and audit outbox [T-010]
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled

This commit is contained in:
QiuSW
2026-08-07 18:23:24 +08:00
parent 8abff0a566
commit 36786723e3
23 changed files with 1346 additions and 99 deletions
+332 -4
View File
@@ -48,6 +48,7 @@ func TestPostgresDefaultAndMaximumQuota(t *testing.T) {
}
radar := device.Device{
ID: "radar-default", TenantID: "tenant-default", SiteID: "site-default",
AreaID: "area-default",
SerialNumber: "radar-default", Name: "Radar", Modality: device.ModalityRadar,
Capabilities: []device.Capability{device.CapabilityTelemetry},
DesiredState: device.DesiredEnabled, ActualState: device.ActualPending,
@@ -113,8 +114,8 @@ func TestPostgresProjectionFailureAndRollbackFailClosed(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
err := store.CreateDevice(ctx, videoDevice(1, "missing-tenant", "missing-site"))
if !errors.Is(err, ErrQuotaProjectionUnavailable) {
t.Fatalf("missing projection must fail closed, got %v", err)
if !errors.Is(err, ErrAreaPolicyUnavailable) {
t.Fatalf("missing Area projection must fail closed first, got %v", err)
}
insertBellSite(t, admin, "tenant", "site", 2)
@@ -285,12 +286,12 @@ func TestPostgresOpenRejectsOverprivilegedRuntimeRole(t *testing.T) {
_, admin := openPostgresTestStore(t)
ctx := context.Background()
if _, err := admin.ExecContext(ctx,
`GRANT UPDATE ON bell.site_quota_v1 TO yovision_t009_sense`); err != nil {
`GRANT UPDATE ON bell.site_quota_v1 TO yovision_t010_sense`); err != nil {
t.Fatal(err)
}
defer func() {
_, _ = admin.ExecContext(context.Background(),
`REVOKE UPDATE ON bell.site_quota_v1 FROM yovision_t009_sense`)
`REVOKE UPDATE ON bell.site_quota_v1 FROM yovision_t010_sense`)
}()
value, err := OpenPostgres(ctx, os.Getenv(postgresTestDSNEnv))
if value != nil {
@@ -302,6 +303,316 @@ func TestPostgresOpenRejectsOverprivilegedRuntimeRole(t *testing.T) {
}
}
func TestPostgresAreaPolicyAllowsNonImagingAndDeniesImagingCreate(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
insertBellSite(t, admin, "tenant", "site", 2)
if _, err := admin.ExecContext(ctx, `UPDATE bell.areas SET capture_policy = 'non_imaging_only'
WHERE tenant_id = 'tenant' AND id = 'area-default'`); err != nil {
t.Fatal(err)
}
blocked := videoDevice(1, "tenant", "site")
blocked.DesiredState = device.DesiredDisabled
err := store.CreateDevice(ctx, blocked)
if !errors.Is(err, ErrAreaPolicyDenied) {
t.Fatalf("disabled imaging create must still be denied, got %v", err)
}
radar := device.Device{
ID: "radar-001", TenantID: "tenant", SiteID: "site", AreaID: "area-default",
SerialNumber: "radar-001", Name: "Radar", Modality: device.ModalityRadar,
Capabilities: []device.Capability{device.CapabilityTelemetry},
DesiredState: device.DesiredEnabled, ActualState: device.ActualPending,
}
if err := store.CreateDevice(ctx, radar); err != nil {
t.Fatalf("non-imaging device must be allowed: %v", err)
}
stored, err := store.GetDevice(ctx, radar.ID)
if err != nil {
t.Fatal(err)
}
if stored.AreaID != "area-default" || stored.AreaPolicySourceVersion != 2 {
t.Fatalf("Area projection evidence was not stored: %+v", stored)
}
var devices, audits int
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.devices`).Scan(&devices); err != nil {
t.Fatal(err)
}
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.device_operation_outbox`).Scan(&audits); err != nil {
t.Fatal(err)
}
if devices != 1 || audits != 1 {
t.Fatalf("denied create left partial state: devices=%d audits=%d", devices, audits)
}
}
func TestPostgresAreaProjectionMissingAndRollbackFailClosed(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
insertBellSite(t, admin, "tenant", "site", 2)
missing := videoDevice(1, "tenant", "site")
missing.AreaID = "missing-area"
if err := store.CreateDevice(ctx, missing); !errors.Is(err, ErrAreaPolicyUnavailable) {
t.Fatalf("missing Area projection must fail closed, got %v", err)
}
if _, err := store.db.ExecContext(ctx, `INSERT INTO sense.area_policy_projection_state(
tenant_id, site_id, area_id, source_version, synced_at
) VALUES ('tenant', 'site', 'area-default', 99, clock_timestamp())`); err != nil {
t.Fatal(err)
}
if err := store.CreateDevice(ctx, videoDevice(2, "tenant", "site")); !errors.Is(err, ErrAreaPolicyInvalid) {
t.Fatalf("Area source-version rollback must fail closed, got %v", err)
}
var devices, audits int
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.devices`).Scan(&devices); err != nil {
t.Fatal(err)
}
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.device_operation_outbox`).Scan(&audits); err != nil {
t.Fatal(err)
}
if devices != 0 || audits != 0 {
t.Fatalf("failed Area admissions persisted state: devices=%d audits=%d", devices, audits)
}
}
func TestPostgresConcurrentAreaObservationIsMonotonic(t *testing.T) {
store, admin := openPostgresTestStore(t)
insertBellSite(t, admin, "tenant", "site", 2)
ctx := context.Background()
start := make(chan struct{})
errorsFound := make(chan error, 2)
var wait sync.WaitGroup
for index := 1; index <= 2; index++ {
wait.Add(1)
go func(index int) {
defer wait.Done()
value := videoDevice(index, "tenant", "site")
value.DesiredState = device.DesiredDisabled
<-start
errorsFound <- store.CreateDevice(ctx, value)
}(index)
}
close(start)
wait.Wait()
close(errorsFound)
for err := range errorsFound {
if err != nil {
t.Fatalf("concurrent Area observation failed: %v", err)
}
}
var sourceVersion int64
if err := admin.QueryRowContext(ctx, `SELECT source_version
FROM sense.area_policy_projection_state
WHERE tenant_id = 'tenant' AND site_id = 'site' AND area_id = 'area-default'`).
Scan(&sourceVersion); err != nil {
t.Fatal(err)
}
if sourceVersion != 1 {
t.Fatalf("concurrent observation recorded version %d", sourceVersion)
}
}
func TestPostgresAreaProjectionCannotCrossTenantBoundary(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
insertBellSite(t, admin, "tenant-a", "site", 2)
insertBellSite(t, admin, "tenant-b", "site", 2)
insertBellArea(t, admin, "tenant-b", "site", "private-area", "video_allowed")
value := videoDevice(1, "tenant-a", "site")
value.AreaID = "private-area"
if err := store.CreateDevice(ctx, value); !errors.Is(err, ErrAreaPolicyUnavailable) {
t.Fatalf("cross-tenant Area was not hidden as unavailable: %v", err)
}
}
func TestPostgresEnableRechecksAreaWithoutStoppingExistingDevice(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
insertBellSite(t, admin, "tenant", "site", 2)
disabled := videoDevice(1, "tenant", "site")
disabled.DesiredState = device.DesiredDisabled
if err := store.CreateDevice(ctx, disabled); err != nil {
t.Fatal(err)
}
if err := store.CreateDevice(ctx, videoDevice(2, "tenant", "site")); err != nil {
t.Fatal(err)
}
if _, err := admin.ExecContext(ctx, `UPDATE bell.areas SET capture_policy = 'non_imaging_only'
WHERE tenant_id = 'tenant' AND id = 'area-default'`); err != nil {
t.Fatal(err)
}
if err := store.SetDesiredState(ctx, disabled.ID, device.DesiredEnabled); !errors.Is(err, ErrAreaPolicyDenied) {
t.Fatalf("enable under non-imaging policy must be denied, got %v", err)
}
stillDisabled, err := store.GetDevice(ctx, disabled.ID)
if err != nil {
t.Fatal(err)
}
stillEnabled, err := store.GetDevice(ctx, "camera-002")
if err != nil {
t.Fatal(err)
}
if stillDisabled.DesiredState != device.DesiredDisabled ||
stillEnabled.DesiredState != device.DesiredEnabled {
t.Fatalf("policy change altered existing state: disabled=%s enabled=%s",
stillDisabled.DesiredState, stillEnabled.DesiredState)
}
}
func TestPostgresAuditContextRedactionAndNoopDesiredState(t *testing.T) {
store, admin := openPostgresTestStore(t)
insertBellSite(t, admin, "tenant", "site", 2)
ctx := WithAuditContext(context.Background(), AuditContext{
ActorType: AuditActorUser, ActorID: "operator-7", Reason: "approved change", TraceID: "trace-7",
})
value := videoDevice(1, "tenant", "site")
if err := store.CreateDevice(ctx, value); err != nil {
t.Fatal(err)
}
if err := store.SetDesiredState(ctx, value.ID, device.DesiredEnabled); err != nil {
t.Fatal(err)
}
stored, err := store.GetDevice(ctx, value.ID)
if err != nil {
t.Fatal(err)
}
if stored.Generation != 1 {
t.Fatalf("no-op desired-state request changed generation to %d", stored.Generation)
}
rows, err := admin.QueryContext(ctx, `SELECT event_id, event_type, actor_type, actor_id,
COALESCE(reason, ''), COALESCE(trace_id, ''), payload::text
FROM sense.device_operation_outbox ORDER BY occurred_at, event_id`)
if err != nil {
t.Fatal(err)
}
defer rows.Close()
var count int
for rows.Next() {
var eventID, eventType, actorType, actorID, reason, traceID, payload string
if err := rows.Scan(&eventID, &eventType, &actorType, &actorID, &reason, &traceID, &payload); err != nil {
t.Fatal(err)
}
count++
if !strings.HasPrefix(eventID, "audit_") || len(eventID) != 38 {
t.Fatalf("invalid audit event ID %q", eventID)
}
if actorType != "user" || actorID != "operator-7" || reason != "approved change" || traceID != "trace-7" {
t.Fatalf("audit principal/context drift: %s/%s %s %s", actorType, actorID, reason, traceID)
}
for _, secret := range []string{value.EndpointRef, value.CredentialRef, value.PathName} {
if strings.Contains(payload, secret) {
t.Fatalf("audit payload leaked sensitive runtime data for %s", eventType)
}
}
}
if err := rows.Err(); err != nil {
t.Fatal(err)
}
if count != 2 {
t.Fatalf("expected create and no-op audit facts, got %d", count)
}
}
func TestPostgresOutboxFailureRollsBackAdmission(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
insertBellSite(t, admin, "tenant", "site", 2)
if _, err := admin.ExecContext(ctx, `CREATE FUNCTION sense.t010_reject_outbox()
RETURNS trigger LANGUAGE plpgsql AS $function$
BEGIN RAISE EXCEPTION 'synthetic outbox failure'; END
$function$;
CREATE TRIGGER t010_reject_outbox BEFORE INSERT ON sense.device_operation_outbox
FOR EACH ROW EXECUTE FUNCTION sense.t010_reject_outbox()`); err != nil {
t.Fatal(err)
}
defer func() {
_, _ = admin.ExecContext(context.Background(),
`DROP TRIGGER IF EXISTS t010_reject_outbox ON sense.device_operation_outbox;
DROP FUNCTION IF EXISTS sense.t010_reject_outbox()`)
}()
if err := store.CreateDevice(ctx, videoDevice(1, "tenant", "site")); err == nil {
t.Fatal("synthetic Outbox failure did not reject device creation")
}
var devices, projections, audits int
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.devices`).Scan(&devices); err != nil {
t.Fatal(err)
}
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.area_policy_projection_state`).Scan(&projections); err != nil {
t.Fatal(err)
}
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.device_operation_outbox`).Scan(&audits); err != nil {
t.Fatal(err)
}
if devices != 0 || projections != 0 || audits != 0 {
t.Fatalf("Outbox failure left partial transaction: devices=%d projections=%d audits=%d",
devices, projections, audits)
}
}
func TestPostgresOutboxFailureRollsBackDesiredState(t *testing.T) {
store, admin := openPostgresTestStore(t)
ctx := context.Background()
insertBellSite(t, admin, "tenant", "site", 2)
value := videoDevice(1, "tenant", "site")
value.DesiredState = device.DesiredDisabled
if err := store.CreateDevice(ctx, value); err != nil {
t.Fatal(err)
}
if _, err := admin.ExecContext(ctx, `CREATE FUNCTION sense.t010_reject_state_audit()
RETURNS trigger LANGUAGE plpgsql AS $function$
BEGIN RAISE EXCEPTION 'synthetic state-audit failure'; END
$function$;
CREATE TRIGGER t010_reject_state_audit BEFORE INSERT ON sense.device_operation_outbox
FOR EACH ROW EXECUTE FUNCTION sense.t010_reject_state_audit()`); err != nil {
t.Fatal(err)
}
defer func() {
_, _ = admin.ExecContext(context.Background(),
`DROP TRIGGER IF EXISTS t010_reject_state_audit ON sense.device_operation_outbox;
DROP FUNCTION IF EXISTS sense.t010_reject_state_audit()`)
}()
if err := store.SetDesiredState(ctx, value.ID, device.DesiredEnabled); err == nil {
t.Fatal("synthetic Outbox failure did not reject desired-state change")
}
stored, err := store.GetDevice(ctx, value.ID)
if err != nil {
t.Fatal(err)
}
if stored.DesiredState != device.DesiredDisabled || stored.Generation != 1 {
t.Fatalf("Outbox failure committed desired state: %+v", stored)
}
var audits int
if err := admin.QueryRowContext(ctx, `SELECT count(*) FROM sense.device_operation_outbox`).Scan(&audits); err != nil {
t.Fatal(err)
}
if audits != 1 {
t.Fatalf("failed desired-state transaction changed Outbox count to %d", audits)
}
}
func TestPostgresOpenRejectsAreaSourcePrivilege(t *testing.T) {
_, admin := openPostgresTestStore(t)
ctx := context.Background()
if _, err := admin.ExecContext(ctx,
`GRANT SELECT ON bell.areas TO yovision_t010_sense`); err != nil {
t.Fatal(err)
}
defer func() {
_, _ = admin.ExecContext(context.Background(),
`REVOKE SELECT ON bell.areas FROM yovision_t010_sense`)
}()
value, err := OpenPostgres(ctx, os.Getenv(postgresTestDSNEnv))
if value != nil {
_ = value.Close()
t.Fatal("Area-source privilege was accepted")
}
if err == nil || !strings.Contains(err.Error(), "privilege boundary") {
t.Fatalf("expected Area privilege-boundary error, got %v", err)
}
}
func openPostgresTestStore(t *testing.T) (*Postgres, *sql.DB) {
t.Helper()
dsn := os.Getenv(postgresTestDSNEnv)
@@ -318,10 +629,13 @@ func openPostgresTestStore(t *testing.T) (*Postgres, *sql.DB) {
t.Fatal("connect PostgreSQL test administrator")
}
if _, err := admin.ExecContext(context.Background(), `TRUNCATE
sense.device_operation_outbox,
sense.device_capabilities,
sense.reconcile_state,
sense.devices,
sense.site_quota_projection_state,
sense.area_policy_projection_state,
bell.areas,
bell.sites CASCADE`); err != nil {
admin.Close()
t.Fatal(err)
@@ -350,4 +664,18 @@ func insertBellSite(t *testing.T, admin *sql.DB, tenantID, siteID string, quota
if _, err := admin.ExecContext(context.Background(), query, arguments...); err != nil {
t.Fatal(err)
}
insertBellArea(t, admin, tenantID, siteID, "area-default", "video_allowed")
}
func insertBellArea(
t *testing.T,
admin *sql.DB,
tenantID, siteID, areaID, capturePolicy string,
) {
t.Helper()
if _, err := admin.ExecContext(context.Background(), `INSERT INTO bell.areas(
tenant_id, site_id, id, name, capture_policy
) VALUES ($1, $2, $3, $4, $5)`, tenantID, siteID, areaID, "Test Area", capturePolicy); err != nil {
t.Fatal(err)
}
}