Skip to content

Commit 850eab2

Browse files
fix(cli)!: os build refuses a field the __c extension-field reservation refuses, as os validate does (#28727) (#28757)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X6mHGkBS1EHQfRT9qA8xjAT)_ Fixes #28727 Clause-②: no (narrowing) `os build` used to emit `dist/objectstack.json` for a package source whose field breaks the `__c` reservation; it now refuses that build. The spelling it newly refuses has a measured count of 0 on this base (below). No `packages/spec/**` or `packages/metadata-protocol/**` file is touched; no schema, type, export or error code moves. Metadata-protocol: none What a metadata author can write is unchanged at every metadata door: the save, restore and publish doors, `os validate` and the marketplace install already refuse the same fields with the same judge since #28564 (PR #28724). This only moves the same verdict to the build, as #28643 (PR #28681) did for the override write gate, which declared the same pair. Skills-docs: none — no file under `skills/**` teaches a field spelled with the `__c` suffix or what `os build` refuses for one (`git grep` of the suffix over `skills/**` on this base: 0 hits); the `objectstack-data` skill's "Object Extension Model" section names no field spelling, so it stays true. ## 维护者速读 - 改了什么:`os build` 现在和 `os validate` 一样检查 `__c` 扩展字段后缀(ADR-0146 D10)。普通包里出现 `x__c` 字段、或 `.custom` 配对包的扩展字段不带 `__c`,构建直接失败(退出码 1),`dist/` 里什么都不写。 - 为什么改:#28564 让保存/发布/安装/`os validate` 都拒这两种写法,唯独 `os build` 还会产出一个到处都会被拒的产物。现在发货的那道门当场拒,和 #28681 对覆盖声明门做的事一样。 - 风险与回滚:同一个判官、同一条遍历(`findExtensionFieldSuffixRefusals`),没有第二份拼写;新拒的拼法在本基点上实测 0 个字段名。回滚即 revert,无数据迁移。 - 先看:`packages/cli/src/commands/compile.ts` 的 3b-iii-bis 一段(紧跟覆盖声明门,在任何产物写入之前);`packages/cli/test/validate-build-gate-parity.test.ts` 的台账行搬家。 - 两分钟验证:`pnpm --filter @objectstack/cli exec vitest run test/build-extension-field-suffix.test.ts`,预期 `Tests 4 passed (4)`。 - 需要你做的:无,席位落地(合同审查后)。 ## What changes - **`packages/cli/src/commands/compile.ts`**: step 3b-iii-bis calls `findExtensionFieldSuffixRefusals` (`utils/extension-field-suffix.ts`, over `@objectstack/metadata-protocol`'s `judgeExtensionFieldSuffix`), the call `os validate` makes at its step 3a-iv. It sits right after #28681's override gate (3b-iii) and ahead of every artifact write, so a refused build writes nothing to `dist/`. Same package-id reading as validate: each `packages[]` entry under its own id, else the stack under `manifest.id`; a stack naming no id is not judged. Faces: `--json` answers this command's failure envelope (`success: false`, `errors` = the rows `os validate --json` carries), the text face prints `os validate`'s heading and bullet rows; exit 1 in both. No second walk, and neither the suffix nor the pair rule is spelled here. - **`packages/cli/test/validate-build-gate-parity.test.ts`**: the `findExtensionFieldSuffixRefusals` row leaves `VALIDATE_ONLY_GATES` for `SHARED_NON_REGISTRY_GATES`, with its reason; the ledger is `{}` again and its docblock now names the three rows it has held and closed. - **`.changeset/28727-build-extension-field-suffix.md`**: `@objectstack/cli` patch, both declaration lines, BREAKING banner and the ADR-0087 `not-required (no-migration-prescription)` disposition. ## Tests - New `packages/cli/test/build-extension-field-suffix.test.ts` (4, unit tier; `os build` and `os validate` run in-process, the recipe of `build-override-declarations.test.ts`): - case (a), a plain package's own object field `x__c`, and case (b), the `com.acme.crm.custom` pair extending `crm_account` with a plain `region`: `os build --json` exits as `os validate --json` does (1), `errors` deep-equals validate's rows for the same config (anti-vacuity: exactly one `VALIDATION_ERROR` row with the expected `rule` and path), and `dist/` does not exist afterwards; - the text face of case (b) exits 1 and prints validate's refusal block verbatim, with no "Build complete"; - CONTROL: the pair extending with `region__c` validates clean and builds, the artifact carrying `region__c`. - **Red on base**: `compile.ts` restored to `db357f6d` (tests kept): 4 failed / 32 passed across the new file and the parity file (the three build pins with `expected +0 to be 1`, and the parity pin "compile.ts no longer calls findExtensionFieldSuffixRefusals"); the control stays green. Restore proved by `git diff HEAD` empty and the blob equal to HEAD's. - **Ablation** (`scripts/ablation-replace.mjs`, anchor hit once, blob `8f239dd8` to `7cc70658`): the compile call replaced by an empty row list: 4 failed / 32 passed, the same four pins. Restore: blob equal to HEAD (`8f239dd8`), `git diff HEAD` empty. The command runs from `src` (the test imports `../src/commands/compile`), so no `dist/` sits on the path. ## Acceptance notes - Serial constraint with #28728 (#28565), which adds a row to the same parity file and a step to `validate.ts`: whichever lands second merges `origin/main` once and keeps both rows (order ruling 5). This branch has not merged it. - The text face prints `os validate`'s heading for this gate verbatim, as the override gate's build face prints validate's; the text pin asserts the two blocks equal. ## Verification Run on `4eb1b66430` (base `db357f6d`; `origin/main` has since moved by 8 commits, none touching this diff's files; not merged, see the deviation below). Each test command under `os-verify-lock` (`VERDICT command-exit 0`). - Build: `pnpm --filter '@objectstack/cli...' build` exit 0; the dogfood closure `pnpm --filter '@objectstack/dogfood^...' build` exit 0. - `@objectstack/cli` unit tier (`vitest run --project unit`): Test Files 366 passed (366); Tests 5180 passed (5180). The new file is unit tier (in-process, no spawn); the integration tier is declared to CI (no integration-tier file or spawn entry is touched). - `pnpm --filter @objectstack/cli typecheck`: exit 0 (`check:test-typecheck: OK`). - `eslint --no-inline-config packages/cli`: exit 0. - Dogfood battery (`pnpm --filter @objectstack/dogfood test`, owed by the narrowing arm): Test Files 374 passed, 3 skipped (377); Tests 3034 passed, 28 skipped (3062). - Derived gates (`dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 61 families, each exit captured before any pipe), reconciled: `✓ dispatch-gates --ran: 61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and none of them is 3)`. `check:i18n-coverage` first read 3 (`@objectstack/connector-slack` had no build output in this worktree) and read 0 after building it: `OK (14 config(s), 527 baselined untranslated string(s), none new)`. - Count (the narrowing's migration note): `git grep -nE "[a-z0-9]__c\b"` over `examples/**`, `packages/*/src/**`, `skills/**`, tests excluded, at `origin/main` `db357f6d`: 8 hits, all doc comments (`protocol-extension-field-suffix.ts`, `field-customization.ts`, `object.zod.ts`, `validation.zod.ts`); 0 field names. - Deviation: `origin/main` was not merged before opening. The 8 incoming commits touch none of the four files here; in `packages/cli` they change only `src/utils/override-declarations.test.ts`. CI's merge-group run judges the joint tree; the serial merge with #28728 stays owed by whichever lands second. --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5oPz5wWFMzWjTGyVUb)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cd0ec76 commit 850eab2

4 files changed

Lines changed: 244 additions & 14 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os build` runs the `__c` extension-field reservation `os validate` runs (ADR-0146 D10, #28727)
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
**BREAKING** accept-set narrowing at `os build` (`@objectstack/cli`): a package source whose field breaks the `__c` reservation now fails the build instead of emitting an artifact.
11+
12+
`os build` now calls `findExtensionFieldSuffixRefusals` beside the override write gate, the same walk over the save and install doors' own judge (`judgeExtensionFieldSuffix`) that `os validate`, the save, restore and publish doors and the marketplace install ask. A field spelled `__c` outside a base's paired `.custom` package, and an `objectExtensions[].fields` key of a `.custom` pair without the suffix, are refused at build with the rows `os validate` prints (`VALIDATION_ERROR`, exit 1), and nothing is written to `dist/`. Before, the build emitted that artifact and the marketplace install or a save or publish door refused it later with the same judge; what any of those doors accepts is unchanged. The fix is the judge's own sentence: rename a plain package's `x__c` field, or suffix the `.custom` extension field (`region` to `region__c`).
13+
14+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: `os build` now runs the `__c` extension-field reservation `os validate`, the save, restore and publish doors and the marketplace install already run, with the same judge (`judgeExtensionFieldSuffix`) and the same rows. An artifact it now refuses was already refused wherever it landed, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone; the remedy is the judge's own sentence (rename the field, or add the suffix). -->

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ import { judgePicklistReferences, picklistRefusalHeading, printPicklistReference
7979
// [ADR-0146 D2, #28643] The override write gate — the walk `os validate` step
8080
// 3a-iii runs, over the publish door's own judge (`judgeOverrideWrite`).
8181
import { findOverrideDeclarationRefusals } from '../utils/override-declarations.js';
82+
// [ADR-0146 D10, #28727] The `__c` extension-field reservation — the walk
83+
// `os validate` step 3a-iv runs, over the save and install doors' own judge
84+
// (`judgeExtensionFieldSuffix`).
85+
import { findExtensionFieldSuffixRefusals } from '../utils/extension-field-suffix.js';
8286

8387
export default class Compile extends Command {
8488
static override description = 'Compile ObjectStack configuration to JSON artifact';
@@ -802,6 +806,37 @@ export default class Compile extends Command {
802806
this.exit(1);
803807
}
804808

809+
// 3b-iii-bis. [ADR-0146 D10, #28727] The `__c` extension-field
810+
// reservation, at build time — the SAME call `os validate` makes at
811+
// its step 3a-iv, over the walk `utils/extension-field-suffix.ts`
812+
// takes and the save and install doors' own judge
813+
// (`judgeExtensionFieldSuffix`): a field spelled `__c` only in a
814+
// base's paired `.custom` package, and every
815+
// `objectExtensions[].fields` key of a `.custom` pair suffixed. Before
816+
// this the build emitted such an artifact and the marketplace install
817+
// or a save or publish door refused it later with the same judge; now
818+
// the door that ships refuses it and writes no artifact.
819+
//
820+
// Beside the override gate above, ahead of every artifact write, with
821+
// the same two faces: this command's failure envelope under `--json`
822+
// (`errors`, the rows `os validate --json` carries), `os validate`'s
823+
// text otherwise.
824+
const suffixRefusals = findExtensionFieldSuffixRefusals(result.data as Record<string, unknown>);
825+
if (suffixRefusals.length > 0) {
826+
if (flags.json) {
827+
await emitJson({ success: false, errors: suffixRefusals, warnings: warningsSoFar(), conversions: conversionNotices }, 0, { compact: true });
828+
this.exit(1);
829+
}
830+
const n = suffixRefusals.length;
831+
console.log('');
832+
printError(`${n} field${n > 1 ? 's break' : ' breaks'} the \`__c\` extension-field reservation (ADR-0146 D10)`);
833+
printBulletList(
834+
suffixRefusals.map((r) => `${r.path} [${r.code}] ${r.message}`),
835+
{ noun: 'extension-field refusal(s)', remedy: JSON_FULL_LIST_REMEDY },
836+
);
837+
this.exit(1);
838+
}
839+
805840
// 3b-quater. ADR-0141 D2, author side — every `manifest.dependencies`
806841
// range of the packages one install act would deliver, judged by the
807842
// install's own predicate (`utils/dependency-range-check.ts`).
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [ADR-0146 D10, #28727] `os build` asks the `__c` extension-field
5+
* reservation that `os validate`, the save, restore and publish doors and the
6+
* marketplace install ask, and refuses the build with the rows `os validate`
7+
* prints: same rows, same exit code, and NOTHING written to `dist/`.
8+
*
9+
* ## The defect, measured before the fix
10+
*
11+
* On a plain package declaring a field `x__c` on its own object (case (a)),
12+
* and on a base's paired `.custom` extending a base object with a field
13+
* `region` that lacks the suffix (case (b)), `os build --json` exited 0 and
14+
* wrote `dist/objectstack.json`, while `os validate --json` exited 1 with one
15+
* `VALIDATION_ERROR` row each. The marketplace install and the save and
16+
* publish doors refused that artifact later with the same judge.
17+
*
18+
* ## One judge, one walk
19+
*
20+
* `compile.ts` makes the call `validate.ts` makes
21+
* (`findExtensionFieldSuffixRefusals`, over `judgeExtensionFieldSuffix`), so
22+
* the pins below assert EQUALITY with what `os validate` answers for the same
23+
* config, run in the same process, rather than a copy of the judge's words.
24+
* Which packages the walk judges is pinned by its own file
25+
* (`src/utils/extension-field-suffix.test.ts`). Both commands run in-process
26+
* (`Command.run`), not as spawned CLIs.
27+
*/
28+
29+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
30+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
31+
import { tmpdir } from 'node:os';
32+
import { join, resolve } from 'node:path';
33+
import { fileURLToPath } from 'node:url';
34+
import Compile from '../src/commands/compile';
35+
import Validate from '../src/commands/validate';
36+
import { defineStackSource, linkSpec } from './helpers/define-stack-fixture.js';
37+
38+
const CLI_ROOT = resolve(fileURLToPath(import.meta.url), '..', '..');
39+
const BASE = 'com.acme.crm';
40+
41+
interface Run {
42+
/** The exit code `this.exit(n)` threw, or 0 when the command returned. */
43+
code: number;
44+
stdout: string;
45+
}
46+
47+
/** Runs one command in-process; stdout captured ANSI-free, the write callback honoured. */
48+
async function run(command: typeof Compile | typeof Validate, argv: string[]): Promise<Run> {
49+
const chunks: string[] = [];
50+
const spy = vi.spyOn(process.stdout, 'write').mockImplementation(((chunk: unknown, ...rest: unknown[]) => {
51+
chunks.push(String(chunk));
52+
const done = rest.find((a) => typeof a === 'function') as ((e?: Error | null) => void) | undefined;
53+
done?.(null);
54+
return true;
55+
}) as never);
56+
const exitCodeBefore = process.exitCode;
57+
let code = 0;
58+
try {
59+
await command.run(argv, { root: CLI_ROOT });
60+
} catch (error) {
61+
const exit = (error as { oclif?: { exit?: unknown } }).oclif?.exit;
62+
if (typeof exit !== 'number') throw error;
63+
code = exit;
64+
} finally {
65+
spy.mockRestore();
66+
process.exitCode = exitCodeBefore;
67+
}
68+
// eslint-disable-next-line no-control-regex
69+
return { code, stdout: chunks.join('').replace(/\u001b\[[0-9;]*m/g, '') };
70+
}
71+
72+
const json = (r: Run, label: string): Record<string, any> => {
73+
try {
74+
return JSON.parse(r.stdout) as Record<string, any>;
75+
} catch {
76+
throw new Error(`${label}: stdout was not one JSON document (exit ${r.code})\n${r.stdout}`);
77+
}
78+
};
79+
80+
/** Case (a): a plain package declaring `x__c` on its own object. */
81+
const plainOwnField = (): Record<string, unknown> => ({
82+
manifest: { id: 'com.acme.partner', name: 'partner', version: '1.0.0', type: 'app', namespace: 'partner' },
83+
objects: [
84+
{
85+
name: 'partner_ticket',
86+
label: 'Ticket',
87+
sharingModel: 'private',
88+
fields: { title: { type: 'text', label: 'Title' }, x__c: { type: 'text', label: 'X' } },
89+
},
90+
],
91+
});
92+
93+
/** The base's paired `.custom` extending the base's `crm_account` with one field named `field`. */
94+
const pairExtension = (field: string): Record<string, unknown> => ({
95+
manifest: {
96+
id: `${BASE}.custom`, name: 'ext', version: '1.0.0', type: 'app', namespace: 'crm_custom',
97+
dependencies: { [BASE]: '^1.0.0' },
98+
},
99+
objectExtensions: [{ extend: 'crm_account', fields: { [field]: { type: 'text', label: 'Region' } } }],
100+
});
101+
102+
let root = '';
103+
const fixtures: Record<string, { config: string; dist: string; artifact: string }> = {};
104+
105+
beforeAll(() => {
106+
root = mkdtempSync(join(tmpdir(), 'os-build-extension-field-suffix-'));
107+
const make = (label: string, s: Record<string, unknown>) => {
108+
const dir = join(root, label);
109+
mkdirSync(dir, { recursive: true });
110+
// `strict: false`: the content is the door's to judge, not the producer's.
111+
writeFileSync(join(dir, 'objectstack.config.ts'), defineStackSource(s, { strict: false }));
112+
linkSpec(dir);
113+
fixtures[label] = {
114+
config: join(dir, 'objectstack.config.ts'),
115+
dist: join(dir, 'dist'),
116+
artifact: join(dir, 'dist', 'objectstack.json'),
117+
};
118+
};
119+
make('plainOwnField', plainOwnField());
120+
make('pairPlain', pairExtension('region'));
121+
make('pairSuffixed', pairExtension('region__c'));
122+
});
123+
124+
afterAll(() => {
125+
if (root) rmSync(root, { recursive: true, force: true });
126+
});
127+
128+
const build = (label: string, ...flags: string[]) =>
129+
run(Compile, [fixtures[label].config, '--output', fixtures[label].artifact, ...flags]);
130+
const validate = (label: string, ...flags: string[]) => run(Validate, [fixtures[label].config, ...flags]);
131+
132+
describe('#28727 — os build refuses what os validate refuses under the `__c` reservation, and writes nothing', () => {
133+
it.each([
134+
['plainOwnField', 'reserved_outside_pair', 'objects'],
135+
['pairPlain', 'unsuffixed_in_pair', 'objectExtensions'],
136+
])('THE PIN (%s): --json exits as validate does, with validate\'s rows, and dist/ stays empty', async (label, rule, at) => {
137+
const validated = await validate(label, '--json');
138+
expect(validated.code, validated.stdout).toBe(1);
139+
const expected = json(validated, `validate ${label}`).errors as Array<Record<string, unknown>>;
140+
// Anti-vacuity: the judge refuses this config, under the case it names.
141+
expect(expected.map((e) => [e.code, e.rule])).toEqual([['VALIDATION_ERROR', rule]]);
142+
expect(String(expected[0]!.path).startsWith(at)).toBe(true);
143+
144+
const built = await build(label, '--json');
145+
const payload = json(built, `build ${label}`);
146+
expect(built.code).toBe(validated.code);
147+
expect(payload.success).toBe(false);
148+
expect(payload.errors).toEqual(expected);
149+
expect(existsSync(fixtures[label].dist), 'os build wrote into dist/ for an artifact the doors refuse').toBe(false);
150+
}, 60_000);
151+
152+
it('the text face exits 1 and prints the refusal block os validate prints', async () => {
153+
const validated = await validate('pairPlain');
154+
expect(validated.code).toBe(1);
155+
const block = validated.stdout.slice(validated.stdout.indexOf('✗ 1 field breaks the')).trimEnd();
156+
expect(block).toContain('objectExtensions[0].fields.region [VALIDATION_ERROR]');
157+
158+
const built = await build('pairPlain');
159+
expect(built.code).toBe(1);
160+
expect(built.stdout).toContain(block);
161+
expect(built.stdout).not.toContain('Build complete');
162+
expect(existsSync(fixtures.pairPlain.dist)).toBe(false);
163+
}, 60_000);
164+
165+
it('CONTROL: the base\'s paired .custom extending with `region__c` builds, and validates clean', async () => {
166+
expect((await validate('pairSuffixed', '--json')).code).toBe(0);
167+
const built = await build('pairSuffixed', '--json');
168+
const payload = json(built, 'build pairSuffixed');
169+
expect(payload.success, JSON.stringify(payload.errors ?? payload.error)).toBe(true);
170+
expect(built.code).toBe(0);
171+
const artifact = JSON.parse(readFileSync(fixtures.pairSuffixed.artifact, 'utf8')) as Record<string, any>;
172+
expect(Object.keys(artifact.objectExtensions[0].fields)).toEqual(['region__c']);
173+
}, 60_000);
174+
});

‎packages/cli/test/validate-build-gate-parity.test.ts‎

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,22 @@ const SHARED_NON_REGISTRY_GATES: readonly string[] = [
197197
// here — deleted there rather than reworded, for the reason the
198198
// `runPerPackageAuthoringRules` row above gives.
199199
'findOverrideDeclarationRefusals',
200+
// [ADR-0146 D10, #28564, #28727] The `__c` extension-field reservation: a
201+
// field spelled `__c` only in a base's paired `.custom` package, and every
202+
// `objectExtensions[].fields` key of a `.custom` pair suffixed, judged by
203+
// the save and install doors' own `judgeExtensionFieldSuffix`. Not a
204+
// registry rule: the WALK decides which package each field is declared in —
205+
// each `packages[]` entry under its own id, or the stack under
206+
// `manifest.id` — and the verdict turns on that package's id, which the
207+
// union stack a registry rule is handed does not carry.
208+
//
209+
// ⭐ This row is the #28564 VALIDATE_ONLY_GATES entry CLOSED. That entry read
210+
// "os build is a further point it did not name", and it was right: the
211+
// build emitted an artifact the marketplace install and the save and
212+
// publish doors refused later. `compile.ts` now makes the same call
213+
// (#28727), so the row moved here — deleted there rather than reworded, for
214+
// the reason the `runPerPackageAuthoringRules` row above gives.
215+
'findExtensionFieldSuffixRefusals',
200216
];
201217

202218
/**
@@ -235,22 +251,13 @@ const BUILD_ONLY_GATES: Readonly<Record<string, string>> = {
235251
* SHARED_NON_REGISTRY_GATES instead.
236252
*
237253
* EMPTY is this ledger's steady state: every row is a gap the build carries.
238-
* Two rows it has held moved to SHARED_NON_REGISTRY_GATES when `compile.ts`
239-
* gained the call: `findViewContainerNameRefusals` (#20331 → #20393) and
240-
* `findOverrideDeclarationRefusals` (#28410 → #28643). It holds two today,
241-
* `findExtensionFieldSuffixRefusals` (#28564) and `findFieldPatchTargetRefusals`
242-
* (#28565), whose cards did not name the build.
254+
* Three rows it has held moved to SHARED_NON_REGISTRY_GATES when `compile.ts`
255+
* gained the call: `findViewContainerNameRefusals` (#20331 → #20393),
256+
* `findOverrideDeclarationRefusals` (#28410 → #28643) and
257+
* `findExtensionFieldSuffixRefusals` (#28564 → #28727). It holds one today,
258+
* `findFieldPatchTargetRefusals` (#28565), whose card did not name the build.
243259
*/
244260
const VALIDATE_ONLY_GATES: Readonly<Record<string, string>> = {
245-
// [#28564] A real gap in the build's direction, reported and not closed —
246-
// the shape `findOverrideDeclarationRefusals` held until #28643 closed it.
247-
// Card #28564 (ADR-0146 D10, decided item 7) named the `__c` reservation's
248-
// doors — the save, restore and publish doors, `os validate`, the install
249-
// doors and the duplicate — and `os build` is not one of them. The artifact
250-
// a build emits is still judged where it lands: the marketplace install and
251-
// the save and publish doors refuse the same fields with the same judge.
252-
findExtensionFieldSuffixRefusals:
253-
'card #28564 placed the __c reservation at the save/restore/publish doors, os validate, the install doors and duplicate; os build is a further point it did not name (as #28410 left the override gate until #28643). The install and publish doors refuse the same fields with the same judge.',
254261
findFieldPatchTargetRefusals:
255262
'[ADR-0146 D10, second rule] An object extension\'s `fieldPatches` naming a field or option value its ' +
256263
'in-stack target lacks, judged by the marketplace install door\'s own `fieldPatchTargetRefusals`. Wired ' +

0 commit comments

Comments
 (0)