Skip to content

Make optional-plugin loading intent-driven: fail-fast on declared-but-missing, drop presence-based auto-enable #1597

Description

@xuyushun441-sys

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:

  1. "is the package installed?" — presence detection via try-import
  2. "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.
  • The single catch can't distinguish "intentionally not installed" (expected, skip) from "plugin actually crashed at startup" (should error). String-matching on the error message is exactly what caused the false-alarm bug in perf(build): OS_SKIP_DTS gating + fix optional AI plugin "Cannot find package" skip #1595.

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 existing requires: [...] capability spec), and resolve at startup:

app declarespackage presentplatform behavior
requiredyesload
requirednothrow — fail startup (fail-fast)
auto / unsetyesload (opt-in convenience)
auto / unsetnoskip silently — no speculative import
offnever 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.

Scope / risk

  • Touches the plugin-loading contract in serve.ts and 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.
  • 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions