[PR #4565] [MERGED] fix(javascript): drop built-in effects for visibly replaced globals #6310

Closed
opened 2026-10-31 01:13:28 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/4565
Author: @amyanger
Created: 10/16/2026
Status: ✅ Merged
Merged: 10/22/2026
Merged by: @morluto

Base: main ← Head: fix/3367-replaced-global-builtins


📝 Commits (1)

  • bde517f fix(javascript): drop built-in effects for visibly replaced globals

📊 Changes

5 files changed (+150 additions, -6 deletions)

View changed files

📝 docs/javascript-application-workflows.md (+5 -0)
📝 src/domain/javascript/javascriptSemanticAnalysis.runtime_boundaries.test.ts (+58 -0)
📝 src/domain/javascript/javascriptSemanticAsyncEffects.ts (+3 -1)
📝 src/domain/javascript/javascriptSemanticDataEffects.ts (+14 -5)
➕ tests/acceptance/applications/javascriptReplacedGlobalEffectsCli.test.ts (+70 -0)

📄 Description

Summary

Fixes #3367.

analyze-javascript-application kept resolved Node timer, fetch request and JSON.parse boundary effects after the source visibly replaced those globals, for example setTimeout = () => null; setTimeout(work, 100). The semantic collectors now require the global to have no visible source-local write before admitting the built-in effect, so a replaced global no longer produces a resolved built-in effect. Unmodified built-ins keep their effects.

Problem and expected behavior

Smallest trigger, from the issue:

function work() {}
setTimeout = () => null;
setTimeout(work, 100);
fetch = () => null;
fetch('https://example.invalid');
JSON.parse = () => null;
JSON.parse('null');

On base this produced resolved schedules-timer, constructs-request and parses relations, identical to a control without the three assignments. That violates the documented rule that resolved relationships name uniquely identified admitted static targets. Expected: no resolved built-in effect after a visible replacement, and explicit uncertainty if a speculative classification remains.

Change and scope

timerMethod (javascriptSemanticAsyncEffects.ts), and boundaryDetails and requestMethod (javascriptSemanticDataEffects.ts) now also call the existing isUnmodifiedGlobalReference guard from javascriptSemanticAmbientWrites.ts for setTimeout/setInterval and their cancel functions, fetch, WebSocket, JSON.parse and the global coercions (String, Number, Boolean, parseInt, parseFloat). The guard reads the ambient-write index, which analyzeParsedJavaScriptSemanticsSteps always builds before these collectors run.

This follows the static analyzer, which already guards fetch, WebSocket, EventSource, URL and Worker with the same helper (javascriptStaticAnalysisCalls.ts:506-512, :173, :207). The existing binding and resolution-blocked checks stay in place, so the change only narrows admission and never admits a new form such as globalThis.JSON.parse(...). Name filters run before the guard, so unrelated unbound calls do not pay for it.

Resulting shapes:

  • A replaced timer, fetch, WebSocket or coercion produces no built-in operation. The call stays visible as an existing call-flow unknown ("Unresolved call target fetch"). This matches the existing "does not treat shadowed timer globals as Node timers" behavior.
  • A replaced JSON.parse falls through to the existing generic .parse rule: a parses relation with candidate resolution plus the boundary unknown "Static parse boundary kind is a candidate."

Precision trade-off (accepted by the human owner after measurement): the guard follows the documented "a visible possible write is sufficient" rule and does not evaluate order or reachability. A computed write through a global object (globalThis[k] = x), a guarded polyfill (if (!globalThis.fetch) globalThis.fetch = poly) or a write after the call also removes the resolved effect for that file. Measured cost on real code is below. docs/javascript-application-workflows.md now states this.

Not included: Promise has the same gap (isUnshadowedGlobalPromise in javascriptSemanticPromises.ts does not consult ambient writes). It is outside this issue and left for a follow-up. Folding the ambient-write check into a shared unshadowed-global helper was also left out, because the ambient-write indexer uses isUnshadowedGlobal to build the index itself.

Prior attempt: #3849 (closed by its author, unmerged) added a three-state classification that kept computed-key writes as "possibly replaced" partial effects. Its automated review found that the extra state could treat writes through unknown targets as exact replacements and could leak partial fetch identity into confirmed response consumers. This PR keeps the existing fail-closed guard instead, so a suppressed fetch also removes its response consumers rather than reporting them as confirmed.

Scope decision: not applicable. This is an ordinary fix inside the existing static analysis boundary.

Contract and boundary impact

  • Semantic owner and earliest changed stage: domain semantic effect collection (src/domain/javascript), before graph projection.
  • Contribution scope fit and linked maintainer decision: ordinary fix, not applicable.
  • CLI and MCP/tool-catalog contract: no schema or catalog change. Output changes only for sources with visible writes to these globals.
  • Provider, bridge, target-format, or platform compatibility: none. Static analysis only.
  • Evidence, artifact, provenance, or reconstruction contract: no schema or version change. Affected graphs lose false resolved effects and, for JSON.parse, gain a candidate relation and boundary unknown. Function fingerprint components.effects can change for affected functions, so cross-version comparisons of such files will show effect differences.
  • Target selection, authority, upstream behavior, and cleanup details: not applicable.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated:
    • tests/acceptance/applications/javascriptReplacedGlobalEffectsCli.test.ts (new): the issue's fixture extended with WebSocket and String, through both the CLI and stdio MCP via cliTest and analyzeJavaScriptDirectory, with a control without the replacements.
    • src/domain/javascript/javascriptSemanticAnalysis.runtime_boundaries.test.ts: a new replaced built-in globals block with 7 replacement cases (setTimeout, globalThis.setTimeout, fetch, WebSocket, JSON.parse, JSON, String) and a control showing unrelated writes (JSON.stringify = x, other.setTimeout = x) keep every effect.
  • Base reproduction (executed, base 8116dc065, public CLI with the issue's fixture and an isolated HOME): replaced fixture printed constructs-request resolved, schedules-timer resolved, parses resolved, no effect unknowns. The control printed the same three. On the branch the replaced fixture printed only parses candidate with the boundary unknown, and the control was unchanged.
  • Final tests against base code (executed in a clean base worktree with only the test files copied): npm run test:focused -- <both files>: 9 failed, 6 passed. All 7 replacement module cases and both acceptance surfaces fail, while the controls and existing cases pass.
  • Mutation (executed, separate worktree, npm run test:local): replacing each of the 4 guards (timer, coercion, request, JSON.parse) with a constant made 2, 1, 2 and 2 new tests fail respectively. The unmutated tree passed 13 of 13.
  • Real inputs (executed, base and branch CLI, one file per run):
    • 44 files from node_modules, matching assignment-like patterns plus random effect-bearing files: 0 changed. 12 had effects, 81 effect relations in all.
    • 30 bundles that write through globalThis, window or self (Algolia UMD builds, pino browser, Playwright, Vitest chunks): 29 unchanged, 1 changed. In vitest/dist/chunks/source-map.*.js, globalThis[SAFE_TIMERS_SYMBOL] = {...} turned one resolved JSON.parse into a candidate. Totals across those 30 files: timers 39 to 39, coercions 30 to 30, resolved parses 47 to 46.
  • User-visible CLI/MCP output: covered by the acceptance test above. Relation lists for the replaced fixture went from constructs-request:resolved, parses:resolved, schedules-timer:resolved to parses:candidate.
  • Remaining proof gaps: the order-blind trade-off described above. Promise replacement is not covered. Large minified real-world application bundles beyond the 74 files above were not measured.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

All on macOS with the .nvmrc Node. Work was done on upstream 8116dc065, then rebased onto ae07d1517 (two Ghidra-only commits); the focused set and test:changed were rerun after the rebase with the results below.

  • npm run test:focused -- src/domain/javascript/javascriptSemanticAnalysis.runtime_boundaries.test.ts tests/acceptance/applications/javascriptReplacedGlobalEffectsCli.test.ts src/domain/javascript/javascriptSemanticAnalysis.builtin_defaults.test.ts src/domain/javascript/javascriptSemanticAmbientWrites.test.ts tests/acceptance/applications/javascriptBuiltinDefaultsCli.test.ts tests/acceptance/applications/javascriptEventPayload.test.ts tests/acceptance/applications/javascriptTimerWritesCli.test.ts: 7 files, 131 tests passed, before and after the rebase
  • Same two new test files against base code: 9 failed, 6 passed (expected, see above)
  • npm run check:fast: passed
  • npm run test:changed -- --base ae07d151704bac5f0129985cedb92546d2d5411e (after rebase): 120 files, 2901 tests passed. Before the rebase, against 8116dc065: same result
  • npm run check: passed, 6 of 6 tasks, module boundaries and architecture guards passed
  • npm run docs:check: passed
  • npm run verify:test-discovery: discovered all 1440 test files exactly once
  • npm run test:local over 61 semantic, application and static analysis test files (src/domain/javascript/javascriptSemantic*.test.ts, javascriptApplication*.test.ts, javascriptStaticAnalysis*.test.ts, src/application/javascript/JavaScriptSemanticGraph*.test.ts), run by an independent reviewer: 1303 tests passed

Review:

  • An independent adversarial reviewer traced consumers (graph projection, timer cancel linking, response consumers, fingerprints), index population and Evidence identity. No crash, regression, schema or golden impact. Its findings were the precision trade-off above and these downstream shapes: a cancel whose schedule was suppressed becomes an unresolved cancel, and response consumers of a suppressed fetch disappear with it.
  • /code-review high, findings and actions:
    • Promise gap: real, out of scope, listed above.
    • Computed-key writes, polyfill idioms and order-blindness: the accepted trade-off, documented.
    • Timers vanish while JSON.parse becomes a candidate: kept. The candidate comes from the pre-existing generic .parse rule, and suppressed calls stay visible as call-flow unknowns.
    • Shared-helper refactor: not done, reason above.
    • "Redundant" binding checks: kept, because isUnshadowedGlobal also admits CommonJS wrapper bindings and the member form would admit new callee shapes.
    • Guard cost before name filtering: fixed, name filters now run first. Mutation rerun passed.
    • Acceptance test inherited HOME and env and missed WebSocket/String: fixed, now uses cliTest on both surfaces with all five globals.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Output narrows for sources that replace these globals.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable. No external provider. The real workflow is the public CLI and stdio MCP, both exercised.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the analyzer only reads source text. No execution or network.

Author readiness

Human owner: @amyanger

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/4565 **Author:** [@amyanger](https://github.com/amyanger) **Created:** 10/16/2026 **Status:** ✅ Merged **Merged:** 10/22/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/3367-replaced-global-builtins` --- ### 📝 Commits (1) - [`bde517f`](https://github.com/morluto/rea/commit/bde517f4077a70638ef77724390af7cedcfb8d92) fix(javascript): drop built-in effects for visibly replaced globals ### 📊 Changes **5 files changed** (+150 additions, -6 deletions) <details> <summary>View changed files</summary> 📝 `docs/javascript-application-workflows.md` (+5 -0) 📝 `src/domain/javascript/javascriptSemanticAnalysis.runtime_boundaries.test.ts` (+58 -0) 📝 `src/domain/javascript/javascriptSemanticAsyncEffects.ts` (+3 -1) 📝 `src/domain/javascript/javascriptSemanticDataEffects.ts` (+14 -5) ➕ `tests/acceptance/applications/javascriptReplacedGlobalEffectsCli.test.ts` (+70 -0) </details> ### 📄 Description ## Summary Fixes #3367. `analyze-javascript-application` kept resolved Node timer, `fetch` request and `JSON.parse` boundary effects after the source visibly replaced those globals, for example `setTimeout = () => null; setTimeout(work, 100)`. The semantic collectors now require the global to have no visible source-local write before admitting the built-in effect, so a replaced global no longer produces a resolved built-in effect. Unmodified built-ins keep their effects. ## Problem and expected behavior Smallest trigger, from the issue: ```js function work() {} setTimeout = () => null; setTimeout(work, 100); fetch = () => null; fetch('https://example.invalid'); JSON.parse = () => null; JSON.parse('null'); ``` On base this produced resolved `schedules-timer`, `constructs-request` and `parses` relations, identical to a control without the three assignments. That violates the documented rule that resolved relationships name uniquely identified admitted static targets. Expected: no resolved built-in effect after a visible replacement, and explicit uncertainty if a speculative classification remains. ## Change and scope `timerMethod` (`javascriptSemanticAsyncEffects.ts`), and `boundaryDetails` and `requestMethod` (`javascriptSemanticDataEffects.ts`) now also call the existing `isUnmodifiedGlobalReference` guard from `javascriptSemanticAmbientWrites.ts` for setTimeout/setInterval and their cancel functions, `fetch`, `WebSocket`, `JSON.parse` and the global coercions (`String`, `Number`, `Boolean`, `parseInt`, `parseFloat`). The guard reads the ambient-write index, which `analyzeParsedJavaScriptSemanticsSteps` always builds before these collectors run. This follows the static analyzer, which already guards `fetch`, `WebSocket`, `EventSource`, `URL` and `Worker` with the same helper (`javascriptStaticAnalysisCalls.ts:506-512`, `:173`, `:207`). The existing binding and resolution-blocked checks stay in place, so the change only narrows admission and never admits a new form such as `globalThis.JSON.parse(...)`. Name filters run before the guard, so unrelated unbound calls do not pay for it. Resulting shapes: - A replaced timer, `fetch`, `WebSocket` or coercion produces no built-in operation. The call stays visible as an existing `call-flow` unknown ("Unresolved call target fetch"). This matches the existing "does not treat shadowed timer globals as Node timers" behavior. - A replaced `JSON.parse` falls through to the existing generic `.parse` rule: a `parses` relation with `candidate` resolution plus the boundary unknown "Static parse boundary kind is a candidate." Precision trade-off (accepted by the human owner after measurement): the guard follows the documented "a visible possible write is sufficient" rule and does not evaluate order or reachability. A computed write through a global object (`globalThis[k] = x`), a guarded polyfill (`if (!globalThis.fetch) globalThis.fetch = poly`) or a write after the call also removes the resolved effect for that file. Measured cost on real code is below. `docs/javascript-application-workflows.md` now states this. Not included: `Promise` has the same gap (`isUnshadowedGlobalPromise` in `javascriptSemanticPromises.ts` does not consult ambient writes). It is outside this issue and left for a follow-up. Folding the ambient-write check into a shared unshadowed-global helper was also left out, because the ambient-write indexer uses `isUnshadowedGlobal` to build the index itself. Prior attempt: #3849 (closed by its author, unmerged) added a three-state classification that kept computed-key writes as "possibly replaced" partial effects. Its automated review found that the extra state could treat writes through unknown targets as exact replacements and could leak partial `fetch` identity into confirmed response consumers. This PR keeps the existing fail-closed guard instead, so a suppressed `fetch` also removes its response consumers rather than reporting them as confirmed. Scope decision: not applicable. This is an ordinary fix inside the existing static analysis boundary. ## Contract and boundary impact - Semantic owner and earliest changed stage: domain semantic effect collection (`src/domain/javascript`), before graph projection. - Contribution scope fit and linked maintainer decision: ordinary fix, not applicable. - CLI and MCP/tool-catalog contract: no schema or catalog change. Output changes only for sources with visible writes to these globals. - Provider, bridge, target-format, or platform compatibility: none. Static analysis only. - Evidence, artifact, provenance, or reconstruction contract: no schema or version change. Affected graphs lose false resolved effects and, for `JSON.parse`, gain a candidate relation and boundary unknown. Function fingerprint `components.effects` can change for affected functions, so cross-version comparisons of such files will show effect differences. - Target selection, authority, upstream behavior, and cleanup details: not applicable. - Generated metadata, package, or installation impact: none. Expected effects: - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: - `tests/acceptance/applications/javascriptReplacedGlobalEffectsCli.test.ts` (new): the issue's fixture extended with `WebSocket` and `String`, through both the CLI and stdio MCP via `cliTest` and `analyzeJavaScriptDirectory`, with a control without the replacements. - `src/domain/javascript/javascriptSemanticAnalysis.runtime_boundaries.test.ts`: a new `replaced built-in globals` block with 7 replacement cases (`setTimeout`, `globalThis.setTimeout`, `fetch`, `WebSocket`, `JSON.parse`, `JSON`, `String`) and a control showing unrelated writes (`JSON.stringify = x`, `other.setTimeout = x`) keep every effect. - Base reproduction (executed, base `8116dc065`, public CLI with the issue's fixture and an isolated HOME): replaced fixture printed `constructs-request resolved`, `schedules-timer resolved`, `parses resolved`, no effect unknowns. The control printed the same three. On the branch the replaced fixture printed only `parses candidate` with the boundary unknown, and the control was unchanged. - Final tests against base code (executed in a clean base worktree with only the test files copied): `npm run test:focused -- <both files>`: 9 failed, 6 passed. All 7 replacement module cases and both acceptance surfaces fail, while the controls and existing cases pass. - Mutation (executed, separate worktree, `npm run test:local`): replacing each of the 4 guards (timer, coercion, request, `JSON.parse`) with a constant made 2, 1, 2 and 2 new tests fail respectively. The unmutated tree passed 13 of 13. - Real inputs (executed, base and branch CLI, one file per run): - 44 files from `node_modules`, matching assignment-like patterns plus random effect-bearing files: 0 changed. 12 had effects, 81 effect relations in all. - 30 bundles that write through `globalThis`, `window` or `self` (Algolia UMD builds, pino browser, Playwright, Vitest chunks): 29 unchanged, 1 changed. In `vitest/dist/chunks/source-map.*.js`, `globalThis[SAFE_TIMERS_SYMBOL] = {...}` turned one resolved `JSON.parse` into a candidate. Totals across those 30 files: timers 39 to 39, coercions 30 to 30, resolved parses 47 to 46. - User-visible CLI/MCP output: covered by the acceptance test above. Relation lists for the replaced fixture went from `constructs-request:resolved, parses:resolved, schedules-timer:resolved` to `parses:candidate`. - Remaining proof gaps: the order-blind trade-off described above. `Promise` replacement is not covered. Large minified real-world application bundles beyond the 74 files above were not measured. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed All on macOS with the `.nvmrc` Node. Work was done on upstream `8116dc065`, then rebased onto `ae07d1517` (two Ghidra-only commits); the focused set and `test:changed` were rerun after the rebase with the results below. - `npm run test:focused -- src/domain/javascript/javascriptSemanticAnalysis.runtime_boundaries.test.ts tests/acceptance/applications/javascriptReplacedGlobalEffectsCli.test.ts src/domain/javascript/javascriptSemanticAnalysis.builtin_defaults.test.ts src/domain/javascript/javascriptSemanticAmbientWrites.test.ts tests/acceptance/applications/javascriptBuiltinDefaultsCli.test.ts tests/acceptance/applications/javascriptEventPayload.test.ts tests/acceptance/applications/javascriptTimerWritesCli.test.ts`: 7 files, 131 tests passed, before and after the rebase - Same two new test files against base code: 9 failed, 6 passed (expected, see above) - `npm run check:fast`: passed - `npm run test:changed -- --base ae07d151704bac5f0129985cedb92546d2d5411e` (after rebase): 120 files, 2901 tests passed. Before the rebase, against `8116dc065`: same result - `npm run check`: passed, 6 of 6 tasks, module boundaries and architecture guards passed - `npm run docs:check`: passed - `npm run verify:test-discovery`: discovered all 1440 test files exactly once - `npm run test:local` over 61 semantic, application and static analysis test files (`src/domain/javascript/javascriptSemantic*.test.ts`, `javascriptApplication*.test.ts`, `javascriptStaticAnalysis*.test.ts`, `src/application/javascript/JavaScriptSemanticGraph*.test.ts`), run by an independent reviewer: 1303 tests passed Review: - An independent adversarial reviewer traced consumers (graph projection, timer cancel linking, response consumers, fingerprints), index population and Evidence identity. No crash, regression, schema or golden impact. Its findings were the precision trade-off above and these downstream shapes: a cancel whose schedule was suppressed becomes an unresolved cancel, and response consumers of a suppressed `fetch` disappear with it. - `/code-review high`, findings and actions: - Promise gap: real, out of scope, listed above. - Computed-key writes, polyfill idioms and order-blindness: the accepted trade-off, documented. - Timers vanish while `JSON.parse` becomes a candidate: kept. The candidate comes from the pre-existing generic `.parse` rule, and suppressed calls stay visible as `call-flow` unknowns. - Shared-helper refactor: not done, reason above. - "Redundant" binding checks: kept, because `isUnshadowedGlobal` also admits CommonJS wrapper bindings and the member form would admit new callee shapes. - Guard cost before name filtering: fixed, name filters now run first. Mutation rerun passed. - Acceptance test inherited HOME and env and missed `WebSocket`/`String`: fixed, now uses `cliTest` on both surfaces with all five globals. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Output narrows for sources that replace these globals. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. No external provider. The real workflow is the public CLI and stdio MCP, both exercised. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the analyzer only reads source text. No execution or network. ## Author readiness Human owner: @amyanger - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:13:28 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6310
No description provided.