@sequenceholdings/managed-functions 0.4.0 → 0.4.1

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 CHANGED
@@ -1,5 +1,75 @@
1
1
  # `@sequenceholdings/managed-functions`
2
2
 
3
+ ## Group-member directory lookup (internal rollout)
4
+
5
+ Functions can resolve selected members of existing Atlas groups to active,
6
+ canonical user identities. This is an opt-in read capability, not a staff
7
+ registry, permission grant, arbitrary directory search, or authentication API.
8
+
9
+ ```yaml
10
+ capabilities:
11
+ directory:
12
+ groups: ["<existing-group-id>"]
13
+ ```
14
+
15
+ ```ts
16
+ import { createFunctionDirectoryClient } from '@sequenceholdings/managed-functions'
17
+
18
+ const directory = createFunctionDirectoryClient({
19
+ baseUrl: process.env.ATLAS_ORIGIN!,
20
+ request: req,
21
+ })
22
+ const { members } = await directory.resolveMembers({
23
+ groupId,
24
+ subs: [selectedCanonicalSubject],
25
+ })
26
+ ```
27
+
28
+ Use `userIds` instead of `subs` to resolve row IDs selected from the existing
29
+ group picker; never submit both. Each call accepts 1–50 selectors. Missing,
30
+ disabled, tombstoned, wrong-tenant, or nonmember selections are omitted. Exact
31
+ subject lookup does not accept an email alias. For a save, require the selected
32
+ subject to appear exactly in the response; a failed request is not an empty
33
+ successful lookup. Historical display labels may retain inactive identities,
34
+ but cannot validate a new assignment.
35
+
36
+ Atlas supplies a dedicated five-minute `x-sequence-directory-token` only for
37
+ declared groups on direct human invocations. System runners and service accounts
38
+ are not supported. Each read rechecks current function invocation access,
39
+ active version, declared group scope, active caller, caller group visibility,
40
+ and active target identities inside the function's tenant. No service identity
41
+ widens the caller's directory visibility. The function still enforces its own
42
+ application action permissions. Validation is point-in-time, not atomic with
43
+ an application's later transaction.
44
+
45
+ Directory and process-read clients share the SDK's invocation transport and
46
+ `{ request, baseUrl, fetchImpl? }` options. Both accept Fetch `Headers` or Node
47
+ request headers (case-insensitively), reject duplicate credentials, check expiry
48
+ before each read, and validate responses. `FunctionDirectoryReadError` exposes
49
+ `status` and `code`, like `FunctionProcessReadError`; failures are never an empty
50
+ member list. Directory replies must contain only distinct requested identities.
51
+ Sharing transport does not share authority: process reads retain service-account
52
+ credentials, while directory reads retain their separate human-caller credential.
53
+
54
+ The internal SDK bridge is `POST /api/managed-functions/directory/members`;
55
+ ordinary Auth0 bearers, storage tokens and ORM tokens do not authenticate it.
56
+ Results are not cached; successful reads emit an audit event. The client never
57
+ follows redirects or forwards an unrelated credential. Allow the Atlas origin
58
+ in the function's existing egress policy. Reviewed Atlas/CLI and SDK releases
59
+ are required before consumers opt in (SDK 0.4.1+, CLI 0.5.2+, and Atlas
60
+ advertising `directory_members`). Preflight rejects absent/disabled support
61
+ and incompatible tooling before upload; older deployments do not supply the
62
+ header. This addition does not activate or redeploy any consumer. The existing
63
+ package root remains Preview/blocked on publication; the raw HTTP route is
64
+ internal, not a new public API contract.
65
+
66
+ Tenant deployments reuse `ATLAS_ARTIFACT_STUDIO_SIGNING_KEY`, already provided
67
+ by the OpCo secrets catalog for Managed Functions. Delegation uses a separate
68
+ HMAC domain, audience, environment, function version and invocation binding.
69
+ There are no new secrets, migrations, groups or grants. Missing signing
70
+ configuration fails closed, including local development.
71
+
72
+
3
73
  `@sequenceholdings/managed-functions` is the TypeScript SDK for Managed
4
74
  Functions. It provides caller-scoped ORM access, explicit service-account ORM
5
75
  access, and opt-in caching. The
@@ -13,6 +13,13 @@ export declare const MANAGED_FUNCTION_CAPABILITIES: {
13
13
  };
14
14
  };
15
15
  readonly features: {
16
+ readonly directory_members: {
17
+ readonly field: "capabilities.directory.groups";
18
+ readonly supported: true;
19
+ readonly limit: "1 to 16 concrete groups; resolve 1 to 50 selected active members per call";
20
+ readonly min_cli: "0.5.2";
21
+ readonly min_sdk: "0.4.1";
22
+ };
16
23
  readonly processes_read: {
17
24
  readonly field: "capabilities.processes.read";
18
25
  readonly supported: true;
@@ -57,6 +64,13 @@ export interface CapabilityTooling {
57
64
  export interface ManagedFunctionCapabilities {
58
65
  tooling: CapabilityTooling;
59
66
  features: {
67
+ directory_members?: {
68
+ field: string;
69
+ supported: boolean;
70
+ limit: string;
71
+ min_cli: string;
72
+ min_sdk: string;
73
+ };
60
74
  processes_read?: {
61
75
  field: string;
62
76
  supported: boolean;
@@ -108,6 +122,7 @@ export interface PreflightManifest {
108
122
  data?: Readonly<Record<string, unknown>>;
109
123
  storage?: unknown;
110
124
  processes?: unknown;
125
+ directory?: unknown;
111
126
  };
112
127
  }
113
128
  export interface PreflightIssue {
@@ -15,6 +15,13 @@ exports.MANAGED_FUNCTION_CAPABILITIES = {
15
15
  sdk: { package: exports.MANAGED_FUNCTION_SDK_PACKAGE, min: '0.3.0' },
16
16
  },
17
17
  features: {
18
+ directory_members: {
19
+ field: 'capabilities.directory.groups',
20
+ supported: true,
21
+ limit: '1 to 16 concrete groups; resolve 1 to 50 selected active members per call',
22
+ min_cli: '0.5.2',
23
+ min_sdk: '0.4.1',
24
+ },
18
25
  processes_read: {
19
26
  field: 'capabilities.processes.read',
20
27
  supported: true,
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { DIRECTORY_TOKEN_HEADER, directoryGroupIdSchema, directoryScopesSchema, directoryMembersInput, directoryMembersResponse, readDirectoryGroups } from './directory-config';
1
2
  export { FUNCTION_PROCESS_TOKEN_HEADER, FUNCTION_PROCESS_READ_PATH, functionProcessReadScopesSchema, readFunctionProcessScopes, functionProcessReadRequestSchema, functionProcessRunSummarySchema } from './processes-contract';
2
3
  export { shouldReportAtlasHttp } from './http-tracing';
3
4
  import { z } from 'zod';
package/dist/config.js CHANGED
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cacheManifestSchema = exports.CACHE_MAX_TTL_SECONDS = exports.preflightManagedFunction = exports.parseDeployedPolicy = exports.versionIsOlder = exports.sdkVersionLowerBound = exports.sdkVersionFromPackageJson = exports.isPostgresEgress = exports.formatValidationDetail = exports.MANAGED_FUNCTION_SDK_PACKAGE = exports.MANAGED_FUNCTION_CLI_PACKAGE = exports.MANAGED_FUNCTION_CAPABILITIES = exports.managedFunctionWorkloadLimitsSchema = exports.MF_LIMITS_DEFAULTS = exports.MF_LIMITS_CAPS = exports.MANAGED_FUNCTION_LIMITS = exports.shouldReportAtlasHttp = exports.functionProcessRunSummarySchema = exports.functionProcessReadRequestSchema = exports.readFunctionProcessScopes = exports.functionProcessReadScopesSchema = exports.FUNCTION_PROCESS_READ_PATH = exports.FUNCTION_PROCESS_TOKEN_HEADER = void 0;
3
+ exports.cacheManifestSchema = exports.CACHE_MAX_TTL_SECONDS = exports.preflightManagedFunction = exports.parseDeployedPolicy = exports.versionIsOlder = exports.sdkVersionLowerBound = exports.sdkVersionFromPackageJson = exports.isPostgresEgress = exports.formatValidationDetail = exports.MANAGED_FUNCTION_SDK_PACKAGE = exports.MANAGED_FUNCTION_CLI_PACKAGE = exports.MANAGED_FUNCTION_CAPABILITIES = exports.managedFunctionWorkloadLimitsSchema = exports.MF_LIMITS_DEFAULTS = exports.MF_LIMITS_CAPS = exports.MANAGED_FUNCTION_LIMITS = exports.shouldReportAtlasHttp = exports.functionProcessRunSummarySchema = exports.functionProcessReadRequestSchema = exports.readFunctionProcessScopes = exports.functionProcessReadScopesSchema = exports.FUNCTION_PROCESS_READ_PATH = exports.FUNCTION_PROCESS_TOKEN_HEADER = exports.readDirectoryGroups = exports.directoryMembersResponse = exports.directoryMembersInput = exports.directoryScopesSchema = exports.directoryGroupIdSchema = exports.DIRECTORY_TOKEN_HEADER = void 0;
4
4
  exports.readCacheConfiguration = readCacheConfiguration;
5
+ var directory_config_1 = require("./directory-config");
6
+ Object.defineProperty(exports, "DIRECTORY_TOKEN_HEADER", { enumerable: true, get: function () { return directory_config_1.DIRECTORY_TOKEN_HEADER; } });
7
+ Object.defineProperty(exports, "directoryGroupIdSchema", { enumerable: true, get: function () { return directory_config_1.directoryGroupIdSchema; } });
8
+ Object.defineProperty(exports, "directoryScopesSchema", { enumerable: true, get: function () { return directory_config_1.directoryScopesSchema; } });
9
+ Object.defineProperty(exports, "directoryMembersInput", { enumerable: true, get: function () { return directory_config_1.directoryMembersInput; } });
10
+ Object.defineProperty(exports, "directoryMembersResponse", { enumerable: true, get: function () { return directory_config_1.directoryMembersResponse; } });
11
+ Object.defineProperty(exports, "readDirectoryGroups", { enumerable: true, get: function () { return directory_config_1.readDirectoryGroups; } });
5
12
  var processes_contract_1 = require("./processes-contract");
6
13
  Object.defineProperty(exports, "FUNCTION_PROCESS_TOKEN_HEADER", { enumerable: true, get: function () { return processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER; } });
7
14
  Object.defineProperty(exports, "FUNCTION_PROCESS_READ_PATH", { enumerable: true, get: function () { return processes_contract_1.FUNCTION_PROCESS_READ_PATH; } });
@@ -42,9 +42,23 @@ function normalizeCapabilities(value) {
42
42
  http_headers: normalizeHeaders(features.http_headers),
43
43
  invoke_timeout: normalizeTimeout(features.invoke_timeout),
44
44
  processes_read: normalizeProcesses(features.processes_read),
45
+ directory_members: normalizeDirectory(features.directory_members),
45
46
  },
46
47
  };
47
48
  }
49
+ function normalizeDirectory(value) {
50
+ if (!value || typeof value.supported !== 'boolean' || typeof value.limit !== 'string')
51
+ return undefined;
52
+ if (typeof value.min_cli !== 'string' || typeof value.min_sdk !== 'string')
53
+ return undefined;
54
+ return {
55
+ field: typeof value.field === 'string' ? value.field : 'capabilities.directory.groups',
56
+ supported: value.supported,
57
+ limit: value.limit,
58
+ min_cli: value.min_cli,
59
+ min_sdk: value.min_sdk,
60
+ };
61
+ }
48
62
  function normalizeProcesses(value) {
49
63
  if (!value || typeof value.supported !== 'boolean' || typeof value.limit !== 'string')
50
64
  return undefined;
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ export declare const DIRECTORY_TOKEN_HEADER = "x-sequence-directory-token";
3
+ export declare const directoryGroupIdSchema: z.ZodString;
4
+ export declare const directoryScopesSchema: z.ZodObject<{
5
+ groups: z.ZodArray<z.ZodString>;
6
+ }, z.core.$strict>;
7
+ export declare const directoryMembersInput: z.ZodUnion<readonly [z.ZodObject<{
8
+ groupId: z.ZodString;
9
+ userIds: z.ZodArray<z.ZodString>;
10
+ }, z.core.$strict>, z.ZodObject<{
11
+ groupId: z.ZodString;
12
+ subs: z.ZodArray<z.ZodString>;
13
+ }, z.core.$strict>]>;
14
+ export declare const directoryMembersResponse: z.ZodObject<{
15
+ members: z.ZodArray<z.ZodObject<{
16
+ id: z.ZodString;
17
+ sub: z.ZodString;
18
+ name: z.ZodString;
19
+ email: z.ZodString;
20
+ deactivated: z.ZodLiteral<false>;
21
+ }, z.core.$strict>>;
22
+ }, z.core.$strict>;
23
+ export declare function readDirectoryGroups(manifest: unknown): string[];
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.directoryMembersResponse = exports.directoryMembersInput = exports.directoryScopesSchema = exports.directoryGroupIdSchema = exports.DIRECTORY_TOKEN_HEADER = void 0;
4
+ exports.readDirectoryGroups = readDirectoryGroups;
5
+ const zod_1 = require("zod");
6
+ exports.DIRECTORY_TOKEN_HEADER = 'x-sequence-directory-token';
7
+ exports.directoryGroupIdSchema = zod_1.z.string().regex(/^(?:[a-f0-9]{24}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i);
8
+ exports.directoryScopesSchema = zod_1.z.object({
9
+ groups: zod_1.z.array(exports.directoryGroupIdSchema).min(1).max(16),
10
+ }).strict();
11
+ const identity = zod_1.z.string().min(1).max(256).regex(/^[^\x00-\x1f\x7f]+$/);
12
+ exports.directoryMembersInput = zod_1.z.union([
13
+ zod_1.z.object({ groupId: exports.directoryGroupIdSchema, userIds: zod_1.z.array(identity).min(1).max(50) }).strict(),
14
+ zod_1.z.object({ groupId: exports.directoryGroupIdSchema, subs: zod_1.z.array(identity).min(1).max(50) }).strict(),
15
+ ]);
16
+ exports.directoryMembersResponse = zod_1.z.object({ members: zod_1.z.array(zod_1.z.object({
17
+ id: zod_1.z.string(), sub: identity, name: zod_1.z.string(), email: zod_1.z.string(), deactivated: zod_1.z.literal(false),
18
+ }).strict()).max(50) }).strict();
19
+ function readDirectoryGroups(manifest) {
20
+ const parsed = zod_1.z.object({ capabilities: zod_1.z.object({ directory: exports.directoryScopesSchema.optional() }).optional() }).safeParse(manifest);
21
+ return parsed.success ? parsed.data.capabilities?.directory?.groups ?? [] : [];
22
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import { directoryMembersInput, directoryMembersResponse } from './directory-config';
3
+ import { type FunctionInvocationClientOptions } from './invocation-client';
4
+ export type FunctionDirectoryOptions = FunctionInvocationClientOptions;
5
+ export type FunctionDirectoryMembersQuery = z.infer<typeof directoryMembersInput>;
6
+ export type FunctionDirectoryMembers = z.infer<typeof directoryMembersResponse>;
7
+ export interface FunctionDirectoryClient {
8
+ resolveMembers(input: FunctionDirectoryMembersQuery): Promise<FunctionDirectoryMembers>;
9
+ }
10
+ export declare class FunctionDirectoryReadError extends Error {
11
+ readonly status: number;
12
+ readonly code: string;
13
+ constructor(status: number, code: string, message: string);
14
+ }
15
+ export declare function createFunctionDirectoryClient({ request, baseUrl, fetchImpl, }: FunctionDirectoryOptions): FunctionDirectoryClient;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FunctionDirectoryReadError = void 0;
4
+ exports.createFunctionDirectoryClient = createFunctionDirectoryClient;
5
+ const zod_1 = require("zod");
6
+ const directory_config_1 = require("./directory-config");
7
+ const invocation_client_1 = require("./invocation-client");
8
+ class FunctionDirectoryReadError extends Error {
9
+ status;
10
+ code;
11
+ constructor(status, code, message) {
12
+ super(message);
13
+ this.status = status;
14
+ this.code = code;
15
+ this.name = 'FunctionDirectoryReadError';
16
+ }
17
+ }
18
+ exports.FunctionDirectoryReadError = FunctionDirectoryReadError;
19
+ function createFunctionDirectoryClient({ request, baseUrl, fetchImpl, }) {
20
+ const credential = (0, invocation_client_1.readInvocationCredential)({
21
+ request,
22
+ header: directory_config_1.DIRECTORY_TOKEN_HEADER,
23
+ maxBytes: 8192,
24
+ invalidContext: () => new FunctionDirectoryReadError(401, 'invalid_context', 'This invocation has no valid directory-read credential'),
25
+ claimsSchema: zod_1.z.object({
26
+ audience: zod_1.z.literal('atlas-function-group-directory-v1'),
27
+ sub: zod_1.z
28
+ .string()
29
+ .min(1)
30
+ .max(256)
31
+ .refine((sub) => sub.includes('|') &&
32
+ !sub.startsWith('service-account|') &&
33
+ !sub.endsWith('@clients')),
34
+ functionId: zod_1.z.string().min(1),
35
+ invocationId: zod_1.z.string().min(1),
36
+ exp: zod_1.z.number().int(),
37
+ }),
38
+ });
39
+ const read = (0, invocation_client_1.createInvocationReadTransport)({
40
+ baseUrl,
41
+ fetchImpl,
42
+ path: '/api/managed-functions/directory/members',
43
+ header: directory_config_1.DIRECTORY_TOKEN_HEADER,
44
+ credential,
45
+ timeoutMs: 10_000,
46
+ error: (status, code, message) => new FunctionDirectoryReadError(status, code, message),
47
+ failure: {
48
+ code: 'directory_read_failed',
49
+ message: 'Directory read failed',
50
+ },
51
+ });
52
+ return {
53
+ async resolveMembers(input) {
54
+ const body = directory_config_1.directoryMembersInput.parse(input);
55
+ const result = await read({ body, schema: directory_config_1.directoryMembersResponse });
56
+ const selectors = new Set('userIds' in body ? body.userIds : body.subs);
57
+ const returned = result.members.map((member) => 'userIds' in body ? member.id : member.sub);
58
+ if (new Set(returned).size !== returned.length ||
59
+ returned.some((value) => !selectors.has(value))) {
60
+ throw new FunctionDirectoryReadError(502, 'malformed_response', 'Atlas returned duplicate or unrequested directory members');
61
+ }
62
+ return result;
63
+ },
64
+ };
65
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export { createFunctionDirectoryClient, FunctionDirectoryReadError } from './directory';
2
+ export type { FunctionDirectoryClient, FunctionDirectoryOptions, FunctionDirectoryMembers, FunctionDirectoryMembersQuery } from './directory';
3
+ export type { FunctionInvocationClientOptions } from './invocation-client';
1
4
  export { createFunctionServiceProcessesClient, FunctionProcessReadError } from './processes';
2
5
  export type { FunctionServiceProcessesClient, FunctionServiceProcessesOptions, FunctionProcessRunQuery, FunctionProcessRunSummary, FunctionProcessRunStatus, FunctionProcessRunsPage, FunctionProcessRunsPageQuery, FunctionProcessReadRequest } from './processes';
3
6
  export { installFunctionTracing, runWithFunctionTracing } from './tracing';
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = exports.FunctionProcessReadError = exports.createFunctionServiceProcessesClient = void 0;
3
+ exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = exports.FunctionProcessReadError = exports.createFunctionServiceProcessesClient = exports.FunctionDirectoryReadError = exports.createFunctionDirectoryClient = void 0;
4
4
  exports.createManagedFunctionContext = createManagedFunctionContext;
5
+ var directory_1 = require("./directory");
6
+ Object.defineProperty(exports, "createFunctionDirectoryClient", { enumerable: true, get: function () { return directory_1.createFunctionDirectoryClient; } });
7
+ Object.defineProperty(exports, "FunctionDirectoryReadError", { enumerable: true, get: function () { return directory_1.FunctionDirectoryReadError; } });
5
8
  var processes_1 = require("./processes");
6
9
  Object.defineProperty(exports, "createFunctionServiceProcessesClient", { enumerable: true, get: function () { return processes_1.createFunctionServiceProcessesClient; } });
7
10
  Object.defineProperty(exports, "FunctionProcessReadError", { enumerable: true, get: function () { return processes_1.FunctionProcessReadError; } });
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export interface FunctionInvocationClientOptions {
3
+ request: {
4
+ headers: Pick<Headers, 'get'> | Readonly<Record<string, string | readonly string[] | undefined>>;
5
+ };
6
+ baseUrl: string;
7
+ fetchImpl?: typeof globalThis.fetch;
8
+ }
9
+ type ClientErrorFactory = (status: number, code: string, message: string) => Error;
10
+ export declare function readInvocationCredential<T extends {
11
+ exp: number;
12
+ }>({ request, header, maxBytes, claimsSchema, invalidContext, }: Pick<FunctionInvocationClientOptions, 'request'> & {
13
+ header: string;
14
+ maxBytes: number;
15
+ claimsSchema: z.ZodType<T>;
16
+ invalidContext: () => Error;
17
+ }): {
18
+ token: string;
19
+ expiresAt: number;
20
+ };
21
+ export declare function createInvocationReadTransport({ baseUrl, fetchImpl, path, header, credential, timeoutMs, error, failure, }: Pick<FunctionInvocationClientOptions, 'baseUrl' | 'fetchImpl'> & {
22
+ path: string;
23
+ header: string;
24
+ credential: {
25
+ token: string;
26
+ expiresAt: number;
27
+ };
28
+ timeoutMs: number;
29
+ error: ClientErrorFactory;
30
+ failure: {
31
+ code: string;
32
+ message: string;
33
+ };
34
+ }): <T>({ body, schema, }: {
35
+ body: unknown;
36
+ schema: z.ZodType<T>;
37
+ }) => Promise<T>;
38
+ export {};
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readInvocationCredential = readInvocationCredential;
4
+ exports.createInvocationReadTransport = createInvocationReadTransport;
5
+ const zod_1 = require("zod");
6
+ function readInvocationCredential({ request, header, maxBytes, claimsSchema, invalidContext, }) {
7
+ const { headers } = request;
8
+ const matches = typeof headers.get === 'function'
9
+ ? [headers.get(header)]
10
+ : Object.entries(headers)
11
+ .filter(([name]) => name.toLowerCase() === header)
12
+ .map(([, value]) => value);
13
+ const token = matches[0];
14
+ if (matches.length !== 1 ||
15
+ typeof token !== 'string' ||
16
+ token.length > maxBytes)
17
+ throw invalidContext();
18
+ const envelope = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
19
+ if (!envelope?.[1])
20
+ throw invalidContext();
21
+ let value;
22
+ try {
23
+ value = JSON.parse(Buffer.from(envelope[1], 'base64url').toString('utf8'));
24
+ }
25
+ catch {
26
+ throw invalidContext();
27
+ }
28
+ const parsed = claimsSchema.safeParse(value);
29
+ if (!parsed.success)
30
+ throw invalidContext();
31
+ return { token, expiresAt: parsed.data.exp };
32
+ }
33
+ function trustedOrigin({ baseUrl, error, }) {
34
+ let url;
35
+ try {
36
+ url = new URL(baseUrl);
37
+ }
38
+ catch {
39
+ throw error(400, 'invalid_configuration', 'Configure a trusted Atlas origin');
40
+ }
41
+ const loopback = url.protocol === 'http:' &&
42
+ ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
43
+ if ((!loopback && url.protocol !== 'https:') ||
44
+ url.username ||
45
+ url.password ||
46
+ url.pathname !== '/' ||
47
+ url.search ||
48
+ url.hash) {
49
+ throw error(400, 'invalid_configuration', 'Configure an HTTPS Atlas origin without credentials, path, query or fragment');
50
+ }
51
+ return url.origin;
52
+ }
53
+ const errorResponseSchema = zod_1.z.object({
54
+ code: zod_1.z.string().max(128).optional(),
55
+ detail: zod_1.z.string().max(2000).optional(),
56
+ });
57
+ function createInvocationReadTransport({ baseUrl, fetchImpl = globalThis.fetch, path, header, credential, timeoutMs, error, failure, }) {
58
+ const url = `${trustedOrigin({ baseUrl, error })}${path}`;
59
+ return async function read({ body, schema, }) {
60
+ if (credential.expiresAt <= Date.now() / 1000) {
61
+ throw error(401, 'expired_context', 'The invocation credential expired; start a new invocation');
62
+ }
63
+ const response = await fetchImpl(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'content-type': 'application/json',
67
+ [header]: credential.token,
68
+ },
69
+ body: JSON.stringify(body),
70
+ redirect: 'error',
71
+ signal: AbortSignal.timeout(timeoutMs),
72
+ });
73
+ const result = await response.json().catch(() => null);
74
+ if (!response.ok) {
75
+ const parsed = errorResponseSchema.safeParse(result);
76
+ throw error(response.status, parsed.success ? (parsed.data.code ?? failure.code) : failure.code, parsed.success
77
+ ? (parsed.data.detail ?? failure.message)
78
+ : failure.message);
79
+ }
80
+ const validated = schema.safeParse(result);
81
+ if (!validated.success)
82
+ throw error(502, 'malformed_response', 'Atlas returned an invalid capability response');
83
+ return validated.data;
84
+ };
85
+ }
@@ -0,0 +1,10 @@
1
+ import { type CapabilityTooling, type ManagedFunctionCapabilities, type PreflightIssue, type PreflightManifest } from './capabilities';
2
+ export declare function checkDirectory({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues, }: {
3
+ manifest: PreflightManifest;
4
+ capabilities: ManagedFunctionCapabilities | null;
5
+ tooling: CapabilityTooling;
6
+ cliVersion: string;
7
+ sdkVersion: string | null;
8
+ checkDeployment: boolean;
9
+ issues: PreflightIssue[];
10
+ }): void;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkDirectory = checkDirectory;
4
+ const capabilities_1 = require("./capabilities");
5
+ const preflight_issue_1 = require("./preflight-issue");
6
+ const directory_config_1 = require("./directory-config");
7
+ const sdk_range_1 = require("./sdk-range");
8
+ function checkDirectory({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues, }) {
9
+ const directory = manifest.capabilities?.directory;
10
+ if (directory === undefined)
11
+ return;
12
+ const deployed = capabilities?.features.directory_members;
13
+ const feature = deployed ?? capabilities_1.MANAGED_FUNCTION_CAPABILITIES.features.directory_members;
14
+ const details = {
15
+ limit: feature.limit, tooling, requiredCli: feature.min_cli,
16
+ requiredSdk: feature.min_sdk, cliVersion, sdkVersion,
17
+ };
18
+ const parsed = directory_config_1.directoryScopesSchema.safeParse(directory);
19
+ if (!parsed.success) {
20
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
21
+ ...details, field: 'capabilities.directory', problem: (0, capabilities_1.formatValidationDetail)(parsed.error.issues),
22
+ }));
23
+ return;
24
+ }
25
+ if (checkDeployment && deployed?.supported !== true) {
26
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
27
+ ...details, field: feature.field, problem: 'group-member directory reads are not supported by this deployment',
28
+ }));
29
+ return;
30
+ }
31
+ if ((0, capabilities_1.versionIsOlder)(cliVersion, feature.min_cli) || (sdkVersion && (0, sdk_range_1.sdkConstraintIsOlder)(sdkVersion, feature.min_sdk))) {
32
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
33
+ ...details, field: feature.field, problem: 'the installed tooling does not support managed function group-member directory reads',
34
+ }));
35
+ }
36
+ }
package/dist/preflight.js CHANGED
@@ -5,6 +5,8 @@ const capabilities_1 = require("./capabilities");
5
5
  const sdk_range_1 = require("./sdk-range");
6
6
  const preflight_issue_1 = require("./preflight-issue");
7
7
  const preflight_processes_1 = require("./preflight-processes");
8
+ const preflight_directory_1 = require("./preflight-directory");
9
+ const directory_config_1 = require("./directory-config");
8
10
  const processes_contract_1 = require("./processes-contract");
9
11
  const WORKLOAD_FIELDS = [
10
12
  'memory_mb',
@@ -21,6 +23,7 @@ function preflightManagedFunction({ manifest, limits, capabilities, cliVersion,
21
23
  checkWorkload({ manifest, limits, tooling, cliVersion, sdkVersion, issues });
22
24
  checkPostgres({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues });
23
25
  (0, preflight_processes_1.checkProcesses)({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues });
26
+ (0, preflight_directory_1.checkDirectory)({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues });
24
27
  checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues });
25
28
  checkSdkTimeout({ manifest, limits, capabilities, tooling, cliVersion, sdkVersion, issues });
26
29
  return issues;
@@ -122,7 +125,8 @@ function needsWideHeaders(manifest) {
122
125
  const hasData = !!data && Object.keys(data).length > 0;
123
126
  const hasStorage = !!storage && typeof storage === 'object' && Object.keys(storage).length > 0;
124
127
  const hasProcesses = processes_contract_1.functionProcessReadScopesSchema.safeParse(manifest.capabilities?.processes).success;
125
- return hasData || hasStorage || hasProcesses;
128
+ const hasDirectory = directory_config_1.directoryScopesSchema.safeParse(manifest.capabilities?.directory).success;
129
+ return hasData || hasStorage || hasProcesses || hasDirectory;
126
130
  }
127
131
  function checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues, }) {
128
132
  const feature = capabilities?.features.http_headers;
@@ -133,7 +137,7 @@ function checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion,
133
137
  return;
134
138
  issues.push((0, preflight_issue_1.createPreflightIssue)({
135
139
  field: feature.field,
136
- problem: `invocation headers for capabilities.data, capabilities.storage, or capabilities.processes exceed ${feature.max_bytes} bytes and will fail with HTTP 431`,
140
+ problem: `invocation headers for capabilities.data, capabilities.storage, capabilities.processes, or capabilities.directory exceed ${feature.max_bytes} bytes and will fail with HTTP 431`,
137
141
  limit: feature.limit,
138
142
  tooling,
139
143
  requiredCli: feature.min_cli,
@@ -1,12 +1,7 @@
1
1
  import { type FunctionProcessRunQuery, type FunctionProcessRunSummary, type FunctionProcessRunsPage, type FunctionProcessRunsPageQuery } from './processes-contract';
2
+ import { type FunctionInvocationClientOptions } from './invocation-client';
2
3
  export * from './processes-contract';
3
- export interface FunctionServiceProcessesOptions {
4
- request: {
5
- headers: Pick<Headers, 'get'> | Readonly<Record<string, string | readonly string[] | undefined>>;
6
- };
7
- baseUrl: string;
8
- fetchImpl?: typeof globalThis.fetch;
9
- }
4
+ export type FunctionServiceProcessesOptions = FunctionInvocationClientOptions;
10
5
  export interface FunctionServiceProcessesClient {
11
6
  getRun(query: FunctionProcessRunQuery): Promise<FunctionProcessRunSummary>;
12
7
  listRunsPage(query: FunctionProcessRunsPageQuery): Promise<FunctionProcessRunsPage>;
package/dist/processes.js CHANGED
@@ -17,99 +17,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.createFunctionServiceProcessesClient = createFunctionServiceProcessesClient;
18
18
  const zod_1 = require("zod");
19
19
  const processes_contract_1 = require("./processes-contract");
20
+ const invocation_client_1 = require("./invocation-client");
20
21
  __exportStar(require("./processes-contract"), exports);
21
22
  const invalidContext = () => new processes_contract_1.FunctionProcessReadError(401, 'invalid_context', 'This invocation has no valid process-read credential');
22
- function readCredential({ request, }) {
23
- const { headers } = request;
24
- const matches = typeof headers.get === 'function'
25
- ? [headers.get(processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER)]
26
- : Object.entries(headers)
27
- .filter(([name]) => name.toLowerCase() === processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER)
28
- .map(([, value]) => value);
29
- const token = matches[0];
30
- if (matches.length !== 1 ||
31
- typeof token !== 'string' ||
32
- token.length > 16_384)
33
- throw invalidContext();
34
- const envelope = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
35
- if (!envelope?.[1])
36
- throw invalidContext();
37
- let value;
38
- try {
39
- value = JSON.parse(Buffer.from(envelope[1], 'base64url').toString('utf8'));
40
- }
41
- catch {
42
- throw invalidContext();
43
- }
44
- const parsed = zod_1.z
45
- .object({
46
- audience: zod_1.z.literal('atlas-managed-function-process-read'),
47
- sub: zod_1.z.string().startsWith('service-account|'),
48
- functionId: zod_1.z.string().min(1),
49
- invocationId: zod_1.z.string().min(1),
50
- exp: zod_1.z.number().int(),
51
- })
52
- .safeParse(value);
53
- if (!parsed.success)
54
- throw invalidContext();
55
- return { token, expiresAt: parsed.data.exp };
56
- }
57
- function origin(baseUrl) {
58
- let url;
59
- try {
60
- url = new URL(baseUrl);
61
- }
62
- catch {
63
- throw new processes_contract_1.FunctionProcessReadError(400, 'invalid_configuration', 'Configure a trusted Atlas origin');
64
- }
65
- const loopback = url.protocol === 'http:' &&
66
- ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
67
- if ((!loopback && url.protocol !== 'https:') ||
68
- url.username ||
69
- url.password ||
70
- url.pathname !== '/' ||
71
- url.search ||
72
- url.hash) {
73
- throw new processes_contract_1.FunctionProcessReadError(400, 'invalid_configuration', 'Configure an HTTPS Atlas origin without credentials, path, query or fragment');
74
- }
75
- return url.origin;
76
- }
77
23
  function createFunctionServiceProcessesClient({ request, baseUrl, fetchImpl = globalThis.fetch, }) {
78
- const url = `${origin(baseUrl)}${processes_contract_1.FUNCTION_PROCESS_READ_PATH}`;
79
- const { token, expiresAt } = readCredential({ request });
80
- async function read({ body, schema, }) {
81
- if (expiresAt <= Date.now() / 1000)
82
- throw new processes_contract_1.FunctionProcessReadError(401, 'expired_context', 'The process-read credential expired; start a new invocation');
83
- const parsed = processes_contract_1.functionProcessReadRequestSchema.parse(body);
84
- const response = await fetchImpl(url, {
85
- method: 'POST',
86
- headers: {
87
- 'content-type': 'application/json',
88
- [processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER]: token,
89
- },
90
- body: JSON.stringify(parsed),
91
- redirect: 'error',
92
- signal: AbortSignal.timeout(30_000),
93
- });
94
- const result = await response.json().catch(() => null);
95
- if (!response.ok) {
96
- const error = zod_1.z
97
- .object({
98
- code: zod_1.z.string().max(128).optional(),
99
- detail: zod_1.z.string().max(2000).optional(),
100
- })
101
- .safeParse(result);
102
- throw new processes_contract_1.FunctionProcessReadError(response.status, error.success
103
- ? (error.data.code ?? 'process_read_failed')
104
- : 'process_read_failed', error.success
105
- ? (error.data.detail ?? 'Process read failed')
106
- : 'Process read failed');
107
- }
108
- const validated = schema.safeParse(result);
109
- if (!validated.success)
110
- throw new processes_contract_1.FunctionProcessReadError(502, 'malformed_response', 'Atlas returned an invalid process-read response');
111
- return validated.data;
112
- }
24
+ const credential = (0, invocation_client_1.readInvocationCredential)({
25
+ request,
26
+ header: processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER,
27
+ maxBytes: 16_384,
28
+ invalidContext,
29
+ claimsSchema: zod_1.z.object({
30
+ audience: zod_1.z.literal('atlas-managed-function-process-read'),
31
+ sub: zod_1.z.string().startsWith('service-account|'),
32
+ functionId: zod_1.z.string().min(1),
33
+ invocationId: zod_1.z.string().min(1),
34
+ exp: zod_1.z.number().int(),
35
+ }),
36
+ });
37
+ const transport = (0, invocation_client_1.createInvocationReadTransport)({
38
+ baseUrl,
39
+ fetchImpl,
40
+ path: processes_contract_1.FUNCTION_PROCESS_READ_PATH,
41
+ header: processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER,
42
+ credential,
43
+ timeoutMs: 30_000,
44
+ error: (status, code, message) => new processes_contract_1.FunctionProcessReadError(status, code, message),
45
+ failure: { code: 'process_read_failed', message: 'Process read failed' },
46
+ });
47
+ const read = ({ body, schema }) => transport({ body: processes_contract_1.functionProcessReadRequestSchema.parse(body), schema });
113
48
  return {
114
49
  async getRun(query) {
115
50
  const run = await read({
package/dist/runtime.cjs CHANGED
@@ -16908,6 +16908,177 @@ var require_zod = __commonJS({
16908
16908
  }
16909
16909
  });
16910
16910
 
16911
+ // dist/directory-config.js
16912
+ var require_directory_config = __commonJS({
16913
+ "dist/directory-config.js"(exports2) {
16914
+ "use strict";
16915
+ Object.defineProperty(exports2, "__esModule", { value: true });
16916
+ exports2.directoryMembersResponse = exports2.directoryMembersInput = exports2.directoryScopesSchema = exports2.directoryGroupIdSchema = exports2.DIRECTORY_TOKEN_HEADER = void 0;
16917
+ exports2.readDirectoryGroups = readDirectoryGroups;
16918
+ var zod_12 = require_zod();
16919
+ exports2.DIRECTORY_TOKEN_HEADER = "x-sequence-directory-token";
16920
+ exports2.directoryGroupIdSchema = zod_12.z.string().regex(/^(?:[a-f0-9]{24}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i);
16921
+ exports2.directoryScopesSchema = zod_12.z.object({
16922
+ groups: zod_12.z.array(exports2.directoryGroupIdSchema).min(1).max(16)
16923
+ }).strict();
16924
+ var identity = zod_12.z.string().min(1).max(256).regex(/^[^\x00-\x1f\x7f]+$/);
16925
+ exports2.directoryMembersInput = zod_12.z.union([
16926
+ zod_12.z.object({ groupId: exports2.directoryGroupIdSchema, userIds: zod_12.z.array(identity).min(1).max(50) }).strict(),
16927
+ zod_12.z.object({ groupId: exports2.directoryGroupIdSchema, subs: zod_12.z.array(identity).min(1).max(50) }).strict()
16928
+ ]);
16929
+ exports2.directoryMembersResponse = zod_12.z.object({ members: zod_12.z.array(zod_12.z.object({
16930
+ id: zod_12.z.string(),
16931
+ sub: identity,
16932
+ name: zod_12.z.string(),
16933
+ email: zod_12.z.string(),
16934
+ deactivated: zod_12.z.literal(false)
16935
+ }).strict()).max(50) }).strict();
16936
+ function readDirectoryGroups(manifest) {
16937
+ const parsed = zod_12.z.object({ capabilities: zod_12.z.object({ directory: exports2.directoryScopesSchema.optional() }).optional() }).safeParse(manifest);
16938
+ return parsed.success ? parsed.data.capabilities?.directory?.groups ?? [] : [];
16939
+ }
16940
+ }
16941
+ });
16942
+
16943
+ // dist/invocation-client.js
16944
+ var require_invocation_client = __commonJS({
16945
+ "dist/invocation-client.js"(exports2) {
16946
+ "use strict";
16947
+ Object.defineProperty(exports2, "__esModule", { value: true });
16948
+ exports2.readInvocationCredential = readInvocationCredential;
16949
+ exports2.createInvocationReadTransport = createInvocationReadTransport;
16950
+ var zod_12 = require_zod();
16951
+ function readInvocationCredential({ request, header, maxBytes, claimsSchema, invalidContext }) {
16952
+ const { headers } = request;
16953
+ const matches = typeof headers.get === "function" ? [headers.get(header)] : Object.entries(headers).filter(([name]) => name.toLowerCase() === header).map(([, value2]) => value2);
16954
+ const token = matches[0];
16955
+ if (matches.length !== 1 || typeof token !== "string" || token.length > maxBytes)
16956
+ throw invalidContext();
16957
+ const envelope = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
16958
+ if (!envelope?.[1])
16959
+ throw invalidContext();
16960
+ let value;
16961
+ try {
16962
+ value = JSON.parse(Buffer.from(envelope[1], "base64url").toString("utf8"));
16963
+ } catch {
16964
+ throw invalidContext();
16965
+ }
16966
+ const parsed = claimsSchema.safeParse(value);
16967
+ if (!parsed.success)
16968
+ throw invalidContext();
16969
+ return { token, expiresAt: parsed.data.exp };
16970
+ }
16971
+ function trustedOrigin({ baseUrl, error }) {
16972
+ let url;
16973
+ try {
16974
+ url = new URL(baseUrl);
16975
+ } catch {
16976
+ throw error(400, "invalid_configuration", "Configure a trusted Atlas origin");
16977
+ }
16978
+ const loopback = url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
16979
+ if (!loopback && url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
16980
+ throw error(400, "invalid_configuration", "Configure an HTTPS Atlas origin without credentials, path, query or fragment");
16981
+ }
16982
+ return url.origin;
16983
+ }
16984
+ var errorResponseSchema = zod_12.z.object({
16985
+ code: zod_12.z.string().max(128).optional(),
16986
+ detail: zod_12.z.string().max(2e3).optional()
16987
+ });
16988
+ function createInvocationReadTransport({ baseUrl, fetchImpl = globalThis.fetch, path, header, credential, timeoutMs, error, failure }) {
16989
+ const url = `${trustedOrigin({ baseUrl, error })}${path}`;
16990
+ return async function read({ body, schema }) {
16991
+ if (credential.expiresAt <= Date.now() / 1e3) {
16992
+ throw error(401, "expired_context", "The invocation credential expired; start a new invocation");
16993
+ }
16994
+ const response = await fetchImpl(url, {
16995
+ method: "POST",
16996
+ headers: {
16997
+ "content-type": "application/json",
16998
+ [header]: credential.token
16999
+ },
17000
+ body: JSON.stringify(body),
17001
+ redirect: "error",
17002
+ signal: AbortSignal.timeout(timeoutMs)
17003
+ });
17004
+ const result = await response.json().catch(() => null);
17005
+ if (!response.ok) {
17006
+ const parsed = errorResponseSchema.safeParse(result);
17007
+ throw error(response.status, parsed.success ? parsed.data.code ?? failure.code : failure.code, parsed.success ? parsed.data.detail ?? failure.message : failure.message);
17008
+ }
17009
+ const validated = schema.safeParse(result);
17010
+ if (!validated.success)
17011
+ throw error(502, "malformed_response", "Atlas returned an invalid capability response");
17012
+ return validated.data;
17013
+ };
17014
+ }
17015
+ }
17016
+ });
17017
+
17018
+ // dist/directory.js
17019
+ var require_directory = __commonJS({
17020
+ "dist/directory.js"(exports2) {
17021
+ "use strict";
17022
+ Object.defineProperty(exports2, "__esModule", { value: true });
17023
+ exports2.FunctionDirectoryReadError = void 0;
17024
+ exports2.createFunctionDirectoryClient = createFunctionDirectoryClient;
17025
+ var zod_12 = require_zod();
17026
+ var directory_config_1 = require_directory_config();
17027
+ var invocation_client_1 = require_invocation_client();
17028
+ var FunctionDirectoryReadError = class extends Error {
17029
+ status;
17030
+ code;
17031
+ constructor(status, code, message) {
17032
+ super(message);
17033
+ this.status = status;
17034
+ this.code = code;
17035
+ this.name = "FunctionDirectoryReadError";
17036
+ }
17037
+ };
17038
+ exports2.FunctionDirectoryReadError = FunctionDirectoryReadError;
17039
+ function createFunctionDirectoryClient({ request, baseUrl, fetchImpl }) {
17040
+ const credential = (0, invocation_client_1.readInvocationCredential)({
17041
+ request,
17042
+ header: directory_config_1.DIRECTORY_TOKEN_HEADER,
17043
+ maxBytes: 8192,
17044
+ invalidContext: () => new FunctionDirectoryReadError(401, "invalid_context", "This invocation has no valid directory-read credential"),
17045
+ claimsSchema: zod_12.z.object({
17046
+ audience: zod_12.z.literal("atlas-function-group-directory-v1"),
17047
+ sub: zod_12.z.string().min(1).max(256).refine((sub) => sub.includes("|") && !sub.startsWith("service-account|") && !sub.endsWith("@clients")),
17048
+ functionId: zod_12.z.string().min(1),
17049
+ invocationId: zod_12.z.string().min(1),
17050
+ exp: zod_12.z.number().int()
17051
+ })
17052
+ });
17053
+ const read = (0, invocation_client_1.createInvocationReadTransport)({
17054
+ baseUrl,
17055
+ fetchImpl,
17056
+ path: "/api/managed-functions/directory/members",
17057
+ header: directory_config_1.DIRECTORY_TOKEN_HEADER,
17058
+ credential,
17059
+ timeoutMs: 1e4,
17060
+ error: (status, code, message) => new FunctionDirectoryReadError(status, code, message),
17061
+ failure: {
17062
+ code: "directory_read_failed",
17063
+ message: "Directory read failed"
17064
+ }
17065
+ });
17066
+ return {
17067
+ async resolveMembers(input) {
17068
+ const body = directory_config_1.directoryMembersInput.parse(input);
17069
+ const result = await read({ body, schema: directory_config_1.directoryMembersResponse });
17070
+ const selectors = new Set("userIds" in body ? body.userIds : body.subs);
17071
+ const returned = result.members.map((member) => "userIds" in body ? member.id : member.sub);
17072
+ if (new Set(returned).size !== returned.length || returned.some((value) => !selectors.has(value))) {
17073
+ throw new FunctionDirectoryReadError(502, "malformed_response", "Atlas returned duplicate or unrequested directory members");
17074
+ }
17075
+ return result;
17076
+ }
17077
+ };
17078
+ }
17079
+ }
17080
+ });
17081
+
16911
17082
  // dist/processes-contract.js
16912
17083
  var require_processes_contract = __commonJS({
16913
17084
  "dist/processes-contract.js"(exports2) {
@@ -17015,77 +17186,34 @@ var require_processes = __commonJS({
17015
17186
  exports2.createFunctionServiceProcessesClient = createFunctionServiceProcessesClient;
17016
17187
  var zod_12 = require_zod();
17017
17188
  var processes_contract_1 = require_processes_contract();
17189
+ var invocation_client_1 = require_invocation_client();
17018
17190
  __exportStar(require_processes_contract(), exports2);
17019
17191
  var invalidContext = () => new processes_contract_1.FunctionProcessReadError(401, "invalid_context", "This invocation has no valid process-read credential");
17020
- function readCredential({ request }) {
17021
- const { headers } = request;
17022
- const matches = typeof headers.get === "function" ? [headers.get(processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER)] : Object.entries(headers).filter(([name]) => name.toLowerCase() === processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER).map(([, value2]) => value2);
17023
- const token = matches[0];
17024
- if (matches.length !== 1 || typeof token !== "string" || token.length > 16384)
17025
- throw invalidContext();
17026
- const envelope = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
17027
- if (!envelope?.[1])
17028
- throw invalidContext();
17029
- let value;
17030
- try {
17031
- value = JSON.parse(Buffer.from(envelope[1], "base64url").toString("utf8"));
17032
- } catch {
17033
- throw invalidContext();
17034
- }
17035
- const parsed = zod_12.z.object({
17036
- audience: zod_12.z.literal("atlas-managed-function-process-read"),
17037
- sub: zod_12.z.string().startsWith("service-account|"),
17038
- functionId: zod_12.z.string().min(1),
17039
- invocationId: zod_12.z.string().min(1),
17040
- exp: zod_12.z.number().int()
17041
- }).safeParse(value);
17042
- if (!parsed.success)
17043
- throw invalidContext();
17044
- return { token, expiresAt: parsed.data.exp };
17045
- }
17046
- function origin(baseUrl) {
17047
- let url;
17048
- try {
17049
- url = new URL(baseUrl);
17050
- } catch {
17051
- throw new processes_contract_1.FunctionProcessReadError(400, "invalid_configuration", "Configure a trusted Atlas origin");
17052
- }
17053
- const loopback = url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
17054
- if (!loopback && url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
17055
- throw new processes_contract_1.FunctionProcessReadError(400, "invalid_configuration", "Configure an HTTPS Atlas origin without credentials, path, query or fragment");
17056
- }
17057
- return url.origin;
17058
- }
17059
17192
  function createFunctionServiceProcessesClient({ request, baseUrl, fetchImpl = globalThis.fetch }) {
17060
- const url = `${origin(baseUrl)}${processes_contract_1.FUNCTION_PROCESS_READ_PATH}`;
17061
- const { token, expiresAt } = readCredential({ request });
17062
- async function read({ body, schema }) {
17063
- if (expiresAt <= Date.now() / 1e3)
17064
- throw new processes_contract_1.FunctionProcessReadError(401, "expired_context", "The process-read credential expired; start a new invocation");
17065
- const parsed = processes_contract_1.functionProcessReadRequestSchema.parse(body);
17066
- const response = await fetchImpl(url, {
17067
- method: "POST",
17068
- headers: {
17069
- "content-type": "application/json",
17070
- [processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER]: token
17071
- },
17072
- body: JSON.stringify(parsed),
17073
- redirect: "error",
17074
- signal: AbortSignal.timeout(3e4)
17075
- });
17076
- const result = await response.json().catch(() => null);
17077
- if (!response.ok) {
17078
- const error = zod_12.z.object({
17079
- code: zod_12.z.string().max(128).optional(),
17080
- detail: zod_12.z.string().max(2e3).optional()
17081
- }).safeParse(result);
17082
- throw new processes_contract_1.FunctionProcessReadError(response.status, error.success ? error.data.code ?? "process_read_failed" : "process_read_failed", error.success ? error.data.detail ?? "Process read failed" : "Process read failed");
17083
- }
17084
- const validated = schema.safeParse(result);
17085
- if (!validated.success)
17086
- throw new processes_contract_1.FunctionProcessReadError(502, "malformed_response", "Atlas returned an invalid process-read response");
17087
- return validated.data;
17088
- }
17193
+ const credential = (0, invocation_client_1.readInvocationCredential)({
17194
+ request,
17195
+ header: processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER,
17196
+ maxBytes: 16384,
17197
+ invalidContext,
17198
+ claimsSchema: zod_12.z.object({
17199
+ audience: zod_12.z.literal("atlas-managed-function-process-read"),
17200
+ sub: zod_12.z.string().startsWith("service-account|"),
17201
+ functionId: zod_12.z.string().min(1),
17202
+ invocationId: zod_12.z.string().min(1),
17203
+ exp: zod_12.z.number().int()
17204
+ })
17205
+ });
17206
+ const transport = (0, invocation_client_1.createInvocationReadTransport)({
17207
+ baseUrl,
17208
+ fetchImpl,
17209
+ path: processes_contract_1.FUNCTION_PROCESS_READ_PATH,
17210
+ header: processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER,
17211
+ credential,
17212
+ timeoutMs: 3e4,
17213
+ error: (status, code, message) => new processes_contract_1.FunctionProcessReadError(status, code, message),
17214
+ failure: { code: "process_read_failed", message: "Process read failed" }
17215
+ });
17216
+ const read = ({ body, schema }) => transport({ body: processes_contract_1.functionProcessReadRequestSchema.parse(body), schema });
17089
17217
  return {
17090
17218
  async getRun(query) {
17091
17219
  const run = await read({
@@ -28185,6 +28313,13 @@ var require_capabilities = __commonJS({
28185
28313
  sdk: { package: exports2.MANAGED_FUNCTION_SDK_PACKAGE, min: "0.3.0" }
28186
28314
  },
28187
28315
  features: {
28316
+ directory_members: {
28317
+ field: "capabilities.directory.groups",
28318
+ supported: true,
28319
+ limit: "1 to 16 concrete groups; resolve 1 to 50 selected active members per call",
28320
+ min_cli: "0.5.2",
28321
+ min_sdk: "0.4.1"
28322
+ },
28188
28323
  processes_read: {
28189
28324
  field: "capabilities.processes.read",
28190
28325
  supported: true,
@@ -28316,10 +28451,24 @@ var require_deployed_policy = __commonJS({
28316
28451
  postgres_egress: normalizePostgres(features.postgres_egress),
28317
28452
  http_headers: normalizeHeaders(features.http_headers),
28318
28453
  invoke_timeout: normalizeTimeout(features.invoke_timeout),
28319
- processes_read: normalizeProcesses(features.processes_read)
28454
+ processes_read: normalizeProcesses(features.processes_read),
28455
+ directory_members: normalizeDirectory(features.directory_members)
28320
28456
  }
28321
28457
  };
28322
28458
  }
28459
+ function normalizeDirectory(value) {
28460
+ if (!value || typeof value.supported !== "boolean" || typeof value.limit !== "string")
28461
+ return void 0;
28462
+ if (typeof value.min_cli !== "string" || typeof value.min_sdk !== "string")
28463
+ return void 0;
28464
+ return {
28465
+ field: typeof value.field === "string" ? value.field : "capabilities.directory.groups",
28466
+ supported: value.supported,
28467
+ limit: value.limit,
28468
+ min_cli: value.min_cli,
28469
+ min_sdk: value.min_sdk
28470
+ };
28471
+ }
28323
28472
  function normalizeProcesses(value) {
28324
28473
  if (!value || typeof value.supported !== "boolean" || typeof value.limit !== "string")
28325
28474
  return void 0;
@@ -28449,6 +28598,58 @@ var require_preflight_processes = __commonJS({
28449
28598
  }
28450
28599
  });
28451
28600
 
28601
+ // dist/preflight-directory.js
28602
+ var require_preflight_directory = __commonJS({
28603
+ "dist/preflight-directory.js"(exports2) {
28604
+ "use strict";
28605
+ Object.defineProperty(exports2, "__esModule", { value: true });
28606
+ exports2.checkDirectory = checkDirectory;
28607
+ var capabilities_1 = require_capabilities();
28608
+ var preflight_issue_1 = require_preflight_issue();
28609
+ var directory_config_1 = require_directory_config();
28610
+ var sdk_range_1 = require_sdk_range();
28611
+ function checkDirectory({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues }) {
28612
+ const directory = manifest.capabilities?.directory;
28613
+ if (directory === void 0)
28614
+ return;
28615
+ const deployed = capabilities?.features.directory_members;
28616
+ const feature = deployed ?? capabilities_1.MANAGED_FUNCTION_CAPABILITIES.features.directory_members;
28617
+ const details = {
28618
+ limit: feature.limit,
28619
+ tooling,
28620
+ requiredCli: feature.min_cli,
28621
+ requiredSdk: feature.min_sdk,
28622
+ cliVersion,
28623
+ sdkVersion
28624
+ };
28625
+ const parsed = directory_config_1.directoryScopesSchema.safeParse(directory);
28626
+ if (!parsed.success) {
28627
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
28628
+ ...details,
28629
+ field: "capabilities.directory",
28630
+ problem: (0, capabilities_1.formatValidationDetail)(parsed.error.issues)
28631
+ }));
28632
+ return;
28633
+ }
28634
+ if (checkDeployment && deployed?.supported !== true) {
28635
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
28636
+ ...details,
28637
+ field: feature.field,
28638
+ problem: "group-member directory reads are not supported by this deployment"
28639
+ }));
28640
+ return;
28641
+ }
28642
+ if ((0, capabilities_1.versionIsOlder)(cliVersion, feature.min_cli) || sdkVersion && (0, sdk_range_1.sdkConstraintIsOlder)(sdkVersion, feature.min_sdk)) {
28643
+ issues.push((0, preflight_issue_1.createPreflightIssue)({
28644
+ ...details,
28645
+ field: feature.field,
28646
+ problem: "the installed tooling does not support managed function group-member directory reads"
28647
+ }));
28648
+ }
28649
+ }
28650
+ }
28651
+ });
28652
+
28452
28653
  // dist/preflight.js
28453
28654
  var require_preflight = __commonJS({
28454
28655
  "dist/preflight.js"(exports2) {
@@ -28459,6 +28660,8 @@ var require_preflight = __commonJS({
28459
28660
  var sdk_range_1 = require_sdk_range();
28460
28661
  var preflight_issue_1 = require_preflight_issue();
28461
28662
  var preflight_processes_1 = require_preflight_processes();
28663
+ var preflight_directory_1 = require_preflight_directory();
28664
+ var directory_config_1 = require_directory_config();
28462
28665
  var processes_contract_1 = require_processes_contract();
28463
28666
  var WORKLOAD_FIELDS = [
28464
28667
  "memory_mb",
@@ -28475,6 +28678,7 @@ var require_preflight = __commonJS({
28475
28678
  checkWorkload({ manifest, limits, tooling, cliVersion, sdkVersion, issues });
28476
28679
  checkPostgres({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues });
28477
28680
  (0, preflight_processes_1.checkProcesses)({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues });
28681
+ (0, preflight_directory_1.checkDirectory)({ manifest, capabilities, tooling, cliVersion, sdkVersion, checkDeployment, issues });
28478
28682
  checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues });
28479
28683
  checkSdkTimeout({ manifest, limits, capabilities, tooling, cliVersion, sdkVersion, issues });
28480
28684
  return issues;
@@ -28576,7 +28780,8 @@ var require_preflight = __commonJS({
28576
28780
  const hasData = !!data && Object.keys(data).length > 0;
28577
28781
  const hasStorage = !!storage && typeof storage === "object" && Object.keys(storage).length > 0;
28578
28782
  const hasProcesses = processes_contract_1.functionProcessReadScopesSchema.safeParse(manifest.capabilities?.processes).success;
28579
- return hasData || hasStorage || hasProcesses;
28783
+ const hasDirectory = directory_config_1.directoryScopesSchema.safeParse(manifest.capabilities?.directory).success;
28784
+ return hasData || hasStorage || hasProcesses || hasDirectory;
28580
28785
  }
28581
28786
  function checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues }) {
28582
28787
  const feature = capabilities?.features.http_headers;
@@ -28587,7 +28792,7 @@ var require_preflight = __commonJS({
28587
28792
  return;
28588
28793
  issues.push((0, preflight_issue_1.createPreflightIssue)({
28589
28794
  field: feature.field,
28590
- problem: `invocation headers for capabilities.data, capabilities.storage, or capabilities.processes exceed ${feature.max_bytes} bytes and will fail with HTTP 431`,
28795
+ problem: `invocation headers for capabilities.data, capabilities.storage, capabilities.processes, or capabilities.directory exceed ${feature.max_bytes} bytes and will fail with HTTP 431`,
28591
28796
  limit: feature.limit,
28592
28797
  tooling,
28593
28798
  requiredCli: feature.min_cli,
@@ -28625,8 +28830,27 @@ var require_config2 = __commonJS({
28625
28830
  "dist/config.js"(exports2) {
28626
28831
  "use strict";
28627
28832
  Object.defineProperty(exports2, "__esModule", { value: true });
28628
- exports2.cacheManifestSchema = exports2.CACHE_MAX_TTL_SECONDS = exports2.preflightManagedFunction = exports2.parseDeployedPolicy = exports2.versionIsOlder = exports2.sdkVersionLowerBound = exports2.sdkVersionFromPackageJson = exports2.isPostgresEgress = exports2.formatValidationDetail = exports2.MANAGED_FUNCTION_SDK_PACKAGE = exports2.MANAGED_FUNCTION_CLI_PACKAGE = exports2.MANAGED_FUNCTION_CAPABILITIES = exports2.managedFunctionWorkloadLimitsSchema = exports2.MF_LIMITS_DEFAULTS = exports2.MF_LIMITS_CAPS = exports2.MANAGED_FUNCTION_LIMITS = exports2.shouldReportAtlasHttp = exports2.functionProcessRunSummarySchema = exports2.functionProcessReadRequestSchema = exports2.readFunctionProcessScopes = exports2.functionProcessReadScopesSchema = exports2.FUNCTION_PROCESS_READ_PATH = exports2.FUNCTION_PROCESS_TOKEN_HEADER = void 0;
28833
+ exports2.cacheManifestSchema = exports2.CACHE_MAX_TTL_SECONDS = exports2.preflightManagedFunction = exports2.parseDeployedPolicy = exports2.versionIsOlder = exports2.sdkVersionLowerBound = exports2.sdkVersionFromPackageJson = exports2.isPostgresEgress = exports2.formatValidationDetail = exports2.MANAGED_FUNCTION_SDK_PACKAGE = exports2.MANAGED_FUNCTION_CLI_PACKAGE = exports2.MANAGED_FUNCTION_CAPABILITIES = exports2.managedFunctionWorkloadLimitsSchema = exports2.MF_LIMITS_DEFAULTS = exports2.MF_LIMITS_CAPS = exports2.MANAGED_FUNCTION_LIMITS = exports2.shouldReportAtlasHttp = exports2.functionProcessRunSummarySchema = exports2.functionProcessReadRequestSchema = exports2.readFunctionProcessScopes = exports2.functionProcessReadScopesSchema = exports2.FUNCTION_PROCESS_READ_PATH = exports2.FUNCTION_PROCESS_TOKEN_HEADER = exports2.readDirectoryGroups = exports2.directoryMembersResponse = exports2.directoryMembersInput = exports2.directoryScopesSchema = exports2.directoryGroupIdSchema = exports2.DIRECTORY_TOKEN_HEADER = void 0;
28629
28834
  exports2.readCacheConfiguration = readCacheConfiguration;
28835
+ var directory_config_1 = require_directory_config();
28836
+ Object.defineProperty(exports2, "DIRECTORY_TOKEN_HEADER", { enumerable: true, get: function() {
28837
+ return directory_config_1.DIRECTORY_TOKEN_HEADER;
28838
+ } });
28839
+ Object.defineProperty(exports2, "directoryGroupIdSchema", { enumerable: true, get: function() {
28840
+ return directory_config_1.directoryGroupIdSchema;
28841
+ } });
28842
+ Object.defineProperty(exports2, "directoryScopesSchema", { enumerable: true, get: function() {
28843
+ return directory_config_1.directoryScopesSchema;
28844
+ } });
28845
+ Object.defineProperty(exports2, "directoryMembersInput", { enumerable: true, get: function() {
28846
+ return directory_config_1.directoryMembersInput;
28847
+ } });
28848
+ Object.defineProperty(exports2, "directoryMembersResponse", { enumerable: true, get: function() {
28849
+ return directory_config_1.directoryMembersResponse;
28850
+ } });
28851
+ Object.defineProperty(exports2, "readDirectoryGroups", { enumerable: true, get: function() {
28852
+ return directory_config_1.readDirectoryGroups;
28853
+ } });
28630
28854
  var processes_contract_1 = require_processes_contract();
28631
28855
  Object.defineProperty(exports2, "FUNCTION_PROCESS_TOKEN_HEADER", { enumerable: true, get: function() {
28632
28856
  return processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER;
@@ -56959,8 +57183,15 @@ var require_valkey = __commonJS({
56959
57183
 
56960
57184
  // dist/index.js
56961
57185
  Object.defineProperty(exports, "__esModule", { value: true });
56962
- exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = exports.FunctionProcessReadError = exports.createFunctionServiceProcessesClient = void 0;
57186
+ exports.readCacheConfiguration = exports.cacheManifestSchema = exports.createCacheManager = exports.CacheError = exports.createFunctionStorageClient = exports.runWithFunctionTracing = exports.installFunctionTracing = exports.FunctionProcessReadError = exports.createFunctionServiceProcessesClient = exports.FunctionDirectoryReadError = exports.createFunctionDirectoryClient = void 0;
56963
57187
  exports.createManagedFunctionContext = createManagedFunctionContext;
57188
+ var directory_1 = require_directory();
57189
+ Object.defineProperty(exports, "createFunctionDirectoryClient", { enumerable: true, get: function() {
57190
+ return directory_1.createFunctionDirectoryClient;
57191
+ } });
57192
+ Object.defineProperty(exports, "FunctionDirectoryReadError", { enumerable: true, get: function() {
57193
+ return directory_1.FunctionDirectoryReadError;
57194
+ } });
56964
57195
  var processes_1 = require_processes();
56965
57196
  Object.defineProperty(exports, "createFunctionServiceProcessesClient", { enumerable: true, get: function() {
56966
57197
  return processes_1.createFunctionServiceProcessesClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequenceholdings/managed-functions",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Managed Function context and opt-in cache runtime",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  "vitest": "^4.1.11",
49
49
  "esbuild": "^0.25.0",
50
50
  "tsup": "^8.3.0",
51
- "@sequenceholdings/orm": "2.6.0"
51
+ "@sequenceholdings/orm": "2.6.1"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=20"