@reventlessdev/reventless-aws 3.0.0-alpha.401 → 3.0.0-alpha.402

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,23 @@
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.402 (2026-10-29)
7
+
8
+ * feat(core)!: the caller's IP address leaves the messages a request causes ([aa9e8c7](https://github.com/ReventlessDev/reventless-core/commit/aa9e8c79c0360528a5cfeda2bb88d1da52f28674))
9
+ ### Features
10
+
11
+ * **upload:** an upload's path carries nothing a person typed ([456c88c](https://github.com/ReventlessDev/reventless-core/commit/456c88c4ccee6a50943e3a71011a2e78aef0d770))
12
+
13
+ ### BREAKING CHANGES
14
+
15
+ * `Message.generateMeta` no longer takes `~ip`. Drop the argument; a
16
+ message that must carry the address declares it as a field of its own payload.
17
+
18
+ Step PD3 of docs/plans/a-personal-field-is-marked-redacted-and-encrypted-at-the-seam.md.
19
+ Claude-Session: https://claude.ai/code/session_01HiQfdxVEsHwLHRoHSUcus1
20
+
21
+
22
+
6
23
  # 3.0.0-alpha.401 (2026-10-29)
7
24
 
8
25
  * refactor!: drop `dcbSources`, the cross-plugin DCB subscription nobody declared ([cf3b66d](https://github.com/ReventlessDev/reventless-core/commit/cf3b66d8c1b5d87673197b64eaee174598bb8253))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.401",
3
+ "version": "3.0.0-alpha.402",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -26,15 +26,15 @@
26
26
  "@reventlessdev/rescript-effect": "1.0.0-alpha.1",
27
27
  "@reventlessdev/rescript-jest": "1.0.0-alpha.13",
28
28
  "@reventlessdev/rescript-node": "2.0.0-alpha.23",
29
- "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.34",
30
- "@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
29
+ "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.35",
31
30
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
32
- "@reventlessdev/rescript-web": "1.0.0-alpha.8",
33
- "@reventlessdev/reventless-core": "3.0.0-alpha.311",
34
- "@reventlessdev/reventless-infra": "3.0.0-alpha.208",
35
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.175",
31
+ "@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
32
+ "@reventlessdev/reventless-core": "3.0.0-alpha.312",
33
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.209",
36
34
  "@reventlessdev/reventless-interop": "3.0.0-alpha.42",
37
- "@reventlessdev/reventless-spec": "3.0.0-alpha.179"
35
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.180",
36
+ "@reventlessdev/rescript-web": "1.0.0-alpha.8",
37
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.176"
38
38
  },
39
39
  "devDependencies": {
40
40
  "rescript": "12.3.1",
@@ -1552,7 +1552,7 @@ function MakeWithConfig(Config) {
1552
1552
  };
1553
1553
  let publishHeartbeat = CommandTopicChannel_SQS_Runtime$ReventlessAws.publishJsons(queue, "SQS_FIFO");
1554
1554
  let message_id = hbConfig.pluginId;
1555
- let message_meta = Message$ReventlessCore.generateMeta(PluginExtensionPointSpec$ReventlessInfra.name, undefined, "DeployHeartbeat", undefined, undefined, undefined, undefined, undefined);
1555
+ let message_meta = Message$ReventlessCore.generateMeta(PluginExtensionPointSpec$ReventlessInfra.name, "DeployHeartbeat", undefined, undefined, undefined, undefined, undefined);
1556
1556
  let message_commandJson = Util_Sury$Reventless.toJson({
1557
1557
  TAG: "RedetectPlugin",
1558
1558
  _0: hbConfig.heartbeatTimeout
@@ -2962,7 +2962,7 @@ function Make($star) {
2962
2962
  };
2963
2963
  let publishHeartbeat = CommandTopicChannel_SQS_Runtime$ReventlessAws.publishJsons(queue, "SQS_FIFO");
2964
2964
  let message_id = hbConfig.pluginId;
2965
- let message_meta = Message$ReventlessCore.generateMeta(PluginExtensionPointSpec$ReventlessInfra.name, undefined, "DeployHeartbeat", undefined, undefined, undefined, undefined, undefined);
2965
+ let message_meta = Message$ReventlessCore.generateMeta(PluginExtensionPointSpec$ReventlessInfra.name, "DeployHeartbeat", undefined, undefined, undefined, undefined, undefined);
2966
2966
  let message_commandJson = Util_Sury$Reventless.toJson({
2967
2967
  TAG: "RedetectPlugin",
2968
2968
  _0: hbConfig.heartbeatTimeout
@@ -383,7 +383,7 @@ let dispatchTool = async (tool: mcpToolEntry, args: JSON.t, identity: Reventless
383
383
  let payload: ReventlessCore.CommandGenerator.payload = {
384
384
  command: tool.name,
385
385
  arguments: args->Obj.magic,
386
- meta: {ip: [], user: identity.userId, info: `mcp/tools/${tool.name}`},
386
+ meta: {user: identity.userId, info: `mcp/tools/${tool.name}`},
387
387
  identity,
388
388
  }
389
389
  // Route through the shared dispatch helper so this tool call gets the same
@@ -254,7 +254,6 @@ async function dispatchTool(tool, args, identity) {
254
254
  let generateCommand = CommandGenerator_CallbackResMjs.makeGenerateCommand(publishJsons, tool.name, Sury.json, tool.componentKind, false);
255
255
  let payload_command = tool.name;
256
256
  let payload_meta = {
257
- ip: [],
258
257
  user: identity.userId,
259
258
  info: `mcp/tools/` + tool.name
260
259
  };
@@ -39,7 +39,6 @@ let handler = async (_event: JSON.t, _context: PulumiAws.Lambda.context) => {
39
39
  id: pluginId,
40
40
  meta: ReventlessCore.Message.generateMeta(
41
41
  ~service=ReventlessInfra.PluginExtensionPointSpec.name,
42
- ~ip="",
43
42
  ~user="Heartbeat",
44
43
  ),
45
44
  commandJson: ReventlessInfra.PluginExtensionPointSpec.Heartbeat(
@@ -26,7 +26,7 @@ let runtimeExtensionsReady = HandlerFactoryHelpersMjs.runtimeExtensionsReady;
26
26
 
27
27
  async function handler(_event, _context) {
28
28
  await runtimeExtensionsReady;
29
- let message_meta = Message$ReventlessCore.generateMeta(PluginExtensionPointSpec$ReventlessInfra.name, "", "Heartbeat", undefined, undefined, undefined, undefined, undefined);
29
+ let message_meta = Message$ReventlessCore.generateMeta(PluginExtensionPointSpec$ReventlessInfra.name, "Heartbeat", undefined, undefined, undefined, undefined, undefined);
30
30
  let message_commandJson = Util_Sury$Reventless.toJson({
31
31
  TAG: "Heartbeat",
32
32
  _0: timeout
@@ -11,7 +11,7 @@
11
11
  // surface — the earlier `decodeJwtSub` path is gone.
12
12
  //
13
13
  // Two operations dispatch on the `operation` field the resolver bakes in:
14
- // presign → derive `{servedPrefix}/{sub}/{uuid}/{fileName}`, presign a PUT to the
14
+ // presign → derive `{servedPrefix}/{sub}/{uuid}.{ext}`, presign a PUT to the
15
15
  // store's bucket (expires 300s), return `{uploadUrl, storageRef}`.
16
16
  // release → apply the release rule (scope + age) to a `storageRef` and, if it
17
17
  // passes, `DeleteObject`; return `{released, reason}`.
@@ -211,6 +211,10 @@ let headLastModifiedMs = async (~client: s3Client, ~bucket: string, ~key: string
211
211
 
212
212
  // ── Operations ──────────────────────────────────────────────────────────────
213
213
 
214
+ // The object key a presign mints, pure so a test can pin its shape.
215
+ let presignKey = (~servedPrefix, ~sub, ~uuid, ~contentType) =>
216
+ `${servedPrefix}/${sub}/${Reventless.StorageRef.objectName(~uuid, ~contentType)}`
217
+
214
218
  let handlePresign = async (
215
219
  ~client: s3Client,
216
220
  ~bucket: string,
@@ -221,11 +225,17 @@ let handlePresign = async (
221
225
  if sub == "" {
222
226
  JsError.throwWithMessage("unauthenticated")
223
227
  }
224
- let fileName = args.fileName->Option.getOr("upload")
225
228
  // The object key doubles as the served path segment: rooted at the store's
226
229
  // served prefix so the CloudFront `{prefix}/*` behavior fronts it, and namespaced
227
230
  // by the verified `sub` so the release rule can tell one caller's objects apart.
228
- let key = `${servedPrefix}/${sub}/${NodeCrypto.randomUUID()}/${fileName}`
231
+ // The `fileName` argument is not used: the key ends in an opaque name, because a
232
+ // ref is stored in an event and a file name is often personal data.
233
+ let key = presignKey(
234
+ ~servedPrefix,
235
+ ~sub,
236
+ ~uuid=NodeCrypto.randomUUID(),
237
+ ~contentType=args.contentType,
238
+ )
229
239
  // The object is provisional from the moment the bytes land: it carries the
230
240
  // pending tag until a committed event referencing it strips it (see
231
241
  // `Upload_Claim_S3_Ops`). Written into the signature here, not asked of the
@@ -9,6 +9,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
10
  import * as ClientS3 from "@aws-sdk/client-s3";
11
11
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
12
+ import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
12
13
  import * as S3RequestPresigner from "@aws-sdk/s3-request-presigner";
13
14
  import * as Upload_PendingTag$ReventlessAws from "./Upload_PendingTag.res.mjs";
14
15
 
@@ -167,12 +168,15 @@ async function headLastModifiedMs(client, bucket, key) {
167
168
  }
168
169
  }
169
170
 
171
+ function presignKey(servedPrefix, sub, uuid, contentType) {
172
+ return servedPrefix + `/` + sub + `/` + StorageRef$Reventless.objectName(uuid, contentType);
173
+ }
174
+
170
175
  async function handlePresign(client, bucket, servedPrefix, sub, args) {
171
176
  if (sub === "") {
172
177
  Stdlib_JsError.throwWithMessage("unauthenticated");
173
178
  }
174
- let fileName = Stdlib_Option.getOr(args.fileName, "upload");
175
- let key = servedPrefix + `/` + sub + `/` + Nodecrypto.randomUUID() + `/` + fileName;
179
+ let key = presignKey(servedPrefix, sub, Nodecrypto.randomUUID(), args.contentType);
176
180
  let command = new ClientS3.PutObjectCommand({
177
181
  Bucket: bucket,
178
182
  Key: key,
@@ -235,6 +239,7 @@ export {
235
239
  releaseResult,
236
240
  keyOfRef,
237
241
  headLastModifiedMs,
242
+ presignKey,
238
243
  handlePresign,
239
244
  handleRelease,
240
245
  handler,
@@ -114,7 +114,7 @@ export const handler = async (event) => {
114
114
  ["REVENTLESS_CORE_STACK", process.env.STACK_ORG + "/" + process.env.STACK_PROJECT + "/" + process.env.STACK_STACK],
115
115
  ["RESTORE_DATE_TIME", event.restoreDateTime],
116
116
  ]);
117
- console.log("clone: requested by user " + event.meta.user + " from ip " + event.meta.ip);
117
+ console.log("clone: requested by user " + event.meta.user);
118
118
  await client.send(new RunTaskCommand({
119
119
  taskDefinition: process.env.TASK_DEFINITION_ARN,
120
120
  cluster: process.env.CLUSTER_ARN,
@@ -358,7 +358,6 @@ export function request(ctx) {
358
358
  payload: {
359
359
  restoreDateTime: ctx.args.restoreDateTime,
360
360
  meta: {
361
- ip: ctx.identity.sourceIp,
362
361
  user: ctx.identity.username
363
362
  }
364
363
  }
@@ -67,7 +67,7 @@ export const handler = async (event) => {
67
67
  ["REVENTLESS_CORE_STACK", process.env.STACK_ORG + "/" + process.env.STACK_PROJECT + "/" + process.env.STACK_STACK],
68
68
  ["RESTORE_DATE_TIME", event.restoreDateTime],
69
69
  ]);
70
- console.log("clone: requested by user " + event.meta.user + " from ip " + event.meta.ip);
70
+ console.log("clone: requested by user " + event.meta.user);
71
71
  await client.send(new RunTaskCommand({
72
72
  taskDefinition: process.env.TASK_DEFINITION_ARN,
73
73
  cluster: process.env.CLUSTER_ARN,
@@ -235,7 +235,6 @@ export function request(ctx) {
235
235
  payload: {
236
236
  restoreDateTime: ctx.args.restoreDateTime,
237
237
  meta: {
238
- ip: ctx.identity.sourceIp,
239
238
  user: ctx.identity.username
240
239
  }
241
240
  }
@@ -17,7 +17,7 @@ function tag(key, value) {
17
17
  }
18
18
 
19
19
  function testMeta() {
20
- return Message$ReventlessCore.generateMeta("test", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
20
+ return Message$ReventlessCore.generateMeta("test", undefined, undefined, undefined, undefined, undefined, undefined);
21
21
  }
22
22
 
23
23
  let table = {
@@ -78,7 +78,7 @@ describe("EventMapperEntryPoint_Ops.makeStreamHandler", () => {
78
78
  }
79
79
  let command: ReventlessCore.Message.commandJson = {
80
80
  id: "prd-1",
81
- meta: ReventlessCore.Message.generateMeta(~service="Product", ~ip="", ~user="test"),
81
+ meta: ReventlessCore.Message.generateMeta(~service="Product", ~user="test"),
82
82
  commandJson: JSON.Encode.string("Add"),
83
83
  }
84
84
  let commonEventsHandler: EventMapperEntryPoint_Ops.commonEventsHandler = async jsons => {
@@ -70,7 +70,7 @@ globalThis.describe("EventMapperEntryPoint_Ops.makeStreamHandler", () => {
70
70
  publishJsons: ops_publishJsons,
71
71
  queryEngine: ops_queryEngine
72
72
  };
73
- let command_meta = Message$ReventlessCore.generateMeta("Product", "", "test", undefined, undefined, undefined, undefined, undefined);
73
+ let command_meta = Message$ReventlessCore.generateMeta("Product", "test", undefined, undefined, undefined, undefined, undefined);
74
74
  let command = {
75
75
  id: "prd-1",
76
76
  meta: command_meta,
@@ -22,7 +22,7 @@ function stored(eventType, tags, data) {
22
22
  eventType: eventType,
23
23
  data: data,
24
24
  tags: tags,
25
- meta: Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined)
25
+ meta: Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined)
26
26
  };
27
27
  }
28
28
 
@@ -124,7 +124,7 @@ if (url !== undefined) {
124
124
  "data",
125
125
  data
126
126
  ]
127
- ].concat(Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined))));
127
+ ].concat(Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined))));
128
128
  await match[1].append(0, "agg-1", [
129
129
  flatItem("agg-1", 0, "NameUpdated", Object.fromEntries([[
130
130
  "n",
@@ -155,7 +155,7 @@ if (url !== undefined) {
155
155
  let mkOps = name => EventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, name, undefined, undefined)[1];
156
156
  let opsB = mkOps("ClassicRelayIsoBEventLog");
157
157
  let opsA = mkOps("ClassicRelayIsoAEventLog");
158
- let meta = () => Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined));
158
+ let meta = () => Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined));
159
159
  let item = (id, eventType) => Object.fromEntries([
160
160
  [
161
161
  "id",
@@ -132,7 +132,7 @@ function flatItem(id, seq, service, event) {
132
132
  "data",
133
133
  match[1]
134
134
  ]
135
- ].concat(Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta(service, undefined, undefined, undefined, undefined, undefined, undefined, undefined))));
135
+ ].concat(Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta(service, undefined, undefined, undefined, undefined, undefined, undefined))));
136
136
  }
137
137
 
138
138
  function asSqsEvent(bodies) {
@@ -88,3 +88,27 @@ describe("Upload_Presign_S3_Ops.decideRelease", () => {
88
88
  )
89
89
  )
90
90
  })
91
+
92
+ describe("Upload_Presign_S3_Ops.presignKey", () => {
93
+ testSync("is {prefix}/{sub}/{uuid}.{ext}, with nothing the uploader typed", () =>
94
+ expect(
95
+ Ops.presignKey(~servedPrefix=prefix, ~sub, ~uuid="0b1c", ~contentType=Some("image/png")),
96
+ )->toEqual("uploads/user-1/0b1c.png")
97
+ )
98
+
99
+ testSync("an undeclared content type is stored as bin", () =>
100
+ expect(Ops.presignKey(~servedPrefix=prefix, ~sub, ~uuid="0b1c", ~contentType=None))->toEqual(
101
+ "uploads/user-1/0b1c.bin",
102
+ )
103
+ )
104
+
105
+ testSync("a minted key passes the release rule's scope check for its own caller", () =>
106
+ expect(
107
+ Ops.scopeCheck(
108
+ ~key=Ops.presignKey(~servedPrefix=prefix, ~sub, ~uuid="0b1c", ~contentType=None),
109
+ ~sub,
110
+ ~servedPrefix=prefix,
111
+ ),
112
+ )->toEqual(Ok())
113
+ )
114
+ })
@@ -89,6 +89,21 @@ globalThis.describe("Upload_Presign_S3_Ops.decideRelease", () => {
89
89
  });
90
90
  });
91
91
 
92
+ globalThis.describe("Upload_Presign_S3_Ops.presignKey", () => {
93
+ globalThis.test("is {prefix}/{sub}/{uuid}.{ext}, with nothing the uploader typed", () => {
94
+ globalThis.expect(Upload_Presign_S3_Ops$ReventlessAws.presignKey(prefix, sub, "0b1c", "image/png")).toEqual("uploads/user-1/0b1c.png");
95
+ });
96
+ globalThis.test("an undeclared content type is stored as bin", () => {
97
+ globalThis.expect(Upload_Presign_S3_Ops$ReventlessAws.presignKey(prefix, sub, "0b1c", undefined)).toEqual("uploads/user-1/0b1c.bin");
98
+ });
99
+ globalThis.test("a minted key passes the release rule's scope check for its own caller", () => {
100
+ globalThis.expect(Upload_Presign_S3_Ops$ReventlessAws.scopeCheck(Upload_Presign_S3_Ops$ReventlessAws.presignKey(prefix, sub, "0b1c", undefined), sub, prefix)).toEqual({
101
+ TAG: "Ok",
102
+ _0: undefined
103
+ });
104
+ });
105
+ });
106
+
92
107
  let Ops;
93
108
 
94
109
  let nowMs = 1000000.0;
@@ -13,7 +13,7 @@ function tag(key, value) {
13
13
  }
14
14
 
15
15
  function meta() {
16
- return Message$ReventlessCore.generateMeta("dcb-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
16
+ return Message$ReventlessCore.generateMeta("dcb-it", undefined, undefined, undefined, undefined, undefined, undefined);
17
17
  }
18
18
 
19
19
  function event(eventType, tags) {