@reventlessdev/reventless-aws 3.0.0-alpha.296 → 3.0.0-alpha.297

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.297 (2026-08-14)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** sweep undirectived fields at the schema assembly choke point ([f0ed736](https://github.com/ReventlessDev/reventless-core/commit/f0ed7369ec4dddfb697a3c93115b12f310a491b0))
11
+
12
+
6
13
  # 3.0.0-alpha.296 (2026-08-14)
7
14
 
8
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.296",
3
+ "version": "3.0.0-alpha.297",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -13,14 +13,14 @@
13
13
  "sury": "11.0.0-alpha.4",
14
14
  "uuid": "^13.0.0",
15
15
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.10",
16
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
17
16
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
17
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
18
18
  "@reventlessdev/rescript-node": "2.0.0-alpha.6",
19
19
  "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.75",
20
20
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
21
+ "@reventlessdev/reventless-core": "3.0.0-alpha.233",
21
22
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
22
23
  "@reventlessdev/reventless-infra": "3.0.0-alpha.140",
23
- "@reventlessdev/reventless-core": "3.0.0-alpha.233",
24
24
  "@reventlessdev/reventless-postgres": "3.0.0-alpha.97",
25
25
  "@reventlessdev/reventless-spec": "3.0.0-alpha.113",
26
26
  "@reventlessdev/reventless-interop": "3.0.0-alpha.31"
@@ -460,6 +460,10 @@ let stitchStandaloneWithAwsDirectives = (
460
460
  ~baseFragment=fragment,
461
461
  ~pluginFragments=[],
462
462
  )
463
+ // Every AWS source-API document assembles through here, so this is the one
464
+ // place that can guarantee no field or type reaches a deployed schema without
465
+ // an auth directive — whichever path injected it.
466
+ let fragment = AppSync_SdlDecorate.stampUndirectivedFields(fragment)
463
467
  ReventlessCore.GraphQL_Stitcher.stitchStandalone(~fragment)
464
468
  ->AppSync_SdlDecorate.injectAwsSubscribe(~sources)
465
469
  ->stampSharedIamTypes
@@ -264,7 +264,8 @@ function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
264
264
 
265
265
  function stitchStandaloneWithAwsDirectives(fragment) {
266
266
  let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(fragment, []);
267
- return AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment), sources)));
267
+ let fragment$1 = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(fragment);
268
+ return AppSync_SdlDecorate$ReventlessAws.stampAllTypesCognito(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment$1), sources)));
268
269
  }
269
270
 
270
271
  function _makeApiResourceWith(name, schema, userPoolConfig, opts) {
@@ -162,6 +162,37 @@ let stampSharedIamTypes = (sdl: string): string =>
162
162
  acc->String.replace(`type ${name} {`, `type ${name} @aws_cognito_user_pools @aws_iam {`)
163
163
  )
164
164
 
165
+ // Final field sweep, run at the assembly choke point on the FRAGMENT (where each
166
+ // element is one whole field, so "does it already carry a directive?" is a
167
+ // reliable question — on raw SDL text a directive placed on the following line
168
+ // would read as absent).
169
+ //
170
+ // `injectAwsAuth` only decorates fields it can pair with a schema entry, and
171
+ // several surfaces never reach it at all: the domain base document
172
+ // (`Platform_ping`), event-history queries, event-log `…_eventAppended`
173
+ // subscriptions, the upload presign/release mutations, `Platform_SetActiveRole`,
174
+ // and `geocode`. Those deployed with no directive — invisible under
175
+ // `defaultAction: ALLOW`, refused under DENY. Sweeping here rather than at each
176
+ // emission site means a future injected field cannot silently miss the net.
177
+ //
178
+ // Idempotent, and deliberately does not second-guess gating: it only fills in
179
+ // "open to any authenticated Cognito caller", which is what an undirectived
180
+ // field already means today.
181
+ let stampUndirectivedFields = (
182
+ fragment: Reventless.Plugin.apiSchemaFragment,
183
+ ): Reventless.Plugin.apiSchemaFragment => {
184
+ let parts = ReventlessCore.GraphQL_Stitcher.decode(fragment)
185
+ let stamp = (field: string): string =>
186
+ field->String.includes("@aws_") ? field : `${field}\n ${cognitoOpenDirective}`
187
+
188
+ ReventlessCore.GraphQL_Stitcher.encode({
189
+ ...parts,
190
+ mutations: parts.mutations->Array.map(stamp),
191
+ queries: parts.queries->Array.map(stamp),
192
+ subscriptions: parts.subscriptions->Array.map(stamp),
193
+ })
194
+ }
195
+
165
196
  // Stamp every object `type` declaration that does not already carry an auth
166
197
  // directive with the group-less Cognito arm. Run on the ASSEMBLED SDL, AFTER
167
198
  // stampSharedIamTypes so the shared traversal types keep their `@aws_iam` arm
@@ -88,6 +88,24 @@ function stampSharedIamTypes(sdl) {
88
88
  return Stdlib_Array.reduce(sharedIamTypeNames, sdl, (acc, name) => acc.replace(`type ` + name + ` {`, `type ` + name + ` @aws_cognito_user_pools @aws_iam {`));
89
89
  }
90
90
 
91
+ function stampUndirectivedFields(fragment) {
92
+ let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
93
+ let stamp = field => {
94
+ if (field.includes("@aws_")) {
95
+ return field;
96
+ } else {
97
+ return field + `\n ` + cognitoOpenDirective;
98
+ }
99
+ };
100
+ return GraphQL_Stitcher$ReventlessCore.encode({
101
+ types: parts.types,
102
+ mutations: parts.mutations.map(stamp),
103
+ queries: parts.queries.map(stamp),
104
+ subscriptions: parts.subscriptions.map(stamp),
105
+ subscriptionSources: parts.subscriptionSources
106
+ });
107
+ }
108
+
91
109
  function stampAllTypesCognito(sdl) {
92
110
  return sdl.split("\n").map(line => {
93
111
  if (!(line.startsWith("type ") && !line.includes("@aws_"))) {
@@ -147,6 +165,7 @@ export {
147
165
  injectAwsAuthAll,
148
166
  sharedIamTypeNames,
149
167
  stampSharedIamTypes,
168
+ stampUndirectivedFields,
150
169
  stampAllTypesCognito,
151
170
  canonicalTypeNames,
152
171
  stampCanonicalTypes,
@@ -188,3 +188,64 @@ describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
188
188
  expect(AppSync_SdlDecorate.stampAllTypesCognito(once))->toBe(once)
189
189
  })
190
190
  })
191
+
192
+ // ── Final field sweep ───────────────────────────────────────────────────────
193
+ //
194
+ // `injectAwsAuth` only decorates fields it can pair with a schema entry. Several
195
+ // surfaces never reach it: the domain base (`Platform_ping`), event-history
196
+ // queries, `…_eventAppended` subscriptions, upload mutations,
197
+ // `Platform_SetActiveRole`, `geocode`. A deployed-SDL count after the first
198
+ // sweep found 11 such fields still bare on the domain API. This pass is the net.
199
+
200
+ describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
201
+ let frag = (~mutations=[], ~queries=[], ~subscriptions=[], ()) =>
202
+ ReventlessCore.GraphQL_Stitcher.encode({
203
+ types: [],
204
+ mutations,
205
+ queries,
206
+ subscriptions,
207
+ subscriptionSources: [],
208
+ })
209
+
210
+ testSync("stamps a query no schema entry ever paired with", () => {
211
+ let out = AppSync_SdlDecorate.stampUndirectivedFields(
212
+ frag(~queries=[" Platform_ping: String"], ()),
213
+ )
214
+ let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
215
+ expect(q)->toContain("@aws_cognito_user_pools")
216
+ expect(q)->not_->toContain("cognito_groups")
217
+ })
218
+
219
+ testSync("stamps an event-log subscription", () => {
220
+ let out = AppSync_SdlDecorate.stampUndirectivedFields(
221
+ frag(~subscriptions=[" onFoo_eventAppended: FooEvent"], ()),
222
+ )
223
+ expect(
224
+ ReventlessCore.GraphQL_Stitcher.decode(out).subscriptions->Array.getUnsafe(0),
225
+ )->toContain("@aws_cognito_user_pools")
226
+ })
227
+
228
+ testSync("leaves an already-gated field untouched, group and all", () => {
229
+ let gated = ` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`
230
+ let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[gated], ()))
231
+ let q = ReventlessCore.GraphQL_Stitcher.decode(out).queries->Array.getUnsafe(0)
232
+ expect(q)->toContain(`cognito_groups: ["Admin"]`)
233
+ expect(q->String.includes("@aws_cognito_user_pools @aws_cognito_user_pools"))->toBe(false)
234
+ })
235
+
236
+ testSync("does not strip an @aws_iam arm", () => {
237
+ let dual = " Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"
238
+ let out = AppSync_SdlDecorate.stampUndirectivedFields(frag(~mutations=[dual], ()))
239
+ expect(
240
+ ReventlessCore.GraphQL_Stitcher.decode(out).mutations->Array.getUnsafe(0),
241
+ )->toContain("@aws_iam")
242
+ })
243
+
244
+ testSync("is idempotent", () => {
245
+ let once = AppSync_SdlDecorate.stampUndirectivedFields(frag(~queries=[" a: String"], ()))
246
+ let twice = AppSync_SdlDecorate.stampUndirectivedFields(once)
247
+ expect(ReventlessCore.GraphQL_Stitcher.decode(twice).queries->Array.getUnsafe(0))->toBe(
248
+ ReventlessCore.GraphQL_Stitcher.decode(once).queries->Array.getUnsafe(0),
249
+ )
250
+ })
251
+ })
@@ -152,6 +152,46 @@ globalThis.describe("AppSync_SdlDecorate.stampAllTypesCognito", () => {
152
152
  });
153
153
  });
154
154
 
155
+ globalThis.describe("AppSync_SdlDecorate.stampUndirectivedFields", () => {
156
+ let frag = (mutationsOpt, queriesOpt, subscriptionsOpt, param) => {
157
+ let mutations = mutationsOpt !== undefined ? mutationsOpt : [];
158
+ let queries = queriesOpt !== undefined ? queriesOpt : [];
159
+ let subscriptions = subscriptionsOpt !== undefined ? subscriptionsOpt : [];
160
+ return GraphQL_Stitcher$ReventlessCore.encode({
161
+ types: [],
162
+ mutations: mutations,
163
+ queries: queries,
164
+ subscriptions: subscriptions,
165
+ subscriptionSources: []
166
+ });
167
+ };
168
+ globalThis.test("stamps a query no schema entry ever paired with", () => {
169
+ let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" Platform_ping: String"], undefined, undefined));
170
+ let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
171
+ globalThis.expect(q).toContain("@aws_cognito_user_pools");
172
+ globalThis.expect(q).not.toContain("cognito_groups");
173
+ });
174
+ globalThis.test("stamps an event-log subscription", () => {
175
+ let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, undefined, [" onFoo_eventAppended: FooEvent"], undefined));
176
+ globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).subscriptions[0]).toContain("@aws_cognito_user_pools");
177
+ });
178
+ globalThis.test("leaves an already-gated field untouched, group and all", () => {
179
+ let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [` Secret_Read: String\n @aws_cognito_user_pools(cognito_groups: ["Admin"])`], undefined, undefined));
180
+ let q = GraphQL_Stitcher$ReventlessCore.decode(out).queries[0];
181
+ globalThis.expect(q).toContain(`cognito_groups: ["Admin"]`);
182
+ globalThis.expect(q.includes("@aws_cognito_user_pools @aws_cognito_user_pools")).toBe(false);
183
+ });
184
+ globalThis.test("does not strip an @aws_iam arm", () => {
185
+ let out = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag([" Sys_Sync: String\n @aws_cognito_user_pools @aws_iam"], undefined, undefined, undefined));
186
+ globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(out).mutations[0]).toContain("@aws_iam");
187
+ });
188
+ globalThis.test("is idempotent", () => {
189
+ let once = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(frag(undefined, [" a: String"], undefined, undefined));
190
+ let twice = AppSync_SdlDecorate$ReventlessAws.stampUndirectivedFields(once);
191
+ globalThis.expect(GraphQL_Stitcher$ReventlessCore.decode(twice).queries[0]).toBe(GraphQL_Stitcher$ReventlessCore.decode(once).queries[0]);
192
+ });
193
+ });
194
+
155
195
  export {
156
196
  sources,
157
197
  stitchedSdl,