Skip to content

Commit 9d313f3

Browse files
refactor(examples): every example-authored metadata name carries its package namespace (#30927)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D9HSxC7WngZADjXgb6UHCVp)_ 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(#30798「同意 c」)第二阶段的改名一半;PR 1(#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 (#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 (#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 (#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` (#30792) and `examples/app-showcase/e2e/global-setup.ts` (#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, #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). <details><summary>Gates run at 73064de88 (94)</summary> ```text 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 ``` </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (`packages/spec` not touched.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (eslint `--no-inline-config` on every changed file: exit 0.) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 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.) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (n/a) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4cyadQ5Zhih2y1GNTQm --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent acd190b commit 9d313f3

79 files changed

Lines changed: 386 additions & 325 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os lint`'s `naming/namespace-prefix` no longer asks a package to rename the `guest_portal` permission set
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The public form doors' submit context requests the permission set named exactly `guest_portal`, and the result masker resolves a set registered under that name. A package that declares it is plugging into that door. Renaming it to `<namespace>_guest_portal`, which the warning used to suggest, would leave the masker with nothing to find. `os lint` now leaves that one name alone in `permissions`. The same spelling in any other collection, or any other permission set name, is still judged.

‎content/docs/ui/doc-pages.mdx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,12 @@ src/docs/crm_user_guide.md ✅ package namespace is "crm"
7474
src/docs/user_guide.md ❌ bare name — lint error (docs/namespace-prefix)
7575
```
7676

77-
This is the same `namespace-prefix` lint the platform applies to other
78-
named metadata for readable, globally-unique filenames. It is no longer
79-
**load-bearing for uniqueness**, however: per **ADR-0048**, doc
80-
resolution is **package-scoped**. Packaged items are stored under a
77+
Docs have their own rule id, but the contract is the one `os lint`'s
78+
`naming/namespace-prefix` warning applies to every other package-authored
79+
name: each one carries its package namespace, so that no two installed
80+
packages hold the same name. The runtime does not rely on the prefix to
81+
keep docs apart, however: per **ADR-0048**, doc resolution is
82+
**package-scoped**. Packaged items are stored under a
8183
composite `<packageId>:<name>` registry key, and each retains its
8284
`_packageId`, so two installed packages may each ship a doc with the same
8385
bare name and coexist — neither silently overwrites the other.

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 15 additions & 15 deletions
Large diffs are not rendered by default.

‎docs/qa/platform-checklist/areas/api-backend.json‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,14 +2736,14 @@
27362736
"surface": "api",
27372737
"personas": [
27382738
"admin, authenticated — the live-boot half",
2739-
"a restricted member who cannot read every field of showcase_project — a member bound to the client_liaison position (showcase_client_liaison set: budget, budget_remaining and spent unreadable) — the masking clause is only observable as a non-admin",
2739+
"a restricted member who cannot read every field of showcase_project — a member bound to the showcase_client_liaison position (showcase_client_liaison set: budget, budget_remaining and spent unreadable) — the masking clause is only observable as a non-admin",
27402740
"no persona for the variant half: a non-default `metadata` block is authorable only by a programmatic embedder (knownGaps)"
27412741
],
27422742
"fixtures": {
27432743
"app": "showcase",
27442744
"requires": [
27452745
"a stock showcase boot for the default-config half (prefix /meta, cache on, masking ON by default)",
2746-
"a persona whose field-level security hides at least one field of a served object — on stock that is the client_liaison position's showcase_client_liaison set, which withholds showcase_project.budget, .spent and .budget_remaining (readable:false); no stock persona carries FLS on any showcase_account field, so a showcase_account diff is empty for everyone. Without such a persona the masking clause scores blocked(fixture), never green",
2746+
"a persona whose field-level security hides at least one field of a served object — on stock that is the showcase_client_liaison position's showcase_client_liaison set, which withholds showcase_project.budget, .spent and .budget_remaining (readable:false); no stock persona carries FLS on any showcase_account field, so a showcase_account diff is empty for everyone. Without such a persona the masking clause scores blocked(fixture), never green",
27472747
"a scratch vitest harness in packages/rest for the variant half (construct, registerRoutes(), getRoutes())"
27482748
],
27492749
"knownGaps": [
@@ -2754,7 +2754,7 @@
27542754
"steps": [
27552755
"boot the showcase on its own port and DB; as admin fire the default-prefix metadata mounts and capture status: GET /api/v1/meta, /meta/types, /meta/diagnostics, /meta/_drafts, GET /meta/object, GET /meta/object/showcase_task, /meta/object/showcase_task/references, /meta/object/showcase_task/layers, /meta/book/<a seeded book>/tree; fire the routing-404 control alongside",
27562756
"GET /api/v1/discovery and read routes.metadata — it must equal the base plus the configured prefix",
2757-
"as the restricted persona (a member bound to the client_liaison position) GET /api/v1/meta/object/showcase_project and diff the served fields against the admin's copy of the same document",
2757+
"as the restricted persona (a member bound to the showcase_client_liaison position) GET /api/v1/meta/object/showcase_project and diff the served fields against the admin's copy of the same document",
27582758
"harness: construct with { metadata: { prefix: '/metadata' } } and confirm every metadata mount re-bases and the discovery body advertises the new prefix",
27592759
"harness: construct with { metadata: { endpoints: { types: false } } } — GET {prefix} and GET {prefix}/types both disappear (one handler, two paths)",
27602760
"harness: construct with { metadata: { endpoints: { items: false } } } and enumerate what leaves: GET {prefix}/:type and NOTHING else — exactly the one mount its describe() names (#15542 moved the whole-store family off this switch; a run that still sees /diagnostics, /_drafts or POST /_migrate-stored leave with it is the old radius returning)",
@@ -3224,7 +3224,7 @@
32243224
"GET /api/v1/meta/email_template/NAME?member=zh-CN; GET /api/v1/meta/object/showcase_task?member=x",
32253225
"GET /api/v1/meta/object/showcase_task?memeber=x (typo)",
32263226
"GET /api/v1/meta/view?package=all vs GET /api/v1/meta/view",
3227-
"as a member whose FLS hides a field F: the stock showcase declares field-level security on showcase_project only (showcase_client_liaison hides budget, budget_remaining and spent), and no demo persona holds the client_liaison position, so mint the member (POST /api/v1/auth/admin/create-user, then the password change) and bind it with a sys_user_position row for client_liaison; GET /api/v1/meta/object/showcase_project and read sortability for budget; GET /api/v1/data/showcase_project?sort=budget"
3227+
"as a member whose FLS hides a field F: the stock showcase declares field-level security on showcase_project only (showcase_client_liaison hides budget, budget_remaining and spent), and no demo persona holds the showcase_client_liaison position, so mint the member (POST /api/v1/auth/admin/create-user, then the password change) and bind it with a sys_user_position row for client_liaison; GET /api/v1/meta/object/showcase_project and read sortability for budget; GET /api/v1/data/showcase_project?sort=budget"
32283228
],
32293229
"acceptance": [
32303230
{
@@ -3254,7 +3254,7 @@
32543254
{
32553255
"clause": "for the FLS-restricted caller F is ABSENT from sortability.fields (never offered — it is not listed with sortable: false) and the data sort on F is 403 — the two agree",
32563256
"oracle": "api",
3257-
"verify": "sortability.fields without F + the data answer, for a member bound to the client_liaison position. meta-read-doors-live.dogfood.test.ts asserts both halves over HTTP on a booted showcase: budget absent from sortability.fields (name listed sortable; the admin's definition offers budget, the CONTROL), and GET /api/v1/data/showcase_project?sort=budget → 403 PERMISSION_DENIED beside a served sort on name. packages/rest/src/meta-object-sortability-caller-fls.test.ts ('sortability never offers the unreadable field') holds the same rule at unit, and showcase-fls-read-mask-strip.dogfood.test.ts the data-sort 403 for a scratch permission set",
3257+
"verify": "sortability.fields without F + the data answer, for a member bound to the showcase_client_liaison position. meta-read-doors-live.dogfood.test.ts asserts both halves over HTTP on a booted showcase: budget absent from sortability.fields (name listed sortable; the admin's definition offers budget, the CONTROL), and GET /api/v1/data/showcase_project?sort=budget → 403 PERMISSION_DENIED beside a served sort on name. packages/rest/src/meta-object-sortability-caller-fls.test.ts ('sortability never offers the unreadable field') holds the same rule at unit, and showcase-fls-read-mask-strip.dogfood.test.ts the data-sort 403 for a scratch permission set",
32583258
"evidence": "both excerpts"
32593259
}
32603260
],

0 commit comments

Comments
 (0)