mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
The AI action broker treated an unreadable operator lock as unlocked: isResourceRemediationLocked returned (false, nil) with no audit store wired, and the caller logged store errors then dispatched anyway. An operator's NeverAutoRemediate=true could be silently ignored whenever the policy store was missing or erroring, which is unacceptable while Patrol and Assistant run at assisted or full autonomy. Posture change at the dispatch decision point: - isResourceRemediationLocked now reports unknown state (nil store or lookup failure) as an ErrRemediationLockStateUnknown-wrapped error instead of silently defaulting to unlocked. - New checkRemediationLockForDispatch gate: dispatches without an approved human decision fail CLOSED on unknown lock state and surface "remediation lock state unknown; operator approval required". Human-approved dispatches keep the historical fail-open behavior with a warning log. A confirmed lock still refuses even approved dispatches, as before. - executeNativeActionWithAudit (TrueNAS app start/stop/restart) now enforces the lock too; it previously skipped the check entirely. - Refusals persist Failed audit records with stable remediation_lock_state_unknown: / resource_remediation_locked: ErrorMessage prefixes. - ai-runtime subsystem contract updated to pin the new posture. Tests cover store-error and nil-store at both autonomy postures on both dispatch paths; routing/control tests now wire an in-memory audit store since autonomous dispatch without one is refused.
296 lines
12 KiB
Go
296 lines
12 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"net/http"
|
|
"time"
|
|
|
|
pkglicensing "github.com/rcourtman/pulse-go-rewrite/pkg/licensing"
|
|
)
|
|
|
|
// Sentinel errors for user-friendly activation error mapping.
|
|
var (
|
|
errMalformedLicenseSentinel = pkglicensing.ErrMalformedLicense
|
|
errInvalidLicenseSentinel = pkglicensing.ErrInvalidLicense
|
|
errSignatureInvalidSentinel = pkglicensing.ErrSignatureInvalid
|
|
errExpiredLicenseSentinel = pkglicensing.ErrExpiredLicense
|
|
errNoPublicKeySentinel = pkglicensing.ErrNoPublicKey
|
|
)
|
|
|
|
type licenseServerErrorModel = pkglicensing.LicenseServerError
|
|
|
|
type licenseService = pkglicensing.Service
|
|
type licenseModel = pkglicensing.License
|
|
type licenseStatus = pkglicensing.LicenseStatus
|
|
type licensePersistence = pkglicensing.Persistence
|
|
type licenseEvaluator = pkglicensing.Evaluator
|
|
type licenseFeatureServiceResolver = pkglicensing.FeatureServiceResolver
|
|
type licenseFeatureChecker = pkglicensing.FeatureChecker
|
|
type licenseFeaturesResponse = pkglicensing.LicenseFeaturesResponse
|
|
type activateLicenseRequestModel = pkglicensing.ActivateLicenseRequest
|
|
type activateLicenseResponseModel = pkglicensing.ActivateLicenseResponse
|
|
type activationStateModel = pkglicensing.ActivationState
|
|
type billingStoreModel = pkglicensing.BillingStore
|
|
type billingState = pkglicensing.BillingState
|
|
type subscriptionState = pkglicensing.SubscriptionState
|
|
type entitlementPayloadModel = pkglicensing.EntitlementPayload
|
|
type commercialPosturePayloadModel = pkglicensing.CommercialPosturePayload
|
|
type runtimeCapabilitiesPayloadModel = pkglicensing.RuntimeCapabilitiesPayload
|
|
type runtimeIdentityModel = pkglicensing.RuntimeIdentity
|
|
type limitStatusModel = pkglicensing.LimitStatus
|
|
type upgradeReasonModel = pkglicensing.UpgradeReason
|
|
type entitlementUsageSnapshotModel = pkglicensing.EntitlementUsageSnapshot
|
|
type legacyConnectionCountsModel = pkglicensing.LegacyConnectionCounts
|
|
type commercialMigrationStatusModel = pkglicensing.CommercialMigrationStatus
|
|
type purchaseReturnClaimsModel = pkglicensing.PurchaseReturnClaims
|
|
type entitlementLeaseClaimsModel = pkglicensing.EntitlementLeaseClaims
|
|
type licenseTier = pkglicensing.Tier
|
|
type checkoutPortalHandoffRequestModel = pkglicensing.CheckoutPortalHandoffRequest
|
|
type checkoutPortalHandoffResponseModel = pkglicensing.CheckoutPortalHandoffResponse
|
|
|
|
const (
|
|
featureMultiTenantKey = pkglicensing.FeatureMultiTenant
|
|
featureAgentProfilesValue = pkglicensing.FeatureAgentProfiles
|
|
featureAIPatrolValue = pkglicensing.FeatureAIPatrol
|
|
featureAIAutoFixValue = pkglicensing.FeatureAIAutoFix
|
|
featureAuditLoggingValue = pkglicensing.FeatureAuditLogging
|
|
featureRBACValue = pkglicensing.FeatureRBAC
|
|
featureAdvancedReportingValue = pkglicensing.FeatureAdvancedReporting
|
|
featureWhiteLabelValue = pkglicensing.FeatureWhiteLabel
|
|
featureLongTermMetricsValue = pkglicensing.FeatureLongTermMetrics
|
|
featureDemoFixturesValue = pkglicensing.FeatureDemoFixtures
|
|
maxUsersLicenseGateKey = pkglicensing.MaxUsersLicenseGateKey
|
|
subscriptionStateActiveValue = pkglicensing.SubStateActive
|
|
subscriptionStateExpiredValue = pkglicensing.SubStateExpired
|
|
subscriptionStateGraceValue = pkglicensing.SubStateGrace
|
|
subscriptionStateCanceledValue = pkglicensing.SubStateCanceled
|
|
subscriptionStateTrialValue = pkglicensing.SubStateTrial
|
|
activationKeyPrefixValue = pkglicensing.ActivationKeyPrefix
|
|
defaultLicenseServerURLValue = pkglicensing.DefaultLicenseServerURL
|
|
)
|
|
|
|
func newLicenseService() *licenseService {
|
|
return pkglicensing.NewService()
|
|
}
|
|
|
|
func hasMultiTenantLicense(service *licenseService) bool {
|
|
return service != nil && service.HasFeature(featureMultiTenantKey)
|
|
}
|
|
|
|
func upgradeURLForFeatureFromLicensing(feature string) string {
|
|
return pkglicensing.UpgradeURLForFeature(feature)
|
|
}
|
|
|
|
func proTrialSignupURLFromLicensing(override string) string {
|
|
return pkglicensing.ResolveProTrialSignupURL(override)
|
|
}
|
|
|
|
func pulseAccountPortalURLFromLicensing(override string) string {
|
|
return pkglicensing.ResolvePulseAccountPortalURL(override)
|
|
}
|
|
|
|
func defaultBillingStateFromLicensing() *billingState {
|
|
return pkglicensing.DefaultBillingState()
|
|
}
|
|
|
|
func normalizeBillingStateFromLicensing(state *billingState) *billingState {
|
|
return pkglicensing.NormalizeBillingState(state)
|
|
}
|
|
|
|
func cloneCommercialMigrationStatusFromLicensing(state *commercialMigrationStatusModel) *commercialMigrationStatusModel {
|
|
return pkglicensing.CloneCommercialMigrationStatus(state)
|
|
}
|
|
|
|
func applyCommercialMigrationFailureTimingFromLicensing(
|
|
status, previous *commercialMigrationStatusModel,
|
|
now time.Time,
|
|
) *commercialMigrationStatusModel {
|
|
return pkglicensing.ApplyCommercialMigrationFailureTiming(status, previous, now)
|
|
}
|
|
|
|
func classifyLegacyExchangeErrorFromLicensing(err error) *commercialMigrationStatusModel {
|
|
return pkglicensing.ClassifyLegacyExchangeError(err)
|
|
}
|
|
|
|
func classifyPersistedLicenseLoadErrorFromLicensing(err error) *commercialMigrationStatusModel {
|
|
return pkglicensing.ClassifyPersistedLicenseLoadError(err)
|
|
}
|
|
|
|
const commercialMigrationStatePendingValue = pkglicensing.CommercialMigrationStatePending
|
|
|
|
func isValidBillingSubscriptionStateFromLicensing(state subscriptionState) bool {
|
|
return pkglicensing.IsValidBillingSubscriptionState(state)
|
|
}
|
|
|
|
func cloudCapabilitiesFromLicensing() []string {
|
|
return pkglicensing.DeriveCapabilitiesFromTier(pkglicensing.TierCloud, nil)
|
|
}
|
|
|
|
func defaultTrialDurationFromLicensing() time.Duration {
|
|
return pkglicensing.DefaultTrialDuration
|
|
}
|
|
|
|
func buildTrialBillingStateWithPlanFromLicensing(now time.Time, capabilities []string, planVersion string, duration time.Duration) *billingState {
|
|
return pkglicensing.BuildTrialBillingStateWithPlan(now, capabilities, planVersion, duration)
|
|
}
|
|
|
|
func newLicensePersistenceFromLicensing(configDir string) (*licensePersistence, error) {
|
|
return pkglicensing.NewPersistence(configDir)
|
|
}
|
|
|
|
func newLicenseServerClientFromLicensing(baseURL string) *pkglicensing.LicenseServerClient {
|
|
return pkglicensing.NewLicenseServerClient(baseURL)
|
|
}
|
|
|
|
func isLicenseValidationDevModeFromLicensing() bool {
|
|
return pkglicensing.IsLicenseValidationDevMode()
|
|
}
|
|
|
|
func newLicenseEvaluatorForBillingStoreFromLicensing(store billingStoreModel, orgID string, cacheTTL time.Duration, expectedInstanceHost string) *licenseEvaluator {
|
|
return pkglicensing.NewEvaluator(
|
|
pkglicensing.NewDatabaseSource(store, orgID, cacheTTL).WithExpectedInstanceHost(expectedInstanceHost),
|
|
)
|
|
}
|
|
|
|
func maxUsersLimitFromLicensing(lic *licenseModel) int {
|
|
return pkglicensing.MaxUsersLimitFromLicense(lic)
|
|
}
|
|
|
|
func exceedsUserLimitFromLicensing(current, additions, limit int) bool {
|
|
return pkglicensing.ExceedsUserLimit(current, additions, limit)
|
|
}
|
|
|
|
func userLimitExceededMessageFromLicensing(current, limit int) string {
|
|
return pkglicensing.UserLimitExceededMessage(current, limit)
|
|
}
|
|
|
|
func mapStripeSubscriptionStatusToStateFromLicensing(status string) subscriptionState {
|
|
return pkglicensing.MapStripeSubscriptionStatusToState(status)
|
|
}
|
|
|
|
func shouldGrantPaidCapabilitiesFromLicensing(state subscriptionState) bool {
|
|
return pkglicensing.ShouldGrantPaidCapabilities(state)
|
|
}
|
|
|
|
func deriveStripePlanVersionFromLicensing(metadata map[string]string, priceID string) string {
|
|
return pkglicensing.DeriveStripePlanVersion(metadata, priceID)
|
|
}
|
|
|
|
func buildEntitlementPayloadFromLicensing(status *licenseStatus, subscriptionState string) entitlementPayloadModel {
|
|
return pkglicensing.BuildEntitlementPayload(status, subscriptionState)
|
|
}
|
|
|
|
func buildFeatureMapFromLicensing(service *licenseService) map[string]bool {
|
|
return pkglicensing.BuildFeatureMap(service, nil)
|
|
}
|
|
|
|
func buildEntitlementPayloadWithUsageFromLicensing(
|
|
status *licenseStatus,
|
|
subscriptionState string,
|
|
usage entitlementUsageSnapshotModel,
|
|
trialEndsAtUnix *int64,
|
|
) entitlementPayloadModel {
|
|
return pkglicensing.BuildEntitlementPayloadWithUsage(status, subscriptionState, usage, trialEndsAtUnix)
|
|
}
|
|
|
|
func buildRuntimeCapabilitiesPayloadWithUsageFromLicensing(
|
|
status *licenseStatus,
|
|
subscriptionState string,
|
|
usage entitlementUsageSnapshotModel,
|
|
) runtimeCapabilitiesPayloadModel {
|
|
return pkglicensing.BuildRuntimeCapabilitiesPayloadWithUsage(status, subscriptionState, usage)
|
|
}
|
|
|
|
func communityRuntimeIdentityFromLicensing() runtimeIdentityModel {
|
|
return pkglicensing.CommunityRuntimeIdentity()
|
|
}
|
|
|
|
func proRuntimeIdentityFromLicensing() runtimeIdentityModel {
|
|
return pkglicensing.ProRuntimeIdentity()
|
|
}
|
|
|
|
func normalizeRuntimeIdentityFromLicensing(identity runtimeIdentityModel) runtimeIdentityModel {
|
|
return pkglicensing.NormalizeRuntimeIdentity(identity)
|
|
}
|
|
|
|
func cloneRuntimeIdentityFromLicensing(identity runtimeIdentityModel) *runtimeIdentityModel {
|
|
return pkglicensing.CloneRuntimeIdentity(identity)
|
|
}
|
|
|
|
func filterCapabilitiesForRuntimeIdentityFromLicensing(
|
|
capabilities []string,
|
|
identity runtimeIdentityModel,
|
|
) ([]string, []pkglicensing.RuntimeCapabilityBlock) {
|
|
return pkglicensing.FilterCapabilitiesForRuntimeIdentity(capabilities, identity)
|
|
}
|
|
|
|
func buildCommercialPosturePayloadWithUsageFromLicensing(
|
|
status *licenseStatus,
|
|
subscriptionState string,
|
|
usage entitlementUsageSnapshotModel,
|
|
trialEndsAtUnix *int64,
|
|
) commercialPosturePayloadModel {
|
|
return pkglicensing.BuildCommercialPosturePayloadWithUsage(
|
|
status,
|
|
subscriptionState,
|
|
usage,
|
|
trialEndsAtUnix,
|
|
)
|
|
}
|
|
|
|
func commercialPosturePayloadFromEntitlementPayloadFromLicensing(
|
|
payload entitlementPayloadModel,
|
|
) commercialPosturePayloadModel {
|
|
return pkglicensing.CommercialPosturePayloadFromEntitlementPayload(payload)
|
|
}
|
|
|
|
func limitStateFromLicensing(current, limit int64) string {
|
|
return pkglicensing.LimitState(current, limit)
|
|
}
|
|
|
|
func trialActivationPublicKeyFromLicensing() (ed25519.PublicKey, error) {
|
|
return pkglicensing.HostedEntitlementPublicKey()
|
|
}
|
|
|
|
func signPurchaseReturnTokenFromLicensing(signingKey []byte, claims purchaseReturnClaimsModel) (string, error) {
|
|
return pkglicensing.SignPurchaseReturnToken(signingKey, claims)
|
|
}
|
|
|
|
func verifyPurchaseReturnTokenFromLicensing(token string, signingKey []byte, expectedInstanceHost string, now time.Time) (*purchaseReturnClaimsModel, error) {
|
|
return pkglicensing.VerifyPurchaseReturnToken(token, signingKey, expectedInstanceHost, now)
|
|
}
|
|
|
|
func verifyEntitlementLeaseTokenFromLicensing(token string, key ed25519.PublicKey, expectedInstanceHost string, now time.Time) (*entitlementLeaseClaimsModel, error) {
|
|
return pkglicensing.VerifyEntitlementLeaseToken(token, key, expectedInstanceHost, now)
|
|
}
|
|
|
|
func parseEntitlementLeaseTokenFromLicensing(token string, key ed25519.PublicKey, expectedInstanceHost string) (*entitlementLeaseClaimsModel, error) {
|
|
return pkglicensing.ParseEntitlementLeaseToken(token, key, expectedInstanceHost)
|
|
}
|
|
|
|
func writePaymentRequiredFromLicensing(w http.ResponseWriter, payload map[string]interface{}) {
|
|
pkglicensing.WritePaymentRequired(w, payload)
|
|
}
|
|
|
|
func writeLicenseRequiredFromLicensing(w http.ResponseWriter, feature, message string) {
|
|
pkglicensing.WriteLicenseRequired(w, feature, message, pkglicensing.UpgradeURLForFeature)
|
|
}
|
|
|
|
// licenseTierFreeValue is the canonical free-tier constant for use outside the bridge.
|
|
const licenseTierFreeValue = pkglicensing.TierFree
|
|
|
|
// overflowDaysRemainingFromLicensing returns the number of days remaining in the overflow window.
|
|
func overflowDaysRemainingFromLicensing(tier licenseTier, overflowGrantedAt *int64, now time.Time) int {
|
|
return pkglicensing.OverflowDaysRemaining(tier, overflowGrantedAt, now)
|
|
}
|
|
|
|
// freeHistoryDaysDefault is the fallback history days when no license service is available.
|
|
var freeHistoryDaysDefault = pkglicensing.TierHistoryDays[pkglicensing.TierFree]
|
|
|
|
func tierHistoryDaysFromLicensing(tier pkglicensing.Tier) int {
|
|
days := pkglicensing.TierHistoryDays[tier]
|
|
if days == 0 {
|
|
days = freeHistoryDaysDefault
|
|
}
|
|
return days
|
|
}
|