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."
96 lines
3.7 KiB
Go
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
|
|
}
|