@agent-native/core-corpus 0.212.1 → 0.213.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) 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/CHANGELOG.md +8 -0
  29. package/corpus/templates/content/actions/_content-space-access.ts +95 -6
  30. package/corpus/templates/content/actions/_database-utils.ts +20 -16
  31. package/corpus/templates/content/actions/_github-source-freshness.ts +72 -17
  32. package/corpus/templates/content/actions/_github-source-metadata.ts +63 -17
  33. package/corpus/templates/content/actions/_github-source.ts +141 -77
  34. package/corpus/templates/content/actions/check-github-connection.ts +15 -0
  35. package/corpus/templates/content/actions/create-github-collection.ts +180 -0
  36. package/corpus/templates/content/actions/disconnect-content-database-source.ts +5 -0
  37. package/corpus/templates/content/actions/link-github-folder.ts +104 -36
  38. package/corpus/templates/content/actions/refresh-content-database-source.ts +37 -4
  39. package/corpus/templates/content/actions/sync-github-folder.ts +23 -5
  40. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +172 -19
  41. package/corpus/templates/content/app/components/editor/database/GitHubLinkDialog.tsx +938 -0
  42. package/corpus/templates/content/app/components/editor/database/GitHubSourcePanel.tsx +228 -0
  43. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +561 -0
  44. package/corpus/templates/content/app/components/editor/database/GitHubUnlinkDialog.tsx +96 -0
  45. package/corpus/templates/content/app/components/editor/database/github-availability.ts +147 -0
  46. package/corpus/templates/content/app/components/editor/database/github-source-text.ts +280 -0
  47. package/corpus/templates/content/app/components/layout/Layout.tsx +2 -0
  48. package/corpus/templates/content/app/hooks/content-action-refresh.ts +6 -0
  49. package/corpus/templates/content/app/hooks/use-content-database.ts +3 -1
  50. package/corpus/templates/content/app/hooks/use-github-folder.ts +199 -0
  51. package/corpus/templates/content/app/i18n-data.ts +154 -1
  52. package/corpus/templates/content/app/lib/github-link-dialog.ts +86 -0
  53. package/corpus/templates/content/changelog/2026-10-18-connected-ai-assistants-can-link-a-new-collection-to-a-github.md +1 -1
  54. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +6 -0
  55. package/corpus/templates/content/changelog/2026-10-21-organization-favorites-include-documents-shared-with-the-cur.md +6 -0
  56. package/corpus/templates/content/docs/product/capabilities/content.source.sync-policy.md +32 -7
  57. package/corpus/templates/content/docs/product/encyclopedia.md +3 -3
  58. package/corpus/templates/content/docs/product/features/content.feature.trust-your-connected-sources.md +2 -2
  59. package/corpus/templates/content/docs/product/roadmap.md +1 -1
  60. package/corpus/templates/content/parity/matrix.md +42 -42
  61. package/corpus/templates/content/parity/matrix.ts +14 -0
  62. package/corpus/templates/content/server/lib/github-source-client.ts +50 -1
  63. package/corpus/templates/content/shared/api.ts +3 -0
  64. package/corpus/templates/content/shared/github-source.ts +68 -0
  65. package/corpus/templates/design/CHANGELOG.md +1 -0
  66. package/corpus/templates/design/actions/connect-assets-mcp.ts +5 -9
  67. package/corpus/templates/design/changelog/2026-10-21-journey-canvases-accept-partial-analytics-samples-when-repla.md +6 -0
  68. package/corpus/templates/design/shared/journey-canvas.ts +6 -4
  69. package/corpus/templates/dispatch/.agents/skills/a2a-protocol/SKILL.md +16 -13
  70. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  71. package/corpus/templates/dispatch/changelog/2026-10-21-customer-ai-credit-questions-route-to-account-health-instead.md +6 -0
  72. package/corpus/templates/factory/server/lib/require-workspace-member.ts +5 -17
  73. package/corpus/templates/mail/server/lib/queued-drafts.ts +9 -4
  74. package/corpus/templates/slides/server/workspace-defaults.ts +12 -15
  75. package/package.json +1 -1
@@ -1,3 +1,8 @@
1
+ import {
2
+ ActionContractError,
3
+ isAgentConnectionRequiredError,
4
+ } from "@agent-native/core/action";
5
+
1
6
  import type { GitHubSourceProblemCode } from "../../shared/github-source.js";
2
7
  import { executeGitHubSourceRequest } from "./provider-api.js";
3
8
 
@@ -201,7 +206,19 @@ async function githubGet(
201
206
  if (target.signal && (target.signal.aborted || endedBySignal(error))) {
202
207
  throw new GitHubSourceDeadlineError();
203
208
  }
204
- throw error;
209
+ // A missing connection is the caller's to explain. Anything else the
210
+ // provider client throws, like a dropped connection, means GitHub never
211
+ // answered.
212
+ if (
213
+ isAgentConnectionRequiredError(error) ||
214
+ error instanceof ActionContractError
215
+ ) {
216
+ throw error;
217
+ }
218
+ throw Object.assign(
219
+ new GitHubSourceError("unavailable", "GitHub couldn't be reached."),
220
+ { cause: error },
221
+ );
205
222
  }
206
223
  const result = asRecord(raw);
207
224
  const response = asRecord(result.response) as unknown as ProviderResponse;
@@ -301,6 +318,38 @@ async function refineNotFound(
301
318
  throw new GitHubSourceError(inside, message, 404);
302
319
  }
303
320
 
321
+ /** How long the connection check waits for GitHub before giving up. */
322
+ const CONNECTION_CHECK_MS = 5_000;
323
+
324
+ /**
325
+ * Whether the person has a GitHub connection Content can read through. It
326
+ * asks for the rate limit, which reads no repository and costs none of it.
327
+ * Null means GitHub couldn't be asked, so the caller shouldn't say either way.
328
+ */
329
+ export async function readGitHubConnected(
330
+ connectionId?: string | null,
331
+ ): Promise<boolean | null> {
332
+ try {
333
+ const raw = await executeGitHubSourceRequest({
334
+ provider: "github",
335
+ method: "GET",
336
+ path: "/rate_limit",
337
+ connectionId: connectionId ?? undefined,
338
+ signal: AbortSignal.timeout(CONNECTION_CHECK_MS),
339
+ });
340
+ const status = asRecord(asRecord(raw).response).status;
341
+ if (typeof status !== "number") return null;
342
+ return status !== 401;
343
+ } catch (error) {
344
+ if (isAgentConnectionRequiredError(error)) return false;
345
+ console.warn(
346
+ "[github-source] connection check failed:",
347
+ error instanceof Error ? error.message : String(error),
348
+ );
349
+ return null;
350
+ }
351
+ }
352
+
304
353
  /** The branch a repository URL without `/tree/` means. */
305
354
  export async function readGitHubDefaultBranch(
306
355
  target: GitHubRepositoryTarget,
@@ -1005,7 +1005,10 @@ export interface ContentGitHubSourceStatus {
1005
1005
  pendingCommitSha: string | null;
1006
1006
  progress: { done: number; total: number } | null;
1007
1007
  files: number;
1008
+ /** Up to 50 of the skipped files, with why each was skipped. */
1008
1009
  skipped: Array<{ path: string; reason: GitHubSourceSkipReason }>;
1010
+ /** How many files were skipped in all. */
1011
+ skippedCount: number;
1009
1012
  /** Images not shown yet because file storage isn't set up. */
1010
1013
  imagesWaiting: number;
1011
1014
  problem: {
@@ -165,6 +165,14 @@ export function githubFolderLabel(location: GitHubFolderLocation): string {
165
165
  return `${location.owner}/${location.repo}${folder ? `/${folder}` : ""}`;
166
166
  }
167
167
 
168
+ /** What people call the folder: its own name, or the repository's at the root. */
169
+ export function githubFolderName(
170
+ location: Pick<GitHubFolderLocation, "repo" | "path">,
171
+ ): string {
172
+ const folder = normalizeGitHubFolderPath(location.path);
173
+ return folder ? folder.slice(folder.lastIndexOf("/") + 1) : location.repo;
174
+ }
175
+
168
176
  /** Joins a folder-relative path back onto the folder for its repository path. */
169
177
  export function githubRepoPath(folder: string, relativePath: string): string {
170
178
  const base = normalizeGitHubFolderPath(folder);
@@ -176,6 +184,8 @@ export type GitHubSourceSkipReason = "mdx" | "too-large" | "not-text";
176
184
  /**
177
185
  * What went wrong reading a linked folder, in terms a status line can explain
178
186
  * and fix: reconnect, regain access, restore the branch or folder, or wait.
187
+ * `unavailable` is GitHub not answering; `sync_failed` is a failure in
188
+ * Content itself, like a database error.
179
189
  */
180
190
  export type GitHubSourceProblemCode =
181
191
  | "connection"
@@ -186,6 +196,7 @@ export type GitHubSourceProblemCode =
186
196
  | "rate_limited"
187
197
  | "too_large"
188
198
  | "unavailable"
199
+ | "sync_failed"
189
200
  | "storage_unavailable";
190
201
 
191
202
  export type GitHubSourceFileClass =
@@ -229,6 +240,31 @@ export type GitHubFieldType = Extract<
229
240
  "text" | "number" | "checkbox" | "date" | "url" | "select" | "multi_select"
230
241
  >;
231
242
 
243
+ /** What a folder would become if linked, from a dry run of the link. */
244
+ export interface GitHubFolderPreview {
245
+ location: GitHubFolderLocation;
246
+ label: string;
247
+ folderUrl: string;
248
+ commitSha: string;
249
+ markdownFiles: number;
250
+ skipped: Array<{ path: string; reason: GitHubSourceSkipReason }>;
251
+ /** Columns the frontmatter would become, with the type each would get. */
252
+ fields: Array<{ key: string; type: GitHubFieldType; examples: string[] }>;
253
+ sample: Array<{ path: string; title: string }>;
254
+ /** How many files the field types were read from. */
255
+ sampledFiles: number;
256
+ /** Image files in the folder, which pages show from Content's file storage. */
257
+ images: number;
258
+ /** Whether file storage is set up to hold those images. */
259
+ storageReady: boolean;
260
+ }
261
+
262
+ /** Why a collection can't be linked: it has pages, a source, or a system role. */
263
+ export type GitHubLinkBlockedReason =
264
+ | "collection-has-rows"
265
+ | "already-linked"
266
+ | "system-collection";
267
+
232
268
  const SELECT_KEY_RE =
233
269
  /^(status|state|stage|category|type|kind|section|level|priority|visibility|difficulty|audience)$/i;
234
270
  const DATE_RE =
@@ -299,6 +335,38 @@ export function inferGitHubFieldType(
299
335
  return "text";
300
336
  }
301
337
 
338
+ const GITHUB_EXAMPLE_LENGTH = 80;
339
+
340
+ /**
341
+ * Up to three example values for a column, as the link preview shows them:
342
+ * blank values and empty lists are skipped, and a long value ends in "…".
343
+ */
344
+ export function githubFieldExamples(values: readonly unknown[]): string[] {
345
+ const examples: string[] = [];
346
+ for (const value of values) {
347
+ if (examples.length === 3) break;
348
+ if (value === null || value === undefined) continue;
349
+ const text = (
350
+ typeof value === "string"
351
+ ? value
352
+ : Array.isArray(value)
353
+ ? value
354
+ .map((entry) =>
355
+ typeof entry === "string" ? entry : JSON.stringify(entry),
356
+ )
357
+ .join(", ")
358
+ : JSON.stringify(value)
359
+ ).trim();
360
+ if (!text) continue;
361
+ examples.push(
362
+ text.length > GITHUB_EXAMPLE_LENGTH
363
+ ? `${text.slice(0, GITHUB_EXAMPLE_LENGTH - 1).trimEnd()}…`
364
+ : text,
365
+ );
366
+ }
367
+ return examples;
368
+ }
369
+
302
370
  /** Whether a frontmatter value can be stored in a column of this type unchanged. */
303
371
  export function fitsGitHubFieldType(
304
372
  type: GitHubFieldType,
@@ -7,6 +7,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
7
7
 
8
8
  ### Fixed
9
9
 
10
+ - Journey canvases accept partial Analytics samples when replay counts are unknown
10
11
  - Design generation now counts file renames and type changes as saved output.
11
12
  - Design reports uncertain generation outcomes clearly and confirms completed file deletions
12
13
  - Generation offers a retry when a completed run saved no design files
@@ -1,5 +1,4 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
- import { getDbExec } from "@agent-native/core/db";
3
2
  import { fetchOrgApps } from "@agent-native/core/mcp";
4
3
  import {
5
4
  addFirstPartyRemoteServer,
@@ -7,7 +6,10 @@ import {
7
6
  listRemoteServers,
8
7
  removeRemoteServer,
9
8
  } from "@agent-native/core/mcp-client";
10
- import { refreshMcpManagerForPrincipal } from "@agent-native/core/server";
9
+ import {
10
+ readOrgMemberRole,
11
+ refreshMcpManagerForPrincipal,
12
+ } from "@agent-native/core/server";
11
13
  import {
12
14
  getRequestOrgId,
13
15
  getRequestUserEmail,
@@ -41,13 +43,7 @@ async function listAssetsServers(orgId: string) {
41
43
  }
42
44
 
43
45
  async function assertCanManageOrgMcp(orgId: string, userEmail: string) {
44
- const result = await getDbExec().execute({
45
- sql: `SELECT role FROM org_members WHERE org_id = $1 AND LOWER(email) = $2 LIMIT 1`,
46
- args: [orgId, userEmail.toLowerCase()],
47
- });
48
- const role = String(
49
- (result.rows[0] as { role?: unknown } | undefined)?.role,
50
- ).toLowerCase();
46
+ const role = await readOrgMemberRole(orgId, userEmail);
51
47
  if (role !== "owner" && role !== "admin") {
52
48
  throw new Error(
53
49
  "Only organization owners and admins can connect org-scoped MCP servers.",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-21
4
+ ---
5
+
6
+ Journey canvases accept partial Analytics samples when replay counts are unknown
@@ -147,7 +147,7 @@ export const journeyExampleSchema = z.object({
147
147
  .describe(
148
148
  "SHA-256 hash used only to compare anonymous identity when available.",
149
149
  ),
150
- recordingId: z.string().max(256).nullable(),
150
+ recordingId: z.string().max(256).nullable().optional(),
151
151
  ts: isoTimestamp,
152
152
  offsetMs: z.number().min(0).nullable(),
153
153
  viewport: z.object({ width: pixels, height: pixels }).nullable(),
@@ -298,7 +298,7 @@ export const journeyTreeSchema = z
298
298
  .describe("Per-app root denominators for separate app-band layouts."),
299
299
  coverage: z.object({
300
300
  sessionsWithEvents: count,
301
- sessionsWithReplay: count,
301
+ sessionsWithReplay: count.nullable(),
302
302
  truncated: z.boolean(),
303
303
  }),
304
304
  nodes: z.array(journeyNodeSchema).min(1).max(MAX_JOURNEY_NODES),
@@ -1406,7 +1406,7 @@ export interface PlannedScreen {
1406
1406
  provenance?: {
1407
1407
  eventAt: string;
1408
1408
  dateLabel: string;
1409
- recordingId: string | null;
1409
+ recordingId?: string | null;
1410
1410
  recordingStartedAt?: string;
1411
1411
  offsetMs: number | null;
1412
1412
  offsetIsObserved: boolean;
@@ -2315,7 +2315,9 @@ export function planJourneyCanvas(
2315
2315
  candidate.caption.evidenceAt !== undefined,
2316
2316
  messages,
2317
2317
  ),
2318
- recordingId: candidateExample.recordingId,
2318
+ ...(candidateExample.recordingId !== undefined
2319
+ ? { recordingId: candidateExample.recordingId }
2320
+ : {}),
2319
2321
  ...(candidate.recordingStartedAt
2320
2322
  ? {
2321
2323
  recordingStartedAt: utcTimestamp(
@@ -144,14 +144,17 @@ The framework supports two credential scopes:
144
144
  | Secret | Scope | How it is set |
145
145
  | --- | --- | --- |
146
146
  | `A2A_SECRET` | User identity across a deployment | env var, **never auto-generated** |
147
- | org `a2a_secret` | Organization principal only | auto-generated on org creation; Team page UI |
147
+ | org `a2a_secret` | Organization principal, with optional current-member delegation | auto-generated on org creation; Team page UI |
148
148
 
149
- An org-secret JWT proves only that the caller holds the organization's shared
150
- credential. Its `sub` or email claims are ignored; do not use it to read
149
+ An ordinary org-secret JWT proves only that the caller holds the organization's
150
+ shared credential. Its `sub` or email claims are ignored; do not use it to read
151
151
  person-owned data, claim a user's task, invoke a user-scoped action, or carry a
152
- human approval. Organization-scoped operations can run with this principal.
153
- Use an `A2A_SECRET`-signed user identity for user-owned operations and
154
- approval continuations. MCP applies the same distinction.
152
+ human approval. A separate `agent-native-org-member-delegation` assertion may
153
+ carry the identity of any current receiver-org member to a caller holding that
154
+ shared secret, but only when the receiver's owner enables the setting, the org
155
+ secret differs from `A2A_SECRET`, and the audience and short expiry verify. The
156
+ receiver checks the live roster, and delegated org actions are always capped at
157
+ `member`. MCP requests remain organization-scoped.
155
158
 
156
159
  Organization IDs are local to each app's database. For calls to a peer app,
157
160
  bind the token to the trusted `org_domain` and the peer's canonical audience;
@@ -161,16 +164,16 @@ ID. An ID-only token is valid only when both sides intentionally share the same
161
164
  local organization ID, such as a credential scoped to one local database.
162
165
 
163
166
  The org secret is managed at **Team page → A2A secret** (owner only), where it
164
- can be revealed, copied, regenerated, and pushed to discovered peers. It is a
165
- fallback for organization-scoped calls, not a fallback user identity.
167
+ can be revealed, copied, regenerated, and pushed to discovered peers. Its
168
+ member-identity delegation setting is owner-only and defaults off.
166
169
 
167
170
  `A2A_SECRET` is the deploy-level path. `workspace-deploy` refuses a production
168
171
  workspace deploy without it and prints the generator command. Peers that must
169
- trust user identity assertions need the *same* value on both sides. Keep it
170
- different from every org `a2a_secret`; a shared value cannot distinguish a
171
- user assertion from an organization credential. An org secret equal to
172
- `A2A_SECRET`, or a user token signed only with the org secret, is verified as an
173
- organization principal and reaches the caller as the typed
172
+ trust deployment-secret user assertions need the *same* value on both sides.
173
+ Keep it different from every org `a2a_secret`; a shared value cannot
174
+ distinguish a deployment user assertion from an organization credential. An
175
+ org secret equal to `A2A_SECRET` is verified as an organization principal and
176
+ reaches the caller as the typed
174
177
  `permanent_precondition` error ("no verified user"): an admin/ops fix, never a
175
178
  retry. Mismatched `A2A_SECRET` values are rejected with `401`
176
179
  (`a2a_auth_rejected`).
@@ -5,6 +5,10 @@ time from the command menu (Cmd+K → "What's new").
5
5
 
6
6
  ## 2026-10-21
7
7
 
8
+ ### Improved
9
+
10
+ - Customer AI credit questions route to account health instead of workspace spend metrics.
11
+
8
12
  ### Fixed
9
13
 
10
14
  - Workspace apps load reliably with organization sign-in, and failures identify the request and status
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-21
4
+ ---
5
+
6
+ Customer AI credit questions route to account health instead of workspace spend metrics.
@@ -1,12 +1,10 @@
1
1
  import type { ActionRunContext } from "@agent-native/core/action";
2
- import { orgMembers, resolveOrgIdForEmail } from "@agent-native/core/org";
2
+ import { resolveOrgIdForEmail } from "@agent-native/core/org";
3
+ import { readOrgMemberRole } from "@agent-native/core/server";
3
4
  import {
4
5
  getRequestOrgId,
5
6
  getRequestUserEmail,
6
7
  } from "@agent-native/core/server/request-context";
7
- import { and, eq, sql } from "drizzle-orm";
8
-
9
- import { getDb } from "../db/index.js";
10
8
 
11
9
  export type WorkspaceMemberIdentity = {
12
10
  userEmail: string;
@@ -38,18 +36,8 @@ export async function requireWorkspaceMember(
38
36
  const orgId = requestOrgId || (await resolveOrgIdForEmail(userEmail));
39
37
  if (!orgId) throw new Error("Active workspace organization required");
40
38
 
41
- const rows = await getDb()
42
- .select({ role: orgMembers.role })
43
- .from(orgMembers)
44
- .where(
45
- and(
46
- eq(orgMembers.orgId, orgId),
47
- eq(sql`lower(${orgMembers.email})`, userEmail),
48
- ),
49
- )
50
- .limit(1);
51
-
52
- if (!rows[0]) throw new Error("Workspace membership required");
39
+ const role = await readOrgMemberRole(orgId, userEmail);
40
+ if (!role) throw new Error("Workspace membership required");
53
41
 
54
- return { userEmail, orgId, role: rows[0].role };
42
+ return { userEmail, orgId, role };
55
43
  }
@@ -5,6 +5,7 @@ import {
5
5
  getAppProductionUrl,
6
6
  getRequestOrgId,
7
7
  getRequestUserEmail,
8
+ readOrgMemberRole,
8
9
  withConfiguredAppBasePath,
9
10
  } from "@agent-native/core/server";
10
11
  import { getUserSetting } from "@agent-native/core/settings";
@@ -107,9 +108,9 @@ export function buildQueuedDraftUrl(id: string): string {
107
108
  async function getMembership(
108
109
  orgId: string,
109
110
  email: string,
110
- ): Promise<{ email: string; role: string } | null> {
111
+ ): Promise<{ email: string } | null> {
111
112
  const [row] = await getDb()
112
- .select({ email: orgMembers.email, role: orgMembers.role })
113
+ .select({ email: orgMembers.email })
113
114
  .from(orgMembers)
114
115
  .where(
115
116
  and(
@@ -119,7 +120,7 @@ async function getMembership(
119
120
  )
120
121
  .limit(1);
121
122
  if (!row) return null;
122
- return { email: row.email, role: row.role };
123
+ return { email: row.email };
123
124
  }
124
125
 
125
126
  export async function requireQueueContext(): Promise<QueueContext> {
@@ -136,11 +137,15 @@ export async function requireQueueContext(): Promise<QueueContext> {
136
137
  if (!membership) {
137
138
  throw new Error("Only members of this organization can queue drafts.");
138
139
  }
140
+ const role = await readOrgMemberRole(orgId, userEmail);
141
+ if (!role) {
142
+ throw new Error("Only members of this organization can queue drafts.");
143
+ }
139
144
 
140
145
  return {
141
146
  userEmail: normalizeEmail(membership.email),
142
147
  orgId,
143
- role: membership.role,
148
+ role,
144
149
  };
145
150
  }
146
151
 
@@ -1,6 +1,8 @@
1
1
  import { fail } from "@agent-native/core/action";
2
- import { canManageOrg, orgMembers, type OrgRole } from "@agent-native/core/org";
2
+ import { canManageOrg, type OrgRole } from "@agent-native/core/org";
3
+ import { readOrgMemberRole } from "@agent-native/core/server";
3
4
  import {
5
+ getRequestContext,
4
6
  getRequestOrgId,
5
7
  getRequestUserEmail,
6
8
  } from "@agent-native/core/server/request-context";
@@ -41,20 +43,15 @@ export async function getCallerOrgRole(): Promise<OrgRole | null> {
41
43
  const orgId = getRequestOrgId();
42
44
  const email = getRequestUserEmail();
43
45
  if (!orgId || !email) return null;
44
- const rows = await getDb()
45
- .select({ role: orgMembers.role })
46
- .from(orgMembers)
47
- .where(
48
- and(
49
- eq(orgMembers.orgId, orgId),
50
- eq(sql`lower(${orgMembers.email})`, email.toLowerCase()),
51
- ),
52
- )
53
- .limit(1);
54
- const role = rows[0]?.role;
55
- return role === "owner" || role === "admin" || role === "member"
56
- ? role
57
- : null;
46
+ const request = getRequestContext();
47
+ if (
48
+ request?.a2aIdentityAssurance === "org-member" &&
49
+ (request.orgId?.trim() !== orgId.trim() ||
50
+ request.userEmail?.trim().toLowerCase() !== email.trim().toLowerCase())
51
+ ) {
52
+ return null;
53
+ }
54
+ return readOrgMemberRole(orgId, email);
58
55
  }
59
56
 
60
57
  export async function canManageWorkspaceDefaults(): Promise<boolean> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core-corpus",
3
- "version": "0.212.1",
3
+ "version": "0.213.1",
4
4
  "description": "Opt-in source corpus for Agent-Native Core source-search.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {