Skip to content

Commit 79af704

Browse files
os-trumpclaude
andauthored
docs(cli,examples): name the live owner-key helper in three stale comments, and converge the one bare 'global' key beside them (#14876)
* docs(cli,examples): name the live owner-key helper in three stale comments PR #14667 (#14422) collapsed the standalone-action owner-key ladder onto one implementation: `standaloneActionOwnerKey` in `packages/objectql/src/action-governance.ts`, exported from `@objectstack/objectql`. The private `ObjectQLPlugin.actionObjectKey` copy is gone (0 occurrences in `packages/objectql/src/plugin.ts` at `origin/main` f3ae441; positive controls on the same class hit: `standaloneActionOwnerKey` 5, `registerAction` 4, `isArtifactShippedAction` 2, `class ObjectQLPlugin` 1). Three comments outside that PR's face still named the deleted method. Each explained something that still exists, so each now names its replacement rather than dropping the sentence: - `packages/cli/src/commands/lint.ts` — the composite-key rationale behind `PREFIXED_TYPES`' actions entry. - `packages/cli/test/lint-namespace-prefix.test.ts` — the same rationale for the #5510 dedup suite. - `examples/app-showcase/test/actions.test.ts` — the object-less specimen's mirror of the engine key. Also corrected in the same stroke: `standaloneActionObjectName` is no longer "kept in lockstep with" a sibling copy — it is a delegating alias that returns `standaloneActionOwnerKey(action)`. Comments-only; no behaviour changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza * refactor(cli): converge the action-dedup object-less key onto GLOBAL_ACTION_OBJECT_KEY Folded in on a PM hand-off from #14678, whose seat deliberately left this one site: it sits inside the same docblock this PR was already editing, five lines below it, so for any other seat it is a merge conflict rather than a two-line change. Verified here rather than taken on report: - the ladder is real — `PREFIXED_TYPES`' `actions` entry built its key half with `typeof item?.objectName === 'string' && item.objectName ? item.objectName : 'global'`; - the role is the right one — that value becomes the object half of the `<objectName>:<name>` engine registration key `executeAction` looks up, which is the discriminator #14678 established (626 occurrences across 160 files share the token across at least five unrelated vocabularies; an `ownerLabel` that becomes a handler-name prefix is NOT this class); - the constant is reachable with no new dependency — `GLOBAL_ACTION_OBJECT_KEY` is exported from `@objectstack/objectql`'s entry (`src/index.ts`) and `@objectstack/cli` already declares that dependency. No behaviour moves: the constant is `'global'`, so every key this table builds is byte-identical. The #5510 dedup suite passes unchanged (24 tests over lint-namespace-prefix + the validate-build-gate-parity source pin). A changeset rides with it. The diff is no longer comments-only: it changes an expression that compiles into the published `dist/`, so the `skip-changeset` closed list no longer covers this shape. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3ad5680 commit 79af704

4 files changed

Lines changed: 56 additions & 20 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
refactor(cli): spell the action-dedup object-less key as `GLOBAL_ACTION_OBJECT_KEY` (#14669)
6+
7+
`os lint` dedups action declarations on the engine's composite registration key
8+
(`<objectName>:<name>`), and the object half of that key terminated on a bare
9+
`'global'` string literal in `lintConfig`'s `PREFIXED_TYPES` table. The engine's
10+
own writers stopped spelling the literal: PR #14667 converged
11+
`ObjectQLPlugin.actionObjectKey` onto the shared `GLOBAL_ACTION_OBJECT_KEY`
12+
constant for exactly this reason — a copy that agrees by value today is the one
13+
that parts from the writer in silence the day the constant moves, with no test
14+
in the repo able to see it. This reader now imports the constant from
15+
`@objectstack/objectql`, which `@objectstack/cli` already depends on.
16+
17+
**No behaviour moves.** `GLOBAL_ACTION_OBJECT_KEY` is `'global'`, so every key
18+
this table builds is byte-identical to the one it built before; the #5510 dedup
19+
suite (`lint-namespace-prefix.test.ts`, 15 declarations over 5 objects) passes
20+
unchanged. Only `objectName` is read, exactly as before — the `object`/`entity`
21+
aliases are still rejected upstream by `ActionSchema`'s strict shape and are
22+
deliberately not admitted here.

examples/app-showcase/test/actions.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ describe('showcase actions — the object-less (`global`) specimen', () => {
106106
const runner = new QuickJSScriptRunner();
107107

108108
it('declares no object, so it keys at `global` (framework#3913)', () => {
109-
// This mirrors ObjectQLPlugin.actionObjectKey / AppPlugin's
109+
// This mirrors `standaloneActionOwnerKey` (`@objectstack/objectql`, the
110+
// helper the ObjectQL plugin calls) / AppPlugin's
110111
// `action.object || 'global'`: neither field set → the 'global' bucket.
111112
const a = PortfolioSnapshotAction as { objectName?: string; object?: string };
112113
expect(a.objectName).toBeUndefined();

packages/cli/src/commands/lint.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import chalk from 'chalk';
55
import { bundleRequire } from 'bundle-require';
66
import { normalizeStackInput, type ConversionNotice } from '@objectstack/spec';
77
import { PROTOCOL_MAJOR } from '@objectstack/spec/kernel';
8+
import { GLOBAL_ACTION_OBJECT_KEY } from '@objectstack/objectql';
89
import { loadConfig, BUNDLE_REQUIRE_EXTERNALS } from '../utils/config.js';
910
import { computeI18nCoverage, type CoverageIssue } from '../utils/i18n-coverage.js';
1011
import { lintDataModel, runAuthoringRules } from '@objectstack/lint';
@@ -295,20 +296,26 @@ export function lintConfig(config: any, opts: LintConfigOptions = {}): LintIssue
295296
{ key: 'dashboards', label: 'Dashboard' },
296297
{ key: 'flows', label: 'Flow' },
297298
// An action's engine registration key is `<objectName>:<name>`, NOT the
298-
// bare name: `ObjectQLPlugin.actionObjectKey` (and the runtime's
299-
// `standaloneActionObjectName`, kept in lockstep with it) resolve the
300-
// object half to `objectName`, falling back to the canonical object-less
301-
// key `'global'` (#3913). So one package legitimately declaring
302-
// `log_call` on each of five objects occupies five distinct keys and
303-
// nothing shadows anything — deduping those on the bare name produced 12
304-
// fixed false positives per `objectstack lint` run on HotCRM, growing
305-
// linearly with the object count (#5510), and "just rename one" would have
306-
// broken the shared i18n keys that shape depends on (#592).
299+
// bare name: `standaloneActionOwnerKey` in `@objectstack/objectql` — the
300+
// single implementation, called directly by the ObjectQL plugin and
301+
// re-exported by the runtime, whose `standaloneActionObjectName` is now a
302+
// delegating alias for it — resolves the object half to `objectName`,
303+
// falling back to the canonical object-less key `GLOBAL_ACTION_OBJECT_KEY`
304+
// (`'global'`, #3913). So one package legitimately declaring `log_call` on
305+
// each of five objects occupies five distinct keys and nothing shadows
306+
// anything — deduping those on the bare name produced 12 fixed false
307+
// positives per `objectstack lint` run on HotCRM, growing linearly with the
308+
// object count (#5510), and "just rename one" would have broken the shared
309+
// i18n keys that shape depends on (#592).
307310
//
308-
// `'global'` rather than an inert sentinel like `''` is deliberate: it is
309-
// the literal the engine really registers under, so an action declared on
310-
// an object actually NAMED `global` and an object-less action of the same
311-
// name collide for real — and are reported, as they must be.
311+
// `GLOBAL_ACTION_OBJECT_KEY` rather than an inert sentinel like `''` is
312+
// deliberate: it is the key the engine really registers under, so an action
313+
// declared on an object actually NAMED `global` and an object-less action
314+
// of the same name collide for real — and are reported, as they must be.
315+
// It is spelled as the imported constant rather than a bare `'global'`
316+
// literal so this reader cannot part from the engine's writer in silence
317+
// the day the constant moves — the same divergence #14667 removed from the
318+
// plugin's own copy.
312319
//
313320
// Only `objectName` is read. `object`/`entity` are rejected outright by
314321
// `ActionSchema`'s strict shape with a rename prescription, so they never
@@ -317,8 +324,13 @@ export function lintConfig(config: any, opts: LintConfigOptions = {}): LintIssue
317324
{
318325
key: 'actions',
319326
label: 'Action',
320-
registryKey: (item, name) =>
321-
`${typeof item?.objectName === 'string' && item.objectName ? item.objectName : 'global'}:${name}`,
327+
registryKey: (item, name) => {
328+
const objectKey =
329+
typeof item?.objectName === 'string' && item.objectName
330+
? item.objectName
331+
: GLOBAL_ACTION_OBJECT_KEY;
332+
return `${objectKey}:${name}`;
333+
},
322334
},
323335
{ key: 'reports', label: 'Report' },
324336
{ key: 'datasets', label: 'Dataset' },

packages/cli/test/lint-namespace-prefix.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,11 @@ describe('lint — intra-package duplicate-name advisory (ADR-0048 §3.4)', () =
126126

127127
describe('lint — actions dedup on the composite engine key, not the bare name (#5510)', () => {
128128
// The engine registers an action under `<objectName>:<name>`
129-
// (`ObjectQLPlugin.actionObjectKey`; the runtime's
130-
// `standaloneActionObjectName` is kept in lockstep with it), with the
131-
// canonical object-less key `'global'` (#3913). Deduping on the bare name
132-
// asked a question the registry never asks.
129+
// (`standaloneActionOwnerKey` in `@objectstack/objectql`, which the ObjectQL
130+
// plugin calls directly and the runtime's `standaloneActionObjectName` now
131+
// delegates to), with the canonical object-less key `'global'`
132+
// (`GLOBAL_ACTION_OBJECT_KEY`, #3913). Deduping on the bare name asked a
133+
// question the registry never asks.
133134
const ACTIVITY_ACTIONS = ['log_call', 'log_meeting', 'schedule_meeting'];
134135
const CRM_OBJECTS = ['crm_lead', 'crm_contact', 'crm_account', 'crm_opportunity', 'crm_case'];
135136

0 commit comments

Comments
 (0)