Skip to content
Merged
22 changes: 22 additions & 0 deletions .changeset/28534-install-local-member-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@objectstack/cloud-connection": minor
---

fix(cloud-connection)!: every member of an installed bundle is recorded as installed, whatever its entry's `source` states

`POST /api/v1/marketplace/install-local` (and `os package install`) now registers every member of a `packages[]` bundle as `source: 'marketplace'`, at the install and at every restart's rehydrate. The `manifest` service stamps an entry's own `source` over the door's, so a member whose entry said `'code'` was recorded as the host's own code, and readers of a package's origin treated it so: the connector materializer let its package-relative file refs read the host's directory.

Clause-②: no (narrowing) — at the install-local door a `packages[]` entry's stated `source` no longer decides the member's recorded origin: a member stating `'code'` is recorded `'marketplace'`, as the door states. Nothing is widened.

Metadata-protocol: narrowed (an installed bundle member's `source: 'code'` is no longer honoured at the install door)

<!-- adr-0087: not-required (no-migration-prescription) No key is added, removed or renamed and no body is refused: the entry's `source` is still accepted, and still honoured where it means something, the boot of a merged artifact; at the install door the door's own statement wins, and it is the origin of every package this door registers. There is nothing for an author or a migration to rewrite. -->

**BREAKING** semantic narrowing at the install-local door (`@objectstack/cloud-connection`), shipped as `minor`.

- **Before.** A bundle installed through the door whose second member's entry stated `source: 'code'` recorded that member as `'code'`, and a ledger entry holding such a bundle did so again at every restart.
- **Now.** Both members are recorded `'marketplace'`, at the install and at the rehydrate, so a ledger entry written earlier is recorded the same way after the next restart.
- **Unchanged.** The `manifest` service's precedence for a boot that registers a merged artifact stating no source of its own: each entry's statement still stands there. An entry stating `'marketplace'` or nothing is handed through as received.
- **Who sees it.** A bundle installed through this door whose entry claimed `'code'`. Its members now read as installed everywhere a package's origin is read.

**The remedy.** None is owed. Drop the entry's `source` if it was written for this door: the door states the origin of what it installs.
22 changes: 22 additions & 0 deletions .changeset/28534-installed-package-file-ref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@objectstack/service-automation": patch
---

fix(service-automation)!: an installed package's connector entry never reads the host's files

A connector entry declared by a package that was installed into the runtime (`os package install`, `POST /api/v1/marketplace/install-local`; the package registry's `source: 'marketplace'`) was handed `loadPackageFile` / `resolvePackagePath` anchored at the HOST's root, whichever package declared it. An installed package has no directory on the runtime it is installed into, so a relative ref resolved against another package's files: an installed `openapi` connector naming `./src/system/connectors/status-openapi.json` materialized `ready` with the showcase's own document, and a stdio `mcp` connector naming `./scripts/mcp-fixture.mjs` spawned the showcase's own fixture in the showcase's directory.

Clause-②: no (narrowing) — a connector entry that is not the host's own (an installed package's, or one whose stamp no registration records) is refused its package-relative file and path refs, which it was handed before against the host's root. Nothing is widened.

Metadata-protocol: narrowed (an installed package's connector `providerConfig` may no longer name a package-relative file, and an installed package's stdio `mcp` transport is refused)

<!-- adr-0087: not-required (no-migration-prescription) No key is added, removed or renamed and the stored shape is unchanged: the same `providerConfig` is accepted, and what is refused is the host's root being read on the installed package's behalf. No conversion can carry a file the install never held; the refusal names the entry, its package, the ref and the remedy, and a current `os build` already writes the document into the artifact. -->

**BREAKING** behaviour narrowing for installed packages' connectors (`@objectstack/service-automation`), shipped as `patch` beside the `minor` of `@objectstack/objectql` and `@objectstack/cloud-connection` that carries the same declaration.

- **Before.** An installed package's entry naming a package-relative file or location read the host's own file, or ran a stdio process in the host's directory.
- **Now.** Both members refuse every ref for such an entry, naming the entry, its package and the ref: a configuration fault, so the instance is not materialized and the reconcile logs the refusal. Whose entry it is comes only from what a registering door recorded (the package row's `source`, or the residual owner's door for an artifact residual), never from the entry's own words: an entry stamped with a package no registration records is refused its refs as well.
- **Unchanged.** An entry carrying its document inline or as an `http(s)` URL, and every entry of the host's own stack, which keeps reading its files as before.
- **Who sees it.** An installed package whose connector names a package-relative `openapi` spec, or declares a stdio `mcp` transport.

**The remedy.** Rebuild the package with a current `os build`, which carries an `openapi` spec file into the artifact, or inline the document; an installed package reaches an MCP server over `http`, since a stdio transport needs a working directory inside the package.
24 changes: 24 additions & 0 deletions .changeset/28534-item-owner-stamp-refused.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@objectstack/objectql": minor
---

fix(objectql)!: a collection item never names its own owning package — package registration refuses a body `_packageId` other than the registering package's

`ObjectQL.registerApp` (a manifest and its nested plugins, seed datasets included) and the `manifest` service's ADR-0130 residual registration now refuse a collection item whose body already carries a `_packageId` other than the package registering it: `VALIDATION_ERROR` / 400 with `issues[]` at `_packageId`, before the item is filed, the envelope of the permission-set owner refusal in the same loop. The registration stamps `_packageId` only while it is unset, so a body could wear another package's id (an installed connector naming the host's own package, or an id nobody holds) and every reader of an item's owner, the connector materializer deciding whose directory a file ref resolves against among them, read the body's claim about itself.

Clause-②: no (narrowing) — package registration refuses a collection item whose body `_packageId` names a package other than the one registering it, which it registered before with the body's stamp kept. Nothing is widened.

Metadata-protocol: narrowed (a collection item's `_packageId`, declared by `MetadataProtectionFields`, may no longer name a package other than the one that registers it)

<!-- adr-0087: not-required (no-migration-prescription) No key is added, removed or renamed, and no value a legitimate producer writes is refused: the registration is the only writer of `_packageId` (`os build` writes none, the package export strips every underscore key, the artifact loader stamps with the very id the registration keys on), so a foreign stamp exists only in a body somebody wrote by hand to claim another owner. A D2 conversion could not tell such a stamp from an agreeing one without the registration that judges it, and the refusal names the item, both ids and the remedy. -->

**BREAKING** accept-set narrowing at package registration (`@objectstack/objectql`), shipped as `minor`.

- **Before.** A manifest `com.acme.app` whose connector carried `_packageId: 'com.acme.host'` registered the connector with that stamp kept, with no refusal and no warning; so did a nested plugin's item, a seed dataset and a multi-package artifact's residual item. `null` and `''` were kept too.
- **Now.** The registration throws `400 VALIDATION_ERROR` with `issues[]` at `_packageId`, naming the item, the stamp and the package registering it, before anything of the item is filed. Through `POST /api/v1/marketplace/install-local` an inline install answers `422 PLUGIN_REGISTER_FAILED` with that message and persists nothing.
- **Unchanged.** An absent `_packageId` and one equal to the registering package register as before. An item object that an earlier registration stamped in place (a module constant handed to a second registration) registers as it always has: only a stamp the object arrives with is judged. Objects (`objects`) are not judged by this seam.
- **Who sees it.** Whoever hand-writes `_packageId` on a manifest item naming another package, which no build, export or install writes.

**The remedy.** Remove `_packageId` from the item. The package an item belongs to is the one that registers it, stamped by the registration.

The `manifest` service also records which door registered each multi-package artifact's residual owner (`ObjectQL.recordResidualOwner` / `residualOwnerSource`: `'code'` for the boot, `'marketplace'` for an install), the only record of that id's origin, since the residual has no package row.
1 change: 1 addition & 0 deletions content/docs/automation/connectors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,7 @@ retry):
| The upstream refuses the credential (`401` / `403`), or an `mcp` server sent a static credential does not list a tool the instance's `include` names | **Not fatal**: the instance registers **degraded**. A stored credential is marked `rejected` and waits until it is set again (or a restart); an environment or vault credential is retried on the backoff |
| Instance `name` collides with a plugin-registered connector | **Hard boot error** — there is no silent precedence between the two worlds |
| stdio transport without host opt-in | **Hard boot error** with the `declarativeStdio` fix inline — a security rejection is never retried |
| An entry of an **installed** package (`os package install`, `POST /api/v1/marketplace/install-local`) names a package-relative file — an `openapi` `spec` path — or needs a location inside its package, as a stdio `mcp` transport's working directory does | Configuration fault, **never resolved against the host's directory**: an installed package has no directory on the runtime it was installed into, and the host's is not its own. The instance is not materialized, and the reconcile's error names the entry, its package and the ref. Rebuild the package with a current `os build`, which carries an `openapi` spec file into the artifact, or inline the document; reach an MCP server from an installed package over `http`. Whether an entry is the host's is read from what the install recorded, never from the package's own words: an entry carrying a `_packageId` other than its own package's is refused at the install, every member of a `packages[]` bundle is recorded as installed whatever its `source` says, and an entry naming a package no registration records is refused its ref too. The host's own entries keep reading their files |
| MCP server unreachable / `tools/list` fails; remote spec URL unreachable or transiently failing (408/429/5xx) | **Not fatal**: the instance registers **degraded** (visible on `GET /api/v1/automation/connectors` with the reason), dispatches fail with that reason, and the platform retries on a 5s→5min backoff — recovery is automatic |
| An `mcp` instance's `toolsSnapshot` does not match what the server serves (a pinned tool's description or `inputSchema` changed, or a pinned tool is no longer listed) | **Not fatal**: the instance registers **degraded** with no actions, its reason naming each tool and the digest it now serves, and the logs say "serves a tool definition that differs from its toolsSnapshot" (`CONNECTOR_UPSTREAM_CHANGED`); it is re-checked on the same backoff and recovers once the server serves the pinned definitions again — or set the pin to the reviewed digest |
| A retry of a degraded instance reaches the upstream and meets a configuration fault (an `include` naming a tool or operation the upstream does not offer; on an `mcp` instance that sends a static credential, the credential row above applies instead) | **Not fatal**: the instance stays **degraded**, now with the configuration reason, and is **not retried** — dispatches fail saying to edit its declaration; an edit of the entry (or a restart) re-attempts it |
Expand Down
Loading
Loading