Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter by claude[bot] · Pull Request #13665 · objectstack-ai/objectstack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter by claude[bot] · Pull Request #13665 · objectstack-ai/objectstack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter by claude[bot] · Pull Request #13665 · objectstack-ai/objectstack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter by claude[bot] · Pull Request #13665 · objectstack-ai/objectstack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter by claude[bot] · Pull Request #13665 · objectstack-ai/objectstack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter by claude[bot] · Pull Request #13665 · objectstack-ai/objectstack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/environment-id-stale-jsdoc.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/metadata-protocol": patch
"@objectstack/objectql": patch
---

docs(metadata-protocol,objectql): stop teaching the retired `environment_id` column stamp/filter (#13434)

Prose only — no behaviour changes, and the `environmentId` option is untouched
and still very much alive. What changes is what the docstrings on the two
plugin options interfaces teach, and those docstrings ship: they are emitted
into both packages' published `.d.ts` on the exported `ObjectQLPluginOptions`
and `MetadataProtocolPluginOptions`, so this is the tooltip a consumer
configuring per-environment scoping actually reads.

Seven passages still described `saveMetaItem` stamping an `environment_id`
column on new `sys_metadata` rows and `loadMetaFromDb` filtering by it. That
job was retired by ADR-0005 (revised 2026-05) / ADR-0006 v4 when each
environment got its own physical database: `organization_id` is the isolation
key that survived. The three files carry **zero** non-comment occurrences of
`environment_id` (positive control in the same files under the same filter:
`organization_id` answers 44 in `protocol.ts`), `loadMetaFromDb`'s actual
where-clause is `{ state: 'active', organization_id: null }`, and
`SysMetadataObject.environment_id`, `DatabaseLoaderOptions.environmentId` and
`DatabaseLoader`'s pin test already say so.

The danger was not staleness but subject: the prose described an **isolation
barrier**, so an author reading it would believe an environment-level boundary
existed inside `sys_metadata`. The replacements say what is true now and say
that the column job was retired, rather than deleting the sentence — a bare
deletion loses the signal for the next reader who wonders whether environment
scoping was ever there.

`environmentId` keeps every job it actually has, and the corrected prose now
names them from measurement: the ADR-0005 overlay-whitelist gate, the ADR-0010
metadata-lock evaluation, the SchemaRegistry hydration/listing posture, the
metadata-service bridge skip, and the local metadata-storage provisioning
decision.
13 changes: 9 additions & 4 deletions packages/metadata-protocol/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,10 +48,15 @@ import type { MetadataAuthoringChannel } from './protocol.js';

export interface MetadataProtocolPluginOptions {
/**
* Per-project scope (cloud per-env kernels). When set, `saveMetaItem`
* stamps `environment_id` on new sys_metadata rows, `loadMetaFromDb`
* filters by it, and the metadata-storage objects are NOT provisioned
* locally (per-project kernels source metadata from the control plane).
* Per-environment scope (cloud per-env kernels). Setting it does NOT put an
* `environment_id` column on anything: `saveMetaItem` no longer stamps that
* column and `loadMetaFromDb` no longer filters by it (ADR-0005 revised
* 2026-05 — each environment owns its own database, and `organization_id`
* is the isolation key that survived). What it still decides is that the
* metadata-storage objects are NOT provisioned locally (per-environment
* kernels source metadata from the control plane), plus the other topology
* postures listed on `ObjectStackProtocolImplementation`'s own
* `environmentId` field.
* Mirrors `ObjectQLPluginOptions.environmentId` — pass the same value.
*/
environmentId?: string;
Expand Down
58 changes: 34 additions & 24 deletions packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4099,18 +4099,20 @@ export class ObjectStackProtocolImplementation implements
private engine: MetadataHostEngine;
private getServicesRegistry?: () => Map<string, any>;
/**
* Project scope applied to sys_metadata reads/writes. When undefined
* (single-kernel deployments), rows land in / come from the
* platform-global bucket (`environment_id IS NULL`). When set, every
* saveMetaItem insert/update and loadMetaFromDb query is filtered by
* `environment_id = environmentId`, so per-project kernels see only their own
* metadata even if several projects share the same physical database.
*
* [#6710] Row scoping ONLY. This key keeps every one of its other jobs —
* the `environment_id` stamp/filter, the ADR-0005 overlay-whitelist gate,
* the local metadata-storage provisioning decision — but it no longer
* decides whether the #4463 runtime authoring rules run.
* See {@link authoringChannel}.
* Environment scope this protocol instance is bound to. It is a topology
* declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so no saveMetaItem insert/update
* writes an `environment_id` column and no loadMetaFromDb query constrains
* on one — the isolation key that survived is `organization_id`, which is
* what `ensureOverlayIndex` below indexes and what `loadMetaFromDb`'s own
* where-clause names. Legacy rows may still carry the column; nothing here
* reads it.
*
* [#6710] Topology ONLY. This key keeps every one of its other jobs — the
* ADR-0005 overlay-whitelist gate, the ADR-0010 metadata-lock evaluation,
* the SchemaRegistry hydration/listing posture, the local metadata-storage
* provisioning decision — but it no longer decides whether the #4463
* runtime authoring rules run. See {@link authoringChannel}.
*
* [#7674] …and no longer whether the #3050 pre-persistence authoring gate
* runs either. The sentence above used to list "the #3050 authoring-gate
Expand DownExpand Up@@ -4489,12 +4491,14 @@ export class ObjectStackProtocolImplementation implements
// mechanism, because the failure mode being designed out is precisely
// "a new assembly variant nobody thought about".
//
// `environmentId` keeps its row-scoping jobs — the `environment_id`
// stamp/filter and the ADR-0005 overlay-whitelist gate. [#7674] It no
// longer keys the #3050 authoring gate below either: #6710 re-keyed
// this activation and left that one on the retired proxy, which cost
// the ADR-0090 D11 object posture gate every host-config deployment
// until #7674 finished the move.
// `environmentId` keeps its topology jobs — the ADR-0005
// overlay-whitelist gate and the registry / provisioning postures. It
// never stamps or filters an `environment_id` column: ADR-0005 (revised
// 2026-05) retired that job when each environment got its own database.
// [#7674] It no longer keys the #3050 authoring gate below either:
// #6710 re-keyed this activation and left that one on the retired
// proxy, which cost the ADR-0090 D11 object posture gate every
// host-config deployment until #7674 finished the move.
if (this.authoringChannel === 'package-author') return [];
if (evt.state !== 'active') return [];
// `os migrate meta --stored --apply` rewrites rows that ALREADY EXIST
Expand DownExpand Up@@ -5084,10 +5088,12 @@ export class ObjectStackProtocolImplementation implements
}

/**
* Exposes the project scope the protocol is bound to. Consumers like
* Exposes the environment scope the protocol is bound to. Consumers like
* the HTTP dispatcher use this to decide whether to trust the process-
* wide SchemaRegistry or whether they must route a read through the
* protocol's environment_id-filtered lookup.
* wide SchemaRegistry (an unscoped kernel owns it) or whether they must
* route the read through this protocol's own sys_metadata lookup — which
* is scoped by `organization_id`, never by an `environment_id` column
* (ADR-0005 revised 2026-05).
*/
getProjectId(): string | undefined {
return this.environmentId;
Expand DownExpand Up@@ -19635,9 +19641,13 @@ export class ObjectStackProtocolImplementation implements
* Loads all active metadata records and registers them in the in-memory registry.
* Safe to call repeatedly — idempotent (latest DB record wins).
*
* Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. Scope filter
* (`environment_id = this.environmentId ?? null`) keeps tenants isolated.
* Per ADR-0005, environment-kernel mode ALSO hydrates from sys_metadata —
* customization overlay rows must survive restart. The where-clause below
* is `{ state: 'active', organization_id: null }`: env-wide rows only, with
* per-org overlays left to `getMetaItem`'s on-demand read so one org's
* customization never lands in the process-wide SchemaRegistry. There is no
* `environment_id` constraint — ADR-0005 (revised 2026-05) gave each
* environment its own database and `organization_id` is the isolation key.
*
* #3903 — two contract duties run per row, and their split is deliberate:
*
Expand Down
27 changes: 21 additions & 6 deletions packages/objectql/src/plugin.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,18 +62,33 @@ function hasLoadMetaFromDb(service: unknown): service is ProtocolWithDbRestore {
/**
* Options for ObjectQLPlugin.
*
* `environmentId` scopes all metadata writes + reads to a specific project.
* When set, `protocol.saveMetaItem` stamps `environment_id = <environmentId>` on
* new sys_metadata rows, and `protocol.loadMetaFromDb` filters by the same
* column. Leave undefined in single-kernel / self-hosted mode — rows land
* in the platform-global scope (environment_id IS NULL).
* `environmentId` declares that this kernel serves ONE environment. It is a
* topology declaration, not a row filter. ADR-0005 (revised 2026-05) gave every
* environment its own physical database, so `protocol.saveMetaItem` stamps no
* `environment_id` column on new sys_metadata rows and `protocol.loadMetaFromDb`
* constrains on none — the isolation key that survived is `organization_id`.
* The column is deprecated on the metadata tables and new writes leave it NULL;
* `SysMetadataObject.environment_id` and `DatabaseLoaderOptions.environmentId`
* both carry that note, and `DatabaseLoader`'s own pin test asserts the write
* does not set it.
*
* What setting it still decides is topology: this kernel skips the boot-time
* `sys_metadata` hydration unless `hydrateMetadataFromDb` opts back in, skips
* bridging the process-wide SchemaRegistry into the metadata service (that
* registry is shared, so the bridge would leak sibling environments' objects),
* and does not provision the metadata-storage platform objects locally. Leave
* undefined in single-kernel / self-hosted mode.
*/
export interface ObjectQLPluginOptions {
/** Optional pre-built engine. When absent, one is lazily created in init. */
ql?: ObjectQL;
/** Passed to `new ObjectQL(...)` when `ql` is not supplied. */
hostContext?: Record<string, any>;
/** Scope sys_metadata reads/writes to this project. */
/**
* Declare that this kernel serves one environment. Topology only — it does
* not scope sys_metadata rows by an `environment_id` column. See the
* interface docblock above.
*/
environmentId?: string;
/**
* [#6710] Which authoring channel this kernel's metadata writes arrive on —
Expand Down
Loading