Skip to content

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

@xuyushun441-sys

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 } = await importFromHost('@objectstack/service-ai');
    await kernel.use(new AIServicePlugin());
  } 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 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.

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions