Skip to content

feat(project): implement project status - #2165

Merged
jariy17 merged 1 commit into
refactorfrom
feat/project-status
Sep 3, 2026
Merged

feat(project): implement project status#2165
jariy17 merged 1 commit into
refactorfrom
feat/project-status

Conversation

@jariy17

@jariy17 jariy17 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The status handler calls ProjectManager.resolveProjectResources to build its report.

resolveProjectResources returns
every resource the project declares, by ARN, reading CfnOutput values from DescribeStacks.
A resource the target's stack has not deployed is reported local-only rather than omitted,
and children such as gateway targets and policies are nested under the resource that owns them.

Given an agentcore.json like this:

{
  "name": "orders",
  "version": 1,
  "runtimes": [{ "name": "assistant" }],
  "memories": [{ "name": "user_mem" }],
  "agentCoreGateways": [
    { "name": "tools", "targets": [{ "name": "search" }] }
  ],
  "policyEngines": [
    { "name": "guards", "policies": [{ "name": "no_pii" }] }
  ]
}

agentcore project status produces:

{
  "projectName": "orders",
  "target": "prod",
  "region": "us-west-2",
  "resources": [
    {
      "resourceType": "runtime",
      "name": "assistant",
      "deploymentState": "deployed",
      "id": "arn:aws:bedrock-agentcore:us-west-2:111122223333:runtime/assistant-AbCdEf1234"
    },
    {
      "resourceType": "memory",
      "name": "user_mem",
      "deploymentState": "deployed",
      "id": "arn:aws:bedrock-agentcore:us-west-2:111122223333:memory/user_mem-K3l4M5n6O7"
    },
    {
      "resourceType": "gateway",
      "name": "tools",
      "deploymentState": "local-only",
      "children": [
        {
          "resourceType": "gateway-target",
          "name": "search",
          "deploymentState": "local-only"
        }
      ]
    },
    {
      "resourceType": "policy-engine",
      "name": "guards",
      "deploymentState": "deployed",
      "id": "arn:aws:bedrock-agentcore:us-west-2:111122223333:policy-engine/guards-1a2b3c",
      "children": [
        {
          "resourceType": "policy",
          "name": "no_pii",
          "deploymentState": "deployed",
          "id": "arn:aws:bedrock-agentcore:us-west-2:111122223333:policy/no_pii-7d8e9f"
        }
      ]
    }
  ]
}

If the target isn't declared in aws-targets.json, it throws ProjectStateError:
No deployment targets are configured for project 'orders'.

@github-actions github-actions Bot added the size/l PR size: L label Sep 2, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added agentcore-harness-reviewing AgentCore Harness review in progress claude-security-reviewing Claude Code /security-review in progress labels Sep 2, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026

@agentcore-devx-automation agentcore-devx-automation Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AgentCore Harness Review

Verdict: Looks good

Reviewed commit d3997453 per the PR description (rest of the branch belongs to #2158).

What I checked

  • src/handlers/project/status/index.ts – handler logic
  • src/handlers/project/index.tswithProject wrapping matches the deploy/build/remove/dev pattern
  • src/handlers/project/project.test.ts – the stub test replacement asserts the withProject wiring
  • src/handlers/project/status/index.test.ts – four handler-level tests through root.route

Notes

  • describe()'s spec walk mirrors the declared set in CdkBackend.resolveDeployedResources, and every field it iterates is either .default([]) in the schema or guarded with ?? [] (unassignedTargets, payments). No undefined‑array footguns.
  • The composite key(resourceType, name, parent) used to match resolver rows against declared rows is symmetric with what the CDK backend produces, so a resource returned by the resolver will always match its declared row.
  • Tests keep the real FsProjectManager and scaffold a real project in a temp directory, stubbing only at the ProjectBackend boundary — matches the guidance on avoiding excessive mocking.
  • Telemetry isn't wired into sibling handlers (deploy, build, remove, etc.), so its absence here isn't a regression against an existing pattern.
  • Scope caveats (pending-removal, datasets, capacity-provider pending l3-cdk-constructs#336, gateway-target reported by Id rather than Arn) are all documented in code or the PR description.

No blocking issues. Ready to merge once #2158 lands.

@agentcore-devx-automation agentcore-devx-automation Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label Sep 2, 2026
@codecov-commenter

codecov-commenter commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.13%. Comparing base (d403f5d) to head (54fed44).
⚠️ Report is 3 commits behind head on refactor.

Additional details and impacted files
@@             Coverage Diff              @@
##           refactor    #2165      +/-   ##
============================================
+ Coverage     97.12%   97.13%   +0.01%     
============================================
  Files           535      535              
  Lines         36844    36980     +136     
============================================
+ Hits          35783    35919     +136     
  Misses         1061     1061              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jariy17

jariy17 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author
{ "projectName": "bbStatus30455", "target": "default", "region": "us-west-2",
  "resources": [
    { "resourceType": "harness", "name": "bbStatus30455", "deploymentState": "deployed",
      "identifier": "arn:aws:bedrock-agentcore:us-west-2:725476964917:harness/bbStatus30455_bbStatus30455-R7ttcnrOG8" },
    { "resourceType": "memory", "name": "user_mem", "deploymentState": "deployed",
      "identifier": "arn:aws:bedrock-agentcore:us-west-2:725476964917:memory/bbStatus30455_user_mem-9ndWxKF91x" },
    { "resourceType": "gateway", "name": "tools", "deploymentState": "deployed",
      "identifier": "arn:aws:bedrock-agentcore:us-west-2:725476964917:gateway/bbstatus30455-tools-hwyi9qiqkn" },
    { "resourceType": "policy-engine", "name": "guards", "deploymentState": "deployed",
      "identifier": "arn:aws:bedrock-agentcore:us-west-2:725476964917:policy-engine/bbStatus30455_guards-qjcpxplfak",
      "children": [
        { "resourceType": "policy", "name": "no_pii", "deploymentState": "deployed",
          "identifier": "arn:aws:bedrock-agentcore:us-west-2:725476964917:policy-engine/bbStatus30455_guards-qjcpxplfak/policy/no_pii-i9vsi_xtqh" } ] } ] }

@AlexanderRichey AlexanderRichey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't look like it implements the approach we discussed of calling DescribeStacks. What motivated changing direction here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this a binary file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My agent add NUL bytes to this file which made github make it into a binary that is now fixed.

@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Sep 2, 2026
@jariy17
jariy17 force-pushed the feat/project-status branch from d399745 to 40d7f76 Compare September 2, 2026 13:14
@jariy17

jariy17 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

First-run fix: project status on a freshly created project

project create leaves agentcore/aws-targets.json as [], and only project deploy provisions the default target. status resolved its target through a path that demanded the target already exist, so the most likely first command after create returned a config error instead of reporting that nothing is deployed yet. allowMissing did not cover it — that flag handles a declared target with no stack, not an undeclared target.

Mechanism. resolveExistingTarget became resolveTarget(project, name, { undeclaredDefaultIsUndeployed }), returning AwsDeploymentTarget | undefined. resolveDeployedResources passes undeclaredDefaultIsUndeployed: input.allowMissing === true and short-circuits to { resources: [] } when no target is declared, so ResolvedDeployedResources.target is now optional. The status envelope falls back to the requested name and the CLI's effective region (RegionKey); a declared target still reports its own region.

Chosen over the alternatives because it keeps status read-only — no aws-targets.json write, no provisionDefaultTarget, no STS lookup — and expresses the absence in the type rather than via caught exceptions or a fabricated account. Only default is treated as implicitly-present, mirroring the guard on the deploy side and for the same reason: inventing a named target would turn a typo'd --target into a report about somewhere unintended.

Live, against a fresh project (no deploy):

Before — exit 1:

Error: No deployment targets are configured for project 'bbFirstRun12141'. Add at least one to .../agentcore/aws-targets.json, for example:
[{ "name": "default", "account": "111122223333", "region": "us-east-1" }]

After — exit 0:

{
  "projectName": "bbFirstRun12141",
  "target": "default",
  "region": "us-west-2",
  "resources": [
    { "resourceType": "harness", "name": "bbFirstRun12141", "deploymentState": "local-only" },
    { "resourceType": "memory", "name": "shortTerm", "deploymentState": "local-only" }
  ]
}

Unchanged, verified live with targets declared:

  • --target typo → exit 1, Project '...' has no deployment target named 'typo'. ... defines: default, eu.
  • --target eu → reports "target": "eu", "region": "eu-west-1", not the ambient region.

One handler test covers it; it fails without the fix and passes with it (verified by mutation).

Separately: the handler was a binary file to git

status/index.ts joined its resource-key parts with literal NUL bytes, so git classified the file as binary and its contents did not render in the diff — the handler was unreviewable in this PR. The separator is now /, which cannot appear in a resource name (^[a-zA-Z][a-zA-Z0-9_]{0,47}$), so collision safety is unchanged. The committed blob is now ASCII text.

Code comments have also been dropped from the code added by this PR and by #2158.

tsc clean · bun test 2608 pass / 0 fail · oxlint clean · prettier clean.

@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Sep 2, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Sep 2, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Sep 2, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@github-actions github-actions Bot removed the size/l PR size: L label Sep 2, 2026
@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@jariy17

jariy17 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

| This doesn't look like it implements the approach we discussed of calling DescribeStacks. What motivated changing direction here?

ProjectManager.resolveProjectResources already calls DescribeStacks so I just expanded to all project resources. The handler will call this function. The only change from yesterday is that I nested resources that are children of a resource i.e GatewayTargets to Gateway.

@tejaskash tejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Built it and drove it against a real account in us-west-2. Status resolves every resource correctly and headless invoke works. Two blockers before merge: the interactive invoke picker is broken (ARNs sent where ids are expected) and child nesting matches parents by name only. Details inline.

Comment thread src/handlers/project/invoke/screen.tsx Outdated
let active = true;
void core.projectManager
.resolveDeployedResources(project, { target: "default" })
.resolveProjectResources(project, { target: "default" })

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Regression from this PR. The manager never forwards an identifier here, so the resolver defaults to ARNs and select() passes them as harnessId/agentRuntimeId. Picking a deployed harness in the TUI fails with Value 'arn:aws:...:harness/...' at 'harnessId' failed to satisfy constraint. Headless invoke was fixed with identifier: "id", this path was not. Returning both id and arn on each row would remove the toggle entirely.

Comment thread src/handlers/project/status/index.ts Outdated
const top: ResourceStatus[] = [];
for (const { resource, row } of entries) {
const owner = resource.parent
? entries.find(({ resource: other }) => !other.parent && other.name === resource.parent)

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Parent is matched by name only. Names are unique per collection, not across them, so a gateway and a policy engine can both be guards. Reproduced live: the deployed policy showed under the gateway and the engine had no children. Match on the expected parent type too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice catch!

region: resolved.target.region,
resources: nest(resolved.resources),
};
ctx.require(JsonRendererKey).renderJson(status);

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

deploy, build, dev and export in this router check JsonKey and print a human line otherwise. This always dumps JSON, so a bare agentcore project status in a terminal gets a raw blob.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agentcore project status will just print out the json output for now. If customers want a human readable, Alex is implementing the tui experience after this pr gets merged.

});
});

test("rejects a freshly created project that declares no targets", async () => {

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

project create leaves aws-targets.json empty and deploy fills it, so right after create status tells the user to hand-write the file with an example account id. Commit bfef4ea fixed this and drop allowMissing undid it. The "all local-only" test above only passes because the fake backend never throws, the real one always does here.

const { target, identifier = "arn" } = input;
const deployedState = await readDeployedState(this.json, project.rootPath);
const stackArn = deployedState.targets[target.name]?.stackArn;
if (!stackArn) {

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For status, a missing stack just means nothing is deployed yet, which is exactly what local-only describes. Deploy and remove can keep the hard failure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah that makes sense. I'll let the handlers throw the ProjectStateError if they want too.

Comment thread src/core/project/backends/cdk.ts Outdated
}
};

const declared: Declared[] = [

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The spec also has datasets and mcpRuntimeTools, and declaresNothingDeployable in deploy counts both. They are missing here, so a deployed dataset gets no row at all and deploy and status disagree on what a resource is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Datasets are being deprecated in favor of the imperative based commands. I think mcpRuntimeTools is also deprecated because I don't see any add command for it. declaresNothingDeployable doesn't exist.

Comment thread src/core/project/backends/cdk.ts Outdated
return byExportName("Policy", parent ?? "", name, suffix);
case "config-bundle":
return byExportName("ConfigBundle", name, suffix);
case "capacity-provider":

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No capacityProviders field exists anywhere in the spec, so this case and the union member are unreachable, and the never default forces every future switch to keep handling it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was thinking we will add capacityProvider in the new cli but I can remove it for now.

Comment thread src/core/project/backends/cdk.ts Outdated
case "capacity-provider":
return byExportName("CapacityProvider", name, suffix);
case "payment":
// Payments doesn't set a ExportName so we search for OutputKey

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This links to the L3 AgentCorePayments construct, but the generated project never uses it. The Payment<name>ManagerArn output comes from toCdkId in the CLI's cdk-stack.ts template. Also the workspace rule is no line comments, so these should go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But I like these comments. It help developers understand why the credentials and Payments are special cases.

Comment thread src/handlers/project/types.ts Outdated
| "policy-engine"
| "policy"
| "config-bundle"
| "payment"

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

project add and project remove call this payment-manager (see ProjectResource below), status calls it payment. Deriving this from ProjectResource keeps the two from drifting.

Comment thread src/core/project/backends/cdk.test.ts Outdated

describe("CdkBackend.resolveDeployedResources", () => {
test("describes the stack once and returns only resources with deployed ID outputs", async () => {
describe("CdkBackend.resolveProjectResources", () => {

@tejaskash tejaskash Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The base branch tested -RuntimeId and -Harness-<name>-Id. Now nothing here asks for identifier: "id", so a typo in the suffix logic would make headless invoke report a deployed runtime as not deployed with CI green. One id-mode case would cover it.

Comment thread src/handlers/project/invoke/screen.tsx Outdated
(r.resourceType === "runtime" || r.resourceType === "harness") &&
r.deploymentState === "deployed",
)
.map((resource) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this code changing in this PR? I thought the scope was the status command.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/l PR size: L labels Sep 2, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 2, 2026
Comment thread src/handlers/project/invoke/runtime.tsx Outdated
const project = ctx.require(ProjectKey);
const name = selectProjectResource(project, "runtime", flags.name);
const deployed = await core.projectManager.resolveDeployedResource(project, {
const deployed = await core.projectManager.resolveProjectResource(project, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There doesn't seem to be anything pressing that necessitates this name change. It's causing a lot of cascading changes that make this code hard to review and increase the risk of introducing errors. Let's omit this part of the PR and focus on just the status functionality.

@jariy17 jariy17 Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I renamed resolveDeployResource to resolveProjectResources because it now returns whether a resource was deployed or not. I'll just make resolveProjectResource it's own function in the next revision. Later we can merge the functions.

@jariy17
jariy17 marked this pull request as draft September 2, 2026 23:14
@jariy17
jariy17 force-pushed the feat/project-status branch from e93ac29 to ffed816 Compare September 3, 2026 13:46
@github-actions github-actions Bot added size/l PR size: L and removed size/xl PR size: XL labels Sep 3, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 3, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 3, 2026
@jariy17
jariy17 force-pushed the feat/project-status branch from ffed816 to 212072b Compare September 3, 2026 14:15
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Sep 3, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 3, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

})),
];
},
async resolveProjectResources() {

@jariy17 jariy17 Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was meant to prevent invoke handlers from using this resolvedProjectResources because they should use resolveDeployedResource. We will merge these functions later.

Adds resolveProjectResources alongside resolveDeployedResources rather than
widening it. The two answer different questions: invoke asks which Runtimes and
Harnesses are live and addresses them by the bare id its data-plane APIs
accept, while status reports every declared resource by ARN, including the ones
the target has not deployed. Keeping them separate leaves the invoke path
untouched.

The new resolver reports a resource the stack does not publish as local-only
instead of omitting it, so status distinguishes "declared but not deployed"
from "not declared". A target with no recorded stack reports everything
local-only without reaching AWS, and a project that has never deployed stands
in an in-memory default target from the region the CLI resolved, rather than
telling the user to hand-write aws-targets.json.

Children nest under the resource that owns them, matched on the owner's type as
well as its name: names are unique per spec collection but not across them, so
a gateway and a policy engine can both be called 'guards'.
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

This was referenced Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants