You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Unify optional-plugin loading: framework serve.ts and cloud objectos-runtime are parallel implementations with divergent capability tokens (ai-studio vs aiStudio) #3265
Surfaced while finishing #1597 (#3228) and bumping cloud's framework pin (objectstack-ai/cloud#857). #1597 made framework's standaloneos serve / os start path (packages/cli/src/commands/serve.ts) load optional service plugins by declared intent — requires → required (fail-fast if the provider package is missing), declared-as-dep → auto (best-effort), otherwise skip with no speculative import; tier gating stays an orthogonal deny.
While verifying the cloud side, I found the multi-tenant control plane does not boot through serve.ts — it uses its own packages/objectos-runtime/src/capability-loader.ts, which already implements the same intent model independently. So there are now two implementations of one contract.
Finding 1 — two parallel implementations of the same contract
Cloud — objectos-runtime/capability-loader.ts: its own CAPABILITY_PROVIDERS map with optional flags, failedRequired + probeRequiredCapabilities (loud on required-missing, quiet on optional-missing), and it already classifies the ESM Cannot find package shape (the perf(build): OS_SKIP_DTS gating + fix optional AI plugin "Cannot find package" skip #1595 fix).
They encode the same rules but can drift: #1597 had to bring framework up to what cloud already did. A future fix to one won't automatically reach the other — declared ≠ enforced across the repo boundary (Prime Directive #10).
Finding 2 — divergent capability tokens (ai-studio vs aiStudio)
Cloud uses camelCase: defaultRequires: ['ai','aiStudio',…] (apps/objectos/objectstack.config.ts), requires: ['aiStudio'] (capability-loader.test.ts), and the provider key aiStudio in capability-loader.ts.
So requires: ['ai-studio'] is honored by framework's serve path but ignored by cloud's runtime, and ['aiStudio'] vice-versa. For an app author moving between the standalone and cloud runtimes, the same declaration doesn't mean the same thing — a one-contract violation (Prime Directive #12). Note that unknown requires tokens are silently ignored on both sides, so the mismatch fails silently rather than loudly.
Not a runtime bug today
Both loaders currently behave correctly on their own path (framework after #3228; cloud already). apps/cloud still boots clean (aiStudio is optional: true → quiet skip, cloud#107). This is a maintainability / contract-consistency concern, not an outage — filed rather than silently expanded into #1597's scope.
Proposed directions (decision needed)
Extract a shared loader (preferred long-term): lift the capability-provider registry + required/optional resolution into a framework-owned module consumed by both serve.ts and objectos-runtime, so there is structurally one implementation. Cloud's version is the more complete starting point (optional flags, failedRequired, probeRequiredCapabilities).
At minimum, reconcile the token vocabulary: pick one canonical spelling for the AI-Studio capability (and any other multiword tokens), alias the other through a deprecation path, and validate requires tokens against a shared allowlist so an unknown/misspelled token is rejected at authoring instead of silently ignored.
Or document a deliberate split: if standalone vs multi-tenant genuinely need different loading, document the boundary + token mapping so it reads as intentional, not accidental drift.
Context
Surfaced while finishing #1597 (#3228) and bumping cloud's framework pin (objectstack-ai/cloud#857). #1597 made framework's standalone
os serve/os startpath (packages/cli/src/commands/serve.ts) load optional service plugins by declared intent —requires→ required (fail-fast if the provider package is missing), declared-as-dep → auto (best-effort), otherwise skip with no speculative import; tier gating stays an orthogonal deny.While verifying the cloud side, I found the multi-tenant control plane does not boot through
serve.ts— it uses its ownpackages/objectos-runtime/src/capability-loader.ts, which already implements the same intent model independently. So there are now two implementations of one contract.Finding 1 — two parallel implementations of the same contract
serve.ts:CAPABILITY_PROVIDERS,CAPABILITY_TO_TIER, the AI block, and therequiresresolver loop (now intent-driven after feat(cli): make optional-plugin loading intent-driven, fail-fast on declared-but-missing (#1597) #3228).objectos-runtime/capability-loader.ts: its ownCAPABILITY_PROVIDERSmap withoptionalflags,failedRequired+probeRequiredCapabilities(loud on required-missing, quiet on optional-missing), and it already classifies the ESMCannot find packageshape (the perf(build): OS_SKIP_DTS gating + fix optional AI plugin "Cannot find package" skip #1595 fix).They encode the same rules but can drift: #1597 had to bring framework up to what cloud already did. A future fix to one won't automatically reach the other — declared ≠ enforced across the repo boundary (Prime Directive #10).
Finding 2 — divergent capability tokens (
ai-studiovsaiStudio)requiresuses kebab-case; feat(cli): make optional-plugin loading intent-driven, fail-fast on declared-but-missing (#1597) #3228 addedai-studio(matchingpinyin-search,hierarchy-security).defaultRequires: ['ai','aiStudio',…](apps/objectos/objectstack.config.ts),requires: ['aiStudio'](capability-loader.test.ts), and the provider keyaiStudioincapability-loader.ts.So
requires: ['ai-studio']is honored by framework's serve path but ignored by cloud's runtime, and['aiStudio']vice-versa. For an app author moving between the standalone and cloud runtimes, the same declaration doesn't mean the same thing — a one-contract violation (Prime Directive #12). Note that unknownrequirestokens are silently ignored on both sides, so the mismatch fails silently rather than loudly.Not a runtime bug today
Both loaders currently behave correctly on their own path (framework after #3228; cloud already). apps/cloud still boots clean (
aiStudioisoptional: true→ quiet skip, cloud#107). This is a maintainability / contract-consistency concern, not an outage — filed rather than silently expanded into #1597's scope.Proposed directions (decision needed)
serve.tsandobjectos-runtime, so there is structurally one implementation. Cloud's version is the more complete starting point (optional flags,failedRequired,probeRequiredCapabilities).requirestokens against a shared allowlist so an unknown/misspelled token is rejected at authoring instead of silently ignored.Evidence
packages/cli/src/commands/serve.ts(post-feat(cli): make optional-plugin loading intent-driven, fail-fast on declared-but-missing (#1597) #3228):CAPABILITY_TO_TIER['ai-studio'] = 'ai',Serve.resolveOptionalPluginLoad(...), thefor (const cap of requires)resolver loop.packages/objectos-runtime/src/capability-loader.ts:CAPABILITY_PROVIDERS.aiStudio { optional: true },failedRequired,probeRequiredCapabilities, ESMCannot find packageclassification.apps/objectos/objectstack.config.ts:defaultRequires: ['ai', 'aiStudio', 'analytics', 'automation', 'triggers'].