@opengeni/db 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/artifact-catalog.d.ts +12 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/capability-reconciliation.js +2 -2
  4. package/dist/{chunk-KEDWSSNP.js → chunk-7Z7ALFRE.js} +2 -2
  5. package/dist/{chunk-OQZOGCLG.js → chunk-BLUQG54M.js} +2 -2
  6. package/dist/{chunk-Y77PVZZD.js → chunk-BZV5ZDRY.js} +2 -2
  7. package/dist/{chunk-MACELCXG.js → chunk-CIGHXFPD.js} +3 -3
  8. package/dist/{chunk-7S53WLPI.js → chunk-EKXCF3FH.js} +4 -4
  9. package/dist/{chunk-F3YMVRF4.js → chunk-GLA5YCQS.js} +4 -4
  10. package/dist/{chunk-KH3TYDGY.js → chunk-J4WFOVBV.js} +2 -2
  11. package/dist/{chunk-NLSG7GGA.js → chunk-KJVOBLFC.js} +3 -3
  12. package/dist/{chunk-7POTLB24.js → chunk-M2YGXCCQ.js} +2 -2
  13. package/dist/{chunk-PQGTWFRF.js → chunk-M3SWH4MU.js} +2 -2
  14. package/dist/{chunk-OMHBVHX7.js → chunk-ODKYBA3C.js} +5 -5
  15. package/dist/{chunk-56MYWKQV.js → chunk-SKYRM7R7.js} +2 -2
  16. package/dist/{chunk-N5QCKHYL.js → chunk-T5NHDEZW.js} +62 -19
  17. package/dist/chunk-T5NHDEZW.js.map +1 -0
  18. package/dist/{chunk-LDTQGYD2.js → chunk-VOB6TXSJ.js} +16 -1
  19. package/dist/chunk-VOB6TXSJ.js.map +1 -0
  20. package/dist/{chunk-UEQQ2WWU.js → chunk-Z5HJXH3X.js} +3 -26
  21. package/dist/chunk-Z5HJXH3X.js.map +1 -0
  22. package/dist/connect-attempts.js +3 -3
  23. package/dist/editable-artifact-durable-export.js +2 -2
  24. package/dist/editable-artifacts.js +3 -3
  25. package/dist/index.d.ts +14 -5
  26. package/dist/index.js +85 -30
  27. package/dist/index.js.map +1 -1
  28. package/dist/managed-auth-session-sets.js +3 -3
  29. package/dist/mcp-operations.js +2 -2
  30. package/dist/organization-integration-policy.js +3 -3
  31. package/dist/provision-roles.js +1 -1
  32. package/dist/retained-provider-commands.js +6 -6
  33. package/dist/runtime-posture.d.ts +3 -2
  34. package/dist/schema.d.ts +93 -0
  35. package/dist/schema.js +3 -1
  36. package/dist/session-background-commands.js +4 -4
  37. package/dist/session-command-output.js +5 -5
  38. package/dist/session-event-slices.js +3 -3
  39. package/dist/session-mcp-credential-rotation.js +5 -5
  40. package/dist/session-tenancy.d.ts +6 -1
  41. package/dist/session-tenancy.js +3 -5
  42. package/dist/video-generation.js +3 -3
  43. package/dist/workspace-tool-defaults.js +4 -4
  44. package/drizzle/0610_artifact_catalog_pins.sql +165 -0
  45. package/drizzle/0611_universal_session_tenancy_activation.sql +166 -0
  46. package/drizzle/0612_scheduled_model_unavailable_refusal.sql +21 -0
  47. package/package.json +7 -7
  48. package/src/artifact-catalog.ts +31 -5
  49. package/src/index.ts +94 -19
  50. package/src/provision-roles.ts +16 -0
  51. package/src/runtime-posture-cli.ts +0 -2
  52. package/src/runtime-posture.ts +64 -27
  53. package/src/schema.ts +17 -0
  54. package/src/session-tenancy.ts +9 -31
  55. package/dist/chunk-LDTQGYD2.js.map +0 -1
  56. package/dist/chunk-N5QCKHYL.js.map +0 -1
  57. package/dist/chunk-UEQQ2WWU.js.map +0 -1
  58. /package/dist/{chunk-KEDWSSNP.js.map → chunk-7Z7ALFRE.js.map} +0 -0
  59. /package/dist/{chunk-OQZOGCLG.js.map → chunk-BLUQG54M.js.map} +0 -0
  60. /package/dist/{chunk-Y77PVZZD.js.map → chunk-BZV5ZDRY.js.map} +0 -0
  61. /package/dist/{chunk-MACELCXG.js.map → chunk-CIGHXFPD.js.map} +0 -0
  62. /package/dist/{chunk-7S53WLPI.js.map → chunk-EKXCF3FH.js.map} +0 -0
  63. /package/dist/{chunk-F3YMVRF4.js.map → chunk-GLA5YCQS.js.map} +0 -0
  64. /package/dist/{chunk-KH3TYDGY.js.map → chunk-J4WFOVBV.js.map} +0 -0
  65. /package/dist/{chunk-NLSG7GGA.js.map → chunk-KJVOBLFC.js.map} +0 -0
  66. /package/dist/{chunk-7POTLB24.js.map → chunk-M2YGXCCQ.js.map} +0 -0
  67. /package/dist/{chunk-PQGTWFRF.js.map → chunk-M3SWH4MU.js.map} +0 -0
  68. /package/dist/{chunk-OMHBVHX7.js.map → chunk-ODKYBA3C.js.map} +0 -0
  69. /package/dist/{chunk-56MYWKQV.js.map → chunk-SKYRM7R7.js.map} +0 -0
@@ -20,9 +20,9 @@ import {
20
20
  reapManagedAuthIsolatedSessions,
21
21
  releaseManagedAuthActorMutationLease,
22
22
  validateManagedAuthActorMutationLease
23
- } from "./chunk-KH3TYDGY.js";
24
- import "./chunk-56MYWKQV.js";
25
- import "./chunk-LDTQGYD2.js";
23
+ } from "./chunk-J4WFOVBV.js";
24
+ import "./chunk-SKYRM7R7.js";
25
+ import "./chunk-VOB6TXSJ.js";
26
26
  import "./chunk-P7YDM2RI.js";
27
27
  import "./chunk-PZ5AY32C.js";
28
28
  export {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  withRlsContext
3
- } from "./chunk-56MYWKQV.js";
4
- import "./chunk-LDTQGYD2.js";
3
+ } from "./chunk-SKYRM7R7.js";
4
+ import "./chunk-VOB6TXSJ.js";
5
5
  import {
6
6
  LOSSLESS_CONTENT_CODEC_VERSION,
7
7
  fromPostgresLosslessJson,
@@ -3,10 +3,10 @@ import {
3
3
  updateOrganizationIntegrationPolicy,
4
4
  withOrganizationIntegrationAcquisition,
5
5
  withOrganizationIntegrationPolicyFence
6
- } from "./chunk-PQGTWFRF.js";
6
+ } from "./chunk-M3SWH4MU.js";
7
7
  import "./chunk-4UJCXBR2.js";
8
- import "./chunk-56MYWKQV.js";
9
- import "./chunk-LDTQGYD2.js";
8
+ import "./chunk-SKYRM7R7.js";
9
+ import "./chunk-VOB6TXSJ.js";
10
10
  import "./chunk-P7YDM2RI.js";
11
11
  import "./chunk-PZ5AY32C.js";
12
12
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  provisionRoles
3
- } from "./chunk-N5QCKHYL.js";
3
+ } from "./chunk-T5NHDEZW.js";
4
4
  import "./chunk-PZ5AY32C.js";
5
5
  export {
6
6
  provisionRoles
@@ -14,13 +14,13 @@ import {
14
14
  retainedProviderCommandPersistence,
15
15
  supervisedCommandProtocolReady,
16
16
  validateRouterOutputAdvance
17
- } from "./chunk-OMHBVHX7.js";
18
- import "./chunk-F3YMVRF4.js";
19
- import "./chunk-7S53WLPI.js";
20
- import "./chunk-MACELCXG.js";
17
+ } from "./chunk-ODKYBA3C.js";
18
+ import "./chunk-GLA5YCQS.js";
19
+ import "./chunk-EKXCF3FH.js";
20
+ import "./chunk-CIGHXFPD.js";
21
21
  import "./chunk-4UJCXBR2.js";
22
- import "./chunk-56MYWKQV.js";
23
- import "./chunk-LDTQGYD2.js";
22
+ import "./chunk-SKYRM7R7.js";
23
+ import "./chunk-VOB6TXSJ.js";
24
24
  import "./chunk-P7YDM2RI.js";
25
25
  import "./chunk-PZ5AY32C.js";
26
26
  export {
@@ -1,5 +1,6 @@
1
1
  import type { Database, RlsStrategy } from "./database.js";
2
2
  export declare const SANDBOX_FILE_PUBLICATION_RUNTIME_ROUTINES: readonly ["record_sandbox_file_publication(uuid, uuid, uuid, uuid)", "list_sandbox_file_publications(uuid, uuid, jsonb)"];
3
+ export declare const ARTIFACT_PIN_RUNTIME_ROUTINES: readonly ["update_artifact_pin(uuid, uuid, text, text, boolean)", "list_artifact_pins(uuid, uuid)", "list_sandbox_file_publications_pinned(uuid, uuid, jsonb)"];
3
4
  export declare const SCHEDULED_SLACK_BOT_MESSAGE_RUNTIME_ROUTINES: readonly ["prepare_scheduled_slack_bot_message(uuid, uuid, uuid, uuid, uuid, integer, text, text, text)", "read_scheduled_slack_bot_message(uuid, uuid, uuid, uuid)"];
4
5
  export declare const ORGANIZATION_SIGNUP_USE_CASE_RUNTIME_ROUTINES: readonly ["record_organization_signup_use_case(uuid, text, text)"];
5
6
  export declare const SLACK_FILE_UPLOAD_OPERATIONS_TABLE = "slack_file_upload_operations";
@@ -65,7 +66,6 @@ export type RuntimeDatabasePostureOptions = {
65
66
  targetSchemaForbiddenRoutines?: readonly string[];
66
67
  /** Frozen binary contract; current callers require both additive Insights capabilities. */
67
68
  modelFactCapabilityRoutines?: readonly string[];
68
- organizationTenancyCanonicalActivationEnabled?: boolean;
69
69
  };
70
70
  export type RuntimeDatabaseIdentity = {
71
71
  currentUser: string;
@@ -130,6 +130,8 @@ export type RuntimePrivateTablePosture = {
130
130
  truncate?: boolean;
131
131
  references?: boolean;
132
132
  trigger?: boolean;
133
+ /** Effective relation ACL privileges not represented by the named flags. */
134
+ extraPrivileges?: string[];
133
135
  };
134
136
  export type RuntimeDatabasePosture = {
135
137
  identity: RuntimeDatabaseIdentity;
@@ -142,7 +144,6 @@ export type RuntimeDatabasePosture = {
142
144
  privateTables: RuntimePrivateTablePosture[];
143
145
  targetRoutines: RuntimeTargetRoutinePosture[];
144
146
  privateRoutines: RuntimeRoutinePosture[];
145
- sessionTenancyProductActivationPresent: boolean;
146
147
  sessionVariableSetAttachmentsCutoverPresent: boolean;
147
148
  claudeSubscriptionPoolActivationPresent: boolean;
148
149
  };
package/dist/schema.d.ts CHANGED
@@ -28905,6 +28905,99 @@ export declare const sandboxFilePublications: import("drizzle-orm/pg-core").PgTa
28905
28905
  };
28906
28906
  dialect: 'pg';
28907
28907
  }>;
28908
+ /** Shared discovery pins; no content or access authority. SQL owns target validation. */
28909
+ export declare const artifactCatalogPins: import("drizzle-orm/pg-core").PgTableWithColumns<{
28910
+ name: "artifact_catalog_pins";
28911
+ schema: "opengeni_private";
28912
+ columns: {
28913
+ accountId: import("drizzle-orm/pg-core").PgColumn<{
28914
+ name: "account_id";
28915
+ tableName: "artifact_catalog_pins";
28916
+ dataType: "string";
28917
+ columnType: "PgUUID";
28918
+ data: string;
28919
+ driverParam: string;
28920
+ notNull: true;
28921
+ hasDefault: false;
28922
+ isPrimaryKey: false;
28923
+ isAutoincrement: false;
28924
+ hasRuntimeDefault: false;
28925
+ enumValues: undefined;
28926
+ baseColumn: never;
28927
+ identity: undefined;
28928
+ generated: undefined;
28929
+ }, {}, {}>;
28930
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
28931
+ name: "workspace_id";
28932
+ tableName: "artifact_catalog_pins";
28933
+ dataType: "string";
28934
+ columnType: "PgUUID";
28935
+ data: string;
28936
+ driverParam: string;
28937
+ notNull: true;
28938
+ hasDefault: false;
28939
+ isPrimaryKey: false;
28940
+ isAutoincrement: false;
28941
+ hasRuntimeDefault: false;
28942
+ enumValues: undefined;
28943
+ baseColumn: never;
28944
+ identity: undefined;
28945
+ generated: undefined;
28946
+ }, {}, {}>;
28947
+ kind: import("drizzle-orm/pg-core").PgColumn<{
28948
+ name: "kind";
28949
+ tableName: "artifact_catalog_pins";
28950
+ dataType: "string";
28951
+ columnType: "PgText";
28952
+ data: string;
28953
+ driverParam: string;
28954
+ notNull: true;
28955
+ hasDefault: false;
28956
+ isPrimaryKey: false;
28957
+ isAutoincrement: false;
28958
+ hasRuntimeDefault: false;
28959
+ enumValues: [string, ...string[]];
28960
+ baseColumn: never;
28961
+ identity: undefined;
28962
+ generated: undefined;
28963
+ }, {}, {}>;
28964
+ artifactId: import("drizzle-orm/pg-core").PgColumn<{
28965
+ name: "artifact_id";
28966
+ tableName: "artifact_catalog_pins";
28967
+ dataType: "string";
28968
+ columnType: "PgText";
28969
+ data: string;
28970
+ driverParam: string;
28971
+ notNull: true;
28972
+ hasDefault: false;
28973
+ isPrimaryKey: false;
28974
+ isAutoincrement: false;
28975
+ hasRuntimeDefault: false;
28976
+ enumValues: [string, ...string[]];
28977
+ baseColumn: never;
28978
+ identity: undefined;
28979
+ generated: undefined;
28980
+ }, {}, {}>;
28981
+ pinnedAt: import("drizzle-orm/pg-core").PgColumn<{
28982
+ name: "pinned_at";
28983
+ tableName: "artifact_catalog_pins";
28984
+ dataType: "date";
28985
+ columnType: "PgTimestamp";
28986
+ data: Date;
28987
+ driverParam: string;
28988
+ notNull: true;
28989
+ hasDefault: true;
28990
+ isPrimaryKey: false;
28991
+ isAutoincrement: false;
28992
+ hasRuntimeDefault: false;
28993
+ enumValues: undefined;
28994
+ baseColumn: never;
28995
+ identity: undefined;
28996
+ generated: undefined;
28997
+ }, {}, {}>;
28998
+ };
28999
+ dialect: 'pg';
29000
+ }>;
28908
29001
  /** Source-session upload fence; never stores file bytes or expiring upload URLs. */
28909
29002
  export declare const slackFileUploadOperations: import("drizzle-orm/pg-core").PgTableWithColumns<{
28910
29003
  name: "slack_file_upload_operations";
package/dist/schema.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  agentRunStates,
6
6
  agentUpdateStatusValues,
7
7
  apiKeys,
8
+ artifactCatalogPins,
8
9
  attachedBrowserDevices,
9
10
  attachedBrowserInventories,
10
11
  auditEvents,
@@ -399,7 +400,7 @@ import {
399
400
  xaiRotationSettings,
400
401
  xaiSessionAccountPins,
401
402
  xaiSubscriptionCredentials
402
- } from "./chunk-LDTQGYD2.js";
403
+ } from "./chunk-VOB6TXSJ.js";
403
404
  import "./chunk-P7YDM2RI.js";
404
405
  import "./chunk-PZ5AY32C.js";
405
406
  export {
@@ -409,6 +410,7 @@ export {
409
410
  agentRunStates,
410
411
  agentUpdateStatusValues,
411
412
  apiKeys,
413
+ artifactCatalogPins,
412
414
  attachedBrowserDevices,
413
415
  attachedBrowserInventories,
414
416
  auditEvents,
@@ -27,11 +27,11 @@ import {
27
27
  settleConnectedMachineSessionBackgroundCommandWithMutation,
28
28
  settleSessionBackgroundCommandForRetainedProcessInTransaction,
29
29
  unobservableRetainedProcessOutcomeSql
30
- } from "./chunk-7S53WLPI.js";
31
- import "./chunk-MACELCXG.js";
30
+ } from "./chunk-EKXCF3FH.js";
31
+ import "./chunk-CIGHXFPD.js";
32
32
  import "./chunk-4UJCXBR2.js";
33
- import "./chunk-56MYWKQV.js";
34
- import "./chunk-LDTQGYD2.js";
33
+ import "./chunk-SKYRM7R7.js";
34
+ import "./chunk-VOB6TXSJ.js";
35
35
  import "./chunk-P7YDM2RI.js";
36
36
  import "./chunk-PZ5AY32C.js";
37
37
  export {
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  appendSessionCommandOutput
3
- } from "./chunk-F3YMVRF4.js";
4
- import "./chunk-7S53WLPI.js";
5
- import "./chunk-MACELCXG.js";
3
+ } from "./chunk-GLA5YCQS.js";
4
+ import "./chunk-EKXCF3FH.js";
5
+ import "./chunk-CIGHXFPD.js";
6
6
  import "./chunk-4UJCXBR2.js";
7
- import "./chunk-56MYWKQV.js";
8
- import "./chunk-LDTQGYD2.js";
7
+ import "./chunk-SKYRM7R7.js";
8
+ import "./chunk-VOB6TXSJ.js";
9
9
  import "./chunk-P7YDM2RI.js";
10
10
  import "./chunk-PZ5AY32C.js";
11
11
  export {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  listSessionEventSlices,
3
3
  readSessionMessageSliceInScope
4
- } from "./chunk-NLSG7GGA.js";
5
- import "./chunk-56MYWKQV.js";
6
- import "./chunk-LDTQGYD2.js";
4
+ } from "./chunk-KJVOBLFC.js";
5
+ import "./chunk-SKYRM7R7.js";
6
+ import "./chunk-VOB6TXSJ.js";
7
7
  import "./chunk-P7YDM2RI.js";
8
8
  import "./chunk-PZ5AY32C.js";
9
9
  export {
@@ -1,23 +1,23 @@
1
1
  import {
2
2
  connectionMetadataMatchesBinding
3
- } from "./chunk-7POTLB24.js";
3
+ } from "./chunk-M2YGXCCQ.js";
4
4
  import {
5
5
  lockSessionEventWriteRows,
6
6
  organizationApiKeyAllowsWorkspace
7
- } from "./chunk-7S53WLPI.js";
8
- import "./chunk-MACELCXG.js";
7
+ } from "./chunk-EKXCF3FH.js";
8
+ import "./chunk-CIGHXFPD.js";
9
9
  import "./chunk-4UJCXBR2.js";
10
10
  import {
11
11
  rawRows,
12
12
  setSubjectRlsContext,
13
13
  withRlsContext
14
- } from "./chunk-56MYWKQV.js";
14
+ } from "./chunk-SKYRM7R7.js";
15
15
  import {
16
16
  apiKeys,
17
17
  sessionCommandReceipts,
18
18
  sessionMcpServers,
19
19
  sessions
20
- } from "./chunk-LDTQGYD2.js";
20
+ } from "./chunk-VOB6TXSJ.js";
21
21
  import "./chunk-P7YDM2RI.js";
22
22
  import "./chunk-PZ5AY32C.js";
23
23
 
@@ -6,6 +6,12 @@ export declare class SessionTenancyConflictError extends Error {
6
6
  readonly name = "SessionTenancyConflictError";
7
7
  constructor(reason: "not_quiescent" | "authority_epoch" | "operation_reuse", blocker?: SessionTenancyBlocker | null, options?: ErrorOptions);
8
8
  }
9
+ /**
10
+ * The organization's owner/admin Only-me setting does not permit a new private
11
+ * session (or private transition) in this shared workspace. Activation itself is
12
+ * universal since migration 0611; the name and the SESSION_TENANCY_NOT_ACTIVATED
13
+ * wire code are kept for client compatibility.
14
+ */
9
15
  export declare class SessionTenancyNotActivatedError extends Error {
10
16
  readonly name = "SessionTenancyNotActivatedError";
11
17
  constructor(options?: ErrorOptions);
@@ -83,7 +89,6 @@ export type ForkSessionContentResult = {
83
89
  runtimeEventId: string | null;
84
90
  runtimeEventSequence: number | null;
85
91
  };
86
- export declare function sessionTenancyProductActivated(db: Database, workspaceId: string): Promise<boolean>;
87
92
  export declare function getPrivateSessionCreatePolicy(db: Database, input: {
88
93
  workspaceId: string;
89
94
  actorSubjectId: string;
@@ -12,11 +12,10 @@ import {
12
12
  openPrivateChildSessionCreateCapability,
13
13
  openPrivateSessionCreateCapability,
14
14
  replayAppliedSessionFork,
15
- sessionTenancyProductActivated,
16
15
  transitionSessionVisibility
17
- } from "./chunk-UEQQ2WWU.js";
18
- import "./chunk-56MYWKQV.js";
19
- import "./chunk-LDTQGYD2.js";
16
+ } from "./chunk-Z5HJXH3X.js";
17
+ import "./chunk-SKYRM7R7.js";
18
+ import "./chunk-VOB6TXSJ.js";
20
19
  import "./chunk-P7YDM2RI.js";
21
20
  import "./chunk-PZ5AY32C.js";
22
21
  export {
@@ -33,7 +32,6 @@ export {
33
32
  openPrivateChildSessionCreateCapability,
34
33
  openPrivateSessionCreateCapability,
35
34
  replayAppliedSessionFork,
36
- sessionTenancyProductActivated,
37
35
  transitionSessionVisibility
38
36
  };
39
37
  //# sourceMappingURL=session-tenancy.js.map
@@ -29,10 +29,10 @@ import {
29
29
  settleVideoGenerationFailure,
30
30
  settleVideoGenerationReady,
31
31
  updateWorkspaceVideoGenerationPolicy
32
- } from "./chunk-MACELCXG.js";
32
+ } from "./chunk-CIGHXFPD.js";
33
33
  import "./chunk-4UJCXBR2.js";
34
- import "./chunk-56MYWKQV.js";
35
- import "./chunk-LDTQGYD2.js";
34
+ import "./chunk-SKYRM7R7.js";
35
+ import "./chunk-VOB6TXSJ.js";
36
36
  import "./chunk-P7YDM2RI.js";
37
37
  import "./chunk-PZ5AY32C.js";
38
38
  export {
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  lockWorkspaceInferenceControl
3
- } from "./chunk-7S53WLPI.js";
4
- import "./chunk-MACELCXG.js";
3
+ } from "./chunk-EKXCF3FH.js";
4
+ import "./chunk-CIGHXFPD.js";
5
5
  import "./chunk-4UJCXBR2.js";
6
6
  import {
7
7
  withWorkspaceRls
8
- } from "./chunk-56MYWKQV.js";
9
- import "./chunk-LDTQGYD2.js";
8
+ } from "./chunk-SKYRM7R7.js";
9
+ import "./chunk-VOB6TXSJ.js";
10
10
  import "./chunk-P7YDM2RI.js";
11
11
  import "./chunk-PZ5AY32C.js";
12
12
 
@@ -0,0 +1,165 @@
1
+ -- deployment-mode: rolling
2
+ -- Shared discovery metadata only. Existing content and authorization domains
3
+ -- remain canonical; older binaries keep their original publication capability.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+
7
+ CREATE TABLE opengeni_private.artifact_catalog_pins (
8
+ account_id uuid NOT NULL,
9
+ workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
10
+ kind text NOT NULL CHECK (kind IN ('site','document','spreadsheet','presentation','image','file')),
11
+ artifact_id text NOT NULL CHECK (length(artifact_id) BETWEEN 1 AND 128),
12
+ pinned_at timestamptz NOT NULL DEFAULT now(),
13
+ PRIMARY KEY (account_id, workspace_id, kind, artifact_id)
14
+ );
15
+ ALTER TABLE opengeni_private.artifact_catalog_pins ENABLE ROW LEVEL SECURITY;
16
+ ALTER TABLE opengeni_private.artifact_catalog_pins FORCE ROW LEVEL SECURITY;
17
+ CREATE POLICY workspace_isolation ON opengeni_private.artifact_catalog_pins
18
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
19
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
20
+
21
+ CREATE FUNCTION opengeni_private.update_artifact_pin(
22
+ p_account uuid, p_workspace uuid, p_kind text, p_id text, p_pinned boolean
23
+ ) RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp AS $body$
24
+ DECLARE v_visible boolean := false;
25
+ BEGIN
26
+ IF p_account IS NULL OR p_workspace IS NULL OR p_kind IS NULL OR p_id IS NULL OR p_pinned IS NULL
27
+ OR p_account IS DISTINCT FROM opengeni_private.current_account_id()
28
+ OR p_workspace IS DISTINCT FROM opengeni_private.current_workspace_id()
29
+ OR p_kind NOT IN ('site','document','spreadsheet','presentation','image','file')
30
+ OR length(p_id) NOT BETWEEN 1 AND 128
31
+ THEN RAISE EXCEPTION 'artifact pin scope mismatch' USING ERRCODE = '42501'; END IF;
32
+ -- Serialize opposite writes even before the first row exists.
33
+ PERFORM pg_advisory_xact_lock(hashtextextended('artifact-pin:' || p_account || ':' || p_workspace || ':' || p_kind || ':' || p_id, 0));
34
+ IF p_kind = 'site' THEN
35
+ SELECT EXISTS (SELECT 1 FROM workspace_artifacts a WHERE a.account_id=p_account
36
+ AND a.workspace_id=p_workspace AND a.id::text=p_id) INTO v_visible;
37
+ ELSIF p_kind IN ('document','spreadsheet','presentation') THEN
38
+ SELECT EXISTS (SELECT 1 FROM editable_artifacts a WHERE a.account_id=p_account
39
+ AND a.workspace_id=p_workspace AND a.id=p_id AND a.modality=p_kind) INTO v_visible;
40
+ ELSE
41
+ SELECT EXISTS (SELECT 1 FROM files f WHERE f.account_id=p_account AND f.workspace_id=p_workspace
42
+ AND f.id::text=p_id AND f.status='ready'
43
+ AND (f.private_owner_subject_ids IS NULL OR nullif(current_setting('opengeni.private_file_owner',true),'')=ANY(f.private_owner_subject_ids))
44
+ AND ((p_kind='image' AND EXISTS (SELECT 1 FROM generated_image_artifacts g
45
+ WHERE g.account_id=p_account AND g.workspace_id=p_workspace AND g.artifact_id=f.id AND g.status='ready'))
46
+ OR (EXISTS (SELECT 1 FROM opengeni_private.sandbox_file_publications p
47
+ WHERE p.account_id=p_account AND p.workspace_id=p_workspace AND p.file_id=f.id)
48
+ AND p_kind=CASE WHEN f.content_type IN ('image/png','image/jpeg','image/gif','image/webp','image/avif','image/svg+xml')
49
+ THEN 'image' ELSE 'file' END))) INTO v_visible;
50
+ END IF;
51
+ -- Also validate unpin: no probe or write for missing, foreign or hidden targets.
52
+ IF NOT v_visible THEN RAISE EXCEPTION 'artifact pin target unavailable' USING ERRCODE = '42501'; END IF;
53
+ IF p_pinned THEN
54
+ INSERT INTO opengeni_private.artifact_catalog_pins(account_id,workspace_id,kind,artifact_id)
55
+ VALUES(p_account,p_workspace,p_kind,p_id) ON CONFLICT DO NOTHING;
56
+ ELSE
57
+ DELETE FROM opengeni_private.artifact_catalog_pins p WHERE p.account_id=p_account
58
+ AND p.workspace_id=p_workspace AND p.kind=p_kind AND p.artifact_id=p_id;
59
+ END IF;
60
+ END $body$;
61
+
62
+ CREATE FUNCTION opengeni_private.list_artifact_pins(p_account uuid, p_workspace uuid)
63
+ RETURNS TABLE(kind text, artifact_id text)
64
+ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = pg_catalog, pg_temp AS $body$
65
+ BEGIN
66
+ IF p_account IS NULL OR p_workspace IS NULL
67
+ OR p_account IS DISTINCT FROM opengeni_private.current_account_id()
68
+ OR p_workspace IS DISTINCT FROM opengeni_private.current_workspace_id()
69
+ THEN RAISE EXCEPTION 'artifact pin scope mismatch' USING ERRCODE = '42501'; END IF;
70
+ -- Only joined to independently authorized candidates. Private files remain
71
+ -- hidden even when the migration owner is a superuser rather than FORCE-RLS-bound.
72
+ RETURN QUERY SELECT p.kind,p.artifact_id FROM opengeni_private.artifact_catalog_pins p
73
+ WHERE p.account_id=p_account AND p.workspace_id=p_workspace
74
+ AND (p.kind NOT IN ('file','image') OR EXISTS (SELECT 1 FROM files f
75
+ WHERE f.account_id=p_account AND f.workspace_id=p_workspace AND f.id::text=p.artifact_id AND f.status='ready'
76
+ AND (f.private_owner_subject_ids IS NULL OR nullif(current_setting('opengeni.private_file_owner',true),'')=ANY(f.private_owner_subject_ids))));
77
+ END $body$;
78
+
79
+ -- A page-local re-sort is incorrect: this bounded branch must apply the same
80
+ -- leading pin key and frontier as the complete union before taking its LIMIT.
81
+ CREATE FUNCTION opengeni_private.list_sandbox_file_publications_pinned(
82
+ p_account uuid, p_workspace uuid, p_query jsonb
83
+ ) RETURNS TABLE(file_id uuid, title text, kind text, source_session_id uuid, published_at timestamptz, pinned boolean)
84
+ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = pg_catalog, pg_temp AS $body$
85
+ DECLARE
86
+ v_limit integer := (p_query->>'limit')::integer;
87
+ v_sort text := p_query->>'sort';
88
+ v_after text := p_query#>>'{after,key}';
89
+ v_after_pinned boolean := coalesce((p_query#>>'{after,pinned}')::boolean,false);
90
+ BEGIN
91
+ IF p_account IS NULL OR p_workspace IS NULL
92
+ OR p_account IS DISTINCT FROM opengeni_private.current_account_id()
93
+ OR p_workspace IS DISTINCT FROM opengeni_private.current_workspace_id()
94
+ OR v_limit IS NULL OR v_limit NOT BETWEEN 1 AND 201
95
+ OR v_sort IS NULL OR v_sort NOT IN ('updated','newest','title')
96
+ OR jsonb_typeof(p_query) IS DISTINCT FROM 'object'
97
+ OR jsonb_typeof(p_query->'kinds') IS DISTINCT FROM 'array'
98
+ OR jsonb_array_length(p_query->'kinds') NOT BETWEEN 1 AND 2
99
+ OR length(coalesce(p_query->>'q',''))>200
100
+ THEN RAISE EXCEPTION 'publication list scope mismatch' USING ERRCODE = '42501'; END IF;
101
+ RETURN QUERY WITH candidates AS (
102
+ SELECT p.file_id, f.filename AS title,
103
+ CASE WHEN f.content_type IN ('image/png','image/jpeg','image/gif','image/webp','image/avif','image/svg+xml')
104
+ THEN 'image' ELSE 'file' END AS kind,
105
+ p.source_session_id, p.published_at,
106
+ CASE WHEN v_sort='title' THEN lower(f.filename)
107
+ ELSE to_char(p.published_at AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS.US') END AS sort_key
108
+ FROM opengeni_private.sandbox_file_publications p JOIN files f
109
+ ON f.account_id=p.account_id AND f.workspace_id=p.workspace_id AND f.id=p.file_id
110
+ WHERE p.account_id=p_account AND p.workspace_id=p_workspace AND f.status='ready'
111
+ AND (f.private_owner_subject_ids IS NULL OR nullif(current_setting('opengeni.private_file_owner',true),'')=ANY(f.private_owner_subject_ids))
112
+ AND (p_query->>'sourceSessionId' IS NULL OR p.source_session_id=(p_query->>'sourceSessionId')::uuid)
113
+ AND p.published_at<=(p_query->>'snapshotAt')::timestamptz
114
+ AND strpos(lower(f.filename),lower(coalesce(p_query->>'q','')))>0
115
+ ), ordered AS (
116
+ SELECT c.*, (pins.artifact_id IS NOT NULL) AS pinned FROM candidates c
117
+ LEFT JOIN opengeni_private.list_artifact_pins(p_account,p_workspace) pins
118
+ ON pins.kind=c.kind AND pins.artifact_id=c.file_id::text
119
+ ) SELECT c.file_id,c.title,c.kind,c.source_session_id,c.published_at,c.pinned FROM ordered c
120
+ WHERE (p_query->'kinds') ? c.kind AND (v_after IS NULL OR c.pinned<v_after_pinned OR
121
+ (c.pinned=v_after_pinned AND (
122
+ (v_sort='title' AND c.sort_key COLLATE "C">v_after COLLATE "C") OR
123
+ (v_sort<>'title' AND c.sort_key COLLATE "C"<v_after COLLATE "C") OR
124
+ (c.sort_key=v_after AND (c.kind COLLATE "C",c.file_id::text COLLATE "C")>
125
+ ((p_query#>>'{after,kind}') COLLATE "C",(p_query#>>'{after,id}') COLLATE "C")))))
126
+ ORDER BY c.pinned DESC, CASE WHEN v_sort='title' THEN c.sort_key END COLLATE "C" ASC,
127
+ CASE WHEN v_sort<>'title' THEN c.sort_key END COLLATE "C" DESC,c.kind COLLATE "C",c.file_id::text COLLATE "C"
128
+ LIMIT v_limit;
129
+ END $body$;
130
+ REVOKE ALL ON TABLE opengeni_private.artifact_catalog_pins FROM PUBLIC;
131
+ REVOKE ALL ON FUNCTION opengeni_private.update_artifact_pin(uuid,uuid,text,text,boolean),
132
+ opengeni_private.list_artifact_pins(uuid,uuid),
133
+ opengeni_private.list_sandbox_file_publications_pinned(uuid,uuid,jsonb) FROM PUBLIC;
134
+
135
+ DO $grants$
136
+ DECLARE target_schema text := current_schema(); recipient record; routine text;
137
+ BEGIN
138
+ FOREACH routine IN ARRAY ARRAY['update_artifact_pin(uuid,uuid,text,text,boolean)',
139
+ 'list_artifact_pins(uuid,uuid)','list_sandbox_file_publications_pinned(uuid,uuid,jsonb)']
140
+ LOOP
141
+ EXECUTE format('ALTER FUNCTION opengeni_private.%s SET search_path = pg_catalog, %I, pg_temp', routine, target_schema);
142
+ -- Strip inherited default function ACLs, then grant only existing runtimes.
143
+ FOR recipient IN SELECT DISTINCT acl.grantee,r.rolname FROM pg_proc p
144
+ CROSS JOIN LATERAL aclexplode(p.proacl) acl LEFT JOIN pg_roles r ON r.oid=acl.grantee
145
+ WHERE p.oid=to_regprocedure('opengeni_private.' || routine) AND acl.grantee<>p.proowner
146
+ LOOP
147
+ EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.%s FROM %s', routine,
148
+ CASE WHEN recipient.grantee=0 THEN 'PUBLIC' ELSE quote_ident(recipient.rolname) END);
149
+ END LOOP;
150
+ END LOOP;
151
+ FOR recipient IN SELECT DISTINCT acl.grantee,r.rolname FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
152
+ CROSS JOIN LATERAL aclexplode(c.relacl) acl LEFT JOIN pg_roles r ON r.oid=acl.grantee
153
+ WHERE n.nspname='opengeni_private' AND c.relname='artifact_catalog_pins' AND acl.grantee<>c.relowner
154
+ LOOP
155
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.artifact_catalog_pins FROM %s',
156
+ CASE WHEN recipient.grantee=0 THEN 'PUBLIC' ELSE quote_ident(recipient.rolname) END);
157
+ END LOOP;
158
+ FOR recipient IN SELECT DISTINCT r.rolname FROM pg_proc p
159
+ CROSS JOIN LATERAL aclexplode(p.proacl) acl JOIN pg_roles r ON r.oid=acl.grantee
160
+ WHERE p.oid=to_regprocedure('opengeni_private.record_sandbox_file_publication(uuid,uuid,uuid,uuid)')
161
+ AND acl.privilege_type='EXECUTE' AND acl.grantee<>p.proowner
162
+ LOOP
163
+ EXECUTE format('GRANT EXECUTE ON FUNCTION opengeni_private.update_artifact_pin(uuid,uuid,text,text,boolean), opengeni_private.list_artifact_pins(uuid,uuid), opengeni_private.list_sandbox_file_publications_pinned(uuid,uuid,jsonb) TO %I', recipient.rolname);
164
+ END LOOP;
165
+ END $grants$;