Skip to content

Commit b3cdb43

Browse files
committed
feat(spec)!: remove deprecated aiStudio/aiSeat capability aliases (#3308)
The one-cycle deprecation window opened in #3265 is over. The legacy camelCase `requires` spellings `aiStudio`/`aiSeat` are no longer canonicalized to `ai-studio`/`ai-seat` — they are now plain unknown tokens, rejected by defineStack like any other typo. - spec: drop DEPRECATED_PLATFORM_CAPABILITY_ALIASES and canonicalizePlatformCapability exports; isKnownPlatformCapability no longer canonicalizes. - spec: defineStack no longer rewrites aliases (canonicalizeStackRequires removed); validateKnownCapabilities rejects the legacy spellings. - cli: serve resolver no longer canonicalizes raw-artifact requires. - Regenerate api-surface snapshot (4 export deletions). BREAKING shipped as minor per the launch-window convention. Migration: use the canonical kebab-case tokens ai-studio / ai-seat. First-party configs were migrated in cloud #862/#863; cloud's objectos-runtime (pinned to an older framework) follows on its next .framework-sha bump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjHfkKmEvgk8v7N8nTe5sH
1 parent 16ebe1a commit b3cdb43

9 files changed

Lines changed: 90 additions & 177 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/cli": minor
4+
---
5+
6+
feat(spec)!: remove deprecated `aiStudio`/`aiSeat` capability aliases (#3308)
7+
8+
**BREAKING** (shipped as minor per the launch-window convention). The one-cycle
9+
deprecation window from #3265 is over: the legacy camelCase `requires` spellings
10+
`aiStudio`/`aiSeat` are no longer canonicalized to `ai-studio`/`ai-seat` — they
11+
are now plain unknown tokens, rejected by `defineStack` like any other typo.
12+
13+
- Removed exports `DEPRECATED_PLATFORM_CAPABILITY_ALIASES` and
14+
`canonicalizePlatformCapability` from `@objectstack/spec`; `isKnownPlatformCapability`
15+
no longer canonicalizes.
16+
- `defineStack` no longer rewrites aliases (the `canonicalizeStackRequires` pass
17+
is gone); the serve resolver no longer canonicalizes raw-artifact `requires`.
18+
19+
Migration: use the canonical kebab-case tokens `ai-studio` / `ai-seat`. All
20+
first-party configs were migrated in #862/#863; only stacks still carrying the
21+
legacy spelling are affected. Cloud's `objectos-runtime` (pinned to an older
22+
framework) follows on its next `.framework-sha` bump.

packages/cli/src/commands/serve.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { loadConfig, BUNDLE_REQUIRE_EXTERNALS } from '../utils/config.js';
1010
import { isHostConfig, shouldBootWithLibrary } from '../utils/plugin-detection.js';
1111
import { resolveDriverType, createStorageDriver, UnsupportedDriverError } from '../utils/storage-driver.js';
1212
import { readEnvWithDeprecation, resolveMultiOrgEnabled, resolveAllowDegradedTenancy, isMcpServerEnabled, resolveSearchPinyinEnabled, isModuleNotFoundError } from '@objectstack/types';
13-
import { PLATFORM_CAPABILITY_TOKENS, canonicalizePlatformCapability } from '@objectstack/spec/kernel';
13+
import { PLATFORM_CAPABILITY_TOKENS } from '@objectstack/spec/kernel';
1414
import { resolveObjectStackHome } from '@objectstack/runtime';
1515
import { LOG_LEVELS, resolveLogLevel, readLogLevelEnv } from '../utils/log-level.js';
1616
import {
@@ -695,22 +695,14 @@ export default class Serve extends Command {
695695
// instance wins over the auto-loader).
696696
const presetName = flags.preset ?? (isDev ? 'default' : 'default');
697697
const presetTiers = Serve.TIER_PRESETS[presetName] ?? Serve.TIER_PRESETS.default;
698+
// Dedupe `requires` (Set keeps first-seen order). The deprecated
699+
// `aiStudio`/`aiSeat` alias canonicalization was removed in framework#3308
700+
// — legacy spellings are now unknown tokens (warned below, rejected at
701+
// authoring by defineStack).
698702
const rawRequires: string[] = Array.isArray((config as any).requires)
699703
? (config as any).requires.filter((c: unknown) => typeof c === 'string')
700704
: [];
701-
// Canonicalize deprecated capability spellings (aiStudio → ai-studio, …)
702-
// so artifacts compiled by an older spec keep booting; `defineStack`
703-
// already rewrites + warns on the source path, this covers raw artifact
704-
// JSON (framework#3265). Dedupe after mapping (Set keeps first-seen order).
705-
const requires: string[] = [...new Set(rawRequires.map((c: string) => {
706-
const canonical = canonicalizePlatformCapability(c);
707-
if (canonical !== c) {
708-
console.warn(chalk.yellow(
709-
` ⚠ requires: '${c}' is a deprecated spelling — use '${canonical}' (framework#3265)`,
710-
));
711-
}
712-
return canonical;
713-
}))];
705+
const requires: string[] = [...new Set(rawRequires)];
714706
// Snapshot the app's EXPLICIT capability declarations BEFORE the platform
715707
// appends its own convenience defaults (auth→email, mcp, pinyin-search,
716708
// ALWAYS_ON_CAPABILITIES, queue/job). Only these explicit declarations carry

packages/cli/test/serve-capability-vocabulary.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import {
3-
PLATFORM_CAPABILITY_TOKENS,
4-
DEPRECATED_PLATFORM_CAPABILITY_ALIASES,
5-
} from '@objectstack/spec/kernel';
2+
import { PLATFORM_CAPABILITY_TOKENS } from '@objectstack/spec/kernel';
63
import Serve from '../src/commands/serve.js';
74

85
// framework#3265 — drift guard: the serve path's provider registries must stay
@@ -22,8 +19,8 @@ describe('serve capability registries vs spec vocabulary (#3265)', () => {
2219
}
2320
});
2421

25-
it('registries use only canonical spellings — never deprecated aliases', () => {
26-
const legacy = Object.keys(DEPRECATED_PLATFORM_CAPABILITY_ALIASES);
22+
it('registries use only canonical spellings — never the removed camelCase aliases (#3308)', () => {
23+
const legacy = ['aiStudio', 'aiSeat'];
2724
for (const token of [...Object.keys(Serve.CAPABILITY_PROVIDERS), ...Object.keys(Serve.CAPABILITY_TO_TIER)]) {
2825
expect(legacy).not.toContain(token);
2926
}

packages/spec/api-surface.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"ConversionFixture (interface)",
2727
"ConversionNotice (interface)",
2828
"CronExpressionInputSchema (const)",
29-
"DEPRECATED_PLATFORM_CAPABILITY_ALIASES (const)",
3029
"DatasourceMappingRule (type)",
3130
"DatasourceMappingRuleSchema (const)",
3231
"DefineStackOptions (interface)",
@@ -102,7 +101,6 @@
102101
"applyConversions (function)",
103102
"applyConversionsToFlow (function)",
104103
"applyMetaMigrations (function)",
105-
"canonicalizePlatformCapability (function)",
106104
"cel (function)",
107105
"collectConversionNotices (function)",
108106
"composeMigrationChain (function)",
@@ -1294,7 +1292,6 @@
12941292
"CustomizationPolicy (type)",
12951293
"CustomizationPolicySchema (const)",
12961294
"DEFAULT_METADATA_TYPE_REGISTRY (const)",
1297-
"DEPRECATED_PLATFORM_CAPABILITY_ALIASES (const)",
12981295
"DeadLetterQueueEntry (type)",
12991296
"DeadLetterQueueEntrySchema (const)",
13001297
"DependencyConflict (type)",
@@ -1750,7 +1747,6 @@
17501747
"VersionConstraint (type)",
17511748
"VersionConstraintSchema (const)",
17521749
"VulnerabilitySeverity (type)",
1753-
"canonicalizePlatformCapability (function)",
17541750
"deriveNamespaceFromPackageId (function)",
17551751
"evaluateLockForDelete (function)",
17561752
"evaluateLockForWrite (function)",

packages/spec/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,6 @@ export { type PluginContext } from './kernel/plugin.zod';
123123
// Platform SERVICE capability vocabulary for `requires: [...]` (framework#3265).
124124
export {
125125
PLATFORM_CAPABILITY_TOKENS,
126-
DEPRECATED_PLATFORM_CAPABILITY_ALIASES,
127-
canonicalizePlatformCapability,
128126
isKnownPlatformCapability,
129127
} from './kernel/platform-capabilities';
130128

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { describe, expect, it } from 'vitest';
22
import {
33
PLATFORM_CAPABILITY_TOKENS,
4-
DEPRECATED_PLATFORM_CAPABILITY_ALIASES,
5-
canonicalizePlatformCapability,
64
isKnownPlatformCapability,
75
} from './platform-capabilities';
86

97
// framework#3265 — one capability vocabulary across the standalone serve path
10-
// and cloud's objectos-runtime loader, canonical spelling kebab-case.
8+
// and cloud's objectos-runtime loader, canonical spelling kebab-case. The
9+
// deprecated `aiStudio`/`aiSeat` aliases were removed in #3308.
1110

1211
describe('PLATFORM_CAPABILITY_TOKENS', () => {
1312
it('is frozen and duplicate-free', () => {
@@ -27,37 +26,23 @@ describe('PLATFORM_CAPABILITY_TOKENS', () => {
2726
}
2827
});
2928

30-
it('contains no deprecated spellings', () => {
31-
for (const legacy of Object.keys(DEPRECATED_PLATFORM_CAPABILITY_ALIASES)) {
29+
it('contains no camelCase legacy spellings (aliases removed, #3308)', () => {
30+
for (const legacy of ['aiStudio', 'aiSeat']) {
3231
expect(PLATFORM_CAPABILITY_TOKENS).not.toContain(legacy);
3332
}
3433
});
3534
});
3635

37-
describe('DEPRECATED_PLATFORM_CAPABILITY_ALIASES / canonicalizePlatformCapability', () => {
38-
it('maps every alias onto a token that exists in the vocabulary', () => {
39-
for (const canonical of Object.values(DEPRECATED_PLATFORM_CAPABILITY_ALIASES)) {
40-
expect(PLATFORM_CAPABILITY_TOKENS).toContain(canonical);
41-
}
42-
});
43-
44-
it('canonicalizes the legacy cloud spellings', () => {
45-
expect(canonicalizePlatformCapability('aiStudio')).toBe('ai-studio');
46-
expect(canonicalizePlatformCapability('aiSeat')).toBe('ai-seat');
47-
});
48-
49-
it('is identity for canonical and unknown tokens', () => {
50-
expect(canonicalizePlatformCapability('ai-studio')).toBe('ai-studio');
51-
expect(canonicalizePlatformCapability('automation')).toBe('automation');
52-
expect(canonicalizePlatformCapability('not-a-capability')).toBe('not-a-capability');
53-
});
54-
});
55-
5636
describe('isKnownPlatformCapability', () => {
57-
it('accepts canonical tokens and deprecated aliases, rejects unknowns', () => {
37+
it('accepts canonical tokens verbatim', () => {
5838
expect(isKnownPlatformCapability('ai-studio')).toBe(true);
59-
expect(isKnownPlatformCapability('aiStudio')).toBe(true);
39+
expect(isKnownPlatformCapability('ai-seat')).toBe(true);
6040
expect(isKnownPlatformCapability('governance')).toBe(true);
41+
});
42+
43+
it('rejects the removed camelCase aliases and typos (no canonicalization, #3308)', () => {
44+
expect(isKnownPlatformCapability('aiStudio')).toBe(false);
45+
expect(isKnownPlatformCapability('aiSeat')).toBe(false);
6146
expect(isKnownPlatformCapability('automations')).toBe(false);
6247
});
6348
});

packages/spec/src/kernel/platform-capabilities.ts

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
* ADR-0066 authorization capabilities declared in `capabilities: [...]`.)
1313
*
1414
* Canonical spelling is lower-case kebab-case (`ai-studio`, `pinyin-search`,
15-
* `hierarchy-security`). Legacy camelCase spellings that shipped in cloud
16-
* configs are mapped through {@link DEPRECATED_PLATFORM_CAPABILITY_ALIASES}
17-
* for one deprecation cycle — `defineStack` normalizes them with a warning at
18-
* authoring time, and runtimes canonicalize raw artifact input the same way.
15+
* `hierarchy-security`). The legacy camelCase spellings (`aiStudio` / `aiSeat`)
16+
* that shipped transitionally were honored as deprecated aliases for one cycle
17+
* (framework#3265) and have been REMOVED (framework#3308) — they are now plain
18+
* unknown tokens, rejected by `defineStack` like any other typo.
1919
*
2020
* Growing the platform: when a new `requires`-resolvable service ships, add
2121
* its token HERE as well as to the runtime's provider registry — the CLI's
@@ -66,30 +66,10 @@ export const PLATFORM_CAPABILITY_TOKENS: readonly string[] = Object.freeze([
6666
]);
6767

6868
/**
69-
* Deprecated `requires` spellings → canonical token. One deprecation cycle:
70-
* authoring (`defineStack`) rewrites these with a warning; runtimes accept
71-
* them via {@link canonicalizePlatformCapability} so artifacts built by an
72-
* older spec keep booting. Do not add new aliases — new tokens ship in
73-
* canonical kebab-case only.
74-
*/
75-
export const DEPRECATED_PLATFORM_CAPABILITY_ALIASES: Readonly<Record<string, string>> =
76-
Object.freeze({
77-
aiStudio: 'ai-studio',
78-
aiSeat: 'ai-seat',
79-
});
80-
81-
/**
82-
* Map a `requires` token to its canonical spelling (identity for tokens that
83-
* are already canonical or unknown).
84-
*/
85-
export function canonicalizePlatformCapability(token: string): string {
86-
return DEPRECATED_PLATFORM_CAPABILITY_ALIASES[token] ?? token;
87-
}
88-
89-
/**
90-
* True when the token (after alias canonicalization) is part of the platform
91-
* capability vocabulary.
69+
* True when the token is part of the platform capability vocabulary. There is
70+
* no longer any alias canonicalization (framework#3308) — a token is known iff
71+
* it appears verbatim in {@link PLATFORM_CAPABILITY_TOKENS}.
9272
*/
9373
export function isKnownPlatformCapability(token: string): boolean {
94-
return PLATFORM_CAPABILITY_TOKENS.includes(canonicalizePlatformCapability(token));
74+
return PLATFORM_CAPABILITY_TOKENS.includes(token);
9575
}
Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,16 @@
1-
import { afterEach, describe, expect, it, vi } from 'vitest';
1+
import { describe, expect, it } from 'vitest';
22
import { defineStack } from './stack.zod';
33

4-
// framework#3265 — defineStack canonicalizes deprecated `requires` spellings at
5-
// the PRODUCER (authoring time) and validates tokens against the platform
6-
// capability vocabulary: deprecated aliases are rewritten with a warning, an
7-
// unknown token is a hard error (no runtime provides it → declared ≠ enforced).
4+
// framework#3265/#3308 — defineStack validates `requires` tokens against the
5+
// platform capability vocabulary at the PRODUCER (authoring time): an unknown
6+
// token is a hard error (no runtime provides it → declared ≠ enforced). The
7+
// deprecated `aiStudio`/`aiSeat` aliases were removed in #3308, so those now
8+
// reject like any other typo — there is no canonicalization step left.
89

9-
afterEach(() => {
10-
vi.restoreAllMocks();
11-
});
12-
13-
describe('defineStack requires canonicalization (#3265)', () => {
14-
it('rewrites deprecated aliases to canonical kebab tokens with a warning', () => {
15-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
16-
const stack = defineStack({ requires: ['ai', 'aiStudio', 'automation'] });
17-
expect(stack.requires).toEqual(['ai', 'ai-studio', 'automation']);
18-
expect(warn).toHaveBeenCalledTimes(1);
19-
expect(warn.mock.calls[0][0]).toContain("'aiStudio' is a deprecated spelling");
20-
expect(warn.mock.calls[0][0]).toContain("'ai-studio'");
21-
});
22-
23-
it('canonical declarations pass through untouched and warning-free', () => {
24-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
25-
const stack = defineStack({ requires: ['ai', 'ai-studio', 'hierarchy-security', 'governance'] });
26-
expect(stack.requires).toEqual(['ai', 'ai-studio', 'hierarchy-security', 'governance']);
27-
expect(warn).not.toHaveBeenCalled();
10+
describe('defineStack requires validation (#3265/#3308)', () => {
11+
it('canonical declarations pass through untouched', () => {
12+
const stack = defineStack({ requires: ['ai', 'ai-studio', 'ai-seat', 'hierarchy-security', 'governance'] });
13+
expect(stack.requires).toEqual(['ai', 'ai-studio', 'ai-seat', 'hierarchy-security', 'governance']);
2814
});
2915

3016
it('THROWS on an unknown token (a typo no runtime provides), naming it', () => {
@@ -33,6 +19,15 @@ describe('defineStack requires canonicalization (#3265)', () => {
3319
);
3420
});
3521

22+
it('the removed camelCase aliases now REJECT like any other unknown token (#3308)', () => {
23+
expect(() => defineStack({ requires: ['aiStudio'] })).toThrowError(
24+
/'aiStudio' is not a known platform capability/,
25+
);
26+
expect(() => defineStack({ requires: ['aiSeat'] })).toThrowError(
27+
/'aiSeat' is not a known platform capability/,
28+
);
29+
});
30+
3631
it('reports every distinct unknown token but not known ones', () => {
3732
let msg = '';
3833
try {
@@ -46,23 +41,8 @@ describe('defineStack requires canonicalization (#3265)', () => {
4641
expect(msg).not.toContain("'ai' is not"); // known token isn't flagged
4742
});
4843

49-
it('a deprecated alias is NOT treated as unknown — it canonicalizes and passes', () => {
50-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
51-
expect(() => defineStack({ requires: ['aiStudio', 'ai-seat'] })).not.toThrow();
52-
// aiStudio → ai-studio (warned); ai-seat is already canonical
53-
expect(warn).toHaveBeenCalledTimes(1);
54-
});
55-
56-
it('warns once per distinct token, not once per occurrence', () => {
57-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
58-
defineStack({ requires: ['aiStudio', 'aiStudio'] });
59-
expect(warn).toHaveBeenCalledTimes(1);
60-
});
61-
62-
it('non-strict mode skips canonicalization and warnings by contract', () => {
63-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
44+
it('non-strict mode skips validation by contract (unknown token passes through)', () => {
6445
const stack = defineStack({ requires: ['aiStudio'] }, { strict: false });
6546
expect(stack.requires).toEqual(['aiStudio']);
66-
expect(warn).not.toHaveBeenCalled();
6747
});
6848
});

0 commit comments

Comments
 (0)