okengine 0.3.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/package.json +14 -12
- package/site/content/docs/ai/index.mdx +24 -0
- package/site/content/docs/ai/llms-txt.mdx +3 -0
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +5 -3
- package/site/content/docs/console/gates.mdx +46 -8
- package/site/content/docs/console/index.mdx +54 -0
- package/site/content/docs/console/meta.json +1 -0
- package/site/content/docs/elements/ai.mdx +2 -0
- package/site/content/docs/elements/channel.mdx +2 -0
- package/site/content/docs/elements/clock.mdx +1 -4
- package/site/content/docs/elements/flow.mdx +4 -10
- package/site/content/docs/elements/gate.mdx +189 -48
- package/site/content/docs/elements/index.mdx +45 -0
- package/site/content/docs/elements/meta.json +1 -1
- package/site/content/docs/elements/signal.mdx +1 -5
- package/site/content/docs/elements/store.mdx +27 -6
- package/site/content/docs/elements/vault.mdx +10 -11
- package/site/content/docs/get-started/basic-usage.mdx +79 -43
- package/site/content/docs/get-started/index.mdx +33 -0
- package/site/content/docs/get-started/installation.mdx +95 -43
- package/site/content/docs/get-started/introduction.mdx +128 -75
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/why.mdx +141 -0
- package/site/content/docs/index.mdx +9 -31
- package/site/content/docs/plugins/anonymous.mdx +95 -0
- package/site/content/docs/plugins/compression.mdx +2 -2
- package/site/content/docs/plugins/cors.mdx +2 -2
- package/site/content/docs/plugins/csrf.mdx +2 -2
- package/site/content/docs/plugins/email-otp.mdx +111 -0
- package/site/content/docs/plugins/{security-headers.mdx → headers.mdx} +2 -2
- package/site/content/docs/plugins/index.mdx +69 -0
- package/site/content/docs/plugins/ip-allowlist.mdx +1 -2
- package/site/content/docs/plugins/magic-link.mdx +112 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +8 -8
- package/site/content/docs/plugins/meta.json +10 -1
- package/site/content/docs/plugins/passkey.mdx +128 -0
- package/site/content/docs/plugins/phone-number.mdx +111 -0
- package/site/content/docs/plugins/two-factor.mdx +116 -0
- package/site/content/docs/plugins/username.mdx +117 -0
- package/site/content/docs/reference/client.mdx +331 -0
- package/site/content/docs/reference/configuration.mdx +1 -1
- package/site/content/docs/reference/environment-variables.mdx +5 -3
- package/site/content/docs/reference/fx.mdx +36 -8
- package/site/content/docs/reference/index.mdx +45 -0
- package/site/content/docs/reference/meta.json +11 -1
- package/site/content/docs/reference/plugins.mdx +25 -14
- package/src/auth/auth.test.ts +20 -2
- package/src/auth/bindings.ts +439 -0
- package/src/auth/breach-check.ts +112 -0
- package/src/auth/config.ts +288 -0
- package/src/auth/cookies.ts +123 -0
- package/src/auth/gate-auth.test.ts +379 -0
- package/src/auth/identity.ts +190 -0
- package/src/auth/index.ts +117 -1
- package/src/auth/method-context.ts +33 -0
- package/src/auth/operator.ts +27 -1
- package/src/auth/password-policy.test.ts +126 -0
- package/src/auth/password-policy.ts +77 -0
- package/src/auth/plugin.ts +62 -4
- package/src/auth/rate.ts +45 -0
- package/src/auth/schema.ts +260 -0
- package/src/auth/secondary-storage.ts +37 -0
- package/src/auth/sessions.ts +58 -1
- package/src/auth/tables.ts +4 -0
- package/src/auth/verification.ts +78 -0
- package/src/cli/competitor-mention-removal.test.ts +117 -0
- package/src/cli/dev.test.ts +3 -3
- package/src/cli/dev.ts +20 -0
- package/src/cli/meilisearch-local.test.ts +69 -0
- package/src/cli/meilisearch-local.ts +188 -0
- package/src/cli/schema.ts +95 -23
- package/src/client/auth.ts +120 -0
- package/src/client-react/index.ts +93 -0
- package/src/compiler/aot.test.ts +2 -1
- package/src/compiler/extract.ts +19 -0
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +5 -1
- package/src/compiler/response.ts +16 -2
- package/src/console/server/app.ts +10 -6
- package/src/console/server/auth-rate.test.ts +3 -3
- package/src/console/server/bind.ts +12 -1
- package/src/console/server/channels.test.ts +1 -1
- package/src/console/server/console-gates.ts +14 -0
- package/src/console/server/console.test.ts +6 -6
- package/src/console/server/flows-invoke.test.ts +2 -2
- package/src/console/server/flows.ts +2 -0
- package/src/console/server/gates.ts +8 -1
- package/src/console/server/operator-db.test.ts +4 -4
- package/src/console/server/operator-db.ts +22 -4
- package/src/console/server/security.gate.test.ts +3 -3
- package/src/console/server/store.test.ts +1 -1
- package/src/console/server/store.ts +11 -1
- package/src/console/ui/dist/assets/index-CjxwRGVv.js +10 -0
- package/src/console/ui/dist/assets/panel-access-BGv45snf.js +64 -0
- package/src/console/ui/dist/assets/{panel-ai-D_m6WQI8.js → panel-ai-B2S7LEii.js} +1 -1
- package/src/console/ui/dist/assets/{panel-architecture-CKnXFyUx.js → panel-architecture-D7UJh91v.js} +1 -1
- package/src/console/ui/dist/assets/{panel-channels-DCDd4WAC.js → panel-channels-9T3ybqRu.js} +1 -1
- package/src/console/ui/dist/assets/panel-clock-Cb1UXGRQ.js +1 -0
- package/src/console/ui/dist/assets/{panel-diff-cdonmH8c.js → panel-diff-DmYbKWmN.js} +1 -1
- package/src/console/ui/dist/assets/panel-flows-PiHwT55z.js +48 -0
- package/src/console/ui/dist/assets/{panel-gates-B5eTE8XH.js → panel-gates-BQGYXvjT.js} +1 -1
- package/src/console/ui/dist/assets/panel-overview-BBnRO18l.js +1 -0
- package/src/console/ui/dist/assets/{panel-plugins-Cj7DK1er.js → panel-plugins-D0PsmVw2.js} +1 -1
- package/src/console/ui/dist/assets/panel-runs-CWuRDe0r.js +1 -0
- package/src/console/ui/dist/assets/{panel-signals-whmDXIg3.js → panel-signals-Bbg4ewpP.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-CEMHLvaw.js → panel-store-CPCbsDRa.js} +1 -1
- package/src/console/ui/dist/assets/panel-traces-DVAzuA_S.js +1 -0
- package/src/console/ui/dist/assets/{panel-vault-C9wjbki8.js → panel-vault-D1_MvOmo.js} +1 -1
- package/src/console/ui/dist/assets/{rolldown-runtime-CNC7AqOf.js → rolldown-runtime-B0Z9INg1.js} +1 -1
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/gates/fixture.ts +4 -0
- package/src/console/ui/gates/types.ts +2 -0
- package/src/console/ui/shell/client.ts +1 -0
- package/src/docker/compose.ts +5 -0
- package/src/docker/docker.test.ts +41 -0
- package/src/docker/recipes/index.ts +10 -2
- package/src/docker/recipes/meilisearch.ts +31 -0
- package/src/drivers/conformance.test.ts +16 -1
- package/src/drivers/conformance.ts +40 -3
- package/src/drivers/index.ts +14 -2
- package/src/drivers/libsql.ts +4 -4
- package/src/drivers/meilisearch.integration.test.ts +77 -0
- package/src/drivers/meilisearch.test.ts +181 -0
- package/src/drivers/meilisearch.ts +208 -0
- package/src/drivers/memory.ts +4 -4
- package/src/drivers/pgvector.ts +6 -6
- package/src/drivers/types.ts +93 -12
- package/src/drivers/vault-driver-removal.test.ts +6 -0
- package/src/drivers/vault-types.ts +4 -4
- package/src/elements/ai/runtime.ts +6 -0
- package/src/elements/ai.test.ts +22 -0
- package/src/elements/gate/boot.ts +136 -0
- package/src/elements/gate/config.ts +69 -0
- package/src/elements/gate/declare.ts +51 -1
- package/src/elements/gate/runtime.ts +3 -1
- package/src/elements/gate.test.ts +77 -0
- package/src/elements/gate.ts +20 -1
- package/src/elements/index.ts +8 -0
- package/src/elements/store/index-boot.test.ts +49 -7
- package/src/elements/store/runtime.ts +50 -15
- package/src/elements/store.ts +2 -0
- package/src/elements/vault.test.ts +27 -4
- package/src/elements/vault.ts +1 -1
- package/src/index.ts +15 -0
- package/src/kernel/app.ts +253 -32
- package/src/kernel/boot-bind/store.test.ts +9 -0
- package/src/kernel/boot-bind/store.ts +30 -2
- package/src/kernel/boot.test.ts +40 -3
- package/src/kernel/boot.ts +8 -0
- package/src/kernel/call.test.ts +46 -2
- package/src/kernel/concurrency.test.ts +58 -0
- package/src/kernel/concurrency.ts +48 -0
- package/src/kernel/edge.test.ts +3 -3
- package/src/kernel/flow.test.ts +2 -2
- package/src/kernel/fx.test.ts +12 -2
- package/src/kernel/fx.ts +97 -5
- package/src/kernel/hooks.test.ts +4 -4
- package/src/kernel/index.ts +22 -1
- package/src/kernel/pipeline.test.ts +12 -8
- package/src/kernel/pipeline.ts +23 -4
- package/src/kernel/plugin/decorate.test.ts +3 -3
- package/src/kernel/plugin/scoping.test.ts +3 -3
- package/src/kernel/plugin-elements.test.ts +51 -0
- package/src/kernel/plugin-needs.test.ts +83 -0
- package/src/kernel/plugin-needs.ts +129 -0
- package/src/kernel/plugin.ts +101 -0
- package/src/kernel/redacted.ts +74 -0
- package/src/kernel/registry-isolation.test.ts +5 -5
- package/src/kernel/registry.ts +102 -3
- package/src/kernel/router.ts +3 -3
- package/src/manifest/types.ts +2 -0
- package/src/plugins/anonymous.ts +58 -0
- package/src/plugins/auth/shared.ts +121 -0
- package/src/plugins/auth-methods.test.ts +176 -0
- package/src/plugins/compression.test.ts +5 -5
- package/src/plugins/config-source.test.ts +1 -1
- package/src/plugins/cors.test.ts +16 -10
- package/src/plugins/csrf.test.ts +1 -1
- package/src/plugins/email-otp.ts +161 -0
- package/src/plugins/index.ts +31 -0
- package/src/plugins/ip-allowlist.test.ts +19 -9
- package/src/plugins/magic-link.ts +163 -0
- package/src/plugins/maintenance-mode.test.ts +9 -5
- package/src/plugins/passkey.ts +216 -0
- package/src/plugins/phone-number.ts +149 -0
- package/src/plugins/security-headers.test.ts +14 -14
- package/src/plugins/two-factor.ts +249 -0
- package/src/plugins/username.ts +148 -0
- package/src/runs/runs.test.ts +6 -2
- package/src/runtime/primitives.ts +37 -4
- package/src/runtime/serve.test.ts +3 -2
- package/src/runtime/types.ts +25 -2
- package/src/test/create-test-app.test.ts +1 -1
- package/src/test/create-test-app.ts +4 -1
- package/src/test/provisions.integration.test.ts +2 -2
- package/site/content/docs/get-started/comparison.mdx +0 -65
- package/src/console/ui/dist/assets/index-CrKMmO__.js +0 -10
- package/src/console/ui/dist/assets/panel-access-C0J2D-a2.js +0 -64
- package/src/console/ui/dist/assets/panel-clock-DjGGFPzr.js +0 -1
- package/src/console/ui/dist/assets/panel-flows-DlCU5zjA.js +0 -45
- package/src/console/ui/dist/assets/panel-overview-BsFvDdts.js +0 -1
- package/src/console/ui/dist/assets/panel-runs-C0gmnoYL.js +0 -1
- package/src/console/ui/dist/assets/panel-traces-BDiAuVSK.js +0 -1
- package/src/drivers/vault-infisical.ts +0 -57
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `oke({ gate })` — nested Gate bag (auth · policies · rate · posture).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { resolveGateAuth, type GateAuthOptions, type ResolvedGateAuth } from "../../auth/config.ts";
|
|
6
|
+
import type { GateDecl } from "./declare.ts";
|
|
7
|
+
|
|
8
|
+
/** Rate-limit defaults under Gate. */
|
|
9
|
+
export interface GateRateLimitOptions {
|
|
10
|
+
/** When true, auth Flows attach stricter rate presets (default true when auth enabled). */
|
|
11
|
+
readonly enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Nested Gate options for {@link oke}.
|
|
16
|
+
*
|
|
17
|
+
* Replaces root `auth` / `gates` / `unguardedHttp`.
|
|
18
|
+
*/
|
|
19
|
+
export interface GateOptions {
|
|
20
|
+
/** Builtin hybrid-session auth + core Flows under `basePath`. */
|
|
21
|
+
readonly auth?: GateAuthOptions;
|
|
22
|
+
/** Named policy / rate declarations for the Gate runtime. */
|
|
23
|
+
readonly policies?: readonly GateDecl[];
|
|
24
|
+
/** Global rate-limit posture (auth path presets). */
|
|
25
|
+
readonly rateLimit?: GateRateLimitOptions;
|
|
26
|
+
/**
|
|
27
|
+
* HTTP auth-posture enforcement at boot.
|
|
28
|
+
* Default `"deny"`. `"allow"` is honoured only when `env === "test"`.
|
|
29
|
+
*/
|
|
30
|
+
readonly unguardedHttp?: "deny" | "allow";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Fully resolved Gate config consumed by `oke` / boot. */
|
|
34
|
+
export interface ResolvedGateConfig {
|
|
35
|
+
readonly auth: ResolvedGateAuth | undefined;
|
|
36
|
+
readonly policies: readonly GateDecl[];
|
|
37
|
+
readonly rateLimitEnabled: boolean;
|
|
38
|
+
readonly unguardedHttp: "deny" | "allow";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Options for {@link resolveGateConfig}. */
|
|
42
|
+
export interface ResolveGateConfigOptions {
|
|
43
|
+
readonly gate?: GateOptions;
|
|
44
|
+
readonly env?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the nested `gate` bag for construction / boot.
|
|
49
|
+
*
|
|
50
|
+
* @param options - Gate bag + env (for auth secret rules)
|
|
51
|
+
*/
|
|
52
|
+
export function resolveGateConfig(options: ResolveGateConfigOptions = {}): ResolvedGateConfig {
|
|
53
|
+
const bag = options.gate ?? {};
|
|
54
|
+
const auth =
|
|
55
|
+
bag.auth !== undefined ? resolveGateAuth({ auth: bag.auth, env: options.env }) : undefined;
|
|
56
|
+
const rateLimitEnabled =
|
|
57
|
+
bag.rateLimit?.enabled !== undefined
|
|
58
|
+
? bag.rateLimit.enabled
|
|
59
|
+
: auth !== undefined
|
|
60
|
+
? true
|
|
61
|
+
: false;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
auth,
|
|
65
|
+
policies: bag.policies ?? [],
|
|
66
|
+
rateLimitEnabled,
|
|
67
|
+
unguardedHttp: bag.unguardedHttp ?? "deny",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -63,6 +63,11 @@ export interface PolicyGateDecl {
|
|
|
63
63
|
readonly name: string;
|
|
64
64
|
readonly check: (ctx: GatePolicyContext) => boolean | Promise<boolean>;
|
|
65
65
|
readonly description?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Scope strings this policy requires (set by {@link GateNamespace.scope}).
|
|
68
|
+
* Same string is the policy id and the scope checked on `auth.scopes`.
|
|
69
|
+
*/
|
|
70
|
+
readonly scopes?: readonly string[];
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
/** Rate gate declaration. */
|
|
@@ -94,6 +99,19 @@ export interface GateNamespace {
|
|
|
94
99
|
name: string,
|
|
95
100
|
checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
|
|
96
101
|
): PolicyGateDecl;
|
|
102
|
+
/**
|
|
103
|
+
* Shorthand for a scope check — single source of truth for the scope string.
|
|
104
|
+
*
|
|
105
|
+
* Equivalent to `gate.policy(name, ({ auth }) => auth.scopes.has(name))`.
|
|
106
|
+
*
|
|
107
|
+
* @param name - Scope id (also the policy name / Module:Action when it contains `:`)
|
|
108
|
+
*/
|
|
109
|
+
scope(name: string): PolicyGateDecl;
|
|
110
|
+
/**
|
|
111
|
+
* Explicit public sentinel — intentionally unauthenticated HTTP surface.
|
|
112
|
+
* Counts as declared auth posture for boot (see gate boot audit).
|
|
113
|
+
*/
|
|
114
|
+
readonly public: PolicyGateDecl;
|
|
97
115
|
/**
|
|
98
116
|
* Declare a rate limit (atomic Lua on the kv driver).
|
|
99
117
|
*
|
|
@@ -102,8 +120,11 @@ export interface GateNamespace {
|
|
|
102
120
|
rate(options: RateOptions): RateGateDecl;
|
|
103
121
|
}
|
|
104
122
|
|
|
123
|
+
/** Reserved policy name for {@link gate.public}. */
|
|
124
|
+
export const GATE_PUBLIC_NAME = "public";
|
|
125
|
+
|
|
105
126
|
/**
|
|
106
|
-
* Gate element namespace — `gate.policy` · `gate.rate`.
|
|
127
|
+
* Gate element namespace — `gate.policy` · `gate.scope` · `gate.public` · `gate.rate`.
|
|
107
128
|
*/
|
|
108
129
|
export const gate: GateNamespace = {
|
|
109
130
|
/**
|
|
@@ -116,6 +137,11 @@ export const gate: GateNamespace = {
|
|
|
116
137
|
name: string,
|
|
117
138
|
checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
|
|
118
139
|
): PolicyGateDecl {
|
|
140
|
+
if (name === GATE_PUBLIC_NAME) {
|
|
141
|
+
throw new TypeError(
|
|
142
|
+
'gate.policy: name "public" is reserved — use gate.public for intentionally unauthenticated surfaces',
|
|
143
|
+
);
|
|
144
|
+
}
|
|
119
145
|
if (typeof checkOrOptions === "function") {
|
|
120
146
|
return { kind: "policy", name, check: checkOrOptions };
|
|
121
147
|
}
|
|
@@ -129,6 +155,30 @@ export const gate: GateNamespace = {
|
|
|
129
155
|
};
|
|
130
156
|
},
|
|
131
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Shorthand for a scope check — single source of truth for the scope string.
|
|
160
|
+
*
|
|
161
|
+
* @param name - Scope id (also the policy name)
|
|
162
|
+
*/
|
|
163
|
+
scope(name: string): PolicyGateDecl {
|
|
164
|
+
if (name === GATE_PUBLIC_NAME) {
|
|
165
|
+
throw new TypeError('gate.scope: name "public" is reserved — use gate.public');
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
kind: "policy",
|
|
169
|
+
name,
|
|
170
|
+
check: ({ auth }) => auth.scopes.has(name),
|
|
171
|
+
scopes: [name],
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
public: {
|
|
176
|
+
kind: "policy",
|
|
177
|
+
name: GATE_PUBLIC_NAME,
|
|
178
|
+
check: () => true,
|
|
179
|
+
description: "Intentionally unauthenticated (public) surface",
|
|
180
|
+
},
|
|
181
|
+
|
|
132
182
|
/**
|
|
133
183
|
* Declare a rate limit (atomic Lua on the kv driver).
|
|
134
184
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { parseDurationMs } from "../clock/duration.ts";
|
|
6
|
-
import type
|
|
6
|
+
import { gate, type GateDecl, type GatePolicyContext, type RateGateDecl } from "./declare.ts";
|
|
7
7
|
import { takeRate } from "./strategies.ts";
|
|
8
8
|
|
|
9
9
|
/** KV surface required for rate gates. */
|
|
@@ -58,6 +58,8 @@ export interface GateRuntime {
|
|
|
58
58
|
*/
|
|
59
59
|
export function createGateRuntime(options: CreateGateRuntimeOptions = {}): GateRuntime {
|
|
60
60
|
const map = new Map<string, GateDecl>();
|
|
61
|
+
// Built-in sentinel — `.gate(gate.public)` works without listing it in `oke({ gates })`.
|
|
62
|
+
map.set(gate.public.name, gate.public);
|
|
61
63
|
for (const g of options.gates ?? []) {
|
|
62
64
|
map.set(g.name, g);
|
|
63
65
|
}
|
|
@@ -11,10 +11,12 @@ import { createRedisFakeClient, redisDriver } from "../drivers/redis.ts";
|
|
|
11
11
|
import type { Manifest } from "../manifest/types.ts";
|
|
12
12
|
import {
|
|
13
13
|
ALL_RATE_STRATEGIES,
|
|
14
|
+
assertHttpGatePosture,
|
|
14
15
|
createGateRuntime,
|
|
15
16
|
deriveModuleActions,
|
|
16
17
|
formatGatesList,
|
|
17
18
|
gate,
|
|
19
|
+
GateBootError,
|
|
18
20
|
takeRate,
|
|
19
21
|
} from "./gate.ts";
|
|
20
22
|
|
|
@@ -35,6 +37,37 @@ describe("gate declaration", () => {
|
|
|
35
37
|
expect(fair.strategy).toBe("sliding-window-counter");
|
|
36
38
|
});
|
|
37
39
|
|
|
40
|
+
test("scope is sugar over policy with auth.scopes.has(name)", async () => {
|
|
41
|
+
const canBook = gate.scope("booking:create");
|
|
42
|
+
expect(canBook.kind).toBe("policy");
|
|
43
|
+
expect(canBook.name).toBe("booking:create");
|
|
44
|
+
expect(canBook.scopes).toEqual(["booking:create"]);
|
|
45
|
+
expect(
|
|
46
|
+
await canBook.check({
|
|
47
|
+
auth: { userId: "u1", scopes: new Set(["booking:create"]), verified: true },
|
|
48
|
+
operator: { id: null },
|
|
49
|
+
}),
|
|
50
|
+
).toBe(true);
|
|
51
|
+
expect(
|
|
52
|
+
await canBook.check({
|
|
53
|
+
auth: { userId: "u1", scopes: new Set(), verified: true },
|
|
54
|
+
operator: { id: null },
|
|
55
|
+
}),
|
|
56
|
+
).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("public sentinel always allows and reserves the name", async () => {
|
|
60
|
+
expect(gate.public.name).toBe("public");
|
|
61
|
+
expect(
|
|
62
|
+
await gate.public.check({
|
|
63
|
+
auth: { userId: null, scopes: new Set() },
|
|
64
|
+
operator: { id: null },
|
|
65
|
+
}),
|
|
66
|
+
).toBe(true);
|
|
67
|
+
expect(() => gate.policy("public", () => true)).toThrow(/reserved/);
|
|
68
|
+
expect(() => gate.scope("public")).toThrow(/reserved/);
|
|
69
|
+
});
|
|
70
|
+
|
|
38
71
|
test("defaults strategy to sliding-window-counter", () => {
|
|
39
72
|
const r = gate.rate({ max: 10, per: "1s" });
|
|
40
73
|
expect(r.strategy).toBe("sliding-window-counter");
|
|
@@ -117,7 +150,51 @@ describe("five rate strategies — concurrency", () => {
|
|
|
117
150
|
});
|
|
118
151
|
});
|
|
119
152
|
|
|
153
|
+
describe("gate boot posture", () => {
|
|
154
|
+
const unguarded = [
|
|
155
|
+
{
|
|
156
|
+
trigger: { kind: "http" as const, method: "GET", path: "/health", gates: [] as const },
|
|
157
|
+
flow: { name: "health.ping" },
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
test("GateBootError lists every unguarded HTTP trigger", () => {
|
|
162
|
+
expect(() =>
|
|
163
|
+
assertHttpGatePosture([
|
|
164
|
+
...unguarded,
|
|
165
|
+
{
|
|
166
|
+
trigger: { kind: "http", method: "POST", path: "/x", gates: [gate.public] },
|
|
167
|
+
flow: { name: "x.public" },
|
|
168
|
+
},
|
|
169
|
+
]),
|
|
170
|
+
).toThrow(GateBootError);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("unguardedHttp allow skips audit only when env is test", () => {
|
|
174
|
+
expect(() =>
|
|
175
|
+
assertHttpGatePosture(unguarded, { unguardedHttp: "allow", env: "test" }),
|
|
176
|
+
).not.toThrow();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("unguardedHttp allow has no effect outside env === test", () => {
|
|
180
|
+
for (const env of ["local", "prod", "docker"] as const) {
|
|
181
|
+
expect(() => assertHttpGatePosture(unguarded, { unguardedHttp: "allow", env })).toThrow(
|
|
182
|
+
GateBootError,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
120
188
|
describe("gate runtime", () => {
|
|
189
|
+
test("public sentinel is always registered", async () => {
|
|
190
|
+
const rt = createGateRuntime();
|
|
191
|
+
const ok = await rt.allow(["public"], {
|
|
192
|
+
auth: { userId: null, scopes: new Set() },
|
|
193
|
+
operator: { id: null },
|
|
194
|
+
});
|
|
195
|
+
expect(ok).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
121
198
|
test("policy then rate — evaluation order", async () => {
|
|
122
199
|
const kv = await memoryKvDriver.open({ name: "gate-rt" });
|
|
123
200
|
const member = gate.policy("member", ({ auth }) => !!auth.verified);
|
package/src/elements/gate.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export { gate } from "./gate/declare.ts";
|
|
11
|
+
export { gate, GATE_PUBLIC_NAME } from "./gate/declare.ts";
|
|
12
12
|
export type {
|
|
13
13
|
GateDecl,
|
|
14
14
|
GatePolicyContext,
|
|
@@ -18,6 +18,25 @@ export type {
|
|
|
18
18
|
RateOptions,
|
|
19
19
|
} from "./gate/declare.ts";
|
|
20
20
|
|
|
21
|
+
export {
|
|
22
|
+
resolveGateConfig,
|
|
23
|
+
type GateOptions,
|
|
24
|
+
type GateRateLimitOptions,
|
|
25
|
+
type ResolvedGateConfig,
|
|
26
|
+
type ResolveGateConfigOptions,
|
|
27
|
+
} from "./gate/config.ts";
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
GateBootError,
|
|
31
|
+
assertHttpGatePosture,
|
|
32
|
+
collectUnguardedHttpGaps,
|
|
33
|
+
hasHttpGatePosture,
|
|
34
|
+
isPublicGateRef,
|
|
35
|
+
unguardedHttpAllowActive,
|
|
36
|
+
type AssertHttpGatePostureOptions,
|
|
37
|
+
type GatePostureGap,
|
|
38
|
+
} from "./gate/boot.ts";
|
|
39
|
+
|
|
21
40
|
export {
|
|
22
41
|
createGateRuntime,
|
|
23
42
|
type CreateGateRuntimeOptions,
|
package/src/elements/index.ts
CHANGED
|
@@ -62,6 +62,13 @@ export type { ClockDecl, ClockOptions, ClockRuntime, TimeTravel, DurableResult }
|
|
|
62
62
|
|
|
63
63
|
export {
|
|
64
64
|
gate,
|
|
65
|
+
GATE_PUBLIC_NAME,
|
|
66
|
+
GateBootError,
|
|
67
|
+
assertHttpGatePosture,
|
|
68
|
+
collectUnguardedHttpGaps,
|
|
69
|
+
hasHttpGatePosture,
|
|
70
|
+
isPublicGateRef,
|
|
71
|
+
unguardedHttpAllowActive,
|
|
65
72
|
createGateRuntime,
|
|
66
73
|
takeRate,
|
|
67
74
|
deriveModuleActions,
|
|
@@ -72,6 +79,7 @@ export {
|
|
|
72
79
|
export type {
|
|
73
80
|
GateDecl,
|
|
74
81
|
GatePolicyContext,
|
|
82
|
+
GatePostureGap,
|
|
75
83
|
GateRuntime,
|
|
76
84
|
RateOptions,
|
|
77
85
|
RateTakeResult,
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
createStoreRuntime,
|
|
23
23
|
index as declareIndex,
|
|
24
24
|
sql as declareSql,
|
|
25
|
-
type IndexStoreFxHandle,
|
|
26
25
|
type SqlStoreHandle,
|
|
26
|
+
type VectorIndexStoreFxHandle,
|
|
27
27
|
} from "../store.ts";
|
|
28
28
|
|
|
29
29
|
const WRITE_CTX = (name: string) => ({
|
|
@@ -67,7 +67,7 @@ describe("store.index boot wiring — memory", () => {
|
|
|
67
67
|
test("boot with no configured index driver resolves memory end to end", async () => {
|
|
68
68
|
const kb = declareIndex("kb", { dims: 3 });
|
|
69
69
|
const runtime = bindStore({ stores: [kb] }, "local", () => Date.now());
|
|
70
|
-
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as
|
|
70
|
+
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
71
71
|
expect(handle.driverId).toBe("memory");
|
|
72
72
|
await handle.upsert("near", [1, 0, 0]);
|
|
73
73
|
await handle.upsert("far", [0, 1, 0]);
|
|
@@ -89,14 +89,14 @@ describe("store.index boot wiring — libsql", () => {
|
|
|
89
89
|
"local",
|
|
90
90
|
() => Date.now(),
|
|
91
91
|
);
|
|
92
|
-
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as
|
|
92
|
+
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
93
93
|
expect(handle.driverId).toBe("libsql");
|
|
94
94
|
|
|
95
95
|
await handle.upsert("near", [1, 0.1, 0], { label: "near" });
|
|
96
96
|
await handle.upsert("far", [0, 1, 0]);
|
|
97
97
|
await handle.upsert("farther", [-1, 0, 0]);
|
|
98
98
|
const hits = await handle.search([1, 0, 0], 3);
|
|
99
|
-
expect(hits.map((h) => h.id)).toEqual(["near", "far", "farther"]);
|
|
99
|
+
expect(hits.map((h: { id: string }) => h.id)).toEqual(["near", "far", "farther"]);
|
|
100
100
|
expect(hits[0]!.score).toBeGreaterThan(hits[1]!.score);
|
|
101
101
|
expect(hits[0]!.meta).toEqual({ label: "near" });
|
|
102
102
|
expect(await handle.delete("farther")).toBe(true);
|
|
@@ -120,7 +120,7 @@ describe("store.index boot wiring — libsql", () => {
|
|
|
120
120
|
index: { kb: { url: ":memory:" } },
|
|
121
121
|
});
|
|
122
122
|
// Index first: it must open the connection the sql facet then reuses.
|
|
123
|
-
const idxHandle = (await runtime.open(kb, WRITE_CTX("kb"))) as
|
|
123
|
+
const idxHandle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
124
124
|
await idxHandle.upsert("a", [1, 0, 0]);
|
|
125
125
|
const sqlHandle = (await runtime.open(docs, {
|
|
126
126
|
effects: { reads: ["sql:docs"], writes: ["sql:docs"] },
|
|
@@ -161,14 +161,14 @@ describe("store.index boot wiring — pglite + pgvector", () => {
|
|
|
161
161
|
"local",
|
|
162
162
|
() => Date.now(),
|
|
163
163
|
);
|
|
164
|
-
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as
|
|
164
|
+
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
165
165
|
expect(handle.driverId).toBe("pgvector");
|
|
166
166
|
|
|
167
167
|
await handle.upsert("near", [1, 0.1, 0], { label: "near" });
|
|
168
168
|
await handle.upsert("far", [0, 1, 0]);
|
|
169
169
|
await handle.upsert("farther", [-1, 0, 0]);
|
|
170
170
|
const hits = await handle.search([1, 0, 0], 3);
|
|
171
|
-
expect(hits.map((h) => h.id)).toEqual(["near", "far", "farther"]);
|
|
171
|
+
expect(hits.map((h: { id: string }) => h.id)).toEqual(["near", "far", "farther"]);
|
|
172
172
|
expect(hits[0]!.score).toBeGreaterThan(hits[1]!.score);
|
|
173
173
|
expect(hits[0]!.meta).toEqual({ label: "near" });
|
|
174
174
|
expect(await handle.delete("farther")).toBe(true);
|
|
@@ -225,6 +225,48 @@ describe("store.index boot wiring — fail loud", () => {
|
|
|
225
225
|
await expect(runtime.open(kb, WRITE_CTX("kb"))).rejects.toThrow(/cannot share sql driver/);
|
|
226
226
|
await runtime.close();
|
|
227
227
|
});
|
|
228
|
+
|
|
229
|
+
test("meilisearch without OKE_STORE_INDEX_URL throws at bind — never sqlUrl, never memory", () => {
|
|
230
|
+
const prev = process.env.OKE_STORE_INDEX_URL;
|
|
231
|
+
delete process.env.OKE_STORE_INDEX_URL;
|
|
232
|
+
try {
|
|
233
|
+
const kb = declareIndex("kb");
|
|
234
|
+
expect(() =>
|
|
235
|
+
bindStore(
|
|
236
|
+
{ config: { drivers: { store: { index: { local: "meilisearch" } } } }, stores: [kb] },
|
|
237
|
+
"local",
|
|
238
|
+
() => Date.now(),
|
|
239
|
+
),
|
|
240
|
+
).toThrow(/OKE_STORE_INDEX_URL/);
|
|
241
|
+
} finally {
|
|
242
|
+
if (prev === undefined) delete process.env.OKE_STORE_INDEX_URL;
|
|
243
|
+
else process.env.OKE_STORE_INDEX_URL = prev;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("meilisearch binds its own URL + key (never the sql connection)", async () => {
|
|
248
|
+
const prevUrl = process.env.OKE_STORE_INDEX_URL;
|
|
249
|
+
const prevKey = process.env.OKE_STORE_INDEX_KEY;
|
|
250
|
+
process.env.OKE_STORE_INDEX_URL = "http://127.0.0.1:7700";
|
|
251
|
+
process.env.OKE_STORE_INDEX_KEY = "master-key";
|
|
252
|
+
try {
|
|
253
|
+
const kb = declareIndex("kb");
|
|
254
|
+
const runtime = bindStore(
|
|
255
|
+
{ config: { drivers: { store: { index: { local: "meilisearch" } } } }, stores: [kb] },
|
|
256
|
+
"local",
|
|
257
|
+
() => Date.now(),
|
|
258
|
+
);
|
|
259
|
+
// No live server in unit tests: opening must fail loud (unreachable),
|
|
260
|
+
// proving the binding pointed at HTTP, not silently degraded to memory.
|
|
261
|
+
await expect(runtime.open(kb, WRITE_CTX("kb"))).rejects.toThrow(/meilisearch index/);
|
|
262
|
+
await runtime.close();
|
|
263
|
+
} finally {
|
|
264
|
+
if (prevUrl === undefined) delete process.env.OKE_STORE_INDEX_URL;
|
|
265
|
+
else process.env.OKE_STORE_INDEX_URL = prevUrl;
|
|
266
|
+
if (prevKey === undefined) delete process.env.OKE_STORE_INDEX_KEY;
|
|
267
|
+
else process.env.OKE_STORE_INDEX_KEY = prevKey;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
228
270
|
});
|
|
229
271
|
|
|
230
272
|
const PGVECTOR_URL = process.env.OKE_TEST_PGVECTOR_URL;
|
|
@@ -18,7 +18,12 @@ import type {
|
|
|
18
18
|
SqlDriver,
|
|
19
19
|
SqlRole,
|
|
20
20
|
SqlRow,
|
|
21
|
+
TextIndexSearchOptions,
|
|
22
|
+
TextIndexSearchResult,
|
|
23
|
+
VectorIndexDriver,
|
|
24
|
+
VectorIndexStore,
|
|
21
25
|
} from "../../drivers/types.ts";
|
|
26
|
+
import { indexDriverNeedsSql } from "../../drivers/types.ts";
|
|
22
27
|
import { buildClassificationMap, type MaskRowsOptions } from "./classify.ts";
|
|
23
28
|
import {
|
|
24
29
|
createStoreCache,
|
|
@@ -64,7 +69,15 @@ export interface CreateStoreRuntimeOptions {
|
|
|
64
69
|
readonly files?: Readonly<Record<string, { readonly root?: string; readonly client?: unknown }>>;
|
|
65
70
|
/** Index open options keyed by store name. */
|
|
66
71
|
readonly index?: Readonly<
|
|
67
|
-
Record<
|
|
72
|
+
Record<
|
|
73
|
+
string,
|
|
74
|
+
{
|
|
75
|
+
readonly dims?: number;
|
|
76
|
+
readonly url?: string;
|
|
77
|
+
readonly apiKey?: string;
|
|
78
|
+
readonly sql?: SqlConnection;
|
|
79
|
+
}
|
|
80
|
+
>
|
|
68
81
|
>;
|
|
69
82
|
/** Clock for cache TTLs. */
|
|
70
83
|
readonly now?: () => number;
|
|
@@ -115,18 +128,30 @@ export interface FilesStoreFxHandle {
|
|
|
115
128
|
list(prefix?: string): Promise<string[]>;
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
/**
|
|
119
|
-
export interface
|
|
131
|
+
/** Vector index handle on `fx.store`. */
|
|
132
|
+
export interface VectorIndexStoreFxHandle {
|
|
120
133
|
readonly ref: `index:${string}`;
|
|
121
134
|
readonly driverId: "memory" | "pgvector" | "libsql";
|
|
122
135
|
upsert(id: string, vector: readonly number[], meta?: Record<string, unknown>): Promise<void>;
|
|
123
136
|
search(
|
|
124
137
|
vector: readonly number[],
|
|
125
138
|
topK?: number,
|
|
126
|
-
): Promise<
|
|
139
|
+
): Promise<ReadonlyArray<{ id: string; score: number; meta?: Record<string, unknown> }>>;
|
|
127
140
|
delete(id: string): Promise<boolean>;
|
|
128
141
|
}
|
|
129
142
|
|
|
143
|
+
/** Full-text index handle on `fx.store`. */
|
|
144
|
+
export interface TextIndexStoreFxHandle {
|
|
145
|
+
readonly ref: `index:${string}`;
|
|
146
|
+
readonly driverId: "meilisearch";
|
|
147
|
+
upsert(id: string, document: Record<string, unknown>): Promise<void>;
|
|
148
|
+
search(q: string, opts?: TextIndexSearchOptions): Promise<TextIndexSearchResult>;
|
|
149
|
+
delete(id: string): Promise<boolean>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Index handle on `fx.store` — discriminated by `driverId`. */
|
|
153
|
+
export type IndexStoreFxHandle = VectorIndexStoreFxHandle | TextIndexStoreFxHandle;
|
|
154
|
+
|
|
130
155
|
/** Store runtime. */
|
|
131
156
|
export interface StoreRuntime {
|
|
132
157
|
/** Shared multi-tier cache. */
|
|
@@ -259,9 +284,9 @@ export function createStoreRuntime(options: CreateStoreRuntimeOptions): StoreRun
|
|
|
259
284
|
return conn;
|
|
260
285
|
}
|
|
261
286
|
|
|
262
|
-
/**
|
|
287
|
+
/** SQL-backed index drivers borrow the sql facet's connection. */
|
|
263
288
|
async function sqlConnForIndex(
|
|
264
|
-
driver:
|
|
289
|
+
driver: VectorIndexDriver & { id: "pgvector" | "libsql" },
|
|
265
290
|
url: string | undefined,
|
|
266
291
|
): Promise<SqlConnection> {
|
|
267
292
|
const sqlDriver = options.drivers.sql;
|
|
@@ -338,24 +363,34 @@ export function createStoreRuntime(options: CreateStoreRuntimeOptions): StoreRun
|
|
|
338
363
|
let idx = indexes.get(decl.name);
|
|
339
364
|
if (!idx) {
|
|
340
365
|
const binding = options.index?.[decl.name] ?? {};
|
|
341
|
-
let sql = binding.sql;
|
|
342
|
-
if (!sql && driver.id !== "memory") {
|
|
343
|
-
sql = await sqlConnForIndex(driver, binding.url);
|
|
344
|
-
}
|
|
345
366
|
idx = await driver.open({
|
|
346
367
|
name: decl.name,
|
|
347
368
|
dims: binding.dims ?? decl.dims ?? 3,
|
|
348
369
|
url: binding.url,
|
|
349
|
-
|
|
370
|
+
apiKey: binding.apiKey,
|
|
371
|
+
sql: indexDriverNeedsSql(driver)
|
|
372
|
+
? (binding.sql ?? (await sqlConnForIndex(driver, binding.url)))
|
|
373
|
+
: undefined,
|
|
350
374
|
});
|
|
351
375
|
indexes.set(decl.name, idx);
|
|
352
376
|
}
|
|
377
|
+
if (idx.driverId === "meilisearch") {
|
|
378
|
+
const text = idx;
|
|
379
|
+
return {
|
|
380
|
+
ref: `index:${decl.name}`,
|
|
381
|
+
driverId: text.driverId,
|
|
382
|
+
upsert: (id, document) => text.upsert(id, document),
|
|
383
|
+
search: (q, opts) => text.search(q, opts),
|
|
384
|
+
delete: (id) => text.delete(id),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const vector = idx as VectorIndexStore;
|
|
353
388
|
return {
|
|
354
389
|
ref: `index:${decl.name}`,
|
|
355
|
-
driverId:
|
|
356
|
-
upsert: (id,
|
|
357
|
-
search: (
|
|
358
|
-
delete: (id) =>
|
|
390
|
+
driverId: vector.driverId,
|
|
391
|
+
upsert: (id, vec, meta) => vector.upsert(id, vec, meta),
|
|
392
|
+
search: (vec, topK) => vector.search(vec, topK),
|
|
393
|
+
delete: (id) => vector.delete(id),
|
|
359
394
|
};
|
|
360
395
|
}
|
|
361
396
|
|
package/src/elements/store.ts
CHANGED
|
@@ -116,6 +116,8 @@ export type {
|
|
|
116
116
|
KvStoreFxHandle,
|
|
117
117
|
FilesStoreFxHandle,
|
|
118
118
|
IndexStoreFxHandle,
|
|
119
|
+
VectorIndexStoreFxHandle,
|
|
120
|
+
TextIndexStoreFxHandle,
|
|
119
121
|
} from "./store/runtime.ts";
|
|
120
122
|
|
|
121
123
|
export { fileKeyWarnings, contentAddressedKey, projectFileKeys } from "./store/files-policy.ts";
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { describe, expect, test } from "bun:test";
|
|
10
10
|
import { envVaultDriver, memoryVaultDriver } from "../drivers/index.ts";
|
|
11
11
|
import { createFx } from "../kernel/fx.ts";
|
|
12
|
+
import { REDACTED_PLACEHOLDER, Redacted } from "../kernel/redacted.ts";
|
|
12
13
|
import {
|
|
13
14
|
createVaultRuntime,
|
|
14
15
|
fingerprintSecretSync,
|
|
@@ -200,17 +201,39 @@ describe("redaction + fingerprints", () => {
|
|
|
200
201
|
const lines: Array<{ message: string; data?: Record<string, unknown> }> = [];
|
|
201
202
|
const { fx } = createFxContextWithVault(runtime, lines);
|
|
202
203
|
|
|
203
|
-
const
|
|
204
|
-
expect(
|
|
204
|
+
const wrapped = fx.vault("STRIPE_KEY");
|
|
205
|
+
expect(wrapped.reveal()).toBe(secret);
|
|
205
206
|
|
|
206
|
-
fx.log.info(`charging with ${
|
|
207
|
+
fx.log.info(`charging with ${wrapped}`, { key: wrapped, nested: { k: wrapped } });
|
|
208
|
+
// Revealed cleartext still hits the known-value substring scrub.
|
|
207
209
|
fx.log.error(secret, { stripe: secret });
|
|
208
210
|
|
|
209
211
|
for (const line of lines) {
|
|
210
212
|
expect(line.message).not.toContain(secret);
|
|
211
213
|
expect(JSON.stringify(line.data ?? {})).not.toContain(secret);
|
|
212
|
-
expect(line.message).toContain(SECRET_MASK);
|
|
213
214
|
}
|
|
215
|
+
expect(lines[0]!.message).toContain(REDACTED_PLACEHOLDER);
|
|
216
|
+
expect(lines[1]!.message).toContain(SECRET_MASK);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("Redacted from fx.vault is masked in fx.log without a vault runtime", () => {
|
|
220
|
+
const secret = "sk_no_vault_runtime";
|
|
221
|
+
const lines: Array<{ message: string; data?: Record<string, unknown> }> = [];
|
|
222
|
+
const fx = createFx({
|
|
223
|
+
flow: "test.redacted",
|
|
224
|
+
secrets: { STRIPE_KEY: secret },
|
|
225
|
+
onLog: (_level, message, data) => lines.push({ message, data }),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const wrapped = fx.vault("STRIPE_KEY");
|
|
229
|
+
expect(wrapped).toBeInstanceOf(Redacted);
|
|
230
|
+
expect(String(wrapped)).toBe(REDACTED_PLACEHOLDER);
|
|
231
|
+
expect(`${wrapped}`).toBe(REDACTED_PLACEHOLDER);
|
|
232
|
+
expect(JSON.stringify({ v: wrapped })).toBe(`{"v":"${REDACTED_PLACEHOLDER}"}`);
|
|
233
|
+
|
|
234
|
+
fx.log.info("paying", { key: wrapped, nested: { k: [wrapped] } });
|
|
235
|
+
expect(JSON.stringify(lines[0]!.data)).not.toContain(secret);
|
|
236
|
+
expect(lines[0]!.data?.key).toBe(REDACTED_PLACEHOLDER);
|
|
214
237
|
});
|
|
215
238
|
|
|
216
239
|
test("fingerprint is stable and not the value", async () => {
|
package/src/elements/vault.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Vault element — protected knowledge.
|
|
3
3
|
*
|
|
4
4
|
* Physics: secrets · config · environment.
|
|
5
|
-
* Drivers: `env` · `openbao` · `
|
|
5
|
+
* Drivers: `env` · `openbao` · `managed`.
|
|
6
6
|
*
|
|
7
7
|
* A declaration is a contract, never a value. Boot validates every contract
|
|
8
8
|
* and lists all gaps at once. Logs/traces receive fingerprints and redaction
|
package/src/index.ts
CHANGED
|
@@ -34,12 +34,20 @@ export {
|
|
|
34
34
|
journey,
|
|
35
35
|
unit,
|
|
36
36
|
rateLimit,
|
|
37
|
+
Redacted,
|
|
38
|
+
isRedacted,
|
|
39
|
+
maskRedactedDeep,
|
|
40
|
+
REDACTED_PLACEHOLDER,
|
|
41
|
+
PluginNeedsError,
|
|
42
|
+
assertPluginNeeds,
|
|
43
|
+
freezePrincipal,
|
|
37
44
|
type OkeApp,
|
|
38
45
|
type OkeOptions,
|
|
39
46
|
type FlowDef,
|
|
40
47
|
type FlowFailure,
|
|
41
48
|
type FlowErrorValue,
|
|
42
49
|
type Fx,
|
|
50
|
+
type FxPrincipal,
|
|
43
51
|
type Binding,
|
|
44
52
|
type PluginDef,
|
|
45
53
|
type PluginCapabilities,
|
|
@@ -90,13 +98,20 @@ export {
|
|
|
90
98
|
|
|
91
99
|
export {
|
|
92
100
|
gate,
|
|
101
|
+
GATE_PUBLIC_NAME,
|
|
102
|
+
GateBootError,
|
|
103
|
+
assertHttpGatePosture,
|
|
93
104
|
createGateRuntime,
|
|
105
|
+
resolveGateConfig,
|
|
94
106
|
takeRate,
|
|
95
107
|
deriveModuleActions,
|
|
96
108
|
ALL_RATE_STRATEGIES,
|
|
97
109
|
type GateDecl,
|
|
110
|
+
type GateOptions,
|
|
111
|
+
type GatePostureGap,
|
|
98
112
|
type GateRuntime,
|
|
99
113
|
type RateOptions,
|
|
114
|
+
type ResolvedGateConfig,
|
|
100
115
|
} from "./elements/gate.ts";
|
|
101
116
|
|
|
102
117
|
export {
|