Skip to content

fix(plugin-security): an organization administrator's refused assignment write answers the same for a row it cannot read as for a missing one - #29932

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/sec-hardening-k
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/sec-hardening-k

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:组织管理员(organization_admin / organization_admin_no_bypass)按 id 删除或修改授权行(sys_user_position、sys_user_permission_set)被拒时,对它读不到的行和根本不存在的 id 给出完全相同的回答(状态码、错误码、文案、details 全同)。委派管理闸门的组织分支改为以调用者自己的读取来判断要删除的那一行。
  • 为什么改:落实 ruling 5985885287:调用者读不到的行与不存在的 id 回答一致,对所有主体类别都适用;本 PR 把这条规则落到组织管理员分支,与按 id 写入门的既有做法一致。
  • 风险与回滚:只改组织管理员分支的前像读取来源;它能读到的行(本组织的行、single 姿态下的行)判定与提示不变,原本放行的删除仍放行,租户级管理员不经过此分支。回滚即撤销本 PR。
  • 先看:packages/plugins/plugin-security/src/delegated-admin-gate.ts 的 organizationTargets 与 organizationAdminFailure 中空前像的拒绝;org-admin-assignment-boundary.posture.test.ts 末尾新增的 describe。
  • 两分钟验证:pnpm --filter @objectstack/plugin-security exec vitest run src/org-admin-assignment-boundary.posture.test.ts,预期全部通过(含 group / isolated 两个姿态下新增的用例)。
  • 需要你做的:无,席位落地。

What changes

  • The organization branch reads the row a removal addresses as the caller. DelegatedAdminGate.organizationAdminFailure (ADR-0090 D12, amended 2026-10-17) now takes its targets from organizationTargets: an insert's payload rows, or the one row a by-id removal names, read through the engine under the caller's own context.
  • No row is one answer. When the caller's read returns no row, the removal is refused with the answer an id naming no row already got: 403 PERMISSION_DENIED, "the assignment row to remove could not be read", with the same details. That refusal stays at the gate and never reaches the engine.
  • A row the caller reads is judged as before. The organization, business-unit and boundary checks run on the row the caller read, so its guidance (the organization it belongs to, the set outside the boundary and the condition it fails) is unchanged. An edit in place was already refused before any row is read, so update answers are unchanged.
  • Read faults propagate as raised. The caller read has no catch: a store fault is neither an answer nor a refusal, and a declared read refusal is what the by-id write door answers that caller anyway.
  • Changeset: @objectstack/plugin-security patch.

Tests

  • Engine level, real ObjectQL + SQL driver + SecurityPlugin (org-admin-assignment-boundary.posture.test.ts, new top-level describe):
    • under group and isolated, for both administrator variants and both tables, the update and the delete of a row the caller cannot read (precondition: the caller's own read returns nothing) answer exactly what the same write answers for an id naming no row, compared as a whole (status, code, sentence, details), and the row stays;
    • control: a row of the caller's own organization that it reads keeps the boundary guidance and is not answered as no row; the tenant-level administrator is answered by the engine's not-found for both, as before;
    • control, single: a row of another organization that the caller reads keeps the guidance naming its organization.
  • HTTP door (packages/qa/dogfood/test/org-admin-assignment-removal-unreadable-row.dogfood.test.ts, showcase behind a real isolated wall): PATCH and DELETE of two assignment rows the organization owner cannot read answer exactly what an id naming no row answers; a row it reads keeps the guidance.
  • One-shot ablation, not kept: committed fix at d6b573a, the branch's organizationTargets call swapped back for the target walk it replaced (node scripts/ablation-replace.mjs, anchor hit 1 time, blob 589c61df → a91f1b91): org-admin-assignment-boundary.posture.test.ts 2 failed | 63 passed (the two walled postures' new case, orgAdmin delete sys_user_position: the unreadable row answers as no row); every control stayed green. Restored blob == HEAD blob 589c61df and git diff HEAD empty; the restored run 65/65.

Acceptance notes

  • delegated-admin-gate.test.ts's organization harness reads through a fake ql that ignores context, so its foreign-row case stands for a caller who reads the row, and its expectation (the guidance naming the organization) is unchanged, as the single control pins on the real engine.

Verification

Final head 51d9481 (main merged at b6fe08a, clean)
  • pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/org-admin-assignment-boundary.posture.test.ts src/delegated-admin-gate.test.ts: 2 files, 202/202.
  • pnpm --filter @objectstack/plugin-security typecheck: exit 0 (tsc, scripts tsc, test layer 0 errors).
  • Every command node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives: 64 commands, each exit 0, captured before any pipe; --ran reconciliation: 64 derived, 64 run, 0 NOT-MEASURED. check:dual-build-cjs-loads and check:type-check-debt ran with the lock held and the dist they read built.
At b6fe08a (the main merge; the two later commits change only the posture test's caller read)
  • pnpm turbo run build --filter=@objectstack/dogfood --concurrency=2: exit 0.
  • pnpm --filter @objectstack/spec check:generated: exit 0.
  • pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 (full suite): 241 files, 5225 passed, 45 skipped.
  • pnpm --filter @objectstack/dogfood exec vitest run --project isolated test/org-admin-assignment-removal-unreadable-row.dogfood.test.ts: 3/3.
  • pnpm --filter @objectstack/dogfood typecheck: exit 0.
  • pnpm --filter @objectstack/dogfood test (full battery, once): 413 files passed, 3 skipped; 3415 tests passed, 28 skipped.

Generated by Claude Code

…n the caller's own read

An organization administrator's refused assignment write answers the same
for a row it cannot read as for a missing one (ADR-0090 D12; ruling of
record 5985885287): the organization branch of the delegated-administration
gate reads the row a removal addresses under the caller's own context, and
a row that read does not return is refused exactly as an id naming no row
is. A row the caller reads keeps the boundary guidance.

Claude-Session: https://claude.ai/code/session_01QSeKYn5CmmdShCJifm6WYj
Co-authored-by: Claude <noreply@anthropic.com>
…t write at the HTTP door

Behind a real organization wall, an organization administrator's PATCH and
DELETE of an assignment row it cannot read answer exactly what an id naming
no row answers; a row it reads keeps the boundary guidance. Adds the patch
changeset for plugin-security.

Claude-Session: https://claude.ai/code/session_01QSeKYn5CmmdShCJifm6WYj
Co-authored-by: Claude <noreply@anthropic.com>
… options erasure

The posture rig's caller reads keep the engine query options typed, so the
test surface of the query-options erasure ratchet stays at its ceiling.

Claude-Session: https://claude.ai/code/session_01QSeKYn5CmmdShCJifm6WYj
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 4 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/delegated-administration.mdx (via DelegatedAdminGate (symbol, a top-level class))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v13.mdx (via DelegatedAdminGate (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 17 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7b2bb668dacc8d915c161791facf20be78138a99 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a75819f70b5f3159cc9aeec788129b3d2655dbf1 — the merge of head 51d9481a50435663746886fd79a8bca4aaed02f7 into base 7b2bb668dacc8d915c161791facf20be78138a99, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a75819f70b5f3159cc9aeec788129b3d2655dbf1 && git checkout a75819f70b5f3159cc9aeec788129b3d2655dbf1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7b2bb668dacc8d915c161791facf20be78138a99 51d9481a50435663746886fd79a8bca4aaed02f7 && git checkout -B drift-repro 7b2bb668dacc8d915c161791facf20be78138a99 && git merge --no-ff 51d9481a50435663746886fd79a8bca4aaed02f7

node scripts/docs-audit/affected-docs.mjs --json 7b2bb668dacc8d915c161791facf20be78138a99

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7b2bb668dacc8d915c161791facf20be78138a99 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj

Contract-review-claim: session_01QSeKYn5CmmdShCJifm6WYj · head 51d9481 · 2026-11-03T13:43Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 51d9481a50435663746886fd79a8bca4aaed02f7
Local-runs: none
Scope: packages/plugins/plugin-security/src/delegated-admin-gate.ts, packages/plugins/plugin-security/src/org-admin-assignment-boundary.posture.test.ts, packages/qa/dogfood/test/org-admin-assignment-removal-unreadable-row.dogfood.test.ts, .changeset/org-admin-removal-caller-read.md + packages/plugins/plugin-security/src/security-plugin-authz-middleware.ts (readDoorAnswerForGateRefusal, the gate call, step 2.7), packages/plugins/plugin-security/src/security-plugin.ts (getCallerPreImage, absentUnderCallerRead, the gate's construction), packages/plugins/plugin-security/src/security-plugin-addressed-writes.ts (readRowById), packages/plugins/plugin-security/src/security-plugin-object-admission.ts (the admission probe's call into the gate), packages/plugins/plugin-security/src/objects/default-permission-sets.ts (the two organization-administrator sets), packages/plugins/plugin-security/src/objects/sys-user-position.object.ts, packages/plugins/plugin-security/src/objects/sys-user-permission-set.object.ts, packages/objectql/src/engine.ts (findOne), packages/spec/src/identity/eval-user.zod.ts, content/docs/permissions/delegated-administration.mdx

Reviewed on the net diff against main at the merge base (4 files, +319/−2), the PR body and the head's check-runs; no dispatch order and no developer report were read as inputs. The record stays at rule level.

① Derived judgments

What changes, and for whom. One thing: in the ORGANIZATION branch of the ADR-0090 D12 gate, the pre-image of a by-id removal is now the caller's own by-id read through the engine (organizationTargets) instead of a system-context read. The read has the same shape as the by-id write doors' own pre-image read (readRowById: findOne(object, { where: { id }, context })), so it is the read door's own question, every wall and read visibility included.

  • Organization administrator, both variants (organization_admin, organization_admin_no_bypass), under group and isolated. A by-id DELETE of a row the caller's read does not return is refused at the gate with the answer an id naming no row already got (403 PERMISSION_DENIED, "the assignment row to remove could not be read", details {operation, object}), and never reaches the engine. Before this head the same write was refused with guidance naming the row's organization. A row the caller reads is judged on what it read, so an own-organization row keeps its boundary guidance (pinned as a control for both variants and both tables). UPDATE, and the lifecycle verbs, are refused by the branch as "not part of the right" BEFORE any row is read, so their answers are identical for a hidden row and a missing id with or without this change — the tree bears out the body's claim that update answers are unchanged.
  • single. No wall: the caller reads a row of another organization, so the guidance that names that organization stands. Consistent with the ruling (the row is one the caller can read); pinned as a control.
  • Tenant-level / platform administrator. Returns from the gate before the branch; the engine answers both a hidden row and a missing id with its not-found (pinned as a control). Unchanged.
  • Delegate (no organization-administrator set). Never enters the branch; judged by the delegated branch, whose pre-image read is unchanged (see ③).
  • A caller holding BOTH an organization-administrator set and a delegated scope. The organization branch refuses as above, and authorize then hands the write to the delegated branch (held.length > 0), which answers it. That caller class is therefore still answered by the delegated branch — out of this PR by design (③).
  • System. Short-circuits the middleware before the gate; a flow run's system context is refused ahead of it. Unchanged.
  • A caller read that is refused (declared 4xx) or that faults. The gate has no catch, so it propagates; the middleware's readDoorAnswerForGateRefusal then asks the read door again and answers a declared read refusal as the answer for any id, and passes a fault (≥500) through as raised. Both are the same answers the merged by-id write door already gives that caller; a store fault is neither an answer nor a refusal, as readRowById already holds.

Clause-②: no is right. Every removal the branch admitted before is still admitted: admission requires the row to be the caller's own organization's and inside the boundary; both administrator sets hold allowRead on both assignment tables (the walled variant also viewAllRecords), the two objects declare no row-level sharing or owner floor that could hide an own-organization row, and the posture test's control reads another user's own-organization row for both variants under both walled postures. The only answers that change were refusals before and are refusals now, so nothing a consumer could do on main is taken away. The canWrite admission probe, which calls the gate with a synthetic context, answers false for that row before and after.

② Semver level

patch for @objectstack/plugin-security; Metadata-protocol: none. No published schema, spec export, route or wire shape changes; the only observable change is the wording of one refusal for a class of write that was already refused. The changeset carries the rule, the two trailer lines and no mechanism.

③ Boundary flags

  1. HTTP dogfood pin has no red leg. The ablation was run only at the engine level (the posture test went 2 red, controls green, restored run green). The HTTP pin's ability to fail rests on its own controls: the wall is asserted active and not degraded, the caller is asserted no platform administrator and active in its organization, its GET of each foreign row is asserted 404 and of its own row 200, and the own-row control asserts the answer is NOT the missing-id answer. Accepted as a flag; the seat's follow-up should give the HTTP pin a measured red leg.
  2. The dogfood battery ran at the merge commit b6fe08a, not this head. Verified on the commit list: the two later commits (23da8f6, 51d9481) touch only the posture test. Accepted.
  3. Check-runs on this head. Green: Auto Label, Check Changeset, Check PR Size, Dogfood Regression Gate, Flag docs affected, Governed Surface Queue Guard, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · scoped · query-options erasure, Lint · scoped · tooling self-tests, PR-body guards, Plan Test Core shards, Spec property liveness, Test UI, Type Check · source gates, filter, Build Core, Lint · ESLint, Lint · CI/release/engine gates, Lint · repo & tooling gates, Lint · scoped · slot lookup & corpus, Live E2E UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · workspace, Test Core 2/8, 3/8, 5/8, 6/8 and 7/8 (32 green at the last refresh). Skipped (opt-in or no UI change): 18.x upgrade harness, Build & E2E UI, Build Docs, Build UI Deps, the Dogfood Regression Gate shards, Dogfood Verify CLI, Lint UI, Packed-tarball smoke, Schedule gate, Temporal Conformance, Test UI shards, Type Check UI, UI Published Artifacts. STILL RUNNING at the time of this record: Test Core 1/8, 4/8 and 8/8. Their conclusions are the gate verdicts; this PASS does not stand in for them. The Dogfood Regression Gate's three shards were skipped, so the new HTTP pin's CI execution on this head is not evidenced by a check-run.
  4. The delegated branch is left as it was — correctly, and escalated. materializeTargets still reads a removal's pre-image under the system context and does not refuse an empty pre-image at the gate, so for a delegate — and for the organization administrator who also holds a delegated scope (①) — a hidden row and a missing id are not yet answered alike. Leaving it out of this PR is right: it is a different branch with its own pre-image semantics (a merged update image, subtree and allowlist judgments) and needs its own measurement. Escalated to the seat as its follow-up, no card.
  5. Unit harness. delegated-admin-gate.test.ts (not in the diff) drives the organization branch through a fake engine that ignores context, so its foreign-row case now stands for a caller who reads the row and its expectation is unchanged — the same shape the single control pins on the real engine. Fine as is; a follow-up may add a context-honouring double.
  6. Docs. The drift check names content/docs/permissions/delegated-administration.mdx; read at this head it states nothing about the pre-image's read context, so no sentence is contradicted. The lane may add the rule ("the removal is judged as the caller reads the row") when the delegated follow-up lands, so both branches are documented once.

④ Design judgment

Tried to break it against the cited code; no must-fix found.

  • An organization-administrator refusal still worded from a system read? None. Update and the lifecycle verbs are refused before any read; a filter delete is refused before any read; insert judges payload rows. The branch's other reads (positionBoundaryFailure, organizationBoundaryFailure, businessUnitInOrganization) read the catalog and the organization's units by values taken from the row the caller read, and businessUnitInOrganization runs on t.next only (insert). No guidance about a removal's row can come from a read the caller did not make.
  • A caller read that throws something other than a declared 4xx. The call passes only where: { id } (scalar by extractSingleId) and context, both accepted by findOne (requireFindOnePredicate is satisfied; no projection, no order). What can be thrown is a declared read refusal (answered, ①) or a store fault (propagated as raised, exactly as readRowById propagates one). The object-admission probe catches PermissionDeniedError only and lets a fault reach its outer deny-and-log, as before.
  • Cost on the admitted path. Read count is unchanged (one gate read before, one now, plus the middleware's own step 2.7 pre-image read either way), but the gate's read now carries a full read-middleware pass as the caller, and it is not shared with the middleware's opCtx.__preImage memo, so an admitted by-id delete reads the row as the caller twice and a refused one three times (readDoorAnswerForGateRefusal asks again). Bounded (by id, limit 1), on a low-volume administrative write; not a must-fix. A follow-up may let the gate seed or consume that memo (the gate receives the middleware's own opCtx; the admission probe's synthetic context would be unaffected).
  • Race between the caller read and the write. Unchanged window: the gate's read is advisory to the gate alone; the middleware's step 2.7 re-reads the row under the write-class filter and the tenant wall is composed into the write itself, so a row that changes organization after the gate read is refused there or written correctly, never written across the wall. The row-level answer is not an existence answer.
  • An own-organization row whose guidance changed. None found: a readable row is judged on organization_id, position / the grant's set name, and (insert only) business_unit_id; neither set declares field masking on the two tables, and both controls show those fields present after the caller's read.
  • single. The branch behaves as before: no wall, the row is read, the organization guidance stands; callerOrganizationId is the Default Organization. The control pins it for both variants.
  • Nested and on-behalf-of writes. A nested by-id removal under the caller's context is now judged on the caller's read; a cross-wall row was refused before (with guidance) and is refused now (absent-row answer) — no admitted write is lost. An on-behalf-of write reads as the context the write carries, which is the principal the branch judges.
  • The test's precondition is the gate's own read. readAs in the posture test issues the same findOne shape as organizationTargets and asserts it empty before comparing the write answers whole (status, code, sentence, details) and asserting the row is left; the HTTP pin compares whole bodies with only the caller-supplied id written out.

Implemented-by: claude/sec-hardening-k
Reviewed-by: session_01QSeKYn5CmmdShCJifm6WYj

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj

席位核对

ACCEPT · head 51d9481a50435663746886fd79a8bca4aaed02f7 · 2026-11-03T14:05Z

  • Shape: 4 files, +319/−2, not governed, under 3000. Patch for plugin-security, with Clause-②: no and Metadata-protocol: none.
  • Rule held (ruling 5985885287, every principal class; the maintainer confirmed it applies): in the ADR-0090 D12 gate's organization branch, the target's pre-image is the caller's own read. A row the caller cannot read answers exactly what a missing id answers. An empty caller pre-image is refused at the gate, never at the engine.
  • Contract review: comment 6605651408 records Served-tier: CONTRACT_REVIEW_TIER, a Head-sha equal to this head, and VERDICT PASS with no must-fix.
  • Check-runs at this head: 36 success, 13 skipped, 0 failed, all completed. Skips were judged by name. check-expected-skips.mjs was NOT MEASURED.
  • Accepted deviations:
    • The full dogfood battery ran at merge commit b6fe08a5; the later commits touch only the posture test.
    • The HTTP dogfood pin has no red leg; it is backed by the engine-level ablation and its guard controls. The seat owes it a measured red leg as a follow-up.
    • A Metadata-protocol: none line was added for the Check Changeset gate.
  • Out of scope (the security seat's follow-up order): the delegated (adminScope) branch, including an org admin who also holds an adminScope; the edit-without-read delegate; sharing the gate's caller read with the middleware's pre-image memo.
  • Landing: ready, then auto-merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant