nawabari 0.8.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/cli-command-registry.d.ts +6 -0
- package/dist/cli-command-registry.js +26 -5
- package/dist/cli-command-registry.js.map +1 -1
- package/dist/cli.js +70 -8
- package/dist/cli.js.map +1 -1
- package/dist/contract.js +35 -0
- package/dist/contract.js.map +1 -1
- package/dist/domain/doctor.js +44 -0
- package/dist/domain/doctor.js.map +1 -1
- package/dist/domain/session-backend.d.ts +2 -1
- package/dist/domain/session-backend.js +119 -18
- package/dist/domain/session-backend.js.map +1 -1
- package/dist/domain/session.d.ts +100 -19
- package/dist/domain/session.js.map +1 -1
- package/dist/product-state-manifest.d.ts +152 -0
- package/dist/product-state-manifest.js +240 -0
- package/dist/product-state-manifest.js.map +1 -0
- package/dist/public-contract.d.ts +25 -0
- package/dist/public-contract.js +29 -0
- package/dist/public-contract.js.map +1 -0
- package/dist/public-state.d.ts +75 -0
- package/dist/public-state.js +60 -0
- package/dist/public-state.js.map +1 -0
- package/dist/session-lifecycle-actions.d.ts +4 -3
- package/dist/session-lifecycle-actions.js +14 -5
- package/dist/session-lifecycle-actions.js.map +1 -1
- package/dist/session-lifecycle-classification.d.ts +55 -2
- package/dist/session-lifecycle-classification.js +39 -306
- package/dist/session-lifecycle-classification.js.map +1 -1
- package/dist/session-registry.d.ts +83 -1
- package/dist/session-registry.js +440 -21
- package/dist/session-registry.js.map +1 -1
- package/dist/state/index.d.ts +5 -0
- package/dist/state/index.js +4 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/session/actors.d.ts +71 -0
- package/dist/state/session/actors.js +259 -0
- package/dist/state/session/actors.js.map +1 -0
- package/dist/state/session/guards.d.ts +25 -0
- package/dist/state/session/guards.js +108 -0
- package/dist/state/session/guards.js.map +1 -0
- package/dist/state/session/index.d.ts +6 -0
- package/dist/state/session/index.js +5 -0
- package/dist/state/session/index.js.map +1 -0
- package/dist/state/session/machine.d.ts +185 -0
- package/dist/state/session/machine.js +408 -0
- package/dist/state/session/machine.js.map +1 -0
- package/dist/state/session/types.d.ts +94 -0
- package/dist/state/session/types.js +14 -0
- package/dist/state/session/types.js.map +1 -0
- package/package.json +26 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lifecycleTransition } from "./session-lifecycle-classification.js";
|
|
1
2
|
/**
|
|
2
3
|
* The small, public vocabulary for lifecycle recovery. These values are
|
|
3
4
|
* descriptions of an available caller action; none of them grants mutation
|
|
@@ -42,20 +43,28 @@ function reconcileAction(sessionId) {
|
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* Project one canonical lifecycle observation into bounded typed caller
|
|
45
|
-
* actions.
|
|
46
|
-
* function only consumes
|
|
46
|
+
* actions. Lifecycle state and admissibility arrive through the canonical
|
|
47
|
+
* classification projection; this function only consumes that result and
|
|
48
|
+
* already-authoritative blocker facts.
|
|
47
49
|
*/
|
|
48
50
|
export function projectSessionLifecycleActions(input) {
|
|
49
51
|
const { classification, sessionId } = input;
|
|
50
52
|
const blocker = input.blockers?.[0];
|
|
51
53
|
const details = blocker?.details;
|
|
52
|
-
|
|
54
|
+
const close = lifecycleTransition(classification, "close");
|
|
55
|
+
const discard = lifecycleTransition(classification, "discard");
|
|
56
|
+
// Lifecycle state and operation admissibility come from the machine-derived
|
|
57
|
+
// transition projection. This adapter only chooses a caller-facing action
|
|
58
|
+
// for the already-authoritative blocker evidence.
|
|
59
|
+
if (close.allowed || discard.reason === "discarded-terminal" || close.reason === "closed-terminal") {
|
|
53
60
|
return Object.freeze([]);
|
|
54
|
-
|
|
61
|
+
}
|
|
62
|
+
if (close.reason === "physical-reconciliation-required") {
|
|
55
63
|
return Object.freeze([reconcileAction(sessionId)]);
|
|
56
64
|
}
|
|
57
|
-
if (
|
|
65
|
+
if (close.reason !== "recoverable-work-must-be-retained-or-discarded" || !discard.allowed) {
|
|
58
66
|
return Object.freeze([]);
|
|
67
|
+
}
|
|
59
68
|
const actions = [];
|
|
60
69
|
if (blocker?.code === "INTEGRATION_FETCH_FAILED") {
|
|
61
70
|
const integratedRevision = stringDetail(details, "integratedRevision");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-lifecycle-actions.js","sourceRoot":"","sources":["../src/session-lifecycle-actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-lifecycle-actions.js","sourceRoot":"","sources":["../src/session-lifecycle-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const SESSION_LIFECYCLE_CLASSIFICATION_SCHEMA_VERSION: 1;
|
|
9
9
|
export type SessionLifecycleState = "active" | "close-ready" | "blocked-recoverable" | "discarded" | "stale-inconsistent" | "closed";
|
|
10
|
+
/**
|
|
11
|
+
* The public state vocabulary is derived from the machine's own explicit
|
|
12
|
+
* internal-id-to-public-name mapping (`SESSION_LIFECYCLE_STATE_NODE_IDS`),
|
|
13
|
+
* not hand-duplicated here. That mapping is the single declared source of
|
|
14
|
+
* the public lifecycle vocabulary; this module only re-exposes its keys.
|
|
15
|
+
*/
|
|
10
16
|
export declare const SESSION_LIFECYCLE_STATES: readonly SessionLifecycleState[];
|
|
11
17
|
export type SessionLifecycleOperation = "close" | "discard" | "inspect" | "doctor" | "reconcile" | "gc";
|
|
12
18
|
export type SessionLifecyclePhase = "current" | "termination";
|
|
@@ -41,6 +47,43 @@ export interface SessionLifecycleTransition {
|
|
|
41
47
|
readonly authority: "session-registry" | "reconciliation" | "gc" | "caller";
|
|
42
48
|
readonly reason: "observe" | "close-proof-required" | "close-authorized" | "recoverable-work-must-be-retained-or-discarded" | "explicit-discard-required" | "physical-reconciliation-required" | "already-terminal" | "age-is-not-destructive-authority" | "discarded-terminal" | "closed-terminal";
|
|
43
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Static machine-contract projection entry whose runtime admissibility does
|
|
52
|
+
* not depend on the supplied observation beyond state identity. `allowed`,
|
|
53
|
+
* `target`, and `reason` are therefore truthful for every observation that
|
|
54
|
+
* classifies into this lifecycle state.
|
|
55
|
+
*/
|
|
56
|
+
export interface SessionLifecycleUnconditionalTransition extends SessionLifecycleTransition {
|
|
57
|
+
readonly guarded: false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Static machine-contract projection entry for a transition whose
|
|
61
|
+
* admissibility depends on a context/observation-derived guard, not on state
|
|
62
|
+
* identity alone (for example `close-ready` GC, gated on independent GC
|
|
63
|
+
* authorization). Both outcomes are represented explicitly so a static
|
|
64
|
+
* consumer never infers a single flattened verdict for a guard-dependent
|
|
65
|
+
* edge; the caller must know or supply the guard's outcome to pick a branch.
|
|
66
|
+
*/
|
|
67
|
+
export interface SessionLifecycleGuardedTransition {
|
|
68
|
+
readonly operation: SessionLifecycleOperation;
|
|
69
|
+
readonly guarded: true;
|
|
70
|
+
readonly requiresExplicitIntent: boolean;
|
|
71
|
+
readonly authority: SessionLifecycleTransition["authority"];
|
|
72
|
+
/** Metadata when the machine's guard accepts the event for this state. */
|
|
73
|
+
readonly whenGuardAccepts: {
|
|
74
|
+
readonly allowed: true;
|
|
75
|
+
readonly target: SessionLifecycleState;
|
|
76
|
+
readonly reason: SessionLifecycleTransition["reason"];
|
|
77
|
+
};
|
|
78
|
+
/** Metadata when the machine's guard rejects the event for this state. */
|
|
79
|
+
readonly whenGuardRejects: {
|
|
80
|
+
readonly allowed: false;
|
|
81
|
+
readonly target: null;
|
|
82
|
+
readonly reason: SessionLifecycleTransition["reason"];
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** One entry of the static machine-contract projection table. */
|
|
86
|
+
export type SessionLifecycleTransitionProjection = SessionLifecycleUnconditionalTransition | SessionLifecycleGuardedTransition;
|
|
44
87
|
export interface SessionLifecycleClassification {
|
|
45
88
|
readonly schemaVersion: typeof SESSION_LIFECYCLE_CLASSIFICATION_SCHEMA_VERSION;
|
|
46
89
|
readonly state: SessionLifecycleState;
|
|
@@ -63,5 +106,15 @@ export interface SessionLifecycleClassification {
|
|
|
63
106
|
export declare function classifySessionLifecycle(observation: SessionLifecycleObservation): SessionLifecycleClassification;
|
|
64
107
|
/** Return one transition without exposing a mutable table to callers. */
|
|
65
108
|
export declare function lifecycleTransition(classification: SessionLifecycleClassification, operation: SessionLifecycleOperation): SessionLifecycleTransition;
|
|
66
|
-
/**
|
|
67
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Return the operation names currently admissible by the canonical machine
|
|
111
|
+
* projection. Consumers use this for discovery/availability only; it does not
|
|
112
|
+
* authorize or execute a mutation.
|
|
113
|
+
*/
|
|
114
|
+
export declare function availableLifecycleOperations(classification: SessionLifecycleClassification): readonly SessionLifecycleOperation[];
|
|
115
|
+
/**
|
|
116
|
+
* The complete state/operation table, useful to help/discovery consumers.
|
|
117
|
+
* A guard-dependent edge (see `SessionLifecycleGuardedTransition`) is never
|
|
118
|
+
* flattened into a single unconditional verdict here.
|
|
119
|
+
*/
|
|
120
|
+
export declare const SESSION_LIFECYCLE_TRANSITION_TABLE: Readonly<Record<SessionLifecycleState, readonly SessionLifecycleTransitionProjection[]>>;
|
|
@@ -5,272 +5,31 @@
|
|
|
5
5
|
* session, ownership, cleanup, and integration-proof authorities. It does
|
|
6
6
|
* not inspect or mutate Git, the registry, a worktree, or a branch itself.
|
|
7
7
|
*/
|
|
8
|
+
import { classifyObservationBlockers } from "./state/session/guards.js";
|
|
9
|
+
import { projectSessionLifecycleMachine, projectSessionLifecycleTransitionTable, SESSION_LIFECYCLE_STATE_NODE_IDS, } from "./state/session/machine.js";
|
|
8
10
|
export const SESSION_LIFECYCLE_CLASSIFICATION_SCHEMA_VERSION = 1;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
]);
|
|
17
|
-
const AMBIGUOUS_CODES = new Set([
|
|
18
|
-
"GIT_STATE_AMBIGUOUS",
|
|
19
|
-
"PHYSICAL_OBSERVATION_UNAVAILABLE",
|
|
20
|
-
"REPOSITORY_IDENTITY_AMBIGUOUS",
|
|
21
|
-
"WORKTREE_IDENTITY_AMBIGUOUS",
|
|
22
|
-
"GIT_COMMAND_FAILED",
|
|
23
|
-
"GIT_SPAWN_FAILED",
|
|
24
|
-
"GIT_TIMEOUT",
|
|
25
|
-
"GIT_OUTPUT_LIMIT",
|
|
26
|
-
"OWNERSHIP_MISMATCH",
|
|
27
|
-
"DUPLICATE_WORKTREE_OWNERSHIP",
|
|
28
|
-
"DUPLICATE_BRANCH_OWNERSHIP",
|
|
29
|
-
"RECONCILIATION_DRIFT",
|
|
30
|
-
"STALE_REGISTRY",
|
|
31
|
-
]);
|
|
32
|
-
const RECOVERABLE_CODES = new Set([
|
|
33
|
-
"DIRTY_WORKTREE",
|
|
34
|
-
"NESTED_REPOSITORY",
|
|
35
|
-
"RECOVERABLE_COMMITS",
|
|
36
|
-
"RECOVERABLE_STASHES",
|
|
37
|
-
// An explicit bounded integration fetch failed before mutation. The
|
|
38
|
-
// caller may retry the same bounded proof operation; this is not a
|
|
39
|
-
// destructive or ambient fallback.
|
|
40
|
-
"INTEGRATION_FETCH_FAILED",
|
|
41
|
-
]);
|
|
42
|
-
const STALE_PHYSICAL_STATES = new Set([
|
|
43
|
-
"prunable-missing",
|
|
44
|
-
"prunable-present",
|
|
45
|
-
"registered-missing",
|
|
46
|
-
"unregistered-missing",
|
|
47
|
-
"unregistered-present",
|
|
48
|
-
"invalid",
|
|
49
|
-
"unavailable",
|
|
50
|
-
]);
|
|
51
|
-
const KNOWN_SESSION_STATES = new Set(["new", "active", "closing", "closed", "stale"]);
|
|
52
|
-
const KNOWN_PHYSICAL_STATES = new Set(["healthy", "closed", ...STALE_PHYSICAL_STATES]);
|
|
53
|
-
const KNOWN_BLOCKER_CLASSIFICATIONS = new Set(["recoverable", "ambiguous", "stale"]);
|
|
11
|
+
/**
|
|
12
|
+
* The public state vocabulary is derived from the machine's own explicit
|
|
13
|
+
* internal-id-to-public-name mapping (`SESSION_LIFECYCLE_STATE_NODE_IDS`),
|
|
14
|
+
* not hand-duplicated here. That mapping is the single declared source of
|
|
15
|
+
* the public lifecycle vocabulary; this module only re-exposes its keys.
|
|
16
|
+
*/
|
|
17
|
+
export const SESSION_LIFECYCLE_STATES = Object.freeze(Object.keys(SESSION_LIFECYCLE_STATE_NODE_IDS));
|
|
54
18
|
function freezeBlocker(blocker) {
|
|
55
19
|
return Object.freeze({
|
|
56
20
|
code: blocker.code,
|
|
57
21
|
...(blocker.classification === undefined ? {} : { classification: blocker.classification }),
|
|
58
22
|
});
|
|
59
23
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
function isStalePhysicalState(physicalState) {
|
|
70
|
-
return (physicalState === null || !KNOWN_PHYSICAL_STATES.has(physicalState) || STALE_PHYSICAL_STATES.has(physicalState));
|
|
71
|
-
}
|
|
72
|
-
function freezeTransitions(transitions) {
|
|
73
|
-
return Object.freeze(transitions.map((transition) => Object.freeze({ ...transition })));
|
|
74
|
-
}
|
|
75
|
-
function transitionTable(state) {
|
|
76
|
-
const inspect = (operation) => ({
|
|
77
|
-
operation,
|
|
78
|
-
allowed: true,
|
|
79
|
-
target: state,
|
|
80
|
-
requiresExplicitIntent: false,
|
|
81
|
-
authority: operation === "doctor" ? "reconciliation" : "session-registry",
|
|
82
|
-
reason: "observe",
|
|
24
|
+
function normalizeObservation(observation, blockers) {
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
...observation,
|
|
27
|
+
blockers,
|
|
28
|
+
closeReadiness: observation.closeReadiness ?? "not-evaluated",
|
|
29
|
+
ageSuspicious: observation.ageSuspicious === true,
|
|
30
|
+
gcAuthorized: observation.gcAuthorized === true,
|
|
31
|
+
phase: observation.phase ?? "current",
|
|
83
32
|
});
|
|
84
|
-
const reconcile = {
|
|
85
|
-
operation: "reconcile",
|
|
86
|
-
allowed: true,
|
|
87
|
-
target: state,
|
|
88
|
-
requiresExplicitIntent: false,
|
|
89
|
-
authority: "reconciliation",
|
|
90
|
-
reason: "observe",
|
|
91
|
-
};
|
|
92
|
-
switch (state) {
|
|
93
|
-
case "active":
|
|
94
|
-
return freezeTransitions([
|
|
95
|
-
{
|
|
96
|
-
operation: "close",
|
|
97
|
-
allowed: true,
|
|
98
|
-
target: "close-ready",
|
|
99
|
-
requiresExplicitIntent: false,
|
|
100
|
-
authority: "session-registry",
|
|
101
|
-
reason: "close-proof-required",
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
operation: "discard",
|
|
105
|
-
allowed: true,
|
|
106
|
-
target: "discarded",
|
|
107
|
-
requiresExplicitIntent: true,
|
|
108
|
-
authority: "caller",
|
|
109
|
-
reason: "explicit-discard-required",
|
|
110
|
-
},
|
|
111
|
-
inspect("inspect"),
|
|
112
|
-
inspect("doctor"),
|
|
113
|
-
reconcile,
|
|
114
|
-
{
|
|
115
|
-
operation: "gc",
|
|
116
|
-
allowed: false,
|
|
117
|
-
target: null,
|
|
118
|
-
requiresExplicitIntent: false,
|
|
119
|
-
authority: "gc",
|
|
120
|
-
reason: "age-is-not-destructive-authority",
|
|
121
|
-
},
|
|
122
|
-
]);
|
|
123
|
-
case "close-ready":
|
|
124
|
-
return freezeTransitions([
|
|
125
|
-
{
|
|
126
|
-
operation: "close",
|
|
127
|
-
allowed: true,
|
|
128
|
-
target: "closed",
|
|
129
|
-
requiresExplicitIntent: false,
|
|
130
|
-
authority: "session-registry",
|
|
131
|
-
reason: "close-authorized",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
operation: "discard",
|
|
135
|
-
allowed: true,
|
|
136
|
-
target: "discarded",
|
|
137
|
-
requiresExplicitIntent: true,
|
|
138
|
-
authority: "caller",
|
|
139
|
-
reason: "explicit-discard-required",
|
|
140
|
-
},
|
|
141
|
-
inspect("inspect"),
|
|
142
|
-
inspect("doctor"),
|
|
143
|
-
reconcile,
|
|
144
|
-
{
|
|
145
|
-
operation: "gc",
|
|
146
|
-
allowed: true,
|
|
147
|
-
target: "closed",
|
|
148
|
-
requiresExplicitIntent: false,
|
|
149
|
-
authority: "gc",
|
|
150
|
-
reason: "close-authorized",
|
|
151
|
-
},
|
|
152
|
-
]);
|
|
153
|
-
case "blocked-recoverable":
|
|
154
|
-
return freezeTransitions([
|
|
155
|
-
{
|
|
156
|
-
operation: "close",
|
|
157
|
-
allowed: false,
|
|
158
|
-
target: null,
|
|
159
|
-
requiresExplicitIntent: false,
|
|
160
|
-
authority: "session-registry",
|
|
161
|
-
reason: "recoverable-work-must-be-retained-or-discarded",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
operation: "discard",
|
|
165
|
-
allowed: true,
|
|
166
|
-
target: "discarded",
|
|
167
|
-
requiresExplicitIntent: true,
|
|
168
|
-
authority: "caller",
|
|
169
|
-
reason: "explicit-discard-required",
|
|
170
|
-
},
|
|
171
|
-
inspect("inspect"),
|
|
172
|
-
inspect("doctor"),
|
|
173
|
-
reconcile,
|
|
174
|
-
{
|
|
175
|
-
operation: "gc",
|
|
176
|
-
allowed: false,
|
|
177
|
-
target: null,
|
|
178
|
-
requiresExplicitIntent: false,
|
|
179
|
-
authority: "gc",
|
|
180
|
-
reason: "recoverable-work-must-be-retained-or-discarded",
|
|
181
|
-
},
|
|
182
|
-
]);
|
|
183
|
-
case "discarded":
|
|
184
|
-
return freezeTransitions([
|
|
185
|
-
{
|
|
186
|
-
operation: "close",
|
|
187
|
-
allowed: false,
|
|
188
|
-
target: null,
|
|
189
|
-
requiresExplicitIntent: false,
|
|
190
|
-
authority: "session-registry",
|
|
191
|
-
reason: "discarded-terminal",
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
operation: "discard",
|
|
195
|
-
allowed: true,
|
|
196
|
-
target: "discarded",
|
|
197
|
-
requiresExplicitIntent: true,
|
|
198
|
-
authority: "caller",
|
|
199
|
-
reason: "discarded-terminal",
|
|
200
|
-
},
|
|
201
|
-
inspect("inspect"),
|
|
202
|
-
inspect("doctor"),
|
|
203
|
-
reconcile,
|
|
204
|
-
{
|
|
205
|
-
operation: "gc",
|
|
206
|
-
allowed: false,
|
|
207
|
-
target: null,
|
|
208
|
-
requiresExplicitIntent: false,
|
|
209
|
-
authority: "gc",
|
|
210
|
-
reason: "discarded-terminal",
|
|
211
|
-
},
|
|
212
|
-
]);
|
|
213
|
-
case "stale-inconsistent":
|
|
214
|
-
return freezeTransitions([
|
|
215
|
-
{
|
|
216
|
-
operation: "close",
|
|
217
|
-
allowed: false,
|
|
218
|
-
target: null,
|
|
219
|
-
requiresExplicitIntent: false,
|
|
220
|
-
authority: "reconciliation",
|
|
221
|
-
reason: "physical-reconciliation-required",
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
operation: "discard",
|
|
225
|
-
allowed: false,
|
|
226
|
-
target: null,
|
|
227
|
-
requiresExplicitIntent: true,
|
|
228
|
-
authority: "reconciliation",
|
|
229
|
-
reason: "physical-reconciliation-required",
|
|
230
|
-
},
|
|
231
|
-
inspect("inspect"),
|
|
232
|
-
inspect("doctor"),
|
|
233
|
-
reconcile,
|
|
234
|
-
{
|
|
235
|
-
operation: "gc",
|
|
236
|
-
allowed: false,
|
|
237
|
-
target: null,
|
|
238
|
-
requiresExplicitIntent: false,
|
|
239
|
-
authority: "reconciliation",
|
|
240
|
-
reason: "physical-reconciliation-required",
|
|
241
|
-
},
|
|
242
|
-
]);
|
|
243
|
-
case "closed":
|
|
244
|
-
return freezeTransitions([
|
|
245
|
-
{
|
|
246
|
-
operation: "close",
|
|
247
|
-
allowed: true,
|
|
248
|
-
target: "closed",
|
|
249
|
-
requiresExplicitIntent: false,
|
|
250
|
-
authority: "session-registry",
|
|
251
|
-
reason: "closed-terminal",
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
operation: "discard",
|
|
255
|
-
allowed: false,
|
|
256
|
-
target: null,
|
|
257
|
-
requiresExplicitIntent: true,
|
|
258
|
-
authority: "session-registry",
|
|
259
|
-
reason: "closed-terminal",
|
|
260
|
-
},
|
|
261
|
-
inspect("inspect"),
|
|
262
|
-
inspect("doctor"),
|
|
263
|
-
reconcile,
|
|
264
|
-
{
|
|
265
|
-
operation: "gc",
|
|
266
|
-
allowed: false,
|
|
267
|
-
target: null,
|
|
268
|
-
requiresExplicitIntent: false,
|
|
269
|
-
authority: "gc",
|
|
270
|
-
reason: "closed-terminal",
|
|
271
|
-
},
|
|
272
|
-
]);
|
|
273
|
-
}
|
|
274
33
|
}
|
|
275
34
|
/**
|
|
276
35
|
* Classify one already-observed session. The default `current` phase
|
|
@@ -281,56 +40,18 @@ export function classifySessionLifecycle(observation) {
|
|
|
281
40
|
const blockers = Object.freeze((observation.blockers ?? []).map(freezeBlocker));
|
|
282
41
|
const physicalState = observation.physicalState ?? null;
|
|
283
42
|
const closeReadiness = observation.closeReadiness ?? "not-evaluated";
|
|
284
|
-
const recoverability =
|
|
43
|
+
const recoverability = classifyObservationBlockers(blockers);
|
|
285
44
|
const ageSuspicious = observation.ageSuspicious === true;
|
|
286
45
|
const gcAuthorized = observation.gcAuthorized === true;
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
const ambiguousReadiness = closeReadiness === "ambiguous" || (closeReadiness === "blocked" && blockers.length === 0);
|
|
294
|
-
const phase = observation.phase ?? "current";
|
|
295
|
-
let state;
|
|
296
|
-
const physicalEvidenceRequired = observation.sessionState !== "closed";
|
|
297
|
-
if (unknownSessionState ||
|
|
298
|
-
unknownBlocker ||
|
|
299
|
-
staleBlocker ||
|
|
300
|
-
ambiguousReadiness ||
|
|
301
|
-
recoverability === "ambiguous" ||
|
|
302
|
-
(physicalEvidenceRequired && stalePhysical)) {
|
|
303
|
-
state = "stale-inconsistent";
|
|
304
|
-
}
|
|
305
|
-
else if (observation.sessionState === "closed") {
|
|
306
|
-
state = observation.terminalOperation === "discard" ? "discarded" : "closed";
|
|
307
|
-
}
|
|
308
|
-
else if (observation.terminalOperation === "discard") {
|
|
309
|
-
state = "discarded";
|
|
310
|
-
}
|
|
311
|
-
else if (recoverability === "recoverable" || closeReadiness === "external_evidence_required") {
|
|
312
|
-
state = "blocked-recoverable";
|
|
313
|
-
}
|
|
314
|
-
else if (phase === "termination" && closeReadiness === "ready") {
|
|
315
|
-
state = "close-ready";
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
state = "active";
|
|
319
|
-
}
|
|
320
|
-
// A positive GC authority must be independent of elapsed age. In
|
|
321
|
-
// particular, old-but-healthy active sessions remain non-destructive.
|
|
322
|
-
const destructiveCleanupEligible = gcAuthorized && state === "close-ready" && !(ageSuspicious && physicalState === "healthy");
|
|
323
|
-
const transitions = transitionTable(state).map((transition) => transition.operation === "gc" && (!gcAuthorized || !destructiveCleanupEligible)
|
|
324
|
-
? Object.freeze({
|
|
325
|
-
...transition,
|
|
326
|
-
allowed: false,
|
|
327
|
-
target: null,
|
|
328
|
-
reason: "age-is-not-destructive-authority",
|
|
329
|
-
})
|
|
330
|
-
: transition);
|
|
46
|
+
const machineProjection = projectSessionLifecycleMachine({
|
|
47
|
+
observation: normalizeObservation(observation, blockers),
|
|
48
|
+
});
|
|
49
|
+
const transitions = Object.freeze(Object.values(machineProjection.transitions).map(({ eventType: _eventType, ...transition }) => Object.freeze(transition)));
|
|
50
|
+
const gcTransition = machineProjection.transitions.gc;
|
|
51
|
+
const destructiveCleanupEligible = gcTransition.allowed;
|
|
331
52
|
return Object.freeze({
|
|
332
53
|
schemaVersion: SESSION_LIFECYCLE_CLASSIFICATION_SCHEMA_VERSION,
|
|
333
|
-
state,
|
|
54
|
+
state: machineProjection.state,
|
|
334
55
|
sessionState: observation.sessionState,
|
|
335
56
|
physicalState,
|
|
336
57
|
closeReadiness,
|
|
@@ -350,6 +71,18 @@ export function lifecycleTransition(classification, operation) {
|
|
|
350
71
|
}
|
|
351
72
|
return transition;
|
|
352
73
|
}
|
|
353
|
-
/**
|
|
354
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Return the operation names currently admissible by the canonical machine
|
|
76
|
+
* projection. Consumers use this for discovery/availability only; it does not
|
|
77
|
+
* authorize or execute a mutation.
|
|
78
|
+
*/
|
|
79
|
+
export function availableLifecycleOperations(classification) {
|
|
80
|
+
return Object.freeze(classification.transitions.filter((transition) => transition.allowed).map((transition) => transition.operation));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The complete state/operation table, useful to help/discovery consumers.
|
|
84
|
+
* A guard-dependent edge (see `SessionLifecycleGuardedTransition`) is never
|
|
85
|
+
* flattened into a single unconditional verdict here.
|
|
86
|
+
*/
|
|
87
|
+
export const SESSION_LIFECYCLE_TRANSITION_TABLE = projectSessionLifecycleTransitionTable();
|
|
355
88
|
//# sourceMappingURL=session-lifecycle-classification.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-lifecycle-classification.js","sourceRoot":"","sources":["../src/session-lifecycle-classification.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,
|
|
1
|
+
{"version":3,"file":"session-lifecycle-classification.js","sourceRoot":"","sources":["../src/session-lifecycle-classification.ts"],"names":[],"mappings":"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"}
|