@opengeni/core 3.0.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/access/index.d.ts +131 -7
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +1 -1
- package/dist/application/connect-operation.d.ts +6 -0
- package/dist/application/external-workspace-members.d.ts +13 -1
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +29 -2
- package/dist/application/organization-privacy.d.ts +57 -0
- package/dist/application/prepared-mcp-connection.d.ts +31 -0
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +3 -0
- package/dist/application/session-tenancy.d.ts +13 -4
- package/dist/billing/agent-run-admission.d.ts +21 -0
- package/dist/billing/limits.d.ts +27 -1
- package/dist/billing/model-call-admission.d.ts +34 -0
- package/dist/billing/model-usage-settlement.d.ts +112 -0
- package/dist/canonical-human-identities.js +1 -1
- package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
- package/dist/chunk-7T2SZ7BI.js.map +1 -0
- package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
- package/dist/chunk-OMJFIIWK.js.map +1 -0
- package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
- package/dist/chunk-QI2M2B2K.js.map +1 -0
- package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
- package/dist/chunk-TTWTXQB2.js.map +1 -0
- package/dist/codex-model-availability.d.ts +55 -0
- package/dist/default-session-model.d.ts +192 -0
- package/dist/dependencies.d.ts +41 -7
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +8 -1
- package/dist/domain/capabilities.d.ts +112 -8
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +62 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +3 -1
- package/dist/domain/editable-artifacts/types.d.ts +1 -1
- package/dist/domain/encryption-key.d.ts +12 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +21 -3
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -76
- package/dist/domain/mcp-account-bindings.d.ts +17 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +73 -12
- package/dist/domain/pr-review.d.ts +121 -6
- package/dist/domain/realtime-voice-billing.d.ts +103 -0
- package/dist/domain/resources.d.ts +17 -1
- package/dist/domain/scheduled-task-access.d.ts +265 -0
- package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
- package/dist/domain/scheduled-task-audience.d.ts +70 -0
- package/dist/domain/scheduled-tasks.d.ts +61 -18
- package/dist/domain/session-connection-accounts.d.ts +53 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +102 -4
- package/dist/domain/sessions.d.ts +179 -15
- package/dist/domain/site-audience.d.ts +52 -0
- package/dist/domain/skills.d.ts +29 -1
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
- package/dist/domain/vaults/index.d.ts +7 -0
- package/dist/domain/vaults/mapping.d.ts +25 -0
- package/dist/domain/vaults/provider-registry.d.ts +72 -0
- package/dist/domain/vaults/provider-sync.d.ts +176 -0
- package/dist/domain/vaults/provider-writes.d.ts +57 -0
- package/dist/domain/vaults/secret-resolver.d.ts +34 -0
- package/dist/domain/vaults/types.d.ts +24 -0
- package/dist/domain/vaults/vault-service.d.ts +35 -0
- package/dist/domain/voice-input-billing.d.ts +77 -0
- package/dist/domain/web-search-billing.d.ts +51 -0
- package/dist/domain/workspace-credits.d.ts +7 -0
- package/dist/editable-artifact-live/application.d.ts +2 -1
- package/dist/editable-artifact-live/server.d.ts +5 -1
- package/dist/editable-artifact-live/ticket.d.ts +2 -1
- package/dist/editable-artifact-live/types.d.ts +9 -0
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -2
- package/dist/goal-admission.d.ts +627 -0
- package/dist/host-session-usage-limit.d.ts +15 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.js +22682 -11743
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +110 -7
- package/dist/model-call.d.ts +58 -0
- package/dist/model-catalog.d.ts +29 -3
- package/dist/model-provider-credentials.d.ts +43 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +1 -13
- package/dist/sandbox/fleet.d.ts +58 -4
- package/dist/sandbox/routing.d.ts +42 -2
- package/dist/sandbox/runtime-settings.d.ts +27 -17
- package/dist/session-authorization.d.ts +10 -0
- package/dist/session-usage-limit-metrics.d.ts +60 -0
- package/dist/session-usage-limits.d.ts +89 -0
- package/dist/site-agent-clamp.d.ts +71 -0
- package/dist/site-session-origin.d.ts +39 -2
- package/dist/transcription.d.ts +120 -3
- package/dist/turn-surface.d.ts +20 -0
- package/dist/user-presence.d.ts +32 -0
- package/dist/workflow-wake-contract.d.ts +13 -1
- package/package.json +15 -22
- package/src/access/index.ts +961 -38
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +9 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +3 -2
- package/src/application/connect-authority.ts +3 -3
- package/src/application/connect-operation.ts +37 -3
- package/src/application/external-continuation.ts +20 -3
- package/src/application/external-link-work-admission.ts +3 -0
- package/src/application/external-workspace-members.ts +51 -3
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +172 -55
- package/src/application/organization-privacy.ts +161 -0
- package/src/application/prepared-mcp-connection.ts +133 -0
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +73 -1
- package/src/application/session-mcp-credential-rotation.ts +61 -18
- package/src/application/session-tenancy.ts +262 -44
- package/src/application/user-resource-grants.ts +1 -29
- package/src/billing/agent-run-admission.ts +117 -0
- package/src/billing/limits.ts +138 -14
- package/src/billing/model-call-admission.ts +118 -0
- package/src/billing/model-usage-settlement.ts +472 -0
- package/src/codex-model-availability.ts +264 -0
- package/src/default-session-model.ts +692 -0
- package/src/dependencies.ts +48 -6
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +76 -2
- package/src/domain/capabilities.ts +830 -181
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +572 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +44 -6
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +20 -0
- package/src/domain/editable-artifacts/types.ts +1 -1
- package/src/domain/encryption-key.ts +26 -0
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/github-action-policies.ts +1 -1
- package/src/domain/github-skill-source.ts +3 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +167 -43
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +617 -5
- package/src/domain/knowledge-search.ts +240 -23
- package/src/domain/mcp-account-bindings.ts +169 -0
- package/src/domain/mcp-account-routes.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/personal-connection-delegations.ts +399 -257
- package/src/domain/pr-review.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +425 -0
- package/src/domain/resources.ts +50 -4
- package/src/domain/scheduled-task-access.ts +1089 -0
- package/src/domain/scheduled-task-account-audit.ts +90 -0
- package/src/domain/scheduled-task-audience.ts +249 -0
- package/src/domain/scheduled-tasks.ts +1313 -374
- package/src/domain/session-connection-accounts.ts +239 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +60 -0
- package/src/domain/session-tool-policy.ts +489 -14
- package/src/domain/sessions.ts +2254 -717
- package/src/domain/site-audience.ts +130 -0
- package/src/domain/skills.ts +9 -0
- package/src/domain/slack-bot.ts +141 -4
- package/src/domain/slack-publication-secret-safety.ts +1 -1
- package/src/domain/vaults/index.ts +19 -0
- package/src/domain/vaults/mapping.ts +190 -0
- package/src/domain/vaults/provider-registry.ts +201 -0
- package/src/domain/vaults/provider-sync.ts +531 -0
- package/src/domain/vaults/provider-writes.ts +288 -0
- package/src/domain/vaults/secret-resolver.ts +464 -0
- package/src/domain/vaults/types.ts +26 -0
- package/src/domain/vaults/vault-service.ts +1316 -0
- package/src/domain/voice-input-billing.ts +330 -0
- package/src/domain/web-search-billing.ts +169 -0
- package/src/domain/workspace-credits.ts +13 -0
- package/src/editable-artifact-live/application.ts +8 -1
- package/src/editable-artifact-live/server.ts +154 -6
- package/src/editable-artifact-live/ticket.ts +86 -1
- package/src/editable-artifact-live/types.ts +10 -0
- package/src/goal-admission.ts +220 -0
- package/src/host-session-usage-limit.ts +25 -0
- package/src/index.ts +75 -11
- package/src/managed-session.ts +137 -6
- package/src/model-call.ts +79 -0
- package/src/model-catalog.ts +345 -91
- package/src/model-provider-credentials.ts +394 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +23 -111
- package/src/sandbox/fleet.ts +253 -28
- package/src/sandbox/routing.ts +204 -43
- package/src/sandbox/runtime-settings.ts +159 -85
- package/src/session-authorization.ts +172 -23
- package/src/session-usage-limit-metrics.ts +95 -0
- package/src/session-usage-limits.ts +415 -0
- package/src/site-agent-clamp.ts +226 -0
- package/src/site-session-origin.ts +84 -5
- package/src/transcription.ts +123 -4
- package/src/turn-surface.ts +47 -0
- package/src/user-presence.ts +134 -0
- package/src/workflow-wake-contract.ts +14 -1
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/chunk-OF65T3PM.js.map +0 -1
- package/dist/chunk-VCZFFEPB.js.map +0 -1
- package/dist/chunk-ZYCNLYGH.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/environments.d.ts +0 -33
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/environments.ts +0 -120
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
|
@@ -26,10 +26,10 @@ import {
|
|
|
26
26
|
type SpreadsheetArtifactKernelQuery,
|
|
27
27
|
} from "@opengeni/contracts/editable-artifacts";
|
|
28
28
|
|
|
29
|
-
import
|
|
30
|
-
EditableArtifactDurableExportService,
|
|
31
|
-
EditableArtifactMaterializationFormat,
|
|
32
|
-
EditableArtifactMaterializationJob,
|
|
29
|
+
import {
|
|
30
|
+
type EditableArtifactDurableExportService,
|
|
31
|
+
type EditableArtifactMaterializationFormat,
|
|
32
|
+
type EditableArtifactMaterializationJob,
|
|
33
33
|
} from "./durable-export";
|
|
34
34
|
import {
|
|
35
35
|
EditableArtifactDomainError,
|
|
@@ -76,6 +76,7 @@ export type EditableArtifactAgentContext = Readonly<{
|
|
|
76
76
|
|
|
77
77
|
export type EditableArtifactAgentMetadata = Readonly<{
|
|
78
78
|
id: string;
|
|
79
|
+
artifactReference: string;
|
|
79
80
|
modality: EditableArtifactModality;
|
|
80
81
|
title: string;
|
|
81
82
|
lifecycle: EditableArtifact["lifecycle"];
|
|
@@ -174,6 +175,14 @@ export type EditableArtifactAgentApplicationDependencies = Readonly<{
|
|
|
174
175
|
exports: EditableArtifactDurableExportService;
|
|
175
176
|
associations: EditableArtifactAgentAssociationPort;
|
|
176
177
|
inspector: EditableArtifactAgentInspectionKernelPort;
|
|
178
|
+
/** Production persists proof only after a successful native query. */
|
|
179
|
+
recordInspection?: (
|
|
180
|
+
input: EditableArtifactAgentContext & {
|
|
181
|
+
artifact: EditableArtifactAgentMetadata;
|
|
182
|
+
queryHash: string;
|
|
183
|
+
queryKind: string;
|
|
184
|
+
},
|
|
185
|
+
) => Promise<string>;
|
|
177
186
|
officeImports: EditableArtifactOfficeImportPort;
|
|
178
187
|
workspaceFiles: EditableArtifactAgentWorkspaceFilePort;
|
|
179
188
|
}>;
|
|
@@ -183,6 +192,9 @@ export type EditableArtifactAgentApplicationDependencies = Readonly<{
|
|
|
183
192
|
* not another artifact engine: every mutation terminates at EditableArtifactService.
|
|
184
193
|
*/
|
|
185
194
|
export class EditableArtifactAgentApplication {
|
|
195
|
+
describeExportFormats(): string {
|
|
196
|
+
return this.dependencies.exports.describeFormats();
|
|
197
|
+
}
|
|
186
198
|
constructor(private readonly dependencies: EditableArtifactAgentApplicationDependencies) {}
|
|
187
199
|
|
|
188
200
|
async list(
|
|
@@ -291,7 +303,13 @@ export class EditableArtifactAgentApplication {
|
|
|
291
303
|
async inspect(
|
|
292
304
|
input: EditableArtifactAgentContext &
|
|
293
305
|
Readonly<{ artifactId: EditableArtifactId; request: EditableArtifactAgentQuery }>,
|
|
294
|
-
): Promise<
|
|
306
|
+
): Promise<
|
|
307
|
+
Readonly<{
|
|
308
|
+
artifact: EditableArtifactAgentMetadata;
|
|
309
|
+
projection: unknown;
|
|
310
|
+
inspectionReceiptId?: string;
|
|
311
|
+
}>
|
|
312
|
+
> {
|
|
295
313
|
const context = agentContext(input);
|
|
296
314
|
const artifactId = editableArtifactId(input.artifactId);
|
|
297
315
|
const state = await this.dependencies.domain.readCurrentKernelState({
|
|
@@ -304,8 +322,21 @@ export class EditableArtifactAgentApplication {
|
|
|
304
322
|
const queryBytes = encodeQuery(input.request);
|
|
305
323
|
const responseBytes = await this.dependencies.inspector.query({ state, queryBytes });
|
|
306
324
|
const projection = decodeQueryResponse(input.request.modality, responseBytes);
|
|
325
|
+
const inspectionReceiptId =
|
|
326
|
+
state.modality === "document"
|
|
327
|
+
? await this.dependencies.recordInspection?.({
|
|
328
|
+
...context,
|
|
329
|
+
artifact: projectArtifact(state.artifact),
|
|
330
|
+
queryHash: createHash("sha256").update(queryBytes).digest("hex"),
|
|
331
|
+
queryKind: input.request.query.kind,
|
|
332
|
+
})
|
|
333
|
+
: undefined;
|
|
307
334
|
await this.touch(context, artifactId);
|
|
308
|
-
return Object.freeze({
|
|
335
|
+
return Object.freeze({
|
|
336
|
+
artifact: projectArtifact(state.artifact),
|
|
337
|
+
projection,
|
|
338
|
+
...(inspectionReceiptId ? { inspectionReceiptId } : {}),
|
|
339
|
+
});
|
|
309
340
|
}
|
|
310
341
|
|
|
311
342
|
async apply(
|
|
@@ -445,6 +476,12 @@ export class EditableArtifactAgentApplication {
|
|
|
445
476
|
actor: context.actor,
|
|
446
477
|
artifactId,
|
|
447
478
|
});
|
|
479
|
+
// Refuse before pinning a version: the agent learns what does exist.
|
|
480
|
+
await this.dependencies.exports.preflight({
|
|
481
|
+
modality: artifact.modality,
|
|
482
|
+
format: input.format,
|
|
483
|
+
...(input.options ? { options: input.options } : {}),
|
|
484
|
+
});
|
|
448
485
|
const version = await this.dependencies.exports.pinVersion({
|
|
449
486
|
scope: context.scope,
|
|
450
487
|
actor: context.actor,
|
|
@@ -603,6 +640,7 @@ function commandProtocolVersion(modality: EditableArtifactModality): number {
|
|
|
603
640
|
function projectArtifact(artifact: EditableArtifact): EditableArtifactAgentMetadata {
|
|
604
641
|
return Object.freeze({
|
|
605
642
|
id: artifact.id,
|
|
643
|
+
artifactReference: `[Open report](/workspaces/${artifact.scope.workspaceId}/artifacts/editable/${artifact.id})`,
|
|
606
644
|
modality: artifact.modality,
|
|
607
645
|
title: artifact.title,
|
|
608
646
|
lifecycle: artifact.lifecycle,
|
|
@@ -33,6 +33,63 @@ export type EditableArtifactMaterializationFormat =
|
|
|
33
33
|
| "png"
|
|
34
34
|
| "webp";
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Stock production export formats. Custom deployments advertise their own
|
|
38
|
+
* capabilities on the profile port; this list never overrides their resolver.
|
|
39
|
+
* Extend the stock list only together with a real deployed codec.
|
|
40
|
+
*/
|
|
41
|
+
export const EDITABLE_ARTIFACT_EXPORT_FORMATS: Readonly<
|
|
42
|
+
Record<EditableArtifactModality, readonly EditableArtifactMaterializationFormat[]>
|
|
43
|
+
> = Object.freeze({
|
|
44
|
+
spreadsheet: Object.freeze(["xlsx"] as const),
|
|
45
|
+
document: Object.freeze([] as const),
|
|
46
|
+
presentation: Object.freeze([] as const),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/** Human/agent-readable summary of {@link EDITABLE_ARTIFACT_EXPORT_FORMATS}. */
|
|
50
|
+
export function describeEditableArtifactExportFormats(): string {
|
|
51
|
+
return describeConfiguredExportFormats(EDITABLE_ARTIFACT_EXPORT_FORMATS);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function describeConfiguredExportFormats(
|
|
55
|
+
configuredFormats: Readonly<
|
|
56
|
+
Record<EditableArtifactModality, readonly EditableArtifactMaterializationFormat[]>
|
|
57
|
+
>,
|
|
58
|
+
): string {
|
|
59
|
+
const entries = Object.entries(configuredFormats) as Array<
|
|
60
|
+
[EditableArtifactModality, readonly EditableArtifactMaterializationFormat[]]
|
|
61
|
+
>;
|
|
62
|
+
const supported = entries
|
|
63
|
+
.filter(([, formats]) => formats.length > 0)
|
|
64
|
+
.map(([modality, formats]) => `${modality} → ${formats.join(", ")}`);
|
|
65
|
+
const none = entries.filter(([, formats]) => formats.length === 0).map(([modality]) => modality);
|
|
66
|
+
return [
|
|
67
|
+
supported.length > 0
|
|
68
|
+
? `Supported exports: ${supported.join("; ")}.`
|
|
69
|
+
: "No exports are available.",
|
|
70
|
+
none.length > 0
|
|
71
|
+
? `${none.join(" and ")} artifacts cannot be exported yet (no PDF, DOCX, PPTX, or image export); share the live artifact with its artifactReference link instead.`
|
|
72
|
+
: "",
|
|
73
|
+
]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join(" ");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Typed refusal that tells the caller which formats do exist. */
|
|
79
|
+
export function unsupportedEditableArtifactExport(
|
|
80
|
+
modality: EditableArtifactModality,
|
|
81
|
+
format: string,
|
|
82
|
+
formats = EDITABLE_ARTIFACT_EXPORT_FORMATS,
|
|
83
|
+
): EditableArtifactDurableExportError {
|
|
84
|
+
const listed = (formats[modality] as readonly string[]).includes(format);
|
|
85
|
+
return new EditableArtifactDurableExportError(
|
|
86
|
+
"unsupported_format",
|
|
87
|
+
listed
|
|
88
|
+
? `unsupported_format: this deployment does not serve a ${format} export of a ${modality} artifact with these options; retry with options omitted. ${describeConfiguredExportFormats(formats)}`
|
|
89
|
+
: `unsupported_format: a ${modality} artifact cannot be exported as ${format}. ${describeConfiguredExportFormats(formats)}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
36
93
|
export type EditableArtifactMaterializationMimeType =
|
|
37
94
|
| "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
38
95
|
| "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
@@ -98,6 +155,11 @@ export type EditableArtifactMaterializationProfile = Readonly<{
|
|
|
98
155
|
|
|
99
156
|
/** Server-owned profile lookup. Clients never supply executable identity facts. */
|
|
100
157
|
export interface EditableArtifactMaterializationProfilePort {
|
|
158
|
+
/** Deployment-owned discovery; resolve remains the authority for exact options.
|
|
159
|
+
* Optional for existing custom adapters, whose support is resolved dynamically. */
|
|
160
|
+
supportedFormats?: Readonly<
|
|
161
|
+
Record<EditableArtifactModality, readonly EditableArtifactMaterializationFormat[]>
|
|
162
|
+
>;
|
|
101
163
|
resolve(
|
|
102
164
|
input: Readonly<{
|
|
103
165
|
modality: EditableArtifactModality;
|
|
@@ -223,8 +285,11 @@ export type EditableArtifactDurableExportErrorCode =
|
|
|
223
285
|
| "unavailable";
|
|
224
286
|
|
|
225
287
|
export class EditableArtifactDurableExportError extends Error {
|
|
226
|
-
constructor(
|
|
227
|
-
|
|
288
|
+
constructor(
|
|
289
|
+
readonly code: EditableArtifactDurableExportErrorCode,
|
|
290
|
+
message?: string,
|
|
291
|
+
) {
|
|
292
|
+
super(message ?? code);
|
|
228
293
|
this.name = "EditableArtifactDurableExportError";
|
|
229
294
|
}
|
|
230
295
|
}
|
|
@@ -247,6 +312,38 @@ const PROFILE_TEXT = /^[\x21-\x7e]+$/u;
|
|
|
247
312
|
export class EditableArtifactDurableExportService {
|
|
248
313
|
constructor(private readonly dependencies: EditableArtifactDurableExportServiceDependencies) {}
|
|
249
314
|
|
|
315
|
+
describeFormats(): string {
|
|
316
|
+
const formats = this.dependencies.profiles.supportedFormats;
|
|
317
|
+
return formats
|
|
318
|
+
? describeConfiguredExportFormats(formats)
|
|
319
|
+
: "Export formats are determined by this deployment's configured exporter; unsupported format/options combinations are refused before pinning.";
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Read-only exact preflight: no snapshot, ID allocation, or store write. */
|
|
323
|
+
async preflight(input: {
|
|
324
|
+
modality: EditableArtifactModality;
|
|
325
|
+
format: EditableArtifactMaterializationFormat;
|
|
326
|
+
options?: Readonly<Record<string, unknown>>;
|
|
327
|
+
}): Promise<EditableArtifactMaterializationProfile> {
|
|
328
|
+
const format = materializationFormat(input.format);
|
|
329
|
+
const options = exactOptions(input.options ?? Object.freeze({}));
|
|
330
|
+
const profile = await this.dependencies.profiles.resolve({
|
|
331
|
+
modality: input.modality,
|
|
332
|
+
format,
|
|
333
|
+
options,
|
|
334
|
+
});
|
|
335
|
+
if (!profile) {
|
|
336
|
+
const formats = this.dependencies.profiles.supportedFormats;
|
|
337
|
+
if (formats) throw unsupportedEditableArtifactExport(input.modality, format, formats);
|
|
338
|
+
throw new EditableArtifactDurableExportError(
|
|
339
|
+
"unsupported_format",
|
|
340
|
+
`unsupported_format: this deployment does not serve ${input.modality} → ${format} with these options. ${this.describeFormats()}`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
validateProfile(profile, input.modality, format);
|
|
344
|
+
return profile;
|
|
345
|
+
}
|
|
346
|
+
|
|
250
347
|
async pinVersion(
|
|
251
348
|
input: DurableExportContext & {
|
|
252
349
|
idempotencyKey: string;
|
|
@@ -307,13 +404,11 @@ export class EditableArtifactDurableExportService {
|
|
|
307
404
|
const format = materializationFormat(input.format);
|
|
308
405
|
const options = exactOptions(input.options ?? Object.freeze({}));
|
|
309
406
|
const version = await this.getVersion(context, versionId);
|
|
310
|
-
const profile = await this.
|
|
407
|
+
const profile = await this.preflight({
|
|
311
408
|
modality: version.modality,
|
|
312
409
|
format,
|
|
313
410
|
options,
|
|
314
411
|
});
|
|
315
|
-
if (!profile) throw new EditableArtifactDurableExportError("unsupported_format");
|
|
316
|
-
validateProfile(profile, version.modality, format);
|
|
317
412
|
const requestHash = hashPublicRequest("materialize", [
|
|
318
413
|
idempotencyKey,
|
|
319
414
|
versionId,
|
|
@@ -429,6 +429,9 @@ export class InMemoryEditableArtifactStore
|
|
|
429
429
|
artifactId: EditableArtifactId,
|
|
430
430
|
request: ReadEditableArtifactTransactionBasisRequest,
|
|
431
431
|
): Promise<EditableArtifactTransactionBasis> {
|
|
432
|
+
if (request.authorizeCommit) {
|
|
433
|
+
throw new TypeError("Transactional artifact authority requires a PostgreSQL store");
|
|
434
|
+
}
|
|
432
435
|
const normalizedScope = editableArtifactScope(scope);
|
|
433
436
|
const normalizedArtifactId = editableArtifactId(artifactId);
|
|
434
437
|
const key = aggregateKey(normalizedScope, normalizedArtifactId);
|
|
@@ -496,6 +499,9 @@ export class InMemoryEditableArtifactStore
|
|
|
496
499
|
async tryCommitAppliedTransaction(
|
|
497
500
|
request: TryCommitAppliedEditableArtifactTransactionRequest,
|
|
498
501
|
): Promise<TryCommitAppliedEditableArtifactTransactionResult> {
|
|
502
|
+
if (request.authorizeCommit) {
|
|
503
|
+
throw new TypeError("Transactional artifact authority requires a PostgreSQL store");
|
|
504
|
+
}
|
|
499
505
|
const scope = editableArtifactScope(request.scope);
|
|
500
506
|
const artifactId = editableArtifactId(request.artifactId);
|
|
501
507
|
const key = aggregateKey(scope, artifactId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Database } from "@opengeni/db";
|
|
1
2
|
import type {
|
|
2
3
|
EditableArtifact,
|
|
3
4
|
EditableArtifactActor,
|
|
@@ -288,6 +289,8 @@ export type ReadEditableArtifactTransactionBasisRequest = Readonly<{
|
|
|
288
289
|
clientTransactionId: EditableArtifactReceipt["clientTransactionId"];
|
|
289
290
|
previousLocalTransactionId: EditableArtifactReceipt["previousLocalTransactionId"];
|
|
290
291
|
selectiveUndoOperationIds: readonly EditableArtifactOperationId[];
|
|
292
|
+
/** Backend-only live authority; checked transactionally even on receipt replay. */
|
|
293
|
+
authorizeCommit?: EditableArtifactCommitAuthorizer;
|
|
291
294
|
}>;
|
|
292
295
|
|
|
293
296
|
export type EditableArtifactTransactionUndoBasis = Readonly<{
|
|
@@ -324,6 +327,13 @@ export type ExpectedEditableArtifactPredecessor = Readonly<{
|
|
|
324
327
|
replicaCounter: number;
|
|
325
328
|
}>;
|
|
326
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Receives the actual persistence transaction, never a separate pool handle.
|
|
332
|
+
* May run repeatedly for replay, lock admission and final persistence; must not
|
|
333
|
+
* perform external mutations. Stores without this transaction capability deny.
|
|
334
|
+
*/
|
|
335
|
+
export type EditableArtifactCommitAuthorizer = (tx: Database) => Promise<void>;
|
|
336
|
+
|
|
327
337
|
export type TryCommitAppliedEditableArtifactTransactionRequest =
|
|
328
338
|
CommitAppliedEditableArtifactTransaction &
|
|
329
339
|
Readonly<{
|
|
@@ -333,6 +343,7 @@ export type TryCommitAppliedEditableArtifactTransactionRequest =
|
|
|
333
343
|
expectedAuthorizationRevision: number;
|
|
334
344
|
/** Rechecked by durable production stores in the commit transaction. */
|
|
335
345
|
authorizationActor: EditableArtifactActor;
|
|
346
|
+
authorizeCommit?: EditableArtifactCommitAuthorizer;
|
|
336
347
|
actorKey: string;
|
|
337
348
|
clientTransactionId: EditableArtifactReceipt["clientTransactionId"];
|
|
338
349
|
requestHash: EditableArtifactReceipt["requestHash"];
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
EditableArtifactUndoTargetError,
|
|
14
14
|
} from "./errors";
|
|
15
15
|
import type {
|
|
16
|
+
EditableArtifactCommitAuthorizer,
|
|
16
17
|
AuthoritativeEditableArtifactKernelPort,
|
|
17
18
|
EditableArtifactAuthorizationPort,
|
|
18
19
|
EditableArtifactClockPort,
|
|
@@ -547,6 +548,8 @@ export class EditableArtifactService {
|
|
|
547
548
|
artifactId: EditableArtifact["id"];
|
|
548
549
|
actor: EditableArtifactActor;
|
|
549
550
|
request: ApplyEditableArtifactTransactionRequest;
|
|
551
|
+
/** Authenticated backend authority, not part of the client mutation envelope. */
|
|
552
|
+
authorizeCommit?: EditableArtifactCommitAuthorizer;
|
|
550
553
|
}): Promise<ApplyEditableArtifactTransactionResult> {
|
|
551
554
|
const context = normalizeBoundaryContext(input);
|
|
552
555
|
const request = normalizeTransactionEnvelope(input.request);
|
|
@@ -570,6 +573,19 @@ export class EditableArtifactService {
|
|
|
570
573
|
}
|
|
571
574
|
const intentBytes = request.intentBytes.slice();
|
|
572
575
|
const actorKey = editableArtifactActorKey(context.actor);
|
|
576
|
+
// A source-bound caller must prove its own authority on commit/replay.
|
|
577
|
+
// Sharing another caller's in-flight result would skip that proof.
|
|
578
|
+
if (input.authorizeCommit) {
|
|
579
|
+
return await this.applyTransactionOptimistically({
|
|
580
|
+
context,
|
|
581
|
+
actorKey,
|
|
582
|
+
request,
|
|
583
|
+
intent,
|
|
584
|
+
intentBytes,
|
|
585
|
+
authorizationRevision,
|
|
586
|
+
authorizeCommit: input.authorizeCommit,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
573
589
|
const inFlightKey = transactionInFlightKey(
|
|
574
590
|
context.scope,
|
|
575
591
|
context.artifactId,
|
|
@@ -631,6 +647,7 @@ export class EditableArtifactService {
|
|
|
631
647
|
intent: ValidatedEditableArtifactMutationIntent;
|
|
632
648
|
intentBytes: Uint8Array;
|
|
633
649
|
authorizationRevision: number;
|
|
650
|
+
authorizeCommit?: EditableArtifactCommitAuthorizer;
|
|
634
651
|
}): Promise<ApplyEditableArtifactTransactionResult> {
|
|
635
652
|
const { context, actorKey, request, intent, intentBytes } = input;
|
|
636
653
|
let authorizationRevision = input.authorizationRevision;
|
|
@@ -643,6 +660,7 @@ export class EditableArtifactService {
|
|
|
643
660
|
clientTransactionId: intent.clientTransactionId,
|
|
644
661
|
previousLocalTransactionId: intent.previousLocalTransactionId,
|
|
645
662
|
selectiveUndoOperationIds: intent.selectiveUndoOperationIds,
|
|
663
|
+
...(input.authorizeCommit ? { authorizeCommit: input.authorizeCommit } : {}),
|
|
646
664
|
},
|
|
647
665
|
);
|
|
648
666
|
if (basisRead.kind === "existing") {
|
|
@@ -797,6 +815,7 @@ export class EditableArtifactService {
|
|
|
797
815
|
expectedLifecycle: "active",
|
|
798
816
|
expectedAuthorizationRevision: authorizationRevision,
|
|
799
817
|
authorizationActor: context.actor,
|
|
818
|
+
...(input.authorizeCommit ? { authorizeCommit: input.authorizeCommit } : {}),
|
|
800
819
|
expectedHeadSequence: artifact.headSequence,
|
|
801
820
|
actorKey,
|
|
802
821
|
clientTransactionId: intent.clientTransactionId,
|
|
@@ -1037,6 +1056,7 @@ export class EditableArtifactService {
|
|
|
1037
1056
|
expectedLifecycle: "active",
|
|
1038
1057
|
expectedAuthorizationRevision: authorizationRevision,
|
|
1039
1058
|
authorizationActor: context.actor,
|
|
1059
|
+
...(input.authorizeCommit ? { authorizeCommit: input.authorizeCommit } : {}),
|
|
1040
1060
|
expectedHeadSequence: artifact.headSequence,
|
|
1041
1061
|
actorKey,
|
|
1042
1062
|
clientTransactionId: intent.clientTransactionId,
|
|
@@ -7,7 +7,7 @@ import { EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES } from "@opengeni/contracts/
|
|
|
7
7
|
/**
|
|
8
8
|
* Domain vocabulary for collaborative, editable Office-like artifacts.
|
|
9
9
|
*
|
|
10
|
-
* This is intentionally unrelated to
|
|
10
|
+
* This is intentionally unrelated to Opengeni's published HTML artifacts and
|
|
11
11
|
* document-ingestion records. An editable artifact is mutable canonical model
|
|
12
12
|
* state plus a causally ordered operation history.
|
|
13
13
|
*/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { environmentsEncryptionKeyBytes, type Settings } from "@opengeni/config";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The deployment's at-rest encryption key (`OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY`),
|
|
6
|
+
* shared by vault values and stored connection credentials.
|
|
7
|
+
*/
|
|
8
|
+
export function requireEnvironmentEncryption(settings: Settings): Uint8Array {
|
|
9
|
+
const key = environmentsEncryptionKeyBytes(settings);
|
|
10
|
+
if (!key) {
|
|
11
|
+
throw new HTTPException(503, {
|
|
12
|
+
message: "secrets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return key;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* RM-1 for internal callers: Variable Sets were replaced by vaults, so a
|
|
20
|
+
* request that still selects one is refused instead of silently ignored.
|
|
21
|
+
*/
|
|
22
|
+
export function variableSetsRemovedError(field: string): HTTPException {
|
|
23
|
+
return new HTTPException(422, {
|
|
24
|
+
message: `variable_sets_removed: ${field} is no longer supported; Variable Sets were replaced by vaults (use vaultPolicy)`,
|
|
25
|
+
});
|
|
26
|
+
}
|
package/src/domain/file-owner.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { requireLiveAgentAttemptAuthorization } from "../session-authorization";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
freezeAgentLearningPolicy,
|
|
4
|
+
getSessionAuthorityProjection,
|
|
5
|
+
type SessionRlsActorContext,
|
|
6
|
+
} from "@opengeni/db";
|
|
3
7
|
import type { AccessGrant, Permission } from "@opengeni/contracts";
|
|
4
8
|
import { requirePermission, type AccessGrantAuthorization } from "../access";
|
|
5
9
|
import type { ApiRouteDeps } from "../dependencies";
|
|
@@ -44,7 +48,18 @@ export async function fileOwnerContextForAgent(
|
|
|
44
48
|
executionGeneration: attempt.executionGeneration,
|
|
45
49
|
},
|
|
46
50
|
});
|
|
51
|
+
const sessionAuthority = await getSessionAuthorityProjection(
|
|
52
|
+
deps.db,
|
|
53
|
+
grant.workspaceId,
|
|
54
|
+
sessionId,
|
|
55
|
+
);
|
|
56
|
+
if (!sessionAuthority) throw new Error("File session authority unavailable");
|
|
47
57
|
return {
|
|
58
|
+
sessionAttachmentReadAccess: {
|
|
59
|
+
sessionId,
|
|
60
|
+
authorityEpoch: sessionAuthority.authorityEpoch,
|
|
61
|
+
actor: attempt,
|
|
62
|
+
},
|
|
48
63
|
subjectId: grant.subjectId,
|
|
49
64
|
initiatingHumanSubjectId: attempt.initiatingHumanSubjectId,
|
|
50
65
|
privateFileOwnerSubjectId: snapshot.defaultScope === "personal" ? snapshot.subjectId : null,
|
|
@@ -150,7 +150,7 @@ export function githubAppActionPolicyActor(input: {
|
|
|
150
150
|
}): GitHubActionPolicyActorBinding {
|
|
151
151
|
return {
|
|
152
152
|
actor: { kind: "workspace_app", installationId: input.installationId },
|
|
153
|
-
label: input.accountLogin ? `
|
|
153
|
+
label: input.accountLogin ? `Opengeni bot on ${input.accountLogin}` : "Opengeni bot",
|
|
154
154
|
connectionId: `github-app:${input.installationId}`,
|
|
155
155
|
serverId: GITHUB_REST_MCP_APP_SERVER_ID,
|
|
156
156
|
};
|
|
@@ -4,6 +4,7 @@ import type { GitHubSkillSourceClient, GitHubSkillTreeEntry } from "./skill-impo
|
|
|
4
4
|
import { pinnedFetch, readResponseJsonBounded, readResponseTextBounded } from "@opengeni/network";
|
|
5
5
|
import {
|
|
6
6
|
PORTABLE_SKILL_MAX_FILES,
|
|
7
|
+
PORTABLE_SKILL_MAX_FILE_BYTES,
|
|
7
8
|
PORTABLE_SKILL_MAX_TOTAL_BYTES,
|
|
8
9
|
type SkillLibraryFile,
|
|
9
10
|
} from "@opengeni/runtime/skill-library";
|
|
@@ -11,7 +12,8 @@ import {
|
|
|
11
12
|
const githubApiBase = "https://api.github.com";
|
|
12
13
|
const githubRequestTimeoutMs = 15_000;
|
|
13
14
|
const githubMetadataMaxBytes = 4 * 1024 * 1024;
|
|
14
|
-
|
|
15
|
+
// GitHub blobs are base64-encoded JSON; allow framing and line wrapping.
|
|
16
|
+
const githubBlobResponseMaxBytes = PORTABLE_SKILL_MAX_FILE_BYTES * 2;
|
|
15
17
|
|
|
16
18
|
type GitHubJsonRequest = (path: string, maxBytes: number, label: string) => Promise<unknown>;
|
|
17
19
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { withDatabaseTimingObserver, type DatabaseTimingObservation } from "@opengeni/db";
|
|
2
|
+
import type { Observability } from "@opengeni/observability";
|
|
3
|
+
|
|
4
|
+
const phases = new Set([
|
|
5
|
+
"auth",
|
|
6
|
+
"require_workspace",
|
|
7
|
+
"model_bundle",
|
|
8
|
+
"usage_bundle",
|
|
9
|
+
"live_warm",
|
|
10
|
+
"scheduled_tasks",
|
|
11
|
+
"session_depth",
|
|
12
|
+
"floor_sessions",
|
|
13
|
+
"online_machines",
|
|
14
|
+
"attached_sessions",
|
|
15
|
+
"scheduled_fires",
|
|
16
|
+
] as const);
|
|
17
|
+
export type InsightsPhase = typeof phases extends Set<infer T> ? T : never;
|
|
18
|
+
export type InsightsPhaseObservation = {
|
|
19
|
+
phase: InsightsPhase;
|
|
20
|
+
stage: "helper" | DatabaseTimingObservation["stage"];
|
|
21
|
+
outcome: "completed" | "failed";
|
|
22
|
+
durationMs: number;
|
|
23
|
+
};
|
|
24
|
+
export type InsightsPhaseObserver = (observation: InsightsPhaseObservation) => void;
|
|
25
|
+
const stages = new Set([
|
|
26
|
+
"helper",
|
|
27
|
+
"transaction_admission",
|
|
28
|
+
"savepoint_admission",
|
|
29
|
+
"rls_setup",
|
|
30
|
+
"scoped_callback",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
function observeSafely(
|
|
34
|
+
observer: InsightsPhaseObserver,
|
|
35
|
+
observation: InsightsPhaseObservation,
|
|
36
|
+
): void {
|
|
37
|
+
try {
|
|
38
|
+
void Promise.resolve(observer(observation)).catch(() => undefined);
|
|
39
|
+
} catch {
|
|
40
|
+
// Telemetry is never authoritative for results or authorization.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Helper durations overlap under Promise.all; they must not be summed as request latency. */
|
|
45
|
+
export async function measureInsightsPhase<T>(
|
|
46
|
+
observer: InsightsPhaseObserver | undefined,
|
|
47
|
+
phase: InsightsPhase,
|
|
48
|
+
work: () => Promise<T>,
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
if (!observer) return work();
|
|
51
|
+
const started = performance.now();
|
|
52
|
+
let outcome: InsightsPhaseObservation["outcome"] = "failed";
|
|
53
|
+
try {
|
|
54
|
+
const result = await withDatabaseTimingObserver(
|
|
55
|
+
(observation) => observeSafely(observer, { ...observation, phase }),
|
|
56
|
+
work,
|
|
57
|
+
);
|
|
58
|
+
outcome = "completed";
|
|
59
|
+
return result;
|
|
60
|
+
} finally {
|
|
61
|
+
observeSafely(observer, {
|
|
62
|
+
phase,
|
|
63
|
+
stage: "helper",
|
|
64
|
+
outcome,
|
|
65
|
+
durationMs: performance.now() - started,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Closed runtime label projection: caller input, SQL and identities never become labels. */
|
|
71
|
+
export function workspaceInsightsPhaseMetricObserver(
|
|
72
|
+
observability: Pick<Observability, "observeHistogram"> | null | undefined,
|
|
73
|
+
): InsightsPhaseObserver | undefined {
|
|
74
|
+
if (!observability) return undefined;
|
|
75
|
+
return (observation) => {
|
|
76
|
+
try {
|
|
77
|
+
const result = observability.observeHistogram({
|
|
78
|
+
name: "opengeni_workspace_insights_phase_duration_seconds",
|
|
79
|
+
help: "Insights helper and scoped DB durations; admission includes BEGIN or SAVEPOINT, scoped callback is not pure SQL time. Concurrent phases overlap.",
|
|
80
|
+
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 3, 5, 10, 30, 60],
|
|
81
|
+
labels: {
|
|
82
|
+
phase: phases.has(observation.phase) ? observation.phase : "unknown",
|
|
83
|
+
stage: stages.has(observation.stage) ? observation.stage : "unknown",
|
|
84
|
+
outcome: observation.outcome === "completed" ? "completed" : "failed",
|
|
85
|
+
},
|
|
86
|
+
value: Number.isFinite(observation.durationMs)
|
|
87
|
+
? Math.max(0, observation.durationMs) / 1_000
|
|
88
|
+
: 0,
|
|
89
|
+
});
|
|
90
|
+
void Promise.resolve(result).catch(() => undefined);
|
|
91
|
+
} catch {
|
|
92
|
+
// Includes exporter and metric registration failures.
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InsightsCallsQuery,
|
|
3
|
+
InsightsCallsResponse,
|
|
4
|
+
InsightsUsageQuery,
|
|
5
|
+
InsightsUsageRange,
|
|
6
|
+
InsightsUsageResponse,
|
|
7
|
+
} from "@opengeni/contracts/insights-usage";
|
|
8
|
+
import { readInsightsCalls, readInsightsUsage, type Database } from "@opengeni/db";
|
|
9
|
+
|
|
10
|
+
export type InsightsQueryScope = {
|
|
11
|
+
accountId: string;
|
|
12
|
+
/** null means organization scope; it does not grant workspace/session access. */
|
|
13
|
+
workspaceId: string | null;
|
|
14
|
+
/** Server-verified metadata authority; billing permission alone supplies none. */
|
|
15
|
+
detailsWorkspaceIds?: readonly string[];
|
|
16
|
+
/** Existing organization service-key authority applies only to shared workspaces. */
|
|
17
|
+
detailsSharedWorkspaces?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function getInsightsUsage(
|
|
21
|
+
db: Database,
|
|
22
|
+
input: InsightsQueryScope & { query: InsightsUsageQuery; now?: Date },
|
|
23
|
+
): Promise<InsightsUsageResponse> {
|
|
24
|
+
return await readInsightsUsage(db, { ...input, now: input.now ?? new Date() });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function listInsightsCalls(
|
|
28
|
+
db: Database,
|
|
29
|
+
input: InsightsQueryScope & { query: InsightsCallsQuery; now?: Date },
|
|
30
|
+
): Promise<InsightsCallsResponse> {
|
|
31
|
+
return await readInsightsCalls(db, { ...input, now: input.now ?? new Date() });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** UTC windows retain the actual request timestamp, including empty midnight windows. */
|
|
35
|
+
export function insightsUsageWindow(range: InsightsUsageRange, now = new Date()) {
|
|
36
|
+
if (!Number.isFinite(now.getTime())) throw new Error("Invalid Insights request time");
|
|
37
|
+
const until = new Date(now);
|
|
38
|
+
const since = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
39
|
+
switch (range) {
|
|
40
|
+
case "today":
|
|
41
|
+
break;
|
|
42
|
+
case "week":
|
|
43
|
+
since.setUTCDate(since.getUTCDate() - 6);
|
|
44
|
+
break;
|
|
45
|
+
case "month":
|
|
46
|
+
since.setUTCDate(1);
|
|
47
|
+
break;
|
|
48
|
+
case "30d":
|
|
49
|
+
since.setUTCDate(since.getUTCDate() - 29);
|
|
50
|
+
break;
|
|
51
|
+
case "90d":
|
|
52
|
+
since.setUTCDate(since.getUTCDate() - 89);
|
|
53
|
+
break;
|
|
54
|
+
case "ytd":
|
|
55
|
+
since.setUTCMonth(0, 1);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const priorUntil = new Date(since);
|
|
59
|
+
const priorSince = new Date(priorUntil.getTime() - (until.getTime() - since.getTime()));
|
|
60
|
+
return {
|
|
61
|
+
since,
|
|
62
|
+
until,
|
|
63
|
+
priorSince,
|
|
64
|
+
priorUntil,
|
|
65
|
+
bucket: range === "today" ? ("hour" as const) : ("day" as const),
|
|
66
|
+
};
|
|
67
|
+
}
|