Skip to content

refactor(examples): every example-authored metadata name carries its package namespace - #30927

Draft
objectstack-fleet[bot] wants to merge 4 commits into
mainfrom
claude/project-thread-uogs4i
Draft

objectstack-fleet[bot] wants to merge 4 commits into
mainfrom
claude/project-thread-uogs4i

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:devx#1 · os-elon-musk · session_016WV4cyadQ5Zhih2y1GNTQm

Part of #30804
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:四个示例里包作者写的 40 个裸元数据名全部加上命名空间前缀(todo 20、crm 6、showcase 12、multi-package 1),连同所有拼写它们的地方(引用、种子、翻译键、测试、QA 清单)。另有一处 os lint 小改:平台按原名索取的权限集 guest_portal 不再被要求改名。
  • 为什么改:裁决 C([decision] A non-object metadata name shared by two installed packages often resolves to one package silently: which naming contract replaces ADR-0048 §3.4? #30798「同意 c」)第二阶段的改名一半;PR 1(fix(cli): os lint warns on every bare package-authored metadata name, and os generate writes them prefixed #30842)的 lint 已合并,本 PR 之后四个示例在该规则下 0 条警告。
  • 风险与回滚:只动示例(都是 private 包)、测试和文档,外加 @objectstack/cli 一个 patch。已有的 showcase 开发库里还是旧岗位名,重新播种即可。回滚即 revert。
  • 先看:① examples/app-showcase/src/security/positions.ts 与它在流程、共享规则、种子里的引用;② lint.ts 的 PLATFORM_REQUESTED_BARE_NAMES。
  • 两分钟验证:在任一示例目录跑 os lint,naming/namespace-prefix 应为 0 条;或 pnpm --filter @objectstack/example-showcase exec vitest run。
  • 需要你做的:裁一个开放问题——guest_portal 是否豁免前缀规则(本 PR 按 (a) 豁免实现,见 Acceptance notes)。裁定前本 PR 保持草稿。

What changes

Every bare name the examples author now carries the package namespace. Each rename is the fix PR 1's naming/namespace-prefix warning gives, and every reference that spells the name moves with it.

Example (namespace) Renamed
app-todo (todo) dashboard task_dashboard; reports completed_tasks, tasks_by_owner, tasks_by_priority, tasks_by_status, time_tracking; dataset task_metrics; actions clone_task, complete_task, defer_task, delete_completed, export_csv, mass_complete, set_reminder, start_task; flows task_reminder, overdue_escalation, task_completion, quick_add_task; hook task_logic
app-crm (crm) dashboard pipeline_dashboard; dataset opportunity_metrics; positions sales_rep, sales_manager, finance_approver; hook opportunity_stage_probability
app-showcase (showcase) positions contributor, manager, exec, auditor, ops, field_ops_delegate, client_liaison, client_portal_user, finance, legal; sharing rules share_new_inquiries_with_field_ops, share_key_account_qualified_contacts_with_managers
app-multi-package (package crm) app multi_crm → crm_multi_crm

Each new name is <namespace>_<old name>. The references that moved with them:

  • dataset, report and dashboard references, and the app navigation's dashboardName;
  • the action handlers register-handlers.ts binds by name, and the flows' trigger and target names;
  • flow approver steps ({ type: 'position', value }), position:<name> actor ids, sharing-rule recipients (sharedWith), and the showcase's seeded sys_user_position rows (seed-approval-demo.ts);
  • translation bundles keyed by these names (todo zh-CN and ja-JP, crm, showcase);
  • the example tests, the 18 dogfood tests that boot an example and spell one of these names (plus one dogfood fixture's comment), and 9 QA platform-checklist area files (their path#symbol anchors included; the "change" history lines are left as written).

Not renamed, on purpose: dashboard widget ids such as id: 'tasks_by_status', which are not metadata names; approver group: labels such as finance, which are not position names; and the crm permission set guest_portal, which is the open question below.

os lint leaves the platform-requested guest_portal permission set alone ('@objectstack/cli': patch). The public form doors' submit context requests the permission set named exactly guest_portal (permissions: ['guest_portal'], packages/rest/src/form-routes.ts:653), and the grant's result masker resolves a set registered under that name. ADR-0138's ledger keeps the name ("fate: keep") and cites examples/app-crm/src/security/sales-positions.ts#GuestPortalProfile as its reference declaration. Renaming it to crm_guest_portal, which the warning suggested, would leave the masker with nothing to find. The exemption is one exported constant, PLATFORM_REQUESTED_BARE_NAMES in packages/cli/src/commands/lint.ts ({ permissions: ['guest_portal'] }), so phase 3 (#30806) can read the same list. The same spelling in any other collection, or any other permission set name, is still judged, and the test pins both twins.

content/docs/ui/doc-pages.mdx no longer says the namespace prefix is "no longer load-bearing for uniqueness". It now says the doc prefix is the same contract naming/namespace-prefix applies to every package-authored name, and it keeps saying that the runtime resolves docs per package (ADR-0048).

Why

Ruling C on #30798 (maintainer, verbatim 「同意 c」): every package-authored metadata name carries its namespace, phased. PR 1 (#30842, merged as 1697d5ca9c) made os lint warn on every bare name. This PR brings the examples, which authors copy from, onto that rule. The one-file ADR-0048 §5 bullet PR follows this one and carries Fixes #30804.

Acceptance notes

  • Open question for a ruling: is guest_portal exempt? This PR builds on option (a), the seat's recommendation: a named exemption in the lint, with the citation above. If the ruling is that it must be prefixed, the constant, its one condition and its test come out, and the form door at form-routes.ts:653 and ADR-0138's ledger have to learn the new name first. That is a runtime change outside this card. Phase 3 (Bare-name phase 3: defineStack and the save door refuse a bare package-authored name; ADR-0129 and ADR-0048 amended; migration of stored and external references (ruling C on #30798) #30806) needs the same answer.

  • Bare-name counts under naming/namespace-prefix, measured with lintConfig on each example's objectstack.config.ts. Duplicates are 0 in every row, before and after.

    Config Before (main 1697d5ca9) After (this PR)
    examples/app-todo 20 (actions 8, reports 5, flows 4, dashboards 1, datasets 1, hooks 1) 0
    examples/app-crm 7 (positions 3, dashboards 1, datasets 1, permissions 1, hooks 1) 0 (6 renamed, guest_portal exempt)
    examples/app-showcase 12 (positions 10, sharingRules 2) 0
    examples/app-multi-package 1 (apps 1) 0
    create-objectstack blank template 0 0 (not touched)
    examples/embed-objectql 0 (no manifest namespace) 0 (not touched)

    Two of these renames also clear the cross-repo name coincidences phase 1's ADR-0048 text (feat(objectql)!: a picklist, mapping or dataset name holds one package (bare-name phase 1, 1/4) #30836) measured: the datasets task_metrics (app-todo) and opportunity_metrics (app-crm) shared a bare name with HotCRM's; they are now todo_task_metrics and crm_opportunity_metrics.

    The showcase's two dotted capabilities (showcase.export_data, showcase.restricted_ops) carry the namespace under Elon's 2026-11-06T11:08Z ruling (Bare-name phase 2: the namespace-prefix lint warns on every bare non-object name; first-party templates and examples adopt the prefix (ruling C on #30798) #30804, comment 6683456244), so they are not renamed.

  • Fixtures left as written, on purpose. These test files spell the same words as their own self-contained fixtures and load no example, so their names are not the examples' names: the ui workspace tests, the runtime, lint, metadata-protocol and plugin unit tests, the multi_crm fixtures in the cli and metadata tests, the position-name-fold census, docs/qa/scenarios/*.json, and sharing-rule-criteria-required.dogfood.test.ts (its sales_rep recipient is invented for the test; the showcase has no such position). The illustrative names in content/docs/** (for example "positions": ["sales_rep"] in permissions/authentication.mdx) are not tied to an example either. The upgrade harness's historical database snapshots (packages/qa/dogfood/test/upgrade-harness/fixture-18.0.0-next.5/*.sql) and populate-fixture.ts record an 18.0 boot and stay as they are. The CHANGELOG.md files are not hand-edited.

  • Held files not touched. create-objectstack/src/templates/AGENTS.md, blank/README.md (fix(cli,rest)!: os package install takes -u/--url and -t/--token; the /meta permission refusal is localized #30792) and examples/app-showcase/e2e/global-setup.ts (feat(console): an administrator's first-run checklist on Home replaces the currency and time zone dialog #30552) needed no change, and the showcase e2e specs spell none of the renamed names.

  • An existing showcase dev database keeps its old position rows until it is reseeded. The examples are private packages, and no published data changes.

  • Out of scope, found on the way. pnpm --filter @objectstack/example-todo test:mcp (examples/app-todo/test/mcp-actions.e2e.ts) exits 1 at its step 3 on main 1697d5ca9 too: the seed and the step-3 insert fail with Organization is required because the script boots with no organization. Steps 1 and 2, which spell the renamed action names, pass on both. No workflow or script runs it. Reported on the card for the seat to route.

Verification

  • os validate in each of the four examples: exit 0.
  • Example suites: app-showcase 46 files / 544 tests, app-todo 8 / 240, app-crm 7 / 48, all passed.
  • cli: lint-namespace-prefix, generate-object-namespace-prefix, generate-scaffold-validates and generate-stack-reach, 4 files / 115 tests passed.
  • Dogfood, real boots: the 18 changed dogfood test files plus sharing-rule-criteria-required, 19 files / 205 tests passed; upgrade-18x-harness, multi-package-artifact and install-local-purge-sample-data, 3 files / 34 tests passed. After rebasing onto 0e882086b (which brought phase 1, feat(objectql)!: a picklist, mapping or dataset name holds one package (bare-name phase 1, 1/4) #30836): bare-name-one-holder and multi-package-artifact, 2 files / 13 tests passed, and the three example suites again passed in full.
  • node scripts/check-platform-checklist.mjs: OK, every path#symbol anchor in the changed area files resolves.
  • os lint scope check: naming/namespace-prefix reports 0 bare names and 0 duplicates in all four examples (table above).
  • eslint --no-inline-config on every changed .ts/.mjs file: exit 0. typecheck for example-todo, example-crm, example-showcase, example-multi-package, dogfood and cli: exit 0 each.
  • Local gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 73064de88 derived 94 commands; all 94 ran and exited 0. After the rebase, the derivation at 367de6725 lists the same 94 commands, and --ran there reconciles 94 derived / 94 run / 0 NOT-MEASURED. The non---commands listing also named check-issue-citations.mjs --census (ran, exit 0) and three CI-only commands that need the runner's matrix and RUNNER_TEMP (not run locally).
Gates run at 73064de88 (94)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-test-child-env :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:examples-live-imports :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-coverage :: exit 0
pnpm check:i18n-walk-parity :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-checklist :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (packages/spec not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (eslint --no-inline-config on every changed file: exit 0.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (@objectstack/cli is the only published package touched; the examples and dogfood are private.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (n/a)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cyadQ5Zhih2y1GNTQm

…ission set alone

The public form doors' submit context requests the permission set named
exactly `guest_portal` (packages/rest/src/form-routes.ts), and the result
masker resolves a set registered under that name. Renaming it to
`<namespace>_guest_portal`, as `naming/namespace-prefix` suggested, would
leave the masker with nothing to find. `PLATFORM_REQUESTED_BARE_NAMES`
names that one spelling in `permissions`, so phase 3 (#30806) can reuse it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyadQ5Zhih2y1GNTQm
…package namespace

Ruling C on #30798, phase 2: app-todo (20 names), app-crm (6), app-showcase
(12) and app-multi-package (1) now spell every package-authored name
`<namespace>_<name>`, so `os lint`'s `naming/namespace-prefix` reports 0
bare names in each. Every reference that spells a renamed name moves with
it: dataset, report and dashboard refs, app navigation, flow and sharing
recipients, `position:` actor ids, seed position rows, translation keys,
the example and dogfood tests, and the QA platform checklist.

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

`content/docs/ui/doc-pages.mdx` said the prefix is "no longer load-bearing
for uniqueness". Under ruling C on #30798 every package-authored name
carries its namespace, and `os lint` now warns on every bare one. The
runtime still keeps same-named docs apart by package (ADR-0048), and the
page keeps saying so.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyadQ5Zhih2y1GNTQm
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/ui/forms.mdx (via guest_portal (literal, a string literal in a comment on a changed line; a string literal in permissions))
  • content/docs/ui/public-data-collection.mdx (via guest_portal (literal, a string literal in a comment on a changed line; a string literal in permissions))
What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os lint (command, 42 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 222 client-bound route-ledger rows — the other 159 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 159: 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; 104 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 — 35 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 26c965cb8f61ca59804d55e27e737bf7ff91b383 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c14b90ab451502e5879a3ce9cdfcf4b34137cf5f — the merge of head 31f0b054e77ef65ef3712ea5e8dbb955275fdb0a into base 26c965cb8f61ca59804d55e27e737bf7ff91b383, 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 c14b90ab451502e5879a3ce9cdfcf4b34137cf5f && git checkout c14b90ab451502e5879a3ce9cdfcf4b34137cf5f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 26c965cb8f61ca59804d55e27e737bf7ff91b383 31f0b054e77ef65ef3712ea5e8dbb955275fdb0a && git checkout -B drift-repro 26c965cb8f61ca59804d55e27e737bf7ff91b383 && git merge --no-ff 31f0b054e77ef65ef3712ea5e8dbb955275fdb0a

node scripts/docs-audit/affected-docs.mjs --json 26c965cb8f61ca59804d55e27e737bf7ff91b383

⚠️ 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 26c965cb8f61ca59804d55e27e737bf7ff91b383 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…name the showcase position

Two `sys_user_position` rows the fixture writes through the system path
still named the bare `contributor` while the door-written row beside them
names `showcase_contributor`. All three now name the position the
showcase declares.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyadQ5Zhih2y1GNTQm

This branch has not been deployed

No deployments
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/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant