@weaveintel/realm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @weaveintel/realm
2
+
3
+ **One default for everyone, a private copy for anyone — resolved correctly, with a paper trail.**
4
+
5
+ ## Why it exists
6
+
7
+ Ship a piece of configuration once — a prompt, a guardrail, a skill — and in a multi-tenant product
8
+ you immediately want three things that fight each other:
9
+
10
+ 1. a **global default** everyone gets out of the box,
11
+ 2. the ability for one customer to **tweak their own copy** without touching anybody else's, and
12
+ 3. a way, later, to tell whether that tweak has **drifted** from the default it was based on — so a
13
+ product update never silently clobbers a customer's edits, and never leaves them stuck on a stale one.
14
+
15
+ `@weaveintel/realm` gives you exactly that, and the drift check is the same three-way (base / yours /
16
+ theirs) comparison git uses for a merge — just applied to configuration. It runs identically on SQLite
17
+ and Postgres, and builds on the tenant tree from `@weaveintel/identity`.
18
+
19
+ ## The mental model
20
+
21
+ - A **global** record is the shared default.
22
+ - A tenant can **customize** — that makes a private copy (copy-on-write) that remembers where it came from.
23
+ - A tenant can **share** its copy down its part of the org tree, so a parent company's choice flows to
24
+ its subsidiaries.
25
+ - When someone asks for a config, resolution picks the **nearest owner**: your own copy beats a parent's
26
+ shared copy, which beats the global default.
27
+
28
+ ## Quickstart
29
+
30
+ ```ts
31
+ import { createInMemoryRealmStore, createRealmResolver } from '@weaveintel/realm';
32
+ import { createInMemoryTenantHierarchy, } from '@weaveintel/identity';
33
+ import { buildRealmContext } from '@weaveintel/realm';
34
+
35
+ const store = createInMemoryRealmStore(); // or the SQL-backed store (SQLite/Postgres)
36
+ const resolver = createRealmResolver({ store });
37
+
38
+ // 1) The product publishes a global default.
39
+ await store.publishGlobal('assistant.general', { template: 'You are a helpful assistant.' });
40
+
41
+ // 2) A tenant customizes their own copy.
42
+ const org = createInMemoryTenantHierarchy();
43
+ const acme = await org.create({ id: 'acme', name: 'Acme' });
44
+ const ctx = await buildRealmContext(org, 'acme');
45
+ await store.customize('assistant.general', ctx, { template: 'You are Acme’s in-house assistant.' });
46
+
47
+ // 3) Resolution gives each caller the right one, and says where it came from.
48
+ const forAcme = await resolver.resolve('assistant.general', ctx);
49
+ forAcme.template // 'You are Acme’s in-house assistant.'
50
+ forAcme.realmProvenance // { kind: 'own_override', drift: 'in_sync' }
51
+
52
+ const forSomeoneElse = await resolver.resolve('assistant.general', await buildRealmContext(org, 'other'));
53
+ forSomeoneElse.realmProvenance.kind // 'global' — untouched tenants still get the default
54
+ ```
55
+
56
+ ### Provenance — every resolved record says where it came from
57
+
58
+ - `{ kind: 'global' }` — the shared default.
59
+ - `{ kind: 'native', ownerTenantId }` — a record this tenant authored from scratch (no global equivalent).
60
+ - `{ kind: 'own_override', ownerTenantId, drift }` — this tenant's own edited copy (+ its drift state).
61
+ - `{ kind: 'inherited', fromTenantId, distance }` — a parent/ancestor's shared copy, `distance` levels up.
62
+
63
+ Stamp it into your run traces and you can always answer *"which prompt version produced this output for
64
+ this tenant?"*.
65
+
66
+ ### Drift — has a customization gone stale, or diverged?
67
+
68
+ `drift` on an override is one of:
69
+
70
+ | drift | meaning |
71
+ |---|---|
72
+ | `in_sync` | your copy is unchanged and the default is unchanged — identical |
73
+ | `customized` | you edited it; the default hasn't moved — your change is the only difference |
74
+ | `stale` | you didn't touch it; the default moved on — safe to refresh in one click |
75
+ | `diverged` | both changed — needs a real look (a merge) |
76
+
77
+ This is the exact same signal for two problems: *"a tenant customized a built-in"* and *"a package update
78
+ changed a default a tenant was using."*
79
+
80
+ ### Sharing down the tree
81
+
82
+ ```ts
83
+ const edit = await store.customize('brand.tone', parentCtx, { tone: 'formal' });
84
+ await store.setShareMode(edit.id, 'subtree'); // now every subsidiary inherits it (unless they have their own)
85
+ ```
86
+
87
+ `private` (default) keeps it to yourself, `children` shares to direct children only, `subtree` shares to
88
+ your whole branch. A parent's *private* copy stays invisible to children — privacy hides the customization,
89
+ never the concept.
90
+
91
+ ## Backing it with a real database
92
+
93
+ The SQL store speaks to any client with a `query(text, params) → { rows }` method — a `pg.Pool` fits
94
+ directly; for SQLite, wrap `better-sqlite3` in a few lines. The visibility rule (who may see which copy)
95
+ is one WHERE clause — no recursive SQL — so SQLite and Postgres behave identically (proven by the same
96
+ conformance test running against both).
97
+
98
+ ```ts
99
+ import { createSqlRealmStore } from '@weaveintel/realm';
100
+ import pg from 'pg';
101
+ const store = createSqlRealmStore({ client: new pg.Pool({ connectionString: process.env.DATABASE_URL }), dialect: 'postgres' });
102
+ ```
103
+
104
+ ## License
105
+
106
+ MIT.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Realm context — "who is asking, and where do they sit in the tenant tree?"
3
+ *
4
+ * Resolution needs the asking tenant's lineage (itself + its ancestors, each with a depth) so it can
5
+ * pick the *nearest* owner of a config and decide what a parent has chosen to share downward. That
6
+ * lineage comes straight from the Phase 0 tenant hierarchy (`@weaveintel/identity`).
7
+ */
8
+ import type { TenantHierarchyStore } from '@weaveintel/identity';
9
+ /** One tenant on the lineage from root to the asking tenant. */
10
+ export interface LineageNode {
11
+ readonly tenantId: string;
12
+ /** Depth in the tree — roots are 0, deeper is larger. */
13
+ readonly depth: number;
14
+ }
15
+ /**
16
+ * The asking tenant's position. `lineage` runs root → … → self (self is the last element). A "system"
17
+ * / global caller has `tenantId: null` and an empty lineage — it sees only global records.
18
+ */
19
+ export interface RealmContext {
20
+ readonly tenantId: string | null;
21
+ readonly depth: number;
22
+ /** root → self; ancestors are all but the last. */
23
+ readonly lineage: readonly LineageNode[];
24
+ }
25
+ /** The global/system context — sees only global records, no tenant overrides. */
26
+ export declare const GLOBAL_CONTEXT: RealmContext;
27
+ /** Immediate parent tenant id, or null at a root. */
28
+ export declare function parentTenantId(ctx: RealmContext): string | null;
29
+ /** Ancestor tenant ids ABOVE the immediate parent (grandparent → root). */
30
+ export declare function strictGrandAncestorIds(ctx: RealmContext): string[];
31
+ /** Depth of a given owner tenant id within this context's lineage, or undefined if not an ancestor/self. */
32
+ export declare function ownerDepth(ctx: RealmContext, ownerTenantId: string | null): number | undefined;
33
+ /**
34
+ * Build a context from the tenant hierarchy: fetch the tenant and its ancestors, ordered root → self.
35
+ * Pass `null` for the global/system caller.
36
+ */
37
+ export declare function buildRealmContext(hierarchy: TenantHierarchyStore, tenantId: string | null): Promise<RealmContext>;
38
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;CAC1C;AAED,iFAAiF;AACjF,eAAO,MAAM,cAAc,EAAE,YAAyD,CAAC;AAEvF,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAE/D;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,EAAE,CAElE;AAED,4GAA4G;AAC5G,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAG9F;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,oBAAoB,EAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,YAAY,CAAC,CAUvB"}
@@ -0,0 +1,34 @@
1
+ /** The global/system context — sees only global records, no tenant overrides. */
2
+ export const GLOBAL_CONTEXT = { tenantId: null, depth: -1, lineage: [] };
3
+ /** Immediate parent tenant id, or null at a root. */
4
+ export function parentTenantId(ctx) {
5
+ return ctx.lineage.length >= 2 ? ctx.lineage[ctx.lineage.length - 2].tenantId : null;
6
+ }
7
+ /** Ancestor tenant ids ABOVE the immediate parent (grandparent → root). */
8
+ export function strictGrandAncestorIds(ctx) {
9
+ return ctx.lineage.slice(0, Math.max(0, ctx.lineage.length - 2)).map((n) => n.tenantId);
10
+ }
11
+ /** Depth of a given owner tenant id within this context's lineage, or undefined if not an ancestor/self. */
12
+ export function ownerDepth(ctx, ownerTenantId) {
13
+ if (ownerTenantId == null)
14
+ return undefined; // global
15
+ return ctx.lineage.find((n) => n.tenantId === ownerTenantId)?.depth;
16
+ }
17
+ /**
18
+ * Build a context from the tenant hierarchy: fetch the tenant and its ancestors, ordered root → self.
19
+ * Pass `null` for the global/system caller.
20
+ */
21
+ export async function buildRealmContext(hierarchy, tenantId) {
22
+ if (tenantId == null)
23
+ return GLOBAL_CONTEXT;
24
+ const self = await hierarchy.get(tenantId);
25
+ if (!self)
26
+ throw new Error(`buildRealmContext: tenant ${JSON.stringify(tenantId)} not found.`);
27
+ const ancestors = await hierarchy.ancestors(tenantId); // root → parent
28
+ const lineage = [
29
+ ...ancestors.map((a) => ({ tenantId: a.id, depth: a.depth })),
30
+ { tenantId: self.id, depth: self.depth },
31
+ ];
32
+ return { tenantId, depth: self.depth, lineage };
33
+ }
34
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AA4BA,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAiB,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAEvF,qDAAqD;AACrD,MAAM,UAAU,cAAc,CAAC,GAAiB;IAC9C,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;AACxF,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,sBAAsB,CAAC,GAAiB;IACtD,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC1F,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,UAAU,CAAC,GAAiB,EAAE,aAA4B;IACxE,IAAI,aAAa,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC,CAAC,SAAS;IACtD,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,CAAC,EAAE,KAAK,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA+B,EAC/B,QAAuB;IAEvB,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,cAAc,CAAC;IAC5C,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC/F,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB;IACvE,MAAM,OAAO,GAAkB;QAC7B,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;KACzC,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @weaveintel/realm — resolve the effective configuration for a tenant across a global→tenant
3
+ * hierarchy, with provenance and git-style drift. Phase 1 of the tenancy realm design.
4
+ */
5
+ export * from './realm-record.js';
6
+ export * from './context.js';
7
+ export * from './resolve.js';
8
+ export * from './realm-store.js';
9
+ export * from './realm-store-sql.js';
10
+ export * from './realm-contract.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /**
3
+ * @weaveintel/realm — resolve the effective configuration for a tenant across a global→tenant
4
+ * hierarchy, with provenance and git-style drift. Phase 1 of the tenancy realm design.
5
+ */
6
+ export * from './realm-record.js';
7
+ export * from './context.js';
8
+ export * from './resolve.js';
9
+ export * from './realm-store.js';
10
+ export * from './realm-store-sql.js';
11
+ export * from './realm-contract.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B;;;GAGG;AACH,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Conformance contract for any `RealmConfigStore` + the resolver on top of it. Framework-agnostic:
3
+ * returns pass/fail results. The in-memory store and the SQL store (SQLite + Postgres) all run this
4
+ * exact suite. Contexts are supplied by the caller (built from a tenant tree) so the store stays
5
+ * decoupled from identity.
6
+ */
7
+ import { type RealmConfigStore } from './realm-store.js';
8
+ import type { RealmContext } from './context.js';
9
+ export interface ContractCheck {
10
+ readonly name: string;
11
+ readonly ok: boolean;
12
+ readonly error?: string;
13
+ }
14
+ export declare function realmContractPassed(r: ContractCheck[]): boolean;
15
+ /**
16
+ * The fixed tenant tree the contract resolves against:
17
+ * acme (root, d0) → emea (d1) → uk (d2)
18
+ * acme → apac (d1)
19
+ * Contexts are hand-built so no identity dependency is needed.
20
+ */
21
+ export declare const CTX: {
22
+ global: RealmContext;
23
+ acme: RealmContext;
24
+ emea: RealmContext;
25
+ uk: RealmContext;
26
+ apac: RealmContext;
27
+ };
28
+ export interface RealmContractOptions {
29
+ readonly makeStore: () => Promise<RealmConfigStore> | RealmConfigStore;
30
+ readonly cleanup?: (s: RealmConfigStore) => Promise<void> | void;
31
+ }
32
+ export declare function runRealmContract(opts: RealmContractOptions): Promise<ContractCheck[]>;
33
+ //# sourceMappingURL=realm-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-contract.d.ts","sourceRoot":"","sources":["../src/realm-contract.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,eAAO,MAAM,GAAG;YACwC,YAAY;UACiB,YAAY;UAK1F,YAAY;QAKZ,YAAY;UAKZ,YAAY;CAClB,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClE;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAgK3F"}
@@ -0,0 +1,191 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /**
3
+ * Conformance contract for any `RealmConfigStore` + the resolver on top of it. Framework-agnostic:
4
+ * returns pass/fail results. The in-memory store and the SQL store (SQLite + Postgres) all run this
5
+ * exact suite. Contexts are supplied by the caller (built from a tenant tree) so the store stays
6
+ * decoupled from identity.
7
+ */
8
+ import { createRealmResolver } from './realm-store.js';
9
+ import { driftState } from './realm-record.js';
10
+ export function realmContractPassed(r) {
11
+ return r.length > 0 && r.every((x) => x.ok);
12
+ }
13
+ /**
14
+ * The fixed tenant tree the contract resolves against:
15
+ * acme (root, d0) → emea (d1) → uk (d2)
16
+ * acme → apac (d1)
17
+ * Contexts are hand-built so no identity dependency is needed.
18
+ */
19
+ export const CTX = {
20
+ global: { tenantId: null, depth: -1, lineage: [] },
21
+ acme: { tenantId: 'acme', depth: 0, lineage: [{ tenantId: 'acme', depth: 0 }] },
22
+ emea: {
23
+ tenantId: 'emea',
24
+ depth: 1,
25
+ lineage: [{ tenantId: 'acme', depth: 0 }, { tenantId: 'emea', depth: 1 }],
26
+ },
27
+ uk: {
28
+ tenantId: 'uk',
29
+ depth: 2,
30
+ lineage: [{ tenantId: 'acme', depth: 0 }, { tenantId: 'emea', depth: 1 }, { tenantId: 'uk', depth: 2 }],
31
+ },
32
+ apac: {
33
+ tenantId: 'apac',
34
+ depth: 1,
35
+ lineage: [{ tenantId: 'acme', depth: 0 }, { tenantId: 'apac', depth: 1 }],
36
+ },
37
+ };
38
+ export async function runRealmContract(opts) {
39
+ const results = [];
40
+ const check = async (name, fn) => {
41
+ let s;
42
+ try {
43
+ s = await opts.makeStore();
44
+ await fn(s);
45
+ results.push({ name, ok: true });
46
+ }
47
+ catch (e) {
48
+ results.push({ name, ok: false, error: e instanceof Error ? `${e.name}: ${e.message}` : String(e) });
49
+ }
50
+ finally {
51
+ if (s && opts.cleanup)
52
+ await opts.cleanup(s);
53
+ }
54
+ };
55
+ const eq = (a, b, m) => {
56
+ if (JSON.stringify(a) !== JSON.stringify(b))
57
+ throw new Error(`${m} — expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}`);
58
+ };
59
+ const rejects = async (fn, m) => {
60
+ try {
61
+ await fn();
62
+ }
63
+ catch {
64
+ return;
65
+ }
66
+ throw new Error(`${m} — did not throw`);
67
+ };
68
+ // ── global-only world (single-org / community): everyone gets the default ────────────────────────
69
+ await check('global default resolves for every tenant when nobody has customized', async (s) => {
70
+ await s.publishGlobal('assistant.general', { template: 'You are helpful.' });
71
+ const r = createRealmResolver({ store: s });
72
+ for (const ctx of [CTX.global, CTX.acme, CTX.emea, CTX.uk]) {
73
+ const eff = await r.resolve('assistant.general', ctx);
74
+ eq([eff?.['template'], eff?.realmProvenance.kind], ['You are helpful.', 'global'], `resolve for ${ctx.tenantId}`);
75
+ }
76
+ });
77
+ // ── own override wins over global, only for its owner ────────────────────────────────────────────
78
+ await check("a tenant's own customization wins for it — and nobody else sees it", async (s) => {
79
+ await s.publishGlobal('assistant.general', { template: 'GLOBAL' });
80
+ await s.customize('assistant.general', CTX.uk, { template: 'UK EDIT' });
81
+ const r = createRealmResolver({ store: s });
82
+ eq((await r.resolve('assistant.general', CTX.uk))?.['template'], 'UK EDIT', 'uk sees its edit');
83
+ eq((await r.resolve('assistant.general', CTX.uk))?.realmProvenance.kind, 'own_override', 'provenance own_override');
84
+ eq((await r.resolve('assistant.general', CTX.emea))?.['template'], 'GLOBAL', 'sibling/parent still global');
85
+ eq((await r.resolve('assistant.general', CTX.apac))?.['template'], 'GLOBAL', 'unrelated tenant still global');
86
+ });
87
+ // ── nearest owner wins, and privacy hides the parent's copy ──────────────────────────────────────
88
+ await check('nearest owner wins; a parent PRIVATE override is invisible to children', async (s) => {
89
+ await s.publishGlobal('k', { v: 'GLOBAL' });
90
+ await s.customize('k', CTX.emea, { v: 'EMEA EDIT' }); // private by default
91
+ const r = createRealmResolver({ store: s });
92
+ // uk is emea's child, but emea's edit is private → uk falls through to global.
93
+ eq((await r.resolve('k', CTX.uk))?.['v'], 'GLOBAL', 'private parent override hidden → global');
94
+ eq((await r.resolve('k', CTX.emea))?.['v'], 'EMEA EDIT', 'emea sees its own');
95
+ });
96
+ await check("a parent's SHARED override is inherited by descendants (nearest wins over global)", async (s) => {
97
+ await s.publishGlobal('k', { v: 'GLOBAL' });
98
+ const emeaEdit = await s.customize('k', CTX.emea, { v: 'EMEA SHARED' });
99
+ await s.setShareMode(emeaEdit.id, 'subtree');
100
+ const r = createRealmResolver({ store: s });
101
+ const uk = await r.resolve('k', CTX.uk);
102
+ eq(uk?.['v'], 'EMEA SHARED', 'uk inherits the shared parent override');
103
+ eq(uk?.realmProvenance.kind, 'inherited', 'provenance inherited');
104
+ eq((uk?.realmProvenance).distance, 1, 'distance = 1 level up');
105
+ // apac is not under emea → still global.
106
+ eq((await r.resolve('k', CTX.apac))?.['v'], 'GLOBAL', 'cousin unaffected');
107
+ });
108
+ await check("'children' share reaches only direct children, not grandchildren", async (s) => {
109
+ await s.publishGlobal('k', { v: 'GLOBAL' });
110
+ const acmeEdit = await s.customize('k', CTX.acme, { v: 'ACME CHILDREN' });
111
+ await s.setShareMode(acmeEdit.id, 'children');
112
+ const r = createRealmResolver({ store: s });
113
+ eq((await r.resolve('k', CTX.emea))?.['v'], 'ACME CHILDREN', 'direct child inherits');
114
+ eq((await r.resolve('k', CTX.uk))?.['v'], 'GLOBAL', 'grandchild does NOT (children-only)');
115
+ });
116
+ // ── own override closer than a shared ancestor override ──────────────────────────────────────────
117
+ await check('own override beats an inherited shared override', async (s) => {
118
+ await s.publishGlobal('k', { v: 'GLOBAL' });
119
+ const acmeEdit = await s.customize('k', CTX.acme, { v: 'ACME' });
120
+ await s.setShareMode(acmeEdit.id, 'subtree');
121
+ await s.customize('k', CTX.uk, { v: 'UK' });
122
+ const r = createRealmResolver({ store: s });
123
+ eq((await r.resolve('k', CTX.uk))?.['v'], 'UK', "uk's own beats acme's shared");
124
+ eq((await r.resolve('k', CTX.emea))?.['v'], 'ACME', 'emea (no own) inherits acme shared');
125
+ });
126
+ // ── native records (no global equivalent) ────────────────────────────────────────────────────────
127
+ await check('a tenant-native record (no global) resolves as native', async (s) => {
128
+ await s.putNative('tenant.only', 'uk', { v: 'UK ONLY' });
129
+ const r = createRealmResolver({ store: s });
130
+ eq((await r.resolve('tenant.only', CTX.uk))?.realmProvenance.kind, 'native', 'native provenance');
131
+ eq(await r.resolve('tenant.only', CTX.apac), null, 'invisible to others (no global)');
132
+ });
133
+ // ── drift (Base / Local / Remote) ────────────────────────────────────────────────────────────────
134
+ await check('drift: in_sync → customized → stale → diverged', async (s) => {
135
+ await s.publishGlobal('k', { v: 'V1' }); // base
136
+ await s.customize('k', CTX.uk, { v: 'V1' }); // fork with identical content → in_sync
137
+ const r = createRealmResolver({ store: s });
138
+ const prov = async () => (await r.resolve('k', CTX.uk)).realmProvenance.drift;
139
+ eq(await prov(), 'in_sync', 'fork identical to base');
140
+ await s.customize('k', CTX.uk, { v: 'V1-uk' }); // local edit
141
+ eq(await prov(), 'customized', 'local edit, source unchanged');
142
+ await s.customize('k', CTX.uk, { v: 'V1' }); // revert local to base
143
+ await s.publishGlobal('k', { v: 'V2' }); // source moves on
144
+ eq(await prov(), 'stale', 'local unchanged, source moved');
145
+ await s.customize('k', CTX.uk, { v: 'V2-uk' }); // and now local edits too
146
+ eq(await prov(), 'diverged', 'both changed');
147
+ });
148
+ await check('listEffective returns exactly one record per logical key', async (s) => {
149
+ await s.publishGlobal('a', { v: 'A' });
150
+ await s.publishGlobal('b', { v: 'B' });
151
+ await s.customize('a', CTX.uk, { v: 'A-uk' });
152
+ const r = createRealmResolver({ store: s });
153
+ const eff = await r.listEffective(CTX.uk);
154
+ eq(eff.map((e) => e.logicalKey), ['a', 'b'], 'one per key, sorted');
155
+ eq(eff.map((e) => e.realmProvenance.kind), ['own_override', 'global'], 'a=own, b=global');
156
+ });
157
+ // ── negative ─────────────────────────────────────────────────────────────────────────────────────
158
+ await check('customize with nothing to fork throws', async (s) => {
159
+ await rejects(() => s.customize('missing.key', CTX.uk, { v: 'x' }), 'no base');
160
+ });
161
+ await check('global caller sees only global records', async (s) => {
162
+ await s.publishGlobal('k', { v: 'G' });
163
+ await s.putNative('k2', 'uk', { v: 'UK' });
164
+ const r = createRealmResolver({ store: s });
165
+ eq((await r.resolve('k', CTX.global))?.['v'], 'G', 'global visible');
166
+ eq(await r.resolve('k2', CTX.global), null, 'tenant-native invisible to global');
167
+ });
168
+ // ── security / isolation ─────────────────────────────────────────────────────────────────────────
169
+ await check('SECURITY: sibling tenants cannot read each other’s private overrides', async (s) => {
170
+ await s.publishGlobal('secret.prompt', { v: 'GLOBAL' });
171
+ await s.customize('secret.prompt', CTX.emea, { v: 'EMEA CONFIDENTIAL' });
172
+ await s.customize('secret.prompt', CTX.apac, { v: 'APAC CONFIDENTIAL' });
173
+ const r = createRealmResolver({ store: s });
174
+ eq((await r.resolve('secret.prompt', CTX.emea))?.['v'], 'EMEA CONFIDENTIAL', 'emea sees own');
175
+ eq((await r.resolve('secret.prompt', CTX.apac))?.['v'], 'APAC CONFIDENTIAL', 'apac sees own');
176
+ // Neither leaks to the other; a child of emea never sees apac's, and vice-versa.
177
+ eq((await r.resolve('secret.prompt', CTX.uk))?.['v'], 'GLOBAL', "emea's child sees neither sibling's secret");
178
+ const visForApac = await s.listVisible(CTX.apac, ['secret.prompt']);
179
+ eq(visForApac.some((x) => x.v === 'EMEA CONFIDENTIAL'), false, 'apac candidate set excludes emea secret');
180
+ });
181
+ await check('drift helper matches the state table', () => {
182
+ eq(driftState('B', 'B', 'B'), 'in_sync', 'in_sync');
183
+ eq(driftState('B', 'L', 'B'), 'customized', 'customized');
184
+ eq(driftState('B', 'B', 'R'), 'stale', 'stale');
185
+ eq(driftState('B', 'L', 'R'), 'diverged', 'diverged');
186
+ eq(driftState(null, 'L', 'R'), 'not_a_fork', 'no base');
187
+ return Promise.resolve();
188
+ });
189
+ return results;
190
+ }
191
+ //# sourceMappingURL=realm-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-contract.js","sourceRoot":"","sources":["../src/realm-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /** Is this a shared global default, or a tenant's own copy? */
2
+ export type RealmClass = 'global' | 'tenant';
3
+ /** How far down the tree a tenant's own record is offered to its descendants. */
4
+ export type ShareMode = 'private' | 'children' | 'subtree';
5
+ /** Whether a forked copy stays pinned to the version it forked, or auto-follows the source. */
6
+ export type TrackMode = 'pin' | 'track_latest';
7
+ /** The realm columns every realm-enabled config row gains. */
8
+ export interface RealmFields {
9
+ /** 'global' = a shared default; 'tenant' = a specific tenant's copy. */
10
+ realm: RealmClass;
11
+ /** The owning tenant's id. NULL exactly when realm='global'. */
12
+ ownerTenantId: string | null;
13
+ /** The stable identity of the config across copies (e.g. a prompt's key). References use this. */
14
+ logicalKey: string;
15
+ /** The row this was forked from (a global default, or a parent's shared copy). NULL for originals/natives. */
16
+ originId: string | null;
17
+ /** The origin's content hash AT FORK TIME — the "Base" in a three-way drift check. */
18
+ originHash: string | null;
19
+ /** Hash of this row's own semantic content — the "Local". */
20
+ contentHash: string;
21
+ /** Pinned to the fork point, or auto-following the source. */
22
+ trackMode: TrackMode;
23
+ /** Sharing reach down the tenant tree. */
24
+ shareMode: ShareMode;
25
+ }
26
+ /** A config row = the app's own payload columns plus the realm fields plus an id. */
27
+ export type RealmRecord<T = Record<string, unknown>> = T & RealmFields & {
28
+ id: string;
29
+ };
30
+ /** The three shapes a realm row can take, distinguished by (realm, originId). */
31
+ export type RealmArchetype = 'global_original' | 'tenant_override' | 'tenant_native';
32
+ /** Classify a row: a shared default, a fork of one, or a tenant's from-scratch record. */
33
+ export declare function archetypeOf(f: Pick<RealmFields, 'realm' | 'originId'>): RealmArchetype;
34
+ /**
35
+ * Deterministic content hash over just the *semantic* fields (the substance — a prompt's template &
36
+ * variables, NOT its id, timestamps, enabled flag or realm columns). Stable across key order and
37
+ * environments, so two rows with the same meaning hash the same on any machine or database.
38
+ */
39
+ export declare function computeContentHash(semantic: Record<string, unknown>): string;
40
+ /** Canonical JSON: object keys sorted recursively so hashing is order-independent. */
41
+ export declare function canonicalize(value: unknown): string;
42
+ /** The four drift states of a tenant's forked copy versus the default it came from. */
43
+ export type DriftState = 'in_sync' | 'customized' | 'stale' | 'diverged';
44
+ /**
45
+ * The three-way drift check — Base = the hash at fork time, Local = this copy's current hash, Remote =
46
+ * the source's current hash. Exactly git's merge logic, for config.
47
+ */
48
+ export declare function driftState(base: string | null, local: string, remote: string | null): DriftState | 'not_a_fork';
49
+ /** Default realm fields for a brand-new GLOBAL original (what a package seed publishes). */
50
+ export declare function globalOriginalFields(logicalKey: string, contentHash: string): RealmFields;
51
+ //# sourceMappingURL=realm-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-record.d.ts","sourceRoot":"","sources":["../src/realm-record.ts"],"names":[],"mappings":"AAiBA,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE7C,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3D,+FAA+F;AAC/F,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,cAAc,CAAC;AAE/C,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,KAAK,EAAE,UAAU,CAAC;IAClB,gEAAgE;IAChE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kGAAkG;IAClG,UAAU,EAAE,MAAM,CAAC;IACnB,8GAA8G;IAC9G,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sFAAsF;IACtF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,EAAE,SAAS,CAAC;IACrB,0CAA0C;IAC1C,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,qFAAqF;AACrF,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,eAAe,CAAC;AAErF,0FAA0F;AAC1F,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG,cAAc,CAGtF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE5E;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAWD,uFAAuF;AACvF,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,YAAY,GACZ,OAAO,GACP,UAAU,CAAC;AAEf;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,CAQ/G;AAED,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,CAWzF"}
@@ -0,0 +1,76 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /**
3
+ * Realm records — the small set of columns a *configuration* row carries so it can live in a
4
+ * global→tenant hierarchy.
5
+ *
6
+ * Think of any piece of shipped configuration — a prompt template, a skill, a guardrail. In a single
7
+ * product there's one copy. In a multi-tenant product you want three things at once:
8
+ * 1. a **global** default everyone gets,
9
+ * 2. the ability for a tenant to **customize** its own copy (without touching anyone else's), and
10
+ * 3. a way to tell, later, whether that customized copy has drifted from the default it was forked
11
+ * from — so a product update doesn't silently clobber a customer's edits (or leave them stale).
12
+ *
13
+ * These fields make that possible, and the drift check is the exact same three-way (Base / Local /
14
+ * Remote) comparison git uses for a merge — just applied to configuration instead of source files.
15
+ */
16
+ import { createHash } from 'node:crypto';
17
+ /** Classify a row: a shared default, a fork of one, or a tenant's from-scratch record. */
18
+ export function archetypeOf(f) {
19
+ if (f.realm === 'global')
20
+ return 'global_original';
21
+ return f.originId == null ? 'tenant_native' : 'tenant_override';
22
+ }
23
+ /**
24
+ * Deterministic content hash over just the *semantic* fields (the substance — a prompt's template &
25
+ * variables, NOT its id, timestamps, enabled flag or realm columns). Stable across key order and
26
+ * environments, so two rows with the same meaning hash the same on any machine or database.
27
+ */
28
+ export function computeContentHash(semantic) {
29
+ return `sha256:${createHash('sha256').update(canonicalize(semantic), 'utf8').digest('hex')}`;
30
+ }
31
+ /** Canonical JSON: object keys sorted recursively so hashing is order-independent. */
32
+ export function canonicalize(value) {
33
+ return JSON.stringify(sortDeep(value));
34
+ }
35
+ function sortDeep(v) {
36
+ if (Array.isArray(v))
37
+ return v.map(sortDeep);
38
+ if (v && typeof v === 'object') {
39
+ const out = {};
40
+ for (const k of Object.keys(v).sort())
41
+ out[k] = sortDeep(v[k]);
42
+ return out;
43
+ }
44
+ return v;
45
+ }
46
+ /**
47
+ * The three-way drift check — Base = the hash at fork time, Local = this copy's current hash, Remote =
48
+ * the source's current hash. Exactly git's merge logic, for config.
49
+ */
50
+ export function driftState(base, local, remote) {
51
+ if (base == null || remote == null)
52
+ return 'not_a_fork'; // an original/native has nothing to drift from
53
+ const localChanged = local !== base;
54
+ const remoteChanged = remote !== base;
55
+ if (!localChanged && !remoteChanged)
56
+ return 'in_sync';
57
+ if (localChanged && !remoteChanged)
58
+ return 'customized';
59
+ if (!localChanged && remoteChanged)
60
+ return 'stale';
61
+ return 'diverged';
62
+ }
63
+ /** Default realm fields for a brand-new GLOBAL original (what a package seed publishes). */
64
+ export function globalOriginalFields(logicalKey, contentHash) {
65
+ return {
66
+ realm: 'global',
67
+ ownerTenantId: null,
68
+ logicalKey,
69
+ originId: null,
70
+ originHash: null,
71
+ contentHash,
72
+ trackMode: 'pin',
73
+ shareMode: 'private',
74
+ };
75
+ }
76
+ //# sourceMappingURL=realm-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-record.js","sourceRoot":"","sources":["../src/realm-record.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAqCzC,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CAAC,CAA0C;IACpE,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,iBAAiB,CAAC;IACnD,OAAO,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAiC;IAClE,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC;AACD,SAAS,QAAQ,CAAC,CAAU;IAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAA4B,CAAC,CAAC,IAAI,EAAE;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAE,CAA6B,CAAC,CAAC,CAAC,CAAC,CAAC;QACvH,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AASD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAmB,EAAE,KAAa,EAAE,MAAqB;IAClF,IAAI,IAAI,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO,YAAY,CAAC,CAAC,+CAA+C;IACxG,MAAM,YAAY,GAAG,KAAK,KAAK,IAAI,CAAC;IACpC,MAAM,aAAa,GAAG,MAAM,KAAK,IAAI,CAAC;IACtC,IAAI,CAAC,YAAY,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACtD,IAAI,YAAY,IAAI,CAAC,aAAa;QAAE,OAAO,YAAY,CAAC;IACxD,IAAI,CAAC,YAAY,IAAI,aAAa;QAAE,OAAO,OAAO,CAAC;IACnD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,WAAmB;IAC1E,OAAO;QACL,KAAK,EAAE,QAAQ;QACf,aAAa,EAAE,IAAI;QACnB,UAAU;QACV,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;QAChB,WAAW;QACX,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC"}