@vantageos/cloud-identity 0.2.0 → 0.4.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"human-path.d.ts","sourceRoot":"","sources":["../src/human-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,sBAAsB;;;;;EAKjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAMtE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC3C,qBAAqB,CAiCvB"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @vantageos/cloud-identity 0.4.0 — Human-path resolver (Clerk → { tenant,
3
+ * subject, role }).
4
+ *
5
+ * Additive-only: does not import any Clerk SDK, and does not modify any
6
+ * existing export. The caller resolves a Clerk session (or any session
7
+ * provider with an equivalent shape) *outside* this package and passes in a
8
+ * plain, already-resolved object; this module only normalizes it into the
9
+ * package's contract.
10
+ *
11
+ * `role` is a NEW literal union (`owner | admin | member | client`),
12
+ * distinct from `workspaceRoleSchema` (`Admin | Editor | Viewer`) in
13
+ * `./tenancy-domain.ts` — the two are unrelated vocabularies (workspace
14
+ * membership role vs. organization-account role) and are never conflated.
15
+ *
16
+ * Consistent with the package's "a right is presented, never inferred from
17
+ * an absence" contract: a session with no organization refuses (throws),
18
+ * and an unrecognized/absent Clerk org role refuses (throws) rather than
19
+ * silently defaulting to a role.
20
+ */
21
+ import { z } from "zod";
22
+ // ---------------------------------------------------------------------------
23
+ // HumanAccountRole
24
+ // ---------------------------------------------------------------------------
25
+ export const humanAccountRoleSchema = z.enum([
26
+ "owner",
27
+ "admin",
28
+ "member",
29
+ "client",
30
+ ]);
31
+ /**
32
+ * Maps Clerk's own org-role strings (`org:admin`, `org:member`, etc. — and
33
+ * their bare forms `admin`, `member`) onto the package's `HumanAccountRole`
34
+ * union. Deliberately explicit and exhaustive-by-throw: any string not
35
+ * listed here is refused rather than silently coerced to a default, per
36
+ * "presented, never inferred from an absence."
37
+ */
38
+ const CLERK_ORG_ROLE_MAP = {
39
+ "org:owner": "owner",
40
+ owner: "owner",
41
+ "org:admin": "admin",
42
+ admin: "admin",
43
+ "org:member": "member",
44
+ member: "member",
45
+ "org:client": "client",
46
+ client: "client",
47
+ };
48
+ /**
49
+ * @security ⚠️ DECODE/NORMALIZE-ONLY. NOT AUTHENTICATION. NOT VERIFICATION.
50
+ *
51
+ * This function does NOT authenticate a request and does NOT verify
52
+ * anything. It NORMALIZES a session that the caller has ALREADY verified
53
+ * upstream — e.g. via Clerk's own server-side `auth()` in a Next.js/Convex
54
+ * server context — into this package's `{ tenant, subject, role }` contract.
55
+ *
56
+ * Passing an unverified, client-supplied object here (for example
57
+ * `req.body`, a query-string payload, or anything else an attacker
58
+ * controls) makes THAT object the trusted source of `tenant`, `subject`,
59
+ * AND `role` — a privilege-escalation bug. The shape checks below (missing
60
+ * orgId/userId/orgRole) only guard against malformed input; they perform
61
+ * NO signature check, NO issuer check, NO session-validity check.
62
+ *
63
+ * Callers MUST verify the session upstream (Clerk `auth()` server-side, or
64
+ * an equivalent signed-session verifier) BEFORE calling this function. Only
65
+ * pass in the object Clerk itself already verified — never pass through an
66
+ * unverified request body.
67
+ *
68
+ * Resolves an already-verified, framework-agnostic Clerk session object into
69
+ * the package's `{ tenant, subject, role }` contract.
70
+ *
71
+ * - throws when `session` is null/undefined ("Unauthenticated: no
72
+ * session.") — same refusal message class as `requireTenantId`'s
73
+ * session branch, for consistent error handling across both resolvers.
74
+ * - throws when `session.orgId` is missing or empty ("No active
75
+ * organization on this session...") — human path is cloud fail-closed,
76
+ * same invariant as `requireTenantId`.
77
+ * - throws when `session.userId` is missing or empty.
78
+ * - throws when `session.orgRole` is missing, empty, or not a recognized
79
+ * Clerk org role (unknown roles are refused, never defaulted).
80
+ *
81
+ * Named to make the already-verified precondition explicit at the
82
+ * call-site — see CHANGELOG.md 0.4.0 for the naming history — mirroring
83
+ * `decodeUnverifiedBearer`'s equivalent rename in `./tenancy-domain.ts`.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * // session MUST already be verified upstream, e.g.:
88
+ * // const { orgId, userId, orgRole } = await auth(); // Clerk server-side
89
+ * const { tenant, subject, role } = normalizeVerifiedHumanSession({
90
+ * orgId: "org_abc",
91
+ * userId: "user_123",
92
+ * orgRole: "org:admin",
93
+ * });
94
+ * // -> { tenant: "org_abc", subject: "user_123", role: "admin" }
95
+ * ```
96
+ */
97
+ export function normalizeVerifiedHumanSession(session) {
98
+ if (!session) {
99
+ throw new Error("Unauthenticated: no session.");
100
+ }
101
+ const { orgId, userId, orgRole } = session;
102
+ if (typeof orgId !== "string" || orgId.length === 0) {
103
+ throw new Error("No active organization on this session: select an organization before accessing agents.");
104
+ }
105
+ if (typeof userId !== "string" || userId.length === 0) {
106
+ throw new Error("No user id on this session: cannot resolve subject.");
107
+ }
108
+ if (typeof orgRole !== "string" || orgRole.length === 0) {
109
+ throw new Error("No organization role on this session: role must be presented explicitly, never inferred.");
110
+ }
111
+ const mapped = CLERK_ORG_ROLE_MAP[orgRole];
112
+ if (!mapped) {
113
+ throw new Error(`Unrecognized organization role "${orgRole}": expected one of ${Object.keys(CLERK_ORG_ROLE_MAP).join(", ")}.`);
114
+ }
115
+ return { tenant: orgId, subject: userId, role: mapped };
116
+ }
117
+ //# sourceMappingURL=human-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"human-path.js","sourceRoot":"","sources":["../src/human-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3C,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;CACT,CAAC,CAAC;AA6BH;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAqC;IAC3D,WAAW,EAAE,OAAO;IACpB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,OAAO;IACpB,KAAK,EAAE,OAAO;IACd,YAAY,EAAE,QAAQ;IACtB,MAAM,EAAE,QAAQ;IAChB,YAAY,EAAE,QAAQ;IACtB,MAAM,EAAE,QAAQ;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,UAAU,6BAA6B,CAC3C,OAA4C;IAE5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAE3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,mCAAmC,OAAO,sBAAsB,MAAM,CAAC,IAAI,CACzE,kBAAkB,CACnB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1D,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * @vantageos/cloud-identity — public surface.
3
3
  *
4
- * Framework-agnostic identity + scope-filter primitives for VantagePeers Cloud
5
- * (multi-tenant MCP). Consumed by `vantage-peers-mcp` and
6
- * `@vantageos/vantage-crm-mcp`.
4
+ * Framework-agnostic building blocks for multi-tenant authorization:
5
+ * tenant resolution, membership requirement, row filtering and bearer
6
+ * validation.
7
7
  */
8
8
  export { timingSafeEqual } from "./crypto.js";
9
- export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, type ScopeFilterable, } from "./scope-filter.js";
9
+ export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, type ScopeFilterable, } from "./scope-filter.js";
10
10
  export { validateMasterBearer } from "./bearer-validation.js";
11
11
  export type { OAuthCtx, ScopeProfile, NamespacePrefix, FromAllowListEntry, ValidateMasterBearerResult, } from "./types.js";
12
12
  export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantContextSchema, ScopeViolationError, getEffectiveTenantId, decodeUnverifiedBearer, } from "./tenancy-domain.js";
13
13
  export type { Workspace, WorkspaceMember, WorkspaceRole, TenantContext, ScopeViolationPayload, BearerPayload, } from "./tenancy-domain.js";
14
+ export { requireTenantId } from "./org-guard.js";
15
+ export type { SessionIdentity, TenantSource, DeploymentMode, } from "./org-guard.js";
16
+ export { humanAccountRoleSchema, normalizeVerifiedHumanSession } from "./human-path.js";
17
+ export type { HumanAccountRole, ClerkSessionLike, ResolvedHumanIdentity, } from "./human-path.js";
14
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EACV,eAAe,EACf,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AACxF,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -1,13 +1,17 @@
1
1
  /**
2
2
  * @vantageos/cloud-identity — public surface.
3
3
  *
4
- * Framework-agnostic identity + scope-filter primitives for VantagePeers Cloud
5
- * (multi-tenant MCP). Consumed by `vantage-peers-mcp` and
6
- * `@vantageos/vantage-crm-mcp`.
4
+ * Framework-agnostic building blocks for multi-tenant authorization:
5
+ * tenant resolution, membership requirement, row filtering and bearer
6
+ * validation.
7
7
  */
8
8
  export { timingSafeEqual } from "./crypto.js";
9
- export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, } from "./scope-filter.js";
9
+ export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, } from "./scope-filter.js";
10
10
  export { validateMasterBearer } from "./bearer-validation.js";
11
11
  // 0.2.0 — Domain-tenancy layer (additive)
12
12
  export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantContextSchema, ScopeViolationError, getEffectiveTenantId, decodeUnverifiedBearer, } from "./tenancy-domain.js";
13
+ // Organization-membership guard (unified session + bearer + self-host contract)
14
+ export { requireTenantId } from "./org-guard.js";
15
+ // 0.4.0 — Human-path resolver (Clerk-shaped session -> { tenant, subject, role })
16
+ export { humanAccountRoleSchema, normalizeVerifiedHumanSession } from "./human-path.js";
13
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAU7B,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAOjD,kFAAkF;AAClF,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @vantageos/cloud-identity — Organization-membership guard.
3
+ * (See package.json "version" for the release this shipped in.)
4
+ *
5
+ * Closes the second measured 0.2.0 defect: the published package exposed no
6
+ * "must belong to an organization" gate anywhere (`grep -rlE
7
+ * "requireOrg|orgRequired|orgId" package/dist/*.js` returned no matches on
8
+ * the 0.2.0 tarball). Absence of an organization must REFUSE, never fall
9
+ * through to full access — the same defect class as the scope-filter fix in
10
+ * `./scope-filter.ts`.
11
+ *
12
+ * The semantics are reused verbatim from a Convex implementation of the same
13
+ * guard rather than reinvented — a second implementation of one authorization
14
+ * check is a second chance to get it wrong. That original:
15
+ *
16
+ * - takes a Convex `QueryCtx | MutationCtx | ActionCtx`
17
+ * - derives identity via `ctx.auth.getUserIdentity()`
18
+ * - throws when there is no session
19
+ * - throws when `identity.org_id` is not a non-empty string
20
+ * - returns the org id
21
+ *
22
+ * ADAPTATION (named per the brief's requirement to flag any semantic
23
+ * deviation): this package is framework-agnostic and has no `ctx.auth`. The
24
+ * caller pre-resolves whichever identity/bearer object it already has
25
+ * (Clerk session identity for the human path, or the package's own
26
+ * `TenantContext` — see `./tenancy-domain.ts` — for the machine/bearer path)
27
+ * and passes it in as a `TenantSource`. The refuse-on-absence /
28
+ * refuse-on-empty semantics are otherwise identical to the source function;
29
+ * no other behavioural change was made.
30
+ *
31
+ * This is the membership half of the package's contract; tenant resolution,
32
+ * row filtering and identifier validation are covered by
33
+ * `getEffectiveTenantId`, `passesScopeFilter`/`scopeFilterList` and
34
+ * `validateMasterBearer` respectively. Framework adapters wrap
35
+ * `requireTenantId`; they must never reimplement its checks.
36
+ */
37
+ import type { TenantContext } from "./tenancy-domain.js";
38
+ /**
39
+ * Minimal shape of a resolved human-path session identity. Intentionally
40
+ * framework-agnostic: the caller derives this from Clerk (or any other
41
+ * session provider) before calling `requireTenantId` — this package never
42
+ * imports a session SDK.
43
+ */
44
+ export type SessionIdentity = {
45
+ orgId?: string | null;
46
+ };
47
+ /**
48
+ * Named deployment mode — 0.4.0. `cloud` requires an organization/workspace
49
+ * on every request (the existing, unchanged `session`/`bearer` behaviour
50
+ * below). `self-host` is single-tenant: the tenant id is DECLARED via
51
+ * explicit configuration, never inferred from the absence of an
52
+ * organization. See the `self-host` branch of `TenantSource` and
53
+ * `requireTenantId` for the refuse-on-missing-config semantics.
54
+ */
55
+ export type DeploymentMode = "cloud" | "self-host";
56
+ /**
57
+ * Discriminated union covering both VantagePeers Cloud entry paths behind
58
+ * one contract:
59
+ * - `session`: human path, via a connection/login provider (Clerk, etc.).
60
+ * - `bearer`: machine path, via a resolved, ALREADY-VERIFIED token
61
+ * (`TenantContext` — see `decodeUnverifiedBearer` / the planned signed-JWT
62
+ * verifier for how a `TenantContext` gets produced).
63
+ * - `self-host`: single-tenant path (0.4.0, additive). The tenant id is
64
+ * presented explicitly by configuration, not inferred: `requireTenantId`
65
+ * returns `tenantId` only when it is a non-empty string, and throws
66
+ * otherwise — a right is never granted by absence, here either.
67
+ */
68
+ export type TenantSource = {
69
+ kind: "session";
70
+ identity: SessionIdentity | null | undefined;
71
+ } | {
72
+ kind: "bearer";
73
+ context: TenantContext;
74
+ } | {
75
+ kind: "self-host";
76
+ tenantId: string | null | undefined;
77
+ };
78
+ /**
79
+ * Resolve the caller's tenant id, refusing when no organization/workspace is
80
+ * attached — for EITHER entry path, behind one contract.
81
+ *
82
+ * - `kind: "session"` and `identity` is null/undefined → throws
83
+ * ("Unauthenticated: no session.")
84
+ * - `kind: "session"` and `identity.orgId` is missing or empty → throws
85
+ * ("No active organization on this session...")
86
+ * - `kind: "bearer"` and `context.workspaceId` is missing or empty → throws
87
+ * ("No workspace on this bearer-resolved tenant context...")
88
+ * - otherwise → returns the tenant id (orgId, or workspaceId)
89
+ *
90
+ * A right is never granted by absence: every branch above refuses by
91
+ * throwing, there is no fallback branch that returns a value when
92
+ * organization/workspace information is missing.
93
+ */
94
+ export declare function requireTenantId(source: TenantSource): string;
95
+ //# sourceMappingURL=org-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-guard.d.ts","sourceRoot":"","sources":["../src/org-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,WAAW,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CA+B5D"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @vantageos/cloud-identity — Organization-membership guard.
3
+ * (See package.json "version" for the release this shipped in.)
4
+ *
5
+ * Closes the second measured 0.2.0 defect: the published package exposed no
6
+ * "must belong to an organization" gate anywhere (`grep -rlE
7
+ * "requireOrg|orgRequired|orgId" package/dist/*.js` returned no matches on
8
+ * the 0.2.0 tarball). Absence of an organization must REFUSE, never fall
9
+ * through to full access — the same defect class as the scope-filter fix in
10
+ * `./scope-filter.ts`.
11
+ *
12
+ * The semantics are reused verbatim from a Convex implementation of the same
13
+ * guard rather than reinvented — a second implementation of one authorization
14
+ * check is a second chance to get it wrong. That original:
15
+ *
16
+ * - takes a Convex `QueryCtx | MutationCtx | ActionCtx`
17
+ * - derives identity via `ctx.auth.getUserIdentity()`
18
+ * - throws when there is no session
19
+ * - throws when `identity.org_id` is not a non-empty string
20
+ * - returns the org id
21
+ *
22
+ * ADAPTATION (named per the brief's requirement to flag any semantic
23
+ * deviation): this package is framework-agnostic and has no `ctx.auth`. The
24
+ * caller pre-resolves whichever identity/bearer object it already has
25
+ * (Clerk session identity for the human path, or the package's own
26
+ * `TenantContext` — see `./tenancy-domain.ts` — for the machine/bearer path)
27
+ * and passes it in as a `TenantSource`. The refuse-on-absence /
28
+ * refuse-on-empty semantics are otherwise identical to the source function;
29
+ * no other behavioural change was made.
30
+ *
31
+ * This is the membership half of the package's contract; tenant resolution,
32
+ * row filtering and identifier validation are covered by
33
+ * `getEffectiveTenantId`, `passesScopeFilter`/`scopeFilterList` and
34
+ * `validateMasterBearer` respectively. Framework adapters wrap
35
+ * `requireTenantId`; they must never reimplement its checks.
36
+ */
37
+ /**
38
+ * Resolve the caller's tenant id, refusing when no organization/workspace is
39
+ * attached — for EITHER entry path, behind one contract.
40
+ *
41
+ * - `kind: "session"` and `identity` is null/undefined → throws
42
+ * ("Unauthenticated: no session.")
43
+ * - `kind: "session"` and `identity.orgId` is missing or empty → throws
44
+ * ("No active organization on this session...")
45
+ * - `kind: "bearer"` and `context.workspaceId` is missing or empty → throws
46
+ * ("No workspace on this bearer-resolved tenant context...")
47
+ * - otherwise → returns the tenant id (orgId, or workspaceId)
48
+ *
49
+ * A right is never granted by absence: every branch above refuses by
50
+ * throwing, there is no fallback branch that returns a value when
51
+ * organization/workspace information is missing.
52
+ */
53
+ export function requireTenantId(source) {
54
+ if (source.kind === "session") {
55
+ if (!source.identity) {
56
+ throw new Error("Unauthenticated: no session.");
57
+ }
58
+ const orgId = source.identity.orgId;
59
+ if (typeof orgId !== "string" || orgId.length === 0) {
60
+ throw new Error("No active organization on this session: select an organization before accessing agents.");
61
+ }
62
+ return orgId;
63
+ }
64
+ if (source.kind === "self-host") {
65
+ const tenantId = source.tenantId;
66
+ if (typeof tenantId !== "string" || tenantId.length === 0) {
67
+ throw new Error("No tenant id configured for self-host mode: declare a tenantId explicitly — it is never inferred from absence.");
68
+ }
69
+ return tenantId;
70
+ }
71
+ const workspaceId = source.context?.workspaceId;
72
+ if (typeof workspaceId !== "string" || workspaceId.length === 0) {
73
+ throw new Error("No workspace on this bearer-resolved tenant context: the bearer must resolve to a non-empty workspaceId.");
74
+ }
75
+ return workspaceId;
76
+ }
77
+ //# sourceMappingURL=org-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-guard.js","sourceRoot":"","sources":["../src/org-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAyCH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,MAAoB;IAClD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACjC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,gHAAgH,CACjH,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC;IAChD,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -1,18 +1,24 @@
1
1
  /**
2
- * Sprint S3.1 B2 — scope-aware filter helpers for VP MCP tools.
2
+ * Scope-aware row filter.
3
3
  *
4
- * Ported from `vantage-memory/mcp-server/src/scope-filter.ts`. Behaviour is
5
- * preserved verbatim; only the type dependency on the in-tree `OAuthContext`
6
- * is replaced by the framework-agnostic `OAuthCtx` from `./types`.
4
+ * The behaviour was preserved verbatim from an earlier in-tree
5
+ * implementation; the only change was replacing a framework-specific context
6
+ * type with the framework-agnostic `OAuthCtx` from `./types`.
7
7
  *
8
- * Doctrine references:
9
- * - decisions/doctrine-scope-aware-filter-2026-05-26.md (D3 base)
10
- * - memory j579y6f31g7xzgtgdnpgetdmjx87ztyj (D9-D14 extension)
11
- *
12
- * Contract:
8
+ * Contract (0.3.0 — BREAKING vs. 0.2.0):
9
+ * - `oauthCtx` is now MANDATORY. A right is never granted by absence: the
10
+ * 0.2.0 behaviour of `oauthCtx === undefined` silently passing everything
11
+ * (as if the caller were master-scoped) was the exact defect class this
12
+ * release closes. Every exported function below now requires `OAuthCtx`
13
+ * at the type level — an omitted argument breaks the caller's build,
14
+ * it never falls through to a more permissive runtime path.
15
+ * - Callers that genuinely need the old legacy-bearer wildcard behaviour
16
+ * MUST import and pass `LEGACY_WILDCARD_CTX` explicitly — the compat
17
+ * path is requested by name, never inferred from a missing argument.
18
+ * - As defense-in-depth against callers that bypass the type system
19
+ * (untyped JS, `as any`), every function also throws at runtime if
20
+ * `oauthCtx` is nullish, instead of silently granting access.
13
21
  * - Master scope (scope === "master" OR fromAllowList includes "*") = wildcard pass.
14
- * - Legacy bearer (oauthCtx === undefined) = wildcard pass (treated as master-
15
- * equivalent for backward-compatibility with mcpTenants Pi/Tau/Phi paths).
16
22
  * - Non-master scope = row passes iff:
17
23
  * row.createdBy ∈ oauthCtx.fromAllowList
18
24
  * OR
@@ -24,6 +30,12 @@
24
30
  * imports — to keep the helpers trivially unit-testable.
25
31
  */
26
32
  import type { OAuthCtx } from "./types.js";
33
+ /**
34
+ * Explicit legacy-compat sentinel. Passing this constant reproduces the
35
+ * pre-0.3.0 "no oauthCtx" wildcard behaviour — but the caller must import
36
+ * and pass it BY NAME. It is never inferred from an omitted argument.
37
+ */
38
+ export declare const LEGACY_WILDCARD_CTX: OAuthCtx;
27
39
  /**
28
40
  * Row shape accepted by the scope filter. All fields optional because real
29
41
  * Convex documents from list_peers / list_messages / etc. don't all carry both.
@@ -37,35 +49,38 @@ export type ScopeFilterable = {
37
49
  * Mirrors `isMasterScope` from the source module: `scope === "master"` OR
38
50
  * `fromAllowList` contains the `"*"` sentinel.
39
51
  */
40
- export declare function isMasterScope(ctx: OAuthCtx | undefined): boolean;
52
+ export declare function isMasterScope(ctx: OAuthCtx): boolean;
41
53
  /**
42
54
  * Core predicate. Returns true when the row is visible to the caller.
43
55
  *
44
56
  * - Master scope (isMasterScope === true) → true (wildcard)
45
- * - Legacy bearer (oauthCtx === undefined) → true (back-compat)
46
57
  * - row.createdBy ∈ oauthCtx.fromAllowList → true
47
58
  * - row.namespace === prefix OR startsWith prefix+'/'
48
59
  * for any prefix ∈ oauthCtx.namespaceReadPrefixes → true
49
60
  * - otherwise → false
50
61
  *
62
+ * `oauthCtx` is mandatory (see module-level contract note above): an omitted
63
+ * argument throws instead of silently granting wildcard access. Callers that
64
+ * need the old legacy-bearer wildcard behaviour must pass `LEGACY_WILDCARD_CTX`
65
+ * explicitly.
66
+ *
51
67
  * Substring matches that don't fall on a '/' boundary are explicitly rejected
52
68
  * (e.g. namespace="orchestrator/alphabet" does NOT match prefix
53
69
  * "orchestrator/alpha"). This avoids the classic prefix-isolation bypass.
54
70
  */
55
- export declare function passesScopeFilter(oauthCtx: OAuthCtx | undefined, row: ScopeFilterable): boolean;
71
+ export declare function passesScopeFilter(oauthCtx: OAuthCtx, row: ScopeFilterable): boolean;
56
72
  /**
57
73
  * Filter a list of rows (post-query). Used by list_* tools.
58
74
  */
59
- export declare function scopeFilterList<T extends ScopeFilterable>(oauthCtx: OAuthCtx | undefined, rows: T[]): T[];
75
+ export declare function scopeFilterList<T extends ScopeFilterable>(oauthCtx: OAuthCtx, rows: T[]): T[];
60
76
  /**
61
77
  * Assert a single row passes (get_* tools). Returns the row when allowed, null
62
78
  * otherwise — callers translate null to a 404-equivalent "not found" MCP error
63
79
  * to avoid leaking the difference between "absent" and "filtered out".
64
80
  */
65
- export declare function scopeFilterGet<T extends ScopeFilterable>(oauthCtx: OAuthCtx | undefined, row: T | null | undefined): T | null;
81
+ export declare function scopeFilterGet<T extends ScopeFilterable>(oauthCtx: OAuthCtx, row: T | null | undefined): T | null;
66
82
  /**
67
83
  * Helper for callers that want a single "is master / legacy" predicate.
68
- * Mirrors the legacy-bearer-passes-through convention.
69
84
  */
70
- export declare function isWildcardScope(ctx: OAuthCtx | undefined): boolean;
85
+ export declare function isWildcardScope(ctx: OAuthCtx): boolean;
71
86
  //# sourceMappingURL=scope-filter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-filter.d.ts","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAIhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,GAAG,EAAE,eAAe,GACnB,OAAO,CAYT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,IAAI,EAAE,CAAC,EAAE,GACR,CAAC,EAAE,CAEL;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,eAAe,EACtD,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,GAAG,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACxB,CAAC,GAAG,IAAI,CAGV;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAGlE"}
1
+ {"version":3,"file":"scope-filter.d.ts","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAKjC,CAAC;AAWF;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,eAAe,GACnB,OAAO,CAYT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,EAAE,GACR,CAAC,EAAE,CAGL;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,eAAe,EACtD,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACxB,CAAC,GAAG,IAAI,CAIV;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAGtD"}
@@ -1,18 +1,24 @@
1
1
  /**
2
- * Sprint S3.1 B2 — scope-aware filter helpers for VP MCP tools.
2
+ * Scope-aware row filter.
3
3
  *
4
- * Ported from `vantage-memory/mcp-server/src/scope-filter.ts`. Behaviour is
5
- * preserved verbatim; only the type dependency on the in-tree `OAuthContext`
6
- * is replaced by the framework-agnostic `OAuthCtx` from `./types`.
4
+ * The behaviour was preserved verbatim from an earlier in-tree
5
+ * implementation; the only change was replacing a framework-specific context
6
+ * type with the framework-agnostic `OAuthCtx` from `./types`.
7
7
  *
8
- * Doctrine references:
9
- * - decisions/doctrine-scope-aware-filter-2026-05-26.md (D3 base)
10
- * - memory j579y6f31g7xzgtgdnpgetdmjx87ztyj (D9-D14 extension)
11
- *
12
- * Contract:
8
+ * Contract (0.3.0 — BREAKING vs. 0.2.0):
9
+ * - `oauthCtx` is now MANDATORY. A right is never granted by absence: the
10
+ * 0.2.0 behaviour of `oauthCtx === undefined` silently passing everything
11
+ * (as if the caller were master-scoped) was the exact defect class this
12
+ * release closes. Every exported function below now requires `OAuthCtx`
13
+ * at the type level — an omitted argument breaks the caller's build,
14
+ * it never falls through to a more permissive runtime path.
15
+ * - Callers that genuinely need the old legacy-bearer wildcard behaviour
16
+ * MUST import and pass `LEGACY_WILDCARD_CTX` explicitly — the compat
17
+ * path is requested by name, never inferred from a missing argument.
18
+ * - As defense-in-depth against callers that bypass the type system
19
+ * (untyped JS, `as any`), every function also throws at runtime if
20
+ * `oauthCtx` is nullish, instead of silently granting access.
13
21
  * - Master scope (scope === "master" OR fromAllowList includes "*") = wildcard pass.
14
- * - Legacy bearer (oauthCtx === undefined) = wildcard pass (treated as master-
15
- * equivalent for backward-compatibility with mcpTenants Pi/Tau/Phi paths).
16
22
  * - Non-master scope = row passes iff:
17
23
  * row.createdBy ∈ oauthCtx.fromAllowList
18
24
  * OR
@@ -23,14 +29,30 @@
23
29
  * NOTE: this module is intentionally framework-agnostic — no McpServer / Hono
24
30
  * imports — to keep the helpers trivially unit-testable.
25
31
  */
32
+ /**
33
+ * Explicit legacy-compat sentinel. Passing this constant reproduces the
34
+ * pre-0.3.0 "no oauthCtx" wildcard behaviour — but the caller must import
35
+ * and pass it BY NAME. It is never inferred from an omitted argument.
36
+ */
37
+ export const LEGACY_WILDCARD_CTX = {
38
+ scope: "master",
39
+ fromAllowList: ["*"],
40
+ namespaceReadPrefixes: [],
41
+ namespaceWritePrefixes: [],
42
+ };
43
+ function assertOauthCtx(oauthCtx) {
44
+ if (oauthCtx == null) {
45
+ throw new TypeError("oauthCtx is required — pass an explicit OAuthCtx, or LEGACY_WILDCARD_CTX " +
46
+ "if you deliberately want the pre-0.3.0 legacy-bearer wildcard behaviour.");
47
+ }
48
+ }
26
49
  /**
27
50
  * Returns true when the scope profile grants full, wildcard access.
28
51
  * Mirrors `isMasterScope` from the source module: `scope === "master"` OR
29
52
  * `fromAllowList` contains the `"*"` sentinel.
30
53
  */
31
54
  export function isMasterScope(ctx) {
32
- if (!ctx)
33
- return false;
55
+ assertOauthCtx(ctx);
34
56
  if (ctx.scope === "master")
35
57
  return true;
36
58
  return ctx.fromAllowList.includes("*");
@@ -39,19 +61,22 @@ export function isMasterScope(ctx) {
39
61
  * Core predicate. Returns true when the row is visible to the caller.
40
62
  *
41
63
  * - Master scope (isMasterScope === true) → true (wildcard)
42
- * - Legacy bearer (oauthCtx === undefined) → true (back-compat)
43
64
  * - row.createdBy ∈ oauthCtx.fromAllowList → true
44
65
  * - row.namespace === prefix OR startsWith prefix+'/'
45
66
  * for any prefix ∈ oauthCtx.namespaceReadPrefixes → true
46
67
  * - otherwise → false
47
68
  *
69
+ * `oauthCtx` is mandatory (see module-level contract note above): an omitted
70
+ * argument throws instead of silently granting wildcard access. Callers that
71
+ * need the old legacy-bearer wildcard behaviour must pass `LEGACY_WILDCARD_CTX`
72
+ * explicitly.
73
+ *
48
74
  * Substring matches that don't fall on a '/' boundary are explicitly rejected
49
75
  * (e.g. namespace="orchestrator/alphabet" does NOT match prefix
50
76
  * "orchestrator/alpha"). This avoids the classic prefix-isolation bypass.
51
77
  */
52
78
  export function passesScopeFilter(oauthCtx, row) {
53
- if (!oauthCtx)
54
- return true;
79
+ assertOauthCtx(oauthCtx);
55
80
  if (isMasterScope(oauthCtx))
56
81
  return true;
57
82
  const { createdBy, namespace } = row;
@@ -71,6 +96,7 @@ export function passesScopeFilter(oauthCtx, row) {
71
96
  * Filter a list of rows (post-query). Used by list_* tools.
72
97
  */
73
98
  export function scopeFilterList(oauthCtx, rows) {
99
+ assertOauthCtx(oauthCtx);
74
100
  return rows.filter((r) => passesScopeFilter(oauthCtx, r));
75
101
  }
76
102
  /**
@@ -79,17 +105,16 @@ export function scopeFilterList(oauthCtx, rows) {
79
105
  * to avoid leaking the difference between "absent" and "filtered out".
80
106
  */
81
107
  export function scopeFilterGet(oauthCtx, row) {
108
+ assertOauthCtx(oauthCtx);
82
109
  if (row == null)
83
110
  return null;
84
111
  return passesScopeFilter(oauthCtx, row) ? row : null;
85
112
  }
86
113
  /**
87
114
  * Helper for callers that want a single "is master / legacy" predicate.
88
- * Mirrors the legacy-bearer-passes-through convention.
89
115
  */
90
116
  export function isWildcardScope(ctx) {
91
- if (!ctx)
92
- return true;
117
+ assertOauthCtx(ctx);
93
118
  return isMasterScope(ctx);
94
119
  }
95
120
  //# sourceMappingURL=scope-filter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-filter.js","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAaH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAyB;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAA8B,EAC9B,GAAoB;IAEpB,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,aAAa,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC;IACrC,IAAI,SAAS,IAAI,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,qBAAqB,EAAE,CAAC;YAC/C,IAAI,SAAS,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACjC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,QAA8B,EAC9B,IAAS;IAET,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA8B,EAC9B,GAAyB;IAEzB,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,iBAAiB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,GAAyB;IACvD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"scope-filter.js","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAa;IAC3C,KAAK,EAAE,QAAQ;IACf,aAAa,EAAE,CAAC,GAAG,CAAC;IACpB,qBAAqB,EAAE,EAAE;IACzB,sBAAsB,EAAE,EAAE;CAC3B,CAAC;AAEF,SAAS,cAAc,CAAC,QAAkB;IACxC,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,SAAS,CACjB,2EAA2E;YACzE,0EAA0E,CAC7E,CAAC;IACJ,CAAC;AACH,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAa;IACzC,cAAc,CAAC,GAAG,CAAC,CAAC;IACpB,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAkB,EAClB,GAAoB;IAEpB,cAAc,CAAC,QAAQ,CAAC,CAAC;IACzB,IAAI,aAAa,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC;IACrC,IAAI,SAAS,IAAI,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,qBAAqB,EAAE,CAAC;YAC/C,IAAI,SAAS,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACjC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAkB,EAClB,IAAS;IAET,cAAc,CAAC,QAAQ,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAkB,EAClB,GAAyB;IAEzB,cAAc,CAAC,QAAQ,CAAC,CAAC;IACzB,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,iBAAiB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAa;IAC3C,cAAc,CAAC,GAAG,CAAC,CAAC;IACpB,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC"}
@@ -12,12 +12,8 @@
12
12
  * - getEffectiveTenantId(ctx, args) — multi-tenant isolation guard
13
13
  * - decodeUnverifiedBearer(token) — DECODE-ONLY helper, NOT authentication.
14
14
  * The decoded payload is attacker-controlled. Production code MUST use a
15
- * signed JWT or opaque-token lookup (planned: 0.3.0) before treating the
16
- * payload as a trust boundary.
17
- *
18
- * Task: k177hejb4tc5em5p70m9hxwn3x88kyp4
19
- * Mission: k57b4t2q VR Cloud MVP Day 100
20
- * Orchestrator: Sigma — VantagePeers | 2026-06-13
15
+ * signed JWT or opaque-token lookup before treating the payload as a
16
+ * trust boundary.
21
17
  */
22
18
  import { z } from "zod";
23
19
  export declare const workspaceRoleSchema: z.ZodEnum<{
@@ -110,8 +106,8 @@ export declare function getEffectiveTenantId(ctx: TenantContext, args: {
110
106
  * middleware (the production trust boundary)
111
107
  *
112
108
  * For the production trust boundary, callers MUST substitute a signed JWT
113
- * (planned export in 0.3.0) or an opaque-token lookup against a Convex
114
- * tenancy table.
109
+ * verifier (not yet exported by this package) or an opaque-token lookup
110
+ * against a Convex tenancy table.
115
111
  *
116
112
  * Renamed from `resolveBearer` (0.2.0 pre-release) to make the lack of
117
113
  * verification impossible to miss at the call-site.
@@ -1 +1 @@
1
- {"version":3,"file":"tenancy-domain.d.ts","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,mBAAmB;;;;EAAwC,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,eAAO,MAAM,eAAe;;;;;iBAK1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAMxD,eAAO,MAAM,qBAAqB;;;;;;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE,eAAO,MAAM,mBAAmB;;;;;;;;iBAI9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;IAC3C,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAE5B,OAAO,EAAE,qBAAqB;CAS3C;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CASR;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,QAAA,MAAM,mBAAmB;;;;;;;;iBAIvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC,CAmBxB"}
1
+ {"version":3,"file":"tenancy-domain.d.ts","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,mBAAmB;;;;EAAwC,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,eAAO,MAAM,eAAe;;;;;iBAK1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAMxD,eAAO,MAAM,qBAAqB;;;;;;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE,eAAO,MAAM,mBAAmB;;;;;;;;iBAI9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;IAC3C,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAE5B,OAAO,EAAE,qBAAqB;CAS3C;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CASR;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,QAAA,MAAM,mBAAmB;;;;;;;;iBAIvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC,CAmBxB"}