@opengeni/core 0.28.1 → 2.1.0-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.
Files changed (99) hide show
  1. package/dist/access/index.d.ts +37 -0
  2. package/dist/application/new-session-drafts.d.ts +8 -1
  3. package/dist/application/session-commands.d.ts +18 -11
  4. package/dist/application/session-tenancy.d.ts +18 -0
  5. package/dist/application/user-resource-grants.d.ts +20 -0
  6. package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
  7. package/dist/chunk-H7X52RI2.js.map +1 -0
  8. package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
  9. package/dist/chunk-VCZFFEPB.js.map +1 -0
  10. package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
  11. package/dist/chunk-ZYCNLYGH.js.map +1 -0
  12. package/dist/dependencies.d.ts +12 -1
  13. package/dist/domain/automations.d.ts +51 -0
  14. package/dist/domain/company-brain-governed-writes.d.ts +70 -0
  15. package/dist/domain/company-profile-agent-admin.d.ts +24 -0
  16. package/dist/domain/connection-authority.d.ts +97 -0
  17. package/dist/domain/connection-authority.js +169 -0
  18. package/dist/domain/connection-authority.js.map +1 -0
  19. package/dist/domain/editable-artifacts/service.d.ts +1 -2
  20. package/dist/domain/environments.d.ts +1 -1
  21. package/dist/domain/github-repository-bindings.d.ts +117 -0
  22. package/dist/domain/governed-learning-activation.d.ts +16 -0
  23. package/dist/domain/governed-learning-evaluator.d.ts +6 -0
  24. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  25. package/dist/domain/memory-slack-delivery.d.ts +1 -4
  26. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  27. package/dist/domain/packs.d.ts +5 -1
  28. package/dist/domain/personal-connection-delegations.d.ts +44 -2
  29. package/dist/domain/pr-review.d.ts +149 -0
  30. package/dist/domain/remember.d.ts +69 -0
  31. package/dist/domain/resources.d.ts +23 -2
  32. package/dist/domain/scheduled-tasks.d.ts +43 -2
  33. package/dist/domain/session-tool-policy.d.ts +1 -1
  34. package/dist/domain/sessions.d.ts +43 -9
  35. package/dist/editable-artifact-live/application.d.ts +10 -2
  36. package/dist/editable-artifact-live/ports.d.ts +0 -1
  37. package/dist/editable-artifact-live/server.d.ts +0 -1
  38. package/dist/editable-artifact-live/ticket.d.ts +0 -1
  39. package/dist/editable-artifact-live/types.d.ts +5 -2
  40. package/dist/editable-artifact-live.js +2 -2
  41. package/dist/editable-artifacts.js +2 -4
  42. package/dist/index.d.ts +13 -1
  43. package/dist/index.js +4973 -1243
  44. package/dist/index.js.map +1 -1
  45. package/dist/rigs/index.d.ts +22 -5
  46. package/dist/sandbox/fleet.d.ts +34 -6
  47. package/dist/sandbox/routing.d.ts +15 -1
  48. package/dist/sandbox/runtime-settings.d.ts +39 -0
  49. package/package.json +16 -11
  50. package/src/access/index.ts +151 -31
  51. package/src/application/new-session-drafts.ts +32 -5
  52. package/src/application/session-commands.ts +305 -71
  53. package/src/application/session-tenancy.ts +321 -0
  54. package/src/application/user-resource-grants.ts +140 -0
  55. package/src/billing/limits.ts +25 -15
  56. package/src/dependencies.ts +12 -1
  57. package/src/domain/automations.ts +196 -0
  58. package/src/domain/capabilities.ts +7 -1
  59. package/src/domain/company-brain-governed-writes.ts +330 -0
  60. package/src/domain/company-profile-agent-admin.ts +55 -0
  61. package/src/domain/connection-authority.ts +329 -0
  62. package/src/domain/editable-artifacts/agent-application.ts +17 -9
  63. package/src/domain/editable-artifacts/service.ts +12 -1
  64. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  65. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  66. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  67. package/src/domain/environments.ts +16 -3
  68. package/src/domain/github-repository-bindings.ts +295 -0
  69. package/src/domain/governed-learning-activation.ts +44 -0
  70. package/src/domain/governed-learning-evaluator.ts +49 -0
  71. package/src/domain/governed-learning-slack-publication.ts +157 -0
  72. package/src/domain/insights.ts +9 -0
  73. package/src/domain/memory-slack-delivery.ts +0 -30
  74. package/src/domain/organization-membership-lifecycle.ts +9 -0
  75. package/src/domain/packs.ts +106 -3
  76. package/src/domain/personal-connection-delegations.ts +597 -42
  77. package/src/domain/pr-review.ts +671 -0
  78. package/src/domain/remember.ts +549 -0
  79. package/src/domain/resources.ts +144 -6
  80. package/src/domain/scheduled-tasks.ts +504 -77
  81. package/src/domain/session-tool-policy.ts +12 -9
  82. package/src/domain/sessions.ts +629 -176
  83. package/src/editable-artifact-live/application.ts +21 -3
  84. package/src/editable-artifact-live/ports.ts +0 -1
  85. package/src/editable-artifact-live/server.ts +13 -7
  86. package/src/editable-artifact-live/ticket.ts +1 -5
  87. package/src/editable-artifact-live/types.ts +6 -2
  88. package/src/editable-artifact-live/wire.ts +0 -10
  89. package/src/index.ts +13 -1
  90. package/src/rigs/index.ts +165 -48
  91. package/src/sandbox/fleet.ts +376 -30
  92. package/src/sandbox/routing.ts +91 -29
  93. package/src/sandbox/runtime-settings.ts +274 -0
  94. package/src/session-authorization.ts +17 -49
  95. package/dist/chunk-6WKPWE5S.js.map +0 -1
  96. package/dist/chunk-JJU6I5XD.js.map +0 -1
  97. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  98. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  99. package/src/domain/durable-learning-slack-publication.ts +0 -202
@@ -0,0 +1,295 @@
1
+ import type { RepositoryResourceRef, ResourceRef } from "@opengeni/contracts";
2
+ import {
3
+ hasAuditableGitHubInstallationAuthority,
4
+ listGitHubInstallationAccessForWorkspace,
5
+ type Database,
6
+ } from "@opengeni/db";
7
+
8
+ /**
9
+ * GitHub App repository-binding resolution for bare repository resources.
10
+ *
11
+ * A repository resource that names a GitHub repository by URI alone (no
12
+ * `githubInstallationId`/`githubRepositoryId`) never mints an installation
13
+ * token, so the sandbox clones it anonymously and cannot push or use `gh`.
14
+ * The workspace allowlist (`github_installation_repositories`) stores only
15
+ * numeric repository ids, so resolving a URI needs the installation's owner
16
+ * login from the durable binding plus one provider read for the repository id.
17
+ * This module owns the pure parts: URI parsing, candidate selection under the
18
+ * workspace's auditable bindings, and the exactly-one-match stamping rule. The
19
+ * provider read is injected so the worker can supply the live GitHub lookup
20
+ * while tests stay deterministic.
21
+ */
22
+
23
+ export type GitHubRepositoryCoordinates = { owner: string; name: string };
24
+
25
+ const GITHUB_OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/u;
26
+ const GITHUB_NAME_PATTERN = /^[A-Za-z0-9._-]+$/u;
27
+ const GITHUB_HOSTS = new Set(["github.com", "www.github.com"]);
28
+ const GITHUB_SCP_LIKE_PATTERN = /^git@(?:www\.)?github\.com:(.+)$/iu;
29
+
30
+ function coordinatesFromPath(path: string): GitHubRepositoryCoordinates | null {
31
+ const segments = path.split("/").filter(Boolean);
32
+ if (segments.length !== 2) return null;
33
+ const owner = segments[0]!;
34
+ const name = segments[1]!.replace(/\.git$/u, "");
35
+ if (!GITHUB_OWNER_PATTERN.test(owner) || !name || !GITHUB_NAME_PATTERN.test(name)) {
36
+ return null;
37
+ }
38
+ return { owner, name };
39
+ }
40
+
41
+ /**
42
+ * Parse a GitHub repository reference into owner/name, else null. Accepts
43
+ * `https://github.com/<owner>/<repo>[.git][/]`, `www.github.com`,
44
+ * `ssh://git@github.com/<owner>/<repo>[.git]`, and the scp-like
45
+ * `git@github.com:<owner>/<repo>[.git]`.
46
+ */
47
+ export function parseGitHubRepositoryCoordinates(uri: string): GitHubRepositoryCoordinates | null {
48
+ const trimmed = uri.trim();
49
+ const scpLike = GITHUB_SCP_LIKE_PATTERN.exec(trimmed);
50
+ if (scpLike) {
51
+ return coordinatesFromPath(scpLike[1]!);
52
+ }
53
+ let url: URL;
54
+ try {
55
+ url = new URL(trimmed);
56
+ } catch {
57
+ return null;
58
+ }
59
+ if (!GITHUB_HOSTS.has(url.hostname.toLowerCase()) || url.search || url.hash) {
60
+ return null;
61
+ }
62
+ if (url.protocol === "https:" || url.protocol === "http:") {
63
+ if (url.username || url.password) return null;
64
+ } else if (url.protocol === "ssh:") {
65
+ if (url.password || (url.username && url.username !== "git")) return null;
66
+ } else {
67
+ return null;
68
+ }
69
+ return coordinatesFromPath(url.pathname);
70
+ }
71
+
72
+ export type UnboundGitHubRepositoryResource = {
73
+ resource: RepositoryResourceRef;
74
+ owner: string;
75
+ name: string;
76
+ };
77
+
78
+ /**
79
+ * Repository resources that name a github.com repository without any
80
+ * platform identity: no GitHub App ids, no credential binding, no personal
81
+ * connection, and either the explicit `github` provider or no provider at all.
82
+ * Resources that already carry ids, legacy provider ids, a binding, or a
83
+ * connection are someone else's authority and are never rewritten here.
84
+ */
85
+ export function unboundGitHubRepositoryResources(
86
+ resources: readonly ResourceRef[],
87
+ ): UnboundGitHubRepositoryResource[] {
88
+ const out: UnboundGitHubRepositoryResource[] = [];
89
+ for (const resource of resources) {
90
+ if (resource.kind !== "repository") continue;
91
+ if (resource.provider !== undefined && resource.provider !== "github") continue;
92
+ if (
93
+ resource.githubInstallationId !== undefined ||
94
+ resource.githubRepositoryId !== undefined ||
95
+ resource.installationId !== undefined ||
96
+ resource.repositoryId !== undefined ||
97
+ resource.credentialBindingId !== undefined ||
98
+ resource.connectionId !== undefined ||
99
+ resource.connectionType !== undefined
100
+ ) {
101
+ continue;
102
+ }
103
+ const coordinates = parseGitHubRepositoryCoordinates(resource.uri);
104
+ if (!coordinates) continue;
105
+ out.push({ resource, ...coordinates });
106
+ }
107
+ return out;
108
+ }
109
+
110
+ export type GitHubRepositoryBindingCandidate = {
111
+ installationId: number;
112
+ accountLogin: string;
113
+ repositoryIds: number[];
114
+ };
115
+
116
+ /**
117
+ * The workspace's auditable GitHub App bindings, under workspace RLS. Only
118
+ * bindings that could authorize a token mint are candidates; legacy
119
+ * `unverified` rows never resolve a URI.
120
+ */
121
+ export async function listGitHubRepositoryBindingCandidates(
122
+ db: Database,
123
+ workspaceId: string,
124
+ ): Promise<GitHubRepositoryBindingCandidate[]> {
125
+ const installations = await listGitHubInstallationAccessForWorkspace(db, workspaceId);
126
+ return installations
127
+ .filter((installation) => hasAuditableGitHubInstallationAuthority(installation))
128
+ .map((installation) => ({
129
+ installationId: installation.installationId,
130
+ accountLogin: installation.accountLogin ?? "",
131
+ repositoryIds: [...installation.repositoryIds],
132
+ }))
133
+ .filter((candidate) => candidate.accountLogin.length > 0);
134
+ }
135
+
136
+ export type GitHubRepositoryBindingLookup = (input: {
137
+ installationId: number;
138
+ owner: string;
139
+ name: string;
140
+ }) => Promise<{ id: number } | null>;
141
+
142
+ export type GitHubRepositoryBinding = { installationId: number; repositoryId: number };
143
+
144
+ export type GitHubRepositoryBindingOutcome =
145
+ | { status: "resolved"; binding: GitHubRepositoryBinding }
146
+ /** No workspace binding owns this repository's account: anonymous, as before. */
147
+ | { status: "unbound" }
148
+ /** The account is bound, but no installation's allowlist holds this repository. */
149
+ | { status: "not_allowlisted"; installationIds: number[] }
150
+ /** More than one bound installation's allowlist holds this repository. */
151
+ | { status: "ambiguous"; installationIds: number[] }
152
+ /** The provider lookup failed (suspended/deleted installation, outage). */
153
+ | { status: "unavailable"; installationIds: number[]; message: string };
154
+
155
+ export type GitHubRepositoryBindingResolution = {
156
+ uri: string;
157
+ owner: string;
158
+ name: string;
159
+ outcome: GitHubRepositoryBindingOutcome;
160
+ };
161
+
162
+ export type GitHubRepositoryBindingResolutionResult = {
163
+ resources: ResourceRef[];
164
+ bindings: Map<string, GitHubRepositoryBinding>;
165
+ resolutions: GitHubRepositoryBindingResolution[];
166
+ };
167
+
168
+ /**
169
+ * Resolve every bare github.com repository resource against the workspace's
170
+ * auditable bindings. A resource is stamped only when exactly one bound
171
+ * installation's allowlist contains the repository id GitHub reports for that
172
+ * URI through that installation. Every other case leaves the resource bare
173
+ * and reports why, so the caller can surface a warning without ever failing
174
+ * the turn or the session. Distinct URIs resolve concurrently, as do the
175
+ * candidate installations of one owner.
176
+ */
177
+ export async function resolveGitHubRepositoryBindings(input: {
178
+ resources: readonly ResourceRef[];
179
+ candidates: readonly GitHubRepositoryBindingCandidate[];
180
+ lookup: GitHubRepositoryBindingLookup;
181
+ }): Promise<GitHubRepositoryBindingResolutionResult> {
182
+ const unbound = unboundGitHubRepositoryResources(input.resources);
183
+ const bindings = new Map<string, GitHubRepositoryBinding>();
184
+ if (unbound.length === 0) {
185
+ return { resources: [...input.resources], bindings, resolutions: [] };
186
+ }
187
+ const candidatesByLogin = new Map<string, GitHubRepositoryBindingCandidate[]>();
188
+ for (const candidate of input.candidates) {
189
+ const key = candidate.accountLogin.toLowerCase();
190
+ const list = candidatesByLogin.get(key) ?? [];
191
+ list.push(candidate);
192
+ candidatesByLogin.set(key, list);
193
+ }
194
+ const distinct = new Map<string, UnboundGitHubRepositoryResource>();
195
+ for (const entry of unbound) {
196
+ if (!distinct.has(entry.resource.uri)) distinct.set(entry.resource.uri, entry);
197
+ }
198
+ const resolutions = await Promise.all(
199
+ [...distinct.values()].map(async (entry) => ({
200
+ uri: entry.resource.uri,
201
+ owner: entry.owner,
202
+ name: entry.name,
203
+ outcome: await resolveOne(
204
+ entry,
205
+ candidatesByLogin.get(entry.owner.toLowerCase()) ?? [],
206
+ input.lookup,
207
+ ),
208
+ })),
209
+ );
210
+ for (const resolution of resolutions) {
211
+ if (resolution.outcome.status === "resolved") {
212
+ bindings.set(resolution.uri, resolution.outcome.binding);
213
+ }
214
+ }
215
+ return {
216
+ resources: applyGitHubRepositoryBindings(input.resources, bindings),
217
+ bindings,
218
+ resolutions,
219
+ };
220
+ }
221
+
222
+ async function resolveOne(
223
+ entry: UnboundGitHubRepositoryResource,
224
+ installations: readonly GitHubRepositoryBindingCandidate[],
225
+ lookup: GitHubRepositoryBindingLookup,
226
+ ): Promise<GitHubRepositoryBindingOutcome> {
227
+ if (installations.length === 0) {
228
+ return { status: "unbound" };
229
+ }
230
+ const installationIds = installations.map((installation) => installation.installationId);
231
+ const results = await Promise.allSettled(
232
+ installations.map(async (installation) => {
233
+ const found = await lookup({
234
+ installationId: installation.installationId,
235
+ owner: entry.owner,
236
+ name: entry.name,
237
+ });
238
+ return found &&
239
+ Number.isSafeInteger(found.id) &&
240
+ found.id > 0 &&
241
+ installation.repositoryIds.includes(found.id)
242
+ ? { installationId: installation.installationId, repositoryId: found.id }
243
+ : null;
244
+ }),
245
+ );
246
+ const failure = results.find(
247
+ (result): result is PromiseRejectedResult => result.status === "rejected",
248
+ );
249
+ if (failure) {
250
+ return {
251
+ status: "unavailable",
252
+ installationIds,
253
+ message: failure.reason instanceof Error ? failure.reason.message : String(failure.reason),
254
+ };
255
+ }
256
+ const matches = results.flatMap((result) =>
257
+ result.status === "fulfilled" && result.value ? [result.value] : [],
258
+ );
259
+ if (matches.length === 1) {
260
+ return { status: "resolved", binding: matches[0]! };
261
+ }
262
+ if (matches.length === 0) {
263
+ return { status: "not_allowlisted", installationIds };
264
+ }
265
+ return {
266
+ status: "ambiguous",
267
+ installationIds: matches.map((match) => match.installationId),
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Stamp resolved GitHub App ids onto the matching bare resources of any
273
+ * resource list (turn resources and runtime resources must agree, because the
274
+ * credential binding id and token filename derive from the installation id).
275
+ */
276
+ export function applyGitHubRepositoryBindings(
277
+ resources: readonly ResourceRef[],
278
+ bindings: ReadonlyMap<string, GitHubRepositoryBinding>,
279
+ ): ResourceRef[] {
280
+ if (bindings.size === 0) return [...resources];
281
+ const stampable = new Set(
282
+ unboundGitHubRepositoryResources(resources).map((entry) => entry.resource),
283
+ );
284
+ return resources.map((resource) => {
285
+ if (resource.kind !== "repository" || !stampable.has(resource)) return resource;
286
+ const binding = bindings.get(resource.uri);
287
+ if (!binding) return resource;
288
+ return {
289
+ ...resource,
290
+ provider: "github",
291
+ githubInstallationId: binding.installationId,
292
+ githubRepositoryId: binding.repositoryId,
293
+ };
294
+ });
295
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ GovernedLearningActivationCaller,
3
+ ActivateGovernedLearningDecisionRequest,
4
+ UndoGovernedLearningActivationRequest,
5
+ type GovernedLearningActivationReceipt,
6
+ type GovernedLearningActivationUndoReceipt,
7
+ } from "@opengeni/contracts";
8
+ import {
9
+ activateGovernedLearningDecision,
10
+ undoGovernedLearningActivation,
11
+ type Database,
12
+ } from "@opengeni/db";
13
+
14
+ export function createGovernedLearningActivationController(options: {
15
+ db: Database;
16
+ activate?: typeof activateGovernedLearningDecision;
17
+ undo?: typeof undoGovernedLearningActivation;
18
+ }): {
19
+ activate(input: {
20
+ caller: unknown;
21
+ request: unknown;
22
+ }): Promise<GovernedLearningActivationReceipt>;
23
+ undo(input: {
24
+ caller: unknown;
25
+ request: unknown;
26
+ }): Promise<GovernedLearningActivationUndoReceipt>;
27
+ } {
28
+ const activate = options.activate ?? activateGovernedLearningDecision;
29
+ const undo = options.undo ?? undoGovernedLearningActivation;
30
+ return {
31
+ async activate(input) {
32
+ return await activate(options.db, {
33
+ caller: GovernedLearningActivationCaller.parse(input.caller),
34
+ request: ActivateGovernedLearningDecisionRequest.parse(input.request),
35
+ });
36
+ },
37
+ async undo(input) {
38
+ return await undo(options.db, {
39
+ caller: GovernedLearningActivationCaller.parse(input.caller),
40
+ request: UndoGovernedLearningActivationRequest.parse(input.request),
41
+ });
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ GOVERNED_LEARNING_AUTOMATIC_CONFIDENCE_FLOOR_BPS,
3
+ GovernedLearningDecision,
4
+ GovernedLearningDecisionFacts,
5
+ type GovernedLearningDecision as GovernedLearningDecisionType,
6
+ type GovernedLearningDecisionFacts as GovernedLearningDecisionFactsType,
7
+ type GovernedLearningDecisionReason,
8
+ } from "@opengeni/contracts";
9
+
10
+ /**
11
+ * Pure, closed evaluator mirrored by the 0268 database authority. It never
12
+ * receives source content and cannot activate a destination authority.
13
+ */
14
+ export function deriveGovernedLearningDecision(
15
+ input: GovernedLearningDecisionFactsType,
16
+ ): GovernedLearningDecisionType {
17
+ const facts = GovernedLearningDecisionFacts.parse(input);
18
+ const reasons: GovernedLearningDecisionReason[] = [];
19
+
20
+ if (facts.effectiveMode === "off") reasons.push("policy_off");
21
+ if (facts.revoked) reasons.push("evidence_revoked");
22
+ if (facts.stale) reasons.push("proposal_stale");
23
+ if (facts.conflictCount > 0) reasons.push("evidence_conflict");
24
+ if (facts.confidenceBps < GOVERNED_LEARNING_AUTOMATIC_CONFIDENCE_FLOOR_BPS) {
25
+ reasons.push("confidence_below_floor");
26
+ }
27
+ if (facts.effectiveMode === "suggest") reasons.push("policy_suggest");
28
+ if (facts.effectiveMode === "automatic") reasons.push("policy_automatic");
29
+
30
+ const outcome =
31
+ facts.effectiveMode === "off"
32
+ ? "off"
33
+ : facts.revoked
34
+ ? "revoked"
35
+ : facts.stale
36
+ ? "stale"
37
+ : facts.conflictCount > 0
38
+ ? "conflict"
39
+ : facts.confidenceBps < GOVERNED_LEARNING_AUTOMATIC_CONFIDENCE_FLOOR_BPS
40
+ ? "confidence"
41
+ : facts.effectiveMode;
42
+
43
+ return GovernedLearningDecision.parse({
44
+ outcome,
45
+ reasons,
46
+ automaticEligible: outcome === "automatic",
47
+ confidenceFloorBps: GOVERNED_LEARNING_AUTOMATIC_CONFIDENCE_FLOOR_BPS,
48
+ });
49
+ }
@@ -0,0 +1,157 @@
1
+ import type {
2
+ GovernedLearningActivationReceipt,
3
+ GovernedLearningActivationUndoReceipt,
4
+ } from "@opengeni/contracts";
5
+ import {
6
+ enqueueMemorySlackPublication,
7
+ getCurrentMemorySlackPublicationConfiguration,
8
+ resolveGovernedLearningEvidenceOrigin,
9
+ type Database,
10
+ type EnqueueMemorySlackPublicationResult,
11
+ type GovernedLearningEvidenceOrigin,
12
+ } from "@opengeni/db";
13
+
14
+ /**
15
+ * Narrow post-persistence notification port for governed-learning activation
16
+ * and undo receipts (migration 0269). The evaluator/controller own policy,
17
+ * authority, and the immutable receipts; this adapter only projects bounded,
18
+ * content-free facts into the existing Slack publication outbox. It never
19
+ * reads proposal content, evidence bytes, or destination bodies.
20
+ */
21
+ export type GovernedLearningSlackEvent =
22
+ | {
23
+ kind: "activated";
24
+ receipt: GovernedLearningActivationReceipt;
25
+ sessionId: string | null;
26
+ attemptId: string | null;
27
+ }
28
+ | { kind: "undone"; receipt: GovernedLearningActivationUndoReceipt };
29
+
30
+ export type GovernedLearningSlackPublicationResult =
31
+ | {
32
+ kind: "evidence_origin_unresolved" | "slack_derived_evidence" | "policy_quiet";
33
+ enqueue: null;
34
+ }
35
+ | { kind: "enqueued"; enqueue: EnqueueMemorySlackPublicationResult };
36
+
37
+ export type GovernedLearningSlackProjection = {
38
+ summary: string;
39
+ outcome: "activated" | "undone";
40
+ destination: "instruction_policy" | "preference";
41
+ sourceKind: "scoped-knowledge-evidence" | "task-note" | null;
42
+ destinationRevisionId: string;
43
+ destinationVersion: number;
44
+ activationReceiptId: string;
45
+ undoReceiptId: string | null;
46
+ occurredAt: string;
47
+ };
48
+
49
+ const DESTINATION_LABEL: Record<"instruction_policy" | "preference", string> = {
50
+ instruction_policy: "workspace instruction policy",
51
+ preference: "workspace preference",
52
+ };
53
+ const SOURCE_LABEL: Record<"scoped-knowledge-evidence" | "task-note", string> = {
54
+ "scoped-knowledge-evidence": "workspace knowledge evidence",
55
+ "task-note": "a task note",
56
+ };
57
+
58
+ export function governedLearningSlackImportance(
59
+ event: GovernedLearningSlackEvent,
60
+ ): "major" | "normal" | "minor" {
61
+ if (event.kind === "undone") return "major";
62
+ return event.receipt.destination === "instruction_policy" ? "major" : "normal";
63
+ }
64
+
65
+ export function governedLearningSlackProjection(
66
+ event: GovernedLearningSlackEvent,
67
+ ): GovernedLearningSlackProjection {
68
+ if (event.kind === "activated") {
69
+ const receipt = event.receipt;
70
+ return {
71
+ summary: `Automatically activated a ${DESTINATION_LABEL[receipt.destination]} learned from ${SOURCE_LABEL[receipt.sourceKind]} (revision ${receipt.destinationNewVersion}). Undo is available through the workspace learning API.`,
72
+ outcome: "activated",
73
+ destination: receipt.destination,
74
+ sourceKind: receipt.sourceKind,
75
+ destinationRevisionId: receipt.destinationRevisionId,
76
+ destinationVersion: receipt.destinationNewVersion,
77
+ activationReceiptId: receipt.id,
78
+ undoReceiptId: null,
79
+ occurredAt: receipt.effectiveAt,
80
+ };
81
+ }
82
+ const receipt = event.receipt;
83
+ return {
84
+ summary: `Undid an automatic ${DESTINATION_LABEL[receipt.destination]} activation (revision ${receipt.destinationOldVersion} → ${receipt.destinationNewVersion}).`,
85
+ outcome: "undone",
86
+ destination: receipt.destination,
87
+ sourceKind: null,
88
+ destinationRevisionId: receipt.destinationActivatedRevisionId,
89
+ destinationVersion: receipt.destinationNewVersion,
90
+ activationReceiptId: receipt.activationReceiptId,
91
+ undoReceiptId: receipt.id,
92
+ occurredAt: receipt.effectiveAt,
93
+ };
94
+ }
95
+
96
+ /** Evidence that itself came from a Slack knowledge connector must never republish to Slack. */
97
+ export function governedLearningEvidenceIsSlackDerived(
98
+ origin: GovernedLearningEvidenceOrigin,
99
+ ): boolean {
100
+ return origin.kind === "document" && /slack/iu.test(origin.providerKey ?? "");
101
+ }
102
+
103
+ export async function publishGovernedLearningEventToSlack(
104
+ db: Database,
105
+ event: GovernedLearningSlackEvent,
106
+ options: {
107
+ resolveEvidenceOrigin?: typeof resolveGovernedLearningEvidenceOrigin;
108
+ configuration?: typeof getCurrentMemorySlackPublicationConfiguration;
109
+ enqueue?: typeof enqueueMemorySlackPublication;
110
+ } = {},
111
+ ): Promise<GovernedLearningSlackPublicationResult> {
112
+ const workspaceId = event.receipt.workspaceId;
113
+ if (event.kind === "activated") {
114
+ // Loop prevention: evidence that came from a Slack knowledge connector
115
+ // must not be republished to Slack. Task-note evidence is always local.
116
+ const origin = await (options.resolveEvidenceOrigin ?? resolveGovernedLearningEvidenceOrigin)(
117
+ db,
118
+ { workspaceId, evidenceId: event.receipt.evidenceId },
119
+ );
120
+ if (!origin) return { kind: "evidence_origin_unresolved", enqueue: null };
121
+ if (governedLearningEvidenceIsSlackDerived(origin)) {
122
+ return { kind: "slack_derived_evidence", enqueue: null };
123
+ }
124
+ }
125
+ const importance = governedLearningSlackImportance(event);
126
+ const configuration = await (
127
+ options.configuration ?? getCurrentMemorySlackPublicationConfiguration
128
+ )(db, workspaceId);
129
+ const deliveryMode = configuration?.autoImportances.includes(importance)
130
+ ? "auto"
131
+ : configuration?.reviewImportances.includes(importance)
132
+ ? "review"
133
+ : null;
134
+ if (!configuration?.enabled || !deliveryMode) return { kind: "policy_quiet", enqueue: null };
135
+
136
+ const projection = governedLearningSlackProjection(event);
137
+ const receipt = event.receipt;
138
+ const enqueue = await (options.enqueue ?? enqueueMemorySlackPublication)(db, {
139
+ accountId: receipt.accountId,
140
+ workspaceId,
141
+ sourceType: "durable_learning",
142
+ sourceId: receipt.id,
143
+ sourceVersion: String(projection.destinationVersion),
144
+ sourceIdempotencyKey: `governed-learning:${event.kind}:${receipt.id}`,
145
+ projection: { ...projection },
146
+ importance,
147
+ deliveryMode,
148
+ actor: {
149
+ kind: "service",
150
+ subjectId: receipt.serviceActorSubjectId,
151
+ initiatingHumanSubjectId: receipt.initiatingHumanSubjectId,
152
+ sessionId: event.kind === "activated" ? event.sessionId : null,
153
+ attemptId: event.kind === "activated" ? event.attemptId : null,
154
+ },
155
+ });
156
+ return { kind: "enqueued", enqueue };
157
+ }
@@ -8,6 +8,7 @@ import {
8
8
  aggregateModelCallFacts,
9
9
  aggregateModelCallFactsByDay,
10
10
  aggregateModelCallFactsByHour,
11
+ aggregateModelContextContributions,
11
12
  aggregateRootSessionDrivers,
12
13
  aggregateScheduleFacts,
13
14
  aggregateSessionDepth,
@@ -212,6 +213,7 @@ export async function getWorkspaceInsights(
212
213
  agentRunsUsed,
213
214
  facets,
214
215
  recentCalls,
216
+ promptContributions,
215
217
  ] = await Promise.all([
216
218
  sumUsageQuantityInRange(db, {
217
219
  workspaceId: input.workspaceId,
@@ -315,6 +317,12 @@ export async function getWorkspaceInsights(
315
317
  ...filter,
316
318
  limit: 50,
317
319
  }),
320
+ aggregateModelContextContributions(db, {
321
+ workspaceId: input.workspaceId,
322
+ since: window.since,
323
+ until: window.until,
324
+ ...filter,
325
+ }),
318
326
  ]);
319
327
 
320
328
  const [priorRootDrivers, attached, fireCounts] = await Promise.all([
@@ -539,6 +547,7 @@ export async function getWorkspaceInsights(
539
547
  : microsToUsd(row.estimatedProviderCostMicros),
540
548
  pricingSource: pricingSourceOf(row.pricingSource),
541
549
  })),
550
+ promptContributions,
542
551
  warmSeconds,
543
552
  priorWarmSeconds,
544
553
  warmGroups: warmGroups.map((group) => ({
@@ -1,13 +1,10 @@
1
1
  import type { MemorySlackPublicationDistribution } from "@opengeni/contracts";
2
2
  import {
3
- correctWorkspaceMemory,
4
3
  enqueueMemorySlackPublication,
5
4
  getCurrentMemorySlackPublicationConfiguration,
6
5
  getWorkspaceMemorySlackPublicationSnapshot,
7
6
  saveWorkspaceMemory,
8
7
  withWorkspaceRls,
9
- type CorrectWorkspaceMemoryInput,
10
- type CorrectWorkspaceMemoryResult,
11
8
  type Database,
12
9
  type EnqueueMemorySlackPublicationResult,
13
10
  type MemoryEmbedder,
@@ -49,33 +46,6 @@ export async function saveWorkspaceMemoryWithSlackPublication(
49
46
  });
50
47
  }
51
48
 
52
- export async function correctWorkspaceMemoryWithSlackPublication(
53
- db: Database,
54
- input: CorrectWorkspaceMemoryInput,
55
- publication: MemorySlackPublicationCommitRequest | null,
56
- embedder?: MemoryEmbedder,
57
- ): Promise<
58
- CorrectWorkspaceMemoryResult & { slackPublication: MemorySlackPublicationCommitResult }
59
- > {
60
- return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
61
- const result = await correctWorkspaceMemory(scopedDb, input, embedder);
62
- if (!publication || result.action !== "superseded" || !result.replacement) {
63
- return { ...result, slackPublication: { decision: null, enqueue: null } };
64
- }
65
- const replacement = await requiredSnapshot(scopedDb, input.workspaceId, result.replacement.id);
66
- const slackPublication = await evaluateAndEnqueue(scopedDb, {
67
- accountId: input.accountId,
68
- workspaceId: input.workspaceId,
69
- snapshot: replacement,
70
- changeKind: "corrected",
71
- relatedMemoryId: result.memory.id,
72
- occurredAt: result.replacement.updatedAt,
73
- publication,
74
- });
75
- return { ...result, slackPublication };
76
- });
77
- }
78
-
79
49
  async function publishSavedMemoryMutation(
80
50
  db: Database,
81
51
  input: SaveWorkspaceMemoryInput,
@@ -0,0 +1,9 @@
1
+ export function organizationMembershipHttpStatus(
2
+ sqlState: string | null,
3
+ ): 400 | 403 | 404 | 409 | null {
4
+ if (sqlState === "22023" || sqlState === "23514") return 400;
5
+ if (sqlState === "42501") return 403;
6
+ if (sqlState === "P0002") return 404;
7
+ if (sqlState === "23505" || sqlState === "40001" || sqlState === "55000") return 409;
8
+ return null;
9
+ }