Skip to content

Commit 3ae1639

Browse files
os-zhuangclaude
andauthored
docs(adr): status-hygiene sweep — fix 0096 collision, flip implemented statuses, mark PRIORITIZATION stale (#3032)
* docs(adr): status-hygiene sweep — fix 0096 collision, flip implemented statuses, mark PRIORITIZATION stale Follow-up to the ADR-0086 re-evaluation (#2557): an audit of docs/adr found three hygiene defects; this fixes the mechanical ones. 1. Resolve the ADR-0096 numbering collision. Two unrelated ADRs landed as 0096 on the same day; the first-merged one (execution-surface identity admission, #2975) keeps 0096, and the connector ADR (#2977) is renumbered to ADR-0097. All connector-semantic "ADR-0096" references in code comments, test titles, and zod describe/message strings are updated to ADR-0097; execution-surface references (http-dispatcher, authz-conformance, graphql/realtime contracts, service-realtime README) are untouched. CHANGELOGs and commit history are left as historical record. 2. Flip Status lines of implemented ADRs that were still Proposed, with evidence: 0093 (tenancy service + membership reconciler + dogfood test) and 0095 (tenant-layer.ts + posture-ladder.ts + matrix-gate tests). Verified code+tests on main before flipping; other stale-looking ADRs (0049/0056/0057/0066/...) are left for per-ADR verification. 3. Mark docs/adr/PRIORITIZATION.md as a historical snapshot (it predates ADR-0050+) and record the status-hygiene rule: the PR that lands an ADR's implementation flips its Status line with one line of evidence. * chore: retrigger CI * docs: rename remaining connector-semantic ADR-0096 refs introduced by #3016/#3029 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent bf1720b commit 3ae1639

29 files changed

Lines changed: 121 additions & 109 deletions

docs/adr/0093-tenancy-mode-and-membership-lifecycle.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0093: Tenancy mode as a first-class capability, and a single owner for the user→membership lifecycle
22

3-
- **Status:** Proposed (implementation in progress)
3+
- **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`
44
- **Date:** 2026-07-13
55
- **Deciders:** ObjectStack Protocol Architects
66
- **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.

docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0095: Authorization Kernel Chain — Tenant Isolation as Layer 0, a Monotonic Posture Ladder, Capability-Derived Posture
22

3-
**Status**: Proposed (2026-07-14)
3+
**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)
44
**Deciders**: ObjectStack Protocol Architects
55
**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)
66
**Scopes**: the "cross-tenant RLS does not exist here" claim in [ADR-0073](./0073-automation-execution-identity.md) (see Context)

docs/adr/0096-declarative-connector-instances.md renamed to docs/adr/0097-declarative-connector-instances.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ADR-0096: Declarative Connector Instances — Provider-Bound `connectors:` Entries Materialized by Generic Executors
1+
# ADR-0097: Declarative Connector Instances — Provider-Bound `connectors:` Entries Materialized by Generic Executors
22

33
**Status**: Accepted (2026-07-15) — implemented in framework#2977
44
**Deciders**: ObjectStack Protocol Architects

docs/adr/PRIORITIZATION.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# ADR Prioritization Review
22

3+
> ⚠️ **STALE (noted 2026-07-16):** this review predates ADR-0050 onward — the directory now
4+
> reaches ADR-0097, so everything from 0050 up is outside its scope, and several statuses it
5+
> records for 0001–0049 have since moved. Treat it as a historical snapshot, not current
6+
> state. The `Status:` header of each ADR file (kept current per the hygiene rule below) is
7+
> the authority; this document is retained for its method and its ranked-plan rationale.
8+
>
9+
> **Status-hygiene rule (adopted 2026-07-16):** when a PR lands the implementation of an ADR,
10+
> the same PR (or an immediate follow-up) must flip that ADR's `Status:` line to
11+
> `Accepted — implemented` with one line of evidence (key file + test). An ADR left
12+
> `Proposed` after its code ships misleads every later reader — this is the same
13+
> no-silently-stale discipline as ADR-0078, applied to the ADRs themselves.
14+
315
> **Date:** 2026-06-12
416
> **Scope:** All 49 ADRs in `docs/adr/`, cross-checked against actual code on `main`.
517
> **Method:** Every unfinished ADR was read in full and its `Status:` header was

examples/app-showcase/objectstack.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export default defineStack({
109109
// • slack → registered so TaskCompletedSlackFlow resolves its connector;
110110
// live posting needs a real bot token (set SLACK_BOT_TOKEN).
111111
// • openapi → option-less: contributes only the `openapi` provider factory
112-
// (ADR-0096), which materializes the StatusOpenApiConnector
112+
// (ADR-0097), which materializes the StatusOpenApiConnector
113113
// declarative instance below — its OpenAPI document is a
114114
// package-relative FILE PATH read at boot (#3016).
115115
plugins: [
@@ -197,7 +197,7 @@ export default defineStack({
197197
// Declarative REST endpoints (object_operation + flow) — the metadata
198198
// counterpart of the code-mounted recalc endpoint (see src/system/apis/).
199199
apis: allApis,
200-
// Declarative `connectors:` — both kinds (ADR-0096): provider-bound
200+
// Declarative `connectors:` — both kinds (ADR-0097): provider-bound
201201
// INSTANCES (StatusApiConnector via `rest`; StatusOpenApiConnector via
202202
// `openapi` with a package-relative file-path spec, #3016) materialized into
203203
// live, dispatchable connectors at boot, plus a CATALOG DESCRIPTOR

examples/app-showcase/src/automation/flows/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,22 +395,22 @@ export const TaskCompletedRestPingFlow = defineFlow({
395395
});
396396

397397
/**
398-
* Declarative Connector Ping — the worked ADR-0096 example: a `connector_action`
398+
* Declarative Connector Ping — the worked ADR-0097 example: a `connector_action`
399399
* dispatching a **provider-bound declarative connector instance**.
400400
*
401401
* Where {@link TaskCompletedRestPingFlow} targets the `rest` connector a *plugin*
402402
* registered (ConnectorRestPlugin, ADR-0018 §Addendum), this flow targets
403403
* `showcase_status_api` — a connector declared as pure metadata in
404404
* src/system/connectors/ and *materialized* into the registry at boot by the
405-
* `rest` generic executor (ADR-0096). Nothing registered it in code: the
405+
* `rest` generic executor (ADR-0097). Nothing registered it in code: the
406406
* `connectors:` entry named `provider: 'rest'`, and the automation service turned
407407
* it into a live connector. On task creation the flow issues `GET /api/v1/health`
408408
* through it; the call and its `{ status: 'ok' }` response are captured on the
409409
* flow run, proving the declarative path dispatches end-to-end.
410410
*/
411411
export const ShowcaseDeclarativeConnectorPingFlow = defineFlow({
412412
name: 'showcase_declarative_connector_ping',
413-
label: 'Declarative Connector Ping (ADR-0096)',
413+
label: 'Declarative Connector Ping (ADR-0097)',
414414
description:
415415
'Dispatches GET /api/v1/health through showcase_status_api — a provider-bound connector instance materialized from pure metadata at boot.',
416416
type: 'autolaunched',

examples/app-showcase/src/coverage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export const STACK_COLLECTION_COVERAGE: Record<string, KindCoverage> = {
179179
status: 'demonstrated',
180180
files: ['src/system/connectors/index.ts', 'src/automation/flows/index.ts'],
181181
notes:
182-
'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.',
182+
'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.',
183183
},
184184
};
185185

examples/app-showcase/src/system/connectors/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { defineConnector, type Connector } from '@objectstack/spec/integration';
44

55
/**
6-
* Declarative `connectors:` — the collection now holds BOTH kinds (ADR-0096):
6+
* Declarative `connectors:` — the collection now holds BOTH kinds (ADR-0097):
77
*
88
* 1. **Provider-bound instance** ({@link StatusApiConnector}) — a live,
99
* dispatchable connector authored as pure metadata. It names a `provider`
@@ -14,7 +14,7 @@ import { defineConnector, type Connector } from '@objectstack/spec/integration';
1414
* indistinguishable from a hand-written connector — `connector_action`
1515
* dispatches it and `GET /connectors` lists it. {@link
1616
* file://../../automation/flows/index.ts | ShowcaseDeclarativeConnectorPingFlow}
17-
* calls it end-to-end. This is the #2977 / ADR-0096 upgrade of what used to
17+
* calls it end-to-end. This is the #2977 / ADR-0097 upgrade of what used to
1818
* be a purely descriptor-only collection.
1919
*
2020
* 2. **Catalog descriptor** ({@link ErpCatalogConnector}, the #2612 interim
@@ -31,7 +31,7 @@ import { defineConnector, type Connector } from '@objectstack/spec/integration';
3131
*/
3232

3333
/**
34-
* ADR-0096 provider-bound instance — declared as pure metadata, materialized
34+
* ADR-0097 provider-bound instance — declared as pure metadata, materialized
3535
* into a live `rest` connector at boot by ConnectorRestPlugin's provider factory
3636
* (which the plugin registers even though, here, it is also configured with a
3737
* hand-wired `rest` connector). Points at the running server itself, so
@@ -45,7 +45,7 @@ export const StatusApiConnector = defineConnector({
4545
label: 'Status API (Declarative REST Instance)',
4646
type: 'api',
4747
description:
48-
'Provider-bound declarative connector instance (ADR-0096): authored as metadata, materialized into a live, ' +
48+
'Provider-bound declarative connector instance (ADR-0097): authored as metadata, materialized into a live, ' +
4949
'dispatchable `rest` connector at boot. Unlike the ERP descriptor below, this one IS callable from a flow ' +
5050
'connector_action and appears in GET /connectors.',
5151
provider: 'rest',
@@ -59,7 +59,7 @@ export const StatusApiConnector = defineConnector({
5959
auth: { type: 'none' },
6060
});
6161
/**
62-
* ADR-0096 provider-bound instance, **file-path spec** form (#3016): the OpenAPI
62+
* ADR-0097 provider-bound instance, **file-path spec** form (#3016): the OpenAPI
6363
* document lives next to this file (`status-openapi.json`) and is referenced by
6464
* a path resolved relative to THIS package's root at materialization — reads
6565
* are confined to the package root (absolute / `..`-escaping paths are
@@ -76,7 +76,7 @@ export const StatusOpenApiConnector = defineConnector({
7676
label: 'Status API (Declarative OpenAPI Instance, File-Path Spec)',
7777
type: 'api',
7878
description:
79-
'Provider-bound declarative connector instance (ADR-0096) whose OpenAPI document is referenced as a ' +
79+
'Provider-bound declarative connector instance (ADR-0097) whose OpenAPI document is referenced as a ' +
8080
'package-relative file path (#3016) and read at boot, confined to the package root. Materialized into a live ' +
8181
'`openapi` connector — getHealth dispatches GET /api/v1/health against the running server.',
8282
provider: 'openapi',
@@ -102,7 +102,7 @@ export const ErpCatalogConnector = defineConnector({
102102
authentication: { type: 'api-key', key: 'SET_AT_INSTALL_TIME', headerName: 'X-API-Key' },
103103
// Descriptor-level action catalog: key + label + I/O JSON Schemas. Note the
104104
// deliberate absence of any execution binding (HTTP method/path) — that is
105-
// what keeps descriptors inert today and what ADR-0096's provider binding
105+
// what keeps descriptors inert today and what ADR-0097's provider binding
106106
// supplies declaratively.
107107
actions: [
108108
{

packages/connectors/connector-mcp/src/connector-mcp-plugin.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createMcpProviderFactory, MCP_PROVIDER_KEY } from './mcp-provider.js';
77

88
/**
99
* Minimal surface of the automation engine this plugin depends on — the
10-
* connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0096).
10+
* connector registry (ADR-0018 §Addendum) plus the provider registry (ADR-0097).
1111
* Kept structural so the plugin needs no runtime dependency on
1212
* `@objectstack/service-automation`.
1313
*/
@@ -24,7 +24,7 @@ export interface ConnectorRegistrySurface {
2424
}
2525

2626
/**
27-
* Options for {@link ConnectorMcpPlugin}. All optional (ADR-0096): with no
27+
* Options for {@link ConnectorMcpPlugin}. All optional (ADR-0097): with no
2828
* `transport` the plugin contributes only the `mcp` provider factory — so a
2929
* stack can declare `provider: 'mcp'` instances as pure metadata. Supply a
3030
* `transport` to ALSO connect one hand-wired MCP server at `start()`.
@@ -34,7 +34,7 @@ export interface ConnectorMcpPluginOptions extends Partial<McpConnectorOptions>
3434
/**
3535
* ConnectorMcpPlugin — contributes the generic MCP adapter (ADR-0024) in two forms:
3636
*
37-
* 1. **Provider factory** (`mcp`, ADR-0096): registered at `init()` so the
37+
* 1. **Provider factory** (`mcp`, ADR-0097): registered at `init()` so the
3838
* automation service can materialize declarative `provider: 'mcp'`
3939
* `connectors:` entries — connecting to the server and mapping its tools to
4040
* connector actions — at boot.
@@ -65,7 +65,7 @@ export class ConnectorMcpPlugin implements Plugin {
6565
}
6666

6767
async init(ctx: PluginContext): Promise<void> {
68-
// Contribute the `mcp` provider factory (ADR-0096) before the automation
68+
// Contribute the `mcp` provider factory (ADR-0097) before the automation
6969
// service materializes declarative instances during its start().
7070
const automation = this.tryGetAutomation(ctx);
7171
if (automation && typeof automation.registerConnectorProvider === 'function') {

packages/connectors/connector-mcp/src/mcp-provider.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22
//
3-
// ADR-0096 — the `mcp` provider factory: materialize a declarative
3+
// ADR-0097 — the `mcp` provider factory: materialize a declarative
44
// `provider: 'mcp'` connector instance by connecting to the server (an injected
55
// fake client here), listing its tools, and mapping them to actions.
66

@@ -33,7 +33,7 @@ function ctx(partial: Partial<ConnectorProviderContext> & Pick<ConnectorProvider
3333
return { name: 'github', label: 'GitHub', type: 'api', ...partial };
3434
}
3535

36-
describe('mcp provider factory (ADR-0096)', () => {
36+
describe('mcp provider factory (ADR-0097)', () => {
3737
it('advertises the mcp provider key', () => {
3838
expect(MCP_PROVIDER_KEY).toBe('mcp');
3939
});

0 commit comments

Comments
 (0)