@agent-native/core-corpus 0.212.1 → 0.214.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 (90) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/onboarding-journeys/SKILL.md +29 -14
  3. package/corpus/templates/analytics/CHANGELOG.md +11 -0
  4. package/corpus/templates/analytics/actions/estimate-onboarding-journey-cost.ts +23 -6
  5. package/corpus/templates/analytics/actions/get-onboarding-journey.ts +20 -5
  6. package/corpus/templates/analytics/actions/provider-api-register.ts +2 -7
  7. package/corpus/templates/analytics/changelog/2026-10-21-analytics-recognizes-questions-about-connected-data-product-.md +6 -0
  8. package/corpus/templates/analytics/changelog/2026-10-21-onboarding-journeys-now-honor-larger-event-row-budgets.md +6 -0
  9. package/corpus/templates/analytics/changelog/2026-10-21-preserve-unknown-replay-availability-in-partial-journeys.md +6 -0
  10. package/corpus/templates/analytics/package.json +6 -0
  11. package/corpus/templates/analytics/scripts/journey-capture-plan.ts +18 -7
  12. package/corpus/templates/analytics/scripts/journey-capture.ts +3 -1
  13. package/corpus/templates/analytics/server/lib/db-admin-connections.ts +19 -12
  14. package/corpus/templates/analytics/server/lib/journey-tree.ts +24 -9
  15. package/corpus/templates/analytics/server/lib/onboarding-journey-cost.ts +163 -94
  16. package/corpus/templates/analytics/server/lib/onboarding-journey.ts +162 -74
  17. package/corpus/templates/assets/server/lib/org-admin.ts +20 -16
  18. package/corpus/templates/brain/server/lib/zoom-governance.ts +11 -6
  19. package/corpus/templates/clips/CHANGELOG.md +1 -0
  20. package/corpus/templates/clips/changelog/2026-10-16-rewind-status-says-when-it-remembers-audio.md +6 -0
  21. package/corpus/templates/clips/desktop/src/app.tsx +7 -4
  22. package/corpus/templates/clips/desktop/src/components/RewindCaptureStatus.tsx +25 -0
  23. package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +6 -1
  24. package/corpus/templates/clips/desktop/src/shared/config.ts +3 -0
  25. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2 -0
  26. package/corpus/templates/clips/server/lib/recordings.ts +4 -19
  27. package/corpus/templates/community/qr/package.json +1 -1
  28. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +15 -1
  29. package/corpus/templates/content/CHANGELOG.md +13 -0
  30. package/corpus/templates/content/actions/_content-space-access.ts +95 -6
  31. package/corpus/templates/content/actions/_database-utils.ts +20 -16
  32. package/corpus/templates/content/actions/_database-view-setup.ts +305 -11
  33. package/corpus/templates/content/actions/_github-source-freshness.ts +72 -17
  34. package/corpus/templates/content/actions/_github-source-metadata.ts +63 -17
  35. package/corpus/templates/content/actions/_github-source.ts +141 -77
  36. package/corpus/templates/content/actions/_property-utils.ts +3 -0
  37. package/corpus/templates/content/actions/check-github-connection.ts +15 -0
  38. package/corpus/templates/content/actions/create-github-collection.ts +180 -0
  39. package/corpus/templates/content/actions/disconnect-content-database-source.ts +5 -0
  40. package/corpus/templates/content/actions/link-github-folder.ts +104 -36
  41. package/corpus/templates/content/actions/refresh-content-database-source.ts +37 -4
  42. package/corpus/templates/content/actions/sync-github-folder.ts +23 -5
  43. package/corpus/templates/content/actions/update-content-database-view.ts +18 -2
  44. package/corpus/templates/content/actions/view-screen.ts +12 -0
  45. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +263 -35
  46. package/corpus/templates/content/app/components/editor/database/GitHubLinkDialog.tsx +938 -0
  47. package/corpus/templates/content/app/components/editor/database/GitHubSourcePanel.tsx +228 -0
  48. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +561 -0
  49. package/corpus/templates/content/app/components/editor/database/GitHubUnlinkDialog.tsx +96 -0
  50. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +410 -0
  51. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +426 -0
  52. package/corpus/templates/content/app/components/editor/database/ListView.tsx +136 -18
  53. package/corpus/templates/content/app/components/editor/database/github-availability.ts +147 -0
  54. package/corpus/templates/content/app/components/editor/database/github-source-text.ts +280 -0
  55. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +3 -0
  56. package/corpus/templates/content/app/components/editor/database/settings.tsx +3 -3
  57. package/corpus/templates/content/app/components/editor/database/view-config.ts +4 -0
  58. package/corpus/templates/content/app/components/layout/Layout.tsx +2 -0
  59. package/corpus/templates/content/app/global.css +13 -0
  60. package/corpus/templates/content/app/hooks/content-action-refresh.ts +6 -0
  61. package/corpus/templates/content/app/hooks/use-content-database.ts +3 -1
  62. package/corpus/templates/content/app/hooks/use-github-folder.ts +199 -0
  63. package/corpus/templates/content/app/i18n-data.ts +186 -1
  64. package/corpus/templates/content/app/lib/github-link-dialog.ts +86 -0
  65. package/corpus/templates/content/changelog/2026-10-18-connected-ai-assistants-can-link-a-new-collection-to-a-github.md +1 -1
  66. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +6 -0
  67. package/corpus/templates/content/changelog/2026-10-19-list-rows-can-be-customized.md +6 -0
  68. package/corpus/templates/content/changelog/2026-10-19-list-views-fit-phone-screens.md +6 -0
  69. package/corpus/templates/content/changelog/2026-10-21-organization-favorites-include-documents-shared-with-the-cur.md +6 -0
  70. package/corpus/templates/content/docs/product/capabilities/content.source.sync-policy.md +32 -7
  71. package/corpus/templates/content/docs/product/encyclopedia.md +3 -3
  72. package/corpus/templates/content/docs/product/features/content.feature.trust-your-connected-sources.md +2 -2
  73. package/corpus/templates/content/docs/product/roadmap.md +1 -1
  74. package/corpus/templates/content/parity/matrix.md +42 -42
  75. package/corpus/templates/content/parity/matrix.ts +14 -0
  76. package/corpus/templates/content/server/lib/github-source-client.ts +50 -1
  77. package/corpus/templates/content/shared/api.ts +21 -0
  78. package/corpus/templates/content/shared/database-item-layout.ts +591 -0
  79. package/corpus/templates/content/shared/github-source.ts +68 -0
  80. package/corpus/templates/design/CHANGELOG.md +1 -0
  81. package/corpus/templates/design/actions/connect-assets-mcp.ts +5 -9
  82. package/corpus/templates/design/changelog/2026-10-21-journey-canvases-accept-partial-analytics-samples-when-repla.md +6 -0
  83. package/corpus/templates/design/shared/journey-canvas.ts +6 -4
  84. package/corpus/templates/dispatch/.agents/skills/a2a-protocol/SKILL.md +16 -13
  85. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  86. package/corpus/templates/dispatch/changelog/2026-10-21-customer-ai-credit-questions-route-to-account-health-instead.md +6 -0
  87. package/corpus/templates/factory/server/lib/require-workspace-member.ts +5 -17
  88. package/corpus/templates/mail/server/lib/queued-drafts.ts +9 -4
  89. package/corpus/templates/slides/server/workspace-defaults.ts +12 -15
  90. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
- import { orgMembers } from "@agent-native/core/org";
1
+ import { readOrgMemberRole } from "@agent-native/core/server";
2
2
  import {
3
+ getRequestContext,
3
4
  getRequestUserEmail,
4
5
  getRequestOrgId,
5
- } from "@agent-native/core/server/request-context";
6
- import { and, eq, sql } from "drizzle-orm";
7
-
8
- import { getDb } from "../db/index.js";
6
+ } from "@agent-native/core/server";
9
7
 
10
8
  export class ForbiddenAuditError extends Error {
11
9
  readonly statusCode = 403;
@@ -36,22 +34,28 @@ export async function assertOrgAdmin(): Promise<AdminScope> {
36
34
 
37
35
  const orgId = getRequestOrgId();
38
36
  if (!orgId) {
37
+ if (getRequestContext()?.a2aIdentityAssurance === "org-member") {
38
+ throw new ForbiddenAuditError(
39
+ "Could not verify the delegated organization scope; refusing audit access.",
40
+ );
41
+ }
39
42
  return { ownerEmail: email };
40
43
  }
41
44
 
45
+ const request = getRequestContext();
46
+ if (
47
+ request?.a2aIdentityAssurance === "org-member" &&
48
+ (request.orgId?.trim() !== orgId.trim() ||
49
+ request.userEmail?.trim().toLowerCase() !== email.trim().toLowerCase())
50
+ ) {
51
+ throw new ForbiddenAuditError(
52
+ "Could not verify the delegated organization scope; refusing audit access.",
53
+ );
54
+ }
55
+
42
56
  let role: string | null = null;
43
57
  try {
44
- const [row] = await getDb()
45
- .select({ role: orgMembers.role })
46
- .from(orgMembers)
47
- .where(
48
- and(
49
- eq(orgMembers.orgId, orgId),
50
- sql`lower(${orgMembers.email}) = ${email.toLowerCase()}`,
51
- ),
52
- )
53
- .limit(1);
54
- role = row?.role?.toLowerCase() ?? null;
58
+ role = await readOrgMemberRole(orgId, email);
55
59
  } catch {
56
60
  throw new ForbiddenAuditError(
57
61
  "Could not verify org admin role; refusing audit access.",
@@ -1,6 +1,7 @@
1
1
  import { fail } from "@agent-native/core/action";
2
- import { getDbExec } from "@agent-native/core/db";
2
+ import { readOrgMemberRole } from "@agent-native/core/server";
3
3
  import {
4
+ getRequestContext,
4
5
  getRequestOrgId,
5
6
  getRequestUserEmail,
6
7
  } from "@agent-native/core/server/request-context";
@@ -21,11 +22,15 @@ export async function isBrainAdmin(
21
22
  const normalizedEmail = email?.trim();
22
23
  const normalizedOrgId = orgId?.trim();
23
24
  if (!normalizedEmail || !normalizedOrgId) return false;
24
- const { rows } = await getDbExec().execute({
25
- sql: `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = LOWER(?) AND federation_removal_pending_at IS NULL LIMIT 1`,
26
- args: [normalizedOrgId, normalizedEmail],
27
- });
28
- const role = (rows[0] as { role?: unknown } | undefined)?.role;
25
+ const request = getRequestContext();
26
+ if (
27
+ request?.a2aIdentityAssurance === "org-member" &&
28
+ (request.orgId?.trim() !== normalizedOrgId ||
29
+ request.userEmail?.trim().toLowerCase() !== normalizedEmail.toLowerCase())
30
+ ) {
31
+ return false;
32
+ }
33
+ const role = await readOrgMemberRole(normalizedOrgId, normalizedEmail);
29
34
  return role === "owner" || role === "admin";
30
35
  }
31
36
 
@@ -59,6 +59,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
59
59
 
60
60
  ### Improved
61
61
 
62
+ - Rewind settings now show whether Rewind is remembering screen only or screen + audio.
62
63
  - Clips detects browser meetings more reliably and keeps notes until the meeting stop and transcript save are confirmed.
63
64
  - Clips detects browser meetings sooner, asks before starting when the active tab is unclear, and retries failed stops.
64
65
  - Desktop dictation confirms when text is pasted or copied and keeps transcripts available when saving fails.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-16
4
+ ---
5
+
6
+ Rewind settings now show whether Rewind is remembering screen only or screen + audio.
@@ -96,6 +96,7 @@ import {
96
96
  RecordingRecoveryPage,
97
97
  type RecordingRecoveryProps,
98
98
  } from "./components/RecordingRecovery";
99
+ import { RewindCaptureStatus } from "./components/RewindCaptureStatus";
99
100
  import { ShortcutKeycaps } from "./components/ShortcutKeycaps";
100
101
  import { SourceRow, type CaptureSource } from "./components/SourceRow";
101
102
  import { Switch } from "./components/Switch";
@@ -8151,10 +8152,12 @@ function Setup({
8151
8152
  >
8152
8153
  {screenMemoryMessage.text}
8153
8154
  </p>
8154
- ) : rewindOn && rewindStatusPresentation.hasError ? (
8155
- <p className="text-xs text-destructive">
8156
- {rewindStatusPresentation.detail}
8157
- </p>
8155
+ ) : null}
8156
+ {rewindOn ? (
8157
+ <RewindCaptureStatus
8158
+ status={screenMemoryStatus}
8159
+ presentation={rewindStatusPresentation}
8160
+ />
8158
8161
  ) : null}
8159
8162
  </SettingsRow>
8160
8163
  {rewindOn ? (
@@ -0,0 +1,25 @@
1
+ import type { RewindStatusPresentation } from "../lib/rewind-status";
2
+ import type { ScreenMemoryStatus } from "../shared/config";
3
+
4
+ export function RewindCaptureStatus({
5
+ status,
6
+ presentation,
7
+ }: {
8
+ status: ScreenMemoryStatus | null;
9
+ presentation: RewindStatusPresentation;
10
+ }) {
11
+ if (!status || presentation.kind === "off") return null;
12
+ return (
13
+ <>
14
+ <p className="flex items-center gap-2" role="status">
15
+ {presentation.isLive ? (
16
+ <span className="rec-dot" aria-hidden="true" />
17
+ ) : null}
18
+ {presentation.title}
19
+ </p>
20
+ {presentation.hasError ? (
21
+ <p className="text-xs text-destructive">{presentation.detail}</p>
22
+ ) : null}
23
+ </>
24
+ );
25
+ }
@@ -70,9 +70,14 @@ export function getRewindStatusPresentation({
70
70
  }
71
71
 
72
72
  if (status?.state === "recording") {
73
+ // The active segment, not the setting: a clip or meeting notes can add
74
+ // audio while Remember is set to Screen only.
75
+ const capturesAudio = status.activeSegment?.captureMode === "visuals-audio";
73
76
  return {
74
77
  kind: "recording",
75
- title: "Rewind is remembering",
78
+ title: capturesAudio
79
+ ? "Rewind is remembering screen + audio"
80
+ : "Rewind is remembering screen only",
76
81
  detail:
77
82
  status.lastError ||
78
83
  status.coverage ||
@@ -70,6 +70,9 @@ export interface ScreenMemoryActiveSegment {
70
70
  durationMs: number;
71
71
  width?: number | null;
72
72
  height?: number | null;
73
+ // What this segment captures. A clip or meeting can add audio while the
74
+ // configured mode is screen only.
75
+ captureMode: RewindCaptureMode;
73
76
  }
74
77
 
75
78
  export interface ScreenMemoryStatus {
@@ -417,6 +417,7 @@ pub struct ScreenMemoryActiveSegment {
417
417
  pub duration_ms: u128,
418
418
  pub width: Option<u32>,
419
419
  pub height: Option<u32>,
420
+ pub capture_mode: RewindCaptureMode,
420
421
  }
421
422
 
422
423
  #[derive(Debug, Clone, Serialize)]
@@ -1134,6 +1135,7 @@ fn build_status(app: &AppHandle) -> Result<ScreenMemoryStatus, String> {
1134
1135
  duration_ms: active.started_at.elapsed().as_millis(),
1135
1136
  width: active.width,
1136
1137
  height: active.height,
1138
+ capture_mode: active.capture_mode,
1137
1139
  }),
1138
1140
  guard.last_error.clone(),
1139
1141
  guard.exclusion_active,
@@ -1,10 +1,6 @@
1
1
  import { readAppState } from "@agent-native/core/application-state";
2
- import {
3
- implicitServiceOrgRole,
4
- organizations,
5
- orgMembers,
6
- } from "@agent-native/core/org";
7
- import { getSession } from "@agent-native/core/server";
2
+ import { implicitServiceOrgRole, organizations } from "@agent-native/core/org";
3
+ import { getSession, readOrgMemberRole } from "@agent-native/core/server";
8
4
  import {
9
5
  getRequestUserEmail,
10
6
  getRequestOrgId,
@@ -175,20 +171,9 @@ export async function getOrganizationRoleForEmail(
175
171
  organizationId: string,
176
172
  email: string,
177
173
  ): Promise<OrganizationAccessRole | null> {
178
- const lowerEmail = email.toLowerCase();
179
-
180
174
  try {
181
- const [row] = await getDb()
182
- .select({ role: orgMembers.role })
183
- .from(orgMembers)
184
- .where(
185
- and(
186
- eq(orgMembers.orgId, organizationId),
187
- sql`lower(${orgMembers.email}) = ${lowerEmail}`,
188
- ),
189
- )
190
- .limit(1);
191
- if (row?.role) return normalizeOrganizationRole(row.role);
175
+ const role = await readOrgMemberRole(organizationId, email);
176
+ if (role) return normalizeOrganizationRole(role);
192
177
  } catch {
193
178
  // org_members table may not exist yet on first boot before migrations finish.
194
179
  }
@@ -11,7 +11,7 @@
11
11
  "typecheck": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@agent-native/core": "^0.210.2",
14
+ "@agent-native/core": "^0.214.0",
15
15
  "postgres": "^3.4.9",
16
16
  "qrcode": "^1.5.4",
17
17
  "zod": "^4.6.7"
@@ -88,7 +88,7 @@ stable IDs identify edits. Sparse patches preserve omitted fields, and empty
88
88
  lists explicitly clear supported settings.
89
89
 
90
90
  Ordinary setup supports stored property types, additive options, metadata edits,
91
- the existing text natural key, and table presentation. It refuses destructive
91
+ the existing text natural key, table presentation, and list row layouts. It refuses destructive
92
92
  type/option changes, source-managed schema edits, and relationship/computed-field
93
93
  authoring. Source status is readable, but this surface does not define joins,
94
94
  row unions, source bindings, or write-mode changes. Discovery of a source-backed
@@ -230,6 +230,20 @@ all fields for the current view, and it includes a New property control for
230
230
  adding fields without returning to the table header. The New property picker
231
231
  supports searching property types by label or machine name.
232
232
 
233
+ List views can save an item layout (`itemLayout`) that decides what each row
234
+ shows: a start slot (page icon, a checkbox property, a status property, or
235
+ nothing), the title weight, line 2 (a text, URL, or non-body Blocks property,
236
+ clamped to 1-3 lines), and ordered property IDs for line 3 and the end of the
237
+ row; end values move under the title on narrow screens and empty values take
238
+ no space. Ticking a row's checkbox writes the property through
239
+ `set-document-property` and strikes the title through without moving the row.
240
+ A view without `itemLayout` keeps the default rows. Agents update it with an
241
+ `update-content-database-view` `update` patch on the list view: pass
242
+ `itemLayoutPreset` (`checklist`, `reminders`, `tracker`, `digest`, or
243
+ `default`), sparse `itemLayout` slots (omitted slots keep their value), or
244
+ `itemLayout: null` to restore the default rows. List views accept only name,
245
+ icon, and these layout fields on this path.
246
+
233
247
  In unconstrained table views, row drag handles can reorder collection item
234
248
  pages through `move-database-item`; pass both `databaseId` and `itemId` so the
235
249
  membership target is exact, and clear search, sort, and filters before manual
@@ -3,8 +3,18 @@
3
3
  All notable user-facing changes to Agent-Native Content are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-10-21
7
+
8
+ ### Fixed
9
+
10
+ - Organization favorites include documents shared with the current member
11
+
6
12
  ## 2026-10-19
7
13
 
14
+ ### Added
15
+
16
+ - List rows can be customized: start from a preset like **Checklist**, then choose what shows before, under, and after each title
17
+
8
18
  ### Improved
9
19
 
10
20
  - You can comment on text in any Blocks field tab; each tab shows its open comments, and the Comments panel groups and filters them by tab
@@ -12,6 +22,7 @@ time from the command menu (Cmd+K → "What's new").
12
22
 
13
23
  ### Fixed
14
24
 
25
+ - List views no longer run off the side of the screen on phones
15
26
  - Edits made from a block's menu or a table's row and column menus, such as deleting a table, adding a row or turning off the header row, are now saved instead of coming back after a reload
16
27
  - Starting a comment puts the cursor in the comment box, ready to type.
17
28
  - Table row and column options now appear for the row or column under the pointer, Escape selects the cell before leaving the editor, wide tables keep their add-column control on screen on phones, and a tap near a table's edge on a touch screen edits the cell there instead of adding a row or column
@@ -24,6 +35,8 @@ time from the command menu (Cmd+K → "What's new").
24
35
 
25
36
  ### Added
26
37
 
38
+ - Connected AI assistants can make a collection from a GitHub folder, or link an empty one: each Markdown file becomes a view-only page, and a sync brings in the latest pushes
39
+ - Make a collection from a GitHub folder in the sidebar's New menu, or fill an empty one from its Sources settings, which say up front when GitHub can't link and what to do instead. A short preview comes before linking, the collection shows where its pages come from and when they last updated, and unlinking asks first
27
40
  - Connected AI assistants can link a new collection to a GitHub folder: each Markdown file becomes a view-only page, and a sync brings in the latest pushes
28
41
 
29
42
  ### Improved
@@ -1,6 +1,10 @@
1
1
  import { ActionContractError } from "@agent-native/core/action";
2
2
  import { getDbExec, type DbExec } from "@agent-native/core/db";
3
- import { getRequestUserEmail } from "@agent-native/core/server/request-context";
3
+ import { readOrgMemberRole } from "@agent-native/core/server";
4
+ import {
5
+ getRequestContext,
6
+ getRequestUserEmail,
7
+ } from "@agent-native/core/server/request-context";
4
8
  import { and, eq, isNull, sql } from "drizzle-orm";
5
9
 
6
10
  import { getDb, schema } from "../server/db/index.js";
@@ -13,6 +17,29 @@ export type ContentSpaceAccess = {
13
17
  role: ContentSpaceRole;
14
18
  };
15
19
 
20
+ function isDelegatedIdentityMismatch(orgId: string, email: string): boolean {
21
+ const request = getRequestContext();
22
+ return (
23
+ request?.a2aIdentityAssurance === "org-member" &&
24
+ (request.orgId?.trim() !== orgId.trim() ||
25
+ request.userEmail?.trim().toLowerCase() !== email.trim().toLowerCase())
26
+ );
27
+ }
28
+
29
+ function preserveDelegatedMemberRole(
30
+ orgId: string,
31
+ email: string,
32
+ validatedRole: string,
33
+ verifiedRole: string,
34
+ ): string {
35
+ const request = getRequestContext();
36
+ return request?.a2aIdentityAssurance === "org-member" &&
37
+ request.orgId?.trim() === orgId.trim() &&
38
+ request.userEmail?.trim().toLowerCase() === email.trim().toLowerCase()
39
+ ? verifiedRole
40
+ : validatedRole;
41
+ }
42
+
16
43
  type ContentOrganizationMembership = {
17
44
  role: string;
18
45
  name: string;
@@ -59,6 +86,11 @@ export async function getContentOrganizationMembership(
59
86
  userEmail: string,
60
87
  options: { db?: any } = {},
61
88
  ): Promise<{ role: string; name: string; createdBy: string } | null> {
89
+ if (isDelegatedIdentityMismatch(orgId, userEmail)) return null;
90
+
91
+ const verifiedRole = await readOrgMemberRole(orgId, userEmail);
92
+ if (!verifiedRole) return null;
93
+
62
94
  if (options.db) {
63
95
  const { organizations, orgMembers } =
64
96
  await import("@agent-native/core/org");
@@ -81,7 +113,7 @@ export async function getContentOrganizationMembership(
81
113
  )
82
114
  .limit(1);
83
115
  if (!row) return null;
84
- return revalidateFederatedMembership(orgId, userEmail, {
116
+ const validated = await revalidateFederatedMembership(orgId, userEmail, {
85
117
  role: (typeof row.role === "string"
86
118
  ? row.role
87
119
  : (JSON.stringify(row.role) ?? "member")
@@ -91,6 +123,17 @@ export async function getContentOrganizationMembership(
91
123
  identityAuthority: row.identityAuthority,
92
124
  identityId: row.identityId,
93
125
  });
126
+ return validated
127
+ ? {
128
+ ...validated,
129
+ role: preserveDelegatedMemberRole(
130
+ orgId,
131
+ userEmail,
132
+ validated.role,
133
+ verifiedRole,
134
+ ),
135
+ }
136
+ : null;
94
137
  }
95
138
  const result = await getDbExec().execute({
96
139
  sql: `SELECT m.role AS role, o.name AS name, o.created_by AS "createdBy",
@@ -119,7 +162,7 @@ export async function getContentOrganizationMembership(
119
162
  ) {
120
163
  return null;
121
164
  }
122
- return revalidateFederatedMembership(orgId, userEmail, {
165
+ const validated = await revalidateFederatedMembership(orgId, userEmail, {
123
166
  role: (typeof row.role === "string"
124
167
  ? row.role
125
168
  : (JSON.stringify(row.role) ?? "member")
@@ -129,12 +172,34 @@ export async function getContentOrganizationMembership(
129
172
  identityAuthority: row.identityAuthority,
130
173
  identityId: row.identityId,
131
174
  });
175
+ return validated
176
+ ? {
177
+ ...validated,
178
+ role: preserveDelegatedMemberRole(
179
+ orgId,
180
+ userEmail,
181
+ validated.role,
182
+ verifiedRole,
183
+ ),
184
+ }
185
+ : null;
132
186
  }
133
187
 
134
188
  export async function listContentOrganizationMemberships(
135
189
  userEmail: string,
136
190
  transaction?: DbExec,
137
191
  ) {
192
+ const request = getRequestContext();
193
+ const normalizedEmail = normalizeContentSpaceEmail(userEmail);
194
+ const delegated = request?.a2aIdentityAssurance === "org-member";
195
+ if (
196
+ delegated &&
197
+ (!request.orgId?.trim() ||
198
+ request.userEmail?.trim().toLowerCase() !== normalizedEmail)
199
+ ) {
200
+ return [];
201
+ }
202
+
138
203
  if (transaction) {
139
204
  const relation = await transaction.execute({
140
205
  sql: "SELECT to_regclass('org_members') AS relation",
@@ -153,8 +218,11 @@ export async function listContentOrganizationMemberships(
153
218
  INNER JOIN organizations o ON o.id = m.org_id
154
219
  WHERE LOWER(m.email) = $1
155
220
  AND m.federation_removal_pending_at IS NULL
221
+ ${delegated ? "AND m.org_id = $2" : ""}
156
222
  ORDER BY m.org_id ASC`,
157
- args: [normalizeContentSpaceEmail(userEmail)],
223
+ args: delegated
224
+ ? [normalizedEmail, request!.orgId!.trim()]
225
+ : [normalizedEmail],
158
226
  });
159
227
  } catch (error) {
160
228
  if (error instanceof Error && error.message.includes("does not exist")) {
@@ -174,11 +242,21 @@ export async function listContentOrganizationMemberships(
174
242
  identityAuthority: row.identityAuthority ?? row.identity_authority,
175
243
  identityId: row.identityId ?? row.identity_id,
176
244
  }))
245
+ .map((row: ContentOrganizationMembership & { orgId?: string }) => ({
246
+ ...row,
247
+ role:
248
+ row.role === "owner" || row.role === "admin" || row.role === "member"
249
+ ? delegated
250
+ ? "member"
251
+ : row.role
252
+ : null,
253
+ }))
177
254
  .filter(
178
255
  (row): row is ContentOrganizationMembership & { orgId: string } =>
179
256
  typeof row.orgId === "string" &&
180
257
  typeof row.name === "string" &&
181
- typeof row.createdBy === "string",
258
+ typeof row.createdBy === "string" &&
259
+ row.role !== null,
182
260
  );
183
261
  return Promise.all(
184
262
  memberships.map(async ({ orgId, ...membership }) => {
@@ -187,7 +265,18 @@ export async function listContentOrganizationMemberships(
187
265
  userEmail,
188
266
  membership,
189
267
  );
190
- return validated ? { orgId, ...validated } : null;
268
+ return validated
269
+ ? {
270
+ orgId,
271
+ ...validated,
272
+ role: preserveDelegatedMemberRole(
273
+ orgId,
274
+ normalizedEmail,
275
+ validated.role,
276
+ membership.role,
277
+ ),
278
+ }
279
+ : null;
191
280
  }),
192
281
  ).then((validated) =>
193
282
  validated.filter(
@@ -1018,23 +1018,27 @@ export async function getContentDatabasePageResponse(
1018
1018
  items.map((item) => item.documentId),
1019
1019
  ),
1020
1020
  isNull(schema.documents.trashedAt),
1021
- database.systemRole === "workspaces"
1022
- ? workspacesVisibleDocumentIds?.length
1023
- ? inArray(schema.documents.id, workspacesVisibleDocumentIds)
1021
+ database.systemRole === "favorites"
1022
+ ? favoritesVisibleDocumentQuery
1023
+ ? inArray(schema.documents.id, favoritesVisibleDocumentQuery)
1024
1024
  : sql`1 = 0`
1025
- : database.systemRole === "files" && database.orgId
1026
- ? and(
1027
- eq(schema.documents.orgId, database.orgId),
1028
- or(
1029
- eq(schema.documents.visibility, "org"),
1030
- eq(schema.documents.visibility, "public"),
1031
- ),
1032
- or(
1033
- eq(schema.documents.hideFromSearch, 0),
1034
- isNull(schema.documents.hideFromSearch),
1035
- ),
1036
- )
1037
- : eq(schema.documents.ownerEmail, database.ownerEmail),
1025
+ : database.systemRole === "workspaces"
1026
+ ? workspacesVisibleDocumentIds?.length
1027
+ ? inArray(schema.documents.id, workspacesVisibleDocumentIds)
1028
+ : sql`1 = 0`
1029
+ : database.systemRole === "files" && database.orgId
1030
+ ? and(
1031
+ eq(schema.documents.orgId, database.orgId),
1032
+ or(
1033
+ eq(schema.documents.visibility, "org"),
1034
+ eq(schema.documents.visibility, "public"),
1035
+ ),
1036
+ or(
1037
+ eq(schema.documents.hideFromSearch, 0),
1038
+ isNull(schema.documents.hideFromSearch),
1039
+ ),
1040
+ )
1041
+ : eq(schema.documents.ownerEmail, database.ownerEmail),
1038
1042
  ),
1039
1043
  )
1040
1044
  : [];