@segmentstream-cloud/ontology 0.23.0 → 0.24.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.
Files changed (40) hide show
  1. package/README.md +16 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/manifest.d.ts +5 -0
  5. package/dist/manifest.js +2 -0
  6. package/dist/migrate/apply.js +2 -1
  7. package/dist/migrate/model.d.ts +2 -0
  8. package/dist/migrate/plan.js +9 -1
  9. package/dist/migrate/storage.d.ts +3 -0
  10. package/dist/migrate/storage.js +10 -0
  11. package/dist/permissions.d.ts +3 -0
  12. package/dist/permissions.js +8 -1
  13. package/dist/runtime/history.d.ts +22 -0
  14. package/dist/runtime/history.js +2 -0
  15. package/dist/runtime/pipeline.js +6 -2
  16. package/dist/runtime/principals.d.ts +4 -1
  17. package/dist/runtime/principals.js +141 -63
  18. package/dist/runtime/reads.js +9 -1
  19. package/dist/runtime/service-binding.js +2 -1
  20. package/dist/runtime/sql-runtime.d.ts +12 -2
  21. package/dist/runtime/sql-runtime.js +33 -0
  22. package/dist/runtime/workload.d.ts +23 -0
  23. package/dist/runtime/workload.js +22 -0
  24. package/dist/runtime/writes.js +7 -1
  25. package/dist/runtime.d.ts +3 -0
  26. package/dist/service/authentication.d.ts +11 -0
  27. package/dist/service/authentication.js +114 -5
  28. package/dist/service/config.d.ts +4 -0
  29. package/dist/service/config.js +11 -0
  30. package/dist/service/handlers.js +2 -1
  31. package/dist/service/mcp/server.js +5 -4
  32. package/dist/service/operations.d.ts +25 -5
  33. package/dist/service/runtime-contract.d.ts +1 -0
  34. package/dist/service/schemas.d.ts +122 -0
  35. package/dist/storage/attachments.js +4 -4
  36. package/dist/storage/authorization.d.ts +1 -1
  37. package/dist/storage/authorization.js +9 -4
  38. package/dist/storage/sessions.js +3 -3
  39. package/dist/storage/writes.js +1 -1
  40. package/package.json +2 -1
@@ -1,9 +1,9 @@
1
1
  import { Effect } from "effect";
2
2
  import type { SqlClient } from "effect/unstable/sql";
3
- import { type NotFound } from "../errors.js";
3
+ import { Conflict, type NotFound } from "../errors.js";
4
4
  import type { Ontology } from "../ontology.js";
5
5
  import type { Query, QueryResult } from "../query.js";
6
- import type { OntologyRuntimeService, StoredRecord } from "../runtime.js";
6
+ import type { OntologyRuntimeService, StoredRecord, StoredValues } from "../runtime.js";
7
7
  import { InvalidQuery } from "./errors.js";
8
8
  import type { Edit, HistoryEntry, HistoryScope } from "./history.js";
9
9
  /**
@@ -39,6 +39,16 @@ export interface SqlOntologyRuntime extends OntologyRuntimeService {
39
39
  readonly limit?: number;
40
40
  }) => Effect.Effect<QueryResult<HistoryEntry>, InvalidQuery>;
41
41
  }
42
+ /**
43
+ * Platform identity bookkeeping touches only User properties and their history. User is excluded
44
+ * from business replication; keep this internal capability separate from the general runtime so
45
+ * deleting users, cascading relationships or editing business records still takes its fence.
46
+ */
47
+ export declare function makePrincipalUserWriter(ontology: Ontology, sql: SqlClient.SqlClient, scope: Pick<HistoryScope, "action" | "actionId">): {
48
+ recordsSignIn: boolean;
49
+ create: (values: StoredValues) => Effect.Effect<Readonly<Record<string, unknown>>, Conflict | NotFound, never>;
50
+ update: (id: string, values: StoredValues) => Effect.Effect<Readonly<Record<string, unknown>>, Conflict | NotFound, never>;
51
+ };
42
52
  /**
43
53
  * The runtime over one SQL client, normally `@effect/sql-pg`'s. Statements are parameterised; the
44
54
  * client runs them on the transaction of the calling fiber, so a runtime made for an action shares
@@ -2,6 +2,8 @@ import { Effect } from "effect";
2
2
  import { beginReplicationWrite } from "../replication/transaction.js";
3
3
  import { isSqlError } from "effect/unstable/sql/SqlError";
4
4
  import { Conflict } from "../errors.js";
5
+ import { toSnakeCase } from "../names.js";
6
+ import { User } from "../user.js";
5
7
  import { InvalidQuery, WriteOutsideAction } from "./errors.js";
6
8
  import { entityModel, ontologyModel } from "./model.js";
7
9
  import { loadRecord, notFound, readHistory, relatedCondition, runQuery, validateQuery, } from "./reads.js";
@@ -41,6 +43,37 @@ function uniqueConflict(entity, error) {
41
43
  message: `A ${label} with this ${field.name} already exists.`,
42
44
  }));
43
45
  }
46
+ /**
47
+ * Platform identity bookkeeping touches only User properties and their history. User is excluded
48
+ * from business replication; keep this internal capability separate from the general runtime so
49
+ * deleting users, cascading relationships or editing business records still takes its fence.
50
+ */
51
+ export function makePrincipalUserWriter(ontology, sql, scope) {
52
+ const model = ontologyModel(ontology);
53
+ const user = entityModel(model, User.name);
54
+ const allowed = new Set(Object.keys(User.definition.properties).map(toSnakeCase));
55
+ const client = sql.withoutTransforms();
56
+ const context = () => ({
57
+ model,
58
+ scope: { action: scope.action, actionId: scope.actionId, principalId: null },
59
+ edits: [],
60
+ run: (text, params) => client.unsafe(text, params.values),
61
+ });
62
+ const write = (values, operation) => Effect.suspend(() => {
63
+ if (!user.manifest.builtIn ||
64
+ Object.keys(values).some((name) => !allowed.has(name) || user.fieldsByName.get(name)?.source !== "property")) {
65
+ return Effect.die(new Error("Principal writes accept only built-in User properties."));
66
+ }
67
+ return sql
68
+ .withTransaction(operation())
69
+ .pipe(Effect.catchIf(isSqlError, (error) => uniqueConflict(user, error)));
70
+ });
71
+ return {
72
+ recordsSignIn: user.fieldsByName.has("last_sign_in_at"),
73
+ create: (values) => write(values, () => createRecord(context(), user, values)),
74
+ update: (id, values) => write(values, () => updateRecord(context(), user, id, values)),
75
+ };
76
+ }
44
77
  /**
45
78
  * The runtime over one SQL client, normally `@effect/sql-pg`'s. Statements are parameterised; the
46
79
  * client runs them on the transaction of the calling fiber, so a runtime made for an action shares
@@ -0,0 +1,23 @@
1
+ import { Schema } from "effect";
2
+ /** Verified platform claims retained with each write; never supplied by request headers. */
3
+ export declare const WorkloadExecution: Schema.Struct<{
4
+ readonly issuer: Schema.String;
5
+ readonly audience: Schema.String;
6
+ readonly organizationId: Schema.String;
7
+ readonly workspaceId: Schema.String;
8
+ readonly subject: Schema.String;
9
+ readonly deploymentId: Schema.String;
10
+ readonly ontologyDeploymentId: Schema.String;
11
+ readonly servingOntologyDeploymentId: Schema.String;
12
+ readonly ontologyOrigin: Schema.String;
13
+ readonly environment: Schema.Literal<"production">;
14
+ readonly jobName: Schema.String;
15
+ readonly runId: Schema.String;
16
+ readonly executionId: Schema.NullOr<Schema.String>;
17
+ readonly attemptId: Schema.String;
18
+ readonly generation: Schema.Number;
19
+ readonly tokenId: Schema.String;
20
+ readonly issuedAt: Schema.Number;
21
+ readonly expiresAt: Schema.Number;
22
+ }>;
23
+ export type WorkloadExecution = typeof WorkloadExecution.Type;
@@ -0,0 +1,22 @@
1
+ import { Schema } from "effect";
2
+ /** Verified platform claims retained with each write; never supplied by request headers. */
3
+ export const WorkloadExecution = Schema.Struct({
4
+ issuer: Schema.String,
5
+ audience: Schema.String,
6
+ organizationId: Schema.String,
7
+ workspaceId: Schema.String,
8
+ subject: Schema.String,
9
+ deploymentId: Schema.String,
10
+ ontologyDeploymentId: Schema.String,
11
+ servingOntologyDeploymentId: Schema.String,
12
+ ontologyOrigin: Schema.String,
13
+ environment: Schema.Literal("production"),
14
+ jobName: Schema.String,
15
+ runId: Schema.String,
16
+ executionId: Schema.NullOr(Schema.String),
17
+ attemptId: Schema.String,
18
+ generation: Schema.Number,
19
+ tokenId: Schema.String,
20
+ issuedAt: Schema.Number,
21
+ expiresAt: Schema.Number,
22
+ });
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { resolveBlobWrites } from "../storage/writes.js";
3
3
  import { Conflict } from "../errors.js";
4
- import { quoteIdentifier } from "../migrate/storage.js";
4
+ import { ACTION_ATTRIBUTION_TABLE, quoteIdentifier } from "../migrate/storage.js";
5
5
  import { InvalidValues } from "./errors.js";
6
6
  import { uuidv7 } from "./ids.js";
7
7
  import { diffRecords, insertHistory } from "./history.js";
@@ -13,6 +13,12 @@ import { columnOf, Params } from "./statement.js";
13
13
  function recordEdit(context, entity, edit) {
14
14
  return Effect.gen(function* () {
15
15
  const params = new Params();
16
+ if (context.scope.execution !== undefined) {
17
+ const attribution = new Params();
18
+ yield* context.run(`INSERT INTO "${ACTION_ATTRIBUTION_TABLE}" (action_id, execution) VALUES (` +
19
+ `${attribution.add(context.scope.actionId, "uuid")}, ` +
20
+ `${attribution.add(JSON.stringify(context.scope.execution), "jsonb")}) ON CONFLICT DO NOTHING`, attribution);
21
+ }
16
22
  yield* context.run(insertHistory(context.scope, { entity, edit }, params), params);
17
23
  context.edits.push(edit);
18
24
  });
package/dist/runtime.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { Context, Effect } from "effect";
2
2
  import type { Conflict, NotFound } from "./errors.js";
3
3
  import type { Where } from "./query.js";
4
+ import type { WorkloadExecution } from "./runtime/workload.js";
4
5
  /** Who runs an action: an app member or a service principal, both `user` records. */
5
6
  export interface Principal {
7
+ /** Present only for a verified managed pipeline worker. */
8
+ readonly execution?: WorkloadExecution;
6
9
  /** The id of the principal's `user` record. */
7
10
  readonly id: string;
8
11
  readonly kind: "member" | "service";
@@ -2,6 +2,7 @@ import { Context, Effect, Layer, Redacted } from "effect";
2
2
  import { HttpServerRequest, type HttpServerResponse } from "effect/unstable/http";
3
3
  import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi";
4
4
  import { type JWTVerifyGetKey } from "jose";
5
+ import type { WorkloadExecution } from "../runtime/workload.js";
5
6
  import type { Principal } from "../runtime.js";
6
7
  import type { NeonAuthEnvironment } from "./config.js";
7
8
  import { RequestId } from "./errors.js";
@@ -32,6 +33,10 @@ declare const Authentication_base: HttpApiMiddleware.ServiceClass<Authentication
32
33
  export declare class Authentication extends Authentication_base {
33
34
  }
34
35
  export interface TokenVerifierConfig {
36
+ /** Production workload activation and the deployment actually serving the request. */
37
+ readonly workloadTarget?: {
38
+ readonly deploymentId: string;
39
+ };
35
40
  /** The control plane: the issuer of app tokens and the host of their JWKS. */
36
41
  readonly segmentstreamUrl: string;
37
42
  readonly applicationId: string;
@@ -39,6 +44,9 @@ export interface TokenVerifierConfig {
39
44
  readonly organizationId: string;
40
45
  }
41
46
  export type TokenVerification = {
47
+ readonly status: "workload";
48
+ readonly execution: WorkloadExecution;
49
+ } | {
42
50
  readonly status: "valid";
43
51
  readonly identity: SignInIdentity;
44
52
  } | {
@@ -56,6 +64,9 @@ export declare function neonAuthKeySet(jwksUrl: string): JWTVerifyGetKey;
56
64
  * `SEGMENTSTREAM_URL` for this application (`aud: app:<application id>`), organization and workspace.
57
65
  */
58
66
  export declare function makeTokenVerifier(config: TokenVerifierConfig, keySet: JWTVerifyGetKey): TokenVerifier;
67
+ export declare const WORKLOAD_TOKEN_TYPE = "segmentstream-ontology-workload+jwt";
68
+ /** A separate verifier: a workload token can never resolve as a human or act as an agent. */
69
+ export declare function makeWorkloadTokenVerifier(config: TokenVerifierConfig, keySet: JWTVerifyGetKey): TokenVerifier;
59
70
  /**
60
71
  * Neon Auth's issuer is the origin of its Auth URL: `https://ep-….neon.tech/ontology/auth` issues
61
72
  * tokens with `iss: https://ep-….neon.tech`.
@@ -1,7 +1,7 @@
1
1
  import { Cause, Context, Effect, Layer, Redacted } from "effect";
2
2
  import { HttpServerRequest } from "effect/unstable/http";
3
3
  import { HttpApiMiddleware, HttpApiSecurity, OpenApi } from "effect/unstable/httpapi";
4
- import { createRemoteJWKSet, decodeJwt, errors, jwtVerify, } from "jose";
4
+ import { createRemoteJWKSet, decodeJwt, decodeProtectedHeader, errors, jwtVerify, } from "jose";
5
5
  import { API_KEY_PREFIX } from "../runtime/principals.js";
6
6
  import { RequestId, crossOriginSession, forbidden, internalError, notInvited, toServerResponse, unauthorized, unavailable, } from "./errors.js";
7
7
  import { ACCESS_TOKEN_PREFIX, principalOfAccessToken } from "./oauth/protocol.js";
@@ -76,6 +76,7 @@ export function makeTokenVerifier(config, keySet) {
76
76
  try {
77
77
  const verified = await jwtVerify(token, keySet, {
78
78
  algorithms: ["ES256"],
79
+ typ: "JWT",
79
80
  issuer,
80
81
  audience,
81
82
  clockTolerance: CLOCK_TOLERANCE_SECONDS,
@@ -90,6 +91,96 @@ export function makeTokenVerifier(config, keySet) {
90
91
  return identity === undefined ? { status: "invalid" } : { status: "valid", identity };
91
92
  };
92
93
  }
94
+ export const WORKLOAD_TOKEN_TYPE = "segmentstream-ontology-workload+jwt";
95
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
96
+ /** A separate verifier: a workload token can never resolve as a human or act as an agent. */
97
+ export function makeWorkloadTokenVerifier(config, keySet) {
98
+ return async (token) => {
99
+ if (config.workloadTarget === undefined) {
100
+ return { status: "invalid" };
101
+ }
102
+ let payload;
103
+ try {
104
+ const verified = await jwtVerify(token, keySet, {
105
+ algorithms: ["ES256"],
106
+ typ: WORKLOAD_TOKEN_TYPE,
107
+ issuer: new URL(config.segmentstreamUrl).origin,
108
+ audience: `ontology:${config.applicationId}`,
109
+ clockTolerance: CLOCK_TOLERANCE_SECONDS,
110
+ requiredClaims: [
111
+ "sub",
112
+ "exp",
113
+ "iat",
114
+ "jti",
115
+ "org",
116
+ "wsp",
117
+ "deploymentId",
118
+ "ontologyDeploymentId",
119
+ "ontologyOrigin",
120
+ "environment",
121
+ "jobName",
122
+ "runId",
123
+ "executionId",
124
+ "attemptId",
125
+ "generation",
126
+ ],
127
+ });
128
+ payload = verified.payload;
129
+ }
130
+ catch (error) {
131
+ return keySetUnavailable(error) ? { status: "unavailable" } : { status: "invalid" };
132
+ }
133
+ const strings = ["jti", "jobName"];
134
+ const ids = ["deploymentId", "ontologyDeploymentId", "runId", "attemptId"];
135
+ if (payload.org !== config.organizationId ||
136
+ payload.wsp !== config.workspaceId ||
137
+ payload.sub !== `pipeline-workers:${config.workspaceId}` ||
138
+ payload.environment !== "production" ||
139
+ payload.aud !== `ontology:${config.applicationId}` ||
140
+ strings.some((name) => typeof payload[name] !== "string" || payload[name].trim() === "") ||
141
+ ids.some((name) => typeof payload[name] !== "string" || !UUID.test(payload[name])) ||
142
+ (payload.executionId !== null &&
143
+ (typeof payload.executionId !== "string" || !UUID.test(payload.executionId))) ||
144
+ !Number.isSafeInteger(payload.generation) ||
145
+ payload.generation < 1 ||
146
+ !Number.isSafeInteger(payload.iat) ||
147
+ !Number.isSafeInteger(payload.exp) ||
148
+ payload.iat > Math.floor(Date.now() / 1000) + CLOCK_TOLERANCE_SECONDS ||
149
+ payload.exp <= payload.iat ||
150
+ payload.exp - payload.iat > 300 ||
151
+ typeof payload.ontologyOrigin !== "string" ||
152
+ !URL.canParse(payload.ontologyOrigin)) {
153
+ return { status: "invalid" };
154
+ }
155
+ const origin = new URL(payload.ontologyOrigin);
156
+ if (origin.protocol !== "https:" || origin.origin !== payload.ontologyOrigin) {
157
+ return { status: "invalid" };
158
+ }
159
+ return {
160
+ status: "workload",
161
+ execution: {
162
+ issuer: payload.iss,
163
+ audience: payload.aud,
164
+ organizationId: config.organizationId,
165
+ workspaceId: config.workspaceId,
166
+ subject: payload.sub,
167
+ deploymentId: payload.deploymentId,
168
+ ontologyDeploymentId: payload.ontologyDeploymentId,
169
+ servingOntologyDeploymentId: config.workloadTarget.deploymentId,
170
+ ontologyOrigin: payload.ontologyOrigin,
171
+ environment: "production",
172
+ jobName: payload.jobName,
173
+ runId: payload.runId,
174
+ executionId: payload.executionId,
175
+ attemptId: payload.attemptId,
176
+ generation: payload.generation,
177
+ tokenId: payload.jti,
178
+ issuedAt: payload.iat,
179
+ expiresAt: payload.exp,
180
+ },
181
+ };
182
+ };
183
+ }
93
184
  /**
94
185
  * Neon Auth's issuer is the origin of its Auth URL: `https://ep-….neon.tech/ontology/auth` issues
95
186
  * tokens with `iss: https://ep-….neon.tech`.
@@ -166,11 +257,21 @@ export function verifyByIssuer(verifiers) {
166
257
  * once it has Neon Auth. Both are accepted while workspaces move to Neon Auth.
167
258
  */
168
259
  export function tokenVerifier(config) {
260
+ const platformKeys = remoteKeySet(config.segmentstreamUrl);
261
+ const human = makeTokenVerifier(config, platformKeys);
262
+ const workload = makeWorkloadTokenVerifier(config, platformKeys);
263
+ const platform = (token) => {
264
+ try {
265
+ return decodeProtectedHeader(token).typ === WORKLOAD_TOKEN_TYPE
266
+ ? workload(token)
267
+ : human(token);
268
+ }
269
+ catch {
270
+ return Promise.resolve({ status: "invalid" });
271
+ }
272
+ };
169
273
  const verifiers = new Map([
170
- [
171
- new URL(config.segmentstreamUrl).origin,
172
- makeTokenVerifier(config, remoteKeySet(config.segmentstreamUrl)),
173
- ],
274
+ [new URL(config.segmentstreamUrl).origin, platform],
174
275
  ]);
175
276
  if (config.neonAuth !== undefined) {
176
277
  verifiers.set(neonAuthIssuer(config.neonAuth.baseUrl), makeNeonAuthTokenVerifier(config.neonAuth, neonAuthKeySet(config.neonAuth.jwksUrl)));
@@ -337,6 +438,14 @@ export function authenticate(credential, verifyToken, options = {}) {
337
438
  }
338
439
  }
339
440
  const verification = yield* Effect.promise(() => verifyToken(token));
441
+ if (verification.status === "workload") {
442
+ const principal = backend.resolvePipelineWorker === undefined
443
+ ? undefined
444
+ : yield* backend.resolvePipelineWorker(verification.execution);
445
+ return principal === undefined
446
+ ? { status: "rejected" }
447
+ : { status: "authenticated", principal };
448
+ }
340
449
  if (verification.status !== "valid") {
341
450
  return verification.status === "invalid"
342
451
  ? { status: "rejected" }
@@ -66,6 +66,10 @@ export interface OntologyEnvironment {
66
66
  * `segmentstreamUrl` for `workspaceId`; previews and local development have none.
67
67
  */
68
68
  readonly appCredential?: Redacted.Redacted<string>;
69
+ /** Production deployment identity; absent in local, preview and earlier deployments. */
70
+ readonly workloadTarget?: {
71
+ readonly deploymentId: string;
72
+ };
69
73
  /** Present once the workspace has Neon Auth: members then sign in with its tokens. */
70
74
  readonly neonAuth?: NeonAuthEnvironment;
71
75
  /**
@@ -134,6 +134,13 @@ export function configFromEnv(environment = process.env) {
134
134
  message: `The ontology service cannot start: ${list} ${verb} not set.`,
135
135
  });
136
136
  }
137
+ const deploymentId = read("SEGMENTSTREAM_ONTOLOGY_DEPLOYMENT_ID");
138
+ if (deploymentId !== "" &&
139
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(deploymentId)) {
140
+ throw new OntologyServiceConfigError({
141
+ message: "SEGMENTSTREAM_ONTOLOGY_DEPLOYMENT_ID must be a UUID.",
142
+ });
143
+ }
137
144
  const neonAuth = neonAuthFromEnv(read);
138
145
  const runtimeIdentity = runtimeIdentityFromEnv(read);
139
146
  const localConnections = localConnectionsFromEnv(environment);
@@ -163,6 +170,10 @@ export function configFromEnv(environment = process.env) {
163
170
  ...(read(APP_CREDENTIAL_VARIABLE)
164
171
  ? { appCredential: Redacted.make(read(APP_CREDENTIAL_VARIABLE)) }
165
172
  : {}),
173
+ ...(environment.VERCEL_ENV === "production" &&
174
+ read("SEGMENTSTREAM_ONTOLOGY_DEPLOYMENT_ID") !== ""
175
+ ? { workloadTarget: { deploymentId: read("SEGMENTSTREAM_ONTOLOGY_DEPLOYMENT_ID") } }
176
+ : {}),
166
177
  ...(neonAuth === undefined ? {} : { neonAuth }),
167
178
  ...(runtimeIdentity === undefined ? {} : { runtimeIdentity }),
168
179
  ...(runtimeIdentity === undefined || read(AGENT_HOST_VARIABLE) === ""
@@ -1,3 +1,4 @@
1
+ import { effectivePermissions } from "../permissions.js";
1
2
  import { HttpServerResponse } from "effect/unstable/http";
2
3
  import { Effect, Layer, Schema } from "effect";
3
4
  import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi";
@@ -145,7 +146,7 @@ function serviceHandlers(built, operationsOf, aggregate) {
145
146
  return undefined;
146
147
  }
147
148
  const principal = yield* CurrentPrincipal;
148
- return principal.permissions;
149
+ return effectivePermissions(principal);
149
150
  });
150
151
  return handlers
151
152
  .handle("catalog", (request) => Effect.map(permissionsFor(request.query.as), (permissions) => catalogFor(manifest, permissions)))
@@ -1,3 +1,4 @@
1
+ import { effectivePermissions } from "../../permissions.js";
1
2
  import { Cause, Data, Effect, Layer, Option, Schema, SchemaTransformation } from "effect";
2
3
  import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
3
4
  import { HttpApiBuilder, HttpApiSecurity } from "effect/unstable/httpapi";
@@ -175,7 +176,7 @@ function runTool({ built, operations, aggregate, conversations }, tool, args, re
175
176
  case "describe": {
176
177
  const principal = yield* CurrentPrincipal;
177
178
  return {
178
- text: describeCatalog(built.plan.ontology.manifest, principal.permissions),
179
+ text: describeCatalog(built.plan.ontology.manifest, effectivePermissions(principal)),
179
180
  };
180
181
  }
181
182
  case "aggregate": {
@@ -285,7 +286,7 @@ function readResource({ built }, uri) {
285
286
  {
286
287
  uri,
287
288
  mimeType: "application/json",
288
- text: JSON.stringify(encodeManifest(catalogFor(manifest, principal.permissions))),
289
+ text: JSON.stringify(encodeManifest(catalogFor(manifest, effectivePermissions(principal)))),
289
290
  },
290
291
  ];
291
292
  case DESCRIBE_URI:
@@ -293,7 +294,7 @@ function readResource({ built }, uri) {
293
294
  {
294
295
  uri,
295
296
  mimeType: "text/plain",
296
- text: describeCatalog(manifest, principal.permissions),
297
+ text: describeCatalog(manifest, effectivePermissions(principal)),
297
298
  },
298
299
  ];
299
300
  default:
@@ -354,7 +355,7 @@ function dispatch(server, message, request) {
354
355
  title: "Ontology",
355
356
  version: server.built.plan.ontology.manifest.hash,
356
357
  },
357
- instructions: instructions(server, principal.permissions),
358
+ instructions: instructions(server, effectivePermissions(principal)),
358
359
  });
359
360
  case "ping":
360
361
  return rpcResult(id, {});
@@ -74,19 +74,39 @@ export declare function makeOperations(backend: Backend, plan: ApiPlan, hooks: R
74
74
  readonly after?: string;
75
75
  readonly limit?: number;
76
76
  }) => Effect.Effect<QueryResult<{
77
+ readonly at: string;
77
78
  readonly id: string;
78
- readonly recordId: string;
79
- readonly action: string;
80
- readonly actionId: string;
81
79
  readonly kind: "create" | "update" | "delete" | "link" | "unlink";
82
- readonly principalId: string | null;
83
- readonly at: string;
80
+ readonly action: string;
84
81
  readonly changes: {
85
82
  readonly [x: string]: {
86
83
  readonly before: import("effect/Schema").Json;
87
84
  readonly after: import("effect/Schema").Json;
88
85
  };
89
86
  };
87
+ readonly principalId: string | null;
88
+ readonly recordId: string;
89
+ readonly actionId: string;
90
+ readonly execution?: {
91
+ readonly issuer: string;
92
+ readonly audience: string;
93
+ readonly organizationId: string;
94
+ readonly workspaceId: string;
95
+ readonly subject: string;
96
+ readonly deploymentId: string;
97
+ readonly ontologyDeploymentId: string;
98
+ readonly servingOntologyDeploymentId: string;
99
+ readonly ontologyOrigin: string;
100
+ readonly environment: "production";
101
+ readonly jobName: string;
102
+ readonly runId: string;
103
+ readonly executionId: string | null;
104
+ readonly attemptId: string;
105
+ readonly generation: number;
106
+ readonly tokenId: string;
107
+ readonly issuedAt: number;
108
+ readonly expiresAt: number;
109
+ } | undefined;
90
110
  }>, PermissionDenied | import("../errors.js").NotFound | import("../index.js").InvalidQuery, CurrentPrincipal>;
91
111
  /** The records a relationship reaches from one record, as a page of the target entity. */
92
112
  traverse: (planned: PlannedEntity, traversal: Traversal, id: string, query: Query) => Effect.Effect<QueryResult<Readonly<Record<string, unknown>>>, PermissionDenied | import("../errors.js").NotFound | import("../index.js").InvalidQuery, CurrentPrincipal>;
@@ -274,6 +274,7 @@ declare const OntologyBackend_base: Context.ServiceClass<OntologyBackend, "@segm
274
274
  * Finds, links or creates the member's user. `undefined` when the user may not sign in, and
275
275
  * {@link NotInvited} when the sign-in policy turns away a person nobody invited.
276
276
  */
277
+ readonly resolvePipelineWorker?: (execution: import("../runtime/workload.js").WorkloadExecution) => Effect.Effect<Principal | undefined>;
277
278
  readonly resolvePrincipal: (identity: SignInIdentity) => Effect.Effect<Principal | undefined, NotInvited>;
278
279
  /**
279
280
  * Whether a person may sign in with this address, and how, read before the app emails them.