@mnemom/mnemom 0.17.34-next.1 → 0.17.34-next.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/dist/base-url-proxy.mjs +18 -4
- package/dist/commands/agent-config.js +5 -1
- package/dist/commands/agent-doctor.d.ts +9 -0
- package/dist/commands/agent-doctor.js +4 -0
- package/dist/commands/agent-session.d.ts +1 -2
- package/dist/commands/agent-session.js +1 -2
- package/dist/commands/agent-setup.d.ts +6 -0
- package/dist/commands/agent-setup.js +12 -1
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.js +145 -46
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +254 -0
- package/dist/commands/durable-lock.js +595 -0
- package/dist/commands/durable-sessions.d.ts +87 -0
- package/dist/commands/durable-sessions.js +303 -0
- package/dist/commands/durable-verbs.d.ts +116 -0
- package/dist/commands/durable-verbs.js +393 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +7 -1
- package/dist/durable/launch-spec.js +10 -3
- package/dist/durable/seat-proxy-entry.d.ts +11 -0
- package/dist/durable/seat-proxy-entry.js +25 -0
- package/dist/durable/seat-proxy.d.ts +107 -0
- package/dist/durable/seat-proxy.js +599 -0
- package/dist/durable/seat-socket.d.ts +46 -0
- package/dist/durable/seat-socket.js +179 -0
- package/dist/durable/seat-spec-validator.d.ts +28 -0
- package/dist/durable/seat-spec-validator.js +59 -0
- package/dist/durable/test-mode.mjs +81 -0
- package/dist/durable/turn-chain.d.ts +55 -0
- package/dist/durable/turn-chain.js +178 -0
- package/dist/host/auto-continue.d.ts +67 -0
- package/dist/host/auto-continue.js +118 -0
- package/dist/host/bootid.d.ts +13 -0
- package/dist/host/bootid.js +41 -0
- package/dist/host/client.d.ts +123 -0
- package/dist/host/client.js +171 -0
- package/dist/host/commands.d.ts +2 -0
- package/dist/host/commands.js +122 -0
- package/dist/host/control.d.ts +33 -0
- package/dist/host/control.js +144 -0
- package/dist/host/daemon.d.ts +217 -0
- package/dist/host/daemon.js +606 -0
- package/dist/host/gate.d.ts +46 -0
- package/dist/host/gate.js +82 -0
- package/dist/host/grant.d.ts +24 -0
- package/dist/host/grant.js +24 -0
- package/dist/host/instance.d.ts +35 -0
- package/dist/host/instance.js +62 -0
- package/dist/host/integrity.d.ts +35 -0
- package/dist/host/integrity.js +115 -0
- package/dist/host/launchd.d.ts +16 -0
- package/dist/host/launchd.js +45 -0
- package/dist/host/lock.d.ts +20 -0
- package/dist/host/lock.js +63 -0
- package/dist/host/owner.d.ts +55 -0
- package/dist/host/owner.js +121 -0
- package/dist/host/plist.d.ts +16 -0
- package/dist/host/plist.js +85 -0
- package/dist/host/procs.d.ts +27 -0
- package/dist/host/procs.js +105 -0
- package/dist/host/run.d.ts +66 -0
- package/dist/host/run.js +314 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +22 -0
- package/dist/host/s1-names.js +21 -0
- package/dist/host/seat-credential.d.ts +27 -0
- package/dist/host/seat-credential.js +41 -0
- package/dist/host/seat-launch-main.d.ts +1 -0
- package/dist/host/seat-launch-main.js +59 -0
- package/dist/host/seat-launch.d.ts +116 -0
- package/dist/host/seat-launch.js +302 -0
- package/dist/host/seat.d.ts +48 -0
- package/dist/host/seat.js +149 -0
- package/dist/host/state.d.ts +33 -0
- package/dist/host/state.js +117 -0
- package/dist/host/status.d.ts +101 -0
- package/dist/host/status.js +307 -0
- package/dist/host/tmux.d.ts +56 -0
- package/dist/host/tmux.js +182 -0
- package/dist/host/transcript.d.ts +30 -0
- package/dist/host/transcript.js +130 -0
- package/dist/host/verbs.d.ts +50 -0
- package/dist/host/verbs.js +271 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +197 -35
- package/dist/lib/agent.d.ts +8 -26
- package/dist/lib/agent.js +11 -26
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/durable/api.d.ts +136 -0
- package/dist/lib/durable/api.js +220 -0
- package/dist/lib/durable/codes.d.ts +8 -0
- package/dist/lib/durable/codes.js +2 -0
- package/dist/lib/durable/first-turn.d.ts +50 -0
- package/dist/lib/durable/first-turn.js +102 -0
- package/dist/lib/durable/gate.d.ts +48 -0
- package/dist/lib/durable/gate.js +122 -0
- package/dist/lib/durable/handover.d.ts +101 -0
- package/dist/lib/durable/handover.js +175 -0
- package/dist/lib/durable/launch.d.ts +182 -0
- package/dist/lib/durable/launch.js +443 -0
- package/dist/lib/durable/launching.d.ts +15 -0
- package/dist/lib/durable/launching.js +70 -0
- package/dist/lib/durable/live.d.ts +194 -0
- package/dist/lib/durable/live.js +635 -0
- package/dist/lib/durable/refusal.d.ts +57 -0
- package/dist/lib/durable/refusal.js +78 -0
- package/dist/lib/durable/view.d.ts +83 -0
- package/dist/lib/durable/view.js +244 -0
- package/dist/lib/experimental.d.ts +0 -9
- package/dist/lib/experimental.js +18 -17
- package/dist/lib/launcher-config.d.ts +7 -0
- package/dist/lib/launcher-config.js +21 -0
- package/dist/lib/scan/report-email.d.ts +1 -1
- package/dist/lib/scan/report-email.js +9 -1
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/vendor/durable-contract/index.js +240 -18
- package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
- package/dist/vendor/shared/durable-contract/admission.js +533 -0
- package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
- package/dist/vendor/shared/durable-contract/arm.js +133 -0
- package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/canonical.js +98 -0
- package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
- package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
- package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
- package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
- package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
- package/dist/vendor/shared/durable-contract/codes.js +475 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
- package/dist/vendor/shared/durable-contract/constants.js +179 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/doors.js +249 -0
- package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
- package/dist/vendor/shared/durable-contract/envelope.js +317 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
- package/dist/vendor/shared/durable-contract/events.js +630 -0
- package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
- package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
- package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
- package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
- package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
- package/dist/vendor/shared/durable-contract/index.js +37 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
- package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
- package/dist/vendor/shared/durable-contract/keys.js +66 -0
- package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
- package/dist/vendor/shared/durable-contract/lock.js +151 -0
- package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
- package/dist/vendor/shared/durable-contract/names.js +78 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
- package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
- package/dist/vendor/shared/durable-contract/paths.js +35 -0
- package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
- package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
- package/dist/vendor/shared/durable-contract/routes.js +315 -0
- package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
- package/dist/vendor/shared/durable-contract/schema.js +441 -0
- package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
- package/dist/vendor/shared/durable-contract/seal.js +99 -0
- package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
- package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
- package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
- package/dist/vendor/shared/durable-contract/session-id.js +58 -0
- package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
- package/dist/vendor/shared/durable-contract/spec.js +286 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
- package/dist/vendor/shared/durable-contract/states.js +218 -0
- package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
- package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
- package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
- package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
- package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
- package/dist/vendor/shared/durable-contract/turn.js +81 -0
- package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
- package/dist/vendor/shared/durable-contract/vectors.js +208 -0
- package/package.json +3 -2
- package/plugins/integrity-manifest.json +35 -0
- package/plugins/mnemom/hooks/hooks.json +15 -1
- package/plugins/mnemom/hooks/register.tsx +60 -2
- package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
- package/dist/commands/agent-implicit-notice.d.ts +0 -31
- package/dist/commands/agent-implicit-notice.js +0 -73
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session states, the closed state_reason enum per state, effective-state derivation and the
|
|
3
|
+
* user-facing state mapping (design §3.3, §3.4; BACKBONE "state_reason values"; C11-R27).
|
|
4
|
+
*/
|
|
5
|
+
export declare const DURABLE_MODES: readonly ["tracked", "locked"];
|
|
6
|
+
export type DurableMode = (typeof DURABLE_MODES)[number];
|
|
7
|
+
export declare const SESSION_STATES: readonly ["resuming", "running", "detached", "parked", "suspended", "paused", "completed", "failed", "killed", "expired", "ready"];
|
|
8
|
+
export type SessionState = (typeof SESSION_STATES)[number];
|
|
9
|
+
export declare const TERMINAL_STATES: readonly ["completed", "failed", "killed", "expired"];
|
|
10
|
+
export declare const NON_TERMINAL_STATES: readonly ["resuming", "running", "detached", "parked", "suspended", "paused", "ready"];
|
|
11
|
+
export declare const DETACHED_REASONS: readonly ["lease_expired", "hello_missed", "seat_exited"];
|
|
12
|
+
export declare const SUSPENDED_REASONS: readonly ["budget_reached", "enforce_refused", "charter_breach", "account_balance", "identity_mismatch", "grant_expired", "card_drift", "attestation_failed", "owner_departed", "respawn_failed", "approval_expired", "approval_denied"];
|
|
13
|
+
/** breakglass is P0a-B; it stays in the enum so the CHECK never needs widening. */
|
|
14
|
+
export declare const PAUSED_REASONS: readonly ["owner", "agent_contained", "breakglass"];
|
|
15
|
+
export declare const FAILED_REASONS: readonly ["transcript_lost"];
|
|
16
|
+
export declare const KILLED_REASONS: readonly ["agent_killed"];
|
|
17
|
+
export type DetachedReason = (typeof DETACHED_REASONS)[number];
|
|
18
|
+
export type SuspendedReason = (typeof SUSPENDED_REASONS)[number];
|
|
19
|
+
export type PausedReason = (typeof PAUSED_REASONS)[number];
|
|
20
|
+
export type StateReason = DetachedReason | SuspendedReason | PausedReason | (typeof FAILED_REASONS)[number] | (typeof KILLED_REASONS)[number];
|
|
21
|
+
/**
|
|
22
|
+
* The closed state_reason enum per state. `null` in a list means NULL is allowed there and what it
|
|
23
|
+
* means: detached null = a lease that lapsed with no recorded cause; paused null = a plain pause
|
|
24
|
+
* (fixture only: owner pause writes `owner`); killed null = an owner kill. suspended and failed
|
|
25
|
+
* always carry a reason, so NULL there is a schema fault (503 at the gateway).
|
|
26
|
+
*/
|
|
27
|
+
export declare const STATE_REASONS: {
|
|
28
|
+
readonly [S in SessionState]: readonly (StateReason | null)[];
|
|
29
|
+
};
|
|
30
|
+
export declare const ALL_STATE_REASONS: readonly StateReason[];
|
|
31
|
+
/** M1 writers exist for these suspended reasons; the others are fixture-only (P0a-B / P0b writers). */
|
|
32
|
+
export declare const M1_WRITTEN_SUSPENDED_REASONS: readonly ["budget_reached", "identity_mismatch", "attestation_failed", "respawn_failed", "grant_expired", "approval_expired", "approval_denied"];
|
|
33
|
+
/** M1's three wait reasons, then ruling 34.06's ci, question and budget (appended). */
|
|
34
|
+
export declare const WAIT_REASONS: readonly ["approval", "moving", "mnemom_unavailable", "ci", "question", "budget"];
|
|
35
|
+
export type WaitReason = (typeof WAIT_REASONS)[number];
|
|
36
|
+
/**
|
|
37
|
+
* The wait reasons valid only with state 'parked' (ruling 34.06 (1), agent_sessions_wait_parked_ck).
|
|
38
|
+
* While wait_reason is one of these, every claim answers ineligible `waiting` (34.06 (3)).
|
|
39
|
+
*/
|
|
40
|
+
export declare const PARKED_ONLY_WAIT_REASONS: readonly ["ci", "question", "budget"];
|
|
41
|
+
export type ParkedOnlyWaitReason = (typeof PARKED_ONLY_WAIT_REASONS)[number];
|
|
42
|
+
export declare function isSessionState(v: unknown): v is SessionState;
|
|
43
|
+
export declare function isTerminalState(s: SessionState): boolean;
|
|
44
|
+
/** Whether `reason` is allowed with `state` by the closed enum. */
|
|
45
|
+
export declare function isValidStateReason(state: SessionState, reason: unknown): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The effective-state vocabulary: the persisted states plus `suspended_grant_expired`, the
|
|
48
|
+
* resolver's value for a lapsed executor grant (persisted or derived). It is the value set of the
|
|
49
|
+
* resolver's `effective_state` field and of the span attribute `durable_effective_state`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const EFFECTIVE_STATES: readonly ["resuming", "running", "detached", "parked", "suspended", "suspended_grant_expired", "paused", "completed", "failed", "killed", "expired", "ready"];
|
|
52
|
+
export type EffectiveState = (typeof EFFECTIVE_STATES)[number];
|
|
53
|
+
export declare function isEffectiveState(v: unknown): v is EffectiveState;
|
|
54
|
+
/** The effective state of a persisted (state, state_reason) with no derivation applied. */
|
|
55
|
+
export declare function persistedEffectiveState(state: SessionState, reason: StateReason | null): EffectiveState;
|
|
56
|
+
export interface EffectiveStateInput {
|
|
57
|
+
state: SessionState;
|
|
58
|
+
state_reason: StateReason | null;
|
|
59
|
+
/** agent_sessions.expires_at (NULL = never expires; always NULL on M1 write paths). */
|
|
60
|
+
expires_at: string | null;
|
|
61
|
+
/** The home executor's grant is live (grant_expires_at > now()). */
|
|
62
|
+
grant_live: boolean;
|
|
63
|
+
lease_expires_at: string | null;
|
|
64
|
+
/**
|
|
65
|
+
* The row is a pool session (rulings 34.04, 34.04.1): the caller computes home_executor_id IS
|
|
66
|
+
* NULL AND durable_mode = 'locked' AND the org is pool-enabled (poolClaimEnabled). A NULL home
|
|
67
|
+
* alone never means pool: A1's home FK is ON DELETE SET NULL, so erasure can leave a homed
|
|
68
|
+
* session with no home. Absent or false reads exactly as M1 (33f43de1).
|
|
69
|
+
*/
|
|
70
|
+
pool?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The effective state, first match wins (design §3.6 step 4, BACKBONE): expired > grant_expired >
|
|
74
|
+
* pool lease-ready (ruling 34.04 (1): a pool session whose lease lapsed reads `ready`) >
|
|
75
|
+
* lease-detached. Terminal rows never derive. The resolver computes this in SQL with Postgres
|
|
76
|
+
* now(); this is the reference the SQL is parity-tested against. Timestamps are ISO-8601.
|
|
77
|
+
*/
|
|
78
|
+
export declare function deriveEffectiveState(row: EffectiveStateInput, nowMs: number): EffectiveState;
|
|
79
|
+
/**
|
|
80
|
+
* Whether `effective` is reachable from the persisted row by the derivation above. The admission
|
|
81
|
+
* input validator refuses any other pairing as a schema fault (503). `pool` is the same explicit
|
|
82
|
+
* input as EffectiveStateInput.pool (ruling 34.04.1); false answers exactly as M1.
|
|
83
|
+
*/
|
|
84
|
+
export declare function isEffectiveStateConsistent(state: SessionState, reason: StateReason | null, effective: EffectiveState, pool?: boolean): boolean;
|
|
85
|
+
/** User-facing states (design §2, C11-R27), from the effective state. */
|
|
86
|
+
export declare const USER_STATES: readonly ["running", "waiting", "stopped", "ended"];
|
|
87
|
+
export type UserState = (typeof USER_STATES)[number];
|
|
88
|
+
export declare function userStateOf(effective: EffectiveState): UserState;
|
|
89
|
+
/** How the user-facing views word a ready session (ruling 34.02 (5), C11-R27). */
|
|
90
|
+
export declare const READY_USER_TEXT = "waiting for a runner";
|
|
91
|
+
export interface WireEnded {
|
|
92
|
+
state: "completed" | "failed" | "killed" | "expired";
|
|
93
|
+
state_reason: string | null;
|
|
94
|
+
}
|
|
95
|
+
/** The body of every 410 durable_session_ended (gateway and API): expired carries state_reason 'expired' (C11-R11). */
|
|
96
|
+
export declare function endedBody(effective: EffectiveState, reason: StateReason | null): WireEnded | null;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// GENERATED from shared/durable-contract/src/states.ts by cli/scripts/sync-durable-contract.mjs; do not edit.
|
|
2
|
+
/**
|
|
3
|
+
* Session states, the closed state_reason enum per state, effective-state derivation and the
|
|
4
|
+
* user-facing state mapping (design §3.3, §3.4; BACKBONE "state_reason values"; C11-R27).
|
|
5
|
+
*/
|
|
6
|
+
export const DURABLE_MODES = ["tracked", "locked"];
|
|
7
|
+
export const SESSION_STATES = [
|
|
8
|
+
"resuming",
|
|
9
|
+
"running",
|
|
10
|
+
"detached",
|
|
11
|
+
"parked",
|
|
12
|
+
"suspended",
|
|
13
|
+
"paused",
|
|
14
|
+
"completed",
|
|
15
|
+
"failed",
|
|
16
|
+
"killed",
|
|
17
|
+
"expired",
|
|
18
|
+
// ruling 34.02: appended last so no existing index moves
|
|
19
|
+
"ready",
|
|
20
|
+
];
|
|
21
|
+
export const TERMINAL_STATES = [
|
|
22
|
+
"completed",
|
|
23
|
+
"failed",
|
|
24
|
+
"killed",
|
|
25
|
+
"expired",
|
|
26
|
+
];
|
|
27
|
+
export const NON_TERMINAL_STATES = [
|
|
28
|
+
"resuming",
|
|
29
|
+
"running",
|
|
30
|
+
"detached",
|
|
31
|
+
"parked",
|
|
32
|
+
"suspended",
|
|
33
|
+
"paused",
|
|
34
|
+
"ready",
|
|
35
|
+
];
|
|
36
|
+
export const DETACHED_REASONS = ["lease_expired", "hello_missed", "seat_exited"];
|
|
37
|
+
export const SUSPENDED_REASONS = [
|
|
38
|
+
"budget_reached",
|
|
39
|
+
"enforce_refused",
|
|
40
|
+
"charter_breach",
|
|
41
|
+
"account_balance",
|
|
42
|
+
"identity_mismatch",
|
|
43
|
+
"grant_expired",
|
|
44
|
+
"card_drift",
|
|
45
|
+
"attestation_failed",
|
|
46
|
+
"owner_departed",
|
|
47
|
+
"respawn_failed",
|
|
48
|
+
/** ruling 34.22: a question wait's, or any non-budget approval wait's, approval expired or was denied; the owner resumes by hand. Budget waits keep budget_reached. */
|
|
49
|
+
"approval_expired",
|
|
50
|
+
"approval_denied",
|
|
51
|
+
];
|
|
52
|
+
/** breakglass is P0a-B; it stays in the enum so the CHECK never needs widening. */
|
|
53
|
+
export const PAUSED_REASONS = ["owner", "agent_contained", "breakglass"];
|
|
54
|
+
export const FAILED_REASONS = ["transcript_lost"];
|
|
55
|
+
export const KILLED_REASONS = ["agent_killed"];
|
|
56
|
+
/**
|
|
57
|
+
* The closed state_reason enum per state. `null` in a list means NULL is allowed there and what it
|
|
58
|
+
* means: detached null = a lease that lapsed with no recorded cause; paused null = a plain pause
|
|
59
|
+
* (fixture only: owner pause writes `owner`); killed null = an owner kill. suspended and failed
|
|
60
|
+
* always carry a reason, so NULL there is a schema fault (503 at the gateway).
|
|
61
|
+
*/
|
|
62
|
+
export const STATE_REASONS = {
|
|
63
|
+
resuming: [null],
|
|
64
|
+
running: [null],
|
|
65
|
+
detached: [null, ...DETACHED_REASONS],
|
|
66
|
+
parked: [null],
|
|
67
|
+
suspended: [...SUSPENDED_REASONS],
|
|
68
|
+
paused: [null, ...PAUSED_REASONS],
|
|
69
|
+
completed: [null],
|
|
70
|
+
failed: [...FAILED_REASONS],
|
|
71
|
+
killed: [null, ...KILLED_REASONS],
|
|
72
|
+
expired: [null],
|
|
73
|
+
/** ruling 34.02 (1): a ready row's state_reason is always NULL. */
|
|
74
|
+
ready: [null],
|
|
75
|
+
};
|
|
76
|
+
export const ALL_STATE_REASONS = [
|
|
77
|
+
...DETACHED_REASONS,
|
|
78
|
+
...SUSPENDED_REASONS,
|
|
79
|
+
...PAUSED_REASONS,
|
|
80
|
+
...FAILED_REASONS,
|
|
81
|
+
...KILLED_REASONS,
|
|
82
|
+
];
|
|
83
|
+
/** M1 writers exist for these suspended reasons; the others are fixture-only (P0a-B / P0b writers). */
|
|
84
|
+
export const M1_WRITTEN_SUSPENDED_REASONS = [
|
|
85
|
+
"budget_reached",
|
|
86
|
+
"identity_mismatch",
|
|
87
|
+
"attestation_failed",
|
|
88
|
+
"respawn_failed",
|
|
89
|
+
"grant_expired",
|
|
90
|
+
/** ruling 34.22: written by the approval-wait expiry/denial path. */
|
|
91
|
+
"approval_expired",
|
|
92
|
+
"approval_denied",
|
|
93
|
+
];
|
|
94
|
+
/** M1's three wait reasons, then ruling 34.06's ci, question and budget (appended). */
|
|
95
|
+
export const WAIT_REASONS = [
|
|
96
|
+
"approval",
|
|
97
|
+
"moving",
|
|
98
|
+
"mnemom_unavailable",
|
|
99
|
+
"ci",
|
|
100
|
+
"question",
|
|
101
|
+
"budget",
|
|
102
|
+
];
|
|
103
|
+
/**
|
|
104
|
+
* The wait reasons valid only with state 'parked' (ruling 34.06 (1), agent_sessions_wait_parked_ck).
|
|
105
|
+
* While wait_reason is one of these, every claim answers ineligible `waiting` (34.06 (3)).
|
|
106
|
+
*/
|
|
107
|
+
export const PARKED_ONLY_WAIT_REASONS = [
|
|
108
|
+
"ci",
|
|
109
|
+
"question",
|
|
110
|
+
"budget",
|
|
111
|
+
];
|
|
112
|
+
export function isSessionState(v) {
|
|
113
|
+
return typeof v === "string" && SESSION_STATES.includes(v);
|
|
114
|
+
}
|
|
115
|
+
export function isTerminalState(s) {
|
|
116
|
+
return TERMINAL_STATES.includes(s);
|
|
117
|
+
}
|
|
118
|
+
/** Whether `reason` is allowed with `state` by the closed enum. */
|
|
119
|
+
export function isValidStateReason(state, reason) {
|
|
120
|
+
if (reason !== null && typeof reason !== "string")
|
|
121
|
+
return false;
|
|
122
|
+
return STATE_REASONS[state].includes(reason);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The effective-state vocabulary: the persisted states plus `suspended_grant_expired`, the
|
|
126
|
+
* resolver's value for a lapsed executor grant (persisted or derived). It is the value set of the
|
|
127
|
+
* resolver's `effective_state` field and of the span attribute `durable_effective_state`.
|
|
128
|
+
*/
|
|
129
|
+
export const EFFECTIVE_STATES = [
|
|
130
|
+
"resuming",
|
|
131
|
+
"running",
|
|
132
|
+
"detached",
|
|
133
|
+
"parked",
|
|
134
|
+
"suspended",
|
|
135
|
+
"suspended_grant_expired",
|
|
136
|
+
"paused",
|
|
137
|
+
"completed",
|
|
138
|
+
"failed",
|
|
139
|
+
"killed",
|
|
140
|
+
"expired",
|
|
141
|
+
// ruling 34.02 (5): ready is persisted and never derived from another state, except 34.04's pool arm
|
|
142
|
+
"ready",
|
|
143
|
+
];
|
|
144
|
+
export function isEffectiveState(v) {
|
|
145
|
+
return typeof v === "string" && EFFECTIVE_STATES.includes(v);
|
|
146
|
+
}
|
|
147
|
+
/** The effective state of a persisted (state, state_reason) with no derivation applied. */
|
|
148
|
+
export function persistedEffectiveState(state, reason) {
|
|
149
|
+
return state === "suspended" && reason === "grant_expired" ? "suspended_grant_expired" : state;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The effective state, first match wins (design §3.6 step 4, BACKBONE): expired > grant_expired >
|
|
153
|
+
* pool lease-ready (ruling 34.04 (1): a pool session whose lease lapsed reads `ready`) >
|
|
154
|
+
* lease-detached. Terminal rows never derive. The resolver computes this in SQL with Postgres
|
|
155
|
+
* now(); this is the reference the SQL is parity-tested against. Timestamps are ISO-8601.
|
|
156
|
+
*/
|
|
157
|
+
export function deriveEffectiveState(row, nowMs) {
|
|
158
|
+
if (isTerminalState(row.state))
|
|
159
|
+
return persistedEffectiveState(row.state, row.state_reason);
|
|
160
|
+
if (row.expires_at !== null && Date.parse(row.expires_at) <= nowMs)
|
|
161
|
+
return "expired";
|
|
162
|
+
if (!row.grant_live)
|
|
163
|
+
return "suspended_grant_expired";
|
|
164
|
+
const leaseLapsed = row.lease_expires_at !== null && Date.parse(row.lease_expires_at) < nowMs;
|
|
165
|
+
if ((row.state === "running" || row.state === "resuming") && leaseLapsed)
|
|
166
|
+
return row.pool === true ? "ready" : "detached";
|
|
167
|
+
return persistedEffectiveState(row.state, row.state_reason);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether `effective` is reachable from the persisted row by the derivation above. The admission
|
|
171
|
+
* input validator refuses any other pairing as a schema fault (503). `pool` is the same explicit
|
|
172
|
+
* input as EffectiveStateInput.pool (ruling 34.04.1); false answers exactly as M1.
|
|
173
|
+
*/
|
|
174
|
+
export function isEffectiveStateConsistent(state, reason, effective, pool = false) {
|
|
175
|
+
if (effective === persistedEffectiveState(state, reason))
|
|
176
|
+
return true;
|
|
177
|
+
if (isTerminalState(state))
|
|
178
|
+
return false;
|
|
179
|
+
if (effective === "expired" || effective === "suspended_grant_expired")
|
|
180
|
+
return true;
|
|
181
|
+
// a lapsed lease reads detached for a homed session, ready for a pool session (34.04, 34.04.1)
|
|
182
|
+
return (effective === (pool ? "ready" : "detached") && (state === "running" || state === "resuming"));
|
|
183
|
+
}
|
|
184
|
+
/** User-facing states (design §2, C11-R27), from the effective state. */
|
|
185
|
+
export const USER_STATES = ["running", "waiting", "stopped", "ended"];
|
|
186
|
+
export function userStateOf(effective) {
|
|
187
|
+
switch (effective) {
|
|
188
|
+
case "running":
|
|
189
|
+
case "resuming":
|
|
190
|
+
return "running";
|
|
191
|
+
case "parked":
|
|
192
|
+
case "detached":
|
|
193
|
+
case "ready":
|
|
194
|
+
return "waiting";
|
|
195
|
+
case "paused":
|
|
196
|
+
case "suspended":
|
|
197
|
+
case "suspended_grant_expired":
|
|
198
|
+
return "stopped";
|
|
199
|
+
default:
|
|
200
|
+
return "ended";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** How the user-facing views word a ready session (ruling 34.02 (5), C11-R27). */
|
|
204
|
+
export const READY_USER_TEXT = "waiting for a runner";
|
|
205
|
+
/** The body of every 410 durable_session_ended (gateway and API): expired carries state_reason 'expired' (C11-R11). */
|
|
206
|
+
export function endedBody(effective, reason) {
|
|
207
|
+
switch (effective) {
|
|
208
|
+
case "expired":
|
|
209
|
+
return { state: "expired", state_reason: "expired" };
|
|
210
|
+
case "completed":
|
|
211
|
+
return { state: "completed", state_reason: null };
|
|
212
|
+
case "failed":
|
|
213
|
+
case "killed":
|
|
214
|
+
return { state: effective, state_reason: reason };
|
|
215
|
+
default:
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable telemetry: span names, attribute keys and closed value sets, with the M1 allowlists
|
|
3
|
+
* (C19, C20-R14/R15; rulings r2 I04, I07, r3 I02, L01, L02, final F05). Spans only, never OTLP
|
|
4
|
+
* metrics (repo CLAUDE.md). Attribute keys are snake_case and low-cardinality; no identifier,
|
|
5
|
+
* content or free text ever rides on a durable span.
|
|
6
|
+
*/
|
|
7
|
+
/** The gateway's one admission span per mnm_ds_ request (a dedicated span; C19 OQ). */
|
|
8
|
+
export declare const SPAN_DURABLE_ADMISSION = "durable_admission";
|
|
9
|
+
export declare const SPAN_DURABLE_CLAIM = "durable_claim";
|
|
10
|
+
export declare const SPAN_DURABLE_SEAT_HELLO = "durable_seat_hello";
|
|
11
|
+
export declare const SPAN_DURABLE_SWEEP = "durable_sweep";
|
|
12
|
+
/**
|
|
13
|
+
* The api's one C16 span (C16-R24, rulings I16): under DEVICE_VERIFY_AAL=observe an aal1 login-flow
|
|
14
|
+
* approval that `on` would refuse is allowed and emits exactly one span with this name, status ok,
|
|
15
|
+
* device_verify_would_refuse = "true". Never emitted under off, unset or on, and never for the
|
|
16
|
+
* owner_mutation mode (which always enforces).
|
|
17
|
+
*/
|
|
18
|
+
export declare const SPAN_DEVICE_VERIFY_WOULD_REFUSE = "device_verify_would_refuse";
|
|
19
|
+
/**
|
|
20
|
+
* The api's executor enrollment span (C04-R16; wave-0 fix 3c): one per enrollment step, carrying
|
|
21
|
+
* durable_enrollment_outcome only (a separate key because `outcome` is frozen to the sweep's
|
|
22
|
+
* ok|error|disabled). Status error on every refused_*, denied and expired. Never carries the key,
|
|
23
|
+
* poll secret, user_code or owner credentials (SECRET_SCOPE).
|
|
24
|
+
*/
|
|
25
|
+
export declare const SPAN_DURABLE_ENROLLMENT = "durable_enrollment";
|
|
26
|
+
export declare const DEVICE_VERIFY_AAL_VALUES: readonly ["off", "observe", "on"];
|
|
27
|
+
export declare const DEVICE_VERIFY_AAL_DEFAULT = "off";
|
|
28
|
+
export declare const M1_SPAN_NAMES: readonly ["durable_admission", "durable_claim", "durable_seat_hello", "durable_sweep", "device_verify_would_refuse", "durable_enrollment"];
|
|
29
|
+
/** Forbidden in M1 by prefix (final F05; cut with PR 10). */
|
|
30
|
+
export declare const FORBIDDEN_SPAN_PREFIXES_M1: readonly ["durable_resolve"];
|
|
31
|
+
/** mnm_ds_ requests emit no gateway.auth span; the admission span replaces it (C19 OQ). */
|
|
32
|
+
export declare const GATEWAY_AUTH_FOR_LEASE_KEYS: "not_emitted";
|
|
33
|
+
/** durable_enrollment_outcome values (C04-R16). */
|
|
34
|
+
export declare const ENROLLMENT_OUTCOMES: readonly ["started", "approved", "refused_aal", "refused_stale", "refused_not_self", "refused_machine", "refused_entitlement", "denied", "expired", "consumed", "replay"];
|
|
35
|
+
export type EnrollmentOutcome = (typeof ENROLLMENT_OUTCOMES)[number];
|
|
36
|
+
/** Span status of an enrollment outcome: error on every refused_*, denied and expired. */
|
|
37
|
+
export declare function enrollmentSpanStatus(o: EnrollmentOutcome): "ok" | "error";
|
|
38
|
+
export declare const REFUSAL_NONE = "none";
|
|
39
|
+
export declare const REFUSAL_UNKNOWN_KEY = "unknown_key";
|
|
40
|
+
export declare const ATTRIBUTE_VALUES: {
|
|
41
|
+
readonly durable_refusal: readonly ["none", "unknown_key", "durable_admission_unavailable", "durable_key_revoked", "durable_identity_mismatch", "durable_unlocked", "durable_session_ended", "durable_lease_fenced", "durable_session_stopped", "durable_guard_unavailable", "durable_unattended_unavailable", "goal_contract_enforce_refused", "durable_route_refused", "durable_executor_key_refused", "contract_mismatch"];
|
|
42
|
+
readonly durable_posture: readonly ["attended", "unattended"];
|
|
43
|
+
readonly durable_door: readonly ["match", "mismatch", "unmapped"];
|
|
44
|
+
readonly durable_effective_state: readonly ["resuming", "running", "detached", "parked", "suspended", "suspended_grant_expired", "paused", "completed", "failed", "killed", "expired", "ready", "unknown"];
|
|
45
|
+
/** `cache` is CUT M1 -> P1 (STALE_SERVE_M1=false). */
|
|
46
|
+
readonly durable_fence_source: readonly ["fresh", "nofence"];
|
|
47
|
+
readonly durable_charter_source: readonly ["pg", "rehydrated", "lru", "kv", "unavailable", "none"];
|
|
48
|
+
/**
|
|
49
|
+
* The applyDurableArm site that emitted this span (wave-0 fix ruling 5; C06-R21), on every path:
|
|
50
|
+
* admit, every refusal, resolver-fault 503. Whether the arm header was present is asserted on
|
|
51
|
+
* the response head (C06-R17), not here.
|
|
52
|
+
*/
|
|
53
|
+
readonly durable_arm: readonly ["anchor", "key_gate"];
|
|
54
|
+
readonly durable_never_hold: readonly ["off"];
|
|
55
|
+
readonly durable_guard: readonly ["ok", "unavailable", "not_applicable"];
|
|
56
|
+
readonly durable_claim_outcome: readonly ["acquired", "reclaimed", "epoch_bump", "needs_key", "lost_race", "ineligible", "unavailable", "rate_limited"];
|
|
57
|
+
readonly durable_claim_cause: readonly ["schema", "arm_probe", "entitlement_lookup", "protection_probe", "guard", "lease_cap_config"];
|
|
58
|
+
readonly durable_hello_outcome: readonly ["accepted", "repeat", "late", "refused"];
|
|
59
|
+
readonly outcome: readonly ["ok", "error", "disabled"];
|
|
60
|
+
readonly budget_exhausted: readonly ["true", "false"];
|
|
61
|
+
readonly lag_bucket: readonly ["none", "<1m", "1-5m", "5-30m", ">30m"];
|
|
62
|
+
/** The frozen would-refuse marker: the only value it ever carries. */
|
|
63
|
+
readonly device_verify_would_refuse: readonly ["true"];
|
|
64
|
+
/** Why `on` would refuse: assurance below aal2, or the step older than DEVICE_STEP_FRESHNESS_S. */
|
|
65
|
+
readonly device_verify_reason: readonly ["aal", "freshness"];
|
|
66
|
+
/** The assurance the approval actually carried. */
|
|
67
|
+
readonly device_verify_assurance: readonly ["aal1", "aal2"];
|
|
68
|
+
/** C04-R16, exactly. */
|
|
69
|
+
readonly durable_enrollment_outcome: readonly ["started", "approved", "refused_aal", "refused_stale", "refused_not_self", "refused_machine", "refused_entitlement", "denied", "expired", "consumed", "replay"];
|
|
70
|
+
};
|
|
71
|
+
export type DurableAttributeKey = keyof typeof ATTRIBUTE_VALUES | "test_case";
|
|
72
|
+
/** Allowed keys per span (closure: a key outside its span's list is a failure). */
|
|
73
|
+
export declare const SPAN_ATTRIBUTE_KEYS: {
|
|
74
|
+
readonly [S in (typeof M1_SPAN_NAMES)[number]]: readonly DurableAttributeKey[];
|
|
75
|
+
};
|
|
76
|
+
/** Keys that must never appear on an M1 durable span (r3 L01, L02; I12; U24). */
|
|
77
|
+
export declare const FORBIDDEN_ATTRIBUTE_KEYS_M1: readonly ["durable_degraded", "durable_hold_decision", "durable_host_policy", "durable_org_usage", "reaped", "expired", "lag_ms"];
|
|
78
|
+
export declare const TEST_CASE_RE: RegExp;
|
|
79
|
+
export declare const TEST_CASE_HEADER = "x-mnemom-test-case";
|
|
80
|
+
/** Span status: refusals are error, admits ok (an attended guard fault is admitted, so ok). */
|
|
81
|
+
export declare function spanStatusOf(refusal: string): "ok" | "error";
|
|
82
|
+
/** The sweep's lag bucket for the oldest overdue row's lag (ms); null lag = nothing overdue. */
|
|
83
|
+
export declare function lagBucket(lagMs: number | null): (typeof ATTRIBUTE_VALUES.lag_bucket)[number];
|
|
84
|
+
/** budget_exhausted: rows persisted reached max_rows (an exact multiple counts) or the time budget elapsed. */
|
|
85
|
+
export declare function budgetExhausted(persisted: number, maxRows: number, elapsedMs: number, budgetMs: number): boolean;
|
|
86
|
+
/** The sweep log line (not a span): name and its closed field list, no session ids. */
|
|
87
|
+
export declare const SWEEP_LOG_NAME = "durable_sweep_run";
|
|
88
|
+
export declare const SWEEP_LOG_FIELDS: readonly ["reaped", "expired", "skipped_locked", "lag_ms"];
|
|
89
|
+
export interface AttributeFinding {
|
|
90
|
+
key: string;
|
|
91
|
+
problem: "unknown_span" | "key_not_allowed" | "forbidden_key" | "value_not_allowed";
|
|
92
|
+
}
|
|
93
|
+
/** Closure check over one captured span (C19 allowlist_and_enum_closure reference). */
|
|
94
|
+
export declare function checkDurableSpan(name: string, attrs: Readonly<Record<string, unknown>>, testCaseAllowed: boolean): AttributeFinding[];
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// GENERATED from shared/durable-contract/src/telemetry.ts by cli/scripts/sync-durable-contract.mjs; do not edit.
|
|
2
|
+
/**
|
|
3
|
+
* Durable telemetry: span names, attribute keys and closed value sets, with the M1 allowlists
|
|
4
|
+
* (C19, C20-R14/R15; rulings r2 I04, I07, r3 I02, L01, L02, final F05). Spans only, never OTLP
|
|
5
|
+
* metrics (repo CLAUDE.md). Attribute keys are snake_case and low-cardinality; no identifier,
|
|
6
|
+
* content or free text ever rides on a durable span.
|
|
7
|
+
*/
|
|
8
|
+
import { CLAIM_OUTCOMES, CLAIM_UNAVAILABLE_CAUSES, GATEWAY_REFUSAL_CODES } from "./codes.js";
|
|
9
|
+
import { EFFECTIVE_STATES } from "./states.js";
|
|
10
|
+
import { DOOR_VALUES } from "./cell-id.js";
|
|
11
|
+
/** The gateway's one admission span per mnm_ds_ request (a dedicated span; C19 OQ). */
|
|
12
|
+
export const SPAN_DURABLE_ADMISSION = "durable_admission";
|
|
13
|
+
export const SPAN_DURABLE_CLAIM = "durable_claim";
|
|
14
|
+
export const SPAN_DURABLE_SEAT_HELLO = "durable_seat_hello";
|
|
15
|
+
export const SPAN_DURABLE_SWEEP = "durable_sweep";
|
|
16
|
+
/**
|
|
17
|
+
* The api's one C16 span (C16-R24, rulings I16): under DEVICE_VERIFY_AAL=observe an aal1 login-flow
|
|
18
|
+
* approval that `on` would refuse is allowed and emits exactly one span with this name, status ok,
|
|
19
|
+
* device_verify_would_refuse = "true". Never emitted under off, unset or on, and never for the
|
|
20
|
+
* owner_mutation mode (which always enforces).
|
|
21
|
+
*/
|
|
22
|
+
export const SPAN_DEVICE_VERIFY_WOULD_REFUSE = "device_verify_would_refuse";
|
|
23
|
+
/**
|
|
24
|
+
* The api's executor enrollment span (C04-R16; wave-0 fix 3c): one per enrollment step, carrying
|
|
25
|
+
* durable_enrollment_outcome only (a separate key because `outcome` is frozen to the sweep's
|
|
26
|
+
* ok|error|disabled). Status error on every refused_*, denied and expired. Never carries the key,
|
|
27
|
+
* poll secret, user_code or owner credentials (SECRET_SCOPE).
|
|
28
|
+
*/
|
|
29
|
+
export const SPAN_DURABLE_ENROLLMENT = "durable_enrollment";
|
|
30
|
+
export const DEVICE_VERIFY_AAL_VALUES = ["off", "observe", "on"];
|
|
31
|
+
export const DEVICE_VERIFY_AAL_DEFAULT = "off";
|
|
32
|
+
export const M1_SPAN_NAMES = [
|
|
33
|
+
SPAN_DURABLE_ADMISSION,
|
|
34
|
+
SPAN_DURABLE_CLAIM,
|
|
35
|
+
SPAN_DURABLE_SEAT_HELLO,
|
|
36
|
+
SPAN_DURABLE_SWEEP,
|
|
37
|
+
SPAN_DEVICE_VERIFY_WOULD_REFUSE,
|
|
38
|
+
SPAN_DURABLE_ENROLLMENT,
|
|
39
|
+
];
|
|
40
|
+
/** Forbidden in M1 by prefix (final F05; cut with PR 10). */
|
|
41
|
+
export const FORBIDDEN_SPAN_PREFIXES_M1 = ["durable_resolve"];
|
|
42
|
+
/** mnm_ds_ requests emit no gateway.auth span; the admission span replaces it (C19 OQ). */
|
|
43
|
+
export const GATEWAY_AUTH_FOR_LEASE_KEYS = "not_emitted";
|
|
44
|
+
/** durable_enrollment_outcome values (C04-R16). */
|
|
45
|
+
export const ENROLLMENT_OUTCOMES = [
|
|
46
|
+
"started",
|
|
47
|
+
"approved",
|
|
48
|
+
"refused_aal",
|
|
49
|
+
"refused_stale",
|
|
50
|
+
"refused_not_self",
|
|
51
|
+
"refused_machine",
|
|
52
|
+
"refused_entitlement",
|
|
53
|
+
"denied",
|
|
54
|
+
"expired",
|
|
55
|
+
"consumed",
|
|
56
|
+
"replay",
|
|
57
|
+
];
|
|
58
|
+
/** Span status of an enrollment outcome: error on every refused_*, denied and expired. */
|
|
59
|
+
export function enrollmentSpanStatus(o) {
|
|
60
|
+
return o.startsWith("refused_") || o === "denied" || o === "expired" ? "error" : "ok";
|
|
61
|
+
}
|
|
62
|
+
export const REFUSAL_NONE = "none";
|
|
63
|
+
export const REFUSAL_UNKNOWN_KEY = "unknown_key";
|
|
64
|
+
export const ATTRIBUTE_VALUES = {
|
|
65
|
+
durable_refusal: [REFUSAL_NONE, REFUSAL_UNKNOWN_KEY, ...GATEWAY_REFUSAL_CODES],
|
|
66
|
+
durable_posture: ["attended", "unattended"],
|
|
67
|
+
durable_door: DOOR_VALUES,
|
|
68
|
+
durable_effective_state: [...EFFECTIVE_STATES, "unknown"],
|
|
69
|
+
/** `cache` is CUT M1 -> P1 (STALE_SERVE_M1=false). */
|
|
70
|
+
durable_fence_source: ["fresh", "nofence"],
|
|
71
|
+
durable_charter_source: ["pg", "rehydrated", "lru", "kv", "unavailable", "none"],
|
|
72
|
+
/**
|
|
73
|
+
* The applyDurableArm site that emitted this span (wave-0 fix ruling 5; C06-R21), on every path:
|
|
74
|
+
* admit, every refusal, resolver-fault 503. Whether the arm header was present is asserted on
|
|
75
|
+
* the response head (C06-R17), not here.
|
|
76
|
+
*/
|
|
77
|
+
durable_arm: ["anchor", "key_gate"],
|
|
78
|
+
durable_never_hold: ["off"],
|
|
79
|
+
durable_guard: ["ok", "unavailable", "not_applicable"],
|
|
80
|
+
durable_claim_outcome: CLAIM_OUTCOMES,
|
|
81
|
+
durable_claim_cause: CLAIM_UNAVAILABLE_CAUSES,
|
|
82
|
+
durable_hello_outcome: ["accepted", "repeat", "late", "refused"],
|
|
83
|
+
outcome: ["ok", "error", "disabled"],
|
|
84
|
+
budget_exhausted: ["true", "false"],
|
|
85
|
+
lag_bucket: ["none", "<1m", "1-5m", "5-30m", ">30m"],
|
|
86
|
+
/** The frozen would-refuse marker: the only value it ever carries. */
|
|
87
|
+
device_verify_would_refuse: ["true"],
|
|
88
|
+
/** Why `on` would refuse: assurance below aal2, or the step older than DEVICE_STEP_FRESHNESS_S. */
|
|
89
|
+
device_verify_reason: ["aal", "freshness"],
|
|
90
|
+
/** The assurance the approval actually carried. */
|
|
91
|
+
device_verify_assurance: ["aal1", "aal2"],
|
|
92
|
+
/** C04-R16, exactly. */
|
|
93
|
+
durable_enrollment_outcome: ENROLLMENT_OUTCOMES,
|
|
94
|
+
};
|
|
95
|
+
/** Allowed keys per span (closure: a key outside its span's list is a failure). */
|
|
96
|
+
export const SPAN_ATTRIBUTE_KEYS = {
|
|
97
|
+
durable_admission: [
|
|
98
|
+
"durable_refusal",
|
|
99
|
+
"durable_posture",
|
|
100
|
+
"durable_door",
|
|
101
|
+
"durable_effective_state",
|
|
102
|
+
"durable_fence_source",
|
|
103
|
+
"durable_charter_source",
|
|
104
|
+
"durable_arm",
|
|
105
|
+
"durable_never_hold",
|
|
106
|
+
"durable_guard",
|
|
107
|
+
"test_case",
|
|
108
|
+
],
|
|
109
|
+
durable_claim: ["durable_claim_outcome", "durable_claim_cause", "test_case"],
|
|
110
|
+
durable_seat_hello: ["durable_hello_outcome", "test_case"],
|
|
111
|
+
durable_sweep: ["outcome", "budget_exhausted", "lag_bucket", "test_case"],
|
|
112
|
+
device_verify_would_refuse: [
|
|
113
|
+
"device_verify_would_refuse",
|
|
114
|
+
"device_verify_reason",
|
|
115
|
+
"device_verify_assurance",
|
|
116
|
+
"test_case",
|
|
117
|
+
],
|
|
118
|
+
durable_enrollment: ["durable_enrollment_outcome", "test_case"],
|
|
119
|
+
};
|
|
120
|
+
/** Keys that must never appear on an M1 durable span (r3 L01, L02; I12; U24). */
|
|
121
|
+
export const FORBIDDEN_ATTRIBUTE_KEYS_M1 = [
|
|
122
|
+
"durable_degraded",
|
|
123
|
+
"durable_hold_decision",
|
|
124
|
+
"durable_host_policy",
|
|
125
|
+
"durable_org_usage",
|
|
126
|
+
"reaped",
|
|
127
|
+
"expired",
|
|
128
|
+
"lag_ms",
|
|
129
|
+
];
|
|
130
|
+
export const TEST_CASE_RE = /^[a-z0-9-]{1,40}$/;
|
|
131
|
+
export const TEST_CASE_HEADER = "x-mnemom-test-case";
|
|
132
|
+
/** Span status: refusals are error, admits ok (an attended guard fault is admitted, so ok). */
|
|
133
|
+
export function spanStatusOf(refusal) {
|
|
134
|
+
return refusal === REFUSAL_NONE ? "ok" : "error";
|
|
135
|
+
}
|
|
136
|
+
/** The sweep's lag bucket for the oldest overdue row's lag (ms); null lag = nothing overdue. */
|
|
137
|
+
export function lagBucket(lagMs) {
|
|
138
|
+
if (lagMs === null || lagMs <= 0)
|
|
139
|
+
return "none";
|
|
140
|
+
if (lagMs < 60_000)
|
|
141
|
+
return "<1m";
|
|
142
|
+
if (lagMs < 300_000)
|
|
143
|
+
return "1-5m";
|
|
144
|
+
if (lagMs < 1_800_000)
|
|
145
|
+
return "5-30m";
|
|
146
|
+
return ">30m";
|
|
147
|
+
}
|
|
148
|
+
/** budget_exhausted: rows persisted reached max_rows (an exact multiple counts) or the time budget elapsed. */
|
|
149
|
+
export function budgetExhausted(persisted, maxRows, elapsedMs, budgetMs) {
|
|
150
|
+
return persisted >= maxRows || elapsedMs >= budgetMs;
|
|
151
|
+
}
|
|
152
|
+
/** The sweep log line (not a span): name and its closed field list, no session ids. */
|
|
153
|
+
export const SWEEP_LOG_NAME = "durable_sweep_run";
|
|
154
|
+
export const SWEEP_LOG_FIELDS = ["reaped", "expired", "skipped_locked", "lag_ms"];
|
|
155
|
+
/** Closure check over one captured span (C19 allowlist_and_enum_closure reference). */
|
|
156
|
+
export function checkDurableSpan(name, attrs, testCaseAllowed) {
|
|
157
|
+
if (!M1_SPAN_NAMES.includes(name))
|
|
158
|
+
return [{ key: name, problem: "unknown_span" }];
|
|
159
|
+
const allowed = SPAN_ATTRIBUTE_KEYS[name];
|
|
160
|
+
const out = [];
|
|
161
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
162
|
+
if (FORBIDDEN_ATTRIBUTE_KEYS_M1.includes(key)) {
|
|
163
|
+
out.push({ key, problem: "forbidden_key" });
|
|
164
|
+
}
|
|
165
|
+
else if (!allowed.includes(key) ||
|
|
166
|
+
(key === "test_case" && !testCaseAllowed)) {
|
|
167
|
+
out.push({ key, problem: "key_not_allowed" });
|
|
168
|
+
}
|
|
169
|
+
else if (key === "test_case") {
|
|
170
|
+
if (typeof value !== "string" || !TEST_CASE_RE.test(value))
|
|
171
|
+
out.push({ key, problem: "value_not_allowed" });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const values = ATTRIBUTE_VALUES[key];
|
|
175
|
+
if (!values.includes(String(value)))
|
|
176
|
+
out.push({ key, problem: "value_not_allowed" });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed TEST_MODE seam list T1-T12 and the per-repo guard lists (C17-R16, rulings I06, I22,
|
|
3
|
+
* I29; BACKBONE "TEST_MODE"). A seam is honoured only when DURABLE_SESSIONS_TEST_MODE is exactly
|
|
4
|
+
* "1" AND every URL on the repo's guard list is a loopback literal; an unset URL fails. Each repo
|
|
5
|
+
* reads the env in exactly one module (TEST_MODE_MODULE).
|
|
6
|
+
*/
|
|
7
|
+
export declare const TEST_MODE_ENV = "DURABLE_SESSIONS_TEST_MODE";
|
|
8
|
+
export declare const REPOS: readonly ["cli", "api", "gateway"];
|
|
9
|
+
export type Repo = (typeof REPOS)[number];
|
|
10
|
+
export interface Seam {
|
|
11
|
+
id: string;
|
|
12
|
+
repo: Repo | "api+gateway";
|
|
13
|
+
kind: "env" | "route" | "header" | "body";
|
|
14
|
+
names: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export declare const TEST_SEAMS: readonly Seam[];
|
|
17
|
+
export declare const T2_PAUSE_POINTS: readonly ["after_claim_before_spawn", "after_spawn_before_register", "after_register_before_hello", "after_first_batch", "after_verify_before_exec"];
|
|
18
|
+
export declare const T5_BARRIERS: readonly ["lock.after_preconditions", "lock.before_commit", "session_create.before_insert", "session_key_mint.before_insert"];
|
|
19
|
+
export declare const T6_FAULTS: readonly ["drop_response_after_commit"];
|
|
20
|
+
export declare const T9_SWEEP_FAULTS: readonly ["after_update"];
|
|
21
|
+
/** Dropped seams: must be absent from every repo (C17-R16). */
|
|
22
|
+
export declare const DROPPED_SEAMS: readonly ["DURABLE_CLAUDE_BIN", "DURABLE_SPAWN_PAUSE_AFTER_VERIFY_MS", "DURABLE_SWEEP_BUDGET", "/internal/durable/test/lru-flush", "durable_test_barrier", "test_barriers_enabled"];
|
|
23
|
+
export declare const GUARD_LISTS: {
|
|
24
|
+
readonly [R in Repo]: readonly string[];
|
|
25
|
+
};
|
|
26
|
+
/** The one module per repo that reads TEST_MODE and holds every seam getter (repo-relative). */
|
|
27
|
+
export declare const TEST_MODE_MODULE: {
|
|
28
|
+
readonly [R in Repo]: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function seamsOf(repo: Repo): string[];
|
|
31
|
+
export declare const LOOPBACK_HOSTS: readonly ["127.0.0.1", "[::1]", "localhost"];
|
|
32
|
+
/** A URL whose host is exactly a loopback literal (look-alikes such as 127.0.0.1.nip.io fail). */
|
|
33
|
+
export declare function isLoopbackUrl(value: string | undefined): boolean;
|
|
34
|
+
/** The seam guard: TEST_MODE exactly "1" and every guard URL a loopback literal. */
|
|
35
|
+
export declare function testModeActive(repo: Repo, env: Readonly<Record<string, string | undefined>>): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The C17-R18 integrity allowlist (ruling 31(2)): the pre-existing tests and generated snapshots
|
|
38
|
+
* that M1 may legitimately change, so the test_integrity tool does not count their edits as a
|
|
39
|
+
* deletion, skip or weakening. Closed to R18's three kinds. Each entry is a literal repo-relative
|
|
40
|
+
* path (the narrowest glob): spec coverage is the api's spec-coverage gates, route inventory is
|
|
41
|
+
* `npm run inventory:check` (the extractor's tests and its exempt lists), openapi snapshots are
|
|
42
|
+
* the two files `npm run openapi:check` diffs. Repos use the vitest_suite enum; gateway, cli and
|
|
43
|
+
* the api postgrest suite hold no file of these kinds.
|
|
44
|
+
*/
|
|
45
|
+
export declare const TEST_ALLOWLIST_KINDS: readonly ["spec_coverage", "route_inventory", "openapi_snapshot"];
|
|
46
|
+
export type TestAllowlistKind = (typeof TEST_ALLOWLIST_KINDS)[number];
|
|
47
|
+
export declare const TEST_ALLOWLIST_REPOS: readonly ["gateway", "api", "api_postgrest", "cli", "durable_contract"];
|
|
48
|
+
export type TestAllowlistRepo = (typeof TEST_ALLOWLIST_REPOS)[number];
|
|
49
|
+
export interface TestAllowlistEntry {
|
|
50
|
+
readonly repo: TestAllowlistRepo;
|
|
51
|
+
readonly kind: TestAllowlistKind;
|
|
52
|
+
readonly path: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* test_integrity baseline runs (ruling 31.1): the merge-base suites run this many times to record
|
|
56
|
+
* flaky tests (passes and fails across runs) and baseline_failing ones (fails every run); also the
|
|
57
|
+
* most attempts a test recorded flaky gets in the candidate run.
|
|
58
|
+
*/
|
|
59
|
+
export declare const TEST_FLAKY_RUNS = 3;
|
|
60
|
+
export declare const TEST_ALLOWLIST: readonly TestAllowlistEntry[];
|
|
61
|
+
/** Whether `path` (repo-relative, exact) is on TEST_ALLOWLIST for `repo`. */
|
|
62
|
+
export declare function isTestAllowlisted(repo: TestAllowlistRepo, path: string): boolean;
|