@agent-native/creative-context 0.3.0 → 0.5.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 (185) hide show
  1. package/README.md +51 -9
  2. package/agent-native.package.json +4 -0
  3. package/dist/actions/index.d.ts.map +1 -1
  4. package/dist/actions/index.js +8 -0
  5. package/dist/actions/index.js.map +1 -1
  6. package/dist/actions/list-context-memberships.d.ts +11 -0
  7. package/dist/actions/list-context-memberships.d.ts.map +1 -0
  8. package/dist/actions/list-context-memberships.js +47 -0
  9. package/dist/actions/list-context-memberships.js.map +1 -0
  10. package/dist/actions/list-creative-contexts.d.ts +12 -0
  11. package/dist/actions/list-creative-contexts.d.ts.map +1 -0
  12. package/dist/actions/list-creative-contexts.js +28 -0
  13. package/dist/actions/list-creative-contexts.js.map +1 -0
  14. package/dist/actions/manage-context-membership.d.ts +64 -0
  15. package/dist/actions/manage-context-membership.d.ts.map +1 -0
  16. package/dist/actions/manage-context-membership.js +70 -0
  17. package/dist/actions/manage-context-membership.js.map +1 -0
  18. package/dist/actions/manage-context-pack.d.ts +3 -0
  19. package/dist/actions/manage-context-pack.js +6 -0
  20. package/dist/actions/manage-context-pack.js.map +1 -1
  21. package/dist/actions/manage-context-source.js +4 -11
  22. package/dist/actions/manage-context-source.js.map +1 -1
  23. package/dist/actions/manage-creative-context.d.ts +28 -0
  24. package/dist/actions/manage-creative-context.d.ts.map +1 -0
  25. package/dist/actions/manage-creative-context.js +65 -0
  26. package/dist/actions/manage-creative-context.js.map +1 -0
  27. package/dist/actions/process-context-purge.d.ts +16 -1
  28. package/dist/actions/process-context-purge.js +2 -1
  29. package/dist/actions/process-context-purge.js.map +1 -1
  30. package/dist/actions/search-creative-context.d.ts +1 -0
  31. package/dist/actions/search-creative-context.js +2 -1
  32. package/dist/actions/search-creative-context.js.map +1 -1
  33. package/dist/client/CreativeContextChip.d.ts +5 -1
  34. package/dist/client/CreativeContextChip.d.ts.map +1 -1
  35. package/dist/client/CreativeContextChip.js +36 -7
  36. package/dist/client/CreativeContextChip.js.map +1 -1
  37. package/dist/client/CreativeContextPanel.d.ts.map +1 -1
  38. package/dist/client/CreativeContextPanel.js +406 -89
  39. package/dist/client/CreativeContextPanel.js.map +1 -1
  40. package/dist/client/CreativeContextShareTab.d.ts +56 -0
  41. package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
  42. package/dist/client/CreativeContextShareTab.js +213 -0
  43. package/dist/client/CreativeContextShareTab.js.map +1 -0
  44. package/dist/client/actions.d.ts +210 -0
  45. package/dist/client/actions.d.ts.map +1 -1
  46. package/dist/client/actions.js +318 -0
  47. package/dist/client/actions.js.map +1 -1
  48. package/dist/client/application-state.d.ts +2 -0
  49. package/dist/client/application-state.d.ts.map +1 -1
  50. package/dist/client/application-state.js +25 -2
  51. package/dist/client/application-state.js.map +1 -1
  52. package/dist/client/index.d.ts +4 -3
  53. package/dist/client/index.d.ts.map +1 -1
  54. package/dist/client/index.js +4 -3
  55. package/dist/client/index.js.map +1 -1
  56. package/dist/client/messages.d.ts +1 -1
  57. package/dist/client/messages.d.ts.map +1 -1
  58. package/dist/client/messages.js +77 -0
  59. package/dist/client/messages.js.map +1 -1
  60. package/dist/connectors/types.d.ts +2 -2
  61. package/dist/connectors/types.d.ts.map +1 -1
  62. package/dist/schema/index.d.ts +1592 -0
  63. package/dist/schema/index.d.ts.map +1 -1
  64. package/dist/schema/index.js +95 -0
  65. package/dist/schema/index.js.map +1 -1
  66. package/dist/schema/migrations.d.ts.map +1 -1
  67. package/dist/schema/migrations.js +80 -0
  68. package/dist/schema/migrations.js.map +1 -1
  69. package/dist/server/generation-artifact-access.js +2 -0
  70. package/dist/server/generation-artifact-access.js.map +1 -1
  71. package/dist/server/generation-context.d.ts +12 -4
  72. package/dist/server/generation-context.d.ts.map +1 -1
  73. package/dist/server/generation-context.js +129 -7
  74. package/dist/server/generation-context.js.map +1 -1
  75. package/dist/server/index.d.ts +4 -0
  76. package/dist/server/index.d.ts.map +1 -1
  77. package/dist/server/index.js +16 -0
  78. package/dist/server/index.js.map +1 -1
  79. package/dist/server/isolated-a2a.d.ts +3 -0
  80. package/dist/server/isolated-a2a.d.ts.map +1 -1
  81. package/dist/server/isolated-a2a.js +3 -1
  82. package/dist/server/isolated-a2a.js.map +1 -1
  83. package/dist/server/media.d.ts +1 -1
  84. package/dist/server/media.d.ts.map +1 -1
  85. package/dist/server/media.js +24 -9
  86. package/dist/server/media.js.map +1 -1
  87. package/dist/server/native-resource-capture.d.ts +78 -0
  88. package/dist/server/native-resource-capture.d.ts.map +1 -0
  89. package/dist/server/native-resource-capture.js +94 -0
  90. package/dist/server/native-resource-capture.js.map +1 -0
  91. package/dist/server/public-serialization.js +1 -1
  92. package/dist/server/public-serialization.js.map +1 -1
  93. package/dist/server/retrieval.d.ts +1 -0
  94. package/dist/server/retrieval.d.ts.map +1 -1
  95. package/dist/server/retrieval.js +2 -0
  96. package/dist/server/retrieval.js.map +1 -1
  97. package/dist/server/safe-native-preview.d.ts +15 -0
  98. package/dist/server/safe-native-preview.d.ts.map +1 -0
  99. package/dist/server/safe-native-preview.js +149 -0
  100. package/dist/server/safe-native-preview.js.map +1 -0
  101. package/dist/store/brand.d.ts.map +1 -1
  102. package/dist/store/brand.js +2 -16
  103. package/dist/store/brand.js.map +1 -1
  104. package/dist/store/content.d.ts +1 -0
  105. package/dist/store/content.d.ts.map +1 -1
  106. package/dist/store/content.js +84 -20
  107. package/dist/store/content.js.map +1 -1
  108. package/dist/store/contexts.d.ts +216 -0
  109. package/dist/store/contexts.d.ts.map +1 -0
  110. package/dist/store/contexts.js +1341 -0
  111. package/dist/store/contexts.js.map +1 -0
  112. package/dist/store/index.d.ts +1 -0
  113. package/dist/store/index.d.ts.map +1 -1
  114. package/dist/store/index.js +1 -0
  115. package/dist/store/index.js.map +1 -1
  116. package/dist/store/packs.d.ts +3 -0
  117. package/dist/store/packs.d.ts.map +1 -1
  118. package/dist/store/packs.js +126 -9
  119. package/dist/store/packs.js.map +1 -1
  120. package/dist/store/purge.d.ts.map +1 -1
  121. package/dist/store/purge.js +103 -6
  122. package/dist/store/purge.js.map +1 -1
  123. package/dist/store/sources.d.ts.map +1 -1
  124. package/dist/store/sources.js +2 -15
  125. package/dist/store/sources.js.map +1 -1
  126. package/dist/store/suggestions.d.ts.map +1 -1
  127. package/dist/store/suggestions.js +3 -1
  128. package/dist/store/suggestions.js.map +1 -1
  129. package/dist/types.d.ts +77 -0
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +9 -1
  132. package/dist/types.js.map +1 -1
  133. package/docs/skills/creative-context/SKILL.md +13 -5
  134. package/package.json +3 -3
  135. package/src/actions/index.ts +8 -0
  136. package/src/actions/job-public-serialization.spec.ts +21 -0
  137. package/src/actions/list-context-memberships.ts +56 -0
  138. package/src/actions/list-creative-contexts.spec.ts +38 -0
  139. package/src/actions/list-creative-contexts.ts +32 -0
  140. package/src/actions/manage-context-membership.ts +80 -0
  141. package/src/actions/manage-context-pack.ts +6 -0
  142. package/src/actions/manage-context-source.ts +8 -11
  143. package/src/actions/manage-creative-context.ts +73 -0
  144. package/src/actions/process-context-purge.ts +4 -1
  145. package/src/actions/search-creative-context.ts +2 -1
  146. package/src/client/CreativeContextChip.spec.ts +32 -0
  147. package/src/client/CreativeContextChip.tsx +83 -14
  148. package/src/client/CreativeContextPanel.tsx +1947 -885
  149. package/src/client/CreativeContextShareTab.spec.ts +70 -0
  150. package/src/client/CreativeContextShareTab.tsx +657 -0
  151. package/src/client/actions.spec.ts +177 -0
  152. package/src/client/actions.ts +566 -0
  153. package/src/client/application-state.spec.ts +5 -0
  154. package/src/client/application-state.ts +30 -2
  155. package/src/client/index.ts +33 -0
  156. package/src/client/messages.ts +95 -0
  157. package/src/connectors/types.ts +6 -8
  158. package/src/eval/library-flow.acceptance.spec.tsx +1 -0
  159. package/src/schema/index.ts +116 -0
  160. package/src/schema/migrations.ts +80 -0
  161. package/src/server/generation-artifact-access.ts +1 -0
  162. package/src/server/generation-context-a2a.spec.ts +133 -0
  163. package/src/server/generation-context.ts +153 -7
  164. package/src/server/index.spec.ts +15 -0
  165. package/src/server/index.ts +16 -0
  166. package/src/server/isolated-a2a.ts +4 -1
  167. package/src/server/media.spec.ts +30 -2
  168. package/src/server/media.ts +31 -12
  169. package/src/server/native-resource-capture.spec.ts +121 -0
  170. package/src/server/native-resource-capture.ts +198 -0
  171. package/src/server/public-serialization.spec.ts +1 -0
  172. package/src/server/public-serialization.ts +1 -1
  173. package/src/server/retrieval.ts +3 -0
  174. package/src/server/safe-native-preview.spec.ts +29 -0
  175. package/src/server/safe-native-preview.ts +200 -0
  176. package/src/store/access.integration.spec.ts +1116 -0
  177. package/src/store/brand.ts +2 -21
  178. package/src/store/content.ts +217 -17
  179. package/src/store/contexts.ts +1826 -0
  180. package/src/store/index.ts +1 -0
  181. package/src/store/packs.ts +279 -24
  182. package/src/store/purge.ts +139 -6
  183. package/src/store/sources.ts +2 -18
  184. package/src/store/suggestions.ts +3 -1
  185. package/src/types.ts +95 -0
@@ -1,5 +1,6 @@
1
1
  export * from "./brand.js";
2
2
  export * from "./content.js";
3
+ export * from "./contexts.js";
3
4
  export * from "./generation.js";
4
5
  export * from "./inference.js";
5
6
  export * from "./jobs.js";
@@ -3,9 +3,20 @@ import {
3
3
  assertAccess,
4
4
  resolveAccess,
5
5
  } from "@agent-native/core/sharing";
6
- import { and, asc, eq, gt, inArray, isNull, ne } from "drizzle-orm";
6
+ import {
7
+ and,
8
+ asc,
9
+ eq,
10
+ gt,
11
+ inArray,
12
+ isNotNull,
13
+ isNull,
14
+ ne,
15
+ or,
16
+ } from "drizzle-orm";
7
17
 
8
18
  import { getCreativeContext } from "../server/context.js";
19
+ import { sanitizePublicMetadata } from "../server/public-serialization.js";
9
20
  import type {
10
21
  ContextPackDetail,
11
22
  ContextPackMember,
@@ -20,6 +31,16 @@ import {
20
31
  stringifyJson,
21
32
  } from "./helpers.js";
22
33
 
34
+ type ResolvedPackMember = {
35
+ itemId: string;
36
+ itemVersionId: string;
37
+ reason?: string;
38
+ score?: number;
39
+ scoreMetadata?: Record<string, unknown>;
40
+ };
41
+
42
+ const MAX_NATIVE_PACK_DEPENDENCIES = 256;
43
+
23
44
  export function assertImmutablePackMembership(operation: string): never {
24
45
  throw new Error(
25
46
  `Context-pack membership is append-only; ${operation} must derive a new pack snapshot`,
@@ -48,15 +69,9 @@ async function resolveBrandDnaVersion(
48
69
  return dnaVersionId;
49
70
  }
50
71
 
51
- async function resolveMembers(members: ContextPackMemberInput[]): Promise<
52
- Array<{
53
- itemId: string;
54
- itemVersionId: string;
55
- reason?: string;
56
- score?: number;
57
- scoreMetadata?: Record<string, unknown>;
58
- }>
59
- > {
72
+ async function resolveMembers(
73
+ members: ContextPackMemberInput[],
74
+ ): Promise<ResolvedPackMember[]> {
60
75
  const { getDb, schema } = getCreativeContext();
61
76
  const itemIds = members.map((member) => member.itemId);
62
77
  if (new Set(itemIds).size !== itemIds.length) {
@@ -73,12 +88,34 @@ async function resolveMembers(members: ContextPackMemberInput[]): Promise<
73
88
  schema.contextSources,
74
89
  eq(schema.contextSources.id, schema.contextItems.sourceId),
75
90
  )
91
+ .leftJoin(
92
+ schema.creativeContextMemberships,
93
+ and(
94
+ eq(
95
+ schema.creativeContextMemberships.publishedItemId,
96
+ schema.contextItems.id,
97
+ ),
98
+ eq(schema.creativeContextMemberships.status, "active"),
99
+ ),
100
+ )
101
+ .leftJoin(
102
+ schema.creativeContexts,
103
+ eq(
104
+ schema.creativeContexts.id,
105
+ schema.creativeContextMemberships.contextId,
106
+ ),
107
+ )
76
108
  .where(
77
109
  and(
78
110
  inArray(schema.contextItems.id, itemIds),
79
- accessFilter(schema.contextSources, schema.contextSourceShares),
80
- ne(schema.contextSources.upstreamAccess, "restricted"),
81
- ne(schema.contextSources.status, "archived"),
111
+ or(
112
+ and(
113
+ accessFilter(schema.contextSources, schema.contextSourceShares),
114
+ ne(schema.contextSources.upstreamAccess, "restricted"),
115
+ ne(schema.contextSources.status, "archived"),
116
+ ),
117
+ accessFilter(schema.creativeContexts, schema.creativeContextShares),
118
+ ),
82
119
  eq(schema.contextItems.curationStatus, "included"),
83
120
  ne(schema.contextItems.curationRank, "ignored"),
84
121
  eq(schema.contextItems.status, "active"),
@@ -130,12 +167,140 @@ async function resolveMembers(members: ContextPackMemberInput[]): Promise<
130
167
  });
131
168
  }
132
169
 
170
+ async function includeNativeArtifactDependencies(
171
+ explicitMembers: ResolvedPackMember[],
172
+ ): Promise<ResolvedPackMember[]> {
173
+ if (!explicitMembers.length) return explicitMembers;
174
+ const { getDb, schema } = getCreativeContext();
175
+ const members = [...explicitMembers];
176
+ const byItemId = new Map(members.map((member) => [member.itemId, member]));
177
+ const sourceByVersion = new Map<string, string>();
178
+ const initialItems = await getDb()
179
+ .select({
180
+ itemId: schema.contextItems.id,
181
+ itemVersionId: schema.contextItemVersions.id,
182
+ sourceId: schema.contextItems.sourceId,
183
+ })
184
+ .from(schema.contextItemVersions)
185
+ .innerJoin(
186
+ schema.contextItems,
187
+ eq(schema.contextItems.id, schema.contextItemVersions.itemId),
188
+ )
189
+ .where(
190
+ inArray(
191
+ schema.contextItemVersions.id,
192
+ explicitMembers.map((member) => member.itemVersionId),
193
+ ),
194
+ );
195
+ for (const item of initialItems as Array<{
196
+ itemId: string;
197
+ itemVersionId: string;
198
+ sourceId: string;
199
+ }>) {
200
+ sourceByVersion.set(item.itemVersionId, item.sourceId);
201
+ }
202
+
203
+ let frontier = explicitMembers.map((member) => member.itemVersionId);
204
+ let dependencyCount = 0;
205
+ while (frontier.length) {
206
+ const edges = await getDb()
207
+ .select({
208
+ fromItemVersionId: schema.contextEdges.fromItemVersionId,
209
+ toItemId: schema.contextEdges.toItemId,
210
+ toItemVersionId: schema.contextEdges.toItemVersionId,
211
+ toExternalId: schema.contextEdges.toExternalId,
212
+ childSourceId: schema.contextItems.sourceId,
213
+ childVersionId: schema.contextItemVersions.id,
214
+ })
215
+ .from(schema.contextEdges)
216
+ .leftJoin(
217
+ schema.contextItems,
218
+ eq(schema.contextItems.id, schema.contextEdges.toItemId),
219
+ )
220
+ .leftJoin(
221
+ schema.contextItemVersions,
222
+ and(
223
+ eq(
224
+ schema.contextItemVersions.id,
225
+ schema.contextEdges.toItemVersionId,
226
+ ),
227
+ eq(schema.contextItemVersions.itemId, schema.contextEdges.toItemId),
228
+ ),
229
+ )
230
+ .where(
231
+ and(
232
+ inArray(schema.contextEdges.fromItemVersionId, frontier),
233
+ eq(schema.contextEdges.relation, "contains-native-child"),
234
+ ),
235
+ )
236
+ .orderBy(
237
+ asc(schema.contextEdges.fromItemVersionId),
238
+ asc(schema.contextEdges.toExternalId),
239
+ );
240
+ const next: string[] = [];
241
+ for (const edge of edges as Array<{
242
+ fromItemVersionId: string;
243
+ toItemId: string | null;
244
+ toItemVersionId: string | null;
245
+ toExternalId: string | null;
246
+ childSourceId: string | null;
247
+ childVersionId: string | null;
248
+ }>) {
249
+ if (
250
+ !edge.toItemId ||
251
+ !edge.toItemVersionId ||
252
+ !edge.childSourceId ||
253
+ !edge.childVersionId
254
+ ) {
255
+ throw new Error(
256
+ `Native artifact child ${edge.toExternalId ?? "(unknown)"} has no immutable version reference`,
257
+ );
258
+ }
259
+ const parentSourceId = sourceByVersion.get(edge.fromItemVersionId);
260
+ if (!parentSourceId || parentSourceId !== edge.childSourceId) {
261
+ throw new Error(
262
+ "Native artifact dependencies must stay within the pinned source snapshot",
263
+ );
264
+ }
265
+ const existing = byItemId.get(edge.toItemId);
266
+ if (existing) {
267
+ if (existing.itemVersionId !== edge.toItemVersionId) {
268
+ throw new Error(
269
+ "A native artifact dependency conflicts with an explicitly pinned item version",
270
+ );
271
+ }
272
+ continue;
273
+ }
274
+ dependencyCount += 1;
275
+ if (dependencyCount > MAX_NATIVE_PACK_DEPENDENCIES) {
276
+ throw new Error(
277
+ "Native artifact pack dependencies exceed the safe limit",
278
+ );
279
+ }
280
+ const dependency: ResolvedPackMember = {
281
+ itemId: edge.toItemId,
282
+ itemVersionId: edge.toItemVersionId,
283
+ reason: "Native artifact dependency",
284
+ };
285
+ members.push(dependency);
286
+ byItemId.set(dependency.itemId, dependency);
287
+ sourceByVersion.set(dependency.itemVersionId, edge.childSourceId);
288
+ next.push(dependency.itemVersionId);
289
+ }
290
+ frontier = [...new Set(next)];
291
+ }
292
+ return members;
293
+ }
294
+
133
295
  export async function createContextPack(input: {
134
296
  name: string;
135
297
  description?: string | null;
136
298
  derivedFromPackId?: string;
137
299
  brandDnaVersionId?: string | null;
138
300
  contextMode?: string;
301
+ baseContextId?: string | null;
302
+ specialtyContextId?: string | null;
303
+ selectionReason?: string | null;
139
304
  request?: Record<string, unknown>;
140
305
  members: ContextPackMemberInput[];
141
306
  pinned?: boolean;
@@ -144,10 +309,12 @@ export async function createContextPack(input: {
144
309
  const actor = requireActor();
145
310
  const timestamp = nowIso();
146
311
  const id = newId("ccp");
147
- const [resolvedMembers, brandDnaVersionId] = await Promise.all([
312
+ const [explicitMembers, brandDnaVersionId] = await Promise.all([
148
313
  resolveMembers(input.members),
149
314
  resolveBrandDnaVersion(input.brandDnaVersionId),
150
315
  ]);
316
+ const resolvedMembers =
317
+ await includeNativeArtifactDependencies(explicitMembers);
151
318
  if (input.derivedFromPackId) {
152
319
  await assertAccess(
153
320
  "creative-context-pack",
@@ -157,6 +324,18 @@ export async function createContextPack(input: {
157
324
  { skipResourceBody: true },
158
325
  );
159
326
  }
327
+ const contextAccess = input.baseContextId
328
+ ? await resolveAccess("creative-context", input.baseContextId)
329
+ : null;
330
+ if (input.baseContextId && !contextAccess) {
331
+ throw new Error("Base Creative Context not found or not accessible");
332
+ }
333
+ const inheritedShares = input.baseContextId
334
+ ? await getDb()
335
+ .select()
336
+ .from(schema.creativeContextShares)
337
+ .where(eq(schema.creativeContextShares.resourceId, input.baseContextId))
338
+ : [];
160
339
  await getDb().transaction(async (tx: any) => {
161
340
  await tx.insert(schema.contextPacks).values({
162
341
  id,
@@ -165,11 +344,14 @@ export async function createContextPack(input: {
165
344
  derivedFromPackId: input.derivedFromPackId ?? null,
166
345
  brandDnaVersionId,
167
346
  contextMode: input.contextMode ?? "manual",
347
+ baseContextId: input.baseContextId ?? null,
348
+ specialtyContextId: input.specialtyContextId ?? null,
349
+ selectionReason: input.selectionReason ?? null,
168
350
  request: stringifyJson(input.request),
169
351
  archivedAt: null,
170
352
  ownerEmail: actor.ownerEmail,
171
353
  orgId: actor.orgId,
172
- visibility: "private",
354
+ visibility: contextAccess?.resource.visibility ?? "private",
173
355
  createdAt: timestamp,
174
356
  });
175
357
  if (resolvedMembers.length) {
@@ -189,6 +371,19 @@ export async function createContextPack(input: {
189
371
  })),
190
372
  );
191
373
  }
374
+ if (inheritedShares.length) {
375
+ await tx.insert(schema.contextPackShares).values(
376
+ (inheritedShares as any[]).map((share) => ({
377
+ id: newId("ccpsh"),
378
+ resourceId: id,
379
+ principalType: share.principalType,
380
+ principalId: share.principalId,
381
+ role: share.role,
382
+ createdBy: actor.ownerEmail,
383
+ createdAt: timestamp,
384
+ })),
385
+ );
386
+ }
192
387
  if (input.pinned) {
193
388
  await tx.insert(schema.contextPackPins).values({
194
389
  id: newId("ccpp"),
@@ -241,6 +436,9 @@ export async function deriveContextPack(input: {
241
436
  ? base.brandDnaVersionId
242
437
  : input.brandDnaVersionId,
243
438
  contextMode: base.contextMode,
439
+ baseContextId: base.baseContextId,
440
+ specialtyContextId: base.specialtyContextId,
441
+ selectionReason: base.selectionReason,
244
442
  request: base.request,
245
443
  members: Array.from(members.values()),
246
444
  pinned: input.pinned,
@@ -338,12 +536,37 @@ export async function listContextPacks(input: {
338
536
  schema.contextSources,
339
537
  eq(schema.contextSources.id, schema.contextItems.sourceId),
340
538
  )
539
+ .leftJoin(
540
+ schema.creativeContextPublishedSnapshots,
541
+ and(
542
+ eq(
543
+ schema.creativeContextPublishedSnapshots.itemId,
544
+ schema.contextPackMembers.itemId,
545
+ ),
546
+ eq(
547
+ schema.creativeContextPublishedSnapshots.itemVersionId,
548
+ schema.contextPackMembers.itemVersionId,
549
+ ),
550
+ eq(
551
+ schema.creativeContextPublishedSnapshots.sourceId,
552
+ schema.contextItems.sourceId,
553
+ ),
554
+ ),
555
+ )
341
556
  .where(
342
557
  and(
343
558
  inArray(schema.contextPackMembers.packId, ids),
344
- accessFilter(schema.contextSources, schema.contextSourceShares),
345
- ne(schema.contextSources.upstreamAccess, "restricted"),
346
- ne(schema.contextSources.status, "archived"),
559
+ or(
560
+ and(
561
+ accessFilter(
562
+ schema.contextSources,
563
+ schema.contextSourceShares,
564
+ ),
565
+ ne(schema.contextSources.upstreamAccess, "restricted"),
566
+ ne(schema.contextSources.status, "archived"),
567
+ ),
568
+ isNotNull(schema.creativeContextPublishedSnapshots.id),
569
+ ),
347
570
  eq(schema.contextItems.curationStatus, "included"),
348
571
  ne(schema.contextItems.curationRank, "ignored"),
349
572
  eq(schema.contextItems.status, "active"),
@@ -375,7 +598,11 @@ export async function listContextPacks(input: {
375
598
  derivedFromPackId: row.derivedFromPackId ?? null,
376
599
  brandDnaVersionId: row.brandDnaVersionId ?? null,
377
600
  contextMode: row.contextMode,
378
- request: parseJson(row.request, {}),
601
+ baseContextId: row.baseContextId ?? null,
602
+ specialtyContextId: row.specialtyContextId ?? null,
603
+ selectionReason: row.selectionReason ?? null,
604
+ request: (sanitizePublicMetadata(parseJson(row.request, {})) ??
605
+ {}) as Record<string, unknown>,
379
606
  memberCount: memberCount.get(row.id) ?? 0,
380
607
  pinned: pinned.has(row.id),
381
608
  archivedAt: row.archivedAt ?? null,
@@ -414,12 +641,34 @@ export async function getContextPack(
414
641
  schema.contextSources,
415
642
  eq(schema.contextSources.id, schema.contextItems.sourceId),
416
643
  )
644
+ .leftJoin(
645
+ schema.creativeContextPublishedSnapshots,
646
+ and(
647
+ eq(
648
+ schema.creativeContextPublishedSnapshots.itemId,
649
+ schema.contextPackMembers.itemId,
650
+ ),
651
+ eq(
652
+ schema.creativeContextPublishedSnapshots.itemVersionId,
653
+ schema.contextPackMembers.itemVersionId,
654
+ ),
655
+ eq(
656
+ schema.creativeContextPublishedSnapshots.sourceId,
657
+ schema.contextItems.sourceId,
658
+ ),
659
+ ),
660
+ )
417
661
  .where(
418
662
  and(
419
663
  eq(schema.contextPackMembers.packId, packId),
420
- accessFilter(schema.contextSources, schema.contextSourceShares),
421
- ne(schema.contextSources.upstreamAccess, "restricted"),
422
- ne(schema.contextSources.status, "archived"),
664
+ or(
665
+ and(
666
+ accessFilter(schema.contextSources, schema.contextSourceShares),
667
+ ne(schema.contextSources.upstreamAccess, "restricted"),
668
+ ne(schema.contextSources.status, "archived"),
669
+ ),
670
+ isNotNull(schema.creativeContextPublishedSnapshots.id),
671
+ ),
423
672
  eq(schema.contextItems.curationStatus, "included"),
424
673
  ne(schema.contextItems.curationRank, "ignored"),
425
674
  eq(schema.contextItems.status, "active"),
@@ -446,7 +695,9 @@ export async function getContextPack(
446
695
  ordinal: member.ordinal,
447
696
  reason: member.reason ?? null,
448
697
  score: member.score ?? null,
449
- scoreMetadata: parseJson(member.scoreMetadata, {}),
698
+ scoreMetadata: (sanitizePublicMetadata(
699
+ parseJson(member.scoreMetadata, {}),
700
+ ) ?? {}) as Record<string, unknown>,
450
701
  }));
451
702
  return {
452
703
  id: row.id,
@@ -455,7 +706,11 @@ export async function getContextPack(
455
706
  derivedFromPackId: row.derivedFromPackId ?? null,
456
707
  brandDnaVersionId: row.brandDnaVersionId ?? null,
457
708
  contextMode: row.contextMode,
458
- request: parseJson(row.request, {}),
709
+ baseContextId: row.baseContextId ?? null,
710
+ specialtyContextId: row.specialtyContextId ?? null,
711
+ selectionReason: row.selectionReason ?? null,
712
+ request: (sanitizePublicMetadata(parseJson(row.request, {})) ??
713
+ {}) as Record<string, unknown>,
459
714
  memberCount: members.length,
460
715
  pinned: Boolean(pinRows[0]),
461
716
  archivedAt: row.archivedAt ?? null,
@@ -5,7 +5,7 @@ import {
5
5
  sharedResourceOwner,
6
6
  } from "@agent-native/core/resources/store";
7
7
  import { accessFilter, assertAccess } from "@agent-native/core/sharing";
8
- import { and, eq, inArray, or } from "drizzle-orm";
8
+ import { and, eq, inArray, like, or } from "drizzle-orm";
9
9
 
10
10
  import { parsePrivateBlobHandle } from "../connectors/private-artifacts.js";
11
11
  import { deletePostgresFtsDocuments } from "../search/postgres-fts.js";
@@ -29,12 +29,75 @@ export async function purgeContextSourceArtifacts(sourceId: string) {
29
29
  .limit(1);
30
30
  const source = sourceRows[0];
31
31
  if (!source) throw new Error("Creative context source not found");
32
- const items = await getDb()
32
+ const originalItems = await getDb()
33
33
  .select({ id: schema.contextItems.id })
34
34
  .from(schema.contextItems)
35
35
  .where(eq(schema.contextItems.sourceId, sourceId));
36
+ const derivedSubmissions = await getDb()
37
+ .select({
38
+ id: schema.creativeContextSubmissions.id,
39
+ membershipId: schema.creativeContextSubmissions.membershipId,
40
+ stagingItemId: schema.creativeContextSubmissions.stagingItemId,
41
+ publishedItemId: schema.creativeContextSubmissions.publishedItemId,
42
+ privateMetadata: schema.creativeContextSubmissions.privateMetadata,
43
+ })
44
+ .from(schema.creativeContextSubmissions)
45
+ .where(
46
+ like(schema.creativeContextSubmissions.artifactKey, `${sourceId}:%`),
47
+ );
48
+ const derivedSubmissionIds = derivedSubmissions.map((row: any) =>
49
+ String(row.id),
50
+ );
51
+ const publishedCopies = derivedSubmissionIds.length
52
+ ? await getDb()
53
+ .select({ itemId: schema.creativeContextPublishedSnapshots.itemId })
54
+ .from(schema.creativeContextPublishedSnapshots)
55
+ .where(
56
+ inArray(
57
+ schema.creativeContextPublishedSnapshots.submissionId,
58
+ derivedSubmissionIds,
59
+ ),
60
+ )
61
+ : [];
62
+ const stagedChildIds = derivedSubmissions.flatMap((row: any) => {
63
+ const metadata = parseJson<Record<string, unknown>>(
64
+ row.privateMetadata,
65
+ {},
66
+ );
67
+ return Array.isArray(metadata.stagedChildren)
68
+ ? metadata.stagedChildren.flatMap((child) =>
69
+ child &&
70
+ typeof child === "object" &&
71
+ typeof (child as { itemId?: unknown }).itemId === "string"
72
+ ? [(child as { itemId: string }).itemId]
73
+ : [],
74
+ )
75
+ : [];
76
+ });
77
+ const candidateItemIds = [
78
+ ...originalItems.map((item: any) => String(item.id)),
79
+ ...derivedSubmissions.flatMap((row: any) =>
80
+ [row.stagingItemId, row.publishedItemId].filter(
81
+ (value): value is string => typeof value === "string",
82
+ ),
83
+ ),
84
+ ...publishedCopies.map((row: any) => String(row.itemId)),
85
+ ...stagedChildIds,
86
+ ];
87
+ const items = candidateItemIds.length
88
+ ? await getDb()
89
+ .select({
90
+ id: schema.contextItems.id,
91
+ sourceId: schema.contextItems.sourceId,
92
+ })
93
+ .from(schema.contextItems)
94
+ .where(inArray(schema.contextItems.id, [...new Set(candidateItemIds)]))
95
+ : [];
36
96
  const itemIds = items.map((item: any) => item.id as string);
37
97
  if (!itemIds.length) return { sourceId, purgedItems: 0, purgedBlobs: 0 };
98
+ const affectedSourceIds = [
99
+ ...new Set([sourceId, ...items.map((item: any) => String(item.sourceId))]),
100
+ ];
38
101
  const versions = await getDb()
39
102
  .select({
40
103
  id: schema.contextItemVersions.id,
@@ -43,6 +106,16 @@ export async function purgeContextSourceArtifacts(sourceId: string) {
43
106
  .from(schema.contextItemVersions)
44
107
  .where(inArray(schema.contextItemVersions.itemId, itemIds));
45
108
  const versionIds = versions.map((version: any) => version.id as string);
109
+ const referencedMemberships = await getDb()
110
+ .select({ id: schema.creativeContextMemberships.id })
111
+ .from(schema.creativeContextMemberships)
112
+ .where(inArray(schema.creativeContextMemberships.publishedItemId, itemIds));
113
+ const affectedMembershipIds = [
114
+ ...new Set([
115
+ ...referencedMemberships.map((row: any) => String(row.id)),
116
+ ...derivedSubmissions.map((row: any) => String(row.membershipId)),
117
+ ]),
118
+ ];
46
119
  const [
47
120
  chunks,
48
121
  media,
@@ -140,10 +213,24 @@ export async function purgeContextSourceArtifacts(sourceId: string) {
140
213
  for (const layout of promotedLayouts) {
141
214
  await projections?.layoutTemplate?.demote(layout);
142
215
  }
216
+ const cloneBlobRefs = derivedSubmissions.flatMap((row: any) => {
217
+ const metadata = parseJson<Record<string, unknown>>(
218
+ row.privateMetadata,
219
+ {},
220
+ );
221
+ const clone =
222
+ metadata.clone &&
223
+ typeof metadata.clone === "object" &&
224
+ !Array.isArray(metadata.clone)
225
+ ? (metadata.clone as Record<string, unknown>)
226
+ : null;
227
+ return clone?.handle ? [clone.handle] : [];
228
+ });
143
229
  let purgedBlobs = 0;
144
230
  for (const reference of [
145
231
  ...versions.map((version: any) => version.rawSnapshotBlobRef),
146
232
  ...media.map((entry: any) => entry.storageKey),
233
+ ...cloneBlobRefs,
147
234
  ]) {
148
235
  const handle = parsePrivateBlobHandle(reference);
149
236
  if (!handle) continue;
@@ -161,6 +248,40 @@ export async function purgeContextSourceArtifacts(sourceId: string) {
161
248
  chunks.map((chunk: any) => chunk.id),
162
249
  ).catch(() => 0);
163
250
  await getDb().transaction(async (tx: any) => {
251
+ if (affectedMembershipIds.length) {
252
+ await tx
253
+ .update(schema.creativeContextMemberships)
254
+ .set({
255
+ publishedItemId: null,
256
+ publishedItemVersionId: null,
257
+ pendingSubmissionId: null,
258
+ status: "removed",
259
+ updatedAt: nowIso(),
260
+ })
261
+ .where(
262
+ inArray(schema.creativeContextMemberships.id, affectedMembershipIds),
263
+ );
264
+ }
265
+ await tx
266
+ .delete(schema.creativeContextPublishedSnapshots)
267
+ .where(
268
+ derivedSubmissionIds.length
269
+ ? or(
270
+ inArray(schema.creativeContextPublishedSnapshots.itemId, itemIds),
271
+ inArray(
272
+ schema.creativeContextPublishedSnapshots.submissionId,
273
+ derivedSubmissionIds,
274
+ ),
275
+ )
276
+ : inArray(schema.creativeContextPublishedSnapshots.itemId, itemIds),
277
+ );
278
+ if (derivedSubmissionIds.length) {
279
+ await tx
280
+ .delete(schema.creativeContextSubmissions)
281
+ .where(
282
+ inArray(schema.creativeContextSubmissions.id, derivedSubmissionIds),
283
+ );
284
+ }
164
285
  if (derivedEdges.length) {
165
286
  await tx
166
287
  .update(schema.contextItems)
@@ -266,11 +387,23 @@ export async function purgeContextSourceArtifacts(sourceId: string) {
266
387
  await tx
267
388
  .delete(schema.contextItems)
268
389
  .where(inArray(schema.contextItems.id, itemIds));
269
- await tx
270
- .update(schema.contextSources)
271
- .set({ itemCount: 0, restrictedItemCount: 0, updatedAt: nowIso() })
272
- .where(eq(schema.contextSources.id, sourceId));
273
390
  });
391
+ for (const affectedSourceId of affectedSourceIds) {
392
+ const remaining = await getDb()
393
+ .select({ upstreamAccess: schema.contextItems.upstreamAccess })
394
+ .from(schema.contextItems)
395
+ .where(eq(schema.contextItems.sourceId, affectedSourceId));
396
+ await getDb()
397
+ .update(schema.contextSources)
398
+ .set({
399
+ itemCount: remaining.length,
400
+ restrictedItemCount: remaining.filter(
401
+ (item: any) => item.upstreamAccess !== "available",
402
+ ).length,
403
+ updatedAt: nowIso(),
404
+ })
405
+ .where(eq(schema.contextSources.id, affectedSourceId));
406
+ }
274
407
  const profileIds = [
275
408
  ...new Set(affectedProfiles.map((row: any) => row.profileId as string)),
276
409
  ];
@@ -1,4 +1,4 @@
1
- import { getDbExec } from "@agent-native/core/db";
1
+ import { currentRequestUserIsOrgAdmin } from "@agent-native/core/server";
2
2
  import {
3
3
  accessFilter,
4
4
  assertAccess,
@@ -580,22 +580,6 @@ export async function contextSourceBoundary(input: {
580
580
  };
581
581
  }
582
582
 
583
- async function currentUserIsOrgAdmin(
584
- orgId: string,
585
- ownerEmail: string,
586
- ): Promise<boolean> {
587
- try {
588
- const result = await getDbExec().execute({
589
- sql: "SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1",
590
- args: [orgId, ownerEmail.toLowerCase()],
591
- });
592
- const role = String(result.rows[0]?.role ?? "").toLowerCase();
593
- return role === "owner" || role === "admin";
594
- } catch {
595
- return false;
596
- }
597
- }
598
-
599
583
  export async function previewContextSourcePromotion(
600
584
  sourceId: string,
601
585
  ): Promise<ContextSourcePromotionPreview> {
@@ -608,7 +592,7 @@ export async function previewContextSourcePromotion(
608
592
  if (!actor.orgId) {
609
593
  throw new Error("Select an organization before promoting a context source");
610
594
  }
611
- const orgAdmin = await currentUserIsOrgAdmin(actor.orgId, actor.ownerEmail);
595
+ const orgAdmin = await currentRequestUserIsOrgAdmin(actor.orgId);
612
596
  const verifiedOwner =
613
597
  access.role === "owner" &&
614
598
  Boolean(access.resource.containerOwnerVerifiedAt);
@@ -5,6 +5,7 @@ import { assertContextItemSqlTextLimits } from "../connectors/normalize.js";
5
5
  import { reassembleNativeCreativeArtifact } from "../native-artifact-reassembly.js";
6
6
  import { nativeCreativeArtifactFromMetadata } from "../native-artifact.js";
7
7
  import { getCreativeContext } from "../server/context.js";
8
+ import { sanitizePublicMetadata } from "../server/public-serialization.js";
8
9
  import type { CreativeContextSuggestion } from "../types.js";
9
10
  import {
10
11
  getCreativeContextItem,
@@ -30,7 +31,8 @@ function mapSuggestion(row: any): CreativeContextSuggestion {
30
31
  itemId: row.itemId,
31
32
  itemVersionId: row.itemVersionId,
32
33
  reason: row.reason ?? null,
33
- payload: parseJson(row.payload, {}),
34
+ payload: (sanitizePublicMetadata(parseJson(row.payload, {})) ??
35
+ {}) as Record<string, unknown>,
34
36
  createdAt: row.createdAt,
35
37
  updatedAt: row.updatedAt,
36
38
  };