Skip to content

Commit 9cd8fd0

Browse files
authored
Merge pull request #253 from objectstack-ai/copilot/continue-development-based-on-roadmap
2 parents 46526d9 + 4813ea4 commit 9cd8fd0

File tree

17 files changed

+2371
-6
lines changed

17 files changed

+2371
-6
lines changed

ROADMAP.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# ObjectOS Roadmap
22

3-
> **Version**: 8.0.0
3+
> **Version**: 9.0.0
44
> **Date**: February 12, 2026
5-
> **Status**: Phase MTechnical Debt Resolution ✅ COMPLETE
5+
> **Status**: Phase NEnterprise Features 🔄 In Progress
66
> **Spec SDK**: `@objectstack/spec@2.0.7`
77
> **ObjectUI**: `@object-ui/*@2.0.0`
88
@@ -48,6 +48,7 @@ The integration of **@object-ui** (6 packages at v2.0.0) marks a strategic shift
4848
| Permissions | `@objectos/permissions` ||
4949
| Realtime | `@objectos/realtime` ||
5050
| Storage | `@objectos/storage` ||
51+
| Telemetry | `@objectos/telemetry` ||
5152
| Workflow | `@objectos/workflow` ||
5253

5354
**Server Metrics**: 21,947 source lines · 107 TypeScript files · 47 test files · 350+ tests
@@ -97,7 +98,8 @@ The integration of **@object-ui** (6 packages at v2.0.0) marks a strategic shift
9798
| J | Workflow & Automation UI | Feb 2026 ||
9899
| K | Offline & Sync | Feb 2026 ||
99100
| L | Polish & Performance | Feb 2026 ||
100-
| **M** | **Technical Debt Resolution** | **Feb–Sep 2026** | **🔄 In Progress** |
101+
| **M** | **Technical Debt Resolution** | **Feb–Sep 2026** | **✅ Complete** |
102+
| **N** | **Enterprise Features** | **Feb 2026** | **🔄 In Progress** |
101103

102104
### Phase G Outcomes
103105

@@ -274,6 +276,42 @@ Integrate `@objectos/browser` with the Admin Console for offline-first capabilit
274276

275277
---
276278

279+
## Phase N — Enterprise Features (Current — Feb 2026)
280+
281+
Enterprise-grade capabilities for production multi-tenant deployments and observability.
282+
283+
### N.1 — OpenTelemetry Integration (`@objectos/telemetry`)
284+
285+
New plugin providing OpenTelemetry-compatible distributed tracing.
286+
287+
| # | Task | Priority | Status |
288+
|---|------|:--------:|:------:|
289+
| N.1.1 | TelemetryPlugin with span management and buffered export | 🔴 ||
290+
| N.1.2 | W3C Trace Context propagation (traceparent / tracestate) | 🔴 ||
291+
| N.1.3 | Automatic HTTP request instrumentation (Hono middleware) | 🔴 ||
292+
| N.1.4 | Data operation span creation (CRUD hooks) | 🟡 ||
293+
| N.1.5 | Plugin lifecycle tracing (load/enable hooks) | 🟡 ||
294+
| N.1.6 | OTLP HTTP exporter (Jaeger, Zipkin, Grafana Tempo compatible) | 🔴 ||
295+
| N.1.7 | Console exporter for development | 🟢 ||
296+
| N.1.8 | Probabilistic sampling with configurable rate | 🟡 ||
297+
| N.1.9 | Telemetry stats API (`/api/v1/telemetry/stats`) | 🟢 ||
298+
299+
### N.2 — Multi-tenancy Data Isolation
300+
301+
Extend permissions system with organization-scoped data access control.
302+
303+
| # | Task | Priority | Status |
304+
|---|------|:--------:|:------:|
305+
| N.2.1 | Add `organizationId` to `PermissionContext` | 🔴 ||
306+
| N.2.2 | Add `TenantContext` type for middleware integration | 🔴 ||
307+
| N.2.3 | Automatic tenant field stamping on write operations (create/update) | 🔴 ||
308+
| N.2.4 | Automatic tenant filter on read operations (find/delete) | 🔴 ||
309+
| N.2.5 | Configurable tenant field name (`tenantIsolation`, `tenantField`) | 🟡 ||
310+
| N.2.6 | Metadata fallback for `organizationId` extraction | 🟡 ||
311+
| N.2.7 | 12 tenant isolation tests (write, read, custom field, disabled) | 🟡 ||
312+
313+
---
314+
277315
## Release Timeline
278316

279317
### v1.0.0 — Production Release (Target: March 2026)
@@ -312,8 +350,8 @@ Integrate `@objectos/browser` with the Admin Console for offline-first capabilit
312350

313351
- Phase J.3-J.6: Full Workflow & Automation UI ✅
314352
- Phase K: Offline & Sync ✅
315-
- Multi-tenancy data isolation
316-
- OpenTelemetry integration
353+
- Multi-tenancy data isolation ✅ Phase N.2
354+
- OpenTelemetry integration ✅ Phase N.1
317355

318356
### v2.0.0 — Platform (Target: September 2026)
319357

objectstack.config.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { NotificationPlugin } from '@objectos/notification';
2020
import { PermissionsPlugin } from '@objectos/permissions';
2121
import { createRealtimePlugin } from '@objectos/realtime';
2222
import { StoragePlugin } from '@objectos/storage';
23+
import { TelemetryPlugin } from '@objectos/telemetry';
2324
import { UIPlugin } from '@objectos/ui';
2425
import { WorkflowPlugin } from '@objectos/workflow';
2526
import { resolve } from 'path';
@@ -64,6 +65,15 @@ export default defineStack({
6465
new MetricsPlugin(),
6566
new CachePlugin(),
6667
new StoragePlugin(),
68+
new TelemetryPlugin({
69+
serviceName: 'objectos',
70+
serviceVersion: '0.1.0',
71+
environment: process.env.NODE_ENV || 'development',
72+
exporter: {
73+
protocol: (process.env.OTEL_EXPORTER_PROTOCOL as any) || 'none',
74+
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
75+
},
76+
}),
6777

6878
// Core
6979
new AuthPlugin({
@@ -76,7 +86,9 @@ export default defineStack({
7686
})(),
7787
baseUrl: process.env.BETTER_AUTH_URL || 'http://localhost:5320',
7888
}),
79-
new PermissionsPlugin(),
89+
new PermissionsPlugin({
90+
tenantIsolation: true,
91+
}),
8092
new AuditLogPlugin(),
8193

8294
// Logic

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"@objectos/permissions": "workspace:*",
9090
"@objectos/realtime": "workspace:*",
9191
"@objectos/storage": "workspace:*",
92+
"@objectos/telemetry": "workspace:*",
9293
"@objectos/ui": "workspace:*",
9394
"@objectos/workflow": "workspace:*",
9495
"@objectql/core": "^4.2.0",

packages/permissions/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export type {
5353
// Runtime
5454
PermissionContext,
5555
PermissionCheckResult,
56+
// Multi-tenancy
57+
TenantContext,
5658
// Plugin Config
5759
PermissionPluginConfig,
5860
} from './types.js';

packages/permissions/src/plugin.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export class PermissionsPlugin implements Plugin {
5656
defaultDeny: true,
5757
permissionsDir: './permissions',
5858
cachePermissions: true,
59+
tenantIsolation: false,
60+
tenantField: '_organizationId',
5961
...config,
6062
};
6163

@@ -95,6 +97,11 @@ export class PermissionsPlugin implements Plugin {
9597
// Subscribe to data.* hooks for permission checking
9698
await this.setupEventListeners(context);
9799

100+
// Set up tenant isolation hooks (independent of permission checking)
101+
if (this.config.tenantIsolation) {
102+
await this.setupTenantIsolation(context);
103+
}
104+
98105
context.logger.info('[Permissions Plugin] Initialized successfully');
99106

100107
await context.trigger('plugin.initialized', { pluginId: this.name, timestamp: new Date().toISOString() });
@@ -253,6 +260,70 @@ export class PermissionsPlugin implements Plugin {
253260
this.context?.logger.info('[Permissions Plugin] Event listeners registered');
254261
}
255262

263+
/**
264+
* Set up tenant isolation hooks — separate from permission checking.
265+
* These hooks run independently and enforce organization-scoped data access.
266+
*/
267+
private async setupTenantIsolation(context: PluginContext): Promise<void> {
268+
context.hook('data.beforeCreate', async (data: any) => {
269+
this.applyTenantToWrite(data);
270+
});
271+
272+
context.hook('data.beforeUpdate', async (data: any) => {
273+
this.applyTenantToWrite(data);
274+
});
275+
276+
context.hook('data.beforeDelete', async (data: any) => {
277+
this.applyTenantFilter(data);
278+
});
279+
280+
context.hook('data.beforeFind', async (data: any) => {
281+
this.applyTenantFilter(data);
282+
});
283+
284+
context.logger.info(`[Permissions Plugin] Tenant isolation enabled (field: ${this.config.tenantField})`);
285+
}
286+
287+
/**
288+
* Apply tenant ID to write operations (create/update).
289+
* Stamps the tenant field on the record data so it belongs to the user's organization.
290+
*/
291+
private applyTenantToWrite(data: any): void {
292+
if (!this.config.tenantIsolation) return;
293+
294+
const organizationId = data.organizationId || data.metadata?.organizationId;
295+
if (!organizationId) return;
296+
297+
const tenantField = this.config.tenantField || '_organizationId';
298+
299+
// Stamp the tenant field on the record being written
300+
if (data.doc) {
301+
data.doc[tenantField] = organizationId;
302+
}
303+
if (data.record) {
304+
data.record[tenantField] = organizationId;
305+
}
306+
}
307+
308+
/**
309+
* Apply tenant filter to read/delete operations.
310+
* Ensures queries are automatically scoped to the user's organization.
311+
*/
312+
private applyTenantFilter(data: any): void {
313+
if (!this.config.tenantIsolation) return;
314+
315+
const organizationId = data.organizationId || data.metadata?.organizationId;
316+
if (!organizationId) return;
317+
318+
const tenantField = this.config.tenantField || '_organizationId';
319+
320+
// Merge tenant filter into existing query filters
321+
data.filters = {
322+
...data.filters,
323+
[tenantField]: organizationId,
324+
};
325+
}
326+
256327
/**
257328
* Check permission for a data operation
258329
*/
@@ -266,6 +337,7 @@ export class PermissionsPlugin implements Plugin {
266337

267338
const permissionContext: PermissionContext = {
268339
userId,
340+
organizationId: data.organizationId || data.metadata?.organizationId,
269341
profiles: userProfiles || [],
270342
metadata: data.metadata,
271343
};
@@ -302,6 +374,7 @@ export class PermissionsPlugin implements Plugin {
302374

303375
const permissionContext: PermissionContext = {
304376
userId,
377+
organizationId: data.organizationId || data.metadata?.organizationId,
305378
profiles: userProfiles || [],
306379
metadata: data.metadata,
307380
};

packages/permissions/src/types.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,8 @@ export interface RLSEvaluationResult {
344344
export interface PermissionContext {
345345
/** User ID */
346346
userId: string;
347+
/** Organization / Tenant ID for multi-tenancy data isolation */
348+
organizationId?: string;
347349
/** User's profile name */
348350
profileName?: string;
349351
/** User's profiles (array of profile names) */
@@ -356,6 +358,21 @@ export interface PermissionContext {
356358
metadata?: Record<string, any>;
357359
}
358360

361+
/**
362+
* Tenant context — extracted from authenticated session for data isolation.
363+
* Used by the tenant middleware to scope all data queries to a specific organization.
364+
*/
365+
export interface TenantContext {
366+
/** Organization / Tenant ID */
367+
organizationId: string;
368+
/** Organization slug (for URL routing) */
369+
slug?: string;
370+
/** User ID within the organization */
371+
userId: string;
372+
/** User's role within the organization (e.g., 'owner', 'admin', 'member') */
373+
role?: string;
374+
}
375+
359376
/**
360377
* Permission check result
361378
*/
@@ -384,6 +401,10 @@ export interface PermissionPluginConfig {
384401
cachePermissions?: boolean;
385402
/** Custom storage implementation */
386403
storage?: any; // PermissionStorage
404+
/** Enable multi-tenancy data isolation — automatically scopes data queries to the user's organization */
405+
tenantIsolation?: boolean;
406+
/** Field name used to store the tenant/organization ID on data records (default: '_organizationId') */
407+
tenantField?: string;
387408
}
388409

389410
// ─── Kernel Compliance Types (from @objectstack/spec) ──────────────────────────

0 commit comments

Comments
 (0)