Skip to content

Commit b84b240

Browse files
feat(cli,lint)!: os validate and os build refuse a field picklist that names no picklist, and lint R8 counts picklist as an options source (#21003)
Part of #20825 Clause-②: no (narrowing) This PR lands items 2 and 3 of the direction on the card (triage `5908852656`). It measures item 1's ingest half. The `os generate picklist` half of item 1 is NOT here: it needs files outside this card's claimed surface (see "Held" below). #20825 stays open for that half. ## What changes **`os validate` and `os build` refuse a field whose `picklist` names no picklist in the stack (item 2).** Ruling `5755653853` item 3 asks for this loud refusal. `FieldSchema` checks the spelling of the name and nothing else, so before this change a typo parsed. `os validate` exited 0, and `os build` wrote the artifact with the typo in it. Now both doors exit 1 with `picklist-reference-unknown`. The error names the field (`where`), the list it names (`message`), the lists the stack does declare, and the `path`. - The walk is the one the load path registers, the same reading `findViewContainerNameRefusals` makes. A one-package stack is judged on its top-level `objects` and `objectExtensions`. A `packages[]` stack is judged on each body's own. A reference resolves against every picklist the stack declares, including one that a sibling package in the same artifact owns. - **Declared dependencies outside the stack (dispatch assumption 2): reported, never refused.** `manifest.dependencies` maps package ids to version ranges. Nothing maps such an id to a file the CLI could open, so these doors cannot read another package's picklists. Take a reference that resolves nowhere in the stack. If the package declaring the field depends on a package the stack does not carry, the reference gets an `info` notice (`picklist-reference-unverified`). The notice names the field, the list and the dependencies. It rides `warnings` and the console, and it never gates, not even under `--strict`. If the package declaring the field has no outside dependency, the reference is refused. That package's own `dependencies` decides, not a sibling's. - One judge, `packages/cli/src/utils/picklist-references.ts`, is called by both doors right after the parse: `validate.ts` step 2d and `compile.ts` step 3a-bis. `validate-build-gate-parity.test.ts` classifies it as a shared non-registry gate. It is not an `@objectstack/lint` registry rule because the verdict depends on WHICH package declares the field, and the union run hands a registry rule the flattened top level, which carries no package provenance. **Lint R8 `field/select-missing-options` counts `picklist` as an options source (item 3).** A select, multiselect or radio field that names a picklist is no longer reported. A select with neither source still warns (the control), and its `fix` now names `options` and `picklist` as alternatives. Before this change the warning pointed at `options`, the key the schema refuses beside `picklist`. **Item 1, ingest: already true on `origin/main`, measured, no change.** `os build` writes `picklists` and `picklistExtensions` into the artifact as authored, and the field keeps `picklist`. Both doors parse through `ObjectStackDefinitionSchema`, which declares both collections. `metadataFileName('picklist', 'industry')` already answers `industry.picklist.ts`, from the registry's `**/*.picklist.ts`. ## Held: the `os generate picklist` row The generator row was written (`6ef78d3f29`) and taken back out (`e16359a9fa`). A new generator re-derives `os init`'s wired barrels. `create-objectstack-wiring-parity.test.ts` then requires the `npm create objectstack` blank starter to wire `src/picklists` too. Measured: with the row in place, 4 tests in that file were red, and the other 239 files of the cli unit tier were green. Landing the row needs these files, none of them in this card's claim: - `packages/create-objectstack/src/templates/blank/objectstack.config.ts`, plus a new `src/picklists/index.ts` and a create-objectstack changeset; - the generator table in `content/docs/deployment/cli.mdx` and the type list in `packages/cli/README.md`; - `skills/objectstack-platform/SKILL.md`, whose "seven generator barrels" line names the starter's barrels. That path is Tier H. The PM decides on that wider surface. The report on #20825 sets out the options. ## Before / after (real `os` processes on scratch fixtures, run-dev + tsx) | reading | before (`4957ee5ef0`) | after (`a044a7f500`) | |:--|:--|:--| | `os validate`, field `picklist: 'industy'`, stack declares `industry` | exit 0, "Validation passed" | exit 1, `picklist-reference-unknown` naming `pick_account.industry` and `'industy'` | | `os build`, same stack | exit 0, artifact written with `industy` | exit 1, no artifact | | `os validate`, same typo, `manifest.dependencies` names an outside package | exit 0, silent | exit 0, one `picklist-reference-unverified` info line | | `os validate` / `os build` / `os lint`, stack with picklist `industry` and a field naming it | exit 0 / 0 / 0 | exit 0 / 0 / 0 | | `os lint` R8 on that field | "select field ... has no options" | not reported (2 warnings left of 3) | | `os generate picklist industry` | exit 1, "Unknown type: picklist" | unchanged (held, see above) | ## Tests - `packages/cli/src/utils/picklist-references.test.ts` (unit, 9): the walk, the two verdicts, sibling-package resolution, and the declaring package's own dependencies. - `packages/cli/test/picklist-reference-doors.test.ts` (integration: it spawns the CLI, so the PR tiers run it, not the nightly one; 6): all three doors pass a correct stack, the artifact carries the list, `os validate` refuses the typo naming the field and the list (JSON and text), and `os build` refuses it and writes no artifact. - `packages/lint/src/data-model-rules.picklist-options-source.test.ts` (6): R8 for select, multiselect and radio, the neither control, the fix naming both sources, and the empty-string control. - Ablations, run on committed fixes through `scripts/ablation-replace.mjs`; each restore was proven (`git diff HEAD` empty, blob equal to HEAD): - `validate.ts` refusal disabled: 2 red (the two `os validate` pins), 4 green. - `compile.ts` refusal disabled: 1 red (the `os build` pin), 5 green. - R8's `picklist` limb removed: 3 red (select, multiselect, radio), 3 green (the controls). - At `a044a7f500`: `@objectstack/cli` unit tier 240 files / 3401 tests green; the door file 6/6; `@objectstack/cli` typecheck exit 0; `@objectstack/lint` typecheck exit 0; `@objectstack/lint` tests 118 files / 5450 tests green. The cli integration tier beyond the new file is declared to CI. - Gates: the `dispatch-gates --commands` union and `pnpm lint`. The report on #20825 gives each reading against its head. ## Acceptance notes - A picklist-bound field still raises the `liveness-planned-property` advisory ("sets `picklist` but this field property is planned"). That advisory is true until the runtime layer, #19518's sibling #19519, reads the reference. It belongs to `packages/spec`'s liveness ledger and is left alone here. - Not judged by this change: the runtime metadata write path (Studio or REST `/meta`), and a `picklistExtensions` entry whose `extend` names no picklist. - The metadata summary that `os validate` / `os build` / `os info` print has no picklist row (`collectMetadataStats`, `utils/format.ts`). It is an observation only and nothing reads it as a verdict. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1979df0 commit b84b240

9 files changed

Lines changed: 795 additions & 0 deletions
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
'@objectstack/cli': minor
3+
'@objectstack/lint': patch
4+
---
5+
6+
feat(cli)!: `objectstack validate` and `objectstack build` refuse a field whose `picklist` names no picklist the stack declares, and lint R8 counts `picklist` as an options source (#20825)
7+
8+
Clause-②: no (narrowing — `objectstack validate` / `objectstack build` newly refuse a field `picklist` that names no picklist the stack declares; the R8 change removes a false-positive warning and widens no accept set of its own)
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable changes spelling or type: `packages/spec` is untouched, and `Field.picklist` stays the snake_case name it was. What changes is that two authoring commands now refuse one authored shape, a field whose `picklist` names no picklist in the stack. `objectstack migrate meta` could not rewrite that shape even in principle, because which list the author meant is not in the metadata. Nothing here judges a stored row. -->
11+
12+
**BREAKING** — an accept-set narrowing on two authoring commands, shipped as
13+
`minor` under the launch-window convention. A stack with a select field whose
14+
`picklist` names no picklist the stack declares — `picklist: 'industy'` beside a
15+
`picklists: [{ name: 'industry', … }]` — used to pass `objectstack validate` and
16+
`objectstack build` (which wrote the artifact). Both now exit 1 and name the field
17+
and the list it names (`picklist-reference-unknown`).
18+
**One-line fix:** correct `picklist` to a list the stack declares, or declare the
19+
list it names (`picklists: [{ name, label, options }]`, or a `*.picklist.ts` file the
20+
stack imports).
21+
22+
**Which references are judged.** The ones the load path registers: the top-level
23+
`objects` and `objectExtensions` of a one-package stack, or each `packages[]`
24+
entry's own. A reference resolves against every picklist the stack declares,
25+
including one a sibling package in the same artifact owns.
26+
27+
**A list from a package outside the stack is reported, not refused.** When the
28+
package declaring the field lists a `manifest.dependencies` entry the stack does not
29+
carry, the list may live there, and these commands cannot read it. That reference is
30+
an `info` notice (`picklist-reference-unverified`) in `warnings` and on the console,
31+
naming the field, the list and the dependencies — never a failure, not even under
32+
`--strict`.
33+
34+
**Lint R8 (`field/select-missing-options`)** no longer reports a select, multiselect
35+
or radio field that names a `picklist`: the picklist is its options source. The
36+
warning it used to give pointed at `options`, which a field naming a `picklist`
37+
cannot add — the field schema refuses the two together. A select with neither still
38+
warns, and its fix now names both sources as alternatives.

‎packages/cli/src/commands/compile.ts‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ import type { PermissionSetNameCollisionDiagnostic } from '@objectstack/plugin-s
6464
// [#20393] The boot registrar's divergent view-container `name` refusal — the
6565
// walk `os validate` step 2c runs, over `@objectstack/objectql`'s one judge.
6666
import { findViewContainerNameRefusals } from '../utils/view-container-names.js';
67+
// A field `picklist` that names no picklist the stack declares — the walk
68+
// `os validate` step 2d runs, over the same judge.
69+
import { judgePicklistReferences, printPicklistReferenceNotices } from '../utils/picklist-references.js';
6770

6871
export default class Compile extends Command {
6972
static override description = 'Compile ObjectStack configuration to JSON artifact';
@@ -178,6 +181,13 @@ export default class Compile extends Command {
178181
// computes the identical record so the residue pin holds, and it reports
179182
// without ever refusing. Appended LAST, in `os validate`'s order.
180183
let jsxGateNotices: ReturnType<typeof resolveJsxGateManifest>['notices'] = [];
184+
// The `info` records of a field `picklist` reference that resolves nowhere
185+
// in the stack while the declaring package depends on packages outside it
186+
// (step 3a-bis). A member of `warningsSoFar()`, ⛔ not a payload key, for
187+
// the reasons the three lists above record: it reports without refusing,
188+
// and `os validate` computes the identical records. Appended LAST, in
189+
// `os validate`'s order.
190+
let picklistReferenceNotices: ReturnType<typeof judgePicklistReferences>['notices'] = [];
181191
const warningsSoFar = () => [
182192
...ruleAdvisories,
183193
...docWarnings,
@@ -186,6 +196,7 @@ export default class Compile extends Command {
186196
...navGroupWarnings,
187197
...permissionSetCollisionWarnings,
188198
...jsxGateNotices,
199+
...picklistReferenceNotices,
189200
];
190201
// [#18780] ONE rendering of the author-time advisory block, from the
191202
// COMPLETE list — hoisted here for the same reason the lists above are.
@@ -448,6 +459,36 @@ export default class Compile extends Command {
448459
this.exit(1);
449460
}
450461

462+
// 3a-bis. A field `picklist` that names no picklist the stack declares is
463+
// REFUSED — the SAME call `os validate` makes at its step 2d, so the
464+
// two doors cannot disagree about which references resolve. Without
465+
// it this door wrote the artifact carrying the misspelt reference, and
466+
// the command that ships shipped a choice with nothing to choose.
467+
//
468+
// A reference the stack cannot resolve while the declaring package
469+
// depends on packages outside the stack is an `info` notice instead
470+
// (see `utils/picklist-references.ts`): the list may live there, and
471+
// this command cannot read it. Printed here, carried in
472+
// `warningsSoFar()`, never gating.
473+
//
474+
// Right after the parse and ahead of every artifact write, like 3a.
475+
// The `--json` face is 3a's envelope (`errors`); the text face is
476+
// `os validate`'s.
477+
const picklistJudgement = judgePicklistReferences(result.data as Record<string, unknown>);
478+
picklistReferenceNotices = [...picklistJudgement.notices];
479+
if (!flags.json) printPicklistReferenceNotices(picklistReferenceNotices);
480+
if (picklistJudgement.refusals.length > 0) {
481+
if (flags.json) {
482+
await emitJson({ success: false, errors: picklistJudgement.refusals, warnings: warningsSoFar(), conversions: conversionNotices }, 0, { compact: true });
483+
this.exit(1);
484+
}
485+
const n = picklistJudgement.refusals.length;
486+
console.log('');
487+
printError(`A field names a picklist this stack does not declare (${n} reference${n > 1 ? 's' : ''})`);
488+
printAuthoringRuleErrors(picklistJudgement.refusals, { remedy: JSON_FULL_LIST_REMEDY });
489+
this.exit(1);
490+
}
491+
451492
// 3b. The author-time rule registry (#4409) — one table, three commands.
452493
// `os build` was the WEAKEST of the three authoring gates before it:
453494
// it published stacks `os validate` or `os lint` refuse, because the

‎packages/cli/src/commands/validate.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ import type { PermissionSetNameCollisionDiagnostic } from '@objectstack/plugin-s
6464
// over the parsed stack the way the load path registers it. The verdict is
6565
// `@objectstack/objectql`'s; see the module header.
6666
import { findViewContainerNameRefusals } from '../utils/view-container-names.js';
67+
// A field `picklist` that names no picklist the stack declares — refused, or
68+
// reported when the declaring package depends on packages outside the stack.
69+
// Walked the way the load path registers, like the refusal above.
70+
import { judgePicklistReferences, printPicklistReferenceNotices } from '../utils/picklist-references.js';
6771

6872
export default class Validate extends Command {
6973
static override description =
@@ -166,6 +170,13 @@ export default class Validate extends Command {
166170
// of a project without a manifest is unchanged on both faces. `os compile`
167171
// computes the identical record, so the residue pin keeps holding.
168172
let jsxGateNotices: ReturnType<typeof resolveJsxGateManifest>['notices'] = [];
173+
// The `info` records of a field `picklist` reference that resolves nowhere
174+
// in the stack while the declaring package depends on packages outside it
175+
// (step 2d). Same class as the notice above — reports, never refuses, and
176+
// is NOT in the `warnings` list `--strict` reads: the reference may be
177+
// right, and `--strict` failing on it would refuse a correct stack. `os
178+
// compile` computes the identical records, so the residue pin keeps holding.
179+
let picklistReferenceNotices: ReturnType<typeof judgePicklistReferences>['notices'] = [];
169180
const warningsSoFar = () => [
170181
...ruleAdvisories,
171182
...docWarnings,
@@ -184,6 +195,8 @@ export default class Validate extends Command {
184195
...permissionSetCollisionWarnings,
185196
// [#20113] APPENDED for the same reason, one member later again.
186197
...jsxGateNotices,
198+
// APPENDED for the same reason, one member later again.
199+
...picklistReferenceNotices,
187200
];
188201
// [commit 79cf692b0] The ADR-0087 D2 conversion notices, hoisted for the SAME reason
189202
// and under the SAME ruling as the five lists above — one field over. The
@@ -407,6 +420,46 @@ export default class Validate extends Command {
407420
this.exit(1);
408421
}
409422

423+
// 2d. A field `picklist` that names no picklist the stack declares is
424+
// REFUSED, naming the field and the list. `FieldSchema` judges the
425+
// name's spelling only, so a misspelt reference parsed, passed this
426+
// door at exit 0 and reached the runtime as a choice with nothing to
427+
// choose — the silence a NAMED list exists to remove.
428+
//
429+
// The walk is the load path's (see `utils/picklist-references.ts`):
430+
// each `packages[]` body's fields, or the top level's when there is
431+
// no `packages[]`, resolved against every picklist the stack declares.
432+
// A reference that resolves nowhere is refused only when the
433+
// declaring package depends on no package outside the stack; when it
434+
// does, the list may live there, and this command cannot read it — so
435+
// that case is an `info` notice, printed here and carried in
436+
// `warningsSoFar()`, never gating.
437+
//
438+
// Right after the parse, ahead of the rule table, for the reason
439+
// step 2c gives. `os build` runs the same call at its step 3a-bis.
440+
const picklistJudgement = judgePicklistReferences(result.data as Record<string, unknown>);
441+
picklistReferenceNotices = [...picklistJudgement.notices];
442+
if (!flags.json) printPicklistReferenceNotices(picklistReferenceNotices);
443+
if (picklistJudgement.refusals.length > 0) {
444+
if (flags.json) {
445+
await emitJson({
446+
valid: false,
447+
errors: picklistJudgement.refusals,
448+
// Every exit carries the lists the run has computed so far — the
449+
// pre-parse ones and the notices above.
450+
warnings: warningsSoFar(),
451+
conversions: conversionNotices,
452+
duration: timer.elapsed(),
453+
});
454+
this.exit(1);
455+
}
456+
const n = picklistJudgement.refusals.length;
457+
console.log('');
458+
printError(`A field names a picklist this stack does not declare (${n} reference${n > 1 ? 's' : ''})`);
459+
printAuthoringRuleErrors(picklistJudgement.refusals, { remedy: JSON_FULL_LIST_REMEDY });
460+
this.exit(1);
461+
}
462+
410463
// 3. The author-time rule registry (#4409). Every rule the three authoring
411464
// commands share — expressions, view shape, widget/action/filter/name
412465
// references, SDUI styling, page sources, security posture, the CLI's
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* `judgePicklistReferences` — the walk `os validate` (step 2d) and `os build`
5+
* (step 3a-bis) run over a field's `picklist` reference.
6+
*
7+
* Pinned here: which references are judged (the load path's reading — the top
8+
* level when there is no `packages[]`, each body's own otherwise), what they
9+
* resolve against (every picklist the stack declares), and which of the two
10+
* verdicts an unresolved one gets (refused when the declaring package depends
11+
* on nothing outside the stack; an `info` notice when it does). Asserted by
12+
* rule id, severity, the field named in `where`, the list named in `message`
13+
* and the `path` — never by the sentence around them.
14+
*/
15+
16+
import { describe, expect, it } from 'vitest';
17+
import {
18+
judgePicklistReferences,
19+
PICKLIST_REFERENCE_UNKNOWN,
20+
PICKLIST_REFERENCE_UNVERIFIED,
21+
} from './picklist-references.js';
22+
23+
const manifest = (id: string, extra: Record<string, unknown> = {}) => ({
24+
id, name: id, version: '1.0.0', type: 'app', ...extra,
25+
});
26+
27+
const industry = {
28+
name: 'industry',
29+
label: 'Industry',
30+
options: [{ label: 'Technology', value: 'technology' }],
31+
};
32+
33+
const account = (picklist: string) => ({
34+
name: 'pk_account',
35+
label: 'Account',
36+
fields: {
37+
name: { type: 'text', label: 'Name' },
38+
industry: { type: 'select', label: 'Industry', picklist },
39+
},
40+
});
41+
42+
describe('judgePicklistReferences — a one-package stack', () => {
43+
it('CONTROL: a field naming a picklist the stack declares is neither refused nor reported', () => {
44+
expect(judgePicklistReferences({
45+
manifest: manifest('com.example.pk'),
46+
picklists: [industry],
47+
objects: [account('industry')],
48+
})).toEqual({ refusals: [], notices: [] });
49+
});
50+
51+
it('CONTROL: a select with inline options and no `picklist` is not this judge\'s business', () => {
52+
expect(judgePicklistReferences({
53+
manifest: manifest('com.example.pk'),
54+
objects: [{
55+
name: 'pk_account',
56+
fields: { tier: { type: 'select', options: [{ label: 'Gold', value: 'gold' }] } },
57+
}],
58+
})).toEqual({ refusals: [], notices: [] });
59+
});
60+
61+
it('refuses a `picklist` that names no picklist in the stack, naming the field and the list', () => {
62+
const { refusals, notices } = judgePicklistReferences({
63+
manifest: manifest('com.example.pk'),
64+
picklists: [industry],
65+
objects: [account('industy')],
66+
});
67+
expect(notices).toEqual([]);
68+
expect(refusals).toHaveLength(1);
69+
expect(refusals[0]).toMatchObject({
70+
severity: 'error',
71+
rule: PICKLIST_REFERENCE_UNKNOWN,
72+
where: 'field "pk_account.industry"',
73+
path: 'objects[0].fields.industry.picklist',
74+
});
75+
expect(refusals[0].message).toContain("'industy'");
76+
// The lists it could have meant are named, so the typo is visible.
77+
expect(refusals[0].message).toContain("'industry'");
78+
});
79+
80+
it('refuses a dangling reference in a stack that declares no picklist at all', () => {
81+
const { refusals } = judgePicklistReferences({
82+
manifest: manifest('com.example.pk'),
83+
objects: [account('industry')],
84+
});
85+
expect(refusals.map((r) => r.rule)).toEqual([PICKLIST_REFERENCE_UNKNOWN]);
86+
});
87+
88+
it('judges the fields an `objectExtensions` entry merges into its target too', () => {
89+
const { refusals } = judgePicklistReferences({
90+
manifest: manifest('com.example.pk'),
91+
picklists: [industry],
92+
objectExtensions: [{ extend: 'crm_account', fields: { segment: { type: 'select', picklist: 'segment' } } }],
93+
});
94+
expect(refusals).toHaveLength(1);
95+
expect(refusals[0]).toMatchObject({
96+
where: 'field "crm_account.segment"',
97+
path: 'objectExtensions[0].fields.segment.picklist',
98+
});
99+
});
100+
101+
it('REPORTS, never refuses, when the stack depends on a package it does not carry', () => {
102+
const { refusals, notices } = judgePicklistReferences({
103+
manifest: manifest('com.example.pk', { dependencies: { 'com.acme.crm': '^1.0.0' } }),
104+
objects: [account('industry')],
105+
});
106+
expect(refusals).toEqual([]);
107+
expect(notices).toHaveLength(1);
108+
expect(notices[0]).toMatchObject({
109+
severity: 'info',
110+
rule: PICKLIST_REFERENCE_UNVERIFIED,
111+
path: 'objects[0].fields.industry.picklist',
112+
});
113+
// The notice is printed without its `where`, so it names the field, the
114+
// list and the dependency it could not read.
115+
expect(notices[0].message).toContain('pk_account.industry');
116+
expect(notices[0].message).toContain("'industry'");
117+
expect(notices[0].message).toContain("'com.acme.crm'");
118+
});
119+
});
120+
121+
describe('judgePicklistReferences — a `packages[]` stack', () => {
122+
it('resolves a picklist a SIBLING package in the same artifact declares', () => {
123+
expect(judgePicklistReferences({
124+
packages: [
125+
{ manifest: { ...manifest('com.example.core'), picklists: [industry] } },
126+
{
127+
manifest: {
128+
...manifest('com.example.orders', { dependencies: { 'com.example.core': '^1.0.0' } }),
129+
objects: [account('industry')],
130+
},
131+
},
132+
],
133+
})).toEqual({ refusals: [], notices: [] });
134+
});
135+
136+
it('refuses when every declared dependency is inside the artifact — none of them declares the list', () => {
137+
const { refusals, notices } = judgePicklistReferences({
138+
packages: [
139+
{ manifest: { ...manifest('com.example.core'), picklists: [industry] } },
140+
{
141+
manifest: {
142+
...manifest('com.example.orders', { dependencies: { 'com.example.core': '^1.0.0' } }),
143+
objects: [account('region')],
144+
},
145+
},
146+
],
147+
});
148+
expect(notices).toEqual([]);
149+
expect(refusals).toHaveLength(1);
150+
expect(refusals[0]).toMatchObject({
151+
rule: PICKLIST_REFERENCE_UNKNOWN,
152+
path: 'packages[1].manifest.objects[0].fields.industry.picklist',
153+
});
154+
});
155+
156+
it('reads the declaring package\'s OWN dependencies: an outside dependency of a sibling does not soften it', () => {
157+
const { refusals, notices } = judgePicklistReferences({
158+
packages: [
159+
{ manifest: { ...manifest('com.example.core', { dependencies: { 'com.acme.crm': '^1.0.0' } }) } },
160+
{ manifest: { ...manifest('com.example.orders'), objects: [account('region')] } },
161+
{
162+
manifest: {
163+
...manifest('com.example.billing', { dependencies: { 'com.acme.crm': '^1.0.0' } }),
164+
objects: [account('segment')],
165+
},
166+
},
167+
],
168+
});
169+
expect(refusals.map((r) => r.path)).toEqual(['packages[1].manifest.objects[0].fields.industry.picklist']);
170+
expect(notices.map((n) => n.path)).toEqual(['packages[2].manifest.objects[0].fields.industry.picklist']);
171+
});
172+
});

0 commit comments

Comments
 (0)