@opengeni/core 2.4.0-canary.2 → 2.5.2-canary.0

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.
@@ -0,0 +1,113 @@
1
+ import type { ManagedAuthSessionSetMode, ManagedAuthSessionSetProjection } from "@opengeni/contracts/managed-auth-session-sets";
2
+ import { type Database, type ManagedAuthDatabaseProjection, type ManagedAuthSelectedSession } from "@opengeni/db";
3
+ export declare const MANAGED_AUTH_SESSION_SET_COOKIE: "opengeni.session_set";
4
+ export declare const MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE: "opengeni.login_transaction";
5
+ export declare const MANAGED_AUTH_CSRF_HEADER: "x-opengeni-session-csrf";
6
+ export declare const MANAGED_AUTH_ACTOR_EPOCH_HEADER: "x-opengeni-actor-epoch";
7
+ export declare const MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE_PATH: "/v1/auth/session-set/transactions";
8
+ export type ManagedAuthResolvedSession = {
9
+ session: {
10
+ id: string;
11
+ userId: string;
12
+ [key: string]: unknown;
13
+ };
14
+ user: {
15
+ id: string;
16
+ email: string;
17
+ name: string;
18
+ emailVerified: boolean;
19
+ [key: string]: unknown;
20
+ };
21
+ };
22
+ /** Provider-neutral boundary; provider credentials and tokens never cross its output. */
23
+ export interface ManagedAuthSessionAdapter {
24
+ authenticate(input: {
25
+ provider: "email_password";
26
+ transactionId: string;
27
+ credentials: {
28
+ email: string;
29
+ password: string;
30
+ };
31
+ headers: Headers;
32
+ }): Promise<{
33
+ authSessionId: string;
34
+ }>;
35
+ /** Verify an ambient provider cookie without sliding expiry or emitting cookies. */
36
+ resolveAmbientSession(headers: Headers): Promise<ManagedAuthResolvedSession | null>;
37
+ resolveSelectedSession(input: ManagedAuthSelectedSession): Promise<ManagedAuthResolvedSession | null>;
38
+ refreshSelectedSession(input: ManagedAuthSelectedSession): Promise<ManagedAuthResolvedSession | null>;
39
+ revokeSession(input: {
40
+ authSessionId: string;
41
+ }): Promise<void>;
42
+ /** Dual-mode exact selected-session cookie plus stale provider-cache invalidations. */
43
+ createLegacySelectedSessionCookies(input: ManagedAuthSelectedSession | null, currentCookieHeader?: string | null): Promise<string[]>;
44
+ }
45
+ export declare class ManagedAuthActorChangeError extends Error {
46
+ readonly name = "ManagedAuthActorChangeError";
47
+ readonly code = "actor_change_required";
48
+ constructor();
49
+ }
50
+ export declare class ManagedAuthRequestAdmissionError extends Error {
51
+ readonly name = "ManagedAuthRequestAdmissionError";
52
+ readonly code = "origin_rejected";
53
+ }
54
+ export declare class ManagedAuthCompletionOutcomeUnknownError extends Error {
55
+ readonly name = "ManagedAuthCompletionOutcomeUnknownError";
56
+ readonly code = "operation_outcome_unknown";
57
+ constructor(options?: ErrorOptions);
58
+ }
59
+ export declare function requireManagedAuthActorFence(input: {
60
+ mode: ManagedAuthSessionSetMode;
61
+ actorEpoch: string;
62
+ expectedActorEpoch: string | null;
63
+ selectedAuthSessionId: string | null;
64
+ legacyAmbientSessionId?: string | null;
65
+ }): void;
66
+ export declare function managedAuthRandomAuthority(): string;
67
+ export declare function managedAuthSha256(value: string): string;
68
+ export declare function managedAuthCsrfHash(authority: string): string;
69
+ export declare function managedAuthCsrfToken(signingSecret: string, authority: string, generation: string): string;
70
+ export declare function managedAuthTransactionSecret(signingSecret: string, authority: string, operationId: string): string;
71
+ export declare function managedAuthDerivedUuid(namespace: string, value: string): string;
72
+ export declare function managedAuthRequestDigest(value: unknown): string;
73
+ export declare function managedAuthSecretRequestDigest(signingSecret: string, value: unknown): string;
74
+ export declare function withManagedAuthCsrfToken(projection: ManagedAuthDatabaseProjection, signingSecret: string, authority: string): ManagedAuthSessionSetProjection;
75
+ export declare function requireManagedAuthMutationAdmission(input: {
76
+ request: Request;
77
+ allowedOrigins: readonly string[];
78
+ authority: string;
79
+ signingSecret: string;
80
+ expectedGeneration: string;
81
+ }): void;
82
+ export declare function resolveManagedAuthSelectedSession(input: {
83
+ db: Database;
84
+ adapter: ManagedAuthSessionAdapter;
85
+ authority: string;
86
+ mode: ManagedAuthSessionSetMode;
87
+ expectedActorEpoch: string | null;
88
+ legacyAmbientSessionId?: string | null;
89
+ allowRecovery?: boolean;
90
+ }): Promise<{
91
+ session: ManagedAuthResolvedSession | null;
92
+ projection: ManagedAuthDatabaseProjection;
93
+ } | null>;
94
+ export declare function authenticateAndAdoptManagedAuthSession(input: {
95
+ db: Database;
96
+ adapter: ManagedAuthSessionAdapter;
97
+ isolatedHeaders: Headers;
98
+ authority: string;
99
+ csrfHash: string;
100
+ operationId: string;
101
+ requestDigest: string;
102
+ expectedGeneration: string;
103
+ expectedActorEpoch: string;
104
+ transactionId: string;
105
+ transactionSecret: string;
106
+ email: string;
107
+ password: string;
108
+ mode: ManagedAuthSessionSetMode;
109
+ }): Promise<{
110
+ projection: ManagedAuthDatabaseProjection;
111
+ returnIntent: string | null;
112
+ }>;
113
+ export declare function isolatedManagedAuthHeaders(request: Request): Headers;
@@ -0,0 +1,49 @@
1
+ import {
2
+ MANAGED_AUTH_ACTOR_EPOCH_HEADER,
3
+ MANAGED_AUTH_CSRF_HEADER,
4
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE,
5
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE_PATH,
6
+ MANAGED_AUTH_SESSION_SET_COOKIE,
7
+ ManagedAuthActorChangeError,
8
+ ManagedAuthCompletionOutcomeUnknownError,
9
+ ManagedAuthRequestAdmissionError,
10
+ authenticateAndAdoptManagedAuthSession,
11
+ isolatedManagedAuthHeaders,
12
+ managedAuthCsrfHash,
13
+ managedAuthCsrfToken,
14
+ managedAuthDerivedUuid,
15
+ managedAuthRandomAuthority,
16
+ managedAuthRequestDigest,
17
+ managedAuthSecretRequestDigest,
18
+ managedAuthSha256,
19
+ managedAuthTransactionSecret,
20
+ requireManagedAuthActorFence,
21
+ requireManagedAuthMutationAdmission,
22
+ resolveManagedAuthSelectedSession,
23
+ withManagedAuthCsrfToken
24
+ } from "./chunk-YGOMUGYS.js";
25
+ export {
26
+ MANAGED_AUTH_ACTOR_EPOCH_HEADER,
27
+ MANAGED_AUTH_CSRF_HEADER,
28
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE,
29
+ MANAGED_AUTH_LOGIN_TRANSACTION_COOKIE_PATH,
30
+ MANAGED_AUTH_SESSION_SET_COOKIE,
31
+ ManagedAuthActorChangeError,
32
+ ManagedAuthCompletionOutcomeUnknownError,
33
+ ManagedAuthRequestAdmissionError,
34
+ authenticateAndAdoptManagedAuthSession,
35
+ isolatedManagedAuthHeaders,
36
+ managedAuthCsrfHash,
37
+ managedAuthCsrfToken,
38
+ managedAuthDerivedUuid,
39
+ managedAuthRandomAuthority,
40
+ managedAuthRequestDigest,
41
+ managedAuthSecretRequestDigest,
42
+ managedAuthSha256,
43
+ managedAuthTransactionSecret,
44
+ requireManagedAuthActorFence,
45
+ requireManagedAuthMutationAdmission,
46
+ resolveManagedAuthSelectedSession,
47
+ withManagedAuthCsrfToken
48
+ };
49
+ //# sourceMappingURL=managed-auth-session-sets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,6 +1,14 @@
1
1
  import type { Context } from "hono";
2
2
  import type { ManagedAuth } from "./managed-auth-type.js";
3
3
  import type { Database } from "@opengeni/db";
4
+ import type { ManagedAuthSessionSetMode } from "@opengeni/contracts/managed-auth-session-sets";
5
+ import { type ManagedAuthSessionAdapter } from "./managed-auth-session-sets.js";
6
+ type ActorMutationLeaseRuntime = {
7
+ monotonicNow: () => number;
8
+ schedule: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
9
+ cancel: (timer: ReturnType<typeof setTimeout>) => void;
10
+ terminate: () => void;
11
+ };
4
12
  /**
5
13
  * Read a Better Auth session without bypassing its sliding-cookie renewal.
6
14
  *
@@ -9,9 +17,11 @@ import type { Database } from "@opengeni/db";
9
17
  * headers; the HTTP handler does this automatically, but direct API calls do not.
10
18
  */
11
19
  export declare function getManagedSession(c: Context, auth: ManagedAuth, options?: {
12
- db?: Database;
13
- allowIdentityRecovery?: boolean;
14
- }): Promise<{
20
+ db?: Database | undefined;
21
+ allowIdentityRecovery?: boolean | undefined;
22
+ sessionSetMode?: ManagedAuthSessionSetMode | undefined;
23
+ sessionAdapter?: ManagedAuthSessionAdapter | null | undefined;
24
+ }): Promise<import("./managed-auth-session-sets.js").ManagedAuthResolvedSession | {
15
25
  session: {
16
26
  id: string;
17
27
  createdAt: Date;
@@ -55,3 +65,37 @@ export declare function getManagedSession(c: Context, auth: ManagedAuth, options
55
65
  image?: string | null | undefined;
56
66
  };
57
67
  } | null>;
68
+ /** Safe response provenance for a request authenticated through session-set authority. */
69
+ export declare function getManagedAuthRequestActorEpoch(request: Request): string | null;
70
+ /** Release the multi-replica actor fence after the outer HTTP handler settles. */
71
+ export declare function releaseManagedAuthRequestActorLease(request: Request): Promise<void>;
72
+ /** Cooperative cancellation signal for actor-scoped provider/external I/O. */
73
+ export declare function getManagedAuthRequestActorAbortSignal(request: Request): AbortSignal | null;
74
+ /** @internal Deterministic lease-clock seam used only by direct lifecycle tests. */
75
+ export declare function installManagedAuthActorLeaseRuntimeForTest(overrides: Partial<ActorMutationLeaseRuntime>): () => void;
76
+ /**
77
+ * Exact post-handler fence. A finite unsafe response is not released unless
78
+ * its request-owned lease is still live at the same actor epoch.
79
+ */
80
+ export declare function validateManagedAuthRequestActorLease(request: Request): Promise<void>;
81
+ export declare class ManagedAuthActorLeaseOutcomeUnknownError extends Error {
82
+ readonly name = "ManagedAuthActorLeaseOutcomeUnknownError";
83
+ readonly code = "operation_outcome_unknown";
84
+ constructor(options?: ErrorOptions);
85
+ }
86
+ /** A known-applied same-request canonical transition intentionally consumes its lease. */
87
+ export declare function markManagedAuthRequestActorTransitionApplied(request: Request): void;
88
+ export type ManagedAuthActorMutationLeaseStamp = {
89
+ authorityHash: string;
90
+ actorEpoch: string;
91
+ requestId: string;
92
+ };
93
+ export type ManagedAuthActorAdmissionStamp = {
94
+ authorityHash: string;
95
+ actorEpoch: string;
96
+ };
97
+ /** Verified server-owned actor evidence available to both reads and mutations. */
98
+ export declare function getManagedAuthRequestActorAdmissionStamp(request: Request): ManagedAuthActorAdmissionStamp | null;
99
+ /** Exact request-owned fence passed into a same-transaction actor transition. */
100
+ export declare function getManagedAuthRequestActorLeaseStamp(request: Request): ManagedAuthActorMutationLeaseStamp | null;
101
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "2.4.0-canary.2",
3
+ "version": "2.5.2-canary.0",
4
4
  "description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -34,6 +34,10 @@
34
34
  "types": "./dist/canonical-human-identities.d.ts",
35
35
  "import": "./dist/canonical-human-identities.js"
36
36
  },
37
+ "./managed-auth-session-sets": {
38
+ "types": "./dist/managed-auth-session-sets.d.ts",
39
+ "import": "./dist/managed-auth-session-sets.js"
40
+ },
37
41
  "./connection-authority": {
38
42
  "types": "./dist/domain/connection-authority.d.ts",
39
43
  "import": "./dist/domain/connection-authority.js"
@@ -50,15 +54,15 @@
50
54
  },
51
55
  "dependencies": {
52
56
  "@modelcontextprotocol/sdk": "^1.29.0",
53
- "@opengeni/codex": "^0.2.19-canary.3",
54
- "@opengeni/config": "^0.20.1-canary.2",
55
- "@opengeni/contracts": "^2.5.0-canary.2",
56
- "@opengeni/db": "^3.4.0-canary.2",
57
- "@opengeni/documents": "^0.8.2-canary.2",
58
- "@opengeni/events": "^0.4.0-canary.2",
59
- "@opengeni/observability": "^0.8.6-canary.2",
60
- "@opengeni/runtime": "^1.4.0-canary.2",
61
- "@opengeni/storage": "^0.2.108-canary.2",
57
+ "@opengeni/codex": "^0.2.19-canary.4",
58
+ "@opengeni/config": "^0.21.0-canary.0",
59
+ "@opengeni/contracts": "^2.6.0-canary.0",
60
+ "@opengeni/db": "^3.5.2-canary.0",
61
+ "@opengeni/documents": "^0.8.5-canary.0",
62
+ "@opengeni/events": "^0.4.3-canary.0",
63
+ "@opengeni/observability": "^0.8.8-canary.0",
64
+ "@opengeni/runtime": "^1.4.1-canary.0",
65
+ "@opengeni/storage": "^0.2.109-canary.0",
62
66
  "hono": "^4.12.18",
63
67
  "zod": "^4.2.1"
64
68
  },
@@ -18,6 +18,7 @@ import type { Context } from "hono";
18
18
  import { HTTPException } from "hono/http-exception";
19
19
  import type { ManagedAuth } from "../managed-auth-type";
20
20
  import { getManagedSession } from "../managed-session";
21
+ import type { ManagedAuthSessionAdapter } from "../managed-auth-session-sets";
21
22
 
22
23
  const bearerPrefix = "Bearer ";
23
24
  const accessContextByRequest = new WeakMap<Request, Promise<AccessContext | null>>();
@@ -61,6 +62,7 @@ export type AccessDeps = {
61
62
  db: Database;
62
63
  settings: Settings;
63
64
  managedAuth?: ManagedAuth | null;
65
+ managedAuthSessionAdapter?: ManagedAuthSessionAdapter | null;
64
66
  };
65
67
 
66
68
  export async function requireAccessContext(c: Context, deps: AccessDeps): Promise<AccessContext> {
@@ -377,7 +379,11 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
377
379
  }
378
380
 
379
381
  if (deps.managedAuth) {
380
- const session = await getManagedSession(c, deps.managedAuth, { db: deps.db });
382
+ const session = await getManagedSession(c, deps.managedAuth, {
383
+ db: deps.db,
384
+ sessionSetMode: deps.settings.managedAuthSessionSetMode,
385
+ sessionAdapter: deps.managedAuthSessionAdapter,
386
+ });
381
387
  if (session?.user) {
382
388
  // THE canonical managed-cookie (Better Auth) branch, and the only place
383
389
  // that may stamp a context as such. Every `return` above this point leaves
@@ -389,6 +395,7 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
389
395
  name: session.user.name,
390
396
  emailVerified: session.user.emailVerified,
391
397
  provisionFallbackOrganization: false,
398
+ bindPendingInvitations: false,
392
399
  });
393
400
  canonicalManagedCookieContexts.add(context);
394
401
  return context;
@@ -0,0 +1,76 @@
1
+ import type {
2
+ AccessGrant,
3
+ ResourceRef,
4
+ SubmitComposerDraftRequest,
5
+ SubmitComposerDraftResponse,
6
+ } from "@opengeni/contracts";
7
+ import type { AccessGrantAuthorization } from "../access";
8
+ import type { AcceptSessionUserMessageDependencies } from "../dependencies";
9
+ import { acceptSessionUserMessageWithOutcome } from "../domain/sessions";
10
+
11
+ export type SubmitComposerDraftForRequestOptions = {
12
+ authorization?: AccessGrantAuthorization | undefined;
13
+ /**
14
+ * Trusted host-owned resources admitted with this command without writing
15
+ * them into the actor's browser-visible draft first. The saved draft remains
16
+ * the exact content fence for actor-owned text, annotations, resources, and
17
+ * execution policy; these resources participate in the accepted command's
18
+ * idempotency hash, validation, turn, and durable session resource set.
19
+ */
20
+ additionalResources?: ResourceRef[] | undefined;
21
+ };
22
+
23
+ /**
24
+ * Atomically accept one established-session composer draft.
25
+ *
26
+ * This is the application boundary shared by the stock HTTP adapter and an
27
+ * in-process embedding host. A host may prepare its own durable business state
28
+ * before calling this function and project the returned receipt afterward, but
29
+ * OpenGeni remains the sole authority for draft validation/rotation, event
30
+ * append, turn creation, routing, and idempotent replay.
31
+ */
32
+ export async function submitComposerDraftForRequest(
33
+ deps: AcceptSessionUserMessageDependencies,
34
+ grant: AccessGrant,
35
+ workspaceId: string,
36
+ sessionId: string,
37
+ input: SubmitComposerDraftRequest,
38
+ options: SubmitComposerDraftForRequestOptions = {},
39
+ ): Promise<SubmitComposerDraftResponse> {
40
+ const additionalResources = options.additionalResources ?? [];
41
+ const result = await acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, {
42
+ text: input.text,
43
+ annotations: input.annotations,
44
+ modelContext: input.modelContext ?? null,
45
+ resources: [...input.resources, ...additionalResources],
46
+ ...(additionalResources.length > 0 ? { composerDraftResources: input.resources } : {}),
47
+ model: input.model,
48
+ reasoningEffort: input.reasoningEffort,
49
+ latencyMode: input.latencyMode,
50
+ mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
51
+ connectionAuthorities: input.connectionAuthorities,
52
+ ...(input.personalResourceAttachment
53
+ ? { personalResourceAttachment: input.personalResourceAttachment }
54
+ : {}),
55
+ ...(options.authorization ? { authorization: options.authorization } : {}),
56
+ delivery: input.delivery,
57
+ origin: "human",
58
+ expectedDraftRevision: input.expectedDraftRevision,
59
+ clientEventId: input.clientEventId,
60
+ ...(input.controlEtag ? { controlEtag: input.controlEtag } : {}),
61
+ });
62
+
63
+ if (!result.draft) {
64
+ throw new Error("Accepted composer draft submission did not return its next draft");
65
+ }
66
+
67
+ return {
68
+ accepted: result.accepted,
69
+ turn: result.turn,
70
+ draft: result.draft,
71
+ receipt: result.receipt,
72
+ routing: result.routing,
73
+ interruptionCount: result.interruptionCount,
74
+ replay: result.replay,
75
+ };
76
+ }
@@ -511,26 +511,38 @@ export async function controlAgentSessionWorkstream(
511
511
  },
512
512
  );
513
513
  scheduleSessionCommandPostCommit(deps, "agent_control", [
514
- {
515
- kind: "session_event_fanout",
516
- run: async () =>
517
- await publishSessionEventIds(deps, context.workspaceId, input.targetSessionId, [
518
- result.sessionControlEventId,
519
- ]),
520
- },
521
- {
522
- kind: "workspace_control_fanout",
523
- run: async () =>
524
- await publishWorkspaceControlEvent(
525
- deps,
526
- context.workspaceId,
527
- result.workspaceControlEventId,
528
- ),
529
- },
530
- {
531
- kind: "workflow_wake",
532
- run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
533
- },
514
+ ...(result.sessionControlEventId
515
+ ? [
516
+ {
517
+ kind: "session_event_fanout" as const,
518
+ run: async () =>
519
+ await publishSessionEventIds(deps, context.workspaceId, input.targetSessionId, [
520
+ result.sessionControlEventId!,
521
+ ]),
522
+ },
523
+ ]
524
+ : []),
525
+ ...(result.workspaceControlEventId
526
+ ? [
527
+ {
528
+ kind: "workspace_control_fanout" as const,
529
+ run: async () =>
530
+ await publishWorkspaceControlEvent(
531
+ deps,
532
+ context.workspaceId,
533
+ result.workspaceControlEventId!,
534
+ ),
535
+ },
536
+ ]
537
+ : []),
538
+ ...(result.wakeCount > 0
539
+ ? [
540
+ {
541
+ kind: "workflow_wake" as const,
542
+ run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
543
+ },
544
+ ]
545
+ : []),
534
546
  {
535
547
  kind: "workflow_wake",
536
548
  run: async () => {
@@ -832,7 +844,11 @@ export async function controlHumanSessionWorkstreamWithOutcome(
832
844
  } & SessionCommandPostCommitDeps,
833
845
  context: HumanSessionCommandContext,
834
846
  input: SessionControlRequest,
835
- ): Promise<{ response: SessionControlResponse; replay: boolean }> {
847
+ ): Promise<{
848
+ response: SessionControlResponse;
849
+ outcome: "changed" | "unchanged" | "replayed";
850
+ replay: boolean;
851
+ }> {
836
852
  const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
837
853
  const result = await runSessionCommandPersistenceTransaction(
838
854
  deps,
@@ -879,19 +895,27 @@ export async function controlHumanSessionWorkstreamWithOutcome(
879
895
  affected.eventIds,
880
896
  ),
881
897
  })),
882
- {
883
- kind: "workspace_control_fanout",
884
- run: async () =>
885
- await publishWorkspaceControlEvent(
886
- deps,
887
- context.workspaceId,
888
- result.workspaceControlEventId,
889
- ),
890
- },
891
- {
892
- kind: "workflow_wake",
893
- run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
894
- },
898
+ ...(result.workspaceControlEventId
899
+ ? [
900
+ {
901
+ kind: "workspace_control_fanout" as const,
902
+ run: async () =>
903
+ await publishWorkspaceControlEvent(
904
+ deps,
905
+ context.workspaceId,
906
+ result.workspaceControlEventId!,
907
+ ),
908
+ },
909
+ ]
910
+ : []),
911
+ ...(result.wakeCount > 0
912
+ ? [
913
+ {
914
+ kind: "workflow_wake" as const,
915
+ run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
916
+ },
917
+ ]
918
+ : []),
895
919
  {
896
920
  kind: "workflow_wake",
897
921
  run: async () => {
@@ -909,7 +933,7 @@ export async function controlHumanSessionWorkstreamWithOutcome(
909
933
  },
910
934
  },
911
935
  ]);
912
- return { response, replay: result.replay };
936
+ return { response, outcome: result.outcome, replay: result.replay };
913
937
  }
914
938
 
915
939
  /** Backward-compatible response path used by the REST control route. */
@@ -952,19 +976,27 @@ export async function controlHumanWorkspace(
952
976
  wakeCount: result.wakeCount,
953
977
  };
954
978
  scheduleSessionCommandPostCommit(deps, "human_workspace_control", [
955
- {
956
- kind: "workspace_control_fanout",
957
- run: async () =>
958
- await publishWorkspaceControlEvent(
959
- deps,
960
- context.workspaceId,
961
- result.workspaceControlEventId,
962
- ),
963
- },
964
- {
965
- kind: "workflow_wake",
966
- run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
967
- },
979
+ ...(result.workspaceControlEventId
980
+ ? [
981
+ {
982
+ kind: "workspace_control_fanout" as const,
983
+ run: async () =>
984
+ await publishWorkspaceControlEvent(
985
+ deps,
986
+ context.workspaceId,
987
+ result.workspaceControlEventId!,
988
+ ),
989
+ },
990
+ ]
991
+ : []),
992
+ ...(result.wakeCount > 0
993
+ ? [
994
+ {
995
+ kind: "workflow_wake" as const,
996
+ run: async () => await requestControlWakeDispatch(deps, result.wakeCount),
997
+ },
998
+ ]
999
+ : []),
968
1000
  ]);
969
1001
  return response;
970
1002
  }
@@ -3,6 +3,13 @@ import type { Context } from "hono";
3
3
  import { HTTPException } from "hono/http-exception";
4
4
  import type { ManagedAuth } from "./managed-auth-type";
5
5
  import { getManagedSession } from "./managed-session";
6
+ export {
7
+ getManagedAuthRequestActorAdmissionStamp,
8
+ getManagedAuthRequestActorLeaseStamp,
9
+ markManagedAuthRequestActorTransitionApplied,
10
+ } from "./managed-session";
11
+ import type { ManagedAuthSessionAdapter } from "./managed-auth-session-sets";
12
+ import type { ManagedAuthSessionSetMode } from "@opengeni/contracts/managed-auth-session-sets";
6
13
 
7
14
  export type CanonicalHumanRequestIdentity = {
8
15
  authUserId: string;
@@ -11,17 +18,29 @@ export type CanonicalHumanRequestIdentity = {
11
18
 
12
19
  export async function requireCanonicalHumanRequestIdentity(
13
20
  context: Context,
14
- input: { db: Database; managedAuth?: ManagedAuth | null; allowRecovery?: boolean },
21
+ input: {
22
+ db: Database;
23
+ managedAuth?: ManagedAuth | null | undefined;
24
+ managedAuthSessionAdapter?: ManagedAuthSessionAdapter | null | undefined;
25
+ managedAuthSessionSetMode?: ManagedAuthSessionSetMode | undefined;
26
+ allowRecovery?: boolean | undefined;
27
+ },
15
28
  ): Promise<CanonicalHumanRequestIdentity> {
16
29
  if (!input.managedAuth) {
17
- throw new HTTPException(404, { message: "Canonical human identity is unavailable" });
30
+ throw new HTTPException(404, {
31
+ message: "Canonical human identity is unavailable",
32
+ });
18
33
  }
19
34
  const session = await getManagedSession(context, input.managedAuth, {
20
35
  db: input.db,
36
+ sessionAdapter: input.managedAuthSessionAdapter,
37
+ sessionSetMode: input.managedAuthSessionSetMode,
21
38
  ...(input.allowRecovery === undefined ? {} : { allowIdentityRecovery: input.allowRecovery }),
22
39
  });
23
40
  if (!session?.user || typeof session.session?.id !== "string") {
24
- throw new HTTPException(401, { message: "Managed human authentication required" });
41
+ throw new HTTPException(401, {
42
+ message: "Managed human authentication required",
43
+ });
25
44
  }
26
45
  return { authUserId: session.user.id, authSessionId: session.session.id };
27
46
  }
@@ -15,6 +15,7 @@ import type { EventBus } from "@opengeni/events";
15
15
  import type { Observability } from "@opengeni/observability";
16
16
  import type { createObjectStorage } from "@opengeni/storage";
17
17
  import type { ManagedAuth } from "./managed-auth-type";
18
+ import type { ManagedAuthSessionAdapter } from "./managed-auth-session-sets";
18
19
  import type { ApiSandboxClient, ResumeBoxByIdInput, ResumedSandboxSession } from "./sandbox-types";
19
20
  import type { TranscriptionSegmenter, TranscriptionService } from "./transcription";
20
21
  import type { EditableArtifactApplicationPort } from "./editable-artifact-live";
@@ -177,6 +178,8 @@ export type AppDependencies = {
177
178
  */
178
179
  sessionAuthorization?: SessionAuthorizationPort | null;
179
180
  managedAuth?: ManagedAuth | null;
181
+ /** Provider-neutral browser login-slot adapter; required by dual/broker managed auth. */
182
+ managedAuthSessionAdapter?: ManagedAuthSessionAdapter | null;
180
183
  /** Injectable managed-email transport; standalone API defaults to Resend or local capture. */
181
184
  managedEmailTransport?: ManagedEmailTransport;
182
185
  /** Injectable Codex HTTP transport for deterministic API/provider tests. */