Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/plugin-wired-runtime-roster.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@objectstack/spec': minor
---

Add `PLATFORM_PLUGIN_WIRED_RUNTIMES` (and its row type `PlatformPluginWiredRuntime`) to the kernel platform-capability module: a companion provenance roster, keyed by npm package name, for the out-of-repo runtimes that reach the kernel through app `plugins[]` wiring rather than through a `requires` capability token — today `@objectstack/organizations` (loaded by `serve` off the resolved tenancy posture) and `@objectstack/security-enterprise` (which also backs the `hierarchy-security` token). The token-keyed `PLATFORM_CAPABILITY_PROVIDERS` map structurally cannot describe a package that backs no token; this roster makes "is this out-of-repo package real, and where does it ship from?" machine-readable for that population. Provenance only — it adds no capability token, changes no `requires` resolution, and encodes no posture-to-token semantics; drift tests pin the two rosters to agree wherever they name the same package.
59 changes: 58 additions & 1 deletion packages/cli/test/serve-capability-vocabulary.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
import { describe, expect, it } from 'vitest';
import { PLATFORM_CAPABILITY_TOKENS, PLATFORM_CAPABILITY_PROVIDERS } from '@objectstack/spec/kernel';
import {
PLATFORM_CAPABILITY_TOKENS,
PLATFORM_CAPABILITY_PROVIDERS,
PLATFORM_PLUGIN_WIRED_RUNTIMES,
} from '@objectstack/spec/kernel';
import Serve from '../src/commands/serve.js';

// framework#3265 — drift guard: the serve path's provider registries must stay
Expand DownExpand Up@@ -74,3 +78,56 @@ describe('PLATFORM_CAPABILITY_PROVIDERS vs vocabulary + serve resolver (#3366)',
expect(PLATFORM_CAPABILITY_PROVIDERS['ai-studio'].edition).toBe('cloud');
});
});

// #11263 — the companion roster for `plugins[]`-wired out-of-repo runtimes.
// The token-keyed map above structurally cannot hold `@objectstack/organizations`
// (it backs no `requires` token; `serve` loads it off the resolved tenancy
// posture), so its provenance row lives in PLATFORM_PLUGIN_WIRED_RUNTIMES.
// These pins keep the two rosters from diverging on the one fact they can both
// state, and keep the new one inside its own membership rule.
describe('PLATFORM_PLUGIN_WIRED_RUNTIMES vs providers + serve resolver (#11263)', () => {
it('declares @objectstack/organizations — the runtime serve loads off tenancy posture — as enterprise', () => {
const row = PLATFORM_PLUGIN_WIRED_RUNTIMES['@objectstack/organizations'];
expect(row, 'the package serve prints an install remedy for must have a provenance row').toBeTruthy();
expect(row.edition).toBe('enterprise');
});

it('every enterprise-edition provider package has a roster row — enterprise means plugins[]-wired, by definition', () => {
// CapabilityEdition's own definition: `enterprise` = "a separately-licensed
// enterprise package the app installs and wires in via `plugins[]`". So an
// enterprise provider row's package IS a plugins[]-wired out-of-repo
// runtime and must be declared in the companion roster too — this is what
// makes @objectstack/security-enterprise's double listing checked instead
// of divergent, and covers the next enterprise token on arrival.
const enterprisePackages = Object.entries(PLATFORM_CAPABILITY_PROVIDERS)
.filter(([, p]) => p.edition === 'enterprise' && p.package !== null)
.map(([token, p]) => [token, p.package as string] as const);
// Non-vacuity: hierarchy-security → @objectstack/security-enterprise is the
// standing member; an empty population would pass over nothing.
expect(enterprisePackages.length).toBeGreaterThan(0);
for (const [token, pkg] of enterprisePackages) {
const row = PLATFORM_PLUGIN_WIRED_RUNTIMES[pkg];
expect(row, `enterprise provider '${token}' names '${pkg}', which has no roster row`).toBeTruthy();
expect(row.edition, `edition drift for '${pkg}' between the two rosters`).toBe('enterprise');
}
});

it('when both rosters name one package, they agree on its edition', () => {
// The generalized no-divergence pin, both editions covered: the only fact
// the two rosters can state twice must be stated identically.
for (const [pkg, row] of Object.entries(PLATFORM_PLUGIN_WIRED_RUNTIMES)) {
for (const [token, p] of Object.entries(PLATFORM_CAPABILITY_PROVIDERS)) {
if (p.package === pkg) {
expect(p.edition, `'${token}' and the roster disagree on '${pkg}'`).toBe(row.edition);
}
}
}
});

it('no roster package appears in serve CAPABILITY_PROVIDERS — plugins[]-wired is not requires-resolved by serve', () => {
const servePackages = Object.values(Serve.CAPABILITY_PROVIDERS).map((s) => s.pkg);
for (const pkg of Object.keys(PLATFORM_PLUGIN_WIRED_RUNTIMES)) {
expect(servePackages, `'${pkg}' is loaded by serve's open-edition resolver — it belongs in the token-keyed map`).not.toContain(pkg);
}
});
});
2 changes: 2 additions & 0 deletions packages/spec/api-surface/kernel.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -255,6 +255,7 @@
"PLATFORM_ALWAYS_ON_CAPABILITIES (const)",
"PLATFORM_CAPABILITY_PROVIDERS (const)",
"PLATFORM_CAPABILITY_TOKENS (const)",
"PLATFORM_PLUGIN_WIRED_RUNTIMES (const)",
"PROTOCOL_MAJOR (const)",
"PROTOCOL_VERSION (const)",
"PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS (const)",
Expand All@@ -276,6 +277,7 @@
"PermissionScope (type)",
"PermissionScopeSchema (const)",
"PlatformCapabilityProvider (interface)",
"PlatformPluginWiredRuntime (interface)",
"PluginCapability (type)",
"PluginCapabilityManifest (type)",
"PluginCapabilityManifestParsed (type)",
Expand Down
2 changes: 2 additions & 0 deletions packages/spec/api-surface/root.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,9 @@
"PLATFORM_ALWAYS_ON_CAPABILITIES (const)",
"PLATFORM_CAPABILITY_PROVIDERS (const)",
"PLATFORM_CAPABILITY_TOKENS (const)",
"PLATFORM_PLUGIN_WIRED_RUNTIMES (const)",
"PlatformCapabilityProvider (interface)",
"PlatformPluginWiredRuntime (interface)",
"PluginContext (type)",
"Predicate (type)",
"PredicateInput (type)",
Expand Down
2 changes: 2 additions & 0 deletions packages/spec/export-origins/kernel.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -255,6 +255,7 @@
"PLATFORM_ALWAYS_ON_CAPABILITIES": "src/kernel/platform-capabilities.ts#PLATFORM_ALWAYS_ON_CAPABILITIES (const)",
"PLATFORM_CAPABILITY_PROVIDERS": "src/kernel/platform-capabilities.ts#PLATFORM_CAPABILITY_PROVIDERS (const)",
"PLATFORM_CAPABILITY_TOKENS": "src/kernel/platform-capabilities.ts#PLATFORM_CAPABILITY_TOKENS (const)",
"PLATFORM_PLUGIN_WIRED_RUNTIMES": "src/kernel/platform-capabilities.ts#PLATFORM_PLUGIN_WIRED_RUNTIMES (const)",
"PROTOCOL_MAJOR": "src/kernel/protocol-version.ts#PROTOCOL_MAJOR (const)",
"PROTOCOL_VERSION": "src/kernel/protocol-version.ts#PROTOCOL_VERSION (const)",
"PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS": "src/kernel/public-auth-features.ts#PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS (const)",
Expand All@@ -276,6 +277,7 @@
"PermissionScope": "src/kernel/plugin-security-advanced.zod.ts#PermissionScope (type)",
"PermissionScopeSchema": "src/kernel/plugin-security-advanced.zod.ts#PermissionScopeSchema (const)",
"PlatformCapabilityProvider": "src/kernel/platform-capabilities.ts#PlatformCapabilityProvider (interface)",
"PlatformPluginWiredRuntime": "src/kernel/platform-capabilities.ts#PlatformPluginWiredRuntime (interface)",
"PluginCapability": "src/kernel/plugin-capability.zod.ts#PluginCapability (type)",
"PluginCapabilityManifest": "src/kernel/plugin-capability.zod.ts#PluginCapabilityManifest (type)",
"PluginCapabilityManifestParsed": "src/kernel/plugin-capability.zod.ts#PluginCapabilityManifestParsed (type)",
Expand Down
2 changes: 2 additions & 0 deletions packages/spec/export-origins/root.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,9 @@
"PLATFORM_ALWAYS_ON_CAPABILITIES": "src/kernel/platform-capabilities.ts#PLATFORM_ALWAYS_ON_CAPABILITIES (const)",
"PLATFORM_CAPABILITY_PROVIDERS": "src/kernel/platform-capabilities.ts#PLATFORM_CAPABILITY_PROVIDERS (const)",
"PLATFORM_CAPABILITY_TOKENS": "src/kernel/platform-capabilities.ts#PLATFORM_CAPABILITY_TOKENS (const)",
"PLATFORM_PLUGIN_WIRED_RUNTIMES": "src/kernel/platform-capabilities.ts#PLATFORM_PLUGIN_WIRED_RUNTIMES (const)",
"PlatformCapabilityProvider": "src/kernel/platform-capabilities.ts#PlatformCapabilityProvider (interface)",
"PlatformPluginWiredRuntime": "src/kernel/platform-capabilities.ts#PlatformPluginWiredRuntime (interface)",
"PluginContext": "src/kernel/plugin.zod.ts#PluginContext (type)",
"Predicate": "src/shared/expression.zod.ts#Predicate (type)",
"PredicateInput": "src/shared/expression.zod.ts#PredicateInput (type)",
Expand Down
5 changes: 5 additions & 0 deletions packages/spec/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,12 +199,17 @@ export {
PLATFORM_CAPABILITY_TOKENS,
isKnownPlatformCapability,
PLATFORM_CAPABILITY_PROVIDERS,
// The `plugins[]`-wired out-of-repo runtimes the token-keyed map structurally
// cannot describe (no `requires` token to key a row by) — provenance only,
// never resolution (#10921, #11263).
PLATFORM_PLUGIN_WIRED_RUNTIMES,
// The foundational slate every server-side runtime mounts (cloud#925, #3786) —
// one declaration for `objectstack serve` and cloud's per-tenant runtime alike.
PLATFORM_ALWAYS_ON_CAPABILITIES,
classifyRequiredCapability,
type CapabilityEdition,
type PlatformCapabilityProvider,
type PlatformPluginWiredRuntime,
type CapabilityProviderStatus,
type CapabilityClassification,
} from './kernel/platform-capabilities';
Expand Down
48 changes: 48 additions & 0 deletions packages/spec/src/kernel/platform-capabilities.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import {
PLATFORM_CAPABILITY_TOKENS,
isKnownPlatformCapability,
PLATFORM_CAPABILITY_PROVIDERS,
PLATFORM_PLUGIN_WIRED_RUNTIMES,
PLATFORM_ALWAYS_ON_CAPABILITIES,
classifyRequiredCapability,
} from './platform-capabilities';
Expand DownExpand Up@@ -70,6 +71,53 @@ describe('PLATFORM_CAPABILITY_PROVIDERS', () => {
});
});

// #11263 — the companion roster for `plugins[]`-wired out-of-repo runtimes,
// which the token-keyed provider map structurally cannot describe (no
// `requires` token to key a row by). Shape invariants live here; the
// cross-registry drift pins (edition agreement with the token-keyed map, the
// serve-resolver exclusions) live in the CLI's
// `serve-capability-vocabulary.test.ts` beside the map's own 1:1 pins.
describe('PLATFORM_PLUGIN_WIRED_RUNTIMES (#11263)', () => {
it('is frozen and non-empty', () => {
expect(Object.isFrozen(PLATFORM_PLUGIN_WIRED_RUNTIMES)).toBe(true);
expect(Object.keys(PLATFORM_PLUGIN_WIRED_RUNTIMES).length).toBeGreaterThan(0);
});

it('every key is an @objectstack/ npm package name — the key IS the package', () => {
for (const pkg of Object.keys(PLATFORM_PLUGIN_WIRED_RUNTIMES)) {
expect(pkg).toMatch(/^@objectstack\/[a-z0-9-]+$/);
}
});

it('every row carries a non-empty provenance note — recording it is the roster’s whole job', () => {
for (const [pkg, row] of Object.entries(PLATFORM_PLUGIN_WIRED_RUNTIMES)) {
expect(row.note.trim().length, `empty note for '${pkg}'`).toBeGreaterThan(0);
// The type already excludes 'open'; assert the runtime value too so a
// cast or a JS caller cannot smuggle one in.
expect(['enterprise', 'cloud'], `bad edition for '${pkg}'`).toContain(row.edition);
}
});

it('keys are DISJOINT from the capability vocabulary — package names, never tokens', () => {
// The single-list rule's load-bearing half: this roster must never grow a
// second way to spell a `requires` token. A key that is also a vocabulary
// token would be exactly the divergent second description the provider
// map's header warns against.
for (const pkg of Object.keys(PLATFORM_PLUGIN_WIRED_RUNTIMES)) {
expect(PLATFORM_CAPABILITY_TOKENS).not.toContain(pkg);
}
});

it('no roster package is an open-edition provider package — plugins[]-wired means not requires-resolved as open', () => {
const openPackages = Object.values(PLATFORM_CAPABILITY_PROVIDERS)
.filter((p) => p.edition === 'open')
.map((p) => p.package);
for (const pkg of Object.keys(PLATFORM_PLUGIN_WIRED_RUNTIMES)) {
expect(openPackages).not.toContain(pkg);
}
});
});

describe('classifyRequiredCapability (#3366)', () => {
const allInstalled = () => true;
const noneInstalled = () => false;
Expand Down
82 changes: 82 additions & 0 deletions packages/spec/src/kernel/platform-capabilities.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -117,6 +117,11 @@ export interface PlatformCapabilityProvider {
* A drift test (`serve-capability-vocabulary.test.ts`) asserts this map and the
* vocabulary stay in 1:1 sync, and that every `open`-edition entry agrees with
* the package the serve resolver actually loads — so the two can't diverge.
*
* One population is structurally OUTSIDE this map's key space: an out-of-repo
* runtime wired in via `plugins[]` that backs no `requires` token has no token
* to key a row by. {@link PLATFORM_PLUGIN_WIRED_RUNTIMES} is the sibling roster
* that records those — same provenance shape, keyed by package name.
*/
export const PLATFORM_CAPABILITY_PROVIDERS: Readonly<Record<string, PlatformCapabilityProvider>> =
Object.freeze({
Expand DownExpand Up@@ -168,6 +173,83 @@ export const PLATFORM_CAPABILITY_PROVIDERS: Readonly<Record<string, PlatformCapa
governance: { package: null, edition: 'cloud', note: 'cloud governance tier' },
});

/**
* Provenance row for one out-of-repo platform runtime that reaches the kernel
* through app `plugins[]` wiring (host-app declaration) rather than through a
* `requires` capability token. Same provenance facts as
* {@link PlatformCapabilityProvider} minus `package` (here the package name is
* the KEY, so it cannot be `null` and cannot drift from a duplicate field).
*/
export interface PlatformPluginWiredRuntime {
/**
* Which edition ships the runtime. Never `open` — an open-edition package is
* part of the public framework distribution and is resolved through a
* `requires` token, so it belongs in {@link PLATFORM_CAPABILITY_PROVIDERS},
* not here. The exclusion is type-level so a mis-filed row cannot compile.
*/
readonly edition: Exclude<CapabilityEdition, 'open'>;
/**
* Short human note on where the runtime ships from and how it is loaded.
* Required, not optional: recording that provenance is this roster's entire
* job — a row without it answers nothing.
*/
readonly note: string;
}

/**
* The `plugins[]`-wired out-of-repo runtimes, keyed by npm package name — the
* companion roster to {@link PLATFORM_CAPABILITY_PROVIDERS} for the packages
* whose loading is NOT keyed by a `requires` token (#10921, #11263).
*
* Why it exists: this tree names closed-source `@objectstack/` packages it does
* not build — `serve` prints an install instruction for one of them at
* operators — and until this roster, "is that a real out-of-repo package or a
* fabricated name?" was answerable only by grep-and-judgement. A fabricated
* package name (`@objectstack/framework`) sat next to a real one
* (`@objectstack/organizations`) in published docs for months looking identical
* (#10921). A row here is the machine-readable declaration that the package is
* real and where it ships from.
*
* What this roster deliberately is NOT: a resolution registry. It does not make
* these packages `requires`-resolvable, adds nothing to
* {@link PLATFORM_CAPABILITY_TOKENS}, and does not relate tenancy posture to
* the capability vocabulary. Each runtime's load condition lives in the runtime
* that loads it (`serve` loads `@objectstack/organizations` off the resolved
* tenancy posture, ADR-0105; an app wires `@objectstack/security-enterprise`
* into its own `plugins[]`) — the rows record that fact as prose provenance,
* they do not encode it as a lookup.
*
* On the single-list rule (see {@link PLATFORM_ALWAYS_ON_CAPABILITIES} on why a
* second description nobody checks is a defect): this roster's key space is
* DISJOINT from the token-keyed map — package names, not tokens — and the one
* fact the two can state twice (the edition of a package that also backs a
* token, today `@objectstack/security-enterprise` behind `hierarchy-security`)
* is drift-tested to agree, in `serve-capability-vocabulary.test.ts` alongside
* the map's own 1:1 pins. The same test derives membership the other way too:
* every `enterprise`-edition provider row names a `plugins[]`-wired package (by
* {@link CapabilityEdition}'s own definition), so each must have a row here.
*
* Growing it: a new out-of-repo `plugins[]`-wired runtime adds its row HERE in
* the PR that first names the package at operators or in published docs.
*/
export const PLATFORM_PLUGIN_WIRED_RUNTIMES: Readonly<Record<string, PlatformPluginWiredRuntime>> =
Object.freeze({
'@objectstack/organizations': {
edition: 'enterprise',
note:
'closed-source multi-org runtime (ADR-0105); `serve` loads it from the host app ' +
'when the resolved tenancy posture is `group`/`isolated` — no `requires` token; ' +
'not on the public npm registry, distributed with an enterprise/cloud subscription',
},
'@objectstack/security-enterprise': {
edition: 'enterprise',
note:
'closed-source enterprise security runtime; the app wires it in via `plugins[]`. ' +
'Also backs the `hierarchy-security` token above (ADR-0057) — the drift test holds ' +
'both rows to one edition',
},
});

/**
* The foundational capability slate: what every server-side runtime is expected
* to mount whether or not an app names it in `requires`.
Expand Down
Loading