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
Follow-up to #1595 (which fixed the Cannot find package false alarm) and tracking issue objectstack-ai/cloud#107. That PR was a minimal, behavior-preserving bug fix. This issue captures the deeper design problem it exposed.
Problem
os serve (packages/cli/src/commands/serve.ts) auto-loads optional service plugins (@objectstack/service-ai, @objectstack/service-ai-studio) by speculatively importing the package and swallowing the error when it's absent:
if(!hasAIPlugin&&tierEnabled('ai')){try{const{ AIServicePlugin }=awaitimportFromHost('@objectstack/service-ai');awaitkernel.use(newAIServicePlugin());}catch(err){// silently skip if the package isn't installed}}
Two distinct things are conflated:
"is the package installed?" — presence detection via try-import
"does this app actually want AI?" — intent
The trigger today is tierEnabled('ai') (does the license tier allow AI) plus package presence — not an explicit declaration that this app requires AI. Consequences:
Presence ≠ intent. Installing a dependency silently auto-enables a runtime feature. The decision to enable AI should be explicit and resolved deterministically by the platform at startup, not be a side effect of which packages happen to be bundled.
Nothing ever fails loud. Even the explicit requires: [...] capability path only console.warns on a missing package (serve.ts:1657), it does not throw. So an app that genuinely requires AI but ships without the package boots "successfully" in a broken state.
apps/cloud (control-plane host) intentionally does not ship AI Studio and must boot clean. So we cannot globally flip "missing package → error" — that would break the very host #107 was fixing. The current code can't tell "intentionally absent" from "required but absent", which is why #1595 had to keep silent-skip.
Proposed design — intent-driven, three states
Introduce an explicit intent declaration (e.g. per-app config ai: 'required' | 'auto' | 'off', or via the existing requires: [...] capability spec), and resolve at startup:
app declares
package present
platform behavior
required
yes
load
required
no
throw — fail startup (fail-fast)
auto / unset
yes
load (opt-in convenience)
auto / unset
no
skip silently — no speculative import
off
—
never load
Key changes:
Add an explicit "required" notion so declared-but-missing fails loud instead of warning/swallowing.
Stop using "package happens to be installed" as the enable signal; drive enablement from declared intent (tier gating remains an orthogonal deny check).
For the not-declared case, skip without an optimistic import-and-catch, removing the whole class of "message-matching to tell missing from crashed" bugs.
Must keep apps/cloud booting clean (AI Studio simply not declared → skipped, no error).
Applies to both the AIService and AIStudio guards, and should reconcile the requires: [...] capability path (serve.ts:1652-1658) so a declared-required capability also fails fast.
Follow-up to #1595 (which fixed the
Cannot find packagefalse alarm) and tracking issue objectstack-ai/cloud#107. That PR was a minimal, behavior-preserving bug fix. This issue captures the deeper design problem it exposed.Problem
os serve(packages/cli/src/commands/serve.ts) auto-loads optional service plugins (@objectstack/service-ai,@objectstack/service-ai-studio) by speculatively importing the package and swallowing the error when it's absent:Two distinct things are conflated:
The trigger today is
tierEnabled('ai')(does the license tier allow AI) plus package presence — not an explicit declaration that this app requires AI. Consequences:requires: [...]capability path onlyconsole.warns on a missing package (serve.ts:1657), it does not throw. So an app that genuinely requires AI but ships without the package boots "successfully" in a broken state.Constraint that must be preserved
apps/cloud(control-plane host) intentionally does not ship AI Studio and must boot clean. So we cannot globally flip "missing package → error" — that would break the very host #107 was fixing. The current code can't tell "intentionally absent" from "required but absent", which is why #1595 had to keep silent-skip.Proposed design — intent-driven, three states
Introduce an explicit intent declaration (e.g. per-app config
ai: 'required' | 'auto' | 'off', or via the existingrequires: [...]capability spec), and resolve at startup:Key changes:
Scope / risk
serve.tsand interacts with multi-tenant tier logic — larger and riskier than the perf(build): OS_SKIP_DTS gating + fix optional AI plugin "Cannot find package" skip #1595 string-match fix, hence a separate issue.apps/cloudbooting clean (AI Studio simply not declared → skipped, no error).requires: [...]capability path (serve.ts:1652-1658) so a declared-required capability also fails fast.