@opengeni/core 1.0.1 → 2.2.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.
- package/dist/access/index.d.ts +37 -0
- package/dist/application/new-session-drafts.d.ts +8 -1
- package/dist/application/session-commands.d.ts +18 -11
- package/dist/application/session-tenancy.d.ts +18 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
- package/dist/chunk-H7X52RI2.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
- package/dist/chunk-VCZFFEPB.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
- package/dist/chunk-ZYCNLYGH.js.map +1 -0
- package/dist/dependencies.d.ts +12 -1
- package/dist/domain/automations.d.ts +51 -0
- package/dist/domain/company-brain-governed-writes.d.ts +70 -0
- package/dist/domain/company-profile-agent-admin.d.ts +24 -0
- package/dist/domain/connection-authority.d.ts +97 -0
- package/dist/domain/connection-authority.js +169 -0
- package/dist/domain/connection-authority.js.map +1 -0
- package/dist/domain/editable-artifacts/service.d.ts +1 -2
- package/dist/domain/environments.d.ts +1 -1
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/governed-learning-activation.d.ts +16 -0
- package/dist/domain/governed-learning-evaluator.d.ts +6 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/memory-slack-delivery.d.ts +1 -4
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/packs.d.ts +5 -1
- package/dist/domain/personal-connection-delegations.d.ts +34 -2
- package/dist/domain/pr-review.d.ts +149 -0
- package/dist/domain/remember.d.ts +69 -0
- package/dist/domain/resources.d.ts +22 -1
- package/dist/domain/scheduled-tasks.d.ts +43 -2
- package/dist/domain/session-tool-policy.d.ts +1 -1
- package/dist/domain/sessions.d.ts +40 -6
- package/dist/editable-artifact-live/application.d.ts +10 -2
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +0 -1
- package/dist/editable-artifact-live/ticket.d.ts +0 -1
- package/dist/editable-artifact-live/types.d.ts +5 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +2 -4
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4827 -1164
- package/dist/index.js.map +1 -1
- package/dist/rigs/index.d.ts +22 -5
- package/dist/sandbox/fleet.d.ts +34 -6
- package/dist/sandbox/routing.d.ts +15 -1
- package/dist/sandbox/runtime-settings.d.ts +39 -0
- package/package.json +16 -11
- package/src/access/index.ts +157 -31
- package/src/application/new-session-drafts.ts +24 -2
- package/src/application/session-commands.ts +305 -71
- package/src/application/session-tenancy.ts +321 -0
- package/src/application/user-resource-grants.ts +140 -0
- package/src/billing/limits.ts +25 -15
- package/src/dependencies.ts +12 -1
- package/src/domain/automations.ts +196 -0
- package/src/domain/capabilities.ts +7 -1
- package/src/domain/company-brain-governed-writes.ts +330 -0
- package/src/domain/company-profile-agent-admin.ts +55 -0
- package/src/domain/connection-authority.ts +329 -0
- package/src/domain/editable-artifacts/agent-application.ts +17 -9
- package/src/domain/editable-artifacts/service.ts +12 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +16 -3
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/governed-learning-activation.ts +44 -0
- package/src/domain/governed-learning-evaluator.ts +49 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights.ts +9 -0
- package/src/domain/memory-slack-delivery.ts +0 -30
- package/src/domain/organization-membership-lifecycle.ts +9 -0
- package/src/domain/packs.ts +106 -3
- package/src/domain/personal-connection-delegations.ts +532 -51
- package/src/domain/pr-review.ts +671 -0
- package/src/domain/remember.ts +549 -0
- package/src/domain/resources.ts +131 -4
- package/src/domain/scheduled-tasks.ts +497 -76
- package/src/domain/session-tool-policy.ts +12 -9
- package/src/domain/sessions.ts +613 -166
- package/src/editable-artifact-live/application.ts +21 -3
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +13 -7
- package/src/editable-artifact-live/ticket.ts +1 -5
- package/src/editable-artifact-live/types.ts +6 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +13 -1
- package/src/rigs/index.ts +165 -48
- package/src/sandbox/fleet.ts +376 -30
- package/src/sandbox/routing.ts +91 -29
- package/src/sandbox/runtime-settings.ts +274 -0
- package/src/session-authorization.ts +17 -49
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- 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
|
+
}
|
package/src/domain/insights.ts
CHANGED
|
@@ -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
|
+
}
|