@reventlessdev/reventless-aws 3.0.0-alpha.287 → 3.0.0-alpha.290
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 +14 -0
- package/package.json +5 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +7 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +2 -0
- package/src/util/Util_OwnerScopeEnv.res +48 -0
- package/src/util/Util_OwnerScopeEnv.res.mjs +31 -0
- package/tests/Util_OwnerScopeEnvTest.res +60 -0
- package/tests/Util_OwnerScopeEnvTest.res.mjs +62 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.290 (2026-08-13)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** move past a version number that already names other content ([bffba4f](https://github.com/ReventlessDev/reventless-core/commit/bffba4f15eff4a9c30bbf2e28484bb549b865cfc))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.288 (2026-08-12)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **aws:** carry the elevated groups into every Lambda ([ced4d92](https://github.com/ReventlessDev/reventless-core/commit/ced4d9211c3cebe70702757ab331c8a1fa773fca))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.287 (2026-08-12)
|
|
7
21
|
|
|
8
22
|
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.290",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"@aws-sdk/s3-request-presigner": "3.970.0",
|
|
13
13
|
"sury": "11.0.0-alpha.4",
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
|
-
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.7",
|
|
16
15
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
16
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
18
17
|
"@reventlessdev/rescript-node": "2.0.0-alpha.4",
|
|
18
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.7",
|
|
19
19
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.71",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
21
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.228",
|
|
22
21
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.229",
|
|
23
23
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.137",
|
|
24
24
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
25
|
-
"@reventlessdev/reventless-
|
|
26
|
-
"@reventlessdev/reventless-
|
|
25
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.111",
|
|
26
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.93"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
|
@@ -237,6 +237,13 @@ let makeFromCodeAsset: (
|
|
|
237
237
|
// `Lambda.Function.make` builders apply the identical policy.
|
|
238
238
|
Util_LambdaLogging.applyLogLevelDefault(variables)
|
|
239
239
|
|
|
240
|
+
// Same terms, same reason it is here rather than at each builder: the groups
|
|
241
|
+
// exempt from owner scoping are decided by the deploy program, and the runtime
|
|
242
|
+
// that stamps a command has no other way to learn them. Without it a runtime
|
|
243
|
+
// concludes nobody is elevated and stamps an operator's on-behalf write with
|
|
244
|
+
// the operator's own id.
|
|
245
|
+
Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
|
|
246
|
+
|
|
240
247
|
// ESM self-containment (Option C): every code archive built by
|
|
241
248
|
// Util_Bundle.buildCodeArchive ships register-hook.mjs + layer-resolver.mjs at
|
|
242
249
|
// /var/task. --import registers the resolve hook so the ESM entry point (and the
|
|
@@ -24,6 +24,7 @@ import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/sr
|
|
|
24
24
|
import * as Util_DcbMetrics$ReventlessAws from "../../util/Util_DcbMetrics.res.mjs";
|
|
25
25
|
import * as RuntimeExtension$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/RuntimeExtension/RuntimeExtension.res.mjs";
|
|
26
26
|
import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
|
|
27
|
+
import * as Util_OwnerScopeEnv$ReventlessAws from "../../util/Util_OwnerScopeEnv.res.mjs";
|
|
27
28
|
import * as ResourceAttribution$ReventlessCore from "@reventlessdev/reventless-core/src/ResourceAttribution.res.mjs";
|
|
28
29
|
|
|
29
30
|
let additionalEnvVars = {};
|
|
@@ -102,6 +103,7 @@ function makeFromCodeAsset(name, unitKind, componentKind, code, sourceCodeHash,
|
|
|
102
103
|
variables[key] = value;
|
|
103
104
|
});
|
|
104
105
|
Util_LambdaLogging$ReventlessAws.applyLogLevelDefault(variables);
|
|
106
|
+
Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
|
|
105
107
|
variables["NODE_OPTIONS"] = Util_Bundle$ReventlessAws.esmLoaderNodeOptions;
|
|
106
108
|
variables["ESM_FALLBACK_DIRS"] = Util_Bundle$ReventlessAws.esmFallbackDirs;
|
|
107
109
|
if (!RuntimeExtension$ReventlessCore.isEmpty()) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Carries the deployment's elevated groups into Lambda runtimes.
|
|
2
|
+
//
|
|
3
|
+
// A cloud deployment is two kinds of process and only one of them is the deploy
|
|
4
|
+
// program: it bakes the owner predicate into generated resolver source, while
|
|
5
|
+
// command stamping (`CommandGenerator_Callback`) and SQL-backed reads
|
|
6
|
+
// (`PgQueryResolver_Lambda`) run later, in function runtimes the deploy never
|
|
7
|
+
// enters. `Reventless.OwnerScope.elevatedGroups()` answers from an explicit
|
|
8
|
+
// `setElevatedGroups` or from the environment, so a value stated in the deploy
|
|
9
|
+
// program reaches those runtimes only if something puts it in their environment.
|
|
10
|
+
//
|
|
11
|
+
// Nothing did, and the two halves fail in opposite directions. A read that
|
|
12
|
+
// believes nobody is elevated shows an operator too little, which is safe and
|
|
13
|
+
// visible. A write that believes it stamps an operator's on-behalf order with
|
|
14
|
+
// the OPERATOR's id — silently, and the row is wrong from then on. The second is
|
|
15
|
+
// why this is a correctness fix rather than a parity one.
|
|
16
|
+
//
|
|
17
|
+
// Scoped deliberately to `RuntimeEnvironment_Lambda.makeFromCodeAsset`, which
|
|
18
|
+
// every command and query runtime is built through. The bespoke builders that
|
|
19
|
+
// append `Util_LambdaLogging.logLevelEntry()` for themselves — dead letters, the
|
|
20
|
+
// state-topic publisher, geocoding, the two upload Lambdas, the platform UI APIs
|
|
21
|
+
// — neither stamp a command nor serve an owner-scoped view, so they sit outside
|
|
22
|
+
// this policy for the same reason they sit outside several others. The
|
|
23
|
+
// serialized-closure `make` path is likewise outside it, and already outside the
|
|
24
|
+
// logging policy.
|
|
25
|
+
|
|
26
|
+
let key = "REVENTLESS_ELEVATED_GROUPS"
|
|
27
|
+
|
|
28
|
+
/** The `("REVENTLESS_ELEVATED_GROUPS", "Admin,Support")` entry, or `None` when
|
|
29
|
+
the deployment named no elevated groups.
|
|
30
|
+
|
|
31
|
+
Comma-separated because that is what `OwnerScope.parseElevatedGroups` reads,
|
|
32
|
+
which trims each part and drops the empties. Absent rather than empty for the
|
|
33
|
+
empty case: the two mean the same thing there, and writing `""` would suggest
|
|
34
|
+
a deployment had answered the question when it has not. */
|
|
35
|
+
let entry = (): option<(string, Pulumi.Input.t<string>)> =>
|
|
36
|
+
switch Reventless.OwnerScope.elevatedGroups() {
|
|
37
|
+
| [] => None
|
|
38
|
+
| groups => Some((key, groups->Array.join(",")->Pulumi.Input.make))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Default the elevated groups for a Lambda when the caller has not pinned them.
|
|
42
|
+
Mutates in place and must run after the caller's own env vars are set, so
|
|
43
|
+
theirs win — the same contract as `Util_LambdaLogging.applyLogLevelDefault`,
|
|
44
|
+
which it runs beside. */
|
|
45
|
+
let applyElevatedGroupsDefault = (variables: dict<Pulumi.Input.t<string>>) =>
|
|
46
|
+
if variables->Dict.get(key)->Option.isNone {
|
|
47
|
+
entry()->Option.forEach(((k, v)) => variables->Dict.set(k, v))
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
5
|
+
|
|
6
|
+
let key = "REVENTLESS_ELEVATED_GROUPS";
|
|
7
|
+
|
|
8
|
+
function entry() {
|
|
9
|
+
let groups = OwnerScope$Reventless.elevatedGroups();
|
|
10
|
+
if (groups.length !== 0) {
|
|
11
|
+
return [
|
|
12
|
+
key,
|
|
13
|
+
groups.join(",")
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function applyElevatedGroupsDefault(variables) {
|
|
19
|
+
if (Stdlib_Option.isNone(variables[key])) {
|
|
20
|
+
return Stdlib_Option.forEach(entry(), param => {
|
|
21
|
+
variables[param[0]] = param[1];
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
key,
|
|
28
|
+
entry,
|
|
29
|
+
applyElevatedGroupsDefault,
|
|
30
|
+
}
|
|
31
|
+
/* OwnerScope-Reventless Not a pure module */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// Guards the carrier that tells a Lambda runtime which groups are exempt from
|
|
4
|
+
// owner scoping. The deploy program bakes the read predicate itself, but
|
|
5
|
+
// stamping and SQL-backed reads run in function runtimes it never enters, so
|
|
6
|
+
// without this entry those runtimes conclude nobody is elevated — and an
|
|
7
|
+
// operator's on-behalf write is then stamped with the operator's own id.
|
|
8
|
+
//
|
|
9
|
+
// The encoding is load-bearing in both directions: what is written here is read
|
|
10
|
+
// back by `OwnerScope.parseElevatedGroups`, so the two are asserted together
|
|
11
|
+
// rather than each against a literal.
|
|
12
|
+
|
|
13
|
+
let withElevated = (groups, f) => {
|
|
14
|
+
Reventless.OwnerScope.setElevatedGroups(groups)
|
|
15
|
+
let result = f()
|
|
16
|
+
Reventless.OwnerScope.clearElevatedGroups()
|
|
17
|
+
result
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
|
|
21
|
+
testSync("writes what OwnerScope reads back", () => {
|
|
22
|
+
let encoded = withElevated(["Admin", "Support"], () =>
|
|
23
|
+
switch Util_OwnerScopeEnv.entry() {
|
|
24
|
+
| Some((_, value)) => value->Obj.magic
|
|
25
|
+
| None => ""
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
// The round trip, not the spelling: a separator change on either side fails.
|
|
29
|
+
expect(Reventless.OwnerScope.parseElevatedGroups(encoded))->toEqual(["Admin", "Support"])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
testSync("names the variable the runtime actually reads", () => {
|
|
33
|
+
expect(Util_OwnerScopeEnv.key)->toBe("REVENTLESS_ELEVATED_GROUPS")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
testSync("no elevated groups means no variable, not an empty one", () => {
|
|
37
|
+
let entry = withElevated([], () => Util_OwnerScopeEnv.entry())
|
|
38
|
+
expect(entry->Option.isNone)->toBe(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
testSync("defaults the variable when the caller pinned nothing", () => {
|
|
42
|
+
let variables = Dict.make()
|
|
43
|
+
withElevated(["Admin"], () => Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables))
|
|
44
|
+
expect(variables->Dict.get(Util_OwnerScopeEnv.key)->Option.isSome)->toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
testSync("a caller that pinned the variable wins", () => {
|
|
48
|
+
let variables = Dict.fromArray([(Util_OwnerScopeEnv.key, "Pinned"->Obj.magic)])
|
|
49
|
+
withElevated(["Admin"], () => Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables))
|
|
50
|
+
expect(variables->Dict.get(Util_OwnerScopeEnv.key)->Option.getOr(""->Obj.magic)->Obj.magic)->toBe(
|
|
51
|
+
"Pinned",
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
testSync("adds nothing when the deployment named no elevated groups", () => {
|
|
56
|
+
let variables = Dict.make()
|
|
57
|
+
withElevated([], () => Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables))
|
|
58
|
+
expect(variables->Dict.get(Util_OwnerScopeEnv.key)->Option.isNone)->toBe(true)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
5
|
+
import * as Util_OwnerScopeEnv$ReventlessAws from "../src/util/Util_OwnerScopeEnv.res.mjs";
|
|
6
|
+
|
|
7
|
+
function withElevated(groups, f) {
|
|
8
|
+
OwnerScope$Reventless.setElevatedGroups(groups);
|
|
9
|
+
let result = f();
|
|
10
|
+
OwnerScope$Reventless.clearElevatedGroups();
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
globalThis.describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
|
|
15
|
+
globalThis.test("writes what OwnerScope reads back", () => {
|
|
16
|
+
let encoded = withElevated([
|
|
17
|
+
"Admin",
|
|
18
|
+
"Support"
|
|
19
|
+
], () => {
|
|
20
|
+
let match = Util_OwnerScopeEnv$ReventlessAws.entry();
|
|
21
|
+
if (match !== undefined) {
|
|
22
|
+
return match[1];
|
|
23
|
+
} else {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([
|
|
28
|
+
"Admin",
|
|
29
|
+
"Support"
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
globalThis.test("names the variable the runtime actually reads", () => {
|
|
33
|
+
globalThis.expect(Util_OwnerScopeEnv$ReventlessAws.key).toBe("REVENTLESS_ELEVATED_GROUPS");
|
|
34
|
+
});
|
|
35
|
+
globalThis.test("no elevated groups means no variable, not an empty one", () => {
|
|
36
|
+
let entry = withElevated([], () => Util_OwnerScopeEnv$ReventlessAws.entry());
|
|
37
|
+
globalThis.expect(Stdlib_Option.isNone(entry)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
globalThis.test("defaults the variable when the caller pinned nothing", () => {
|
|
40
|
+
let variables = {};
|
|
41
|
+
withElevated(["Admin"], () => Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables));
|
|
42
|
+
globalThis.expect(Stdlib_Option.isSome(variables[Util_OwnerScopeEnv$ReventlessAws.key])).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
globalThis.test("a caller that pinned the variable wins", () => {
|
|
45
|
+
let variables = Object.fromEntries([[
|
|
46
|
+
Util_OwnerScopeEnv$ReventlessAws.key,
|
|
47
|
+
"Pinned"
|
|
48
|
+
]]);
|
|
49
|
+
withElevated(["Admin"], () => Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables));
|
|
50
|
+
globalThis.expect(Stdlib_Option.getOr(variables[Util_OwnerScopeEnv$ReventlessAws.key], "")).toBe("Pinned");
|
|
51
|
+
});
|
|
52
|
+
globalThis.test("adds nothing when the deployment named no elevated groups", () => {
|
|
53
|
+
let variables = {};
|
|
54
|
+
withElevated([], () => Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables));
|
|
55
|
+
globalThis.expect(Stdlib_Option.isNone(variables[Util_OwnerScopeEnv$ReventlessAws.key])).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
withElevated,
|
|
61
|
+
}
|
|
62
|
+
/* Not a pure module */
|