001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020
021import javax.annotation.concurrent.ThreadSafe;
022import java.util.Collections;
023import java.util.EnumSet;
024import java.util.Set;
025
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Immutable, bounded, conservative activity evidence retained at the final shutdown boundary.
030 * Observations may be combined across shutdown phases when complete termination cannot be
031 * proven. A category can therefore describe work that ended after an earlier observation;
032 * the evidence is not a live census of work active at result publication. An empty category
033 * set alone does not prove termination. Published evidence remains unchanged when work exits later.
034 */
035@ThreadSafe
036public final class ResidualActivityEvidence {
037        @NonNull
038        private final Set<@NonNull ResidualActivityType> residualActivityTypes;
039        @NonNull
040        private final String summary;
041
042        ResidualActivityEvidence(
043                        @NonNull Set<@NonNull ResidualActivityType> residualActivityTypes,
044                        @NonNull String summary) {
045                Set<ResidualActivityType> exactTypes = requireNonNull(
046                                residualActivityTypes);
047                this.residualActivityTypes = Collections.unmodifiableSet(
048                                exactTypes.isEmpty()
049                                                ? EnumSet.noneOf(ResidualActivityType.class)
050                                                : EnumSet.copyOf(exactTypes));
051                this.summary = requireNonNull(summary);
052        }
053
054        /** @return immutable conservative activity categories in enum order */
055        @NonNull
056        public Set<@NonNull ResidualActivityType> getResidualActivityTypes() {
057                return this.residualActivityTypes;
058        }
059
060        /**
061         * Returns a framework-generated, control-character-escaped summary capped
062         * at 1,024 Unicode code points.
063         *
064         * @return bounded diagnostic summary
065         */
066        @NonNull
067        public String getSummary() {
068                return this.summary;
069        }
070
071        /**
072         * Returns a bounded, single-line diagnostic containing the enum-ordered
073         * residual-activity categories. The free-text summary is available only
074         * through {@link #getSummary()} and is not rendered here.
075         *
076         * @return compact residual-activity diagnostic; not a serialization format
077         */
078        @Override
079        @NonNull
080        public String toString() {
081                return "ResidualActivityEvidence{residualActivityTypes=" + this.residualActivityTypes + '}';
082        }
083}