Skip to content

fix(service-settings): say the AI runtime is cloud-only instead of telling operators to mount it - #11467

Merged
os-sam merged 2 commits into
mainfrom
claude/issue-11318-ai-manifest-cloud-only-boundary
Aug 23, 2026
Merged

fix(service-settings): say the AI runtime is cloud-only instead of telling operators to mount it#11467
os-sam merged 2 commits into
mainfrom
claude/issue-11318-ai-manifest-cloud-only-boundary

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#11318

What was wrong

packages/services/service-settings/src/manifests/ai.manifest.ts's ai/test
fallback handler told an operator, on all three real-provider branches, to
"Mount @objectstack/service-ai to exercise live calls." This platform's own
capability roster — PLATFORM_CAPABILITY_PROVIDERS.ai in
@objectstack/spec/kernel (packages/spec/src/kernel/platform-capabilities.ts:127-131)
— declares that capability edition: 'cloud', which CapabilityEdition's
docblock defines as "realized only by a cloud runtime tier; there is no
installable version in the open edition
." The instruction was not merely
unhelpful — it was impossible to follow.

Worse than a 404: the package name still resolves on the public npm registry
(@objectstack/service-ai, 57 versions, latest 10.3.0), entirely below the
11.3.0 cut the roster's own note names. An operator following the old
instruction installs a seven-major-old runtime that pins a second
@objectstack/spec beside this repo's.

The fix

The instruction is kept — an operator may genuinely have a cloud tier —
and now carries the boundary the roster already records, read from the roster
(package + note, verbatim) rather than hand-written a fourth time, matching
how packages/cli's capability preflight already surfaces the same note
(PR #11266 precedent). The three provider branches (Vercel AI Gateway,
Cloudflare AI Gateway, plain SDK provider) stay distinct; only the shared
trailing sentence changes.

The embedder hint at a separate site in the same file
(@objectstack/embedder-openai) is deliberately untouched — that package is
built in this repo, so its mount instruction is followable as written. Pinned
by a contrast test so a future edit cannot blanket-reword every hint in the
file.

Where the boundary was verified

Read directly from the roster, not from the card's paraphrase:

// packages/spec/src/kernel/platform-capabilities.ts:127-131ai: {package: '@objectstack/service-ai',edition: 'cloud',note: 'cloud-only since 11.3.0 / ADR-0025',},

CapabilityEdition's docblock: 'cloud' — realized only by a cloud runtime tier; there is **no installable version in the open edition**.

The new message interpolates ai.package and ai.note verbatim and states
"no open-edition version to install" — agreeing with the roster's own words
rather than a hand-typed paraphrase.

Consumer check (Clause-②)

SettingsActionResult.message (packages/spec/src/system/settings-manifest.zod.ts)
is documented as a Toast message — free-form prose a human reads in the
Settings → AI panel after pressing "Test connection." ok and severity are
the fields any caller could switch on, and neither changes in this diff. No
caller in this repo (server or client) parses message's content
programmatically. This is a message, not a consumed contract — Clause-②
grades no.

Tests

packages/services/service-settings/src/manifests/ai.manifest.test.ts — new
describe block, 5 cases:

  • the roster still declares ai with package/edition: 'cloud'/note (the
    premise the rest rests on)
  • each of the three real-provider branches (gateway, Cloudflare, plain SDK)
    states the boundary: message contains ai.package and ai.note verbatim,
    matches /no open-edition version to install/, and does not match the
    old bare Mount @objectstack/service-ai to exercise live calls. string
  • counter-direction, load-bearing: the embedder hint (a capability that
    is operator-mountable) still gets its plain mount line, unedited — pins
    that this fix narrows to the cloud-only site and does not strip every mount
    hint in the file

Checks run (this worktree, final commit 2e4f999b19)

  • pnpm --filter '@objectstack/service-settings^...' build — dependency
    closure (spec, metadata-protocol, objectql, …) — pass, VERDICT command-exit 0
  • pnpm --filter @objectstack/service-settings test — 27 test files / 488
    tests pass (verbose run on ai.manifest.test.ts alone: 25/25, all 5 new
    cases named and green — VERDICT command-exit 0)
  • pnpm --filter @objectstack/service-settings typecheck (tsc --noEmit) —
    pass, VERDICT command-exit 0, 0 error TS lines in output
  • Gate union (node scripts/pm/dispatch-gates.mjs, no hand-supplied paths) —
    16 of 18 named gates run and green: check:changeset-gate-self-tests,
    check:objectui-changeset, check:published-files, check:slot-lookup,
    check:test-source-alias, check:type-source-resolution,
    check:type-check-coverage, check:engine-double-contract,
    check:cross-package-test-inputs, check:where-matcher,
    check-adr-0087-registration, check-changeset-no-major,
    check-ci-filter-parity, check-empty-changeset,
    check-plugin-teardown-shape, docs-audit/check-affected-docs (no drift —
    service-settings/ai.manifest do not appear in its UNREACHABLE rows,
    and this repo's own docs already state the cloud-only boundary correctly:
    content/docs/kernel/services-checklist.mdx, skills/objectstack-ai/SKILL.md).
    check:query-options-erasure and check:type-check-debt (its --re-measure
    closure build) are NOT MEASURED locally — both are whole-packages/**
    ESLint/tsc sweeps, and under this container's contention today (6+ parallel
    agents queued on the shared verify lock, one holding a full-workspace
    turbo build) the erasure gate's real pass was killed by SIGTERM twice —
    once at a 5-minute foreground cap, once 69s after acquiring the shared lock.
    Declared narrowing, not a skip: CI runs the full farm regardless.

Generated by Claude Code

…lling operators to mount it
The `ai/test` fallback handler answered all three real-provider branches with
"Mount @objectstack/service-ai to exercise live calls", while this platform's
own roster — PLATFORM_CAPABILITY_PROVIDERS.ai in @objectstack/spec/kernel —
declares edition: 'cloud', defined as "no installable version in the open
edition". The instruction is kept and gains the boundary it was missing.
The sentence is read from the roster (package + note verbatim) rather than
hand-written a fourth time, matching how packages/cli's capability preflight
already surfaces the same note. The three provider prefixes stay distinct;
only the shared trailing sentence converges.
Fixes#11318
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 7 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8542bd457765444626023db6ffeb6f8105f7f83epackageMentionDocs.

Which tree this was computed on

This run read content/docs from 66eb82b4ec67c3fc3b281cb9b2a3aa6dca1bb80c — the merge of head 2e4f999b19408712b58bee77e2e27a7d1fc86a39 into base 8542bd457765444626023db6ffeb6f8105f7f83e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 66eb82b4ec67c3fc3b281cb9b2a3aa6dca1bb80c && git checkout 66eb82b4ec67c3fc3b281cb9b2a3aa6dca1bb80c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8542bd457765444626023db6ffeb6f8105f7f83e 2e4f999b19408712b58bee77e2e27a7d1fc86a39 && git checkout -B drift-repro 8542bd457765444626023db6ffeb6f8105f7f83e && git merge --no-ff 2e4f999b19408712b58bee77e2e27a7d1fc86a39
node scripts/docs-audit/affected-docs.mjs --json 8542bd457765444626023db6ffeb6f8105f7f83e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-sam@claude