Skip to content

feat(execution): add public platform batch - #3

Merged
izumi0uu merged 3 commits into
hermes/execution-v1from
feat/public-platform-batch-execution-v1
Jul 31, 2026
Merged

feat(execution): add public platform batch#3
izumi0uu merged 3 commits into
hermes/execution-v1from
feat/public-platform-batch-execution-v1

Conversation

@izumi0uu

@izumi0uu izumi0uu commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Trellis task: .trellis/tasks/07-31-agent-reach-public-platform-batch

Adds seven independently closed execution.v1 descriptors in one reviewed public-read batch:

  • YouTube search.videos and read.subtitles
  • V2EX browse.hot, browse.node_topics, read.topic, and read.user
  • Exa search.web

Each operation keeps a static argument/result contract and exact host capabilities. This adds no generic command, argv, backend, endpoint, MCP method, credential, browser, or fallback selector. Exa Web uses the fixed public provider route; query text is provider-visible and may be retained, while local error/provenance paths remain redacted.

Validation:

  • env -u EXA_API_KEY uv run pytest -q (847 passed)
  • focused V2EX/contract suite (120 passed)
  • Ruff check and format
  • mypy for agent_reach/execution
  • wheel/sdist build
  • clean-wheel 14-descriptor smoke
  • git diff --check

Risk: expands the fork-owned execution surface from 7 to 14 operation-scoped descriptors. Hermes still performs independent integrity, worker, result, authorization, receipt, and audit validation.

Rollback: restore exact integration commit 2a5829cf3b50bc435c647bfae4c050b1837d0235. No protocol, database, grant, receipt, or audit migration is required.

This PR must remain unmerged and untagged until the paired Hermes pin and full cross-repository gates pass.

Summary by CodeRabbit

  • New Features
    • Expanded the structured execution API from 7 to 14 operations.
    • Added YouTube search and subtitle retrieval.
    • Added V2EX browsing and profile operations.
    • Added Exa web search.
    • Added support for private workspaces and validated artifact capabilities.
  • Documentation
    • Updated English, Japanese, Korean, and API documentation with expanded capabilities and contracts.
  • Tests
    • Added comprehensive coverage for new operations, validation, security, cancellation, and error handling.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 07bf586f-3983-43e1-ad9e-b03d8f07d12e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The structured execution API expands from seven to fourteen closed operations. It adds YouTube search and subtitles, four V2EX operations, and Exa Web search with new contracts, backends, validation, tests, workflow checks, and documentation.

Changes

Closed execution expansion

Layer / File(s) Summary
Capability contracts and dispatch
agent_reach/execution/v1/contracts.py, agent_reach/execution/v1/registry.py, agent_reach/execution/v1/__init__.py, tests/test_execution_contract.py
Adds private workspace and mcporter artifact capabilities, result schemas, operation metadata, validation rules, dispatch paths, and contract tests.
YouTube search and subtitle execution
agent_reach/execution/v1/youtube.py, tests/test_execution_youtube.py
Adds search and subtitle execution, private workspace handling, bounded subtitle file access, cleanup, checkpoint propagation, and result projection.
V2EX transport and execution
agent_reach/execution/v1/v2ex.py, agent_reach/execution/v1/_v2ex_transport.py, tests/test_execution_v2ex.py
Adds four V2EX read operations with pinned HTTPS transport, DNS and response validation, normalized projections, partial reply handling, and transport tests.
Exa artifact execution and projection
agent_reach/execution/v1/exa.py, tests/test_execution_exa.py
Adds fixed mcporter subprocess execution with artifact integrity checks, isolated environment setup, cancellation handling, strict response parsing, and web-result projection.
Runtime verification and API documentation
.github/workflows/pytest.yml, README.md, docs/README_en.md, docs/README_ja.md, docs/README_ko.md, docs/execution-v1.md, pyproject.toml, tests/test_execution_bilibili.py
Updates dependency declarations, smoke tests, capability counts, runtime import checks, API descriptions, backend constraints, and operation documentation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant registry
  participant execute_v2ex
  participant V2exTransport
  participant execute_exa
  participant mcporter
  Caller->>registry: submit validated operation
  registry->>execute_v2ex: dispatch V2EX request
  execute_v2ex->>V2exTransport: fetch and project API data
  V2exTransport-->>execute_v2ex: normalized execution result
  registry->>execute_exa: dispatch Exa request
  execute_exa->>mcporter: validate artifacts and invoke fixed process
  mcporter-->>execute_exa: parse and project search results
  execute_exa-->>Caller: ExecutionResultV1
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a batch of public platform execution capabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/public-platform-batch-execution-v1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@izumi0uu

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
agent_reach/execution/v1/v2ex.py (2)

319-355: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the loop variable in _project_replies.

Line 328 rebinds the value parameter as the loop variable. The behavior is correct because values is extracted first, but the shadowing makes the function harder to read and fragile under future edits. Use raw_reply for the loop variable.

♻️ Proposed change
-    for value in values:
+    for raw_reply in values:
         _checkpoint(context)
-        raw = _mapping(value)
+        raw = _mapping(raw_reply)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent_reach/execution/v1/v2ex.py` around lines 319 - 355, Rename the
`_project_replies` loop variable from `value` to `raw_reply`, and update the
immediately following `_mapping` call to use `raw_reply`; leave the function’s
behavior and other parameter references unchanged.

83-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Suppress the _CheckpointRaised wrapper when re-raising the host exception. Both backends wrap a host checkpoint failure in an internal _CheckpointRaised exception and then re-raise the original inside the except block. Without from None, Python chains the internal wrapper onto the host's cancellation or deadline exception as __context__, so an implementation detail appears in the host traceback.

  • agent_reach/execution/v1/v2ex.py#L83-L84: change raise raised.original to raise raised.original from None.
  • agent_reach/execution/v1/exa.py#L114-L115: change raise raised.original to raise raised.original from None.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent_reach/execution/v1/v2ex.py` around lines 83 - 84, Suppress the internal
_CheckpointRaised context when re-raising the original host exception: update
the raise in agent_reach/execution/v1/v2ex.py at lines 83-84 and the
corresponding raise in agent_reach/execution/v1/exa.py at lines 114-115 to use
explicit context suppression.

Source: Linters/SAST tools

tests/test_execution_exa.py (1)

114-152: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Share the artifact closure across tests.

The artifact_fixture fixture is function-scoped. For each test it copies the whole Python interpreter to node, and every capability() call hashes that copy through _file_sha256 and walks the tree through _mcporter_tree_digest. Several tests call capability() more than once. With roughly 40 test cases in this module the suite repeats hundreds of megabytes of copying and hashing.

Move the immutable parts (the interpreter copy and pyvenv.cfg) to a session-scoped fixture and keep only the mutable cli.js, package.json, and config files per test. Tests that mutate artifacts, such as test_artifact_drift_fails_before_spawn and _write_cli, must keep writing into per-test copies.

Also applies to: 214-217

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_execution_exa.py` around lines 114 - 152, Refactor
artifact_fixture into shared session-scoped setup for the immutable interpreter
copy and pyvenv.cfg, while retaining per-test creation of cli.js, package.json,
and sterile-config.json. Update the _ArtifactFixture paths and dependent
fixtures as needed so each test still receives isolated mutable artifacts;
preserve mutation behavior for test_artifact_drift_fails_before_spawn and
_write_cli.
agent_reach/execution/v1/contracts.py (2)

961-1004: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared public-URL host checks.

_valid_public_result_url repeats almost all of _valid_public_location (lines 1014-1055): scheme set, credential rejection, default-port rule, local-host rejection, IP scope check, and DNS label rules. The two differ only in the length limit, the extra whitespace and backslash checks, and the query/fragment rule.

Extract one helper that validates the parsed host and port, then let both functions add their own pre-checks. This keeps the two security rules aligned when either is changed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent_reach/execution/v1/contracts.py` around lines 961 - 1004, Extract the
common host and port validation logic from _valid_public_result_url into a new
helper function (such as _valid_public_host_and_port) that validates the scheme,
credentials, port against expected default, local-host rejection, IP address
scope, and DNS label format. Update _valid_public_result_url to apply its unique
pre-checks (type, ascii, length, trimming, control characters, whitespace, and
backslashes) before calling the new helper with the parsed URL. Reference
_valid_public_location which contains similar logic to identify what should move
into the shared helper versus what remains function-specific, ensuring the two
security rules stay aligned as either evolves.

897-935: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Precompile the reply-URL pattern.

Line 921 builds and compiles a regex for every reply item. native_id already matched _POSITIVE_DECIMAL, so the value is safe, but the compile cost repeats per item. Compare the URL against an f-string instead, as _valid_result_schema_sequence already does at line 890.

♻️ Proposed change
     if item.schema_id == "v2ex.reply.v1":
         url = item.fields.get("url")
         author = item.fields.get("author")
+        topic_id = url.split("/t/", 1)[1].split("#", 1)[0] if type(url) is str and "/t/" in url else ""
         return bool(
             type(url) is str
-            and re.fullmatch(
-                rf"https://www[.]v2ex[.]com/t/[1-9][0-9]{{0,31}}`#reply`{re.escape(native_id)}",
-                url,
-            )
+            and _POSITIVE_DECIMAL.fullmatch(topic_id) is not None
+            and url == f"https://www.v2ex.com/t/{topic_id}`#reply`{native_id}"
             and type(author) is str
             and _V2EX_IDENTIFIER.fullmatch(author)
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent_reach/execution/v1/contracts.py` around lines 897 - 935, In the
_valid_v2ex_item function's v2ex.reply.v1 schema branch, replace the
re.fullmatch call that builds and compiles a regex pattern with every item with
a simple f-string equality check. Since native_id has already been validated to
match _POSITIVE_DECIMAL, construct the expected URL directly as an f-string
using the native_id value and compare it directly against the url field,
following the pattern used in the v2ex.topic.v1 branch above it.

Source: Linters/SAST tools

agent_reach/execution/v1/registry.py (2)

377-407: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the argument limits from the capability instead of literals.

Lines 379, 393, and 407 hardcode 50. The V2EX browse.hot and browse.node_topics capabilities already declare maximum_items=50, and the Exa search.web capability declares maximum_items=20. The other branches in this function use capability.maximum_items. A future change to a capability entry will silently desynchronize these literals.

Use capability.maximum_items for both V2EX branches. For Exa, the accepted request limit intentionally exceeds the item cap, so bind it to a named constant that states that intent.

♻️ Proposed change
     if key == ("v2ex", "browse.hot") and set(arguments) == {"limit"}:
         limit = arguments["limit"]
-        return type(limit) is int and 1 <= limit <= 50
+        return type(limit) is int and 1 <= limit <= capability.maximum_items
@@
         return bool(
             _valid_v2ex_identifier(node)
             and type(page) is int
             and 1 <= page <= 100
             and type(limit) is int
-            and 1 <= limit <= 50
+            and 1 <= limit <= capability.maximum_items
         )
@@
     if key == ("exa", "search.web") and set(arguments) == {"query", "limit"}:
-        return _valid_query_and_limit(arguments, maximum_limit=50)
+        return _valid_query_and_limit(arguments, maximum_limit=_MAX_EXA_REQUEST_LIMIT)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent_reach/execution/v1/registry.py` around lines 377 - 407, The validation
logic contains hardcoded limit values (50 for both V2EX branches and for Exa)
that should instead derive from the capability definitions already available in
the codebase. For the two V2EX validations under the browse.hot and
browse.node_topics key checks, replace the hardcoded 50 limit with
capability.maximum_items to keep the validation synchronized with the capability
definitions. For the Exa search.web validation, replace the hardcoded 50 with a
named constant that expresses the intent that the accepted request limit
intentionally exceeds the item cap, ensuring future capability changes won't
silently cause desynchronization.

357-371: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the contract patterns for the language and identifier checks.

Lines 364-369 re-implement _YOUTUBE_LANGUAGE from agent_reach/execution/v1/contracts.py, and _valid_v2ex_identifier re-implements _V2EX_IDENTIFIER. agent_reach/execution/v1/youtube.py revalidates the language with _YOUTUBE_LANGUAGE, and agent_reach/execution/v1/v2ex.py revalidates identifiers with its own copy of the same pattern. Three copies of one rule can drift.

Import the compiled patterns from contracts and match against them here. _valid_query_and_limit also uses _MAX_BILIBILI_QUERY_CHARACTERS for YouTube and Exa queries; rename that constant to a source-neutral name so the shared use is explicit.

Also applies to: 411-434

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent_reach/execution/v1/registry.py` around lines 357 - 371, Replace the
inline validation logic for the YouTube language parameter (lines 364-369 in the
first validation block) with a pattern match against the imported
_YOUTUBE_LANGUAGE pattern from contracts.py. Apply the same approach to the
identifier validation patterns in the second block (also applies to lines
411-434) by importing and matching against the appropriate compiled patterns
from contracts instead of re-implementing the validation rules inline.
Additionally, rename _MAX_BILIBILI_QUERY_CHARACTERS to a more generic name that
reflects its shared use across multiple services (YouTube, Exa, and Bilibili),
and update all references to use the renamed constant throughout the registry
validation logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent_reach/execution/v1/_v2ex_transport.py`:
- Around line 42-47: Promote httpcore==1.0.9 from the dev-only dependency
section into the main pinned optional dependency/lock path so runtime
environments resolve that version. Add an explicit runtime contract test for
pool_factory(...) and url_factory(...) that invokes each with the supported
runtime keyword arguments and verifies the resulting behavior, rather than only
checking symbol availability.

In `@agent_reach/execution/v1/contracts.py`:
- Around line 853-869: Update the subtitle validation and projection flow around
_valid_youtube_subtitle_item and _project_subtitle_success so truncating text
according to ExecutionLimitsV1.maximum_text_characters cannot invalidate the
required WEBVTT prefix. Preserve WEBVTT validation against the original subtitle
text, or enforce a minimum limit that retains the prefix before execution.

In `@agent_reach/execution/v1/exa.py`:
- Around line 170-177: The _validate_artifacts function performs expensive
full-tree validation via _mcporter_tree_digest on every request, which dominates
the process budget. Introduce process-level caching that stores the result of
full tree validation (the call to _mcporter_tree_digest) keyed by the declared
digests (artifacts.node_sha256 and artifacts.mcporter_tree_sha256) plus file
metadata (st_dev, st_ino, st_size, st_mtime_ns) of the node_executable,
mcporter_cli, config_path, and mcporter_root. Check the cache before calling
_mcporter_tree_digest and only perform the expensive walk when the cache key is
not present or has changed, while keeping the existing per-request comparison
logic as a cheap metadata fallback check.
- Around line 687-700: Update _kill_and_reap to guard the
os.killpg/process-group signaling path behind a non-Windows platform check,
avoiding references to unavailable Windows APIs. On Windows, skip directly to
process.kill(), while preserving the existing process-group behavior and
fallback handling on other platforms.

In `@agent_reach/execution/v1/youtube.py`:
- Around line 386-395: The YouTube subtitle flow currently uses the process CWD
as its workspace, allowing cleanup to remove unrelated files. Replace the
_private_workspace_root-based flow with a per-call tempfile.TemporaryDirectory
created under Path.cwd(), route the paths and outtmpl from _fixed_options into
that directory, and remove the entire temporary directory after the call;
eliminate the _cleanup_new_workspace_files snapshot cleanup and preserve
existing workspace validation/error behavior where applicable.

---

Nitpick comments:
In `@agent_reach/execution/v1/contracts.py`:
- Around line 961-1004: Extract the common host and port validation logic from
_valid_public_result_url into a new helper function (such as
_valid_public_host_and_port) that validates the scheme, credentials, port
against expected default, local-host rejection, IP address scope, and DNS label
format. Update _valid_public_result_url to apply its unique pre-checks (type,
ascii, length, trimming, control characters, whitespace, and backslashes) before
calling the new helper with the parsed URL. Reference _valid_public_location
which contains similar logic to identify what should move into the shared helper
versus what remains function-specific, ensuring the two security rules stay
aligned as either evolves.
- Around line 897-935: In the _valid_v2ex_item function's v2ex.reply.v1 schema
branch, replace the re.fullmatch call that builds and compiles a regex pattern
with every item with a simple f-string equality check. Since native_id has
already been validated to match _POSITIVE_DECIMAL, construct the expected URL
directly as an f-string using the native_id value and compare it directly
against the url field, following the pattern used in the v2ex.topic.v1 branch
above it.

In `@agent_reach/execution/v1/registry.py`:
- Around line 377-407: The validation logic contains hardcoded limit values (50
for both V2EX branches and for Exa) that should instead derive from the
capability definitions already available in the codebase. For the two V2EX
validations under the browse.hot and browse.node_topics key checks, replace the
hardcoded 50 limit with capability.maximum_items to keep the validation
synchronized with the capability definitions. For the Exa search.web validation,
replace the hardcoded 50 with a named constant that expresses the intent that
the accepted request limit intentionally exceeds the item cap, ensuring future
capability changes won't silently cause desynchronization.
- Around line 357-371: Replace the inline validation logic for the YouTube
language parameter (lines 364-369 in the first validation block) with a pattern
match against the imported _YOUTUBE_LANGUAGE pattern from contracts.py. Apply
the same approach to the identifier validation patterns in the second block
(also applies to lines 411-434) by importing and matching against the
appropriate compiled patterns from contracts instead of re-implementing the
validation rules inline. Additionally, rename _MAX_BILIBILI_QUERY_CHARACTERS to
a more generic name that reflects its shared use across multiple services
(YouTube, Exa, and Bilibili), and update all references to use the renamed
constant throughout the registry validation logic.

In `@agent_reach/execution/v1/v2ex.py`:
- Around line 319-355: Rename the `_project_replies` loop variable from `value`
to `raw_reply`, and update the immediately following `_mapping` call to use
`raw_reply`; leave the function’s behavior and other parameter references
unchanged.
- Around line 83-84: Suppress the internal _CheckpointRaised context when
re-raising the original host exception: update the raise in
agent_reach/execution/v1/v2ex.py at lines 83-84 and the corresponding raise in
agent_reach/execution/v1/exa.py at lines 114-115 to use explicit context
suppression.

In `@tests/test_execution_exa.py`:
- Around line 114-152: Refactor artifact_fixture into shared session-scoped
setup for the immutable interpreter copy and pyvenv.cfg, while retaining
per-test creation of cli.js, package.json, and sterile-config.json. Update the
_ArtifactFixture paths and dependent fixtures as needed so each test still
receives isolated mutable artifacts; preserve mutation behavior for
test_artifact_drift_fails_before_spawn and _write_cli.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 855aecaf-e8f2-4497-94ad-12479bb7e2d1

📥 Commits

Reviewing files that changed from the base of the PR and between 2a5829c and 2755b0c.

📒 Files selected for processing (19)
  • .github/workflows/pytest.yml
  • README.md
  • agent_reach/execution/v1/__init__.py
  • agent_reach/execution/v1/_v2ex_transport.py
  • agent_reach/execution/v1/contracts.py
  • agent_reach/execution/v1/exa.py
  • agent_reach/execution/v1/registry.py
  • agent_reach/execution/v1/v2ex.py
  • agent_reach/execution/v1/youtube.py
  • docs/README_en.md
  • docs/README_ja.md
  • docs/README_ko.md
  • docs/execution-v1.md
  • pyproject.toml
  • tests/test_execution_bilibili.py
  • tests/test_execution_contract.py
  • tests/test_execution_exa.py
  • tests/test_execution_v2ex.py
  • tests/test_execution_youtube.py

Comment thread agent_reach/execution/v1/_v2ex_transport.py
Comment thread agent_reach/execution/v1/contracts.py
Comment thread agent_reach/execution/v1/exa.py
Comment thread agent_reach/execution/v1/exa.py
Comment thread agent_reach/execution/v1/youtube.py
@izumi0uu
izumi0uu merged commit 9b69146 into hermes/execution-v1 Jul 31, 2026
11 checks passed
@izumi0uu
izumi0uu deleted the feat/public-platform-batch-execution-v1 branch July 31, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant