@fuzdev/fuz_app 0.64.0 → 0.66.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.
Files changed (223) hide show
  1. package/dist/actions/CLAUDE.md +510 -946
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +1 -1
  4. package/dist/actions/action_event_data.d.ts +1 -1
  5. package/dist/actions/broadcast_api.d.ts +1 -1
  6. package/dist/actions/broadcast_api.js +1 -1
  7. package/dist/actions/cancel.d.ts +2 -2
  8. package/dist/actions/cancel.js +3 -3
  9. package/dist/actions/connection_closer.d.ts +1 -4
  10. package/dist/actions/connection_closer.d.ts.map +1 -1
  11. package/dist/actions/connection_closer.js +1 -4
  12. package/dist/actions/register_action_ws.d.ts +2 -2
  13. package/dist/actions/register_ws_endpoint.d.ts +1 -1
  14. package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
  15. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  16. package/dist/actions/transports_ws_auth_guard.js +1 -2
  17. package/dist/auth/CLAUDE.md +570 -1871
  18. package/dist/auth/account_schema.d.ts +1 -1
  19. package/dist/auth/account_schema.d.ts.map +1 -1
  20. package/dist/auth/api_token_queries.js +1 -1
  21. package/dist/auth/audit_log_ddl.d.ts +1 -1
  22. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  23. package/dist/auth/audit_log_ddl.js +1 -1
  24. package/dist/auth/audit_log_schema.js +2 -2
  25. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  26. package/dist/auth/bootstrap_account.js +1 -5
  27. package/dist/auth/bootstrap_routes.d.ts +7 -1
  28. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  29. package/dist/auth/bootstrap_routes.js +15 -11
  30. package/dist/auth/daemon_token_middleware.d.ts +15 -5
  31. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  32. package/dist/auth/daemon_token_middleware.js +24 -15
  33. package/dist/auth/invite_queries.d.ts +17 -7
  34. package/dist/auth/invite_queries.d.ts.map +1 -1
  35. package/dist/auth/invite_queries.js +19 -8
  36. package/dist/auth/keyring.d.ts +6 -6
  37. package/dist/auth/keyring.js +8 -8
  38. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  39. package/dist/auth/role_grant_offer_actions.js +4 -2
  40. package/dist/auth/signup_routes.d.ts +47 -1
  41. package/dist/auth/signup_routes.d.ts.map +1 -1
  42. package/dist/auth/signup_routes.js +103 -52
  43. package/dist/db/create_db.d.ts.map +1 -1
  44. package/dist/db/create_db.js +13 -0
  45. package/dist/dev/setup.d.ts +2 -2
  46. package/dist/dev/setup.js +3 -3
  47. package/dist/env/resolve.d.ts +44 -7
  48. package/dist/env/resolve.d.ts.map +1 -1
  49. package/dist/env/resolve.js +94 -27
  50. package/dist/http/CLAUDE.md +243 -522
  51. package/dist/http/error_schemas.d.ts +0 -4
  52. package/dist/http/error_schemas.d.ts.map +1 -1
  53. package/dist/http/error_schemas.js +0 -4
  54. package/dist/http/ip_canonical.d.ts +5 -4
  55. package/dist/http/ip_canonical.d.ts.map +1 -1
  56. package/dist/http/ip_canonical.js +8 -4
  57. package/dist/http/jsonrpc.d.ts +23 -7
  58. package/dist/http/jsonrpc.d.ts.map +1 -1
  59. package/dist/http/jsonrpc.js +19 -3
  60. package/dist/http/origin.d.ts +1 -1
  61. package/dist/http/origin.js +1 -1
  62. package/dist/http/surface.d.ts +9 -2
  63. package/dist/http/surface.d.ts.map +1 -1
  64. package/dist/runtime/mock.d.ts +1 -1
  65. package/dist/runtime/mock.js +2 -2
  66. package/dist/server/app_server.d.ts +41 -10
  67. package/dist/server/app_server.d.ts.map +1 -1
  68. package/dist/server/app_server.js +10 -4
  69. package/dist/server/env.d.ts +7 -7
  70. package/dist/server/env.d.ts.map +1 -1
  71. package/dist/server/env.js +14 -14
  72. package/dist/server/static.d.ts +4 -4
  73. package/dist/server/static.js +7 -7
  74. package/dist/testing/CLAUDE.md +740 -418
  75. package/dist/testing/admin_integration.d.ts +18 -23
  76. package/dist/testing/admin_integration.d.ts.map +1 -1
  77. package/dist/testing/admin_integration.js +230 -216
  78. package/dist/testing/app_server.d.ts +141 -39
  79. package/dist/testing/app_server.d.ts.map +1 -1
  80. package/dist/testing/app_server.js +157 -44
  81. package/dist/testing/audit_completeness.d.ts +25 -22
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +198 -159
  84. package/dist/testing/bootstrap_success.d.ts +28 -0
  85. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  86. package/dist/testing/bootstrap_success.js +144 -0
  87. package/dist/testing/cross_backend/backend_config.d.ts +113 -0
  88. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -0
  89. package/dist/testing/cross_backend/backend_config.js +1 -0
  90. package/dist/testing/cross_backend/bench/bench_report.d.ts +46 -0
  91. package/dist/testing/cross_backend/bench/bench_report.d.ts.map +1 -0
  92. package/dist/testing/cross_backend/bench/bench_report.js +83 -0
  93. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +44 -0
  94. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts.map +1 -0
  95. package/dist/testing/cross_backend/bench/run_cross_impl_bench.js +38 -0
  96. package/dist/testing/cross_backend/bench/scenario.d.ts +57 -0
  97. package/dist/testing/cross_backend/bench/scenario.d.ts.map +1 -0
  98. package/dist/testing/cross_backend/bench/scenario.js +28 -0
  99. package/dist/testing/cross_backend/bootstrap_backend.d.ts +41 -0
  100. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -0
  101. package/dist/testing/cross_backend/bootstrap_backend.js +34 -0
  102. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +24 -0
  103. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -0
  104. package/dist/testing/cross_backend/build_test_backend_paths.js +33 -0
  105. package/dist/testing/cross_backend/capabilities.d.ts +65 -0
  106. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/capabilities.js +47 -0
  108. package/dist/testing/cross_backend/default_backend_configs.d.ts +122 -0
  109. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -0
  110. package/dist/testing/cross_backend/default_backend_configs.js +111 -0
  111. package/dist/testing/cross_backend/default_secrets.d.ts +40 -0
  112. package/dist/testing/cross_backend/default_secrets.d.ts.map +1 -0
  113. package/dist/testing/cross_backend/default_secrets.js +39 -0
  114. package/dist/testing/cross_backend/default_spine_surface.d.ts +64 -0
  115. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -0
  116. package/dist/testing/cross_backend/default_spine_surface.js +121 -0
  117. package/dist/testing/cross_backend/setup.d.ts +451 -0
  118. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  119. package/dist/testing/cross_backend/setup.js +581 -0
  120. package/dist/testing/cross_backend/spawn_backend.d.ts +58 -0
  121. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -0
  122. package/dist/testing/cross_backend/spawn_backend.js +229 -0
  123. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts +66 -0
  124. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts.map +1 -0
  125. package/dist/testing/cross_backend/spine_stub_backend_config.js +49 -0
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +37 -0
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/sse_round_trip.js +137 -0
  129. package/dist/testing/cross_backend/standard.d.ts +96 -0
  130. package/dist/testing/cross_backend/standard.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/standard.js +49 -0
  132. package/dist/testing/cross_backend/testing_reset_actions.d.ts +171 -0
  133. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/testing_reset_actions.js +213 -0
  135. package/dist/testing/cross_backend/testing_server_bun.d.ts +5 -0
  136. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -0
  137. package/dist/testing/cross_backend/testing_server_bun.js +59 -0
  138. package/dist/testing/cross_backend/testing_server_core.d.ts +140 -0
  139. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_server_core.js +68 -0
  141. package/dist/testing/cross_backend/testing_server_deno.d.ts +5 -0
  142. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/testing_server_deno.js +37 -0
  144. package/dist/testing/cross_backend/testing_server_node.d.ts +5 -0
  145. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/testing_server_node.js +50 -0
  147. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +72 -0
  148. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -0
  149. package/dist/testing/cross_backend/ts_spine_backend_config.js +112 -0
  150. package/dist/testing/cross_backend/ws_round_trip.d.ts +35 -0
  151. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -0
  152. package/dist/testing/cross_backend/ws_round_trip.js +113 -0
  153. package/dist/testing/data_exposure.d.ts +11 -14
  154. package/dist/testing/data_exposure.d.ts.map +1 -1
  155. package/dist/testing/data_exposure.js +123 -146
  156. package/dist/testing/db_entities.d.ts +22 -1
  157. package/dist/testing/db_entities.d.ts.map +1 -1
  158. package/dist/testing/db_entities.js +24 -1
  159. package/dist/testing/integration.d.ts +56 -21
  160. package/dist/testing/integration.d.ts.map +1 -1
  161. package/dist/testing/integration.js +294 -319
  162. package/dist/testing/integration_helpers.d.ts +16 -6
  163. package/dist/testing/integration_helpers.d.ts.map +1 -1
  164. package/dist/testing/integration_helpers.js +7 -7
  165. package/dist/testing/mock_fs.d.ts.map +1 -1
  166. package/dist/testing/mock_fs.js +0 -2
  167. package/dist/testing/rate_limiting.d.ts.map +1 -1
  168. package/dist/testing/rate_limiting.js +9 -0
  169. package/dist/testing/role_grant_helpers.d.ts +31 -0
  170. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  171. package/dist/testing/role_grant_helpers.js +46 -0
  172. package/dist/testing/round_trip.d.ts +20 -16
  173. package/dist/testing/round_trip.d.ts.map +1 -1
  174. package/dist/testing/round_trip.js +61 -86
  175. package/dist/testing/rpc_helpers.d.ts +10 -4
  176. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  177. package/dist/testing/rpc_helpers.js +1 -1
  178. package/dist/testing/rpc_round_trip.d.ts +24 -21
  179. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  180. package/dist/testing/rpc_round_trip.js +87 -104
  181. package/dist/testing/schema_introspect.d.ts +106 -0
  182. package/dist/testing/schema_introspect.d.ts.map +1 -0
  183. package/dist/testing/schema_introspect.js +123 -0
  184. package/dist/testing/schema_parity.d.ts +144 -0
  185. package/dist/testing/schema_parity.d.ts.map +1 -0
  186. package/dist/testing/schema_parity.js +233 -0
  187. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  188. package/dist/testing/sse_round_trip.js +1 -68
  189. package/dist/testing/standard.d.ts +56 -25
  190. package/dist/testing/standard.d.ts.map +1 -1
  191. package/dist/testing/standard.js +62 -5
  192. package/dist/testing/stubs.d.ts +21 -6
  193. package/dist/testing/stubs.d.ts.map +1 -1
  194. package/dist/testing/stubs.js +33 -23
  195. package/dist/testing/testing_rate_limiter.d.ts +59 -0
  196. package/dist/testing/testing_rate_limiter.d.ts.map +1 -0
  197. package/dist/testing/testing_rate_limiter.js +74 -0
  198. package/dist/testing/transports/bootstrap.d.ts +52 -0
  199. package/dist/testing/transports/bootstrap.d.ts.map +1 -0
  200. package/dist/testing/transports/bootstrap.js +70 -0
  201. package/dist/testing/transports/fetch_transport.d.ts +81 -0
  202. package/dist/testing/transports/fetch_transport.d.ts.map +1 -0
  203. package/dist/testing/transports/fetch_transport.js +74 -0
  204. package/dist/testing/transports/sse_frame_reader.d.ts +41 -0
  205. package/dist/testing/transports/sse_frame_reader.d.ts.map +1 -0
  206. package/dist/testing/transports/sse_frame_reader.js +84 -0
  207. package/dist/testing/transports/sse_transport.d.ts +54 -0
  208. package/dist/testing/transports/sse_transport.d.ts.map +1 -0
  209. package/dist/testing/transports/sse_transport.js +51 -0
  210. package/dist/testing/transports/ws_client.d.ts +108 -0
  211. package/dist/testing/transports/ws_client.d.ts.map +1 -0
  212. package/dist/testing/transports/ws_client.js +56 -0
  213. package/dist/testing/transports/ws_transport.d.ts +43 -0
  214. package/dist/testing/transports/ws_transport.d.ts.map +1 -0
  215. package/dist/testing/transports/ws_transport.js +169 -0
  216. package/dist/testing/ws_round_trip.d.ts +21 -103
  217. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  218. package/dist/testing/ws_round_trip.js +42 -40
  219. package/dist/ui/CLAUDE.md +5 -3
  220. package/dist/ui/MenuLink.svelte +16 -16
  221. package/dist/ui/MenuLink.svelte.d.ts +13 -4
  222. package/dist/ui/MenuLink.svelte.d.ts.map +1 -1
  223. package/package.json +10 -4
@@ -10,53 +10,102 @@
10
10
  * - Easy to grep: `grep '\$\$'`
11
11
  * - Fails loud if accidentally shell-processed (`$$`=PID in shell)
12
12
  *
13
+ * # Syntax
14
+ *
15
+ * - `$$VAR$$` — required reference. Missing or empty fails validation
16
+ * (see `validate_env_vars`).
17
+ * - `$$?VAR$$` — optional reference. Missing or empty resolves to the
18
+ * empty string and passes validation. Use for vars that exist in the
19
+ * contract but may be intentionally blank (e.g. `SMTP_PASSWORD=` on
20
+ * a deployment that hasn't configured SMTP yet).
21
+ * - `\$$VAR$$` — escape. The leading backslash is dropped at resolution
22
+ * time and the rest is emitted literally. Use this when documenting
23
+ * the syntax inside a string literal (comments in env-file templates,
24
+ * for example) where a regex scan would otherwise treat the mention
25
+ * as a real reference. Combines with `?` as `\$$?VAR$$`.
26
+ *
13
27
  * @module
14
28
  */
15
29
  /**
16
- * Pattern for environment variable references: `$$VAR$$`.
30
+ * Pattern matching `$$VAR$$`, `$$?VAR$$`, and their escaped forms
31
+ * `\$$VAR$$` / `\$$?VAR$$`. Capture groups:
32
+ *
33
+ * - 1: the `$$...$$` body (without any leading escape char) — what to
34
+ * emit when the match is escaped.
35
+ * - 2: `?` if the reference is optional, empty otherwise.
36
+ * - 3: the variable name (without `$$` delimiters).
37
+ *
38
+ * The leading `\\?` (optional backslash) is the escape signal — its
39
+ * presence in `match[0]` (vs. `match[1]`) drives the literal-emission
40
+ * branch in the resolver.
17
41
  */
18
- const ENV_VAR_PATTERN = /\$\$([A-Za-z_][A-Za-z0-9_]*)\$\$/g;
42
+ const ENV_VAR_PATTERN = /\\?(\$\$(\??)([A-Za-z_][A-Za-z0-9_]*)\$\$)/g;
19
43
  /**
20
44
  * Resolve environment variable references in a string.
21
45
  *
22
- * Uses `$$VAR$$` syntax (bookended double-dollar signs).
23
- * Only resolves variables that are actually set in the environment.
24
- * Unset variables are left as-is for clear error messages.
46
+ * - `$$VAR$$` resolves from the runtime env; missing values are left as-is
47
+ * for the validation phase to report.
48
+ * - `$$?VAR$$` is the optional form missing or empty resolves to the empty
49
+ * string. Required validation skips refs marked optional.
50
+ * - `\$$VAR$$` / `\$$?VAR$$` are escapes — the leading backslash is dropped
51
+ * and the body is emitted literally (no resolution attempted).
25
52
  *
26
53
  * @param runtime - runtime with `env_get` capability
27
54
  * @param value - string that may contain `$$VAR$$` references
28
55
  * @returns string with env vars resolved
29
56
  */
30
57
  export const resolve_env_vars = (runtime, value) => {
31
- return value.replace(ENV_VAR_PATTERN, (match, name) => {
58
+ return value.replace(ENV_VAR_PATTERN, (match, body, modifier, name) => {
59
+ if (match.startsWith('\\')) {
60
+ // Escaped: drop the leading backslash, emit the body literally.
61
+ return body;
62
+ }
32
63
  const resolved = runtime.env_get(name);
33
- // leave unresolved for the validation phase to report
34
- return resolved !== undefined ? resolved : match;
64
+ if (resolved !== undefined && resolved !== '')
65
+ return resolved;
66
+ // Optional: empty-on-miss. Required: leave unresolved for validation.
67
+ if (modifier === '?')
68
+ return '';
69
+ return match;
35
70
  });
36
71
  };
37
72
  /**
38
73
  * Check if a string contains unresolved env var references.
39
74
  *
75
+ * Escaped references (`\$$VAR$$`) do not count — they're literal text once
76
+ * resolved.
77
+ *
40
78
  * @param value - string to check
41
- * @returns `true` if string contains `$$VAR$$` patterns
79
+ * @returns `true` if string contains unescaped `$$VAR$$` patterns
42
80
  */
43
81
  export const has_env_vars = (value) => {
44
- // use a fresh regex to avoid global regex lastIndex state issues
45
- return /\$\$[A-Za-z_][A-Za-z0-9_]*\$\$/.test(value);
82
+ // Iterate the shared pattern (fresh regex; lastIndex is fine on construct).
83
+ const pattern = new RegExp(ENV_VAR_PATTERN.source, 'g');
84
+ let match;
85
+ while ((match = pattern.exec(value)) !== null) {
86
+ if (!match[0].startsWith('\\'))
87
+ return true;
88
+ }
89
+ return false;
46
90
  };
47
91
  /**
48
92
  * Get list of env var names referenced in a string.
49
93
  *
94
+ * Escaped references are skipped; optional and required references are both
95
+ * included (callers that care about the distinction should use `scan_env_vars`
96
+ * which preserves the `optional` flag per ref).
97
+ *
50
98
  * @param value - string to scan
51
99
  * @returns array of variable names (without `$$` delimiters)
52
100
  */
53
101
  export const get_env_var_names = (value) => {
54
102
  const names = [];
55
- let match;
56
- // reset regex lastIndex since it's global
57
103
  const pattern = new RegExp(ENV_VAR_PATTERN.source, 'g');
104
+ let match;
58
105
  while ((match = pattern.exec(value)) !== null) {
59
- names.push(match[1]);
106
+ if (match[0].startsWith('\\'))
107
+ continue;
108
+ names.push(match[3]);
60
109
  }
61
110
  return names;
62
111
  };
@@ -79,7 +128,9 @@ export const resolve_env_vars_in_object = (runtime, obj) => {
79
128
  /**
80
129
  * Resolve env vars and throw if any are missing/empty.
81
130
  *
82
- * Use this for values that must be present.
131
+ * Use this for values that must be present. `$$?VAR$$` (optional) refs
132
+ * resolve to the empty string on miss without contributing to the error.
133
+ * Escaped references (`\$$VAR$$`) emit literally and never check the env.
83
134
  *
84
135
  * @param runtime - runtime with `env_get` capability
85
136
  * @param value - string with `$$VAR$$` references
@@ -89,13 +140,18 @@ export const resolve_env_vars_in_object = (runtime, obj) => {
89
140
  */
90
141
  export const resolve_env_vars_required = (runtime, value, context) => {
91
142
  const missing = [];
92
- const result = value.replace(ENV_VAR_PATTERN, (match, name) => {
93
- const resolved = runtime.env_get(name);
94
- if (resolved === undefined || resolved === '') {
95
- missing.push(name);
96
- return match; // keep original for error message
143
+ const result = value.replace(ENV_VAR_PATTERN, (match, body, modifier, name) => {
144
+ if (match.startsWith('\\')) {
145
+ // Escaped emit body, no env lookup.
146
+ return body;
97
147
  }
98
- return resolved;
148
+ const resolved = runtime.env_get(name);
149
+ if (resolved !== undefined && resolved !== '')
150
+ return resolved;
151
+ if (modifier === '?')
152
+ return '';
153
+ missing.push(name);
154
+ return match; // keep original for error message
99
155
  });
100
156
  if (missing.length > 0) {
101
157
  throw new Error(`Missing required environment variable(s) for ${context}: ${missing.join(', ')}`);
@@ -106,10 +162,13 @@ export const resolve_env_vars_required = (runtime, value, context) => {
106
162
  * Recursively scan an object for `$$VAR$$` env var references.
107
163
  *
108
164
  * Walks all string values in the object tree and extracts env var names
109
- * with their path context for error reporting.
165
+ * with their path context for error reporting. Escaped references
166
+ * (`\$$VAR$$`) are skipped — they're literal text, not references.
167
+ * The `optional` flag on each ref distinguishes `$$VAR$$` (required) from
168
+ * `$$?VAR$$` (optional) for downstream validation.
110
169
  *
111
170
  * @param obj - object to scan (typically a config)
112
- * @returns array of env var references with paths
171
+ * @returns array of env var references with paths and optional flags
113
172
  */
114
173
  export const scan_env_vars = (obj) => {
115
174
  const refs = [];
@@ -117,13 +176,17 @@ export const scan_env_vars = (obj) => {
117
176
  return refs;
118
177
  };
119
178
  /**
120
- * Recursive helper for `scan_env_vars`.
179
+ * Recursive helper for `scan_env_vars`. Uses the full pattern (not
180
+ * `get_env_var_names`) to preserve the `optional` modifier on each ref.
121
181
  */
122
182
  const scan_recursive = (value, path, refs) => {
123
183
  if (typeof value === 'string') {
124
- const names = get_env_var_names(value);
125
- for (const name of names) {
126
- refs.push({ name, path });
184
+ const pattern = new RegExp(ENV_VAR_PATTERN.source, 'g');
185
+ let match;
186
+ while ((match = pattern.exec(value)) !== null) {
187
+ if (match[0].startsWith('\\'))
188
+ continue; // escaped — literal text
189
+ refs.push({ name: match[3], path, optional: match[2] === '?' });
127
190
  }
128
191
  }
129
192
  else if (Array.isArray(value)) {
@@ -144,6 +207,8 @@ const scan_recursive = (value, path, refs) => {
144
207
  *
145
208
  * Returns all missing refs (including duplicates by name). Grouping
146
209
  * and deduplication is handled by `format_missing_env_vars` at display time.
210
+ * Refs marked `optional: true` (from `$$?VAR$$` syntax) are skipped — a
211
+ * deliberately-blank var is contract, not a missing dependency.
147
212
  *
148
213
  * @param runtime - runtime with `env_get` capability
149
214
  * @param refs - env var references from `scan_env_vars`
@@ -152,6 +217,8 @@ const scan_recursive = (value, path, refs) => {
152
217
  export const validate_env_vars = (runtime, refs) => {
153
218
  let missing = null;
154
219
  for (const ref of refs) {
220
+ if (ref.optional)
221
+ continue;
155
222
  const value = runtime.env_get(ref.name);
156
223
  if (value === undefined || value === '') {
157
224
  (missing ??= []).push(ref);