diff --git a/docs/adr/0093-tenancy-mode-and-membership-lifecycle.md b/docs/adr/0093-tenancy-mode-and-membership-lifecycle.md index 002aba2c3..628858cb8 100644 --- a/docs/adr/0093-tenancy-mode-and-membership-lifecycle.md +++ b/docs/adr/0093-tenancy-mode-and-membership-lifecycle.md @@ -1,6 +1,6 @@ # ADR-0093: Tenancy mode as a first-class capability, and a single owner for the user→membership lifecycle -- **Status:** Proposed (implementation in progress) +- **Status:** Accepted (2026-07-13) — implemented: `tenancy` kernel service (`plugin-auth/src/tenancy-service.ts`), membership reconciler (`reconcile-membership.ts`), locked by `dogfood/test/membership-reconciler.dogfood.test.ts` - **Date:** 2026-07-13 - **Deciders:** ObjectStack Protocol Architects - **Implementation:** #2882 (Phase 0 — tactical create-user bind, merged) → this PR (Phases 1–3 — `tenancy` service, fail-fast boot guard, membership reconciler, consumer migration, backfill, docs). One revision from the original plan, ratified in D2: the endpoint-level create-user bind **delegates to the shared reconciler** (one implementation, two call sites) instead of being deleted. Runtime verification confirmed the hook fires for `admin.createUser`, but better-auth *defers* `user.create.after` post-commit (#1881), so the endpoint keeps its delegated call to report `organizationId` / `membershipCreated` deterministically in its response. Cloud-host semantics (personal-org hook precedence, multi-org non-binding, D5 blast radius) verified against `objectstack-ai/cloud` — see D2/D3/D5. diff --git a/docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md b/docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md index 6a3a3bd55..a9283cab6 100644 --- a/docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md +++ b/docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md @@ -1,6 +1,6 @@ # ADR-0095: Authorization Kernel Chain — Tenant Isolation as Layer 0, a Monotonic Posture Ladder, Capability-Derived Posture -**Status**: Proposed (2026-07-14) +**Status**: Accepted (2026-07-14) — implemented: D1 tenant Layer 0 (`plugin-security/src/tenant-layer.ts`), D2/D3 posture ladder (`core/src/security/posture-ladder.ts`), locked by `authz-matrix-gate.test.ts` + `posture-ladder.test.ts` (#2920 B1–B4) **Deciders**: ObjectStack Protocol Architects **Builds on**: [ADR-0002](./0002-environment-database-isolation.md) (environment-per-database), [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (scope depth), [ADR-0066](./0066-unified-authorization-model.md) (unified authz model, superuser bypass), [ADR-0090](./0090-permission-model-v2-concept-convergence.md) (permission model v2), [ADR-0093](./0093-tenancy-mode-and-membership-lifecycle.md) (tenancy service) **Scopes**: the "cross-tenant RLS does not exist here" claim in [ADR-0073](./0073-automation-execution-identity.md) (see Context) diff --git a/docs/adr/0096-declarative-connector-instances.md b/docs/adr/0097-declarative-connector-instances.md similarity index 99% rename from docs/adr/0096-declarative-connector-instances.md rename to docs/adr/0097-declarative-connector-instances.md index d86c99d61..d4e44e571 100644 --- a/docs/adr/0096-declarative-connector-instances.md +++ b/docs/adr/0097-declarative-connector-instances.md @@ -1,4 +1,4 @@ -# ADR-0096: Declarative Connector Instances — Provider-Bound `connectors:` Entries Materialized by Generic Executors +# ADR-0097: Declarative Connector Instances — Provider-Bound `connectors:` Entries Materialized by Generic Executors **Status**: Accepted (2026-07-15) — implemented in framework#2977 **Deciders**: ObjectStack Protocol Architects diff --git a/docs/adr/PRIORITIZATION.md b/docs/adr/PRIORITIZATION.md index 8497aa741..ca5e09231 100644 --- a/docs/adr/PRIORITIZATION.md +++ b/docs/adr/PRIORITIZATION.md @@ -1,5 +1,17 @@ # ADR Prioritization Review +> ⚠️ **STALE (noted 2026-07-16):** this review predates ADR-0050 onward — the directory now +> reaches ADR-0097, so everything from 0050 up is outside its scope, and several statuses it +> records for 0001–0049 have since moved. Treat it as a historical snapshot, not current +> state. The `Status:` header of each ADR file (kept current per the hygiene rule below) is +> the authority; this document is retained for its method and its ranked-plan rationale. +> +> **Status-hygiene rule (adopted 2026-07-16):** when a PR lands the implementation of an ADR, +> the same PR (or an immediate follow-up) must flip that ADR's `Status:` line to +> `Accepted — implemented` with one line of evidence (key file + test). An ADR left +> `Proposed` after its code ships misleads every later reader — this is the same +> no-silently-stale discipline as ADR-0078, applied to the ADRs themselves. + > **Date:** 2026-06-12 > **Scope:** All 49 ADRs in `docs/adr/`, cross-checked against actual code on `main`. > **Method:** Every unfinished ADR was read in full and its `Status:` header was diff --git a/examples/app-showcase/objectstack.config.ts b/examples/app-showcase/objectstack.config.ts index 1c56291e2..de342542e 100644 --- a/examples/app-showcase/objectstack.config.ts +++ b/examples/app-showcase/objectstack.config.ts @@ -109,7 +109,7 @@ export default defineStack({ // • slack → registered so TaskCompletedSlackFlow resolves its connector; // live posting needs a real bot token (set SLACK_BOT_TOKEN). // • openapi → option-less: contributes only the `openapi` provider factory - // (ADR-0096), which materializes the StatusOpenApiConnector + // (ADR-0097), which materializes the StatusOpenApiConnector // declarative instance below — its OpenAPI document is a // package-relative FILE PATH read at boot (#3016). plugins: [ @@ -197,7 +197,7 @@ export default defineStack({ // Declarative REST endpoints (object_operation + flow) — the metadata // counterpart of the code-mounted recalc endpoint (see src/system/apis/). apis: allApis, - // Declarative `connectors:` — both kinds (ADR-0096): provider-bound + // Declarative `connectors:` — both kinds (ADR-0097): provider-bound // INSTANCES (StatusApiConnector via `rest`; StatusOpenApiConnector via // `openapi` with a package-relative file-path spec, #3016) materialized into // live, dispatchable connectors at boot, plus a CATALOG DESCRIPTOR diff --git a/examples/app-showcase/src/automation/flows/index.ts b/examples/app-showcase/src/automation/flows/index.ts index dc74ccc8f..08cc03ebc 100644 --- a/examples/app-showcase/src/automation/flows/index.ts +++ b/examples/app-showcase/src/automation/flows/index.ts @@ -395,14 +395,14 @@ export const TaskCompletedRestPingFlow = defineFlow({ }); /** - * Declarative Connector Ping — the worked ADR-0096 example: a `connector_action` + * Declarative Connector Ping — the worked ADR-0097 example: a `connector_action` * dispatching a **provider-bound declarative connector instance**. * * Where {@link TaskCompletedRestPingFlow} targets the `rest` connector a *plugin* * registered (ConnectorRestPlugin, ADR-0018 §Addendum), this flow targets * `showcase_status_api` — a connector declared as pure metadata in * src/system/connectors/ and *materialized* into the registry at boot by the - * `rest` generic executor (ADR-0096). Nothing registered it in code: the + * `rest` generic executor (ADR-0097). Nothing registered it in code: the * `connectors:` entry named `provider: 'rest'`, and the automation service turned * it into a live connector. On task creation the flow issues `GET /api/v1/health` * through it; the call and its `{ status: 'ok' }` response are captured on the @@ -410,7 +410,7 @@ export const TaskCompletedRestPingFlow = defineFlow({ */ export const ShowcaseDeclarativeConnectorPingFlow = defineFlow({ name: 'showcase_declarative_connector_ping', - label: 'Declarative Connector Ping (ADR-0096)', + label: 'Declarative Connector Ping (ADR-0097)', description: 'Dispatches GET /api/v1/health through showcase_status_api — a provider-bound connector instance materialized from pure metadata at boot.', type: 'autolaunched', diff --git a/examples/app-showcase/src/coverage.ts b/examples/app-showcase/src/coverage.ts index ce6aecce9..92feffbd9 100644 --- a/examples/app-showcase/src/coverage.ts +++ b/examples/app-showcase/src/coverage.ts @@ -179,7 +179,7 @@ export const STACK_COLLECTION_COVERAGE: Record = { status: 'demonstrated', files: ['src/system/connectors/index.ts', 'src/automation/flows/index.ts'], notes: - 'Both connector kinds are demonstrated. (1) Provider-bound INSTANCES (ADR-0096 / #2977): StatusApiConnector declares `provider: rest` (inline config) and StatusOpenApiConnector declares `provider: openapi` with its OpenAPI document referenced as a package-relative FILE PATH (#3016, read at boot and confined to the package root) — both are materialized into live, dispatchable connectors at boot; ShowcaseDeclarativeConnectorPingFlow calls the rest instance via connector_action and both appear in GET /connectors. (2) Catalog DESCRIPTOR (#2612): ErpCatalogConnector has no provider, so it stays inert metadata; enabled:false marks the deliberate catalog entry and silences the boot audit. Plugin-registered connectors (ConnectorRestPlugin/ConnectorSlackPlugin in objectstack.config.ts) are also exercised by the connector flows.', + 'Both connector kinds are demonstrated. (1) Provider-bound INSTANCES (ADR-0097 / #2977): StatusApiConnector declares `provider: rest` (inline config) and StatusOpenApiConnector declares `provider: openapi` with its OpenAPI document referenced as a package-relative FILE PATH (#3016, read at boot and confined to the package root) — both are materialized into live, dispatchable connectors at boot; ShowcaseDeclarativeConnectorPingFlow calls the rest instance via connector_action and both appear in GET /connectors. (2) Catalog DESCRIPTOR (#2612): ErpCatalogConnector has no provider, so it stays inert metadata; enabled:false marks the deliberate catalog entry and silences the boot audit. Plugin-registered connectors (ConnectorRestPlugin/ConnectorSlackPlugin in objectstack.config.ts) are also exercised by the connector flows.', }, }; diff --git a/examples/app-showcase/src/system/connectors/index.ts b/examples/app-showcase/src/system/connectors/index.ts index 1c3515072..7e9a88c48 100644 --- a/examples/app-showcase/src/system/connectors/index.ts +++ b/examples/app-showcase/src/system/connectors/index.ts @@ -3,7 +3,7 @@ import { defineConnector, type Connector } from '@objectstack/spec/integration'; /** - * Declarative `connectors:` — the collection now holds BOTH kinds (ADR-0096): + * Declarative `connectors:` — the collection now holds BOTH kinds (ADR-0097): * * 1. **Provider-bound instance** ({@link StatusApiConnector}) — a live, * dispatchable connector authored as pure metadata. It names a `provider` @@ -14,7 +14,7 @@ import { defineConnector, type Connector } from '@objectstack/spec/integration'; * indistinguishable from a hand-written connector — `connector_action` * dispatches it and `GET /connectors` lists it. {@link * file://../../automation/flows/index.ts | ShowcaseDeclarativeConnectorPingFlow} - * calls it end-to-end. This is the #2977 / ADR-0096 upgrade of what used to + * calls it end-to-end. This is the #2977 / ADR-0097 upgrade of what used to * be a purely descriptor-only collection. * * 2. **Catalog descriptor** ({@link ErpCatalogConnector}, the #2612 interim @@ -31,7 +31,7 @@ import { defineConnector, type Connector } from '@objectstack/spec/integration'; */ /** - * ADR-0096 provider-bound instance — declared as pure metadata, materialized + * ADR-0097 provider-bound instance — declared as pure metadata, materialized * into a live `rest` connector at boot by ConnectorRestPlugin's provider factory * (which the plugin registers even though, here, it is also configured with a * hand-wired `rest` connector). Points at the running server itself, so @@ -45,7 +45,7 @@ export const StatusApiConnector = defineConnector({ label: 'Status API (Declarative REST Instance)', type: 'api', description: - 'Provider-bound declarative connector instance (ADR-0096): authored as metadata, materialized into a live, ' + + 'Provider-bound declarative connector instance (ADR-0097): authored as metadata, materialized into a live, ' + 'dispatchable `rest` connector at boot. Unlike the ERP descriptor below, this one IS callable from a flow ' + 'connector_action and appears in GET /connectors.', provider: 'rest', @@ -59,7 +59,7 @@ export const StatusApiConnector = defineConnector({ auth: { type: 'none' }, }); /** - * ADR-0096 provider-bound instance, **file-path spec** form (#3016): the OpenAPI + * ADR-0097 provider-bound instance, **file-path spec** form (#3016): the OpenAPI * document lives next to this file (`status-openapi.json`) and is referenced by * a path resolved relative to THIS package's root at materialization — reads * are confined to the package root (absolute / `..`-escaping paths are @@ -76,7 +76,7 @@ export const StatusOpenApiConnector = defineConnector({ label: 'Status API (Declarative OpenAPI Instance, File-Path Spec)', type: 'api', description: - 'Provider-bound declarative connector instance (ADR-0096) whose OpenAPI document is referenced as a ' + + 'Provider-bound declarative connector instance (ADR-0097) whose OpenAPI document is referenced as a ' + 'package-relative file path (#3016) and read at boot, confined to the package root. Materialized into a live ' + '`openapi` connector — getHealth dispatches GET /api/v1/health against the running server.', provider: 'openapi', @@ -102,7 +102,7 @@ export const ErpCatalogConnector = defineConnector({ authentication: { type: 'api-key', key: 'SET_AT_INSTALL_TIME', headerName: 'X-API-Key' }, // Descriptor-level action catalog: key + label + I/O JSON Schemas. Note the // deliberate absence of any execution binding (HTTP method/path) — that is - // what keeps descriptors inert today and what ADR-0096's provider binding + // what keeps descriptors inert today and what ADR-0097's provider binding // supplies declaratively. actions: [ { diff --git a/packages/connectors/connector-mcp/src/connector-mcp-plugin.ts b/packages/connectors/connector-mcp/src/connector-mcp-plugin.ts index 32a8645ef..a2b31d1cb 100644 --- a/packages/connectors/connector-mcp/src/connector-mcp-plugin.ts +++ b/packages/connectors/connector-mcp/src/connector-mcp-plugin.ts @@ -7,7 +7,7 @@ import { createMcpProviderFactory, MCP_PROVIDER_KEY } from './mcp-provider.js'; /** * Minimal surface of the automation engine this plugin depends on — the - * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0096). + * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0097). * Kept structural so the plugin needs no runtime dependency on * `@objectstack/service-automation`. */ @@ -24,7 +24,7 @@ export interface ConnectorRegistrySurface { } /** - * Options for {@link ConnectorMcpPlugin}. All optional (ADR-0096): with no + * Options for {@link ConnectorMcpPlugin}. All optional (ADR-0097): with no * `transport` the plugin contributes only the `mcp` provider factory — so a * stack can declare `provider: 'mcp'` instances as pure metadata. Supply a * `transport` to ALSO connect one hand-wired MCP server at `start()`. @@ -34,7 +34,7 @@ export interface ConnectorMcpPluginOptions extends Partial /** * ConnectorMcpPlugin — contributes the generic MCP adapter (ADR-0024) in two forms: * - * 1. **Provider factory** (`mcp`, ADR-0096): registered at `init()` so the + * 1. **Provider factory** (`mcp`, ADR-0097): registered at `init()` so the * automation service can materialize declarative `provider: 'mcp'` * `connectors:` entries — connecting to the server and mapping its tools to * connector actions — at boot. @@ -65,7 +65,7 @@ export class ConnectorMcpPlugin implements Plugin { } async init(ctx: PluginContext): Promise { - // Contribute the `mcp` provider factory (ADR-0096) before the automation + // Contribute the `mcp` provider factory (ADR-0097) before the automation // service materializes declarative instances during its start(). const automation = this.tryGetAutomation(ctx); if (automation && typeof automation.registerConnectorProvider === 'function') { diff --git a/packages/connectors/connector-mcp/src/mcp-provider.test.ts b/packages/connectors/connector-mcp/src/mcp-provider.test.ts index f10a8f536..c64da7e9f 100644 --- a/packages/connectors/connector-mcp/src/mcp-provider.test.ts +++ b/packages/connectors/connector-mcp/src/mcp-provider.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// ADR-0096 — the `mcp` provider factory: materialize a declarative +// ADR-0097 — the `mcp` provider factory: materialize a declarative // `provider: 'mcp'` connector instance by connecting to the server (an injected // fake client here), listing its tools, and mapping them to actions. @@ -33,7 +33,7 @@ function ctx(partial: Partial & Pick { +describe('mcp provider factory (ADR-0097)', () => { it('advertises the mcp provider key', () => { expect(MCP_PROVIDER_KEY).toBe('mcp'); }); diff --git a/packages/connectors/connector-mcp/src/mcp-provider.ts b/packages/connectors/connector-mcp/src/mcp-provider.ts index 94ca03f28..d26e76cae 100644 --- a/packages/connectors/connector-mcp/src/mcp-provider.ts +++ b/packages/connectors/connector-mcp/src/mcp-provider.ts @@ -4,7 +4,7 @@ import type { ConnectorProviderFactory, ResolvedConnectorAuth } from '@objectsta import { createMcpConnector, type McpConnectorOptions, type McpTransport } from './mcp-connector.js'; /** - * The provider key this package contributes (ADR-0096). A declarative + * The provider key this package contributes (ADR-0097). A declarative * `connectors:` entry with `provider: 'mcp'` is materialized by this factory. */ export const MCP_PROVIDER_KEY = 'mcp'; @@ -96,7 +96,7 @@ function normalizeTransport( } /** - * Build the `mcp` {@link ConnectorProviderFactory} (ADR-0096 / ADR-0024). At boot + * Build the `mcp` {@link ConnectorProviderFactory} (ADR-0097 / ADR-0024). At boot * the automation service invokes it for each `provider: 'mcp'` declarative * instance: it connects to the MCP server named by `providerConfig.transport`, * lists its tools, and produces the same `{ def, handlers, close }` bundle @@ -104,7 +104,7 @@ function normalizeTransport( * per tool, dispatched to the server's `tools/call`. * * The connection is opened at materialization; an unreachable server or invalid - * transport therefore fails boot loudly (ADR-0096 fail-loud contract). Prefer a + * transport therefore fails boot loudly (ADR-0097 fail-loud contract). Prefer a * fail-soft plugin instantiation for an *optional* server. */ export function createMcpProviderFactory(deps: McpProviderDeps = {}): ConnectorProviderFactory { diff --git a/packages/connectors/connector-openapi/src/connector-openapi-plugin.ts b/packages/connectors/connector-openapi/src/connector-openapi-plugin.ts index a73d93a5a..eab8d3326 100644 --- a/packages/connectors/connector-openapi/src/connector-openapi-plugin.ts +++ b/packages/connectors/connector-openapi/src/connector-openapi-plugin.ts @@ -7,7 +7,7 @@ import { createOpenApiProviderFactory, OPENAPI_PROVIDER_KEY } from './openapi-pr /** * Minimal surface of the automation engine this package depends on — the - * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0096). + * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0097). * Kept structural so callers need no runtime dependency on * `@objectstack/service-automation` (mirrors connector-rest / connector-mcp). */ @@ -35,7 +35,7 @@ export function registerOpenApiConnector(registry: ConnectorRegistrySurface, con } /** - * Options for {@link ConnectorOpenApiPlugin}. All optional (ADR-0096): with no + * Options for {@link ConnectorOpenApiPlugin}. All optional (ADR-0097): with no * `document` the plugin contributes only the `openapi` provider factory — so a * stack can declare `provider: 'openapi'` instances as pure metadata. Supply a * `document` (+ config) to ALSO register one hand-wired OpenAPI connector. @@ -49,7 +49,7 @@ export interface ConnectorOpenApiPluginOptions extends Partial { - // Contribute the `openapi` provider factory (ADR-0096) before the + // Contribute the `openapi` provider factory (ADR-0097) before the // automation service materializes declarative instances during its start(). const automation = this.tryGetAutomation(ctx); if (automation && typeof automation.registerConnectorProvider === 'function') { diff --git a/packages/connectors/connector-openapi/src/openapi-provider.test.ts b/packages/connectors/connector-openapi/src/openapi-provider.test.ts index 8958eda74..07bee09af 100644 --- a/packages/connectors/connector-openapi/src/openapi-provider.test.ts +++ b/packages/connectors/connector-openapi/src/openapi-provider.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// ADR-0096 — the `openapi` provider factory: materialize a declarative +// ADR-0097 — the `openapi` provider factory: materialize a declarative // `provider: 'openapi'` connector instance from a spec document + resolved auth. import { describe, it, expect } from 'vitest'; @@ -22,7 +22,7 @@ function ctx(partial: Partial & Pick { +describe('openapi provider factory (ADR-0097)', () => { it('advertises the openapi provider key', () => { expect(OPENAPI_PROVIDER_KEY).toBe('openapi'); }); @@ -52,7 +52,7 @@ describe('openapi provider factory (ADR-0096)', () => { await expect(factory(ctx({ providerConfig: {} }))).rejects.toThrow(/providerConfig\.spec/); }); - // ── File-path specs (#3016 — ADR-0096 follow-up) ──────────────────────── + // ── File-path specs (#3016 — ADR-0097 follow-up) ──────────────────────── it('reads a file-path spec through the host loadPackageFile capability', async () => { const requested: string[] = []; diff --git a/packages/connectors/connector-openapi/src/openapi-provider.ts b/packages/connectors/connector-openapi/src/openapi-provider.ts index 4fcab127c..9def34727 100644 --- a/packages/connectors/connector-openapi/src/openapi-provider.ts +++ b/packages/connectors/connector-openapi/src/openapi-provider.ts @@ -12,7 +12,7 @@ import { } from './openapi-connector.js'; /** - * The provider key this package contributes (ADR-0096). A declarative + * The provider key this package contributes (ADR-0097). A declarative * `connectors:` entry with `provider: 'openapi'` is materialized by this factory. */ export const OPENAPI_PROVIDER_KEY = 'openapi'; @@ -36,7 +36,7 @@ interface OpenApiProviderConfig { } /** - * Resolve `providerConfig.spec` into a parsed OpenAPI document (ADR-0096; + * Resolve `providerConfig.spec` into a parsed OpenAPI document (ADR-0097; * union per #3016): an inline document object (the reliable, no-I/O-at-boot * form used by the showcase), an http(s) URL fetched at materialization, or a * **file path** read through the host's `ctx.loadPackageFile` — which resolves @@ -102,7 +102,7 @@ async function loadOpenApiDocument( } /** - * Build the `openapi` {@link ConnectorProviderFactory} (ADR-0096 / ADR-0023). At + * Build the `openapi` {@link ConnectorProviderFactory} (ADR-0097 / ADR-0023). At * boot the automation service invokes it for each `provider: 'openapi'` * declarative instance: it loads the OpenAPI document from `providerConfig.spec`, * then produces the same `{ def, handlers }` bundle {@link createOpenApiConnector} diff --git a/packages/connectors/connector-rest/src/connector-rest-plugin.test.ts b/packages/connectors/connector-rest/src/connector-rest-plugin.test.ts index 0c1f62a98..284876511 100644 --- a/packages/connectors/connector-rest/src/connector-rest-plugin.test.ts +++ b/packages/connectors/connector-rest/src/connector-rest-plugin.test.ts @@ -81,7 +81,7 @@ describe('ConnectorRestPlugin — end to end with the automation engine', () => await kernel.shutdown(); }); - it('materializes a declarative `provider: rest` instance from stack metadata (ADR-0096)', async () => { + it('materializes a declarative `provider: rest` instance from stack metadata (ADR-0097)', async () => { const { impl, calls } = stubFetch(); // A provider-bound `connectors:` entry as it sits in the ObjectQL registry. diff --git a/packages/connectors/connector-rest/src/connector-rest-plugin.ts b/packages/connectors/connector-rest/src/connector-rest-plugin.ts index be2160b2d..4dd450ea8 100644 --- a/packages/connectors/connector-rest/src/connector-rest-plugin.ts +++ b/packages/connectors/connector-rest/src/connector-rest-plugin.ts @@ -7,7 +7,7 @@ import { createRestProviderFactory, REST_PROVIDER_KEY } from './rest-provider.js /** * Minimal surface of the automation engine this plugin depends on — the - * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0096). + * connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0097). * Kept structural so the plugin needs no runtime dependency on * `@objectstack/service-automation`. */ @@ -24,10 +24,10 @@ export interface ConnectorRegistrySurface { } /** - * Options for {@link ConnectorRestPlugin}. All optional (ADR-0096): with no + * Options for {@link ConnectorRestPlugin}. All optional (ADR-0097): with no * `baseUrl` the plugin contributes only the `rest` provider factory — so a stack * can declare `provider: 'rest'` instances as pure metadata. Supply `baseUrl` - * (etc.) to ALSO register a single hand-wired `rest` connector, the pre-ADR-0096 + * (etc.) to ALSO register a single hand-wired `rest` connector, the pre-ADR-0097 * usage. */ export interface ConnectorRestPluginOptions extends Partial {} @@ -36,7 +36,7 @@ export interface ConnectorRestPluginOptions extends Partial { - // Contribute the `rest` provider factory (ADR-0096). Done in init() — not + // Contribute the `rest` provider factory (ADR-0097). Done in init() — not // start() — so it is registered before the automation service materializes // declarative instances during its own start(). const automation = this.tryGetAutomation(ctx); diff --git a/packages/connectors/connector-rest/src/rest-provider.test.ts b/packages/connectors/connector-rest/src/rest-provider.test.ts index 611871ceb..30bc17d4a 100644 --- a/packages/connectors/connector-rest/src/rest-provider.test.ts +++ b/packages/connectors/connector-rest/src/rest-provider.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// ADR-0096 — the `rest` provider factory: materialize a declarative +// ADR-0097 — the `rest` provider factory: materialize a declarative // `provider: 'rest'` connector instance from providerConfig + resolved auth. import { describe, it, expect } from 'vitest'; @@ -26,7 +26,7 @@ function ctx(partial: Partial & Pick { +describe('rest provider factory (ADR-0097)', () => { it('advertises the rest provider key', () => { expect(REST_PROVIDER_KEY).toBe('rest'); }); diff --git a/packages/connectors/connector-rest/src/rest-provider.ts b/packages/connectors/connector-rest/src/rest-provider.ts index 7b8db9919..af3f7bcc2 100644 --- a/packages/connectors/connector-rest/src/rest-provider.ts +++ b/packages/connectors/connector-rest/src/rest-provider.ts @@ -4,7 +4,7 @@ import type { ConnectorProviderFactory, ResolvedConnectorAuth } from '@objectsta import { createRestConnector, type RestAuth } from './rest-connector.js'; /** - * The provider key this package contributes (ADR-0096). A declarative + * The provider key this package contributes (ADR-0097). A declarative * `connectors:` entry with `provider: 'rest'` is materialized by this factory. */ export const REST_PROVIDER_KEY = 'rest'; @@ -27,7 +27,7 @@ function isStringRecord(v: unknown): v is Record { } /** - * Build the `rest` {@link ConnectorProviderFactory} (ADR-0096). At boot the + * Build the `rest` {@link ConnectorProviderFactory} (ADR-0097). At boot the * automation service invokes it for each `provider: 'rest'` declarative instance, * turning `providerConfig.baseUrl` (+ the resolved `auth`) into the same * `{ def, handlers }` bundle {@link createRestConnector} produces for a diff --git a/packages/services/service-automation/src/connector-descriptor-audit.test.ts b/packages/services/service-automation/src/connector-descriptor-audit.test.ts index 3a0cc008c..7e378f39c 100644 --- a/packages/services/service-automation/src/connector-descriptor-audit.test.ts +++ b/packages/services/service-automation/src/connector-descriptor-audit.test.ts @@ -8,7 +8,7 @@ // inert; the audit at kernel:ready surfaces exactly those entries as a loud // warning instead of letting `connector_action` fail mysteriously at runtime. // Provider-bound declarative instances (which upgrade this warning to an -// error) are tracked in #2977 / ADR-0096. +// error) are tracked in #2977 / ADR-0097. import { describe, it, expect, vi } from 'vitest'; import { LiteKernel } from '@objectstack/core'; diff --git a/packages/services/service-automation/src/connector-materialization.test.ts b/packages/services/service-automation/src/connector-materialization.test.ts index 6e439f446..b3ffc5f31 100644 --- a/packages/services/service-automation/src/connector-materialization.test.ts +++ b/packages/services/service-automation/src/connector-materialization.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// ADR-0096 — provider-bound declarative connector instances. A `connectors:` +// ADR-0097 — provider-bound declarative connector instances. A `connectors:` // entry that names a `provider` is materialized at boot by the automation // service: it looks up the provider factory a connector plugin registered, // resolves `auth.credentialRef`, and registers the resulting `{ def, handlers }` @@ -160,7 +160,7 @@ function makeClosableProvider() { /** * Boot with a MUTABLE declared set and expose a `reload(next)` that swaps the * registry contents and fires `metadata:reloaded` — the runtime reconcile path - * (ADR-0096 F1). The harness's ctx is the shared kernel context, so + * (ADR-0097 F1). The harness's ctx is the shared kernel context, so * `ctx.trigger('metadata:reloaded')` invokes the automation plugin's hook. */ async function bootReloadable( @@ -196,7 +196,7 @@ async function bootReloadable( return { kernel, engine: automationOf(kernel), reload }; } -describe('ADR-0096 — declarative connector materialization', () => { +describe('ADR-0097 — declarative connector materialization', () => { it('materializes a provider-bound instance into a live, listed connector', async () => { const { factory, calls } = makeFakeProvider(); const kernel = await boot([providerConnector('billing')], { providerFactory: factory }); @@ -312,7 +312,7 @@ describe('ADR-0096 — declarative connector materialization', () => { await kernel.shutdown(); }); - // ── Hard boot failures (ADR-0096 §Decision / §Acceptance) ────────────── + // ── Hard boot failures (ADR-0097 §Decision / §Acceptance) ────────────── it('fails boot loudly when the provider has no registered factory', async () => { await expect( @@ -354,7 +354,7 @@ describe('ADR-0096 — declarative connector materialization', () => { }); }); -describe('ADR-0096 — runtime re-materialization on metadata:reloaded (F1)', () => { +describe('ADR-0097 — runtime re-materialization on metadata:reloaded (F1)', () => { it('materializes an instance published after boot', async () => { const { factory, calls } = makeClosableProvider(); const { engine, reload, kernel } = await bootReloadable([], { providerFactory: factory }); diff --git a/packages/services/service-automation/src/engine.ts b/packages/services/service-automation/src/engine.ts index 594c6ab6a..f57cd2676 100644 --- a/packages/services/service-automation/src/engine.ts +++ b/packages/services/service-automation/src/engine.ts @@ -161,7 +161,7 @@ export type ConnectorActionHandler = ( ) => Promise>; /** - * How a registered connector reached the engine (ADR-0096 §4). `plugin` — a + * How a registered connector reached the engine (ADR-0097 §4). `plugin` — a * connector plugin called `registerConnector` directly (ADR-0018 §Addendum). * `declarative` — the automation service materialized a provider-bound * `connectors:` stack entry at boot. A name registered under one origin cannot @@ -177,11 +177,11 @@ export type ConnectorOrigin = 'plugin' | 'declarative'; export interface RegisteredConnector { readonly def: Connector; readonly handlers: Record; - /** How this connector was registered (ADR-0096 §4). Defaults to `plugin`. */ + /** How this connector was registered (ADR-0097 §4). Defaults to `plugin`. */ readonly origin: ConnectorOrigin; } -// The connector **provider** contract (ADR-0096) — ConnectorProviderFactory, +// The connector **provider** contract (ADR-0097) — ConnectorProviderFactory, // ConnectorProviderContext, ConnectorMaterialization — lives in // `@objectstack/spec/integration` so a connector plugin can implement a factory // depending only on the spec, with no runtime coupling to this engine. Imported @@ -247,7 +247,7 @@ export interface ConnectorDescriptor { readonly icon?: string; readonly actions: ConnectorActionDescriptor[]; /** - * How the connector reached the registry (ADR-0096 §4): `plugin` — registered + * How the connector reached the registry (ADR-0097 §4): `plugin` — registered * by a connector plugin via `registerConnector`; `declarative` — materialized * from a provider-bound `connectors:` stack entry at boot. Lets a designer * distinguish a live declarative instance from a plugin connector (and both @@ -489,7 +489,7 @@ export class AutomationEngine implements IAutomationService { private boundFlowTriggers = new Map(); /** Connectors registered by integration plugins, keyed by connector name (ADR-0018 §Addendum). */ private connectors = new Map(); - /** Connector provider factories keyed by provider name (ADR-0096 §2 — `openapi`/`mcp`/`rest`/…). */ + /** Connector provider factories keyed by provider name (ADR-0097 §2 — `openapi`/`mcp`/`rest`/…). */ private connectorProviders = new Map(); /** Bridge to the host function registry for `script`-node calls (#1870), if wired. */ private functionResolver: FlowFunctionResolver | null = null; @@ -800,7 +800,7 @@ export class AutomationEngine implements IAutomationService { /** * Register a connector (called by integration plugins, ADR-0018 §Addendum; * and by the automation service for materialized declarative instances, - * ADR-0096 §2). Validates the definition against {@link ConnectorSchema} and + * ADR-0097 §2). Validates the definition against {@link ConnectorSchema} and * asserts every declared action has a handler, so a half-wired connector * fails loudly at registration rather than silently at dispatch. * @@ -808,7 +808,7 @@ export class AutomationEngine implements IAutomationService { * {@link registerNodeExecutor} — supports hot-reload). Re-registering across * origins — a plugin name colliding with a declarative provider-bound * instance, or vice versa — is a **hard error** (the two-sources-of-truth - * hazard, ADR-0096 §4): there is no silent precedence, because silent + * hazard, ADR-0097 §4): there is no silent precedence, because silent * precedence is how the declared def and the plugin def drift apart. * * @param origin how the connector reached the engine (defaults to `plugin`; @@ -837,7 +837,7 @@ export class AutomationEngine implements IAutomationService { throw new Error( `Connector name conflict: '${parsed.name}' is already registered by ${describe(existing.origin)} ` + `and cannot also be registered by ${describe(origin)}. A declarative provider-bound instance and a ` + - `plugin-registered connector must not share a name — there is no silent precedence (ADR-0096 §4). ` + + `plugin-registered connector must not share a name — there is no silent precedence (ADR-0097 §4). ` + `Rename one of them.`, ); } @@ -895,7 +895,7 @@ export class AutomationEngine implements IAutomationService { } /** - * Register a connector **provider factory** (ADR-0096 §2). A connector + * Register a connector **provider factory** (ADR-0097 §2). A connector * plugin (e.g. `@objectstack/connector-openapi`) calls this at `init()` under * its provider key (`openapi`); the automation service then invokes the * factory at boot for every declarative `connectors:` entry naming that diff --git a/packages/services/service-automation/src/index.ts b/packages/services/service-automation/src/index.ts index dfd44ce2c..8b653b6ac 100644 --- a/packages/services/service-automation/src/index.ts +++ b/packages/services/service-automation/src/index.ts @@ -20,7 +20,7 @@ export type { StepLogEntry, } from './engine.js'; -// Connector provider contract (ADR-0096) — re-exported from @objectstack/spec so +// Connector provider contract (ADR-0097) — re-exported from @objectstack/spec so // hosts/tests can reach it via this package too. Connector plugins should import // it directly from `@objectstack/spec/integration` (no coupling to this engine). export type { diff --git a/packages/services/service-automation/src/plugin.ts b/packages/services/service-automation/src/plugin.ts index 911d47cac..fa4759199 100644 --- a/packages/services/service-automation/src/plugin.ts +++ b/packages/services/service-automation/src/plugin.ts @@ -56,7 +56,7 @@ export interface AutomationServicePluginOptions { runHistoryMaxPerFlow?: number; /** * Resolves a declarative connector instance's `auth.credentialRef` to its - * secret at boot (ADR-0096 §3). Defaults to {@link defaultEnvCredentialResolver} + * secret at boot (ADR-0097 §3). Defaults to {@link defaultEnvCredentialResolver} * — the **open tier**: a `credentialRef` names an environment variable. An * enterprise host injects a vault/KMS-backed resolver here without touching * the materialization path. A ref that resolves to `undefined`/empty is a @@ -82,7 +82,7 @@ export interface AutomationServicePluginOptions { * any path that escapes the root after resolution (`../…`, `a/../../…`), so a * declarative entry can never read outside the stack/package that declared it. * A missing/unreadable file throws — the materializer's reconcile policy makes - * that fatal at boot and a skipped entry on reload, like every other ADR-0096 + * that fatal at boot and a skipped entry on reload, like every other ADR-0097 * materialization failure. * * Node builtins are imported lazily inside the returned closure so merely @@ -130,7 +130,7 @@ export function createPackageFileLoader(packageRoot?: string): (relativePath: st export type CredentialResolver = (ref: string) => string | undefined | Promise; /** - * Open-tier credential resolver (ADR-0096 §3, ADR-0015 open/enterprise line): + * Open-tier credential resolver (ADR-0097 §3, ADR-0015 open/enterprise line): * a `credentialRef` is the name of an environment variable. This is the * degraded-but-honest story for environments without a managed secrets service — * static credentials from env/config are open source; managed vaulting, OAuth2 @@ -154,7 +154,7 @@ function stableStringify(input: unknown): string { /** * Stable signature of a provider-bound instance's materialization inputs - * (ADR-0096). Drives the `metadata:reloaded` reconcile: an unchanged signature + * (ADR-0097). Drives the `metadata:reloaded` reconcile: an unchanged signature * means the live connector is left untouched (no MCP reconnect); a changed one * triggers re-materialization. `auth` carries only the `credentialRef`, never a * resolved secret, so hashing the declarative entry is safe. @@ -185,7 +185,7 @@ function connectorInstanceSignature(entry: { * 'connector'). Raw authored values — Zod defaults (e.g. `enabled: true`) * may not have been applied, so `enabled` is only trusted when explicitly * `false`. `provider` (+ `providerConfig`/`auth`) marks a provider-bound - * **instance** the automation service materializes at boot (ADR-0096); + * **instance** the automation service materializes at boot (ADR-0097); * its absence marks a catalog **descriptor** (#2612). */ interface DeclaredConnectorItem { @@ -215,7 +215,7 @@ interface DeclaredConnectorItem { * catalog-only entry), and (d) are NOT provider-bound instances — a * provider-bound entry (`provider` set) is materialized (or fails boot) by * {@link AutomationServicePlugin.materializeDeclaredConnectors}, so it follows - * the ADR-0096 instance contract, not the descriptor-only warning (#2977). + * the ADR-0097 instance contract, not the descriptor-only warning (#2977). */ export function findInertDeclaredConnectors( declared: unknown[], @@ -285,7 +285,7 @@ export class AutomationServicePlugin implements Plugin { private syncedFlowNames = new Set(); /** * Provider-bound declarative connectors this plugin has materialized - * (ADR-0096), keyed by connector name. `signature` is a stable hash of the + * (ADR-0097), keyed by connector name. `signature` is a stable hash of the * entry's materialization inputs so a `metadata:reloaded` reconcile can tell * an unchanged instance (skip — don't re-open its MCP connection) from a * changed one (re-materialize). `close` is the optional teardown (e.g. an MCP @@ -448,7 +448,7 @@ export class AutomationServicePlugin implements Plugin { ctx.logger.warn(`[Automation] flow pull from ObjectQL registry failed: ${msg}`); } - // ── ADR-0096: materialize provider-bound declarative connector instances ── + // ── ADR-0097: materialize provider-bound declarative connector instances ── // Every plugin's init() has completed by start(), so connector plugins have // registered their provider factories (they do so in init()) and the ObjectQL // registry is fully populated with declared `connectors:` entries. Turn each @@ -478,7 +478,7 @@ export class AutomationServicePlugin implements Plugin { // A Studio publish / dev reload can add, change, or remove declarative // provider-bound connector instances — reconcile the live registry so // a newly-published instance becomes dispatchable (and a removed one - // is torn down) without a restart (ADR-0096). Soft mode: a bad publish + // is torn down) without a restart (ADR-0097). Soft mode: a bad publish // logs + skips rather than crashing the running server. await this.materializeDeclaredConnectors(ctx, { fatal: false }); // Re-audit so the inert-descriptor warning stays current for plain @@ -561,12 +561,12 @@ export class AutomationServicePlugin implements Plugin { `@objectstack/connector-rest, @objectstack/connector-slack, @objectstack/connector-openapi, ` + `@objectstack/connector-mcp. Install/instantiate the matching connector plugin, or mark a ` + `deliberate catalog-only entry with \`enabled: false\` to silence this warning. ` + - `Declarative provider-bound connector instances are tracked in #2977 (ADR-0096).`, + `Declarative provider-bound connector instances are tracked in #2977 (ADR-0097).`, ); } /** - * ADR-0096 — reconcile the live connector registry against the declared + * ADR-0097 — reconcile the live connector registry against the declared * provider-bound `connectors:` entries. For each enabled entry naming a * `provider`: look up the provider factory, resolve `auth.credentialRef`, * invoke the factory, and register the result under the **declared** name @@ -620,7 +620,7 @@ export class AutomationServicePlugin implements Plugin { const bound = entry as DeclaredConnectorItem & { name: string; provider: string }; if (bound.enabled === false) continue; if (desired.has(bound.name)) { - fail(`[Automation] duplicate declarative connector instance name '${bound.name}' — connector names must be unique (ADR-0096).`); + fail(`[Automation] duplicate declarative connector instance name '${bound.name}' — connector names must be unique (ADR-0097).`); continue; } desired.set(bound.name, { entry: bound, signature: connectorInstanceSignature(bound) }); @@ -647,7 +647,7 @@ export class AutomationServicePlugin implements Plugin { if (!existing && engine.getConnectorOrigin(name) === 'plugin') { fail( `[Automation] connector name conflict: declarative provider-bound instance '${name}' collides with a ` + - `plugin-registered connector of the same name — there is no silent precedence (ADR-0096 §4). Rename one.`, + `plugin-registered connector of the same name — there is no silent precedence (ADR-0097 §4). Rename one.`, ); continue; } @@ -659,7 +659,7 @@ export class AutomationServicePlugin implements Plugin { `[Automation] connector instance '${name}' declares provider '${provider}', but no provider factory is registered. ` + `Install the connector plugin that supplies it (openapi → @objectstack/connector-openapi, mcp → @objectstack/connector-mcp, ` + `rest → @objectstack/connector-rest) in the stack's plugins: array. Installed providers: ` + - `[${installed.join(', ') || 'none'}] (ADR-0096).`, + `[${installed.join(', ') || 'none'}] (ADR-0097).`, ); continue; } @@ -692,7 +692,7 @@ export class AutomationServicePlugin implements Plugin { } catch (err) { fail( `[Automation] failed to materialize connector instance '${name}' via provider '${provider}': ` + - `${(err as Error).message} (ADR-0096).`, + `${(err as Error).message} (ADR-0097).`, ); continue; } @@ -719,12 +719,12 @@ export class AutomationServicePlugin implements Plugin { if (changed > 0) { ctx.logger.info( - `[Automation] materialized ${changed} provider-bound connector instance(s) (ADR-0096)`, + `[Automation] materialized ${changed} provider-bound connector instance(s) (ADR-0097)`, ); } } - /** Unregister and tear down one materialized declarative connector (ADR-0096). */ + /** Unregister and tear down one materialized declarative connector (ADR-0097). */ private async dematerializeConnector( engine: AutomationEngine, name: string, @@ -743,7 +743,7 @@ export class AutomationServicePlugin implements Plugin { * Resolve a declarative instance's `auth` into the static * {@link ResolvedConnectorAuth} a provider factory applies — dereferencing * `credentialRef` through the resolver. An `undefined`/empty resolution is a - * hard boot error (ADR-0096 §3): an app must not run with a connector whose + * hard boot error (ADR-0097 §3): an app must not run with a connector whose * credentials never loaded. */ private async resolveInstanceAuth( @@ -761,7 +761,7 @@ export class AutomationServicePlugin implements Plugin { `[Automation] connector instance '${connectorName}' (provider '${provider}'): credentialRef ` + `'${auth.credentialRef}' did not resolve to a value. The open tier resolves credentialRef from ` + `environment variables — set the '${auth.credentialRef}' env var, or wire ` + - `AutomationServicePluginOptions.credentialResolver to a secrets service (ADR-0096 §3).`, + `AutomationServicePluginOptions.credentialResolver to a secrets service (ADR-0097 §3).`, ); } @@ -920,7 +920,7 @@ export class AutomationServicePlugin implements Plugin { } async destroy(): Promise { - // Tear down materialized provider-bound connectors (ADR-0096) — e.g. an + // Tear down materialized provider-bound connectors (ADR-0097) — e.g. an // MCP connection's close — in reverse registration order, best-effort, so // no socket / child process leaks. The engine (and its registry) is dropped // right after, so unregistering the connectors themselves is unnecessary. diff --git a/packages/spec/src/integration/connector-provider.test.ts b/packages/spec/src/integration/connector-provider.test.ts index 09df67c5e..5d116e395 100644 --- a/packages/spec/src/integration/connector-provider.test.ts +++ b/packages/spec/src/integration/connector-provider.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// ADR-0096 — schema evolution for provider-bound declarative connector instances. +// ADR-0097 — schema evolution for provider-bound declarative connector instances. // Verifies the new `provider` / `providerConfig` / `auth` fields, the // credentialRef-based instance auth (no inline secrets), and the authoring rules // enforced by DeclarativeConnectorEntrySchema. @@ -12,7 +12,7 @@ import { ConnectorInstanceAuthSchema, } from './connector.zod'; -describe('ADR-0096 connector schema evolution', () => { +describe('ADR-0097 connector schema evolution', () => { describe('ConnectorSchema — new fields', () => { it('defaults authentication to { type: none } so provider-bound instances need not inline it', () => { const c = ConnectorSchema.parse({ name: 'billing', label: 'Billing', type: 'api' }); diff --git a/packages/spec/src/integration/connector-provider.ts b/packages/spec/src/integration/connector-provider.ts index 97f5f0e4e..52befbcca 100644 --- a/packages/spec/src/integration/connector-provider.ts +++ b/packages/spec/src/integration/connector-provider.ts @@ -4,7 +4,7 @@ import type { Connector } from './connector.zod'; import type { ResolvedConnectorAuth } from '../shared/connector-auth.zod'; /** - * Connector **provider** contract (ADR-0096). + * Connector **provider** contract (ADR-0097). * * A provider is a *generic executor* — `openapi` (ADR-0023), `mcp` (ADR-0024), * `rest`, … — contributed by a connector plugin as a **factory**. At boot the @@ -13,7 +13,7 @@ import type { ResolvedConnectorAuth } from '../shared/connector-auth.zod'; * inputs into the same `{ def, handlers }` bundle a hand-written connector hands * to `registerConnector`. The registry, the `connector_action` node, and the * `GET /connectors` discovery route then see a finished connector — they never - * know it was materialized from stack metadata (ADR-0096 §2). + * know it was materialized from stack metadata (ADR-0097 §2). * * These are pure types (no logic — Prime Directive #2) so a connector plugin can * implement a provider factory depending only on `@objectstack/spec`, with no @@ -63,7 +63,7 @@ export interface ConnectorProviderContext { readonly providerConfig: Record; readonly auth?: ResolvedConnectorAuth; /** - * Host-injected package file reader (#3016, ADR-0096 follow-up). Resolves a + * Host-injected package file reader (#3016, ADR-0097 follow-up). Resolves a * **relative** path against the root of the stack/package that declared the * entry and returns the file's UTF-8 text, so a factory can support file-path * refs like `providerConfig.spec: './billing-openapi.json'` without owning @@ -82,7 +82,7 @@ export interface ConnectorProviderContext { * Invoked once per declarative instance at boot; may be async (loading a spec * document, opening a connection). Throwing is a **hard boot error** — invalid * `providerConfig`, an unreachable upstream, etc. surface loudly rather than - * yielding a silently-dead connector (ADR-0096 §Decision). + * yielding a silently-dead connector (ADR-0097 §Decision). */ export type ConnectorProviderFactory = ( ctx: ConnectorProviderContext, diff --git a/packages/spec/src/integration/connector.zod.ts b/packages/spec/src/integration/connector.zod.ts index 40837b9a8..d85d1f6a7 100644 --- a/packages/spec/src/integration/connector.zod.ts +++ b/packages/spec/src/integration/connector.zod.ts @@ -44,7 +44,7 @@ import { FieldMappingSchema as BaseFieldMappingSchema } from '../shared/mapping. * that lack a same-name runtime registration; mark deliberate catalog-only * entries with `enabled: false`. Provider-bound declarative instances that * a generic executor (connector-openapi / connector-mcp) materializes at - * boot are tracked in #2977 (ADR-0096). + * boot are tracked in #2977 (ADR-0097). * * Authentication is now imported from the canonical auth/config.zod.ts. * @@ -582,14 +582,14 @@ export const ConnectorSchema = lazySchema(() => z.object({ * inline, supplied by a plugin at `registerConnector`). Optional and defaults * to `{ type: 'none' }` so a declarative provider-bound instance can reference * credentials through {@link auth}/`credentialRef` instead of inlining them - * here (ADR-0096). Hand-written / plugin connectors keep setting it as before. + * here (ADR-0097). Hand-written / plugin connectors keep setting it as before. */ authentication: ConnectorAuthConfigSchema.optional().default({ type: 'none' }).describe( 'Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead.', ), /** - * ADR-0096 — provider key naming the installed **generic executor** that + * ADR-0097 — provider key naming the installed **generic executor** that * materializes this declarative entry into a live, dispatchable connector at * boot (`openapi`, `mcp`, `rest`, or any provider a connector plugin * contributes). Presence flips the entry from an inert catalog **descriptor** @@ -599,11 +599,11 @@ export const ConnectorSchema = lazySchema(() => z.object({ * error. Omit `provider` to keep the entry a pure descriptor. */ provider: z.string().regex(/^[a-z][a-z0-9_]*$/).optional().describe( - 'Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0096).', + 'Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097).', ), /** - * ADR-0096 — provider-specific configuration, **validated by the provider + * ADR-0097 — provider-specific configuration, **validated by the provider * factory** (not by this schema): the OpenAPI provider expects `{ spec, * baseUrl? }` — where `spec` is an inline OpenAPI document object, a file * path resolved relative to the declaring stack/package root (reads are @@ -620,14 +620,14 @@ export const ConnectorSchema = lazySchema(() => z.object({ ), /** - * ADR-0096 — declarative auth for a provider-bound instance: secret-bearing + * ADR-0097 — declarative auth for a provider-bound instance: secret-bearing * variants carry a `credentialRef` the automation service resolves through the * secrets/env layer at materialization, never an inline secret (§3). Distinct * from {@link authentication}, which is the runtime shape with the resolved * secret inline. Requires `provider`. */ auth: ConnectorInstanceAuthSchema.optional().describe( - 'Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0096).', + 'Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097).', ), /** Zapier-style Capabilities */ @@ -714,7 +714,7 @@ export function defineConnector(config: z.input): Connec } /** - * A declarative `connectors:` **stack entry** (ADR-0096) — {@link ConnectorSchema} + * A declarative `connectors:` **stack entry** (ADR-0097) — {@link ConnectorSchema} * plus the cross-field rules that apply only when a connector is *authored inside * a stack*, as opposed to a def a plugin builds at runtime and hands to * `registerConnector`. `stack.zod.ts` validates the `connectors:` array against @@ -738,14 +738,14 @@ export const DeclarativeConnectorEntrySchema = lazySchema(() => ctx.addIssue({ code: 'custom', path: ['providerConfig'], - message: '`providerConfig` requires a `provider` — a connector entry with no provider is a catalog descriptor (ADR-0096).', + message: '`providerConfig` requires a `provider` — a connector entry with no provider is a catalog descriptor (ADR-0097).', }); } if (entry.auth !== undefined) { ctx.addIssue({ code: 'custom', path: ['auth'], - message: '`auth` requires a `provider` — declarative instance auth applies only to a provider-bound entry (ADR-0096).', + message: '`auth` requires a `provider` — declarative instance auth applies only to a provider-bound entry (ADR-0097).', }); } return; @@ -755,21 +755,21 @@ export const DeclarativeConnectorEntrySchema = lazySchema(() => ctx.addIssue({ code: 'custom', path: ['authentication'], - message: `Provider-bound connector instance '${entry.name}' must not inline secrets via \`authentication\`; reference credentials with \`auth: { type, credentialRef }\` instead (ADR-0096 §3).`, + message: `Provider-bound connector instance '${entry.name}' must not inline secrets via \`authentication\`; reference credentials with \`auth: { type, credentialRef }\` instead (ADR-0097 §3).`, }); } if (entry.actions && entry.actions.length > 0) { ctx.addIssue({ code: 'custom', path: ['actions'], - message: `Provider-bound connector instance '${entry.name}' must not author \`actions\` — the '${entry.provider}' provider derives them from the upstream at boot (ADR-0096 §5).`, + message: `Provider-bound connector instance '${entry.name}' must not author \`actions\` — the '${entry.provider}' provider derives them from the upstream at boot (ADR-0097 §5).`, }); } if (entry.triggers && entry.triggers.length > 0) { ctx.addIssue({ code: 'custom', path: ['triggers'], - message: `Provider-bound connector instance '${entry.name}' must not author \`triggers\` — the '${entry.provider}' provider derives them from the upstream at boot (ADR-0096 §5).`, + message: `Provider-bound connector instance '${entry.name}' must not author \`triggers\` — the '${entry.provider}' provider derives them from the upstream at boot (ADR-0097 §5).`, }); } }), @@ -777,7 +777,7 @@ export const DeclarativeConnectorEntrySchema = lazySchema(() => export type DeclarativeConnectorEntry = z.infer; -// Re-export the declarative-instance auth surface (ADR-0096) so consumers reach +// Re-export the declarative-instance auth surface (ADR-0097) so consumers reach // it through `@objectstack/spec/integration` alongside the connector schema. export { ConnectorInstanceAuthSchema, diff --git a/packages/spec/src/integration/index.ts b/packages/spec/src/integration/index.ts index f94281b5f..824ad0ca2 100644 --- a/packages/spec/src/integration/index.ts +++ b/packages/spec/src/integration/index.ts @@ -15,7 +15,7 @@ // Core Connector Protocol export * from './connector.zod'; -// Connector provider contract (ADR-0096) — declarative instances → live connectors +// Connector provider contract (ADR-0097) — declarative instances → live connectors export * from './connector-provider'; // Connector Templates diff --git a/packages/spec/src/shared/connector-auth.zod.ts b/packages/spec/src/shared/connector-auth.zod.ts index 0db31eb06..66cabaeb5 100644 --- a/packages/spec/src/shared/connector-auth.zod.ts +++ b/packages/spec/src/shared/connector-auth.zod.ts @@ -76,7 +76,7 @@ export type ConnectorAuthConfig = z.infer; * tier a generic executor can apply with no token-acquisition flow (`none` / * `api-key` / `basic` / `bearer`). This is the shape a provider factory receives * *after* a declarative instance's {@link ConnectorInstanceAuth} `credentialRef` - * has been resolved to its secret at materialization (ADR-0096). OAuth2 — + * has been resolved to its secret at materialization (ADR-0097). OAuth2 — * authorization-code/refresh lifecycle — is the enterprise tier (ADR-0015). */ export type ResolvedConnectorAuth = Extract< @@ -85,7 +85,7 @@ export type ResolvedConnectorAuth = Extract< >; // ============================================================================ -// Declarative connector-instance auth (ADR-0096) +// Declarative connector-instance auth (ADR-0097) // // Auth for a provider-bound declarative `connectors:` entry. Unlike // ConnectorAuthConfigSchema — the runtime shape, which carries the *resolved* @@ -93,7 +93,7 @@ export type ResolvedConnectorAuth = Extract< // carries a `credentialRef` **reference** that the automation service resolves // through the secrets/env layer at boot. There is deliberately no field to // inline a secret here: stack metadata is authored, versioned, and shipped, so -// a raw token must never live in it (ADR-0096 §3). OAuth2 is intentionally +// a raw token must never live in it (ADR-0097 §3). OAuth2 is intentionally // absent (enterprise tier, ADR-0015). // ============================================================================ @@ -131,7 +131,7 @@ export const ConnectorInstanceBasicAuthSchema = lazySchema(() => z.object({ /** * Declarative connector-instance auth: a discriminated union whose secret-bearing - * variants carry a `credentialRef` instead of an inline secret (ADR-0096 §3). + * variants carry a `credentialRef` instead of an inline secret (ADR-0097 §3). * Consumed by the `auth` field on a provider-bound `connectors:` entry. */ export const ConnectorInstanceAuthSchema = lazySchema(() => z.discriminatedUnion('type', [ diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index a3728e456..8268cb5ee 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -331,7 +331,7 @@ export const ObjectStackDefinitionSchema = lazySchema(() => z.object({ analyticsCubes: z.array(CubeSchema).optional().describe('Analytics Semantic Layer Cubes'), /** - * Integration Protocol — connectors are of two kinds (ADR-0096): + * Integration Protocol — connectors are of two kinds (ADR-0097): * * 1. **Provider-bound instance** (has `provider`): a live, dispatchable * connector authored as pure metadata. At boot the automation service looks @@ -353,13 +353,13 @@ export const ObjectStackDefinitionSchema = lazySchema(() => z.object({ * Runtime connectors may also be contributed directly by plugins calling * `engine.registerConnector(def, handlers)` (ADR-0018 §Addendum). A * provider-bound instance whose `name` collides with such a plugin-registered - * connector is a hard boot error (no silent precedence, ADR-0096 §4). + * connector is a hard boot error (no silent precedence, ADR-0097 §4). */ connectors: z.array(DeclarativeConnectorEntrySchema).optional().describe( 'External System Connectors. A provider-bound entry (has `provider`: openapi/mcp/rest) is materialized into a ' + 'live, dispatchable connector at boot and referenced by flows via `connector_action`; credentials are `auth.credentialRef` ' + 'references, never inline secrets. An entry with no `provider` is a catalog descriptor only (NOT dispatchable) — set ' + - '`enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0096, #2977).', + '`enabled: false` on deliberate descriptors. Unknown provider / unresolvable credentialRef / name conflict ⇒ hard boot error (ADR-0097, #2977).', ), /**