Skip to content

feat: disallow reviewing proposals not created by Cosmo#2218

Merged
wilsonrivera merged 16 commits intomainfrom
wilson/eng-7878-if-a-proposal-is-originating-from-the-hub-cosmo-should-not
Oct 10, 2025
Merged

feat: disallow reviewing proposals not created by Cosmo#2218
wilsonrivera merged 16 commits intomainfrom
wilson/eng-7878-if-a-proposal-is-originating-from-the-hub-cosmo-should-not

Conversation

@wilsonrivera
Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera commented Sep 16, 2025

Summary by CodeRabbit

  • New Features

    • Proposals now include an Origin (INTERNAL/EXTERNAL) across API surfaces, CLI, protobufs, DB, and UI; create requests accept an origin (defaults to INTERNAL).
  • Behavior Changes

    • In-app updates are gated by origin: proposals created externally (hub) cannot be updated; origin is inferred from client/user-agent.
  • Chores

    • DB migration adds proposal_origin enum/column (default INTERNAL) and supporting indexes.
  • Tests

    • Tests updated/added to cover origin propagation and update restrictions for external proposals.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 16, 2025

Walkthrough

Adds a ProposalOrigin enum and origin field across protobufs, DB, backend services, CLI, UI, and tests; enforces origin-based gating on proposal updates; adds DB migration and conversion helpers; includes minor Python calculator method/signature and variable name changes.

Changes

Cohort / File(s) Summary
Protobuf definitions
proto/wg/cosmo/platform/v1/platform.proto, connect/src/wg/cosmo/platform/v1/platform_pb.ts
Added enum ProposalOrigin (INTERNAL, EXTERNAL) and origin field to Proposal and CreateProposalRequest; updated generated TS descriptors.
CLI proposal create
cli/src/commands/proposal/commands/create.ts
Imported ProposalOrigin and sets origin: ProposalOrigin.INTERNAL on create payloads.
Controlplane bufservices
controlplane/src/core/bufservices/proposal/createProposal.ts, controlplane/src/core/bufservices/proposal/getProposal.ts, controlplane/src/core/bufservices/proposal/getProposalsByFederatedGraph.ts, controlplane/src/core/bufservices/proposal/updateProposal.ts
Create writes origin via converter; read paths populate response origin via converter; update enforces origin check (derived from user-agent/x-cosmo-client) and returns ERR_NOT_FOUND on mismatch.
Repository / types / util
controlplane/src/core/repositories/ProposalRepository.ts, controlplane/src/types/index.ts, controlplane/src/core/util.ts, controlplane/src/db/models.ts
Repository API extended to accept/persist origin; ProposalDTO includes origin; added toProposalOriginEnum / fromProposalOriginEnum converters; exported ProposalOrigin type.
DB schema & migrations
controlplane/src/db/schema.ts, controlplane/migrations/0132_slippery_payback.sql, controlplane/migrations/meta/_journal.json
Added proposal_origin enum and proposals.origin column NOT NULL DEFAULT 'INTERNAL'; migration SQL and journal entry added; added indexes on createdById and federatedGraphId.
Studio UI
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx
Surfaced proposal.origin; gated “Review Changes” UI to enabled only for INTERNAL, otherwise render disabled/tooltip state; imported ProposalOrigin and buttonVariants.
Tests & test utilities
controlplane/test/proposal/create-proposal.test.ts, controlplane/test/proposal/update-proposal.test.ts, controlplane/test/test-util.ts
Tests updated to pass origin: ProposalOrigin.INTERNAL by default; added test for failing update of hub-origin proposals; test interceptor maps x-cosmo-client (cosmo-cli/cosmo-hub) into user-agent.
Misc — Python calculator
src/calculator.py
Added coderabbit_add method; changed coderabbit_formula(x, y)coderabbit_formula(x, y, z); renamed old_global_varnew_global_var.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and concisely describes the primary feature introduced in this pull request, namely preventing review actions on proposals not created by Cosmo using the new origin field. It follows the Conventional Commits style and clearly conveys the main change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Sep 16, 2025

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-6bf918cd6ef40d67c40418d18e4488e979692d12-nonroot

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
controlplane/src/core/bufservices/proposal/createProposal.ts (1)

347-353: Persisting origin on create.

Looks good. Minor: make the mapping exhaustive to surface unknown enum values rather than defaulting silently to COSMO.

Example improvement (in util.ts):

export function toProposalOriginEnum(value: ProposalOriginPB): ProposalOriginDB {
  switch (value) {
    case ProposalOriginPB.HUB:
      return 'HUB';
    case ProposalOriginPB.COSMO:
      return 'COSMO';
    default:
      // optionally log/metrics here
      return 'COSMO';
  }
}
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (1)

304-404: Gate “Review Changes” to COSMO‑origin drafts.

Matches the requirement to disallow reviewing non‑Cosmo proposals. Consider a small UX hint when hidden, so users know why actions aren’t available.

Minimal UI hint example:

-          <div className="flex flex-1 items-center justify-end gap-1">
-            {state === "DRAFT" && origin === ProposalOrigin.COSMO && (
+          <div className="flex flex-1 items-center justify-end gap-1">
+            {state === "DRAFT" && origin === ProposalOrigin.COSMO ? (
               <DropdownMenu>
                 ...
               </DropdownMenu>
-            )}
+            ) : state === "DRAFT" ? (
+              <Badge variant="outline" title="Review is only available for Cosmo-origin proposals">
+                Origin: {origin === ProposalOrigin.HUB ? "Hub" : "Cosmo"}
+              </Badge>
+            ) : null}
           </div>
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

154-173: Fix typo in error message; consider status code.

Message has a double “be”. Also consider ERR_FORBIDDEN if available; NOT_FOUND is acceptable if you’re intentionally hiding existence.

Apply this diff to fix the typo:

-          details: `The proposal ${req.proposalName} cannot be be updated directly.`,
+          details: `The proposal ${req.proposalName} cannot be updated directly.`,

Optional: add a debug/info log when blocking to aid supportability.

proto/wg/cosmo/platform/v1/platform.proto (1)

2690-2691: Additive field looks correct; add a doc comment for clarity.

Proto3 defaults enum fields to 0, so this will read as COSMO when not set. Consider adding a brief comment documenting that behavior.

Apply this diff to document intent:

-  ProposalOrigin origin = 11;
+  // Origin of this proposal. Defaults to COSMO when unset (proto3 enum default = 0).
+  ProposalOrigin origin = 11;
controlplane/src/db/schema.ts (1)

2466-2468: New indexes LGTM.

Helpful for common lookups by createdById and federatedGraphId.

If you’ll frequently filter by origin, consider a small composite index like (federatedGraphId, origin).

controlplane/src/core/util.ts (2)

658-667: Simplify mapping via lookup tables (keeps behavior identical).

Functionally fine; a small table-based mapper trims boilerplate while preserving the COSMO fallback.

Apply this diff:

-export function toProposalOriginEnum(value: ProposalOrigin): ProposalOriginEnum {
-  switch (value) {
-    case ProposalOrigin.HUB: {
-      return 'HUB';
-    }
-    default: {
-      return 'COSMO';
-    }
-  }
-}
+const toDbOriginMap: Record<ProposalOrigin, ProposalOriginEnum> = {
+  [ProposalOrigin.COSMO]: 'COSMO',
+  [ProposalOrigin.HUB]: 'HUB',
+};
+export function toProposalOriginEnum(value: ProposalOrigin): ProposalOriginEnum {
+  return toDbOriginMap[value] ?? 'COSMO';
+}

669-678: Mirror the above simplification for reverse mapping.

Keeps forward compatibility fallback to COSMO.

Apply this diff:

-export function fromProposalOriginEnum(value: ProposalOriginEnum): ProposalOrigin {
-  switch (value) {
-    case 'HUB': {
-      return ProposalOrigin.HUB;
-    }
-    default: {
-      return ProposalOrigin.COSMO;
-    }
-  }
-}
+const fromDbOriginMap: Record<ProposalOriginEnum, ProposalOrigin> = {
+  COSMO: ProposalOrigin.COSMO,
+  HUB: ProposalOrigin.HUB,
+};
+export function fromProposalOriginEnum(value: ProposalOriginEnum): ProposalOrigin {
+  return fromDbOriginMap[value] ?? ProposalOrigin.COSMO;
+}

Add a quick unit test to assert both directions for COSMO/HUB and default fallback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea7120b and fbc9711.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (15)
  • cli/src/commands/proposal/commands/create.ts (2 hunks)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (5 hunks)
  • controlplane/migrations/0132_nappy_hardball.sql (1 hunks)
  • controlplane/migrations/meta/_journal.json (1 hunks)
  • controlplane/src/core/bufservices/proposal/createProposal.ts (2 hunks)
  • controlplane/src/core/bufservices/proposal/getProposal.ts (2 hunks)
  • controlplane/src/core/bufservices/proposal/getProposalsByFederatedGraph.ts (2 hunks)
  • controlplane/src/core/bufservices/proposal/updateProposal.ts (1 hunks)
  • controlplane/src/core/repositories/ProposalRepository.ts (11 hunks)
  • controlplane/src/core/util.ts (2 hunks)
  • controlplane/src/db/models.ts (2 hunks)
  • controlplane/src/db/schema.ts (2 hunks)
  • controlplane/src/types/index.ts (2 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (3 hunks)
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T11:15:52.157Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/ProposalRepository.ts:562-572
Timestamp: 2025-09-10T11:15:52.157Z
Learning: The getLatestCheckForProposal function in controlplane/src/core/repositories/ProposalRepository.ts is only called during proposal creation or updates, so proposal match error checking (hasProposalMatchError) is not needed since the proposal is being modified itself rather than being matched against.

Applied to files:

  • controlplane/src/core/bufservices/proposal/updateProposal.ts
🧬 Code graph analysis (11)
controlplane/src/types/index.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)
connect-go/gen/proto/wg/cosmo/common/common.pb.go (4)
  • EnumStatusCode (23-23)
  • EnumStatusCode (103-105)
  • EnumStatusCode (107-109)
  • EnumStatusCode (116-118)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
cli/src/commands/proposal/commands/create.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/src/db/models.ts (2)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/src/db/schema.ts (1)
  • proposalOriginEnum (2445-2445)
controlplane/src/core/bufservices/proposal/getProposal.ts (1)
controlplane/src/core/util.ts (1)
  • fromProposalOriginEnum (669-678)
controlplane/src/core/bufservices/proposal/getProposalsByFederatedGraph.ts (1)
controlplane/src/core/util.ts (1)
  • fromProposalOriginEnum (669-678)
controlplane/src/core/util.ts (1)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
controlplane/src/core/bufservices/proposal/createProposal.ts (1)
controlplane/src/core/util.ts (1)
  • toProposalOriginEnum (658-667)
controlplane/src/core/repositories/ProposalRepository.ts (1)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect/src/wg/cosmo/platform/v1/platform_pb.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: build-router
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: integration_test (./events)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: image_scan
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_test
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_push_image
  • GitHub Check: build_test
🔇 Additional comments (28)
controlplane/migrations/meta/_journal.json (1)

929-935: Journal entry appended correctly (idx 132).

Idx, tag, and version look consistent with prior entries. No action required.

controlplane/src/types/index.ts (2)

3-10: Importing ProposalOrigin into shared types is appropriate.

This keeps DTOs aligned with DB-backed enum values. Watch for name clashes with the protobuf enum in other modules.

If there are files importing both the DB type and the protobuf enum, ensure imports are aliased to avoid confusion (e.g., ProposalOriginDB vs ProposalOriginPB).


782-783: ProposalDTO now carries origin.

Good addition; downstream API responses and UI can gate actions accordingly.

controlplane/src/db/models.ts (2)

18-19: Imported proposalOriginEnum for typing.

OK.


39-40: Type alias for ProposalOrigin.

Union type derived from enum values is correct and future‑proof.

controlplane/src/core/bufservices/proposal/createProposal.ts (1)

25-25: Convert protobuf origin to DB enum at the boundary.

The util import is appropriate here.

controlplane/migrations/0132_nappy_hardball.sql (1)

1-2: Migration adds enum and not‑null column with default.

Solid: existing rows get COSMO and future inserts are enforced. No further changes needed.

cli/src/commands/proposal/commands/create.ts (2)

4-4: Importing ProposalOrigin from protobuf.

OK.


83-84: CLI sets origin to COSMO.

Matches product intent; server still has a safe default.

studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (2)

73-76: Importing ProposalOrigin for UI gating.

Good.


223-231: Reading origin from payload.

OK.

proto/wg/cosmo/platform/v1/platform.proto (2)

2701-2705: Enum definition is proto3‑friendly (0 == COSMO).

Good choice reserving 0 for the internal default. No issues.


2717-2718: Creation API: defaults to COSMO — no action required

createProposal maps req.origin via toProposalOriginEnum (defaults to 'COSMO'); CLI sets origin to ProposalOrigin.COSMO; DB schema has origin default 'COSMO'. Verified in: controlplane/src/core/bufservices/proposal/createProposal.ts (uses toProposalOriginEnum(req.origin)), controlplane/src/core/util.ts (toProposalOriginEnum default -> 'COSMO'), cli/src/commands/proposal/commands/create.ts (origin: ProposalOrigin.COSMO), controlplane/src/db/schema.ts (origin default 'COSMO').

controlplane/src/core/bufservices/proposal/getProposalsByFederatedGraph.ts (2)

12-12: Import enrichment LGTM.

Pulling in fromProposalOriginEnum here keeps mapping logic centralized.


156-157: Resolve — default‑to‑COSMO is intentional and enforced by migration

Migration controlplane/migrations/0132_nappy_hardball.sql creates enum proposal_origin and adds origin with DEFAULT 'COSMO' NOT NULL; keeping fromProposalOriginEnum defaulting to COSMO is correct.

controlplane/src/core/util.ts (1)

22-24: Import aliasing avoids type name collision.

Clear separation between proto enum (numeric) and DB enum (string). Good.

controlplane/src/db/schema.ts (2)

2445-2446: DB enum created and used correctly in migration

controlplane/migrations/0132_nappy_hardball.sql creates public.proposal_origin AS ENUM('COSMO','HUB') and then ALTER TABLE adds the origin column referencing it; controlplane/src/db/schema.ts export matches — no change required.


2462-2462: Resolve — migration already adds DEFAULT + NOT NULL

controlplane/migrations/0132_nappy_hardball.sql contains:
ALTER TABLE "proposals" ADD COLUMN "origin" "proposal_origin" DEFAULT 'COSMO' NOT NULL;
Existing rows will be valid with the default.

connect/src/wg/cosmo/platform/v1/platform_pb.ts (5)

363-381: Enum addition and registration LGTM.

ProposalOrigin values and setEnumType wiring are correct and consistent with typical proto3 enum patterns. No issues here.


21167-21168: Field descriptor entry LGTM.

Using proto3.getEnumType(ProposalOrigin) for field 11 is correct and matches the enum registration.


21295-21296: Verify generated bindings for 'origin' tag numbers

proto/wg/cosmo/platform/v1/platform.proto shows Proposal.origin = 11 (line 2690) and CreateProposalRequest.origin = 6 (line 2717). Search of generated outputs (connect-go/gen/proto and connect/src/wg/cosmo/platform/v1/platform_pb.ts) did not find an 'origin' field with tag 11. Verify that generated Go/TypeScript bindings for message Proposal use field number 11 — regenerate stubs or fix protos if they do not.


21144-21147: No bare-truthiness usage found; verify one suspicious string comparison

Repo scan found no truthy checks (if/proposal.origin, &&, ternary, switch). Found explicit uses and one questionable comparison — verify and fix if needed:

  • controlplane/src/core/bufservices/proposal/updateProposal.ts:154 — if (proposal.proposal.origin !== 'COSMO') → confirm this matches the enum representation (use ProposalOrigin.COSMO or the mapped string consistently).
  • controlplane/src/core/bufservices/proposal/getProposal.ts:92 — origin: fromProposalOriginEnum(proposal.proposal.origin)
  • controlplane/src/core/bufservices/proposal/getProposalsByFederatedGraph.ts:156 — origin: fromProposalOriginEnum(proposal.proposal.origin)
  • controlplane/src/core/repositories/ProposalRepository.ts:275 — origin: proposal.origin

Action: Replace any truthiness checks with explicit comparisons (e.g., proposal.origin === ProposalOrigin.COSMO) and correct the string-vs-enum comparison in updateProposal.ts if it's mismatched.


21277-21281: Confirm producers and server handling of origin — action: verify and set origin where needed.

  • connect/src/wg/cosmo/platform/v1/platform_pb.ts defines CreateProposalRequest.origin default = ProposalOrigin.COSMO; cli/src/commands/proposal/commands/create.ts explicitly sets origin: ProposalOrigin.COSMO.
  • controlplane util maps unknown/strings to ProposalOrigin, defaulting to COSMO. Tests call client.createProposal(...) widely but do not appear to pass origin in the call sites shown.
  • Risk: non-CLI producers (tests, jobs, other services) that omit origin will be attributed COSMO (proto default / server mapping). Ensure producers that should be HUB (or other) explicitly set origin, or make server-side handling explicit (normalize/validate origin on server).

Actions for author:

  • If intent is to attribute missing origin to COSMO, no change required.
  • If intent is to require explicit origin: add server-side validation or change generated default; update createProposal producers (tests/jobs/services) to pass origin explicitly where appropriate — audit createProposal call sites in controlplane/test and services and set origin.
controlplane/src/core/repositories/ProposalRepository.ts (4)

85-86: ById: origin selection and DTO mapping — OK.

Joins and serialization keep origin intact.

Also applies to: 117-118


148-149: ByName: origin selection and DTO mapping — OK.

Consistent with ById.

Also applies to: 181-182


229-230: ByFederatedGraphId: origin selection and DTO mapping — OK.

End-to-end origin surfaced alongside batched subgraph retrieval.

Also applies to: 275-276


17-23: Origin persisted — migration verified

Found in controlplane/migrations/0132_nappy_hardball.sql: it creates type "proposal_origin" and runs ALTER TABLE "proposals" ADD COLUMN "origin" "proposal_origin" DEFAULT 'COSMO' NOT NULL.

controlplane/src/core/bufservices/proposal/getProposal.ts (1)

13-13: Incorrect enum-mapping direction — current code is correct

fromProposalOriginEnum maps DB/domain -> proto (correct for building response protos); toProposalOriginEnum maps proto -> domain (correct for storing). Evidence: controlplane/src/core/util.ts — toProposalOriginEnum(value: ProposalOrigin): ProposalOriginEnum (line 658), fromProposalOriginEnum(value: ProposalOriginEnum): ProposalOrigin (line 669). Current usages are consistent: createProposal.ts uses toProposalOriginEnum(req.origin) (line 352); getProposal.ts (line 92) and getProposalsByFederatedGraph.ts (line 156) use fromProposalOriginEnum(proposal.proposal.origin). No change required.

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
controlplane/test/proposal/create-proposal.test.ts (1)

118-118: Assert that origin persists (and defaults) to strengthen coverage.

You set origin: COSMO on every create. Add one assertion on getProposal to verify proposal.origin === ProposalOrigin.COSMO, and add a single test that omits origin to confirm server-side defaulting to COSMO.

Apply this minimal assertion in one happy-path test after fetching the proposal:

       expect(proposalResponse.response?.code).toBe(EnumStatusCode.OK);
       expect(proposalResponse.proposal?.name).toBe(`p-1/${proposalName}`);
       expect(proposalResponse.proposal?.federatedGraphName).toBe(fedGraphName);
+      expect(proposalResponse.proposal?.origin).toBe(ProposalOrigin.COSMO);

Optional: add a small test "should default origin to COSMO when omitted" that calls createProposal without origin and asserts the same.

Also applies to: 213-213, 258-258, 337-337, 447-447, 549-549, 653-653, 700-700, 815-815, 1011-1011, 1033-1033, 1120-1120, 1148-1148, 1226-1226, 1294-1294, 1377-1377, 1480-1480, 1575-1575, 1620-1620, 1685-1685, 1751-1751, 1772-1772, 1860-1860, 1887-1887

controlplane/test/proposal/update-proposal.test.ts (3)

52-56: Helper: default origin is good; mark unused param to reduce noise.

options.subgraphSchemaSDL is not used inside createTestProposal. Either use it or underscore-prefix to signal intentional ignore.

Apply a low-impact tweak:

-async function createTestProposal(
+async function createTestProposal(
   client: any,
   options: {
     federatedGraphName: string;
     proposalName: string;
     subgraphName: string;
-    subgraphSchemaSDL: string;
+    subgraphSchemaSDL: string; // currently unused
     updatedSubgraphSDL: string;
     origin?: ProposalOrigin,
   },
 ) {
-  const { federatedGraphName, proposalName, subgraphName, subgraphSchemaSDL, updatedSubgraphSDL, origin } = options;
+  const { federatedGraphName, proposalName, subgraphName, subgraphSchemaSDL: _subgraphSchemaSDL, updatedSubgraphSDL, origin } = options;

Also applies to: 61-61


517-517: Also assert origin in update flows and keep it immutable.

Add an origin assertion after getProposal in at least one update path, and (optionally) a negative test proving origin cannot be changed via updateProposal.

Suggested assertion:

 expect(getProposalResponse.response?.code).toBe(EnumStatusCode.OK);
 expect(getProposalResponse.proposal?.subgraphs.length).toBe(2);
+expect(getProposalResponse.proposal?.origin).toBe(ProposalOrigin.COSMO);

Optional follow-up test: attempt to pass any origin-like field in updateAction (should be rejected/not possible) and assert origin remains COSMO on readback.

Also applies to: 685-685, 1510-1510, 1786-1786


517-517: Add a HUB-origin scenario test (separate file is fine).

Given the PR goal (“disallow reviewing proposals not created by Cosmo”), add a test creating a proposal with origin: HUB, then attempt a review action and expect ERROR_NOT_AUTHORIZED.

I can draft a focused test (e.g., controlplane/test/proposal/review-proposal-origin-gate.test.ts) that:

  • creates proposal with origin HUB,
  • calls review endpoint,
  • asserts ERROR_NOT_AUTHORIZED,
  • repeats with origin COSMO and asserts success.
    Want me to open an issue and provide the test skeleton?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbc9711 and afc1e30.

📒 Files selected for processing (2)
  • controlplane/test/proposal/create-proposal.test.ts (26 hunks)
  • controlplane/test/proposal/update-proposal.test.ts (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
controlplane/test/proposal/create-proposal.test.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/test/proposal/update-proposal.test.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
controlplane/test/test-util.ts (1)
  • DEFAULT_NAMESPACE (52-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build_test
  • GitHub Check: integration_test (./events)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: Analyze (go)
🔇 Additional comments (4)
controlplane/test/proposal/create-proposal.test.ts (2)

3-3: Importing ProposalOrigin is correct and consistent.

Matches the new proto surface and keeps tests explicit.


118-118: Repo-wide check: createProposal calls missing origin — confirm defaulting

Scan found most call sites include an explicit origin; these test calls do not: controlplane/test/proposal/proposal-schema-match.test.ts:67–81, 98–111, 1430–1443. The handler forwards req.origin (toProposalOriginEnum(req.origin)). Confirm the tests intentionally rely on the server default mapping or add an explicit origin.

controlplane/test/proposal/update-proposal.test.ts (2)

4-4: Importing ProposalOrigin is correct.

Keeps tests aligned with the proto enum.


4-4: Repo-wide verification — origin coverage & immutability

Automated scans failed (ripgrep/awk errors). Run these locally and paste results so I can confirm:

# 1) Any updateProposal payloads that include 'origin'
rg -nP -U --hidden --no-ignore --type=ts '\bupdateProposal\s*\(\s*{[^}]*\borigin\b' --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' || true

# 2) Show createProposal call contexts so we can check for missing 'origin'
rg -nP --hidden --no-ignore --type=ts '\bcreateProposal\s*\(' -n --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' | \
while IFS= read -r match; do
  f="${match%%:*}"; ln="${match##*:}"
  echo "---- $f:$ln ----"
  sed -n "${ln},$((ln+80))p" "$f"
  echo
done

Copy link
Copy Markdown
Member

@JivusAyrus JivusAyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a proposal origin check in the updateProposal rpc.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
controlplane/test/test-util.ts (2)

371-381: Broadened interceptor logic is good; update the comment to match behavior.

The code now handles both cosmo-cli and cosmo-hub. The comment still mentions only cosmo-cli; tighten it to avoid drift.

Apply:

-      // Interceptor to handle cosmo-cli user-agent
+      // Interceptor to handle cosmo-{cli,hub} client indicator
-        // Check if x-cosmo-client header is set to cosmo-cli
+        // Check if x-cosmo-client header is set to cosmo-cli or cosmo-hub

375-378: LGTM; minor hardening idea (optional).

Consider accepting any value starting with "cosmo-" to ease future client additions (e.g., cosmo-studio), while still deleting x-cosmo-client.

Example:

-        if (cosmoClient === 'cosmo-cli' || cosmoClient === 'cosmo-hub') {
+        if (cosmoClient && cosmoClient.startsWith('cosmo-')) {
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

162-181: Add server-side observability on origin mismatches.

Return ERR_NOT_FOUND to callers (good for not leaking), but log a structured warn so ops can spot misrouted clients.

Example:

-    if (proposal.proposal.origin !== expectedOrigin) {
+    if (proposal.proposal.origin !== expectedOrigin) {
+      logger.warn(
+        { proposalId: proposal.proposal.id, name: proposal.proposal.name, expectedOrigin, actualOrigin: proposal.proposal.origin },
+        'proposal origin mismatch on updateProposal'
+      );
       return {
         response: {
           code: EnumStatusCode.ERR_NOT_FOUND,
           details: `Proposal ${req.proposalName} not found`,
         },
         ...
       };
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afc1e30 and 9217bb9.

📒 Files selected for processing (3)
  • controlplane/src/core/bufservices/proposal/updateProposal.ts (2 hunks)
  • controlplane/test/proposal/update-proposal.test.ts (8 hunks)
  • controlplane/test/test-util.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • controlplane/test/proposal/update-proposal.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T11:15:52.157Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/ProposalRepository.ts:562-572
Timestamp: 2025-09-10T11:15:52.157Z
Learning: The getLatestCheckForProposal function in controlplane/src/core/repositories/ProposalRepository.ts is only called during proposal creation or updates, so proposal match error checking (hasProposalMatchError) is not needed since the proposal is being modified itself rather than being matched against.

Applied to files:

  • controlplane/src/core/bufservices/proposal/updateProposal.ts
🧬 Code graph analysis (1)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
🔇 Additional comments (1)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

11-11: Import looks correct.

Type import for ProposalOrigin is appropriate.

…-from-the-hub-cosmo-should-not

# Conflicts:
#	connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

154-175: If you keep caller-origin matching, don’t sniff only User-Agent; prefer x-cosmo-client + tolerant match.

This was raised earlier and still applies; UA is spoofable and often unavailable from browsers. Read x-cosmo-client first, fallback to UA, and use case-insensitive substring match.

-    const clientHdr = ctx.requestHeader.get('user-agent')?.toLowerCase() ?? '';
-    const expectedOrigin: ProposalOrigin = clientHdr.includes('cosmo-hub') ? 'HUB' : 'COSMO';
+    const clientHdr =
+      ctx.requestHeader.get('x-cosmo-client') ??
+      ctx.requestHeader.get('user-agent') ??
+      '';
+    const hdr = clientHdr.toLowerCase();
+    const expectedOrigin: ProposalOrigin = hdr.includes('cosmo-hub') ? 'HUB' : 'COSMO';
🧹 Nitpick comments (1)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

11-11: Use import type for type-only symbols.

Prevents emitting a runtime import for TS-only types (safer with .js paths). If you later drop ProposalOrigin (see next comment), adjust accordingly.

-import { ProposalOrigin, ProposalState } from '../../../db/models.js';
+import type { ProposalOrigin, ProposalState } from '../../../db/models.js';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9217bb9 and 68e39ec.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (5)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (5 hunks)
  • controlplane/src/core/bufservices/proposal/updateProposal.ts (2 hunks)
  • controlplane/src/types/index.ts (2 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (3 hunks)
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx
  • controlplane/src/types/index.ts
  • proto/wg/cosmo/platform/v1/platform.proto
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T11:15:52.157Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/ProposalRepository.ts:562-572
Timestamp: 2025-09-10T11:15:52.157Z
Learning: The getLatestCheckForProposal function in controlplane/src/core/repositories/ProposalRepository.ts is only called during proposal creation or updates, so proposal match error checking (hasProposalMatchError) is not needed since the proposal is being modified itself rather than being matched against.

Applied to files:

  • controlplane/src/core/bufservices/proposal/updateProposal.ts
🧬 Code graph analysis (1)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: build-router
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: build_push_image
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_test
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: image_scan
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)

wilsonrivera and others added 3 commits September 18, 2025 11:34
…-originating-from-the-hub-cosmo-should-not' into wilson/eng-7878-if-a-proposal-is-originating-from-the-hub-cosmo-should-not
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (4)

230-231: Guard for undefined origin and derive a readable flag

Older/legacy proposals might lack origin. Derive isCosmoOrigin once to simplify conditions and make the intent explicit.

Apply:

   const {
     name,
     createdAt,
     createdByEmail,
     state,
     subgraphs,
     latestCheckSuccess,
-    origin,
+    origin,
   } = proposal;
+
+  const isCosmoOrigin = origin === ProposalOrigin.COSMO;

308-334: Don’t nest Tooltip content inside a disabled DropdownMenuTrigger; conditionally render the whole menu

With DropdownMenuTrigger disabled, the nested Tooltip may be flaky across browsers/ATs, and the menu content is still in the DOM though unusable. Render the dropdown only when allowed; otherwise render a standalone tooltip + non-interactive control. This improves a11y and reduces markup.

Apply:

-            {state === "DRAFT" && (
-              <DropdownMenu>
-                <DropdownMenuTrigger asChild disabled={origin !== ProposalOrigin.COSMO}>
-                  {origin === ProposalOrigin.COSMO ? (
-                    <Button
-                      className="ml-4"
-                      disabled={isApproving || isClosing}
-                    >
-                      Review Changes
-                      <ChevronDownIcon className="ml-2 h-4 w-4" />
-                    </Button>
-                  ) : (
-                    <Tooltip>
-                      <TooltipTrigger asChild>
-                        <span
-                          className={buttonVariants({
-                            className: "ml-4 cursor-not-allowed opacity-60 hover:!bg-primary",
-                          })}
-                        >
-                          Review Changes
-                          <ChevronDownIcon className="ml-2 h-4 w-4" />
-                        </span>
-                      </TooltipTrigger>
-                      <TooltipContent>
-                        This proposal was created outside of Cosmo and cannot be updated within Cosmo.
-                      </TooltipContent>
-                    </Tooltip>
-                  )}
-                </DropdownMenuTrigger>
-                <DropdownMenuContent align="end" className="w-[500px] p-3">
+            {state === "DRAFT" && (
+              isCosmoOrigin ? (
+                <DropdownMenu>
+                  <DropdownMenuTrigger asChild>
+                    <Button
+                      className="ml-4"
+                      disabled={isApproving || isClosing}
+                    >
+                      Review Changes
+                      <ChevronDownIcon className="ml-2 h-4 w-4" />
+                    </Button>
+                  </DropdownMenuTrigger>
+                  <DropdownMenuContent align="end" className="w-[500px] p-3">
                     ...
-                </DropdownMenuContent>
-              </DropdownMenu>
+                  </DropdownMenuContent>
+                </DropdownMenu>
+              ) : (
+                <Tooltip>
+                  <TooltipTrigger asChild>
+                    <span
+                      className={buttonVariants({
+                        // keep visual consistency with a primary button, but non-interactive
+                        className: "ml-4 cursor-not-allowed opacity-60",
+                      })}
+                      role="button"
+                      aria-disabled="true"
+                      tabIndex={-1}
+                    >
+                      Review Changes
+                      <ChevronDownIcon className="ml-2 h-4 w-4" />
+                    </span>
+                  </TooltipTrigger>
+                  <TooltipContent>
+                    This proposal was created outside of Cosmo and cannot be updated within Cosmo.
+                  </TooltipContent>
+                </Tooltip>
+              )
             )}

Also applies to: 335-424


321-323: Use buttonVariants with explicit variant/size for consistency

Passing only className may miss variants (theme drift). Specify variant/size to match the enabled button.

Example:

- className={buttonVariants({
-   className: "ml-4 cursor-not-allowed opacity-60",
- })}
+ className={buttonVariants({
+   variant: "default",
+   size: "default",
+   className: "ml-4 cursor-not-allowed opacity-60",
+ })}

810-841: Consider adding an e2e test to assert the gating

Add a Studio e2e test that:

  • Loads a HUB-origin proposal and asserts the “Review Changes” control is non-interactive and shows the tooltip.
  • Loads a COSMO-origin proposal and asserts the dropdown opens and actions are enabled/disabled based on isApproving/isClosing.

I can draft a Playwright spec targeting these states if helpful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68e39ec and 95f7ec7.

📒 Files selected for processing (1)
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: build-router
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: image_scan
  • GitHub Check: integration_test (./events)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build_push_image
  • GitHub Check: build_test
🔇 Additional comments (3)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (3)

75-76: Enum import aligns with the PR goal

Importing ProposalOrigin from the generated connect types is correct and matches the feature intent.


308-334: Overall: matches PR intent

Gating “Review Changes” by origin === COSMO fulfills the feature requirement.


308-309: Server-side origin check present — resolved

controlplane/src/core/bufservices/proposal/updateProposal.ts returns an error when proposal.proposal.origin !== expectedOrigin (expectedOrigin is derived from the request user-agent; see lines ~154–158). updateProposal handles state transitions (approve/close), so the backend blocks mismatched-origin update/approve requests.

Copy link
Copy Markdown
Member

@endigma endigma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved on behalf of CLI, please get an approval for controlplane separately

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
controlplane/migrations/0132_slippery_payback.sql (1)

1-2: Qualify enum type and make default cast explicit; consider idempotency guard

  • Minor: keep schema-qualification consistent and make the DEFAULT explicit to the enum to avoid search_path surprises.
  • Optional: if your migration runner can re-run in certain envs, wrap CREATE TYPE in an idempotent guard to avoid failures.

Apply this diff:

-CREATE TYPE "public"."proposal_origin" AS ENUM('INTERNAL', 'EXTERNAL');--> statement-breakpoint
-ALTER TABLE "proposals" ADD COLUMN "origin" "proposal_origin" DEFAULT 'INTERNAL' NOT NULL;
+CREATE TYPE "public"."proposal_origin" AS ENUM('INTERNAL', 'EXTERNAL');--> statement-breakpoint
+ALTER TABLE "proposals"
+  ADD COLUMN "origin" "public"."proposal_origin"
+  NOT NULL
+  DEFAULT 'INTERNAL'::"public"."proposal_origin";

If you want an idempotent type creation, consider:

DO $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
                 WHERE t.typname = 'proposal_origin' AND n.nspname = 'public') THEN
    CREATE TYPE "public"."proposal_origin" AS ENUM('INTERNAL', 'EXTERNAL');
  END IF;
END$$;
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (1)

309-333: Make disabled “Review Changes” fully inert and accessible

Good gating on origin. Minor UX nits:

  • Remove the hover color on the disabled-looking element.
  • Add aria-disabled for assistive tech.

Apply this diff:

-                    <Tooltip>
+                    <Tooltip>
                       <TooltipTrigger asChild>
-                        <span
-                          className={buttonVariants({
-                            className: "ml-4 cursor-not-allowed opacity-60 hover:!bg-primary",
-                          })}
-                        >
+                        <span
+                          aria-disabled="true"
+                          className={buttonVariants({
+                            className: "ml-4 cursor-not-allowed opacity-60",
+                          })}
+                        >
                           Review Changes
                           <ChevronDownIcon className="ml-2 h-4 w-4" />
                         </span>
                       </TooltipTrigger>
controlplane/test/proposal/update-proposal.test.ts (3)

52-62: Default origin handling: simplify and tighten types

Good call passing origin through and defaulting to INTERNAL. Minor cleanups:

  • Set the default at destructuring to avoid the nullish coalescing at usage.
  • Optional: type the client param to PromiseClient for better IDE/type safety.
  • Optional: subgraphSchemaSDL is not used in this helper; consider removing it from the signature to avoid confusion.

Apply this minimal change:

-  const { federatedGraphName, proposalName, subgraphName, subgraphSchemaSDL, updatedSubgraphSDL, origin } = options;
+  const {
+    federatedGraphName,
+    proposalName,
+    subgraphName,
+    subgraphSchemaSDL,
+    updatedSubgraphSDL,
+    origin = ProposalOrigin.INTERNAL,
+  } = options;
...
-    origin: origin ?? ProposalOrigin.INTERNAL,
+    origin,

Type annotations (outside this range) you may consider:

import type { PromiseClient } from '@connectrpc/connect';
import { PlatformService } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_connectquery';

async function createTestProposal(
  client: PromiseClient<typeof PlatformService>,
  options: { /* ... */ }
) { /* ... */ }

513-518: DRY up origin: INTERNAL in direct createProposal calls

You’re explicitly setting origin: ProposalOrigin.INTERNAL in multiple direct createProposal calls. Since your helper already defaults to INTERNAL, consider:

  • Using the helper where feasible to reduce duplication, or
  • Extracting a small factory/local helper for direct multi-subgraph cases to keep origin handling consistent in one place.

Also applies to: 681-686, 1504-1511, 1780-1786


2217-2237: Add coverage for state updates on EXTERNAL-origin proposals

You only validate updatedSubgraphs is blocked. Add a similar check for updateAction: 'state' to ensure all review/update paths are gated by origin.

Apply after the first update attempt:

     const updateProposalResponse = await client.updateProposal({
       proposalName: createProposalResponse.proposalName,
       federatedGraphName: fedGraphName,
       namespace: DEFAULT_NAMESPACE,
       updateAction: {
         case: 'updatedSubgraphs',
         value: {
           subgraphs: [
             {
               name: newSubgraphName,
               schemaSDL: newSubgraphSchemaSDL,
               isDeleted: false,
               isNew: true,
               labels: [label],
             },
           ],
         },
       },
     });

+    // Attempt a state transition should also be blocked
+    const stateUpdateResp = await client.updateProposal({
+      proposalName: createProposalResponse.proposalName,
+      federatedGraphName: fedGraphName,
+      namespace: DEFAULT_NAMESPACE,
+      updateAction: { case: 'state', value: 'APPROVED' },
+    });
+    expect(stateUpdateResp.response?.code).toBe(EnumStatusCode.ERR_NOT_FOUND);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95f7ec7 and d471cd2.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (10)
  • cli/src/commands/proposal/commands/create.ts (2 hunks)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (5 hunks)
  • controlplane/migrations/0132_slippery_payback.sql (1 hunks)
  • controlplane/migrations/meta/_journal.json (1 hunks)
  • controlplane/src/core/util.ts (2 hunks)
  • controlplane/src/db/schema.ts (2 hunks)
  • controlplane/test/proposal/create-proposal.test.ts (26 hunks)
  • controlplane/test/proposal/update-proposal.test.ts (8 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (3 hunks)
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • controlplane/src/db/schema.ts
  • controlplane/test/proposal/create-proposal.test.ts
  • controlplane/src/core/util.ts
  • controlplane/migrations/meta/_journal.json
🧰 Additional context used
🧬 Code graph analysis (4)
connect/src/wg/cosmo/platform/v1/platform_pb.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/test/proposal/update-proposal.test.ts (5)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
controlplane/test/test-util.ts (6)
  • DEFAULT_NAMESPACE (52-52)
  • SetupTest (66-417)
  • createThenPublishSubgraph (550-573)
  • DEFAULT_SUBGRAPH_URL_ONE (49-49)
  • createFederatedGraph (623-638)
  • DEFAULT_ROUTER_URL (48-48)
controlplane/src/core/test-util.ts (2)
  • genID (53-55)
  • genUniqueLabel (57-59)
connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts (2)
  • createFederatedGraph (346-355)
  • enableProposalsForNamespace (2512-2521)
controlplane/src/core/bufservices/proposal/enableProposalsForNamespace.ts (1)
  • enableProposalsForNamespace (17-96)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
cli/src/commands/proposal/commands/create.ts (2)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: Analyze (go)
🔇 Additional comments (7)
proto/wg/cosmo/platform/v1/platform.proto (1)

2717-2718: Field numbers and defaults for ProposalOrigin are sound; ensure codegen is updated

  • origin added to Proposal as field 11 and CreateProposalRequest as field 6; enum defaults to INTERNAL (0) when omitted, matching desired behavior.
  • No numbering collisions observed.

Confirm regenerated artifacts are committed across languages (TS, Go, etc.) to avoid CI breakage. Typical steps:

  • pnpm generate && pnpm build for @wundergraph/cosmo-connect
  • Ensure connect-go/gen and any other generated targets are updated

Also applies to: 2728-2731, 2744-2745

studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/[proposalId]/index.tsx (1)

224-231: Origin plumbed through the UI state correctly

Destructuring origin and using it to gate actions aligns with the backend contract.

cli/src/commands/proposal/commands/create.ts (1)

4-4: All CreateProposal callers use INTERNAL origin by default
CLI explicitly sets ProposalOrigin.INTERNAL; generated CreateProposalRequest defaults to INTERNAL when origin is omitted.

connect/src/wg/cosmo/platform/v1/platform_pb.ts (3)

367-381: Enum addition aligns with proto change

The new ProposalOrigin enum and metadata registration match the proto updates and keep the generated TS artifacts in sync. ✅


21378-21400: Proposal.origin default keeps backward compatibility

Adding the origin field with an INTERNAL default preserves behavior for existing clients while allowing the new gating logic to discriminate external requests. Looks good.


21511-21528: CreateProposalRequest origin plumbing is correct

Propagating the enum to CreateProposalRequest ensures callers can explicitly pass the origin, and the default still favors current clients. Implementation checks out.

controlplane/test/proposal/update-proposal.test.ts (1)

4-4: Import looks correct and necessary

Bringing in ProposalOrigin aligns with the new origin field usage throughout the tests.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

154-175: Policy mismatch — enforce INTERNAL-only for proposal updates

This issue was already flagged in a previous review. The current logic derives expectedOrigin from the caller's user-agent and permits EXTERNAL-origin proposals when the caller sends "cosmo-hub". The PR objective states to "disallow reviewing proposals not created by Cosmo," which means only proposals with origin 'INTERNAL' should be allowed for updates, regardless of the caller.

Apply this diff to enforce INTERNAL-only updates:

-    const clientHdr = ctx.requestHeader.get('user-agent')?.toLowerCase() ?? '';
-    const expectedOrigin: ProposalOrigin = clientHdr.includes('cosmo-hub') ? 'EXTERNAL' : 'INTERNAL';
-    if (proposal.proposal.origin !== expectedOrigin) {
+    // Only proposals created by Cosmo may be reviewed/updated
+    if (proposal.proposal.origin !== 'INTERNAL') {
       return {
         response: {
           code: EnumStatusCode.ERR_NOT_FOUND,
           details: `Proposal ${req.proposalName} not found`,
         },
         breakingChanges: [],
         nonBreakingChanges: [],
         compositionErrors: [],
         checkId: '',
         lintWarnings: [],
         lintErrors: [],
         graphPruneWarnings: [],
         graphPruneErrors: [],
         compositionWarnings: [],
         lintingSkipped: false,
         graphPruningSkipped: false,
         checkUrl: '',
       };
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d348732 and 53cdefc.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (4)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (5 hunks)
  • controlplane/src/core/bufservices/proposal/createProposal.ts (2 hunks)
  • controlplane/src/core/bufservices/proposal/updateProposal.ts (2 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
connect/src/wg/cosmo/platform/v1/platform_pb.ts (2)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
controlplane/src/core/bufservices/proposal/updateProposal.ts (2)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
controlplane/src/core/bufservices/proposal/createProposal.ts (1)
controlplane/src/core/util.ts (1)
  • toProposalOriginEnum (658-667)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: build-router
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: image_scan
  • GitHub Check: build_push_image
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
🔇 Additional comments (10)
controlplane/src/core/bufservices/proposal/updateProposal.ts (1)

11-11: LGTM!

The import of ProposalOrigin is correctly added alongside ProposalState and is necessary for the new origin verification logic.

controlplane/src/core/bufservices/proposal/createProposal.ts (1)

25-25: LGTM!

The import of toProposalOriginEnum is correctly added to support the origin conversion at line 352.

proto/wg/cosmo/platform/v1/platform.proto (3)

2728-2731: LGTM!

The ProposalOrigin enum is well-defined:

  • Follows proto3 conventions (first value is 0)
  • Uses appropriate UPPER_SNAKE_CASE naming
  • INTERNAL as the default (0) value provides a secure default
  • Values are clear and descriptive

2717-2717: LGTM!

The origin field in the Proposal message is correctly defined:

  • Field number 11 is sequential and doesn't conflict with existing fields
  • Not marked optional, defaulting to INTERNAL (0) is appropriate for backwards compatibility
  • Placement is logical within the message structure

2744-2744: Ensure origin validation is enforced server-side.

The origin field in CreateProposalRequest is correctly defined with field number 6. However, since this is a client-provided field, ensure that:

  1. Server-side validation enforces that the provided origin matches the actual request source
  2. Clients cannot arbitrarily set the origin to bypass access controls

The default value of INTERNAL (0) is appropriate for backwards compatibility.

This concern is related to the validation request in createProposal.ts (line 352). The verification script provided there will help confirm if proper validation exists.

connect/src/wg/cosmo/platform/v1/platform_pb.ts (5)

363-381: Enum wiring looks solid.

ProposalOrigin mirrors the proto definition and is correctly registered with proto3.util.setEnumType.


21377-21380: Default aligns with expectations.

Adding origin with a default of ProposalOrigin.INTERNAL keeps existing behavior stable while exposing the new metadata.


21397-21401: Descriptor update matches enum type.

Including origin in the field metadata with proto3.getEnumType(ProposalOrigin) keeps serialization consistent.


21510-21513: Sensible default on request payload.

Create requests now carry origin, defaulting to INTERNAL, which preserves existing clients while enabling overrides.


21525-21528: Field metadata stays in sync.

The descriptor for origin correctly points to the shared ProposalOrigin enum, ensuring consistent encode/decode.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
controlplane/test/proposal/update-proposal.test.ts (1)

2149-2243: LGTM with minor suggestions.

The test correctly validates that external proposals cannot be updated. The regex pattern at line 2240 follows the previous review's recommendation and is safe in this test context despite the static analysis warning (the proposalName is controlled by genID(), not user input, eliminating ReDoS risk).

Consider adding a companion test that attempts to update the state (e.g., to APPROVED or CLOSED) of an external proposal, to ensure state transitions are also blocked. Currently, this test only verifies blocking subgraph updates.

Example test structure:

test('should fail to update state of a proposal created from hub', async () => {
  // ... setup similar to current test ...
  const createProposalResponse = await createTestProposal(client, {
    // ... same setup ...
    origin: ProposalOrigin.EXTERNAL,
  });

  const updateProposalResponse = await client.updateProposal({
    proposalName: createProposalResponse.proposalName,
    federatedGraphName: fedGraphName,
    namespace: DEFAULT_NAMESPACE,
    updateAction: {
      case: 'state',
      value: 'APPROVED',
    },
  });

  expect(updateProposalResponse.response?.code).toBe(EnumStatusCode.ERR_NOT_FOUND);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53cdefc and 4e644d5.

📒 Files selected for processing (1)
  • controlplane/test/proposal/update-proposal.test.ts (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controlplane/test/proposal/update-proposal.test.ts (4)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (4)
  • ProposalOrigin (562-562)
  • ProposalOrigin (591-593)
  • ProposalOrigin (595-597)
  • ProposalOrigin (604-606)
controlplane/src/db/models.ts (1)
  • ProposalOrigin (39-39)
controlplane/test/test-util.ts (6)
  • DEFAULT_NAMESPACE (52-52)
  • SetupTest (66-417)
  • createThenPublishSubgraph (550-573)
  • DEFAULT_SUBGRAPH_URL_ONE (49-49)
  • createFederatedGraph (623-638)
  • DEFAULT_ROUTER_URL (48-48)
controlplane/src/core/bufservices/proposal/enableProposalsForNamespace.ts (1)
  • enableProposalsForNamespace (17-96)
🪛 ast-grep (0.39.6)
controlplane/test/proposal/update-proposal.test.ts

[warning] 2239-2239: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(Proposal .*${proposalName} not found)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: build-router
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: image_scan
  • GitHub Check: integration_test (./events)
🔇 Additional comments (3)
controlplane/test/proposal/update-proposal.test.ts (3)

4-4: LGTM!

The import of ProposalOrigin aligns with the feature requirements and follows the existing import pattern.


44-75: LGTM!

The helper function enhancement maintains backward compatibility while enabling origin-specific testing. The default to ProposalOrigin.INTERNAL ensures existing test cases continue to work as expected.


517-517: LGTM!

Explicitly specifying origin: ProposalOrigin.INTERNAL improves test clarity and ensures the origin assumption is documented rather than implicit. This consistency across test cases is a good practice.

Also applies to: 685-685, 1509-1509, 1657-1657, 1785-1785

Copy link
Copy Markdown
Contributor

@StarpTech StarpTech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wilsonrivera wilsonrivera merged commit d4aee49 into main Oct 10, 2025
44 of 45 checks passed
@wilsonrivera wilsonrivera deleted the wilson/eng-7878-if-a-proposal-is-originating-from-the-hub-cosmo-should-not branch October 10, 2025 12:43
@coderabbitai coderabbitai bot mentioned this pull request Nov 26, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants