Files
cgalo5758 12f1d3fc00 Fix the four Slice 3 walk findings and page every operator list
Archives openspec change slice3-walk-fixes and syncs its five delta
specs (fedwiki-sites, entitlements, operator-panel-navigation,
operator-list-scale, ui-quality-gate).

- FedWiki site usage is read from active site rows in both quota
  readers; the reservation counter converges on the rows: raise-only
  after farm sync and inside the create quota check, exact at boot.
  The understated production counters repair on the first boot.
- The People tile caption excludes the reserved system person through
  the same query parameter the directory uses.
- The operator Domains live-claims list is a governed list: pages of
  50, true total, search over root name and organization, a
  pending/active facet.
- New lint rule table-without-list-controls refuses an unpaged
  page-body table unless it carries a list-scale exempt marker with a
  reason; six curated or detail tables carry one. Its first run caught
  the operator FedWiki sites list, which is now governed the same way.
- Entitlement-set rule copy: "Per unit", "Multiplied by the quantity
  purchased or granted."
2026-09-12 01:16:17 -05:00

96 lines
3.7 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
// Package usage converges the `fedwiki_sites` reservation counter on the
// site rows FedWiki owns (design D2 of slice3-walk-fixes).
//
// It sits below both callers -- the web handlers and the Temporal activities,
// which cannot import each other -- and below the integration root, which runs
// the boot repair.
package usage
import (
"context"
"database/sql"
"errors"
"git.coopcloud.tech/wiki-cafe/member-console/internal/entitlements"
fwmod "git.coopcloud.tech/wiki-cafe/member-console/internal/integrations/fedwiki/store"
)
// ResourceKey is the entitlements resource key sites are counted under.
const ResourceKey = "fedwiki_sites"
// ActiveSites counts a workspace's active site rows: the display truth for
// every quota surface (design D1).
func ActiveSites(ctx context.Context, siteQ fwmod.Querier, workspaceID string) (int64, error) {
return siteQ.CountActiveSitesByWorkspace(ctx, workspaceID)
}
// ReconcileWorkspace raises the workspace's counter to its active site row
// count, never lowering it: between a create workflow's gated increment and
// its row insert the counter legitimately exceeds the rows, and lowering there
// would erase that reservation. Real deletions are lowered by the decrement
// and status-drift paths, and by the exact set at boot.
//
// Returns the observed active row count. A workspace with no primary pool
// assignment has no counter to converge on and is not an error.
func ReconcileWorkspace(ctx context.Context, entQ entitlements.Querier, siteQ fwmod.Querier, workspaceID string) (int64, error) {
observed, poolID, err := observe(ctx, entQ, siteQ, workspaceID)
if err != nil || poolID == "" {
return observed, err
}
_, err = entQ.RaiseNumericUsageTo(ctx, entitlements.RaiseNumericUsageToParams{
PoolID: poolID,
ResourceKey: ResourceKey,
Observed: observed,
})
return observed, err
}
// SetWorkspaceExact sets the workspace's counter to its active site row count,
// for boot reconciliation only: no create workflow holds an unmaterialized
// reservation then, so the rows are the whole truth and an overstated counter
// (which refuses creates) comes back down.
//
// Reports whether a counter row was updated; a workspace whose pool has no
// usage row for the key has nothing to repair (the materializer creates the
// row when the entitlement is materialized).
func SetWorkspaceExact(ctx context.Context, entQ entitlements.Querier, siteQ fwmod.Querier, workspaceID string) (observed int64, updated bool, err error) {
observed, poolID, err := observe(ctx, entQ, siteQ, workspaceID)
if err != nil || poolID == "" {
return observed, false, err
}
res, err := entQ.SetNumericUsage(ctx, entitlements.SetNumericUsageParams{
PoolID: poolID,
ResourceKey: ResourceKey,
Observed: observed,
})
if err != nil {
return observed, false, err
}
rows, err := res.RowsAffected()
if err != nil {
return observed, false, err
}
return observed, rows > 0, nil
}
// observe resolves the workspace's primary pool (the resolution every quota
// read uses) and counts its active site rows. An absent assignment yields an
// empty pool id rather than an error: nothing to reconcile.
func observe(ctx context.Context, entQ entitlements.Querier, siteQ fwmod.Querier, workspaceID string) (int64, string, error) {
assignment, err := entQ.GetPrimaryPoolAssignmentByWorkspace(ctx, workspaceID)
if errors.Is(err, sql.ErrNoRows) {
return 0, "", nil
}
if err != nil {
return 0, "", err
}
observed, err := siteQ.CountActiveSitesByWorkspace(ctx, workspaceID)
if err != nil {
return 0, "", err
}
return observed, assignment.PoolID, nil
}