Skip to content

Commit 2c702fd

Browse files
feat(spec)!: unexport normalizeVisibleWhen and VISIBILITY_ALIAS_KEYS from @objectstack/spec/shared; read visibleWhen directly (#24004)
Fixes #23877 Clause-②: yes ## What `@objectstack/spec/shared` stops exporting `normalizeVisibleWhen` and `VISIBILITY_ALIAS_KEYS`. They were the ADR-0089 fold of the retired `visibleOn` / `visibility` keys onto `visibleWhen`, and the list of those two keys. Since the two keys became `retiredKey()` tombstones at 18, no schema called the fold. The replacement is to read `visibleWhen` directly. - `packages/spec/src/shared/visibility.ts` is deleted. The two exports were all it held (plus the private `WithVisibilityAliases` type and the docblocks that describe them), so the `export * from './visibility'` line leaves the `shared` barrel too. - `api-surface/shared.json` and `export-origins/shared.json` were regenerated with `gen:api-surface` / `gen:export-origins`. Each loses exactly the two rows. `api-surface-signatures.json` and `declaration-map/` never listed either name, so they did not move. `check:authorable-surface` did not move either, because no schema key changed. - ADR-0087 D3: the new semantic entry `visibility-alias-fold-unexported` follows the shape of `visibility-strict-options-unexported`, with no `conversionIds` (a TS surface, never stored). That older entry's `replacement` said the two "stay exported". It now points at the new entry. - Comments that named a removed symbol now name the mechanism instead: `kernel/public-auth-features.ts` (the `requiresFeature` lowering) and `ui/view.zod.ts` (the `groups` → `sections` fold). - A pin, `shared/visibility-fold-unexported.test.ts`: the `shared` barrel's runtime namespace does not carry either name. A surviving export (`lazySchema`) is the lit control. - The changeset is BREAKING `minor` on `@objectstack/spec` with `Clause-②: yes (narrowing)`, a FROM → TO table and the marker `registered visibility-alias-fold-unexported`. It also says it supersedes the pending F4 note's "stay exported" sentence (see Acceptance notes). Not touched: the stored-row conversions `view-visibleOn-to-visibleWhen` and `page-component-visibility-to-visibleWhen`, any schema's accept set, and objectui. ## Zero pull, measured - objectstack @ `2e1562091c` (`git grep` over the whole tree, CHANGELOGs and `dist` excluded): there are no call sites. The only hits were the definition, the two baselines, the two precedent comments, the D3 entry `18.visibility-strict-options-unexported.ts:16-17`, and one sentence of the pending F4 changeset (see Acceptance notes). - objectui @ the `.objectui-sha` pin `f8267df93a`: there is one hit, a comment (`packages/core/src/utils/normalize-list-view.ts:374`), and no call. The positive control `visibleWhen` hits in the same tree. ## Ablation The fix was committed first. The mutation went through `scripts/ablation-replace.mjs` with a `trap` restore. It re-adds `export const VISIBILITY_ALIAS_KEYS = …` to `shared/index.ts`. Result: the anchor went 1 → 0, the blob went `e2d66ff65e0b` → `eee4d0b060de`, and `no longer exports VISIBILITY_ALIAS_KEYS` went red (`AssertionError: expected true to be false`, 1 failed | 2 passed). After the restore, the blob equals HEAD and `git diff HEAD` is empty. The first attempt was a no-op because its replacement still contained the anchor; the tool refused it before running anything. The pin imports `./index` from source, so no rebuild applies. ## Verification (head `9d59f55e`, after merging `origin/main`; the final head `5626f603` adds two changeset lines on top of it) - `pnpm --filter @objectstack/spec build` passes. `check:generated` reports all 14 artifacts up to date (before regeneration it flagged exactly `api-surface/` and `export-origins/`, `./shared` − the two names). - `pnpm --filter @objectstack/spec typecheck` exits 0. - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2`: 755 files, 22018 passed, 1 todo. - `node scripts/check-adr-0087-registration.mjs` passes: `[BREAKING+bang+clause-②-narrowing] registered visibility-alias-fold-unexported (new here)`. - `node scripts/check-platform-checklist.mjs --changed-from origin/main` reports OK: 24 anchors judged, 0 lost. - `dispatch-gates --commands` derived 88 families, all run, with exit codes captured before any pipe. 87 exited 0. One was NOT MEASURED (`check-plugin-teardown-shape --self-test`, exit 3: its fixture commit is beyond this shallow clone). `--ran` reconciled 88/88. - Re-run on `5626f603` for the changeset text: `check-adr-0087-registration`, `check-changeset-no-major`, `check-empty-changeset`, `check:doc-authoring`, `check:issue-citations`, `check:nul-bytes`, `check:pm-changeset-deadline-census`, `check:objectui-changeset`, `check:published-files`. All exited 0. ## Acceptance notes - `packages/spec/src/data/field.zod.ts:10` had a comment saying `editability-boundary`'s only runtime import was `shared/visibility.ts`. In fact it is `shared/visibility-strict-options.ts`, and the deleted file would have left the comment pointing nowhere. The comment was re-pointed in place (one line, comment only). That file is outside the claim's listed surface, which is why this note exists. - The pending (unreleased, not in `pre.json`) changeset `.changeset/23543-f4-visibility-alias-keys-retired.md:42` says `normalizeVisibleWhen` and `VISIBILITY_ALIAS_KEYS` "stay exported", which this PR makes false for the same release. Correcting it in place makes `check-empty-changeset` red by design (it classes this as a deliberate correction to another PR's release note, which needs a person to confirm), and the file is outside this claim's surface. So this PR leaves it as authored, and its own changeset says it supersedes that sentence. If the seat wants it corrected in place, the one-line rewrite is: drop the first clause, and say the two leave `@objectstack/spec/shared` in a separate change of the same release (D3 entry `visibility-alias-fold-unexported`). - `packages/spec/scripts/build-skill-references.ts:274` mentions `visibility.ts` in a historical note about leaked transitive imports. It names no symbol and stays as is. --- _Generated by [Claude Code](https://claude.ai/code/session_01T2YHPgcuCRcypzyAuxvAap)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cf31c34 commit 2c702fd

11 files changed

Lines changed: 107 additions & 86 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: `@objectstack/spec/shared` no longer exports `normalizeVisibleWhen` or `VISIBILITY_ALIAS_KEYS`; read `visibleWhen` directly
6+
7+
Clause-②: yes (narrowing)
8+
9+
**BREAKING (TS/API surface):** two exports of `@objectstack/spec/shared` are gone — the function `normalizeVisibleWhen`, which folded the retired `visibleOn` / `visibility` keys onto the canonical `visibleWhen`, and the const `VISIBILITY_ALIAS_KEYS`, which listed those two keys. Use `visibleWhen` directly instead: it is the one conditional-visibility key, and since 18 every schema that had the two old keys (the form field, the form section and the page component) refuses them by name. No schema used the fold after those keys retired, and no caller of either export was found in this repository or in the pinned renderer.
10+
11+
## FROM → TO
12+
13+
| you wrote | write instead |
14+
| --- | --- |
15+
| `import { normalizeVisibleWhen } from '@objectstack/spec/shared'`, then `normalizeVisibleWhen(node)` | read and write `node.visibleWhen`; there is no alias left to fold |
16+
| `import { VISIBILITY_ALIAS_KEYS } from '@objectstack/spec/shared'` | nothing — the two keys it named are refused by the schemas; to reject them yourself, parse with the schema |
17+
18+
**The one-line fix:** delete the import and the call, and read `visibleWhen`.
19+
20+
This supersedes the sentence in this release's `visibleOn` / `visibility` retirement note that says the two exports "stay exported": they leave in the same release.
21+
22+
**What an author who still imports them sees:** `tsc` fails with TS2305 (`Module '"@objectstack/spec/shared"' has no exported member …`); an ESM named import fails at link time with a `SyntaxError` (the module does not provide an export of that name); a CommonJS `require` reads the member as `undefined`.
23+
24+
**Nothing to migrate in metadata.** No authored document ever carried either export, so `os migrate meta` has nothing to visit. The stored-row conversions `view-visibleOn-to-visibleWhen` and `page-component-visibility-to-visibleWhen` are unchanged: a pre-18 `sys_metadata` view or page row that still carries an old key keeps loading with `visibleWhen`.
25+
26+
<!-- adr-0087: registered visibility-alias-fold-unexported -->

‎packages/spec/api-surface/shared.json‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
"TemplateExpressionInput (type)",
9797
"TemplateExpressionInputSchema (const)",
9898
"TypedExpressionDialect (type)",
99-
"VISIBILITY_ALIAS_KEYS (const)",
10099
"ValueDomain (type)",
101100
"ValueDomainSchema (const)",
102101
"applyProtection (function)",
@@ -116,7 +115,6 @@
116115
"normalizeMetadataCollection (function)",
117116
"normalizePluginMetadata (function)",
118117
"normalizeStackInput (function)",
119-
"normalizeVisibleWhen (function)",
120118
"objectStackErrorMap (const)",
121119
"pluralToSingular (function)",
122120
"renderDiffMessage (function)",

‎packages/spec/export-origins/shared.json‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@
9191
"TemplateExpressionInput": "src/shared/expression.zod.ts#TemplateExpressionInput (type)",
9292
"TemplateExpressionInputSchema": "src/shared/expression.zod.ts#TemplateExpressionInputSchema (const)",
9393
"TypedExpressionDialect": "src/shared/expression.zod.ts#TypedExpressionDialect (type)",
94-
"VISIBILITY_ALIAS_KEYS": "src/shared/visibility.ts#VISIBILITY_ALIAS_KEYS (const)",
9594
"ValueDomain": "src/shared/value-domain.zod.ts#ValueDomain (type)",
9695
"ValueDomainSchema": "src/shared/value-domain.zod.ts#ValueDomainSchema (const)",
9796
"applyProtection": "src/shared/protection.zod.ts#applyProtection (function)",
@@ -111,7 +110,6 @@
111110
"normalizeMetadataCollection": "src/shared/metadata-collection.zod.ts#normalizeMetadataCollection (function)",
112111
"normalizePluginMetadata": "src/shared/metadata-collection.zod.ts#normalizePluginMetadata (function)",
113112
"normalizeStackInput": "src/shared/metadata-collection.zod.ts#normalizeStackInput (function)",
114-
"normalizeVisibleWhen": "src/shared/visibility.ts#normalizeVisibleWhen (function)",
115113
"objectStackErrorMap": "src/shared/error-map.zod.ts#objectStackErrorMap (const)",
116114
"pluralToSingular": "src/meta-spelling/manifest-collection-spelling.ts#pluralToSingular (function)",
117115
"renderDiffMessage": "src/shared/external-errors.ts#renderDiffMessage (function)",

‎packages/spec/src/data/field.zod.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { KeySetGuidance } from '../shared/suggestions.zod';
77
// Package-internal, like `strict-object` itself — the `shared/index.ts` barrel
88
// deliberately does not re-export it, so nothing about the public API surface
99
// moves. No cycle back into this file: that module's only runtime import is
10-
// `shared/visibility.ts`, which imports nothing at runtime.
10+
// `shared/visibility-strict-options.ts`, which imports nothing at runtime.
1111
import { SELECT_OPTION_EDITABILITY_GUIDANCE } from '../shared/editability-boundary';
1212
import { MetadataProtectionFields } from '../kernel/metadata-protection.zod';
1313
// The ADR-0078 completeness predicate the choice door below applies, read

‎packages/spec/src/kernel/public-auth-features.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,10 @@ type WithRequiresFeature = {
317317

318318
/**
319319
* Lower the declarative `requiresFeature: '<flag>'` sugar into the canonical
320-
* `visible` CEL predicate and strip the sugar key from the output — mirroring
321-
* `normalizeVisibleWhen` (ADR-0089): persisted artifacts, lint, runtime, and
322-
* objectui only ever see the canonical envelope.
320+
* `visible` CEL predicate and strip the sugar key from the output — the same
321+
* schema-boundary fold onto one canonical key that ADR-0089 ran for
322+
* `visibleOn` / `visibility` → `visibleWhen` through 17: persisted artifacts,
323+
* lint, runtime, and objectui only ever see the canonical envelope.
323324
*
324325
* - No existing `visible` → `{ dialect: 'cel', source: <gate> }`, string-equal
325326
* to the hand-written gates it replaces.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { SemanticMigration } from '../../types.js';
4+
5+
// A TS/API surface, never stored in stack metadata: there is no source for the
6+
// chain to rewrite, so this entry is the whole ADR-0087 registration. The two
7+
// stored-row conversions that rename the retired keys on a pre-18 `sys_metadata`
8+
// row (`view-visibleOn-to-visibleWhen`, `page-component-visibility-to-visibleWhen`)
9+
// are a different mechanism and are not touched by this removal.
10+
export const entry: SemanticMigration = {
11+
id: 'visibility-alias-fold-unexported',
12+
surface:
13+
'`normalizeVisibleWhen` (function) and `VISIBILITY_ALIAS_KEYS` (const) on '
14+
+ '`@objectstack/spec/shared` — the fold of the retired `visibleOn` / `visibility` '
15+
+ 'keys onto the canonical `visibleWhen` (ADR-0089 D2), and the list of those two keys',
16+
replacement:
17+
'(removed from the public surface — no replacement export. Read and write `visibleWhen` '
18+
+ 'directly: it is the one conditional-visibility key every schema accepts, and the form '
19+
+ 'field, form section and page component shapes refuse `visibleOn` and `visibility` by '
20+
+ 'name with the rename as the prescription. A pre-18 document carrying either key is '
21+
+ 'renamed by hand; a stored row is renamed by the rehydration conversions.)',
22+
reason:
23+
'ADR-0049 enforce-or-remove applied to an export. Since the two alias keys retired at 18, '
24+
+ 'no schema calls the fold, and a measured search of this repository and of the pinned '
25+
+ 'renderer found no caller of either export, only comments. A published helper that '
26+
+ 'quietly re-accepts two keys every parsing door refuses invites exactly the consumer-side '
27+
+ 'tolerance the contract-first rule forbids: a renderer calling it would honour `visibleOn` '
28+
+ 'where the spec rejects it.',
29+
acceptanceCriteria:
30+
'No code imports `normalizeVisibleWhen` or `VISIBILITY_ALIAS_KEYS` from '
31+
+ '`@objectstack/spec/shared` or any other entry (TS2305 after upgrade). Every '
32+
+ 'visibility-carrying shape parses and refuses exactly as before — no schema used the '
33+
+ 'fold. No authored metadata document ever carried either export, so `os migrate meta` '
34+
+ 'has nothing to visit.',
35+
};

‎packages/spec/src/migrations/entries/semantic/18.visibility-strict-options-unexported.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export const entry: SemanticMigration = {
1313
'(removed from the public surface — no replacement export. It was an internal option bag '
1414
+ 'for this package\'s own schemas; the visibility contract it configures is unchanged and '
1515
+ 'still published through the schemas that use it — `FormFieldSchema`, `FormSectionSchema` '
16-
+ 'and the page component — together with `normalizeVisibleWhen` and '
17-
+ '`VISIBILITY_ALIAS_KEYS`, which stay exported.)',
16+
+ 'and the page component. The alias fold `normalizeVisibleWhen` and its key list '
17+
+ '`VISIBILITY_ALIAS_KEYS` are removed from the same entry in 18 as well (the D3 entry '
18+
+ '`visibility-alias-fold-unexported`).)',
1819
reason:
1920
'ADR-0049 enforce-or-remove applied to an export. The const was barrel-exported while its '
2021
+ 'type, `StrictObjectOptions`, is deliberately unpublished, so no consumer could annotate it, '

‎packages/spec/src/shared/index.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export * from './metadata-collection.zod';
2929
export * from './metadata-url-spelling';
3030
export * from './lazy-schema';
3131
export * from './expression.zod';
32-
export * from './visibility';
3332
export * from './protection.zod';
3433
export * from './resilient-fetch';
3534
// The closed standard-domain vocabulary (`iana_time_zone` / `iso_4217_currency`
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* `@objectstack/spec/shared` no longer exports the ADR-0089 alias fold.
5+
*
6+
* `normalizeVisibleWhen` folded the retired `visibleOn` / `visibility` keys onto
7+
* the canonical `visibleWhen`, and `VISIBILITY_ALIAS_KEYS` listed those two keys.
8+
* Once both keys became `retiredKey()` tombstones at 18, no schema called the
9+
* fold and no consumer in either repository did. A published helper that
10+
* re-accepts what every parsing door refuses is an invitation to consumer-side
11+
* tolerance, so both left the surface (ADR-0087 D3 entry
12+
* `visibility-alias-fold-unexported`). The replacement is to read `visibleWhen`
13+
* directly.
14+
*
15+
* Read from the barrel the `./shared` subpath export compiles, through the
16+
* runtime namespace, with a surviving export as the lit control that the
17+
* namespace is really populated.
18+
*/
19+
20+
import { describe, it, expect } from 'vitest';
21+
import * as shared from './index';
22+
23+
describe('`./index` (the `@objectstack/spec/shared` surface)', () => {
24+
it('still exports its surviving members (the control)', () => {
25+
expect(typeof shared.lazySchema).toBe('function');
26+
expect('lazySchema' in (shared as Record<string, unknown>)).toBe(true);
27+
});
28+
29+
it.each(['normalizeVisibleWhen', 'VISIBILITY_ALIAS_KEYS'])(
30+
'no longer exports `%s`',
31+
(name) => {
32+
expect(name in (shared as Record<string, unknown>)).toBe(false);
33+
},
34+
);
35+
});

‎packages/spec/src/shared/visibility.ts‎

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)