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
5 changes: 5 additions & 0 deletions .changeset/draft-inherit-cross-scope-11087.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
'@objectstack/metadata-protocol': patch
---

Draft package inheritance (#11087) resolves the overlaid active row with the same two-scope reach as `listDrafts` (caller org + env-wide) — an org-scoped console save now inherits from the env-wide active base instead of finding nothing in its own scope.
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,6 +131,28 @@ describe('SysMetadataRepository draft-save package inheritance (#11087)', () =>
expect(draft.package_id ?? null).toBeNull();
});

it('an ORG-scoped save inherits from the env-wide active row — the ADR-0005 overlay reach (#11087 layer 1b)', async () => {
// The console's session carries an active org, so its draft lands in the
// org scope while the active base row is env-wide. A same-org-only
// inheritance lookup finds nothing there (measured live), so the reach
// must mirror listDrafts' $or contract.
const engine = makeFakeEngine([
{
type: REF.type, name: REF.name, organization_id: null, state: 'active',
package_id: 'app.k9qk', metadata: '{"label":"Member"}', checksum: 'sha-active', version: 1,
},
]);
const repo = new SysMetadataRepository({
engine: engine as never,
organizationId: 'org_1',
orgLabel: 'org_1',
} as never);
await repo.put(REF, { label: 'Member v2' }, { parentVersion: null, actor: 't', state: 'draft' as const });
const draft = engine.rows.find((r) => r.state === 'draft')!;
expect(draft.organization_id).toBe('org_1'); // org-scoped save, unchanged
expect(draft.package_id).toBe('app.k9qk'); // inherited across scopes
});

it('adopts a pre-fix orphan draft (NULL package) instead of forking a second draft row', async () => {
const engine = makeFakeEngine([
{
Expand Down
24 changes: 21 additions & 3 deletions packages/metadata-protocol/src/sys-metadata-repository.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -464,9 +464,27 @@ export class SysMetadataRepository implements MetadataRepository {
// ADR-0048), and with no active row — a brand-new item drafted first —
// there is nothing to inherit and the package-less semantics stand.
if (state === 'draft' && opts.packageId == null) {
const activeRow = await this.engine.findOne('sys_metadata', {
where: this.whereFor(ref, 'active'),
});
// The overlaid base is resolved with the SAME two-scope reach the draft
// list applies (`listDrafts`' $or contract): an org-scoped caller's
// active row usually lives ENV-WIDE (`organization_id IS NULL`) — the
// ADR-0005 overlay order — so a same-org-only lookup here found nothing
// for exactly the console-session saves this inheritance exists for
// (measured live: an org-scoped view draft stayed `package_id NULL`
// over an env-wide active row bound to `app.k9qk`).
const activeWhere: Record<string, unknown> = {
type: ref.type,
name: ref.name,
state: 'active',
};
if (this.organizationId != null) {
activeWhere.$or = [
{ organization_id: this.organizationId },
{ organization_id: null },
];
} else {
activeWhere.organization_id = null;
}
const activeRow = await this.engine.findOne('sys_metadata', { where: activeWhere });
const activePkg = (activeRow as { package_id?: string | null } | null)?.package_id ?? null;
if (activePkg) targetPackageId = activePkg;
}
Expand Down
Loading