@opengeni/db 0.12.6 → 0.13.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/{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 +2499 -1865
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +155 -12
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-CeXWlYcG.d.ts → schema-Bh1Hr7xY.d.ts} +2052 -944
- 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 +356 -6
- 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;
|
|
@@ -4723,6 +4756,8 @@ const connectionMetadataColumns = {
|
|
|
4723
4756
|
lastUsedAt: schema.connections.lastUsedAt,
|
|
4724
4757
|
lastError: schema.connections.lastError,
|
|
4725
4758
|
version: schema.connections.version,
|
|
4759
|
+
verifiedInstallAt: schema.connections.verifiedInstallAt,
|
|
4760
|
+
verifiedInstallVersion: schema.connections.verifiedInstallVersion,
|
|
4726
4761
|
metadata: schema.connections.metadata,
|
|
4727
4762
|
createdBySubjectId: schema.connections.createdBySubjectId,
|
|
4728
4763
|
updatedBySubjectId: schema.connections.updatedBySubjectId,
|
|
@@ -4739,7 +4774,7 @@ function connectionSubjectVisibility(subjectId?: string | null): SQL {
|
|
|
4739
4774
|
export async function createConnection(
|
|
4740
4775
|
db: Database,
|
|
4741
4776
|
input: CreateConnectionInput,
|
|
4742
|
-
): Promise<
|
|
4777
|
+
): Promise<ConnectionMetadataWithVerification> {
|
|
4743
4778
|
return await withRlsContext(
|
|
4744
4779
|
db,
|
|
4745
4780
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
@@ -4756,6 +4791,8 @@ export async function createConnection(
|
|
|
4756
4791
|
credentialEncrypted: input.credentialEncrypted,
|
|
4757
4792
|
grantedScopes: input.grantedScopes ?? [],
|
|
4758
4793
|
expiresAt: input.expiresAt ?? null,
|
|
4794
|
+
verifiedInstallAt: input.verifiedInstallAt ?? null,
|
|
4795
|
+
verifiedInstallVersion: input.verifiedInstallVersion ?? null,
|
|
4759
4796
|
metadata: input.metadata ?? {},
|
|
4760
4797
|
createdBySubjectId: input.createdBySubjectId ?? null,
|
|
4761
4798
|
updatedBySubjectId: input.updatedBySubjectId ?? input.createdBySubjectId ?? null,
|
|
@@ -4773,7 +4810,7 @@ export async function listConnectionsMetadata(
|
|
|
4773
4810
|
db: Database,
|
|
4774
4811
|
workspaceId: string,
|
|
4775
4812
|
subjectId?: string | null,
|
|
4776
|
-
): Promise<
|
|
4813
|
+
): Promise<ConnectionMetadataWithVerification[]> {
|
|
4777
4814
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
4778
4815
|
const rows = await scopedDb
|
|
4779
4816
|
.select(connectionMetadataColumns)
|
|
@@ -4794,7 +4831,7 @@ export async function getConnectionMetadata(
|
|
|
4794
4831
|
workspaceId: string,
|
|
4795
4832
|
connectionId: string,
|
|
4796
4833
|
subjectId?: string | null,
|
|
4797
|
-
): Promise<
|
|
4834
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
4798
4835
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
4799
4836
|
const [row] = await scopedDb
|
|
4800
4837
|
.select(connectionMetadataColumns)
|
|
@@ -4814,7 +4851,7 @@ export async function getConnectionMetadata(
|
|
|
4814
4851
|
export async function updateConnection(
|
|
4815
4852
|
db: Database,
|
|
4816
4853
|
input: UpdateConnectionInput,
|
|
4817
|
-
): Promise<
|
|
4854
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
4818
4855
|
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
4819
4856
|
const set = {
|
|
4820
4857
|
updatedAt: new Date(),
|
|
@@ -4831,6 +4868,12 @@ export async function updateConnection(
|
|
|
4831
4868
|
: {}),
|
|
4832
4869
|
...(input.grantedScopes !== undefined ? { grantedScopes: input.grantedScopes } : {}),
|
|
4833
4870
|
...(input.expiresAt !== undefined ? { expiresAt: input.expiresAt } : {}),
|
|
4871
|
+
...(input.verifiedInstallAt !== undefined
|
|
4872
|
+
? { verifiedInstallAt: input.verifiedInstallAt }
|
|
4873
|
+
: {}),
|
|
4874
|
+
...(input.verifiedInstallVersion !== undefined
|
|
4875
|
+
? { verifiedInstallVersion: input.verifiedInstallVersion }
|
|
4876
|
+
: {}),
|
|
4834
4877
|
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
|
4835
4878
|
...(input.updatedBySubjectId !== undefined
|
|
4836
4879
|
? { updatedBySubjectId: input.updatedBySubjectId }
|
|
@@ -4859,7 +4902,7 @@ export async function revokeConnection(
|
|
|
4859
4902
|
workspaceId: string,
|
|
4860
4903
|
connectionId: string,
|
|
4861
4904
|
updatedBySubjectId?: string | null,
|
|
4862
|
-
): Promise<
|
|
4905
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
4863
4906
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
4864
4907
|
const [row] = await scopedDb
|
|
4865
4908
|
.update(schema.connections)
|
|
@@ -4868,6 +4911,13 @@ export async function revokeConnection(
|
|
|
4868
4911
|
// The version bump invalidates any in-flight refresh's (id, version) CAS,
|
|
4869
4912
|
// so a racing refresh cannot commit and flip the row back to active.
|
|
4870
4913
|
version: sql`${schema.connections.version} + 1`,
|
|
4914
|
+
// Status-only revocation does not replace the verified credential or bot
|
|
4915
|
+
// identity. Carry the marker to the same new CAS version so the dedicated
|
|
4916
|
+
// reinstall path can still recognize (but not use) the inactive row.
|
|
4917
|
+
verifiedInstallVersion: sql`case
|
|
4918
|
+
when ${schema.connections.verifiedInstallAt} is null then null
|
|
4919
|
+
else ${schema.connections.version} + 1
|
|
4920
|
+
end`,
|
|
4871
4921
|
updatedBySubjectId: updatedBySubjectId ?? null,
|
|
4872
4922
|
updatedAt: new Date(),
|
|
4873
4923
|
})
|
|
@@ -4886,6 +4936,272 @@ export async function revokeConnection(
|
|
|
4886
4936
|
});
|
|
4887
4937
|
}
|
|
4888
4938
|
|
|
4939
|
+
export type ClaimSlackBotPostOperationResult =
|
|
4940
|
+
| { kind: "claimed" | "in_progress" | "completed"; operation: SlackBotPostOperation }
|
|
4941
|
+
| { kind: "conflict" | "connection_not_found" };
|
|
4942
|
+
|
|
4943
|
+
/**
|
|
4944
|
+
* Claims one durable Slack post identity. The insert occurs before any provider
|
|
4945
|
+
* call; retries retain the original client_msg_id and immutable request digest.
|
|
4946
|
+
* A live claim suppresses concurrent sends, while a released/expired claim can
|
|
4947
|
+
* be reclaimed after response loss or process death.
|
|
4948
|
+
*/
|
|
4949
|
+
export async function claimSlackBotPostOperation(
|
|
4950
|
+
db: Database,
|
|
4951
|
+
input: {
|
|
4952
|
+
accountId: string;
|
|
4953
|
+
workspaceId: string;
|
|
4954
|
+
connectionId: string;
|
|
4955
|
+
operationId: string;
|
|
4956
|
+
targetKind: "channel" | "user";
|
|
4957
|
+
targetId: string;
|
|
4958
|
+
requestDigest: string;
|
|
4959
|
+
claimHolderId: string;
|
|
4960
|
+
claimLeaseMs: number;
|
|
4961
|
+
},
|
|
4962
|
+
): Promise<ClaimSlackBotPostOperationResult> {
|
|
4963
|
+
const claimLeaseMs = Math.max(1, Math.min(Math.trunc(input.claimLeaseMs), 120_000));
|
|
4964
|
+
return await withRlsContext(
|
|
4965
|
+
db,
|
|
4966
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
4967
|
+
async (scopedDb) =>
|
|
4968
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
4969
|
+
const tx = txRaw as unknown as Database;
|
|
4970
|
+
const [connection] = await tx
|
|
4971
|
+
.select({ id: schema.connections.id })
|
|
4972
|
+
.from(schema.connections)
|
|
4973
|
+
.where(
|
|
4974
|
+
and(
|
|
4975
|
+
eq(schema.connections.accountId, input.accountId),
|
|
4976
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
4977
|
+
eq(schema.connections.id, input.connectionId),
|
|
4978
|
+
),
|
|
4979
|
+
)
|
|
4980
|
+
.for("share")
|
|
4981
|
+
.limit(1);
|
|
4982
|
+
if (!connection) return { kind: "connection_not_found" } as const;
|
|
4983
|
+
|
|
4984
|
+
const [created] = await tx
|
|
4985
|
+
.insert(schema.slackBotPostOperations)
|
|
4986
|
+
.values({
|
|
4987
|
+
accountId: input.accountId,
|
|
4988
|
+
workspaceId: input.workspaceId,
|
|
4989
|
+
connectionId: input.connectionId,
|
|
4990
|
+
operationId: input.operationId,
|
|
4991
|
+
clientMessageId: input.operationId,
|
|
4992
|
+
targetKind: input.targetKind,
|
|
4993
|
+
targetId: input.targetId,
|
|
4994
|
+
requestDigest: input.requestDigest,
|
|
4995
|
+
status: "provider_started",
|
|
4996
|
+
claimHolderId: input.claimHolderId,
|
|
4997
|
+
claimExpiresAt: sql`now() + (${claimLeaseMs} * interval '1 millisecond')`,
|
|
4998
|
+
attemptCount: 1,
|
|
4999
|
+
})
|
|
5000
|
+
.onConflictDoNothing({
|
|
5001
|
+
target: [
|
|
5002
|
+
schema.slackBotPostOperations.workspaceId,
|
|
5003
|
+
schema.slackBotPostOperations.connectionId,
|
|
5004
|
+
schema.slackBotPostOperations.operationId,
|
|
5005
|
+
],
|
|
5006
|
+
})
|
|
5007
|
+
.returning();
|
|
5008
|
+
if (created) {
|
|
5009
|
+
return { kind: "claimed", operation: mapSlackBotPostOperation(created) } as const;
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
const [existing] = await tx
|
|
5013
|
+
.select()
|
|
5014
|
+
.from(schema.slackBotPostOperations)
|
|
5015
|
+
.where(
|
|
5016
|
+
and(
|
|
5017
|
+
eq(schema.slackBotPostOperations.workspaceId, input.workspaceId),
|
|
5018
|
+
eq(schema.slackBotPostOperations.connectionId, input.connectionId),
|
|
5019
|
+
eq(schema.slackBotPostOperations.operationId, input.operationId),
|
|
5020
|
+
),
|
|
5021
|
+
)
|
|
5022
|
+
.for("update")
|
|
5023
|
+
.limit(1);
|
|
5024
|
+
if (!existing) throw new Error("Slack post operation disappeared after conflict");
|
|
5025
|
+
if (
|
|
5026
|
+
existing.accountId !== input.accountId ||
|
|
5027
|
+
existing.targetKind !== input.targetKind ||
|
|
5028
|
+
existing.targetId !== input.targetId ||
|
|
5029
|
+
existing.requestDigest !== input.requestDigest ||
|
|
5030
|
+
existing.clientMessageId !== input.operationId
|
|
5031
|
+
) {
|
|
5032
|
+
return { kind: "conflict" } as const;
|
|
5033
|
+
}
|
|
5034
|
+
if (existing.status === "completed") {
|
|
5035
|
+
return { kind: "completed", operation: mapSlackBotPostOperation(existing) } as const;
|
|
5036
|
+
}
|
|
5037
|
+
|
|
5038
|
+
const [reclaimed] = await tx
|
|
5039
|
+
.update(schema.slackBotPostOperations)
|
|
5040
|
+
.set({
|
|
5041
|
+
claimHolderId: input.claimHolderId,
|
|
5042
|
+
claimExpiresAt: sql`now() + (${claimLeaseMs} * interval '1 millisecond')`,
|
|
5043
|
+
attemptCount: sql`${schema.slackBotPostOperations.attemptCount} + 1`,
|
|
5044
|
+
lastFailureCode: null,
|
|
5045
|
+
updatedAt: sql`now()`,
|
|
5046
|
+
})
|
|
5047
|
+
.where(
|
|
5048
|
+
and(
|
|
5049
|
+
eq(schema.slackBotPostOperations.id, existing.id),
|
|
5050
|
+
or(
|
|
5051
|
+
isNull(schema.slackBotPostOperations.claimHolderId),
|
|
5052
|
+
lte(schema.slackBotPostOperations.claimExpiresAt, sql`now()`),
|
|
5053
|
+
),
|
|
5054
|
+
),
|
|
5055
|
+
)
|
|
5056
|
+
.returning();
|
|
5057
|
+
return reclaimed
|
|
5058
|
+
? ({ kind: "claimed", operation: mapSlackBotPostOperation(reclaimed) } as const)
|
|
5059
|
+
: ({ kind: "in_progress", operation: mapSlackBotPostOperation(existing) } as const);
|
|
5060
|
+
}),
|
|
5061
|
+
);
|
|
5062
|
+
}
|
|
5063
|
+
|
|
5064
|
+
export async function releaseSlackBotPostOperationClaim(
|
|
5065
|
+
db: Database,
|
|
5066
|
+
input: {
|
|
5067
|
+
accountId: string;
|
|
5068
|
+
workspaceId: string;
|
|
5069
|
+
connectionId: string;
|
|
5070
|
+
operationId: string;
|
|
5071
|
+
claimHolderId: string;
|
|
5072
|
+
failureCode: string;
|
|
5073
|
+
},
|
|
5074
|
+
): Promise<boolean> {
|
|
5075
|
+
return await withRlsContext(
|
|
5076
|
+
db,
|
|
5077
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
5078
|
+
async (scopedDb) => {
|
|
5079
|
+
const rows = await scopedDb
|
|
5080
|
+
.update(schema.slackBotPostOperations)
|
|
5081
|
+
.set({
|
|
5082
|
+
claimHolderId: null,
|
|
5083
|
+
claimExpiresAt: null,
|
|
5084
|
+
lastFailureCode: input.failureCode.slice(0, 128),
|
|
5085
|
+
updatedAt: sql`now()`,
|
|
5086
|
+
})
|
|
5087
|
+
.where(
|
|
5088
|
+
and(
|
|
5089
|
+
eq(schema.slackBotPostOperations.workspaceId, input.workspaceId),
|
|
5090
|
+
eq(schema.slackBotPostOperations.connectionId, input.connectionId),
|
|
5091
|
+
eq(schema.slackBotPostOperations.operationId, input.operationId),
|
|
5092
|
+
eq(schema.slackBotPostOperations.status, "provider_started"),
|
|
5093
|
+
eq(schema.slackBotPostOperations.claimHolderId, input.claimHolderId),
|
|
5094
|
+
),
|
|
5095
|
+
)
|
|
5096
|
+
.returning({ id: schema.slackBotPostOperations.id });
|
|
5097
|
+
return rows.length === 1;
|
|
5098
|
+
},
|
|
5099
|
+
);
|
|
5100
|
+
}
|
|
5101
|
+
|
|
5102
|
+
export type CompleteSlackBotPostOperationResult =
|
|
5103
|
+
| { kind: "completed"; operation: SlackBotPostOperation; newlyCompleted: boolean }
|
|
5104
|
+
| { kind: "not_found" | "not_owned" };
|
|
5105
|
+
|
|
5106
|
+
/** Completion and the single success audit receipt commit atomically. */
|
|
5107
|
+
export async function completeSlackBotPostOperation(
|
|
5108
|
+
db: Database,
|
|
5109
|
+
input: {
|
|
5110
|
+
accountId: string;
|
|
5111
|
+
workspaceId: string;
|
|
5112
|
+
connectionId: string;
|
|
5113
|
+
operationId: string;
|
|
5114
|
+
claimHolderId: string;
|
|
5115
|
+
slackChannelId: string;
|
|
5116
|
+
slackMessageTimestamp: string;
|
|
5117
|
+
subjectId?: string | null;
|
|
5118
|
+
auditMetadata: Record<string, unknown>;
|
|
5119
|
+
},
|
|
5120
|
+
): Promise<CompleteSlackBotPostOperationResult> {
|
|
5121
|
+
return await withRlsContext(
|
|
5122
|
+
db,
|
|
5123
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
5124
|
+
async (scopedDb) =>
|
|
5125
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
5126
|
+
const tx = txRaw as unknown as Database;
|
|
5127
|
+
const [current] = await tx
|
|
5128
|
+
.select()
|
|
5129
|
+
.from(schema.slackBotPostOperations)
|
|
5130
|
+
.where(
|
|
5131
|
+
and(
|
|
5132
|
+
eq(schema.slackBotPostOperations.workspaceId, input.workspaceId),
|
|
5133
|
+
eq(schema.slackBotPostOperations.connectionId, input.connectionId),
|
|
5134
|
+
eq(schema.slackBotPostOperations.operationId, input.operationId),
|
|
5135
|
+
),
|
|
5136
|
+
)
|
|
5137
|
+
.for("update")
|
|
5138
|
+
.limit(1);
|
|
5139
|
+
if (!current) return { kind: "not_found" } as const;
|
|
5140
|
+
if (current.status === "completed") {
|
|
5141
|
+
return {
|
|
5142
|
+
kind: "completed",
|
|
5143
|
+
operation: mapSlackBotPostOperation(current),
|
|
5144
|
+
newlyCompleted: false,
|
|
5145
|
+
} as const;
|
|
5146
|
+
}
|
|
5147
|
+
if (current.claimHolderId !== input.claimHolderId) {
|
|
5148
|
+
return { kind: "not_owned" } as const;
|
|
5149
|
+
}
|
|
5150
|
+
const [completed] = await tx
|
|
5151
|
+
.update(schema.slackBotPostOperations)
|
|
5152
|
+
.set({
|
|
5153
|
+
status: "completed",
|
|
5154
|
+
claimHolderId: null,
|
|
5155
|
+
claimExpiresAt: null,
|
|
5156
|
+
lastFailureCode: null,
|
|
5157
|
+
slackChannelId: input.slackChannelId,
|
|
5158
|
+
slackMessageTimestamp: input.slackMessageTimestamp,
|
|
5159
|
+
completedAt: sql`now()`,
|
|
5160
|
+
updatedAt: sql`now()`,
|
|
5161
|
+
})
|
|
5162
|
+
.where(eq(schema.slackBotPostOperations.id, current.id))
|
|
5163
|
+
.returning();
|
|
5164
|
+
if (!completed) throw new Error("Slack post completion returned no row");
|
|
5165
|
+
await tx.insert(schema.auditEvents).values({
|
|
5166
|
+
accountId: input.accountId,
|
|
5167
|
+
workspaceId: input.workspaceId,
|
|
5168
|
+
subjectId: input.subjectId ?? null,
|
|
5169
|
+
action: "slack_bot.message.post",
|
|
5170
|
+
targetType: "connection",
|
|
5171
|
+
targetId: input.connectionId,
|
|
5172
|
+
metadata: input.auditMetadata,
|
|
5173
|
+
});
|
|
5174
|
+
return {
|
|
5175
|
+
kind: "completed",
|
|
5176
|
+
operation: mapSlackBotPostOperation(completed),
|
|
5177
|
+
newlyCompleted: true,
|
|
5178
|
+
} as const;
|
|
5179
|
+
}),
|
|
5180
|
+
);
|
|
5181
|
+
}
|
|
5182
|
+
|
|
5183
|
+
export async function getSlackBotPostOperation(
|
|
5184
|
+
db: Database,
|
|
5185
|
+
workspaceId: string,
|
|
5186
|
+
connectionId: string,
|
|
5187
|
+
operationId: string,
|
|
5188
|
+
): Promise<SlackBotPostOperation | null> {
|
|
5189
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
5190
|
+
const [row] = await scopedDb
|
|
5191
|
+
.select()
|
|
5192
|
+
.from(schema.slackBotPostOperations)
|
|
5193
|
+
.where(
|
|
5194
|
+
and(
|
|
5195
|
+
eq(schema.slackBotPostOperations.workspaceId, workspaceId),
|
|
5196
|
+
eq(schema.slackBotPostOperations.connectionId, connectionId),
|
|
5197
|
+
eq(schema.slackBotPostOperations.operationId, operationId),
|
|
5198
|
+
),
|
|
5199
|
+
)
|
|
5200
|
+
.limit(1);
|
|
5201
|
+
return row ? mapSlackBotPostOperation(row) : null;
|
|
5202
|
+
});
|
|
5203
|
+
}
|
|
5204
|
+
|
|
4889
5205
|
export async function loadConnectionCredentialForBroker(
|
|
4890
5206
|
db: Database,
|
|
4891
5207
|
settings: Settings,
|
|
@@ -5019,6 +5335,10 @@ export async function setConnectionStatus(
|
|
|
5019
5335
|
status,
|
|
5020
5336
|
lastError,
|
|
5021
5337
|
version: sql`${schema.connections.version} + 1`,
|
|
5338
|
+
verifiedInstallVersion: sql`case
|
|
5339
|
+
when ${schema.connections.verifiedInstallAt} is null then null
|
|
5340
|
+
else ${schema.connections.version} + 1
|
|
5341
|
+
end`,
|
|
5022
5342
|
updatedAt: new Date(),
|
|
5023
5343
|
})
|
|
5024
5344
|
.where(
|
|
@@ -35402,12 +35722,14 @@ function mapConnectionMetadata(row: {
|
|
|
35402
35722
|
lastUsedAt: Date | null;
|
|
35403
35723
|
lastError: string | null;
|
|
35404
35724
|
version: number;
|
|
35725
|
+
verifiedInstallAt: Date | null;
|
|
35726
|
+
verifiedInstallVersion: number | null;
|
|
35405
35727
|
metadata: Record<string, unknown>;
|
|
35406
35728
|
createdBySubjectId: string | null;
|
|
35407
35729
|
updatedBySubjectId: string | null;
|
|
35408
35730
|
createdAt: Date;
|
|
35409
35731
|
updatedAt: Date;
|
|
35410
|
-
}):
|
|
35732
|
+
}): ConnectionMetadataWithVerification {
|
|
35411
35733
|
return {
|
|
35412
35734
|
id: row.id,
|
|
35413
35735
|
accountId: row.accountId,
|
|
@@ -35422,6 +35744,8 @@ function mapConnectionMetadata(row: {
|
|
|
35422
35744
|
lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
|
|
35423
35745
|
lastError: row.lastError,
|
|
35424
35746
|
version: row.version,
|
|
35747
|
+
verifiedInstallAt: row.verifiedInstallAt?.toISOString() ?? null,
|
|
35748
|
+
verifiedInstallVersion: row.verifiedInstallVersion,
|
|
35425
35749
|
metadata: row.metadata,
|
|
35426
35750
|
createdBySubjectId: row.createdBySubjectId,
|
|
35427
35751
|
updatedBySubjectId: row.updatedBySubjectId,
|
|
@@ -35430,6 +35754,32 @@ function mapConnectionMetadata(row: {
|
|
|
35430
35754
|
};
|
|
35431
35755
|
}
|
|
35432
35756
|
|
|
35757
|
+
function mapSlackBotPostOperation(
|
|
35758
|
+
row: typeof schema.slackBotPostOperations.$inferSelect,
|
|
35759
|
+
): SlackBotPostOperation {
|
|
35760
|
+
return {
|
|
35761
|
+
id: row.id,
|
|
35762
|
+
accountId: row.accountId,
|
|
35763
|
+
workspaceId: row.workspaceId,
|
|
35764
|
+
connectionId: row.connectionId,
|
|
35765
|
+
operationId: row.operationId,
|
|
35766
|
+
clientMessageId: row.clientMessageId,
|
|
35767
|
+
targetKind: row.targetKind,
|
|
35768
|
+
targetId: row.targetId,
|
|
35769
|
+
requestDigest: row.requestDigest,
|
|
35770
|
+
status: row.status,
|
|
35771
|
+
claimHolderId: row.claimHolderId,
|
|
35772
|
+
claimExpiresAt: row.claimExpiresAt,
|
|
35773
|
+
attemptCount: row.attemptCount,
|
|
35774
|
+
lastFailureCode: row.lastFailureCode,
|
|
35775
|
+
slackChannelId: row.slackChannelId,
|
|
35776
|
+
slackMessageTimestamp: row.slackMessageTimestamp,
|
|
35777
|
+
completedAt: row.completedAt,
|
|
35778
|
+
createdAt: row.createdAt,
|
|
35779
|
+
updatedAt: row.updatedAt,
|
|
35780
|
+
};
|
|
35781
|
+
}
|
|
35782
|
+
|
|
35433
35783
|
function mapKnowledgeMemory(row: typeof schema.knowledgeMemories.$inferSelect): KnowledgeMemory {
|
|
35434
35784
|
return {
|
|
35435
35785
|
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";
|