Skip to content

Commit 58ea076

Browse files
os-zhuangclaude
andcommitted
docs(adr): ADR-0078 — segment the ObjectStackProtocol god-interface (ISP); keep the engine a pure primitive
Records a deep kernel-architecture finding: the engine (objectql) is a clean, governance-free primitive and should be protected; the real debt is the central wire contract `ObjectStackProtocol` — a 70-method / 11-domain god-interface (60/70 optional; no consumer uses >11%). Decision: segment the contract per ISP into DataProtocol + MetadataProtocol + optional capability protocols (Analytics/Feed/Realtime/Notification/View/…), keeping a composed alias for back-compat. The contract split is spec/type-level and incremental; the impl restructure + package rename ride the cross-repo window together with ADR-0076 Step 2. Also adds a forward-pointer from ADR-0076 to ADR-0078. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d710092 commit 58ea076

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

docs/adr/0076-objectql-core-tiering.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ADR-0076: objectql is the data engine — relocate metadata management (protocol) out of it; enforce the boundary; defer the engine repo-split
22

33
**Status**: Proposed (2026-06-28, rev. 4 — data-driven; centerpiece corrected to protocol relocation) — v12 assessment
4+
**Follow-up**: [ADR-0078](./0078-protocol-segmentation.md) — segments the `ObjectStackProtocol` god-interface (the deeper contract shape this ADR did not address; engine stays a pure primitive).
45
**Deciders**: ObjectStack Protocol Architects
56
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (sys_metadata overlay substrate), [ADR-0025](./0025-plugin-package-distribution.md) (plugin package distribution), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (open-core boundary), [ADR-0048](./0048-cross-package-metadata-collision.md) (package id is the addressing unit), [ADR-0066](./0066-unified-authorization-model.md) (secure-by-default, posture-gated bypass)
67
**Consumers**: **new** `@objectstack/metadata-protocol` (receives `protocol` + `sys-metadata-repository` + `metadata-diagnostics`), `@objectstack/objectql` (loses protocol → becomes a lean data engine; keeps a back-compat re-export), `@objectstack/metadata-core` (gains the `SysMetadataEngine` interface), `@objectstack/plugin-security`, `@objectstack/plugin-sharing`, `@objectstack/spec`, and out-of-tree embedders — notably `../objectbase` (its `gateway`).
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# ADR-0078: Segment the `ObjectStackProtocol` god-interface (ISP); keep the engine a pure primitive
2+
3+
**Status**: Proposed (2026-06-28) — kernel architecture assessment; execution gated to the cross-repo window (with ADR-0076 Step 2)
4+
**Deciders**: ObjectStack Protocol Architects
5+
**Builds on**: [ADR-0076](./0076-objectql-core-tiering.md) (objectql core tiering — protocol relocated to its own package; lean `./core` entry; Step 2 deferred), [ADR-0066](./0066-unified-authorization-model.md) (governance is pluggable, not in the engine), [ADR-0025](./0025-plugin-package-distribution.md) / [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (open-core boundary)
6+
**Consumers**: `@objectstack/spec` (`api/protocol.zod.ts` — the contract), `@objectstack/metadata-protocol` (the impl), `@objectstack/rest` (dispatcher), `@objectstack/objectql` (engine + boot), and the domain services that already implement slices (`service-analytics`, `service-realtime`, `service-messaging`, …).
7+
8+
**Premise**: A deep review of the data kernel (prompted by "why does objectql depend on protocol, and protocol isn't only metadata") found that the engine is well-designed but the **central wire contract is a god-interface**. This ADR records that finding and the decision to segment the contract, separately from the packaging work already done in ADR-0076.
9+
10+
---
11+
12+
## TL;DR
13+
14+
1. **The engine is the crown jewel and it is correct — protect it.** `engine.ts` (~3.3k LOC) hard-codes **zero** governance: RLS, RBAC, field-masking, owner stamping, tenant `organization_id` are all in plugins via `registerMiddleware` + hooks. The driver port is minimal (CRUD+DDL). Boot ordering is sound. **Do not add protocol/governance concerns to the engine.**
15+
2. **The real debt is `ObjectStackProtocol`: a 70-method, 11-domain god-interface.** Data(9) + Metadata(8) + Feed(13) + Notifications(7) + Realtime(6) + Packages(6) + Views(5) + Permissions(3) + Workflows(3) + AI(3) + i18n(3) + Analytics(2) + Automation(1). **60/70 are optional; no consumer uses >11%** (REST ~11%, objectql ~2%, analytics ~3%).
16+
3. **Decision: segment the contract per ISP** into `DataProtocol`, `MetadataProtocol`, and optional capability protocols (Analytics/Feed/Realtime/Notification/View/…). Keep a composed alias `ObjectStackProtocol = DataProtocol & MetadataProtocol & Partial<…>` for back-compat. This is mostly **spec/type-level and incremental**.
17+
4. **The implementation and package naming follow the contract.** Once split, the `DataProtocol` impl is thin (near-engine), the `MetadataProtocol` impl is the heavy control plane; the capability domains are served by their **already-existing** services, not bundled. `@objectstack/metadata-protocol` is then accurately named (or split). Done at the **cross-repo window with ADR-0076 Step 2** (cloud boots `ObjectQLPlugin` directly in ~8 sites).
18+
19+
## Context
20+
21+
### What the review established
22+
23+
| Layer | Finding | Verdict |
24+
|---|---|---|
25+
| `IDataDriver` port | CRUD + DDL only | minimal, clean |
26+
| **engine (`objectql`)** | in-process data primitive; **zero governance hard-coded** (verified: no RLS/permission/tenant logic in `engine.ts`); governance is 100% middleware/hooks (`plugin-security`/`plugin-sharing`/`org-scoping`) | **clean primitive — protect** |
27+
| capability seam | `registerMiddleware((ctx,next)=>…)` onion + per-object/priority hooks | clean, well-defined |
28+
| **`ObjectStackProtocol`** | 70 methods, 11 unrelated domains, 60 optional, no consumer >11% | **god-interface — the debt** |
29+
| `ObjectStackProtocolImplementation` | ~5.6k LOC; *large but maintainable* (uses only public engine API; repository pattern isolates overlay) | size is **driven by the god-interface** |
30+
31+
### Why the god-interface is the root cause
32+
33+
The contract conflates the **data plane** (CRUD), the **metadata control plane** (sys_metadata mgmt), and a long tail of **capability domains** (feed, realtime, notifications, analytics, AI, i18n, …) that **already have their own services**. The interface merely *aggregates* them. Consequences:
34+
35+
- Anything that "implements the protocol" is nominally on the hook for 11 domains → forces a monolithic ~5.6k-LOC facade.
36+
- The contract **cannot be versioned** — a change in any one domain perturbs the whole protocol.
37+
- It is the shared root cause of the symptoms surfaced earlier: the misleading `metadata-protocol` name (the package is *not* only metadata — it also carries the thin data facade), and the difficulty explaining "what protocol is."
38+
39+
### What ADR-0076 already did (and did not)
40+
41+
ADR-0076 relocated the protocol implementation into its own package and added the lean `@objectstack/objectql/core` entry (shipped in #2415). That fixed **packaging and the engine-embed story**. It did **not** address the **shape of the contract** — that is this ADR.
42+
43+
## Decision
44+
45+
### D1 — The engine stays a pure, governance-free primitive [ratify]
46+
No protocol, metadata-management, or governance logic enters `engine.ts` / `@objectstack/objectql/core`. Governance remains pluggable (ADR-0066). The `./core` boundary ratchet (ADR-0076) guards this. This is the kernel's most valuable property; protect it over any tidiness goal.
47+
48+
### D2 — Segment `ObjectStackProtocol` per the Interface Segregation Principle [new]
49+
Split the single interface in `spec/api/protocol.zod.ts` into focused contracts:
50+
- **`DataProtocol`**`findData/getData/createData/updateData/deleteData` (+ batch). Thin wire-normalizers over the engine.
51+
- **`MetadataProtocol`**`getMetaItem(s)/saveMetaItem/publishMetaItem/deleteMetaItem/getMetaTypes/getDiscovery/getUiView` + draft/publish, locks (ADR-0010), commits (ADR-0067), package ownership (ADR-0048), `loadMetaFromDb`. The heavy control plane.
52+
- **Optional capability protocols**`AnalyticsProtocol`, `FeedProtocol`, `RealtimeProtocol`, `NotificationProtocol`, `ViewProtocol`, … each owned by its existing service, each independently optional and versionable.
53+
- **Back-compat**: keep `ObjectStackProtocol = DataProtocol & MetadataProtocol & Partial<AnalyticsProtocol & FeedProtocol & …>` as a composed alias so current callers/types keep working. The split is largely **type-level and can land incrementally** (define sub-interfaces first; narrow consumers over time).
54+
55+
### D3 — Implementation and packaging follow the contract [new]
56+
- `DataProtocol` impl is thin (normalize → engine); it may live next to the engine-adjacent transport layer rather than inside the metadata package.
57+
- `MetadataProtocol` impl is the substantial control plane and is the true content of today's `@objectstack/metadata-protocol`.
58+
- Capability domains are **served by their existing services** (`service-analytics` / `service-realtime` / `service-messaging` / …), not re-bundled into one facade.
59+
- **Rename**: once the data facade is separated, `@objectstack/metadata-protocol` is accurately the metadata-protocol package (or is renamed to reflect the data/metadata split). Treat the rename as breaking and bundle it with the cross-repo window.
60+
61+
### D4 — Sequencing: contract now (incremental, low-risk), packaging at the cross-repo window [new]
62+
- The **contract segmentation (D2)** is spec/type-level, mostly non-breaking via the composed alias, and may begin incrementally at any time.
63+
- The **impl/package restructure + rename (D3)** and **ADR-0076 Step 2** (making the engine package itself protocol-free) share the same blast radius — cloud constructs `ObjectQLPlugin` directly in ~8 sites — and must land in a **coordinated framework+cloud window** (a major). Do them together.
64+
65+
## What stays unchanged
66+
- The engine, the driver port, and the middleware/hook capability seam (ADR-0066/0076).
67+
- Existing domain services (analytics/realtime/messaging) keep their implementations; only the *contract* stops aggregating them.
68+
- `@objectstack/objectql/core` remains the lean embed entry.
69+
70+
## Consequences
71+
- **+** A versionable, segregated contract; implementers depend only on the slice they serve; SDK/codegen per-domain; the ~5.6k-LOC facade is no longer forced.
72+
- **+** Resolves the naming confusion at the root (data vs metadata vs capability domains become distinct).
73+
- **+** The high-leverage part (D2) is incremental and low-risk (type-level + composed alias).
74+
- **** A full split touches `spec` (the contract) and every implementer/consumer; the rename is breaking → must ride the coordinated cross-repo major.
75+
- **Risk**: doing D2 half-way (sub-interfaces defined but consumers never narrowed) yields churn with little gain. Mitigation: pair each new sub-interface with at least one consumer narrowed to it, and track coverage.
76+
77+
## Open questions
78+
1. **Data facade home** — does `DataProtocol` impl live in the engine-adjacent transport package, in `rest`, or in a small `@objectstack/protocol-data`? (It is thin and transport-shaped.)
79+
2. **Metadata package name** — keep `@objectstack/metadata-protocol` for the `MetadataProtocol` impl, or rename to `@objectstack/metadata-runtime` / `@objectstack/protocol-metadata`?
80+
3. **Per-domain versioning** — once segmented, do capability protocols get independent version markers / a capability-discovery method (`getCapabilities()`)?
81+
4. **Order vs ADR-0076 Step 2** — segment the contract first (incremental) and let Step 2 + rename follow, or do all in one coordinated PR set? (Leaning: contract first, packaging at the window.)

0 commit comments

Comments
 (0)