@opengeni/db 0.12.6 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-XRVNI2GB.js → chunk-ELMUCYZF.js} +12 -2
- package/dist/chunk-ELMUCYZF.js.map +1 -0
- package/dist/chunk-N4WTE6SB.js +4033 -0
- package/dist/chunk-N4WTE6SB.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2500 -1866
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +156 -12
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-CeXWlYcG.d.ts → schema-D-hyrsFt.d.ts} +2050 -942
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +9 -1
- package/drizzle/0130_workspace_instruction_policies.sql +330 -0
- package/drizzle/0131_slack_bot_install_and_post_idempotency.sql +133 -0
- package/package.json +3 -3
- package/src/index.ts +358 -7
- package/src/runtime-posture.ts +12 -2
- package/src/schema.ts +87 -0
- package/src/workspace-instruction-policies-schema.ts +140 -0
- package/src/workspace-instruction-policies.ts +624 -0
- package/dist/chunk-CYA6BHV6.js +0 -3822
- package/dist/chunk-CYA6BHV6.js.map +0 -1
- package/dist/chunk-XRVNI2GB.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -214,6 +214,7 @@ export { sql as dbSql } from "drizzle-orm";
|
|
|
214
214
|
export * from "./session-control";
|
|
215
215
|
export * from "./session-queue-commands";
|
|
216
216
|
export * from "./new-session-drafts";
|
|
217
|
+
export * from "./workspace-instruction-policies";
|
|
217
218
|
export { interruptedToolCallResult } from "./session-tool-call-settlement";
|
|
218
219
|
export { decryptEnvironmentValue, encryptEnvironmentValue } from "./environment-crypto";
|
|
219
220
|
export {
|
|
@@ -3144,6 +3145,8 @@ export type CreateConnectionInput = {
|
|
|
3144
3145
|
credentialEncrypted: string;
|
|
3145
3146
|
grantedScopes?: string[];
|
|
3146
3147
|
expiresAt?: Date | null;
|
|
3148
|
+
verifiedInstallAt?: Date | null;
|
|
3149
|
+
verifiedInstallVersion?: number | null;
|
|
3147
3150
|
metadata?: Record<string, unknown>;
|
|
3148
3151
|
createdBySubjectId?: string | null;
|
|
3149
3152
|
updatedBySubjectId?: string | null;
|
|
@@ -3161,10 +3164,40 @@ export type UpdateConnectionInput = {
|
|
|
3161
3164
|
credentialEncrypted?: string;
|
|
3162
3165
|
grantedScopes?: string[];
|
|
3163
3166
|
expiresAt?: Date | null;
|
|
3167
|
+
verifiedInstallAt?: Date | null;
|
|
3168
|
+
verifiedInstallVersion?: number | null;
|
|
3164
3169
|
metadata?: Record<string, unknown>;
|
|
3165
3170
|
updatedBySubjectId?: string | null;
|
|
3166
3171
|
};
|
|
3167
3172
|
|
|
3173
|
+
/** Server-owned verification facts; public schemas expose them read-only and nullable. */
|
|
3174
|
+
export type ConnectionMetadataWithVerification = ConnectionMetadata & {
|
|
3175
|
+
verifiedInstallAt: string | null;
|
|
3176
|
+
verifiedInstallVersion: number | null;
|
|
3177
|
+
};
|
|
3178
|
+
|
|
3179
|
+
export type SlackBotPostOperation = {
|
|
3180
|
+
id: string;
|
|
3181
|
+
accountId: string;
|
|
3182
|
+
workspaceId: string;
|
|
3183
|
+
connectionId: string;
|
|
3184
|
+
operationId: string;
|
|
3185
|
+
clientMessageId: string;
|
|
3186
|
+
targetKind: "channel" | "user";
|
|
3187
|
+
targetId: string;
|
|
3188
|
+
requestDigest: string;
|
|
3189
|
+
status: "provider_started" | "completed";
|
|
3190
|
+
claimHolderId: string | null;
|
|
3191
|
+
claimExpiresAt: Date | null;
|
|
3192
|
+
attemptCount: number;
|
|
3193
|
+
lastFailureCode: string | null;
|
|
3194
|
+
slackChannelId: string | null;
|
|
3195
|
+
slackMessageTimestamp: string | null;
|
|
3196
|
+
completedAt: Date | null;
|
|
3197
|
+
createdAt: Date;
|
|
3198
|
+
updatedAt: Date;
|
|
3199
|
+
};
|
|
3200
|
+
|
|
3168
3201
|
export type ConnectionCredentialForBroker = {
|
|
3169
3202
|
id: string;
|
|
3170
3203
|
accountId: string;
|
|
@@ -3295,6 +3328,7 @@ export type RegistryCapabilityCatalogItemInput = {
|
|
|
3295
3328
|
importBatchId: string;
|
|
3296
3329
|
scopesHint?: string[];
|
|
3297
3330
|
homepageUrl?: string | null;
|
|
3331
|
+
installUrl?: string | null;
|
|
3298
3332
|
tags?: string[];
|
|
3299
3333
|
metadata?: Record<string, unknown>;
|
|
3300
3334
|
};
|
|
@@ -4163,7 +4197,7 @@ export async function upsertRegistryCapabilityCatalogItem(
|
|
|
4163
4197
|
tags: input.tags ?? ["mcp", "integration", input.tier],
|
|
4164
4198
|
homepageUrl: input.homepageUrl ?? `https://${input.providerDomain}`,
|
|
4165
4199
|
endpointUrl: input.mcpUrl,
|
|
4166
|
-
installUrl: input.homepageUrl ?? `https://${input.providerDomain}`,
|
|
4200
|
+
installUrl: input.installUrl ?? input.homepageUrl ?? `https://${input.providerDomain}`,
|
|
4167
4201
|
authModel: input.authKind === "none" ? null : "credential_ref",
|
|
4168
4202
|
providerDomain: input.providerDomain,
|
|
4169
4203
|
surfaceType: "mcp",
|
|
@@ -4723,6 +4757,8 @@ const connectionMetadataColumns = {
|
|
|
4723
4757
|
lastUsedAt: schema.connections.lastUsedAt,
|
|
4724
4758
|
lastError: schema.connections.lastError,
|
|
4725
4759
|
version: schema.connections.version,
|
|
4760
|
+
verifiedInstallAt: schema.connections.verifiedInstallAt,
|
|
4761
|
+
verifiedInstallVersion: schema.connections.verifiedInstallVersion,
|
|
4726
4762
|
metadata: schema.connections.metadata,
|
|
4727
4763
|
createdBySubjectId: schema.connections.createdBySubjectId,
|
|
4728
4764
|
updatedBySubjectId: schema.connections.updatedBySubjectId,
|
|
@@ -4739,7 +4775,7 @@ function connectionSubjectVisibility(subjectId?: string | null): SQL {
|
|
|
4739
4775
|
export async function createConnection(
|
|
4740
4776
|
db: Database,
|
|
4741
4777
|
input: CreateConnectionInput,
|
|
4742
|
-
): Promise<
|
|
4778
|
+
): Promise<ConnectionMetadataWithVerification> {
|
|
4743
4779
|
return await withRlsContext(
|
|
4744
4780
|
db,
|
|
4745
4781
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
@@ -4756,6 +4792,8 @@ export async function createConnection(
|
|
|
4756
4792
|
credentialEncrypted: input.credentialEncrypted,
|
|
4757
4793
|
grantedScopes: input.grantedScopes ?? [],
|
|
4758
4794
|
expiresAt: input.expiresAt ?? null,
|
|
4795
|
+
verifiedInstallAt: input.verifiedInstallAt ?? null,
|
|
4796
|
+
verifiedInstallVersion: input.verifiedInstallVersion ?? null,
|
|
4759
4797
|
metadata: input.metadata ?? {},
|
|
4760
4798
|
createdBySubjectId: input.createdBySubjectId ?? null,
|
|
4761
4799
|
updatedBySubjectId: input.updatedBySubjectId ?? input.createdBySubjectId ?? null,
|
|
@@ -4773,7 +4811,7 @@ export async function listConnectionsMetadata(
|
|
|
4773
4811
|
db: Database,
|
|
4774
4812
|
workspaceId: string,
|
|
4775
4813
|
subjectId?: string | null,
|
|
4776
|
-
): Promise<
|
|
4814
|
+
): Promise<ConnectionMetadataWithVerification[]> {
|
|
4777
4815
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
4778
4816
|
const rows = await scopedDb
|
|
4779
4817
|
.select(connectionMetadataColumns)
|
|
@@ -4794,7 +4832,7 @@ export async function getConnectionMetadata(
|
|
|
4794
4832
|
workspaceId: string,
|
|
4795
4833
|
connectionId: string,
|
|
4796
4834
|
subjectId?: string | null,
|
|
4797
|
-
): Promise<
|
|
4835
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
4798
4836
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
4799
4837
|
const [row] = await scopedDb
|
|
4800
4838
|
.select(connectionMetadataColumns)
|
|
@@ -4814,7 +4852,7 @@ export async function getConnectionMetadata(
|
|
|
4814
4852
|
export async function updateConnection(
|
|
4815
4853
|
db: Database,
|
|
4816
4854
|
input: UpdateConnectionInput,
|
|
4817
|
-
): Promise<
|
|
4855
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
4818
4856
|
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
4819
4857
|
const set = {
|
|
4820
4858
|
updatedAt: new Date(),
|
|
@@ -4831,6 +4869,12 @@ export async function updateConnection(
|
|
|
4831
4869
|
: {}),
|
|
4832
4870
|
...(input.grantedScopes !== undefined ? { grantedScopes: input.grantedScopes } : {}),
|
|
4833
4871
|
...(input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : {}),
|
|
4872
|
+
...(input.verifiedInstallAt !== undefined
|
|
4873
|
+
? { verifiedInstallAt: input.verifiedInstallAt }
|
|
4874
|
+
: {}),
|
|
4875
|
+
...(input.verifiedInstallVersion !== undefined
|
|
4876
|
+
? { verifiedInstallVersion: input.verifiedInstallVersion }
|
|
4877
|
+
: {}),
|
|
4834
4878
|
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
|
4835
4879
|
...(input.updatedBySubjectId !== undefined
|
|
4836
4880
|
? { updatedBySubjectId: input.updatedBySubjectId }
|
|
@@ -4859,7 +4903,7 @@ export async function revokeConnection(
|
|
|
4859
4903
|
workspaceId: string,
|
|
4860
4904
|
connectionId: string,
|
|
4861
4905
|
updatedBySubjectId?: string | null,
|
|
4862
|
-
): Promise<
|
|
4906
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
4863
4907
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
4864
4908
|
const [row] = await scopedDb
|
|
4865
4909
|
.update(schema.connections)
|
|
@@ -4868,6 +4912,13 @@ export async function revokeConnection(
|
|
|
4868
4912
|
// The version bump invalidates any in-flight refresh's (id, version) CAS,
|
|
4869
4913
|
// so a racing refresh cannot commit and flip the row back to active.
|
|
4870
4914
|
version: sql`${schema.connections.version} + 1`,
|
|
4915
|
+
// Status-only revocation does not replace the verified credential or bot
|
|
4916
|
+
// identity. Carry the marker to the same new CAS version so the dedicated
|
|
4917
|
+
// reinstall path can still recognize (but not use) the inactive row.
|
|
4918
|
+
verifiedInstallVersion: sql`case
|
|
4919
|
+
when ${schema.connections.verifiedInstallAt} is null then null
|
|
4920
|
+
else ${schema.connections.version} + 1
|
|
4921
|
+
end`,
|
|
4871
4922
|
updatedBySubjectId: updatedBySubjectId ?? null,
|
|
4872
4923
|
updatedAt: new Date(),
|
|
4873
4924
|
})
|
|
@@ -4886,6 +4937,272 @@ export async function revokeConnection(
|
|
|
4886
4937
|
});
|
|
4887
4938
|
}
|
|
4888
4939
|
|
|
4940
|
+
export type ClaimSlackBotPostOperationResult =
|
|
4941
|
+
| { kind: "claimed" | "in_progress" | "completed"; operation: SlackBotPostOperation }
|
|
4942
|
+
| { kind: "conflict" | "connection_not_found" };
|
|
4943
|
+
|
|
4944
|
+
/**
|
|
4945
|
+
* Claims one durable Slack post identity. The insert occurs before any provider
|
|
4946
|
+
* call; retries retain the original client_msg_id and immutable request digest.
|
|
4947
|
+
* A live claim suppresses concurrent sends, while a released/expired claim can
|
|
4948
|
+
* be reclaimed after response loss or process death.
|
|
4949
|
+
*/
|
|
4950
|
+
export async function claimSlackBotPostOperation(
|
|
4951
|
+
db: Database,
|
|
4952
|
+
input: {
|
|
4953
|
+
accountId: string;
|
|
4954
|
+
workspaceId: string;
|
|
4955
|
+
connectionId: string;
|
|
4956
|
+
operationId: string;
|
|
4957
|
+
targetKind: "channel" | "user";
|
|
4958
|
+
targetId: string;
|
|
4959
|
+
requestDigest: string;
|
|
4960
|
+
claimHolderId: string;
|
|
4961
|
+
claimLeaseMs: number;
|
|
4962
|
+
},
|
|
4963
|
+
): Promise<ClaimSlackBotPostOperationResult> {
|
|
4964
|
+
const claimLeaseMs = Math.max(1, Math.min(Math.trunc(input.claimLeaseMs), 120_000));
|
|
4965
|
+
return await withRlsContext(
|
|
4966
|
+
db,
|
|
4967
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
4968
|
+
async (scopedDb) =>
|
|
4969
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
4970
|
+
const tx = txRaw as unknown as Database;
|
|
4971
|
+
const [connection] = await tx
|
|
4972
|
+
.select({ id: schema.connections.id })
|
|
4973
|
+
.from(schema.connections)
|
|
4974
|
+
.where(
|
|
4975
|
+
and(
|
|
4976
|
+
eq(schema.connections.accountId, input.accountId),
|
|
4977
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
4978
|
+
eq(schema.connections.id, input.connectionId),
|
|
4979
|
+
),
|
|
4980
|
+
)
|
|
4981
|
+
.for("share")
|
|
4982
|
+
.limit(1);
|
|
4983
|
+
if (!connection) return { kind: "connection_not_found" } as const;
|
|
4984
|
+
|
|
4985
|
+
const [created] = await tx
|
|
4986
|
+
.insert(schema.slackBotPostOperations)
|
|
4987
|
+
.values({
|
|
4988
|
+
accountId: input.accountId,
|
|
4989
|
+
workspaceId: input.workspaceId,
|
|
4990
|
+
connectionId: input.connectionId,
|
|
4991
|
+
operationId: input.operationId,
|
|
4992
|
+
clientMessageId: input.operationId,
|
|
4993
|
+
targetKind: input.targetKind,
|
|
4994
|
+
targetId: input.targetId,
|
|
4995
|
+
requestDigest: input.requestDigest,
|
|
4996
|
+
status: "provider_started",
|
|
4997
|
+
claimHolderId: input.claimHolderId,
|
|
4998
|
+
claimExpiresAt: sql`now() + (${claimLeaseMs} * interval '1 millisecond')`,
|
|
4999
|
+
attemptCount: 1,
|
|
5000
|
+
})
|
|
5001
|
+
.onConflictDoNothing({
|
|
5002
|
+
target: [
|
|
5003
|
+
schema.slackBotPostOperations.workspaceId,
|
|
5004
|
+
schema.slackBotPostOperations.connectionId,
|
|
5005
|
+
schema.slackBotPostOperations.operationId,
|
|
5006
|
+
],
|
|
5007
|
+
})
|
|
5008
|
+
.returning();
|
|
5009
|
+
if (created) {
|
|
5010
|
+
return { kind: "claimed", operation: mapSlackBotPostOperation(created) } as const;
|
|
5011
|
+
}
|
|
5012
|
+
|
|
5013
|
+
const [existing] = await tx
|
|
5014
|
+
.select()
|
|
5015
|
+
.from(schema.slackBotPostOperations)
|
|
5016
|
+
.where(
|
|
5017
|
+
and(
|
|
5018
|
+
eq(schema.slackBotPostOperations.workspaceId, input.workspaceId),
|
|
5019
|
+
eq(schema.slackBotPostOperations.connectionId, input.connectionId),
|
|
5020
|
+
eq(schema.slackBotPostOperations.operationId, input.operationId),
|
|
5021
|
+
),
|
|
5022
|
+
)
|
|
5023
|
+
.for("update")
|
|
5024
|
+
.limit(1);
|
|
5025
|
+
if (!existing) throw new Error("Slack post operation disappeared after conflict");
|
|
5026
|
+
if (
|
|
5027
|
+
existing.accountId !== input.accountId ||
|
|
5028
|
+
existing.targetKind !== input.targetKind ||
|
|
5029
|
+
existing.targetId !== input.targetId ||
|
|
5030
|
+
existing.requestDigest !== input.requestDigest ||
|
|
5031
|
+
existing.clientMessageId !== input.operationId
|
|
5032
|
+
) {
|
|
5033
|
+
return { kind: "conflict" } as const;
|
|
5034
|
+
}
|
|
5035
|
+
if (existing.status === "completed") {
|
|
5036
|
+
return { kind: "completed", operation: mapSlackBotPostOperation(existing) } as const;
|
|
5037
|
+
}
|
|
5038
|
+
|
|
5039
|
+
const [reclaimed] = await tx
|
|
5040
|
+
.update(schema.slackBotPostOperations)
|
|
5041
|
+
.set({
|
|
5042
|
+
claimHolderId: input.claimHolderId,
|
|
5043
|
+
claimExpiresAt: sql`now() + (${claimLeaseMs} * interval '1 millisecond')`,
|
|
5044
|
+
attemptCount: sql`${schema.slackBotPostOperations.attemptCount} + 1`,
|
|
5045
|
+
lastFailureCode: null,
|
|
5046
|
+
updatedAt: sql`now()`,
|
|
5047
|
+
})
|
|
5048
|
+
.where(
|
|
5049
|
+
and(
|
|
5050
|
+
eq(schema.slackBotPostOperations.id, existing.id),
|
|
5051
|
+
or(
|
|
5052
|
+
isNull(schema.slackBotPostOperations.claimHolderId),
|
|
5053
|
+
lte(schema.slackBotPostOperations.claimExpiresAt, sql`now()`),
|
|
5054
|
+
),
|
|
5055
|
+
),
|
|
5056
|
+
)
|
|
5057
|
+
.returning();
|
|
5058
|
+
return reclaimed
|
|
5059
|
+
? ({ kind: "claimed", operation: mapSlackBotPostOperation(reclaimed) } as const)
|
|
5060
|
+
: ({ kind: "in_progress", operation: mapSlackBotPostOperation(existing) } as const);
|
|
5061
|
+
}),
|
|
5062
|
+
);
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
export async function releaseSlackBotPostOperationClaim(
|
|
5066
|
+
db: Database,
|
|
5067
|
+
input: {
|
|
5068
|
+
accountId: string;
|
|
5069
|
+
workspaceId: string;
|
|
5070
|
+
connectionId: string;
|
|
5071
|
+
operationId: string;
|
|
5072
|
+
claimHolderId: string;
|
|
5073
|
+
failureCode: string;
|
|
5074
|
+
},
|
|
5075
|
+
): Promise<boolean> {
|
|
5076
|
+
return await withRlsContext(
|
|
5077
|
+
db,
|
|
5078
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
5079
|
+
async (scopedDb) => {
|
|
5080
|
+
const rows = await scopedDb
|
|
5081
|
+
.update(schema.slackBotPostOperations)
|
|
5082
|
+
.set({
|
|
5083
|
+
claimHolderId: null,
|
|
5084
|
+
claimExpiresAt: null,
|
|
5085
|
+
lastFailureCode: input.failureCode.slice(0, 128),
|
|
5086
|
+
updatedAt: sql`now()`,
|
|
5087
|
+
})
|
|
5088
|
+
.where(
|
|
5089
|
+
and(
|
|
5090
|
+
eq(schema.slackBotPostOperations.workspaceId, input.workspaceId),
|
|
5091
|
+
eq(schema.slackBotPostOperations.connectionId, input.connectionId),
|
|
5092
|
+
eq(schema.slackBotPostOperations.operationId, input.operationId),
|
|
5093
|
+
eq(schema.slackBotPostOperations.status, "provider_started"),
|
|
5094
|
+
eq(schema.slackBotPostOperations.claimHolderId, input.claimHolderId),
|
|
5095
|
+
),
|
|
5096
|
+
)
|
|
5097
|
+
.returning({ id: schema.slackBotPostOperations.id });
|
|
5098
|
+
return rows.length === 1;
|
|
5099
|
+
},
|
|
5100
|
+
);
|
|
5101
|
+
}
|
|
5102
|
+
|
|
5103
|
+
export type CompleteSlackBotPostOperationResult =
|
|
5104
|
+
| { kind: "completed"; operation: SlackBotPostOperation; newlyCompleted: boolean }
|
|
5105
|
+
| { kind: "not_found" | "not_owned" };
|
|
5106
|
+
|
|
5107
|
+
/** Completion and the single success audit receipt commit atomically. */
|
|
5108
|
+
export async function completeSlackBotPostOperation(
|
|
5109
|
+
db: Database,
|
|
5110
|
+
input: {
|
|
5111
|
+
accountId: string;
|
|
5112
|
+
workspaceId: string;
|
|
5113
|
+
connectionId: string;
|
|
5114
|
+
operationId: string;
|
|
5115
|
+
claimHolderId: string;
|
|
5116
|
+
slackChannelId: string;
|
|
5117
|
+
slackMessageTimestamp: string;
|
|
5118
|
+
subjectId?: string | null;
|
|
5119
|
+
auditMetadata: Record<string, unknown>;
|
|
5120
|
+
},
|
|
5121
|
+
): Promise<CompleteSlackBotPostOperationResult> {
|
|
5122
|
+
return await withRlsContext(
|
|
5123
|
+
db,
|
|
5124
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
5125
|
+
async (scopedDb) =>
|
|
5126
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
5127
|
+
const tx = txRaw as unknown as Database;
|
|
5128
|
+
const [current] = await tx
|
|
5129
|
+
.select()
|
|
5130
|
+
.from(schema.slackBotPostOperations)
|
|
5131
|
+
.where(
|
|
5132
|
+
and(
|
|
5133
|
+
eq(schema.slackBotPostOperations.workspaceId, input.workspaceId),
|
|
5134
|
+
eq(schema.slackBotPostOperations.connectionId, input.connectionId),
|
|
5135
|
+
eq(schema.slackBotPostOperations.operationId, input.operationId),
|
|
5136
|
+
),
|
|
5137
|
+
)
|
|
5138
|
+
.for("update")
|
|
5139
|
+
.limit(1);
|
|
5140
|
+
if (!current) return { kind: "not_found" } as const;
|
|
5141
|
+
if (current.status === "completed") {
|
|
5142
|
+
return {
|
|
5143
|
+
kind: "completed",
|
|
5144
|
+
operation: mapSlackBotPostOperation(current),
|
|
5145
|
+
newlyCompleted: false,
|
|
5146
|
+
} as const;
|
|
5147
|
+
}
|
|
5148
|
+
if (current.claimHolderId !== input.claimHolderId) {
|
|
5149
|
+
return { kind: "not_owned" } as const;
|
|
5150
|
+
}
|
|
5151
|
+
const [completed] = await tx
|
|
5152
|
+
.update(schema.slackBotPostOperations)
|
|
5153
|
+
.set({
|
|
5154
|
+
status: "completed",
|
|
5155
|
+
claimHolderId: null,
|
|
5156
|
+
claimExpiresAt: null,
|
|
5157
|
+
lastFailureCode: null,
|
|
5158
|
+
slackChannelId: input.slackChannelId,
|
|
5159
|
+
slackMessageTimestamp: input.slackMessageTimestamp,
|
|
5160
|
+
completedAt: sql`now()`,
|
|
5161
|
+
updatedAt: sql`now()`,
|
|
5162
|
+
})
|
|
5163
|
+
.where(eq(schema.slackBotPostOperations.id, current.id))
|
|
5164
|
+
.returning();
|
|
5165
|
+
if (!completed) throw new Error("Slack post completion returned no row");
|
|
5166
|
+
await tx.insert(schema.auditEvents).values({
|
|
5167
|
+
accountId: input.accountId,
|
|
5168
|
+
workspaceId: input.workspaceId,
|
|
5169
|
+
subjectId: input.subjectId ?? null,
|
|
5170
|
+
action: "slack_bot.message.post",
|
|
5171
|
+
targetType: "connection",
|
|
5172
|
+
targetId: input.connectionId,
|
|
5173
|
+
metadata: input.auditMetadata,
|
|
5174
|
+
});
|
|
5175
|
+
return {
|
|
5176
|
+
kind: "completed",
|
|
5177
|
+
operation: mapSlackBotPostOperation(completed),
|
|
5178
|
+
newlyCompleted: true,
|
|
5179
|
+
} as const;
|
|
5180
|
+
}),
|
|
5181
|
+
);
|
|
5182
|
+
}
|
|
5183
|
+
|
|
5184
|
+
export async function getSlackBotPostOperation(
|
|
5185
|
+
db: Database,
|
|
5186
|
+
workspaceId: string,
|
|
5187
|
+
connectionId: string,
|
|
5188
|
+
operationId: string,
|
|
5189
|
+
): Promise<SlackBotPostOperation | null> {
|
|
5190
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
5191
|
+
const [row] = await scopedDb
|
|
5192
|
+
.select()
|
|
5193
|
+
.from(schema.slackBotPostOperations)
|
|
5194
|
+
.where(
|
|
5195
|
+
and(
|
|
5196
|
+
eq(schema.slackBotPostOperations.workspaceId, workspaceId),
|
|
5197
|
+
eq(schema.slackBotPostOperations.connectionId, connectionId),
|
|
5198
|
+
eq(schema.slackBotPostOperations.operationId, operationId),
|
|
5199
|
+
),
|
|
5200
|
+
)
|
|
5201
|
+
.limit(1);
|
|
5202
|
+
return row ? mapSlackBotPostOperation(row) : null;
|
|
5203
|
+
});
|
|
5204
|
+
}
|
|
5205
|
+
|
|
4889
5206
|
export async function loadConnectionCredentialForBroker(
|
|
4890
5207
|
db: Database,
|
|
4891
5208
|
settings: Settings,
|
|
@@ -5019,6 +5336,10 @@ export async function setConnectionStatus(
|
|
|
5019
5336
|
status,
|
|
5020
5337
|
lastError,
|
|
5021
5338
|
version: sql`${schema.connections.version} + 1`,
|
|
5339
|
+
verifiedInstallVersion: sql`case
|
|
5340
|
+
when ${schema.connections.verifiedInstallAt} is null then null
|
|
5341
|
+
else ${schema.connections.version} + 1
|
|
5342
|
+
end`,
|
|
5022
5343
|
updatedAt: new Date(),
|
|
5023
5344
|
})
|
|
5024
5345
|
.where(
|
|
@@ -35402,12 +35723,14 @@ function mapConnectionMetadata(row: {
|
|
|
35402
35723
|
lastUsedAt: Date | null;
|
|
35403
35724
|
lastError: string | null;
|
|
35404
35725
|
version: number;
|
|
35726
|
+
verifiedInstallAt: Date | null;
|
|
35727
|
+
verifiedInstallVersion: number | null;
|
|
35405
35728
|
metadata: Record<string, unknown>;
|
|
35406
35729
|
createdBySubjectId: string | null;
|
|
35407
35730
|
updatedBySubjectId: string | null;
|
|
35408
35731
|
createdAt: Date;
|
|
35409
35732
|
updatedAt: Date;
|
|
35410
|
-
}):
|
|
35733
|
+
}): ConnectionMetadataWithVerification {
|
|
35411
35734
|
return {
|
|
35412
35735
|
id: row.id,
|
|
35413
35736
|
accountId: row.accountId,
|
|
@@ -35422,6 +35745,8 @@ function mapConnectionMetadata(row: {
|
|
|
35422
35745
|
lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
|
|
35423
35746
|
lastError: row.lastError,
|
|
35424
35747
|
version: row.version,
|
|
35748
|
+
verifiedInstallAt: row.verifiedInstallAt?.toISOString() ?? null,
|
|
35749
|
+
verifiedInstallVersion: row.verifiedInstallVersion,
|
|
35425
35750
|
metadata: row.metadata,
|
|
35426
35751
|
createdBySubjectId: row.createdBySubjectId,
|
|
35427
35752
|
updatedBySubjectId: row.updatedBySubjectId,
|
|
@@ -35430,6 +35755,32 @@ function mapConnectionMetadata(row: {
|
|
|
35430
35755
|
};
|
|
35431
35756
|
}
|
|
35432
35757
|
|
|
35758
|
+
function mapSlackBotPostOperation(
|
|
35759
|
+
row: typeof schema.slackBotPostOperations.$inferSelect,
|
|
35760
|
+
): SlackBotPostOperation {
|
|
35761
|
+
return {
|
|
35762
|
+
id: row.id,
|
|
35763
|
+
accountId: row.accountId,
|
|
35764
|
+
workspaceId: row.workspaceId,
|
|
35765
|
+
connectionId: row.connectionId,
|
|
35766
|
+
operationId: row.operationId,
|
|
35767
|
+
clientMessageId: row.clientMessageId,
|
|
35768
|
+
targetKind: row.targetKind,
|
|
35769
|
+
targetId: row.targetId,
|
|
35770
|
+
requestDigest: row.requestDigest,
|
|
35771
|
+
status: row.status,
|
|
35772
|
+
claimHolderId: row.claimHolderId,
|
|
35773
|
+
claimExpiresAt: row.claimExpiresAt,
|
|
35774
|
+
attemptCount: row.attemptCount,
|
|
35775
|
+
lastFailureCode: row.lastFailureCode,
|
|
35776
|
+
slackChannelId: row.slackChannelId,
|
|
35777
|
+
slackMessageTimestamp: row.slackMessageTimestamp,
|
|
35778
|
+
completedAt: row.completedAt,
|
|
35779
|
+
createdAt: row.createdAt,
|
|
35780
|
+
updatedAt: row.updatedAt,
|
|
35781
|
+
};
|
|
35782
|
+
}
|
|
35783
|
+
|
|
35433
35784
|
function mapKnowledgeMemory(row: typeof schema.knowledgeMemories.$inferSelect): KnowledgeMemory {
|
|
35434
35785
|
return {
|
|
35435
35786
|
id: row.id,
|
package/src/runtime-posture.ts
CHANGED
|
@@ -73,12 +73,16 @@ export const FORCE_RLS_TABLES = [
|
|
|
73
73
|
"session_turns",
|
|
74
74
|
"session_workflow_wake_outbox",
|
|
75
75
|
"sessions",
|
|
76
|
+
"slack_bot_post_operations",
|
|
76
77
|
"social_connections",
|
|
77
78
|
"social_posts",
|
|
78
79
|
"usage_events",
|
|
79
80
|
"workspace_captures",
|
|
80
81
|
"workspace_control_events",
|
|
81
82
|
"workspace_inference_controls",
|
|
83
|
+
"workspace_instruction_policy_activation_events",
|
|
84
|
+
"workspace_instruction_policy_heads",
|
|
85
|
+
"workspace_instruction_policy_revisions",
|
|
82
86
|
"workspace_model_policies",
|
|
83
87
|
"workspace_packs",
|
|
84
88
|
"workspace_session_activity_revisions",
|
|
@@ -178,6 +182,7 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
178
182
|
"session_turns",
|
|
179
183
|
"session_workflow_wake_outbox",
|
|
180
184
|
"sessions",
|
|
185
|
+
"slack_bot_post_operations",
|
|
181
186
|
"social_connections",
|
|
182
187
|
"social_posts",
|
|
183
188
|
"stripe_webhook_events",
|
|
@@ -185,6 +190,7 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
185
190
|
"workspace_captures",
|
|
186
191
|
"workspace_control_events",
|
|
187
192
|
"workspace_inference_controls",
|
|
193
|
+
"workspace_instruction_policy_heads",
|
|
188
194
|
"workspace_memberships",
|
|
189
195
|
"workspace_model_policies",
|
|
190
196
|
"workspace_packs",
|
|
@@ -197,8 +203,12 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
197
203
|
/** Configuration is deployment-global and intentionally read-only at runtime. */
|
|
198
204
|
export const RUNTIME_READ_ONLY_TABLES = ["nested_agent_depth_configuration"] as const;
|
|
199
205
|
|
|
200
|
-
/**
|
|
201
|
-
export const RUNTIME_READ_INSERT_TABLES = [
|
|
206
|
+
/** Append-only evidence/revision tables are insertable and queryable, never mutable. */
|
|
207
|
+
export const RUNTIME_READ_INSERT_TABLES = [
|
|
208
|
+
"session_spawn_denials",
|
|
209
|
+
"workspace_instruction_policy_activation_events",
|
|
210
|
+
"workspace_instruction_policy_revisions",
|
|
211
|
+
] as const;
|
|
202
212
|
|
|
203
213
|
/**
|
|
204
214
|
* These FORCE-RLS tables are owned by security-definer host-export routines.
|
package/src/schema.ts
CHANGED
|
@@ -485,6 +485,12 @@ export const connections = pgTable(
|
|
|
485
485
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
486
486
|
lastError: text("last_error"),
|
|
487
487
|
version: integer("version").notNull().default(1),
|
|
488
|
+
// Server-owned proof that the dedicated install route verified the exact
|
|
489
|
+
// credential at this connection version. Generic/legacy writers cannot set
|
|
490
|
+
// these columns, and migration 0131 clears them when protected fields change
|
|
491
|
+
// without a fresh verification in the same statement.
|
|
492
|
+
verifiedInstallAt: timestamp("verified_install_at", { withTimezone: true }),
|
|
493
|
+
verifiedInstallVersion: integer("verified_install_version"),
|
|
488
494
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
489
495
|
createdBySubjectId: text("created_by_subject_id"),
|
|
490
496
|
updatedBySubjectId: text("updated_by_subject_id"),
|
|
@@ -510,6 +516,85 @@ export const connections = pgTable(
|
|
|
510
516
|
}),
|
|
511
517
|
);
|
|
512
518
|
|
|
519
|
+
// Durable provider-operation identity for OpenGeni Slack bot posts. The
|
|
520
|
+
// caller-supplied operation UUID is also Slack's client_msg_id; a bounded claim
|
|
521
|
+
// serializes live attempts while an expired/released claim can safely retry the
|
|
522
|
+
// same provider identity after response loss or process death.
|
|
523
|
+
export const slackBotPostOperations = pgTable(
|
|
524
|
+
"slack_bot_post_operations",
|
|
525
|
+
{
|
|
526
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
527
|
+
accountId: uuid("account_id")
|
|
528
|
+
.notNull()
|
|
529
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
530
|
+
workspaceId: uuid("workspace_id")
|
|
531
|
+
.notNull()
|
|
532
|
+
.references(() => workspaces.id, { onDelete: "cascade" }),
|
|
533
|
+
connectionId: uuid("connection_id")
|
|
534
|
+
.notNull()
|
|
535
|
+
.references(() => connections.id, { onDelete: "cascade" }),
|
|
536
|
+
operationId: uuid("operation_id").notNull(),
|
|
537
|
+
clientMessageId: uuid("client_message_id").notNull(),
|
|
538
|
+
targetKind: text("target_kind").$type<"channel" | "user">().notNull(),
|
|
539
|
+
targetId: text("target_id").notNull(),
|
|
540
|
+
requestDigest: text("request_digest").notNull(),
|
|
541
|
+
status: text("status").$type<"provider_started" | "completed">().notNull(),
|
|
542
|
+
claimHolderId: uuid("claim_holder_id"),
|
|
543
|
+
claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
|
|
544
|
+
attemptCount: integer("attempt_count").notNull().default(0),
|
|
545
|
+
lastFailureCode: text("last_failure_code"),
|
|
546
|
+
slackChannelId: text("slack_channel_id"),
|
|
547
|
+
slackMessageTimestamp: text("slack_message_timestamp"),
|
|
548
|
+
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
549
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
550
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
551
|
+
},
|
|
552
|
+
(table) => ({
|
|
553
|
+
workspaceOperation: uniqueIndex("slack_bot_post_operations_workspace_operation_uq").on(
|
|
554
|
+
table.workspaceId,
|
|
555
|
+
table.connectionId,
|
|
556
|
+
table.operationId,
|
|
557
|
+
),
|
|
558
|
+
workspaceStatus: index("slack_bot_post_operations_workspace_status_idx").on(
|
|
559
|
+
table.workspaceId,
|
|
560
|
+
table.status,
|
|
561
|
+
table.updatedAt,
|
|
562
|
+
),
|
|
563
|
+
targetKindValid: check(
|
|
564
|
+
"slack_bot_post_operations_target_kind_check",
|
|
565
|
+
sql`${table.targetKind} in ('channel', 'user')`,
|
|
566
|
+
),
|
|
567
|
+
statusValid: check(
|
|
568
|
+
"slack_bot_post_operations_status_check",
|
|
569
|
+
sql`${table.status} in ('provider_started', 'completed')`,
|
|
570
|
+
),
|
|
571
|
+
identityValid: check(
|
|
572
|
+
"slack_bot_post_operations_identity_check",
|
|
573
|
+
sql`${table.clientMessageId} = ${table.operationId}
|
|
574
|
+
and length(${table.targetId}) between 1 and 64
|
|
575
|
+
and ${table.requestDigest} ~ '^[0-9a-f]{64}$'
|
|
576
|
+
and ${table.attemptCount} > 0
|
|
577
|
+
and ((${table.claimHolderId} is null) = (${table.claimExpiresAt} is null))`,
|
|
578
|
+
),
|
|
579
|
+
completionValid: check(
|
|
580
|
+
"slack_bot_post_operations_completion_check",
|
|
581
|
+
sql`(
|
|
582
|
+
${table.status} = 'provider_started'
|
|
583
|
+
and ${table.slackChannelId} is null
|
|
584
|
+
and ${table.slackMessageTimestamp} is null
|
|
585
|
+
and ${table.completedAt} is null
|
|
586
|
+
) or (
|
|
587
|
+
${table.status} = 'completed'
|
|
588
|
+
and ${table.claimHolderId} is null
|
|
589
|
+
and ${table.claimExpiresAt} is null
|
|
590
|
+
and ${table.slackChannelId} is not null
|
|
591
|
+
and ${table.slackMessageTimestamp} is not null
|
|
592
|
+
and ${table.completedAt} is not null
|
|
593
|
+
)`,
|
|
594
|
+
),
|
|
595
|
+
}),
|
|
596
|
+
);
|
|
597
|
+
|
|
513
598
|
// OAuth client registrations minted through MCP DCR, keyed by authorization
|
|
514
599
|
// server issuer. This is deployment-wide client identity, not a workspace
|
|
515
600
|
// credential; per-user/provider tokens still live only in connections.
|
|
@@ -4333,3 +4418,5 @@ export const rigChanges = pgTable(
|
|
|
4333
4418
|
workspaceStatus: index("rig_changes_workspace_status_idx").on(table.workspaceId, table.status),
|
|
4334
4419
|
}),
|
|
4335
4420
|
);
|
|
4421
|
+
|
|
4422
|
+
export * from "./workspace-instruction-policies-schema";
|