@volter/world-core 3.0.161 → 3.0.163

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.
@@ -1,3 +1,5 @@
1
+ import { storageTenant } from './request-scope.ts';
2
+ import { isWorldAccountName } from './world-account-name.ts';
1
3
  import { parseExactJson, compareExactNumbers, exactInteger, isExactIntegerValue } from './exact-json.ts';
2
4
  // THE DERIVED CORE — what a derived pack does for an operation it declares no handler for
3
5
  // (docs/contributing/architecture.md, "Protocol 3"). The generated surface says what the
@@ -20,7 +22,7 @@ import { openSessions, socketWrite, type SocketDecl, type SocketSession } from '
20
22
  import { liveAccess } from './live/access.ts';
21
23
  import type { LiveAccess, LiveDecl } from './live/types.ts';
22
24
  import { awaitWrite, awaitCondition, heldReadsWrite } from './held-reads.ts';
23
- import { MACHINE_POOL, poolFor, guardedMachinePool, type MachinePool, type MachineHttpReply, type LightPoolOptions } from './machines.ts';
25
+ import { MACHINE_POOL, poolFor, guardedMachinePool, type MachinePool, type MachineHttpReply } from './machines.ts';
24
26
  import { askApplication, deliverEvents, type ApplicationAnswer, type ApplicationStandIn, type EventRender, type EventScheme, type EventsDecl, type EventValues } from './events.ts';
25
27
  import type { RateBudgetDeclaration } from './rateBudget.ts';
26
28
  import { handlerCrypto, hmac, ksuidFrom, lettersFrom, sha256, uuidFrom, type HandlerCrypto } from './signing.ts';
@@ -41,7 +43,7 @@ import type { SubjectFields } from './hash.ts';
41
43
  import { twinPublicBase, withRequestScopes } from './twin-fetch.ts';
42
44
  import { inVendorMove, isReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.ts';
43
45
  import type { DerivedCall, DerivedCoreOutcome, DerivedHandler, DerivedOperation } from './derived.ts';
44
- import type { PackDescriptor } from './packRegistry.ts';
46
+ import { getPack, type PackDescriptor } from './packRegistry.ts';
45
47
  import type { ScenarioDecision, ScenarioHandler } from './scenario.ts';
46
48
  import { bodyTextOf, checkJsonBody, takeParsedBody } from './derived.ts';
47
49
 
@@ -524,7 +526,7 @@ export type DerivedManifest = {
524
526
  * an organization, a team, an account — and a caller reaches only its own tenant's, as a parent's children are
525
527
  * reached under it. `field` holds it on each subject; `semantics/tenant.ts` names the caller's. The core scopes every
526
528
  * read, list, update and delete by it and stamps it on every create, for each resource not `shared`. */
527
- tenant?: { field: string };
529
+ tenant?: { field: string; storage?: 'namespaced' };
528
530
  /** The vendor's own lanes behind this manifest's API ("Other wires: lanes"), dispatched by the kernel before the
529
531
  * vendor's own doors, clock and API (LanesDecl). */
530
532
  lanes?: LanesDecl;
@@ -744,13 +746,19 @@ function notFound(manifest: DerivedManifest, object: string, id: string, param?:
744
746
 
745
747
  // ── the tree ─────────────────────────────────────────────────────────────────────────────────
746
748
 
747
- export const storedType = (m: DerivedManifest, resource: string): string => m.resources[resource]?.storedAs ?? resource;
749
+ const TENANT_TYPE = '::account:';
750
+ const accountScope = (m: DerivedManifest): string | undefined => m.tenant?.storage === 'namespaced' ? storageTenant(m.service) : undefined;
751
+ const scopeLabel = (m: DerivedManifest, label: string): string => { const account = accountScope(m); return account && account !== 'default' ? `account:${account}:${label}` : label; };
752
+ export const storedType = (m: DerivedManifest, resource: string): string => {
753
+ const base = m.resources[resource]?.storedAs ?? resource, account = accountScope(m);
754
+ return account && account !== 'default' && !m.resources[resource]?.shared ? `${base}${TENANT_TYPE}${encodeURIComponent(account)}` : base;
755
+ };
748
756
 
749
757
  // A service's resources by type, folded once per World state (the tree's stamp; any write moves it), not on every read:
750
758
  // a read of one resource type projected the service's whole tree, so a pack whose tree also holds many subjects of
751
759
  // other types (PlanetScale's SQL rows beside its management records) paid for all of them on each read. A read gets its
752
760
  // own copy of each resource, as twinResources hands out, with the vendor's own id/type/updatedAt a spread would drop.
753
- const typeIndexes = new WeakMap<object, Map<string, { stamp: string; byType: Map<string, Map<string, TwinResource>> }>>();
761
+ const typeIndexes = new WeakMap<object, Map<string, { stamp: string; byType: Map<string, Map<string, TwinResource>>; typeByKey: Map<string, string> }>>();
754
762
  /** A service's resources of one type, each the reader's own copy: `twinResources` filtered by type, from an index
755
763
  * that follows each write rather than projecting the whole tree per call. */
756
764
  export function resourcesOfType(service: string, type: string, root?: string): TwinResource[] {
@@ -764,8 +772,13 @@ export function resourcesOfType(service: string, type: string, root?: string): T
764
772
  if (held && delta) {
765
773
  // the index keeps the tree's order: a subject updated where it stands stays there, one new to the tree (or made
766
774
  // again after a delete) goes to the end in the order the tree holds it
767
- for (const k of [...delta.removed, ...delta.appended]) held.byType.get(k.slice(0, k.indexOf(':')))?.delete(k);
775
+ for (const k of [...delta.removed, ...delta.appended]) {
776
+ const type = held.typeByKey.get(k);
777
+ if (type !== undefined) held.byType.get(type)?.delete(k);
778
+ held.typeByKey.delete(k);
779
+ }
768
780
  const byKey = new Map(delta.changed.map((r) => [`${r.type}:${r.id}`, r]));
781
+ for (const [k, r] of byKey) held.typeByKey.set(k, r.type);
769
782
  for (const [k, r] of byKey) if (!delta.appended.includes(k)) (held.byType.get(r.type) ?? held.byType.set(r.type, new Map()).get(r.type)!).set(k, r);
770
783
  for (const k of delta.appended) { const r = byKey.get(k)!; (held.byType.get(r.type) ?? held.byType.set(r.type, new Map()).get(r.type)!).set(k, r); }
771
784
  held.stamp = delta.stamp;
@@ -775,7 +788,7 @@ export function resourcesOfType(service: string, type: string, root?: string): T
775
788
  if (!held || held.stamp !== stamp) {
776
789
  const byType = new Map<string, Map<string, TwinResource>>();
777
790
  for (const r of twinResources(service, root)) (byType.get(r.type) ?? byType.set(r.type, new Map()).get(r.type)!).set(`${r.type}:${r.id}`, r);
778
- held = { stamp, byType };
791
+ held = { stamp, byType, typeByKey: new Map([...byType].flatMap(([type, rows]) => [...rows.keys()].map(key => [key, type] as const))) };
779
792
  indexes.set(key, held);
780
793
  }
781
794
  }
@@ -785,7 +798,9 @@ export function resourcesOfType(service: string, type: string, root?: string): T
785
798
  function stored(m: DerivedManifest, resource: string, root?: string, opts: { withDeleted?: boolean } = {}): TwinResource[] {
786
799
  const type = storedType(m, resource);
787
800
  const when = Object.entries(m.resources[resource]?.readableWhen ?? {});
788
- return resourcesOfType(m.service, type, root).filter((r) => (opts.withDeleted || r.deleted !== true) && when.every(([k, v]) => (r as Record<string, unknown>)[k] === v));
801
+ const rows = m.tenant?.storage === 'namespaced' && accountScope(m) === undefined && !m.resources[resource]?.shared
802
+ ? twinResources(m.service, root).filter(row => row.type === type || row.type.startsWith(`${type}${TENANT_TYPE}`)) : resourcesOfType(m.service, type, root);
803
+ return rows.filter((r) => (opts.withDeleted || r.deleted !== true) && when.every(([k, v]) => (r as Record<string, unknown>)[k] === v));
789
804
  }
790
805
 
791
806
  /** The subject an item operation names: the manifest's key over the path parameters, or the last one. */
@@ -821,7 +836,7 @@ function missingParent(m: DerivedManifest, resource: string, call: DerivedCall,
821
836
  const id = parentValue(parent, call.params);
822
837
  const where = Object.entries(parent.where ?? {}).map(([field, template]) => [field, String(fill(template, call.params))] as const);
823
838
  const found = (r: TwinResource): boolean => (where.length ? where.every(([field, v]) => String(parentField(r, field)) === v) : r.id === id)
824
- && (!m.tenant || m.resources[parent.resource]?.shared || (r as Record<string, unknown>)[m.tenant.field] === tenant);
839
+ && (!m.tenant || m.tenant.storage === 'namespaced' || m.resources[parent.resource]?.shared || (r as Record<string, unknown>)[m.tenant.field] === tenant);
825
840
  return id !== undefined && stored(m, parent.resource, root, { withDeleted: parent.allowDeleted === true }).some(found) ? undefined : notFound(m, parent.resource, String(id), parent.param ?? parent.params?.at(-1));
826
841
  }
827
842
 
@@ -953,15 +968,90 @@ function worldSeed(service: string, root: string | undefined, at: string): Promi
953
968
  const pending = seeding.get(key);
954
969
  if (pending) return pending;
955
970
  const made = (async () => {
956
- const value = Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');
957
- // the World's own bookkeeping, made on first use whatever the request: a read-only one lands it too (a vendor's move)
958
- await runAsVendorMove(() => applyTwinWrite(service, { operation: 'world_seed.record', subjectType: SEED, subjectId: 'seed', fields: { value }, occurredAt: at, actor: { kind: 'system' } }, root));
971
+ // The action lock also joins first asks from several renderers/processes to the same seed.
972
+ const { value } = await runAsVendorMove(() => applyTwinWriteAtomic(service, snapshot => {
973
+ const existing = snapshot.find(row => row.type === SEED && row.id === 'seed');
974
+ if (typeof existing?.value === 'string') return { kind: 'skip', value: existing.value };
975
+ const value = Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');
976
+ return { kind: 'write', value, write: { operation: 'world_seed.record', subjectType: SEED, subjectId: 'seed', fields: { value }, occurredAt: at, actor: { kind: 'system' } } };
977
+ }, root));
959
978
  return value;
960
979
  })().finally(() => seeding.delete(key));
961
980
  seeding.set(key, made);
962
981
  return made;
963
982
  }
964
983
 
984
+ /** World-owned generated values use the same private seed and log as pack secrets. */
985
+ export async function worldSecret(service: string, root: string, label: string, at = new Date().toISOString()): Promise<string> {
986
+ return hmac(await worldSeed(service, root, at), label, 'base64url');
987
+ }
988
+
989
+ /** The same retained account primitive for pack doors and private native twin fronts. */
990
+ export async function worldAccount(service: string, root: string | undefined, id: unknown = 'default', at = worldNow(), provisioning = false): Promise<string> {
991
+ if (!isWorldAccountName(id)) throw new Error('Credential account must be a name of at most 128 letters, digits, dots, underscores or hyphens');
992
+ await applyTwinWriteAtomic(service, (snapshot) => {
993
+ const held = snapshot.find(row => row.type === '_world_account' && row.id === id);
994
+ return held ? { kind: 'skip', value: id } : { kind: 'write', value: id, write: { operation: 'world_account.create', subjectType: '_world_account', subjectId: id, fields: { name: id }, ...(provisioning ? { input: { activity: 'provisioning' } } : {}), occurredAt: at, actor: { kind: 'system' } } };
995
+ }, root);
996
+ return id;
997
+ }
998
+
999
+ type WorldOrganization = { id: string; label: string; members: string[] };
1000
+ type VendorNameRule = { max: number; lower?: boolean; allowed: RegExp; separator?: string; first?: RegExp };
1001
+ type NativeAccountLink = { resource: string; id: string };
1002
+
1003
+ function nativeAccountLink(row: TwinResource | undefined, field?: string): NativeAccountLink | undefined {
1004
+ if (!row || row.deleted === true) return undefined;
1005
+ const fields = ownFields(row);
1006
+ const value = field === undefined ? fields : fields[field];
1007
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
1008
+ const link = value as Record<string, unknown>;
1009
+ return typeof link.resource === 'string' && typeof link.id === 'string' ? { resource: link.resource, id: link.id } : undefined;
1010
+ }
1011
+
1012
+ const sameAccountLink = (a: NativeAccountLink | undefined, b: NativeAccountLink | undefined): boolean =>
1013
+ a?.resource === b?.resource && a?.id === b?.id;
1014
+
1015
+ /** Native creation and reconstruction run outside the action lock; only the retained link is compared and written. */
1016
+ async function retainNativeAccountLink<T>(
1017
+ service: string, root: string | undefined, at: string, call: DerivedCall,
1018
+ type: string, id: string, field: string | undefined, resource: string,
1019
+ make: () => Promise<string>, read: (link: NativeAccountLink) => Promise<T | undefined>,
1020
+ ): Promise<T> {
1021
+ for (;;) {
1022
+ const before = nativeAccountLink(resourcesOfType(service, type, root).find((row) => row.id === id), field);
1023
+ const held = before && await read(before);
1024
+ if (held !== undefined) return held;
1025
+ const made = { resource, id: await make() };
1026
+ if (await read(made) === undefined) throw new Error(`Credential ${type}/${id}: native ${resource}/${made.id} is unavailable after creation`);
1027
+ const { value } = await applyTwinWriteAtomic(service, (snapshot) => {
1028
+ const current = nativeAccountLink(snapshot.find((row) => row.type === type && row.id === id), field);
1029
+ if (!sameAccountLink(current, before)) return { kind: 'skip', value: current };
1030
+ return {
1031
+ kind: 'write', value: made,
1032
+ write: {
1033
+ operation: `${type.slice(1)}.link`, subjectType: type, subjectId: id,
1034
+ fields: field === undefined ? made : { name: id, [field]: made },
1035
+ ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' },
1036
+ },
1037
+ };
1038
+ }, root);
1039
+ const linked = value && await read(value);
1040
+ if (linked !== undefined) return linked;
1041
+ }
1042
+ }
1043
+
1044
+ function vendorName(label: string, rule: VendorNameRule, { suffixed = false, identity = label }: { suffixed?: boolean; identity?: string } = {}): string {
1045
+ const allowed = new RegExp(rule.allowed.source, rule.allowed.flags.replace(/[gy]/g, ''));
1046
+ const separator = rule.separator ?? '-';
1047
+ if ([...separator].length !== 1 || !allowed.test(separator)) throw new Error('Credential name separator must be one allowed character');
1048
+ const first = rule.first && new RegExp(rule.first.source, rule.first.flags.replace(/[gy]/g, ''));
1049
+ let name = [...(rule.lower ? label.toLowerCase() : label)].map((letter) => allowed.test(letter) ? letter : separator).join('');
1050
+ name = name.replace(new RegExp(`${escapeRe(separator)}+`, 'g'), separator);
1051
+ if (first && !first.test(name[0] ?? '')) name = `w${separator}${name}`;
1052
+ return name !== label || name.length > rule.max || suffixed ? `${name.slice(0, Math.max(0, rule.max - 9))}${separator}${sha256(identity).slice(0, 8)}` : name;
1053
+ }
1054
+
965
1055
  /** A World's signing key for a label (ctx.signingKey): made once from the platform's random source, then read; the
966
1056
  * making is one per service store and label in the process, as the seed's is. */
967
1057
  const SIGNING_KEY = '_world_signing_key';
@@ -995,7 +1085,13 @@ function worldSigningKey(service: string, root: string | undefined, at: string,
995
1085
  return made;
996
1086
  }
997
1087
 
998
- function mintId(m: DerivedManifest, resource: string, root?: string, at?: string, snapshot?: readonly TwinResource[]): string {
1088
+ /** Vendor ids remain unique across its accounts, including public links that carry an id instead of a key. */
1089
+ function mintRows(m: DerivedManifest, resource: string, root?: string, snapshot?: readonly TwinResource[]): readonly TwinResource[] {
1090
+ const base = m.resources[resource]?.storedAs ?? resource;
1091
+ if (m.tenant?.storage === 'namespaced') return (snapshot ?? twinResources(m.service, root)).filter(row => row.type === base || row.type.startsWith(`${base}${TENANT_TYPE}`));
1092
+ return snapshot ? snapshot.filter(row => row.type === base) : resourcesOfType(m.service, base, root);
1093
+ }
1094
+ function mintId(m: DerivedManifest, resource: string, root: string | undefined, at: string, snapshot?: readonly TwinResource[]): string {
999
1095
  if (!m.resources[resource]) throw new Error(`mint: ${resource} is not a resource ${m.vendor}'s manifest declares`);
1000
1096
  const prefix = m.resources[resource]!.idPrefix;
1001
1097
  const template = m.resources[resource]!.ids ?? m.ids.template;
@@ -1012,7 +1108,7 @@ function mintId(m: DerivedManifest, resource: string, root?: string, at?: string
1012
1108
  if (digits) {
1013
1109
  const width = Number(digits[1]);
1014
1110
  if (!Number.isInteger(width) || width < 1 || width > 77) throw new Error('mint: decimal width must be between 1 and 77');
1015
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
1111
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
1016
1112
  const floor = 10n ** BigInt(width - 1);
1017
1113
  for (let n = held.size + 1; ; n++) {
1018
1114
  const id = String(floor + BigInt('0x' + sha256(`${m.service}:${resource}:${n}`)) % (9n * floor));
@@ -1031,7 +1127,7 @@ function mintId(m: DerivedManifest, resource: string, root?: string, at?: string
1031
1127
  const pattern = new RegExp(`^${escapeRe(String(fill(head!, { prefix })))}(\\d+)${escapeRe(String(fill(tail, { prefix })))}$`);
1032
1128
  let max = 0n;
1033
1129
  // the one type, from the index that follows writes (a scan of the whole World per create grew with it)
1034
- for (const r of (snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root))) {
1130
+ for (const r of mintRows(m, resource, root, snapshot)) {
1035
1131
  const hit = pattern.exec(r.id);
1036
1132
  if (hit && BigInt(hit[1]!) > max) max = BigInt(hit[1]!);
1037
1133
  }
@@ -1045,13 +1141,13 @@ function mintId(m: DerivedManifest, resource: string, root?: string, at?: string
1045
1141
  /** The last snowflake minted per vendor and World in this process: two mints before the first is written (a message
1046
1142
  * and its attachment) must not meet. */
1047
1143
  const lastSnowflake = new Map<string, bigint>();
1048
- function mintSnowflake(m: DerivedManifest, epoch: bigint, root?: string, at?: string, snapshot?: readonly TwinResource[]): string {
1049
- const ms = BigInt(Date.parse(at ?? '') || 0);
1144
+ function mintSnowflake(m: DerivedManifest, epoch: bigint, root: string | undefined, at: string, snapshot?: readonly TwinResource[]): string {
1145
+ const ms = BigInt(Date.parse(at) || 0);
1050
1146
  const base = ms > epoch ? (ms - epoch) << 22n : 0n;
1051
1147
  let max = -1n;
1052
1148
  for (const [name, decl] of Object.entries(m.resources)) {
1053
1149
  if (!/^\{snowflake:\d+\}$/.test(decl.ids ?? m.ids.template)) continue;
1054
- for (const r of (snapshot ? snapshot.filter((r) => r.type === storedType(m, name)) : resourcesOfType(m.service, storedType(m, name), root))) if (/^\d+$/.test(r.id)) { const v = BigInt(r.id); if (v > max) max = v; }
1150
+ for (const r of mintRows(m, name, root, snapshot)) if (/^\d+$/.test(r.id)) { const v = BigInt(r.id); if (v > max) max = v; }
1055
1151
  }
1056
1152
  const key = `${m.service}\0${root ?? ''}`;
1057
1153
  const last = lastSnowflake.get(key) ?? -1n;
@@ -1063,9 +1159,9 @@ function mintSnowflake(m: DerivedManifest, epoch: bigint, root?: string, at?: st
1063
1159
 
1064
1160
  /** The next KSUID for a resource (Clerk's and Svix's ids, `ksuidFrom`): the World's instant, then the type's mint
1065
1161
  * count, so a frozen clock still lists them as made; past any the World already holds. */
1066
- function mintKsuid(m: DerivedManifest, resource: string, at: string | undefined, root?: string, prefix = '', snapshot?: readonly TwinResource[], width = 27): string {
1067
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
1068
- const seconds = Math.floor((Date.parse(at ?? '') || 0) / 1000);
1162
+ function mintKsuid(m: DerivedManifest, resource: string, at: string, root: string | undefined, prefix: string, snapshot: readonly TwinResource[] | undefined, width: number): string {
1163
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
1164
+ const seconds = Math.floor((Date.parse(at) || 0) / 1000);
1069
1165
  for (let n = held.size + 1; ; n++) {
1070
1166
  const id = `${prefix}${ksuidFrom(seconds, n, `${m.service}:${resource}:${n}`, width)}`;
1071
1167
  if (!held.has(id)) return id;
@@ -1074,8 +1170,8 @@ function mintKsuid(m: DerivedManifest, resource: string, at: string | undefined,
1074
1170
 
1075
1171
  /** The next id of `count` lowercase letters for a resource, derived from the resource and the count before it, past
1076
1172
  * any the World already holds. */
1077
- function mintLetters(m: DerivedManifest, resource: string, count: number, root?: string, prefix = '', snapshot?: readonly TwinResource[]): string {
1078
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
1173
+ function mintLetters(m: DerivedManifest, resource: string, count: number, root: string | undefined, prefix: string, snapshot?: readonly TwinResource[]): string {
1174
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
1079
1175
  for (let n = held.size + 1; ; n++) {
1080
1176
  const id = `${prefix}${lettersFrom(`${m.service}:${resource}:${n}`, count)}`;
1081
1177
  if (!held.has(id)) return id;
@@ -1084,8 +1180,8 @@ function mintLetters(m: DerivedManifest, resource: string, count: number, root?:
1084
1180
 
1085
1181
  /** The next hex id for a resource: N hex digits derived from the resource and the count before it, past any the World
1086
1182
  * already holds (a Cloudflare id, 32). */
1087
- function mintHex(m: DerivedManifest, resource: string, count: number, root?: string, prefix = '', snapshot?: readonly TwinResource[]): string {
1088
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === storedType(m, resource)) : resourcesOfType(m.service, storedType(m, resource), root)).map((r) => r.id));
1183
+ function mintHex(m: DerivedManifest, resource: string, count: number, root: string | undefined, prefix: string, snapshot?: readonly TwinResource[]): string {
1184
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
1089
1185
  for (let n = held.size + 1; ; n++) {
1090
1186
  const id = `${prefix}${sha256(`${m.service}:${resource}:${n}`).slice(0, count)}`;
1091
1187
  if (!held.has(id)) return id;
@@ -1095,8 +1191,7 @@ function mintHex(m: DerivedManifest, resource: string, count: number, root?: str
1095
1191
  /** The next UUID for a resource: a version-4-shaped UUID derived from the resource and the count before it, past any
1096
1192
  * the World already holds. */
1097
1193
  function mintUuid(m: DerivedManifest, resource: string, root?: string, snapshot?: readonly TwinResource[]): string {
1098
- const type = storedType(m, resource);
1099
- const held = new Set((snapshot ? snapshot.filter((r) => r.type === type) : resourcesOfType(m.service, type, root)).map((r) => r.id));
1194
+ const held = new Set(mintRows(m, resource, root, snapshot).map((r) => r.id));
1100
1195
  for (let n = held.size + 1; ; n++) {
1101
1196
  const id = uuidFrom(`${m.service}:${resource}:${n}`);
1102
1197
  if (!held.has(id)) return id;
@@ -1352,13 +1447,14 @@ async function finishWrite(m: DerivedManifest, call: DerivedCall, resource: stri
1352
1447
  // the managed database), at the write's moment
1353
1448
  const request = requestOfCall.get(call) ?? call.request;
1354
1449
  const context = (): Promise<SemanticsContext> => contextFor(m, { ...call, request: request.clone() }, { ...(scopeOfCall.get(call) ?? {}), ...(root !== undefined ? { root } : {}), clock: () => occurredAt });
1355
- if (m.onWrite) await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
1450
+ if (m.onWrite) await m.onWrite({ operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
1356
1451
  if (m.events) {
1357
- const events = m.events;
1358
- const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
1452
+ const endpoint = (kind: import('./events.ts').EndpointsDecl): import('./events.ts').EndpointsDecl => ({ ...kind, storedAs: storedType(m, kind.storedAs), ...(kind.joins ? { joins: Object.fromEntries(Object.entries(kind.joins).map(([name, join]) => [name, { ...join, storedAs: storedType(m, join.storedAs) }])) } : {}) });
1453
+ const events = { ...m.events, ...(m.events.store ? { store: storedType(m, m.events.store) } : {}), endpoints: 'storedAs' in m.events.endpoints ? endpoint(m.events.endpoints) : m.events.endpoints.map(endpoint) };
1454
+ const write = { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
1359
1455
  await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values!(await context(), write, type) : undefined);
1360
1456
  }
1361
- if (m.sockets?.length) socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
1457
+ if (m.sockets?.length) socketWrite(m.service, root, { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
1362
1458
  // a read held until the World writes (held-reads.ts) hears it
1363
1459
  heldReadsWrite(m.service, root, storedType(m, resource));
1364
1460
  // the subject's id as it stands after the write: the vendor's, when a live head minted one
@@ -1517,21 +1613,21 @@ function adoptedPath(m: DerivedManifest, call: DerivedCall, root?: string): Deri
1517
1613
  /** A call's fields with each declared reference that names an adopted subject by its local id moved to the adopted
1518
1614
  * id. A call (a handler's operation) need not say which subject type it writes, so every reference the pack declares
1519
1615
  * is tried: a field is rewritten only when its value is a local id the alias map holds for the reference's target. */
1520
- function adoptedInCall(service: string, fields: Record<string, unknown>, root?: string): Record<string, unknown> {
1521
- const refs = packReferences(service);
1616
+ function adoptedInCall(m: DerivedManifest, fields: Record<string, unknown>, root?: string): Record<string, unknown> {
1617
+ const service = m.service, refs = packReferences(service);
1522
1618
  if (refs.length === 0) return fields;
1523
1619
  const aliases = aliasesNow(service, root);
1524
1620
  if (aliases.size === 0) return fields;
1525
1621
  let out = fields;
1526
1622
  for (const ref of refs) {
1527
1623
  const local = ref.key(out as SubjectFields);
1528
- const adopted = local === undefined ? undefined : aliases.get(`${ref.to}:${local}`);
1624
+ const adopted = local === undefined ? undefined : aliases.get(`${storedType(m, ref.to)}:${local}`);
1529
1625
  if (adopted !== undefined && adopted !== local) out = { ...out, ...ref.adopt(out as SubjectFields, adopted) };
1530
1626
  }
1531
1627
  return out;
1532
1628
  }
1533
1629
  async function boundaryParams(m: DerivedManifest, call: DerivedCall, root?: string): Promise<Record<string, unknown>> {
1534
- return adoptedInCall(m.service, await readParams(m, call.request, call.operation), root);
1630
+ return adoptedInCall(m, await readParams(m, call.request, call.operation), root);
1535
1631
  }
1536
1632
 
1537
1633
  /** The refusal a subject's fields would draw from its resource's `unique` rules: another live subject (not `except`)
@@ -1726,7 +1822,7 @@ export async function serveCore(m: DerivedManifest, call: DerivedCall, scope: Co
1726
1822
  const parent = parentOfCall(decl, call);
1727
1823
  const parentId = parent ? parentValue(parent, call.params) : undefined;
1728
1824
  // the caller's tenant, when the vendor has tenancy and the resource is not shared by every tenant
1729
- const tenantField = m.tenant && !decl.shared ? m.tenant.field : undefined;
1825
+ const tenantField = m.tenant?.storage !== 'namespaced' && m.tenant && !decl.shared ? m.tenant.field : undefined;
1730
1826
  const tenant = tenantField && scope.tenant ? await scope.tenant(await contextFor(m, { ...call, request: call.request.clone() }, scope)) : undefined;
1731
1827
  if (tenantField && tenant === undefined) return { unmodeled: `the caller names no tenant (semantics/tenant.ts) for ${resource}` };
1732
1828
  const orphan = missingParent(m, resource, call, root, tenant);
@@ -1917,6 +2013,20 @@ export type SemanticsContext = {
1917
2013
  * random, and keeps as bookkeeping no door answers. The same label gives the same secret for the World's whole life,
1918
2014
  * and no one can compute it from ids the World shows, as they can a value derived from them alone. */
1919
2015
  secret(label: string): Promise<string>;
2016
+ /** Select an account at a pack's credential door. Packs retain their own vendor account/key semantics. */
2017
+ account(field?: string): Promise<string>;
2018
+ /** The selected organization's label and people from the World's organization registry. */
2019
+ organization(): Promise<WorldOrganization>;
2020
+ /** The selected deployment, or an empty string for the organization's own level. */
2021
+ deployment(): string;
2022
+ /** A vendor-safe label; retained links keep identity independent of names. */
2023
+ vendorName(label: string, rule: VendorNameRule, options?: { suffixed?: boolean; identity?: string }): string;
2024
+ /** Retain the native customer tenant for the selected organization. */
2025
+ tenant(resource: string, make: (organization: WorldOrganization) => Promise<string>): Promise<Record<string, unknown>>;
2026
+ /** Retain one native resource of this kind for the selected organization and deployment. */
2027
+ deploymentObject(kind: string, resource: string, make: (deployment: string) => Promise<string>): Promise<Record<string, unknown>>;
2028
+ /** Reconstruct a live native key, replacing its link when the vendor's reader refuses it. */
2029
+ accountKey(purpose: string, resource: string, key: { make: () => Promise<string>; read: (row: Record<string, unknown>) => Promise<string | undefined> }): Promise<{ id: string; secret: string }>;
1920
2030
  /** The World's machine pool (machines.ts), as the pack's `machine-pool` door enrolled it; `none` when none was. */
1921
2031
  machines: MachinePool;
1922
2032
  /** A signing key pair for `label` (an issuer's ID tokens, a provider's JWTs, a CA's or a log's key), PEM: RSA 2048
@@ -1930,7 +2040,7 @@ export type SemanticsContext = {
1930
2040
  /** Allocate a stored occurrence and append its fields under one action lock. The optional field factory is pure. */
1931
2041
  create(resource: string, fields: Record<string, unknown> | ((id: string) => Record<string, unknown>), operation: string): Promise<Record<string, unknown>>;
1932
2042
  /** Create under declared unique keys and the action lock; a collision returns the existing own-field view without a write. */
1933
- createUnique(resource: string, fields: Record<string, unknown> | ((id: string) => Record<string, unknown>), operation: string, options?: { input: 'omit' }): Promise<{ created: boolean; row: Record<string, unknown> }>;
2043
+ createUnique(resource: string, fields: Record<string, unknown> | ((id: string) => Record<string, unknown>), operation: string, options?: { input?: 'omit'; id?: string }): Promise<{ created: boolean; row: Record<string, unknown> }>;
1934
2044
  /** Decide an update from the current live stored row under the action lock; undefined leaves it unchanged. */
1935
2045
  change(resource: string, id: string, decide: (row: Record<string, unknown>) => Record<string, unknown> | undefined, operation: string, options?: { input: 'omit' }): Promise<Record<string, unknown> | undefined>;
1936
2046
  write(resource: string, id: string, fields: Record<string, unknown>, operation: string): Promise<Record<string, unknown>>;
@@ -2063,7 +2173,7 @@ export type HandlerContext = Omit<SemanticsContext, 'now' | 'core' | 'atomically
2063
2173
  };
2064
2174
  /** What a write hook reads: the writing call's context without its ways to write, so a hook that renders an event
2065
2175
  * cannot write again (and run itself again). */
2066
- export type WriteHookContext = Omit<HandlerContext, 'write' | 'batch' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'at' | 'over'>;
2176
+ export type WriteHookContext = Omit<HandlerContext, 'write' | 'batch' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'tenant' | 'deploymentObject' | 'accountKey' | 'at' | 'over'>;
2067
2177
  /** A derived pack's handler: one operation, by its operationId, over the contract's context. */
2068
2178
  export type Handler = (ctx: HandlerContext) => Promise<Response>;
2069
2179
 
@@ -2098,7 +2208,7 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2098
2208
  const raw = !parts && typeof body === 'string' && body !== '' ? await rawBody(call.request) : undefined;
2099
2209
  sentOf.set(call, { ...sentRequest(m, asAsked, parts ? Object.fromEntries(parts.filter((p) => p.filename === null).map((p) => [p.name, new TextDecoder().decode(p.body)])) : raw ?? body), ...(parts ? { parts } : {}), ...(raw ? { type: call.request.headers.get('content-type') ?? undefined } : {}) });
2100
2210
  // a handler reading the body itself sees the adopted id too (the boundary's one resolution, below)
2101
- if (body && typeof body === 'object' && !Array.isArray(body)) body = adoptedInCall(m.service, body as Record<string, unknown>, root);
2211
+ if (body && typeof body === 'object' && !Array.isArray(body)) body = adoptedInCall(m, body as Record<string, unknown>, root);
2102
2212
  // every read of the body is of a copy, so the handler's own `call.request` and the core's answer (`core()`) each
2103
2213
  // still find it unread, whichever of them reads it first
2104
2214
  const pristine = call.request.clone();
@@ -2111,12 +2221,25 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2111
2221
  });
2112
2222
  const paths = expandPaths(m, params);
2113
2223
  const stateChecks: StateChecks = new Map();
2224
+ const credentialDoor = m.descriptor?.credentialDoor ?? getPack(m.vendor)?.credentialDoor;
2225
+ const credentialFields = body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {};
2114
2226
  const rawResources = new WeakMap<Record<string, unknown>, string>();
2115
2227
  const rawRow = (resource: string, row: TwinResource): Record<string, unknown> => {
2116
2228
  const name = m.resources[resource] ? resource : Object.keys(m.resources).find((name) => storedType(m, name) === row.type);
2229
+ if (m.tenant?.storage === 'namespaced') {
2230
+ const [base, encoded] = row.type.split(TENANT_TYPE);
2231
+ const owner = m.resources[name ?? resource]?.shared && row[m.tenant.field] !== undefined ? row[m.tenant.field] : encoded ? decodeURIComponent(encoded) : 'default';
2232
+ row = copyResource(row);
2233
+ row.type = base!;
2234
+ row[m.tenant.field] = owner;
2235
+ }
2117
2236
  rawResources.set(row as Record<string, unknown>, name ?? resource);
2118
2237
  return row as Record<string, unknown>;
2119
2238
  };
2239
+ const liveNativeRow = (link: NativeAccountLink): Record<string, unknown> | undefined => {
2240
+ const row = context.row(link.resource, link.id);
2241
+ return row && row.deleted !== true ? row : undefined;
2242
+ };
2120
2243
  const context: SemanticsContext = {
2121
2244
  call,
2122
2245
  params,
@@ -2131,7 +2254,7 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2131
2254
  asVendor: (fn) => runAsVendorMove(fn),
2132
2255
  git: (name) => {
2133
2256
  if (!/^[A-Za-z0-9_.-]+(\/[A-Za-z0-9_.-]+)*$/.test(name) || name.split('/').some((seg) => seg === '.' || seg === '..')) throw new Error(`semantics: ${name} is not a git repository name`);
2134
- const plane = `${worldPaths(m.service, root).dir}/git`;
2257
+ const plane = `${worldPaths(m.service, root).dir}/git${accountScope(m) && accountScope(m) !== 'default' ? `/accounts/${encodeURIComponent(accountScope(m)!)}` : ''}`;
2135
2258
  return { store: new GitObjectStore(getActiveBlobStore(), `${plane}/repos/${name}`), refs: new GitRefs(getActiveWorldStore(), `${plane}/repos/${name}`) };
2136
2259
  },
2137
2260
  mail: (route, mail, headers) => sendMail(route, mail, headers),
@@ -2168,16 +2291,50 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2168
2291
  return value;
2169
2292
  },
2170
2293
  occurredAt: at,
2171
- secret: async (label) => hmac(await worldSeed(m.service, root, at), label, 'base64url'),
2294
+ secret: async (label) => hmac(await worldSeed(m.service, root, at), scopeLabel(m, label), 'base64url'),
2295
+ account: async (field = credentialDoor?.account?.field ?? 'account') => {
2296
+ return worldAccount(m.service, root, credentialFields[field] ?? 'default', at, call.request.headers.get('x-volter-world-activity') === 'provisioning');
2297
+ },
2298
+ organization: async () => {
2299
+ const id = await context.account();
2300
+ const row = projectOwnerResources('_world', root, { type: '_world_organization', id }).find((row) => row.type === '_world_organization' && row.id === id && row.deleted !== true);
2301
+ return row ? { id, label: row.label as string, members: [...(row.members as string[])] } : { id, label: id, members: ['owner@world.test'] };
2302
+ },
2303
+ deployment: () => {
2304
+ const id = credentialFields[credentialDoor?.deployment?.field ?? 'deployment'] ?? '';
2305
+ if (id !== '' && !isWorldAccountName(id)) throw new Error('Credential deployment must be a name of at most 128 letters, digits, dots, underscores or hyphens');
2306
+ return id;
2307
+ },
2308
+ vendorName,
2309
+ tenant: async (resource, make) => {
2310
+ const id = await context.account();
2311
+ return retainNativeAccountLink(m.service, root, at, call, '_world_account', id, 'tenant', resource,
2312
+ async () => make(await context.organization()), async (link) => liveNativeRow(link));
2313
+ },
2314
+ deploymentObject: async (kind, resource, make) => {
2315
+ const organization = await context.account(), deployment = context.deployment();
2316
+ return retainNativeAccountLink(m.service, root, at, call, '_world_deployment_object', `${organization}/${deployment}/${kind}`, undefined, resource,
2317
+ () => make(deployment), async (link) => liveNativeRow(link));
2318
+ },
2319
+ accountKey: async (purpose, resource, key) => {
2320
+ const organization = await context.account(), deployment = context.deployment();
2321
+ return retainNativeAccountLink(m.service, root, at, call, '_world_account_key', `${organization}/${deployment}/${purpose}`, undefined, resource,
2322
+ () => key.make(), async (link) => {
2323
+ const row = liveNativeRow(link);
2324
+ if (!row) return undefined;
2325
+ const secret = await key.read(row);
2326
+ return secret === undefined ? undefined : { id: link.id, secret };
2327
+ });
2328
+ },
2172
2329
  record: async (type, fields, id, operation) => {
2173
2330
  if (!type.startsWith('_')) throw new Error(`semantics: ${type} is not a bookkeeping type (bookkeeping types start with _)`);
2174
2331
  // a bookkeeping subject a resource counts (`counts` naming it: sentry's issue users) keeps that count as any
2175
2332
  // child does, the parent it left recounted too
2176
- const keeps = counted(m, type);
2333
+ const keeps = counted(m, type), physicalType = storedType(m, type);
2177
2334
  if (id !== undefined) {
2178
2335
  const before = operation ? stateBeforeWrite(m, type, id, root) : undefined;
2179
- const prior = keeps ? resourcesOfType(m.service, type, root).find((r) => r.id === id) : undefined;
2180
- const { resource: row, result } = await applyTwinWrite(m.service, { operation: operation ?? `${type.slice(1)}.record`, subjectType: type, subjectId: id, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } }, root);
2336
+ const prior = keeps ? resourcesOfType(m.service, physicalType, root).find((r) => r.id === id) : undefined;
2337
+ const { resource: row, result } = await applyTwinWrite(m.service, { operation: operation ?? `${type.slice(1)}.record`, subjectType: physicalType, subjectId: id, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } }, root);
2181
2338
  if (operation) observeStateWrite(m, result, before, root, stateChecks);
2182
2339
  if (keeps) await keepCounts(m, call, type, row as unknown as Record<string, unknown>, params, root, at, prior && { ...prior } as Record<string, unknown>);
2183
2340
  return id;
@@ -2188,25 +2345,25 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2188
2345
  const prefix = `${type.slice(1)}_`;
2189
2346
  let max = 0n;
2190
2347
  for (const r of resources) {
2191
- if (r.type !== type) continue;
2348
+ if (r.type !== physicalType) continue;
2192
2349
  const digits = r.id.startsWith(prefix) ? r.id.slice(prefix.length) : '';
2193
2350
  if (/^\d+$/.test(digits) && BigInt(digits) > max) max = BigInt(digits);
2194
2351
  }
2195
2352
  return `${prefix}${max + 1n}`;
2196
2353
  })();
2197
- return { kind: 'write', value: subject, write: { operation: operation ?? `${type.slice(1)}.record`, subjectType: type, subjectId: subject, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } } };
2198
- }, root, () => resourcesOfType(m.service, type, root));
2354
+ return { kind: 'write', value: subject, write: { operation: operation ?? `${type.slice(1)}.record`, subjectType: physicalType, subjectId: subject, fields, ...provisioningInput(call), occurredAt: at, actor: { kind: 'system' } } };
2355
+ }, root, () => resourcesOfType(m.service, physicalType, root));
2199
2356
  if (operation && result) observeStateWrite(m, result, undefined, root, stateChecks);
2200
- const made = keeps && value !== undefined ? resourcesOfType(m.service, type, root).find((r) => r.id === value) : undefined;
2357
+ const made = keeps && value !== undefined ? resourcesOfType(m.service, physicalType, root).find((r) => r.id === value) : undefined;
2201
2358
  if (made) await keepCounts(m, call, type, made as unknown as Record<string, unknown>, params, root, at);
2202
2359
  return value;
2203
2360
  },
2204
2361
  get machines() {
2205
- const enrolled = resourcesOfType(m.service, MACHINE_POOL, root).find((r) => r.id === 'pool') as unknown as (LightPoolOptions & { kind?: MachinePool['kind']; httpReplies?: MachineHttpReply[] }) | undefined;
2206
- return guardedMachinePool(poolFor(m.service, root, enrolled?.kind ?? 'none', enrolled?.httpReplies, { images: enrolled?.images }), m.service, ['retrieve', 'list'].includes(call.operation.class) || (m.reads?.includes(call.operation.id) ?? false));
2362
+ const enrolled = resourcesOfType(m.service, MACHINE_POOL, root).find((r) => r.id === 'pool') as unknown as { kind?: MachinePool['kind']; httpReplies?: MachineHttpReply[] } | undefined;
2363
+ return guardedMachinePool(poolFor(m.service, root, enrolled?.kind ?? 'none', enrolled?.httpReplies), m.service, ['retrieve', 'list'].includes(call.operation.class) || (m.reads?.includes(call.operation.id) ?? false));
2207
2364
  },
2208
- signingKey: (label, alg) => worldSigningKey(m.service, root, at, label, alg ?? 'RS256'),
2209
- heldSigningKey: (label, alg) => heldWorldSigningKey(m.service, root, label, alg ?? 'RS256'),
2365
+ signingKey: (label, alg) => worldSigningKey(m.service, root, at, scopeLabel(m, label), alg ?? 'RS256'),
2366
+ heldSigningKey: (label, alg) => heldWorldSigningKey(m.service, root, scopeLabel(m, label), alg ?? 'RS256'),
2210
2367
  create: (resource, fields, operation) => atomicWrite(m, call, resource, (snapshot) => {
2211
2368
  const id = mintId(m, resource, root, at, snapshot);
2212
2369
  return { id, fields: typeof fields === 'function' ? fields(id) : fields };
@@ -2217,9 +2374,13 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2217
2374
  if (!rules?.length) throw new Error('createUnique requires declared unique keys');
2218
2375
  let existing: Record<string, unknown> | undefined;
2219
2376
  const row = await atomicWrite(m, call, resource, (snapshot) => {
2220
- const id = mintId(m, resource, root, at, snapshot);
2377
+ const id = options?.id ?? mintId(m, resource, root, at, snapshot);
2221
2378
  const selected = typeof fields === 'function' ? fields(id) : fields;
2222
- const hit = snapshot.find((r) => r.type === storedType(m, resource) && r.deleted !== true && rules.some((rule) => rule.fields.every((f) => selected[f] !== undefined && view(m, resource, r)[f] === selected[f])));
2379
+ const hit = snapshot.find((r) => {
2380
+ if (r.type !== storedType(m, resource) || r.deleted === true) return false;
2381
+ const fields = { [m.resources[resource]?.idAs ?? 'id']: r.id, ...ownFields(r) };
2382
+ return rules.some((rule) => rule.fields.every((field) => selected[field] !== undefined && fields[field] === selected[field]));
2383
+ });
2223
2384
  if (hit) { existing = view(m, resource, hit); return undefined; }
2224
2385
  return { id, fields: selected };
2225
2386
  }, operation, params, root, at, undefined, stateChecks, options?.input === 'omit');
@@ -2251,11 +2412,20 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2251
2412
  },
2252
2413
  writeDetailed: (resource, id, fields, operation) => writeDetailed(m, call, resource, id, fields, operation, params, root, at, stateChecks),
2253
2414
  resolve: (resource, id) => resolveSubjectId(m.service, storedType(m, resource), id, root),
2254
- storedType: (resource) => storedType(m, resource),
2415
+ storedType: (resource) => m.resources[resource]?.storedAs ?? resource,
2255
2416
  ok: (fields) => Response.json({ ...(m.success ?? {}), ...fields }),
2256
2417
  row: (resource, id, opts) => { const row = stored(m, resource, root, opts).find((r) => r.id === id); return row ? rawRow(resource, row) : undefined; },
2257
2418
  rowsRaw: (resource, opts) => stored(m, resource, root, opts).map((row) => rawRow(resource, row)),
2258
- tree: () => twinResources(m.service, root) as Array<Record<string, unknown>>,
2419
+ tree: () => {
2420
+ const rows = twinResources(m.service, root);
2421
+ if (m.tenant?.storage !== 'namespaced') return rows as Array<Record<string, unknown>>;
2422
+ const account = accountScope(m);
2423
+ return rows.filter(row => {
2424
+ const [base, encoded] = row.type.split(TENANT_TYPE);
2425
+ const name = Object.keys(m.resources).find(name => (m.resources[name]?.storedAs ?? name) === base) ?? base!;
2426
+ return account === undefined || m.resources[name]?.shared || (encoded ? decodeURIComponent(encoded) : 'default') === account;
2427
+ }).map(row => rawRow(row.type.split(TENANT_TYPE)[0]!, row));
2428
+ },
2259
2429
  history: (resource, id) => subjectHistory(m.service, { type: storedType(m, resource), id }, root).map((e) => ({ ...(e.operation ? { operation: e.operation } : {}), ...(e.fields ? { fields: e.fields as Record<string, unknown> } : {}), ...(e.occurredAt ? { occurredAt: e.occurredAt } : {}) })),
2260
2430
  raw: (body, init = {}) => new Response(body, { status: init.status ?? 200, headers: init.headers ?? {} }),
2261
2431
  atomically: async (decide) => {
@@ -2309,17 +2479,17 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
2309
2479
  engine: scope.database === undefined ? unboundDatabase(m.service) : managedDatabase(scope.database, { readOnly: scope.readOnly === true || isReadOnlyRequest(call.request) }),
2310
2480
  redis: (commands, options) => execRedisRun(commands, {
2311
2481
  root, occurredAt: at, service: m.service, readOnly: scope.readOnly === true || isReadOnlyRequest(call.request), dialect: options.dialect,
2312
- ...(options.database === undefined ? {} : { database: options.database }),
2482
+ ...(accountScope(m) && accountScope(m) !== 'default' ? { database: scopeLabel(m, options.database ?? 'default') } : options.database === undefined ? {} : { database: options.database }),
2313
2483
  ...(options.transaction === undefined ? {} : { transaction: options.transaction }),
2314
2484
  }),
2315
- live: liveAccess({ rows: (type) => resourcesOfType(m.service, type, root) as unknown as Array<Record<string, unknown>>, blob: (key) => readResourceBlob(m.service, key, root) }, call.request),
2485
+ live: liveAccess({ rows: (type) => stored(m, type, root).map(row => rawRow(type, row)), blob: (key) => readResourceBlob(m.service, scopeLabel(m, key), root) }, call.request),
2316
2486
  blobs: {
2317
- put: (key, bytes) => writeResourceBlob(m.service, key, bytes, root),
2318
- putStream: (key, body, accept) => writeResourceBlobStream(m.service, key, body, accept, root),
2319
- get: (key) => readResourceBlob(m.service, key, root),
2320
- remove: (key) => removeResourceBlob(m.service, key, root),
2321
- size: (key) => resourceBlobSize(m.service, key, root),
2322
- range: (key, start, endInclusive) => readResourceBlobRange(m.service, key, start, endInclusive, root),
2487
+ put: (key, bytes) => writeResourceBlob(m.service, scopeLabel(m, key), bytes, root),
2488
+ putStream: (key, body, accept) => writeResourceBlobStream(m.service, scopeLabel(m, key), body, accept, root),
2489
+ get: (key) => readResourceBlob(m.service, scopeLabel(m, key), root),
2490
+ remove: (key) => removeResourceBlob(m.service, scopeLabel(m, key), root),
2491
+ size: (key) => resourceBlobSize(m.service, scopeLabel(m, key), root),
2492
+ range: (key, start, endInclusive) => readResourceBlobRange(m.service, scopeLabel(m, key), start, endInclusive, root),
2323
2493
  },
2324
2494
  merge: (resource, prior, next) => mergeField(m.resources[resource]?.update, prior, next),
2325
2495
  conflict: (resource, fields, except) => {
@@ -2365,12 +2535,14 @@ const BATCH_REFUSED_READS = ['get', 'rows', 'tree', 'history', 'find', 'list', '
2365
2535
  /** The members that write, or wait on writes, or answer as another context: each lands the staged writes first. */
2366
2536
  const BATCH_LANDING_MEMBERS = ['create', 'createUnique', 'change', 'accumulate', 'writeDetailed', 'record', 'atomically', 'remove', 'issue', 'at', 'over', 'core', 'awaitWrite', 'awaitCondition',
2367
2537
  // a World's seed and signing keys are written when first asked (worldSeed, worldSigningKey, as a vendor's move)
2368
- 'secret', 'signingKey'] as const;
2538
+ 'secret', 'signingKey', 'account', 'organization', 'tenant', 'deploymentObject', 'accountKey'] as const;
2369
2539
 
2370
2540
  /** `ctx.batch` over a context whose writes may be grouped (see `batchable`). */
2371
2541
  function bindBatch(m: DerivedManifest, call: DerivedCall, context: SemanticsContext, params: Record<string, unknown>, root: string | undefined, at: string,
2372
2542
  rawRow: (resource: string, row: TwinResource) => Record<string, unknown>): void {
2373
2543
  let staged: StagedWrite[] | undefined;
2544
+ // A callback's native object must land before the helper writes its retained link.
2545
+ let nativeAccountWrites = 0;
2374
2546
  // each staged subject as it will stand, by `type:id`, in the order first written
2375
2547
  const subjects = new Map<string, Subject>();
2376
2548
  const land = async (): Promise<void> => {
@@ -2384,7 +2556,13 @@ function bindBatch(m: DerivedManifest, call: DerivedCall, context: SemanticsCont
2384
2556
  for (const name of BATCH_LANDING_MEMBERS) {
2385
2557
  const plain = ctx[name];
2386
2558
  if (typeof plain !== 'function') continue;
2387
- ctx[name] = async (...args: unknown[]) => { await land(); return (plain as (...a: unknown[]) => unknown)(...args); };
2559
+ ctx[name] = async (...args: unknown[]) => {
2560
+ await land();
2561
+ const native = name === 'tenant' || name === 'deploymentObject' || name === 'accountKey';
2562
+ if (native) nativeAccountWrites++;
2563
+ try { return await (plain as (...a: unknown[]) => unknown)(...args); }
2564
+ finally { if (native) nativeAccountWrites--; }
2565
+ };
2388
2566
  }
2389
2567
  for (const name of BATCH_REFUSED_READS) {
2390
2568
  const plain = ctx[name];
@@ -2397,7 +2575,7 @@ function bindBatch(m: DerivedManifest, call: DerivedCall, context: SemanticsCont
2397
2575
  const plainWrite = context.write, plainRow = context.row, plainRowsRaw = context.rowsRaw;
2398
2576
  context.write = async (resource, id, fields, operation) => {
2399
2577
  // a vendor's own move (ctx.asVendor) is the clock's write, recorded as the system's: it lands alone, in its place
2400
- if (!staged || inVendorMove()) { await land(); return plainWrite(resource, id, fields, operation); }
2578
+ if (!staged || nativeAccountWrites > 0 || inVendorMove()) { await land(); return plainWrite(resource, id, fields, operation); }
2401
2579
  const type = storedType(m, resource), key = `${type}:${id}`;
2402
2580
  // the one subject, as the memoized tree holds it (readTreeMap would copy the whole tree on every staged write)
2403
2581
  const subject = foldLocalWrite(subjects.get(key) ?? heldSubject(m.service, type, id, root), type, id, fields as SubjectFields, at);
@@ -2614,7 +2792,7 @@ export function authRefusal(m: DerivedManifest, request: Request, root?: string)
2614
2792
  const held = m.auth.held;
2615
2793
  if (held && !held.standing?.includes(key)) {
2616
2794
  const hash = sha256(key);
2617
- const holds = resourcesOfType(m.service, held.storedAs, root).some((r) => r.deleted !== true && (r as Record<string, unknown>)[held.hashField] === hash && (held.unless === undefined || (r as Record<string, unknown>)[held.unless] !== true));
2795
+ const holds = stored(m, held.storedAs, root).some((r) => r.deleted !== true && (r as Record<string, unknown>)[held.hashField] === hash && (held.unless === undefined || (r as Record<string, unknown>)[held.unless] !== true));
2618
2796
  if (!holds) return vendorError(errorManifest, invalid);
2619
2797
  }
2620
2798
  return undefined;
@@ -2691,7 +2869,7 @@ export function crossCutting(m: DerivedManifest, opts: CoreScope & { readOnly?:
2691
2869
  const url = new URL(request.url);
2692
2870
  const signature = hashFieldValue({ path: url.pathname + url.search, body: await bodyTextOf(request.clone()) });
2693
2871
  const id = `idem_${hex(key)}`;
2694
- const prior = twinResources(m.service, opts.root).find((r) => r.type === m.idempotency!.storedAs && r.id === id) as Record<string, unknown> | undefined;
2872
+ const prior = twinResources(m.service, opts.root).find((r) => r.type === storedType(m, m.idempotency!.storedAs) && r.id === id) as Record<string, unknown> | undefined;
2695
2873
  if (prior) {
2696
2874
  if (m.idempotency.conflict && prior.requestHash !== undefined && prior.requestHash !== signature) return vendorError(m, m.idempotency.conflict);
2697
2875
  const answer = prior.response as { status: number; body?: unknown; text?: string; contentType?: string };
@@ -2704,7 +2882,7 @@ export function crossCutting(m: DerivedManifest, opts: CoreScope & { readOnly?:
2704
2882
  if (m.idempotency.onlySuccess && (response.status < 200 || response.status >= 300)) return response;
2705
2883
  const contentType = response.headers.get('content-type') ?? 'application/json';
2706
2884
  const text = await response.clone().text();
2707
- await applyTwinWrite(m.service, { operation: 'idempotency.record', subjectType: m.idempotency.storedAs, subjectId: id, fields: { response: { status: response.status, text, contentType }, requestHash: signature }, occurredAt: (opts.clock ?? worldNow)(), actor: { kind: 'system' } }, opts.root);
2885
+ await applyTwinWrite(m.service, { operation: 'idempotency.record', subjectType: storedType(m, m.idempotency.storedAs), subjectId: id, fields: { response: { status: response.status, text, contentType }, requestHash: signature }, occurredAt: (opts.clock ?? worldNow)(), actor: { kind: 'system' } }, opts.root);
2708
2886
  return response;
2709
2887
  }
2710
2888
  }