@exponential_app/engine 0.0.0-stage → 0.1.1

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.
Files changed (85) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +54 -0
  3. package/README.md +35 -2
  4. package/dist/capabilities.js +29 -0
  5. package/dist/capabilities.js.map +9 -0
  6. package/dist/chunks/index-3nf9f7pv.js +315 -0
  7. package/dist/chunks/index-3nf9f7pv.js.map +10 -0
  8. package/dist/chunks/index-3rgr16n2.js +443 -0
  9. package/dist/chunks/index-3rgr16n2.js.map +9 -0
  10. package/dist/chunks/index-79swfbsm.js +366 -0
  11. package/dist/chunks/index-79swfbsm.js.map +10 -0
  12. package/dist/chunks/index-knntt4cb.js +259 -0
  13. package/dist/chunks/index-knntt4cb.js.map +10 -0
  14. package/dist/chunks/index-m2845dn6.js +72 -0
  15. package/dist/chunks/index-m2845dn6.js.map +10 -0
  16. package/dist/chunks/index-pqpnz4ns.js +2213 -0
  17. package/dist/chunks/index-pqpnz4ns.js.map +9 -0
  18. package/dist/chunks/index-q4zws02e.js +2301 -0
  19. package/dist/chunks/index-q4zws02e.js.map +10 -0
  20. package/dist/chunks/index-s3xjr701.js +170 -0
  21. package/dist/chunks/index-s3xjr701.js.map +10 -0
  22. package/dist/chunks/index-sk8b0dh4.js +126 -0
  23. package/dist/chunks/index-sk8b0dh4.js.map +10 -0
  24. package/dist/chunks/index-tvsykgcv.js +3015 -0
  25. package/dist/chunks/index-tvsykgcv.js.map +19 -0
  26. package/dist/chunks/index-v0m7r4y3.js +6869 -0
  27. package/dist/chunks/index-v0m7r4y3.js.map +13 -0
  28. package/dist/consent.js +24 -0
  29. package/dist/consent.js.map +9 -0
  30. package/dist/index.js +654 -0
  31. package/dist/index.js.map +12 -0
  32. package/dist/lifecycle.js +11 -0
  33. package/dist/lifecycle.js.map +9 -0
  34. package/dist/outbox.js +27 -0
  35. package/dist/outbox.js.map +9 -0
  36. package/dist/pdf.js +15 -0
  37. package/dist/pdf.js.map +9 -0
  38. package/dist/providers.js +26 -0
  39. package/dist/providers.js.map +9 -0
  40. package/dist/runner.js +24 -0
  41. package/dist/runner.js.map +9 -0
  42. package/dist/tools.js +38 -0
  43. package/dist/tools.js.map +9 -0
  44. package/dist/types/domain-contract/src/app-records.d.ts +3148 -0
  45. package/dist/types/domain-contract/src/app-share.d.ts +222 -0
  46. package/dist/types/domain-contract/src/apps.d.ts +612 -0
  47. package/dist/types/eengine/fixtures/providers.json +55 -0
  48. package/dist/types/eengine/src/broker.d.ts +80 -0
  49. package/dist/types/eengine/src/capabilities.d.ts +88 -0
  50. package/dist/types/eengine/src/consent.d.ts +50 -0
  51. package/dist/types/eengine/src/index.d.ts +12 -0
  52. package/dist/types/eengine/src/instance.d.ts +131 -0
  53. package/dist/types/eengine/src/lifecycle.d.ts +64 -0
  54. package/dist/types/eengine/src/outbox.d.ts +3 -0
  55. package/dist/types/eengine/src/pdf.d.ts +36 -0
  56. package/dist/types/eengine/src/providers.d.ts +38 -0
  57. package/dist/types/eengine/src/runner.d.ts +82 -0
  58. package/dist/types/eengine/src/stores.d.ts +125 -0
  59. package/dist/types/eengine/src/synced.d.ts +59 -0
  60. package/dist/types/eengine/src/tools.d.ts +79 -0
  61. package/dist/types/eengine/src/web/index.d.ts +20 -0
  62. package/dist/types/eengine/src/web/mcp.d.ts +27 -0
  63. package/dist/types/eengine/src/web/platform.d.ts +91 -0
  64. package/dist/types/eengine/src/web/stores.d.ts +125 -0
  65. package/dist/types/emcp/fixtures/automations.json +521 -0
  66. package/dist/types/emcp/fixtures/hub.json +498 -0
  67. package/dist/types/emcp/src/access.d.ts +55 -0
  68. package/dist/types/emcp/src/audit.d.ts +22 -0
  69. package/dist/types/emcp/src/automations.d.ts +111 -0
  70. package/dist/types/emcp/src/exposes.d.ts +242 -0
  71. package/dist/types/emcp/src/hub.d.ts +63 -0
  72. package/dist/types/emcp/src/index.d.ts +7 -0
  73. package/dist/types/emcp/src/pure.d.ts +11 -0
  74. package/dist/types/emcp/src/upstream.d.ts +288 -0
  75. package/dist/types/emcp/src/vault.d.ts +34 -0
  76. package/dist/web/index.js +791 -0
  77. package/dist/web/index.js.map +13 -0
  78. package/fixtures/bundle.json +1165 -0
  79. package/fixtures/capabilities.json +434 -0
  80. package/fixtures/consent.json +146 -0
  81. package/fixtures/lifecycle.json +125 -0
  82. package/fixtures/outbox.json +22 -0
  83. package/fixtures/providers.json +55 -0
  84. package/fixtures/runner.json +2167 -0
  85. package/package.json +92 -4
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/eengine/src/runner.ts"],
4
+ "sourcesContent": [
5
+ "// E6 (#VAPP-266 … #VAPP-269): the runner, the prebuilt shell an exported app\n// runs in on macOS, Windows, Linux, Android, iOS and the web. It has no chrome\n// of its own: its navigation, its Permissions entry, a package that cannot\n// load and the failed-action toast are genUI surfaces made of existing\n// components, built here once (the Rust twin `eengine::runner` replays the\n// same cases; Swift and Kotlin reach it over `eengine-ffi`). Locked by\n// `fixtures/runner.json`.\n\nimport { navScreens, packageScreens } from \"@exponential_app/eui/host\"\n\nimport fixture from \"../fixtures/runner.json\" with { type: \"json\" }\nimport type { ManifestEntry } from \"./capabilities\"\nimport { scopeDef } from \"./capabilities\"\nimport type { SurfaceComponent } from \"./consent\"\nimport type { NetworkPolicy } from \"./providers\"\n\nexport const RUNNER_COPY = fixture.copy\nexport const RUNNER_BUNDLE = fixture.bundle\nexport const RUNNER_SHELL = fixture.shell\n\nexport type FormFactor = `wide` | `compact`\nexport type RunnerFailure = `missing` | `config` | `signature` | `invalid` | `busy`\n\n// ---------------------------------------------------------------- config\n\n/** A bundle's `runner.json`, validated. */\nexport interface RunnerConfig {\n id: string\n name: string\n version: string\n network: NetworkPolicy\n}\n\nexport interface RunnerConfigIssue {\n key: string\n code: string\n}\n\nconst CONFIG_KEYS = Object.keys(fixture.config.keys)\n\n/** `runner.json` → the config (absent optional keys = their default) and its issues, in key order then the extra keys sorted. */\nexport function runnerConfig(input: unknown): { config: RunnerConfig | null; issues: RunnerConfigIssue[] } {\n if (!input || typeof input !== `object` || Array.isArray(input)) return { config: null, issues: [{ key: ``, code: `notAnObject` }] }\n const raw = input as Record<string, unknown>\n const issues: RunnerConfigIssue[] = []\n const out: Record<string, unknown> = {}\n for (const key of CONFIG_KEYS) {\n const def = (fixture.config.keys as Record<string, { required?: boolean; pattern?: string; maxLength?: number; values?: string[]; default?: string }>)[key]!\n const value = raw[key]\n if (value === undefined) {\n if (def.required) issues.push({ key, code: `missing` })\n else out[key] = def.default\n continue\n }\n const ok =\n typeof value === `string` &&\n (!def.pattern || new RegExp(def.pattern).test(value)) &&\n (!def.maxLength || ([...value].length <= def.maxLength && value.trim().length > 0)) &&\n (!def.values || def.values.includes(value))\n if (ok) out[key] = value\n else issues.push({ key, code: `invalid` })\n }\n const extra = Object.keys(raw)\n .filter((k) => !CONFIG_KEYS.includes(k))\n .sort()\n for (const key of extra) issues.push({ key, code: fixture.config.forbidden.includes(key) ? `forbidden` : `unknown` })\n return { config: issues.length ? null : (out as unknown as RunnerConfig), issues }\n}\n\n// ---------------------------------------------------------------- the shell\n\ntype Pkg = Record<string, unknown> & { name?: unknown; templates?: unknown; screens?: unknown; capabilities?: unknown }\ntype Template = Record<string, unknown> & { components?: unknown; data?: unknown }\n\nconst manifestOf = (pkg: Pkg): ManifestEntry[] => (Array.isArray(pkg.capabilities) ? (pkg.capabilities as ManifestEntry[]) : [])\n\n/** Does the package declare an `ask` scope (the shell then offers Permissions)? */\nexport function runnerHasPermissions(pkg: Pkg): boolean {\n return manifestOf(pkg).some((m) => {\n const def = scopeDef(m.id)\n return !!def && !def.scope.hostOnly && def.scope.consent === `ask`\n })\n}\n\nconst event = (name: string, context?: Record<string, unknown>) => ({ event: context ? { name, context } : { name } })\n\n/** The shell around one screen's template: its components (the template's root becomes `runner_page`) and its extra data. */\nfunction shellComponents(pkg: Pkg, screen: string, components: SurfaceComponent[], formFactor: FormFactor): { components: SurfaceComponent[]; data: Record<string, unknown> | null } {\n const nav = navScreens(pkg)\n const permissions = runnerHasPermissions(pkg)\n const tabs = nav.length >= 2\n const page = components.map((c) => (c.id === `root` ? { ...c, id: `runner_page` } : c))\n if (!tabs && !permissions)\n return { components: [{ id: `root`, component: `Box`, style: { display: `flex`, flexDirection: `column`, height: `100%`, overflowY: `auto` }, children: [`runner_page`] }, ...page], data: null }\n const S = fixture.shell\n if (formFactor === `wide`) {\n const nav_children = [`runner_title`, ...(tabs ? [`runner_screens`] : []), ...(permissions ? [`runner_permissions`] : [])]\n const shell: SurfaceComponent[] = [\n { id: `root`, component: `Stack`, direction: `horizontal`, gap: `none`, align: `stretch`, style: { height: `100%` }, children: [`runner_sidebar`] },\n { id: `runner_sidebar`, component: `Sidebar`, width: S.sidebarWidth, collapseBelow: `never`, children: [`runner_scroll`], slots: { sidebar: `runner_nav` } },\n { id: `runner_scroll`, component: `Box`, style: { display: `flex`, flexDirection: `column`, flexGrow: 1, minHeight: 0, overflowY: `auto` }, children: [`runner_page`] },\n { id: `runner_nav`, component: `Stack`, gap: `sm`, style: { padding: `$spacing.sm` }, children: nav_children },\n { id: `runner_title`, component: `Heading`, text: typeof pkg.name === `string` ? pkg.name : ``, level: S.titleLevel },\n ]\n if (tabs)\n shell.push(\n { id: `runner_screens`, component: `List`, children: nav.map((_, i) => `runner_screen_${i}`) },\n ...nav.map((s, i) => ({\n id: `runner_screen_${i}`,\n component: `Row`,\n title: s.title ?? s.template,\n ...(s.icon ? { icon: s.icon } : {}),\n selected: s.template === screen,\n density: `compact`,\n pressable: true,\n on: { press: event(`runner.navigate`, { screen: s.template }) },\n })),\n )\n if (permissions)\n shell.push({ id: `runner_permissions`, component: `Row`, title: fixture.copy.permissions, icon: S.permissionsIcon, density: `compact`, pressable: true, on: { press: event(`runner.permissions`) } })\n return { components: [...shell, ...page], data: null }\n }\n const bar = [...(tabs ? [`runner_tabs_box`] : []), ...(permissions ? [`runner_permissions`] : [])]\n const shell: SurfaceComponent[] = [\n { id: `root`, component: `Stack`, gap: `none`, style: { height: `100%` }, children: [`runner_main`, `runner_separator`, `runner_bar`] },\n { id: `runner_main`, component: `Box`, style: { display: `flex`, flexDirection: `column`, flexGrow: 1, minHeight: 0, overflowY: `auto` }, children: [`runner_page`] },\n { id: `runner_separator`, component: `Separator` },\n { id: `runner_bar`, component: `Stack`, direction: `horizontal`, gap: `sm`, align: `center`, justify: `end`, style: { padding: `$spacing.sm` }, children: bar },\n ]\n if (tabs)\n shell.push(\n { id: `runner_tabs_box`, component: `Box`, style: { display: `flex`, flexDirection: `column`, flexGrow: 1, minWidth: 0 }, children: [`runner_tabs`] },\n {\n id: `runner_tabs`,\n component: `Tabs`,\n tabs: nav.map((s) => ({ label: s.title ?? s.template, value: s.template, ...(s.icon ? { icon: s.icon } : {}) })),\n value: { \"@path\": `/runner/screen` },\n fill: true,\n on: { change: event(`runner.navigate`, { screen: { \"@path\": `/runner/screen` } }) },\n },\n )\n if (permissions)\n shell.push({ id: `runner_permissions`, component: `Button`, label: fixture.copy.permissions, icon: S.permissionsIcon, size: `icon`, variant: `ghost`, on: { press: event(`runner.permissions`) } })\n return { components: [...shell, ...page], data: tabs ? { runner: { screen } } : null }\n}\n\n/**\n * The package the runner's painter installs: every screen's template wrapped\n * in the shell for `formFactor`. A package without screens gets one, its first\n * template. The Engine keeps the ORIGINAL package (its signature, its grants);\n * only the painter sees this one.\n */\nexport function runnerPackage(pkg: Pkg, formFactor: FormFactor): Pkg {\n const templates = pkg.templates && typeof pkg.templates === `object` ? (pkg.templates as Record<string, Template>) : {}\n let out: Pkg = pkg\n if (packageScreens(pkg).length === 0) {\n const first = Object.keys(templates)[0]\n if (!first) return pkg\n out = { ...pkg, screens: [{ template: first, title: typeof pkg.name === `string` ? pkg.name : first }] }\n }\n const wrapped: Record<string, Template> = { ...templates }\n for (const s of packageScreens(out)) {\n const t = templates[s.template]\n if (!t || !Array.isArray(t.components)) continue\n const { components, data } = shellComponents(out, s.template, t.components as SurfaceComponent[], formFactor)\n if (components === t.components) continue\n const base = t.data && typeof t.data === `object` && !Array.isArray(t.data) ? (t.data as Record<string, unknown>) : {}\n wrapped[s.template] = { ...t, components, ...(data ? { data: { ...base, ...data } } : {}) }\n }\n return { ...out, templates: wrapped }\n}\n\n// ---------------------------------------------------------------- surfaces\n\n/** What the runner paints instead of the app when its bundle cannot load. */\nexport function runnerFailure(kind: RunnerFailure): SurfaceComponent[] {\n return [{ id: `root`, component: `EmptyState`, icon: fixture.shell.failureIcon, title: fixture.copy.failed.title, description: fixture.copy.failed[kind] }]\n}\n\n/** The failed-action toast (a refused call, a refused link): surface `toast`; a broker reason code reads as `copy.refusals`. */\nexport function runnerToast(message: string): SurfaceComponent[] {\n const refusals = fixture.copy.refusals as Record<string, string>\n return [{ id: `root`, component: `Toast`, title: Object.hasOwn(refusals, message) ? refusals[message]! : message, type: `error` }]\n}\n"
6
+ ],
7
+ "mappings": ";;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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",
8
+ "debugId": "8614238A2FCD206064756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,170 @@
1
+ // packages/eengine/src/stores.ts
2
+ class StoreError2 extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(message);
6
+ this.code = code;
7
+ this.name = `StoreError`;
8
+ }
9
+ }
10
+ function applyBatch2(rows, batch, by) {
11
+ const next = rows.map((r) => ({ ...r }));
12
+ const delta = { upserts: [], deletes: [] };
13
+ const events = [];
14
+ const ids = [];
15
+ for (const w of batch.writes) {
16
+ const at = next.findIndex((r) => r.id === w.id);
17
+ if (w.op === `upsert`) {
18
+ if (at >= 0) {
19
+ if (w.create)
20
+ throw new StoreError2(`CONFLICT`, `${w.id} exists`);
21
+ const row = next[at];
22
+ const before = row.data;
23
+ next[at] = { ...row, data: { ...row.data ?? {}, ...w.data }, updatedAt: batch.at };
24
+ delta.upserts.push(next[at]);
25
+ events.push({ recordId: w.id, op: `update`, actor: by, writeId: batch.writeId, patch: w.data, before, at: batch.at });
26
+ } else {
27
+ if (!w.create)
28
+ throw new StoreError2(`NOT_FOUND`, `no record ${w.id}`);
29
+ const row = { id: w.id, kind: w.kind, data: { ...w.data }, createdBy: by, authorId: by, createdAt: batch.at, updatedAt: batch.at };
30
+ next.push(row);
31
+ delta.upserts.push(row);
32
+ events.push({ recordId: w.id, op: `create`, actor: by, writeId: batch.writeId, patch: w.data, before: null, at: batch.at });
33
+ }
34
+ } else {
35
+ if (at < 0)
36
+ throw new StoreError2(`NOT_FOUND`, `no record ${w.id}`);
37
+ const [gone] = next.splice(at, 1);
38
+ delta.deletes.push(w.id);
39
+ events.push({ recordId: w.id, op: `delete`, actor: by, writeId: batch.writeId, patch: null, before: gone.data, at: batch.at });
40
+ }
41
+ ids.push(w.id);
42
+ }
43
+ return { rows: next, ack: { ids, replayed: false }, delta, events };
44
+ }
45
+
46
+ class LocalRecordStore2 {
47
+ persistence;
48
+ cache = new Map;
49
+ listeners = new Map;
50
+ queue = Promise.resolve();
51
+ constructor(persistence) {
52
+ this.persistence = persistence;
53
+ }
54
+ loaded(app) {
55
+ let rows = this.cache.get(app);
56
+ if (!rows) {
57
+ rows = this.persistence.load(app);
58
+ this.cache.set(app, rows);
59
+ rows.catch(() => this.cache.delete(app));
60
+ }
61
+ return rows;
62
+ }
63
+ async rows(app) {
64
+ return [...await this.loaded(app)];
65
+ }
66
+ write(app, batch, by) {
67
+ const run = async () => {
68
+ const known = await this.persistence.ack(app, batch.writeId);
69
+ if (known)
70
+ return { ids: known, replayed: true };
71
+ const applied = applyBatch2(await this.loaded(app), batch, by);
72
+ await this.persistence.commit(app, { writeId: batch.writeId, ids: applied.ack.ids, upserts: applied.delta.upserts, deletes: applied.delta.deletes, events: applied.events });
73
+ this.cache.set(app, Promise.resolve(applied.rows));
74
+ for (const on of this.listeners.get(app) ?? [])
75
+ on(applied.delta);
76
+ return applied.ack;
77
+ };
78
+ const result = this.queue.then(run, run);
79
+ this.queue = result.catch(() => {});
80
+ return result;
81
+ }
82
+ history(app, id) {
83
+ return this.persistence.history(app, id);
84
+ }
85
+ subscribe(app, on) {
86
+ const set = this.listeners.get(app) ?? new Set;
87
+ set.add(on);
88
+ this.listeners.set(app, set);
89
+ return () => void set.delete(on);
90
+ }
91
+ }
92
+
93
+ class MemoryPersistence2 {
94
+ rows = new Map;
95
+ acks = new Map;
96
+ events = new Map;
97
+ async load(app) {
98
+ return [...this.rows.get(app) ?? []];
99
+ }
100
+ async ack(app, writeId) {
101
+ return this.acks.get(`${app}
102
+ ${writeId}`) ?? null;
103
+ }
104
+ async commit(app, commit) {
105
+ let rows = (this.rows.get(app) ?? []).filter((r) => !commit.deletes.includes(r.id));
106
+ for (const row of commit.upserts) {
107
+ const i = rows.findIndex((r) => r.id === row.id);
108
+ rows = i >= 0 ? rows.map((r, j) => j === i ? row : r) : [...rows, row];
109
+ }
110
+ this.rows.set(app, rows);
111
+ this.acks.set(`${app}
112
+ ${commit.writeId}`, commit.ids);
113
+ for (const e of commit.events)
114
+ this.events.set(`${app}
115
+ ${e.recordId}`, [...this.events.get(`${app}
116
+ ${e.recordId}`) ?? [], e]);
117
+ }
118
+ async history(app, id) {
119
+ return [...this.events.get(`${app}
120
+ ${id}`) ?? []];
121
+ }
122
+ }
123
+
124
+ class MemoryRecordStore2 extends LocalRecordStore2 {
125
+ constructor() {
126
+ super(new MemoryPersistence2);
127
+ }
128
+ }
129
+
130
+ class MemoryVault2 {
131
+ values = new Map;
132
+ async read(key) {
133
+ return this.values.get(key) ?? null;
134
+ }
135
+ async write(key, value) {
136
+ this.values.set(key, value);
137
+ }
138
+ async delete(key) {
139
+ this.values.delete(key);
140
+ }
141
+ }
142
+
143
+ class MemoryBlobStore2 {
144
+ files = new Map;
145
+ async put(app, name, mimeType, bytes) {
146
+ const id = crypto.randomUUID();
147
+ this.files.set(`${app}/${id}`, { name, mimeType, bytes: bytes.slice() });
148
+ return id;
149
+ }
150
+ async get(app, id) {
151
+ return this.files.get(`${app}/${id}`)?.bytes.slice() ?? null;
152
+ }
153
+ async delete(app, id) {
154
+ this.files.delete(`${app}/${id}`);
155
+ }
156
+ }
157
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
158
+ async function localDeviceId2(vault, key) {
159
+ const stored = (await vault.read(key))?.trim();
160
+ if (stored && UUID.test(stored))
161
+ return stored;
162
+ const id = crypto.randomUUID();
163
+ await vault.write(key, id);
164
+ return id;
165
+ }
166
+
167
+ export { StoreError2, applyBatch2, LocalRecordStore2, MemoryPersistence2, MemoryRecordStore2, MemoryVault2, MemoryBlobStore2, localDeviceId2 };
168
+
169
+ //# debugId=964FE18B6622157D64756E2164756E21
170
+ //# sourceMappingURL=index-s3xjr701.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/eengine/src/stores.ts"],
4
+ "sourcesContent": [
5
+ "// VAPP-269: the record store an AppInstance writes through, the twin of the\n// crate's `providers::RecordStore` + `stores.rs`. One store serves every app\n// of a host, keyed by app id: a batch applies all or nothing, a known\n// `writeId` answers its first ack (`replayed`), every write leaves one history\n// event per record. `LocalRecordStore` keeps each app's rows in memory over a\n// `RecordPersistence` (memory here; SQLite-WASM on OPFS and IndexedDB in\n// `./web/stores`), so a read never waits on storage.\n\nimport type { RecordRow } from \"@exponential_app/eui/host\"\n\nimport type { Failure, Vault } from \"./broker\"\nimport type { BlobStore } from \"./tools\"\n\n/** One write of a batch (the store's spelling of the outbox writes). */\nexport type RecordWrite = { op: `upsert`; id: string; kind: string; data: Record<string, unknown>; create: boolean } | { op: `delete`; id: string }\n\n/** One call's writes, all or nothing, with its idempotency key. */\nexport interface WriteBatch {\n writeId: string\n writes: RecordWrite[]\n /** ISO time of the write (createdAt / updatedAt of what it touches). */\n at: string\n}\n\n/** The ids in write order; `replayed` = the writeId was applied before. */\nexport interface WriteAck {\n ids: string[]\n replayed: boolean\n}\n\n/** One history event of a record. */\nexport interface RecordEvent {\n recordId: string\n op: `create` | `update` | `delete`\n actor: string | null\n writeId: string\n patch: unknown\n before: unknown\n at: string\n}\n\nexport interface RecordsDelta {\n upserts: RecordRow[]\n deletes: string[]\n}\n\n/** Where an app's records live. Rejects with a `StoreError`. */\nexport interface EngineRecordStore {\n rows(app: string): Promise<RecordRow[]>\n write(app: string, batch: WriteBatch, by: string | null): Promise<WriteAck>\n history(app: string, id: string): Promise<RecordEvent[]>\n /** Called with every change to `app`'s rows; returns the cancel. */\n subscribe(app: string, on: (delta: RecordsDelta) => void): () => void\n}\n\n/** A store's refusal as an Error carrying the Failure. */\nexport class StoreError extends Error implements Failure {\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n this.name = `StoreError`\n }\n}\n\n/** Apply one batch to `rows` (all or nothing: throws with nothing changed).\n * Answers the next rows, the ack, the delta and the events. */\nexport function applyBatch(rows: readonly RecordRow[], batch: WriteBatch, by: string | null): { rows: RecordRow[]; ack: WriteAck; delta: RecordsDelta; events: RecordEvent[] } {\n const next = rows.map((r) => ({ ...r }))\n const delta: RecordsDelta = { upserts: [], deletes: [] }\n const events: RecordEvent[] = []\n const ids: string[] = []\n for (const w of batch.writes) {\n const at = next.findIndex((r) => r.id === w.id)\n if (w.op === `upsert`) {\n if (at >= 0) {\n if (w.create) throw new StoreError(`CONFLICT`, `${w.id} exists`)\n const row = next[at]!\n const before = row.data\n next[at] = { ...row, data: { ...(row.data ?? {}), ...w.data }, updatedAt: batch.at }\n delta.upserts.push(next[at]!)\n events.push({ recordId: w.id, op: `update`, actor: by, writeId: batch.writeId, patch: w.data, before, at: batch.at })\n } else {\n if (!w.create) throw new StoreError(`NOT_FOUND`, `no record ${w.id}`)\n const row: RecordRow = { id: w.id, kind: w.kind, data: { ...w.data }, createdBy: by, authorId: by, createdAt: batch.at, updatedAt: batch.at }\n next.push(row)\n delta.upserts.push(row)\n events.push({ recordId: w.id, op: `create`, actor: by, writeId: batch.writeId, patch: w.data, before: null, at: batch.at })\n }\n } else {\n if (at < 0) throw new StoreError(`NOT_FOUND`, `no record ${w.id}`)\n const [gone] = next.splice(at, 1)\n delta.deletes.push(w.id)\n events.push({ recordId: w.id, op: `delete`, actor: by, writeId: batch.writeId, patch: null, before: gone!.data, at: batch.at })\n }\n ids.push(w.id)\n }\n return { rows: next, ack: { ids, replayed: false }, delta, events }\n}\n\n/** One committed batch, what a persistence stores in one transaction. */\nexport interface RecordCommit {\n writeId: string\n ids: string[]\n upserts: RecordRow[]\n deletes: string[]\n events: RecordEvent[]\n}\n\n/** Where a `LocalRecordStore` keeps its rows. */\nexport interface RecordPersistence {\n load(app: string): Promise<RecordRow[]>\n /** The ids of an applied writeId, or null. */\n ack(app: string, writeId: string): Promise<string[] | null>\n /** One batch, all or nothing. */\n commit(app: string, commit: RecordCommit): Promise<void>\n history(app: string, id: string): Promise<RecordEvent[]>\n}\n\n/** Every app's rows in memory over a persistence; writes run one at a time. */\nexport class LocalRecordStore implements EngineRecordStore {\n private cache = new Map<string, Promise<RecordRow[]>>()\n private listeners = new Map<string, Set<(delta: RecordsDelta) => void>>()\n private queue: Promise<unknown> = Promise.resolve()\n\n constructor(readonly persistence: RecordPersistence) {}\n\n private loaded(app: string): Promise<RecordRow[]> {\n let rows = this.cache.get(app)\n if (!rows) {\n rows = this.persistence.load(app)\n this.cache.set(app, rows)\n rows.catch(() => this.cache.delete(app))\n }\n return rows\n }\n\n async rows(app: string): Promise<RecordRow[]> {\n return [...(await this.loaded(app))]\n }\n\n write(app: string, batch: WriteBatch, by: string | null): Promise<WriteAck> {\n const run = async (): Promise<WriteAck> => {\n const known = await this.persistence.ack(app, batch.writeId)\n if (known) return { ids: known, replayed: true }\n const applied = applyBatch(await this.loaded(app), batch, by)\n await this.persistence.commit(app, { writeId: batch.writeId, ids: applied.ack.ids, upserts: applied.delta.upserts, deletes: applied.delta.deletes, events: applied.events })\n this.cache.set(app, Promise.resolve(applied.rows))\n for (const on of this.listeners.get(app) ?? []) on(applied.delta)\n return applied.ack\n }\n const result = this.queue.then(run, run)\n this.queue = result.catch(() => {})\n return result\n }\n\n history(app: string, id: string): Promise<RecordEvent[]> {\n return this.persistence.history(app, id)\n }\n\n subscribe(app: string, on: (delta: RecordsDelta) => void): () => void {\n const set = this.listeners.get(app) ?? new Set()\n set.add(on)\n this.listeners.set(app, set)\n return () => void set.delete(on)\n }\n}\n\n/** The persistence of tests and previews. */\nexport class MemoryPersistence implements RecordPersistence {\n private rows = new Map<string, RecordRow[]>()\n private acks = new Map<string, string[]>()\n private events = new Map<string, RecordEvent[]>()\n\n async load(app: string): Promise<RecordRow[]> {\n return [...(this.rows.get(app) ?? [])]\n }\n\n async ack(app: string, writeId: string): Promise<string[] | null> {\n return this.acks.get(`${app}\\n${writeId}`) ?? null\n }\n\n async commit(app: string, commit: RecordCommit): Promise<void> {\n let rows = (this.rows.get(app) ?? []).filter((r) => !commit.deletes.includes(r.id))\n for (const row of commit.upserts) {\n const i = rows.findIndex((r) => r.id === row.id)\n rows = i >= 0 ? rows.map((r, j) => (j === i ? row : r)) : [...rows, row]\n }\n this.rows.set(app, rows)\n this.acks.set(`${app}\\n${commit.writeId}`, commit.ids)\n for (const e of commit.events) this.events.set(`${app}\\n${e.recordId}`, [...(this.events.get(`${app}\\n${e.recordId}`) ?? []), e])\n }\n\n async history(app: string, id: string): Promise<RecordEvent[]> {\n return [...(this.events.get(`${app}\\n${id}`) ?? [])]\n }\n}\n\n/** Every app's rows in memory (tests, previews). */\nexport class MemoryRecordStore extends LocalRecordStore {\n constructor() {\n super(new MemoryPersistence())\n }\n}\n\n/** A process-local vault (tests, previews). */\nexport class MemoryVault implements Vault {\n readonly values = new Map<string, string>()\n\n async read(key: string): Promise<string | null> {\n return this.values.get(key) ?? null\n }\n\n async write(key: string, value: string): Promise<void> {\n this.values.set(key, value)\n }\n\n async delete(key: string): Promise<void> {\n this.values.delete(key)\n }\n}\n\n/** Record files in memory (tests, previews). */\nexport class MemoryBlobStore implements BlobStore {\n readonly files = new Map<string, { name: string; mimeType: string; bytes: Uint8Array }>()\n\n async put(app: string, name: string, mimeType: string, bytes: Uint8Array): Promise<string> {\n const id = crypto.randomUUID()\n this.files.set(`${app}/${id}`, { name, mimeType, bytes: bytes.slice() })\n return id\n }\n\n async get(app: string, id: string): Promise<Uint8Array | null> {\n return this.files.get(`${app}/${id}`)?.bytes.slice() ?? null\n }\n\n async delete(app: string, id: string): Promise<void> {\n this.files.delete(`${app}/${id}`)\n }\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\n/** The `local` identity's device uuid under `key` in the Vault, created once. */\nexport async function localDeviceId(vault: Vault, key: string): Promise<string> {\n const stored = (await vault.read(key))?.trim()\n if (stored && UUID.test(stored)) return stored\n const id = crypto.randomUUID()\n await vault.write(key, id)\n return id\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "964FE18B6622157D64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,126 @@
1
+ import {
2
+ capabilityNameValid2
3
+ } from "./index-tvsykgcv.js";
4
+
5
+ // packages/eengine/src/providers.ts
6
+ import { recordRefusal } from "@exponential_app/eui/host";
7
+ // packages/eengine/fixtures/providers.json
8
+ var providers_default = {
9
+ $comment: "VAPP-260 (Engine E0, epic VAPP-259, docs/spec/apps.md §13.1): WHAT A HOST HANDS THE ENGINE, the `HostConfig`. Each key of `providers` is `required` (the Engine refuses to start without it: `missing`) or optional (absent = its `default`); `list: true` = zero or more. There is NO telemetry and NO update provider: `forbidden` keys are refused by name (`forbidden`), so an exported runner can never grow an analytics, crash-report or over-the-air channel through its config (a new package version = a rebundle, decided 2026-11-05); any other unknown key is `unknown`. `network` is one of `networkPolicies`; `identity.kind` one of `identityKinds`. A capability is mounted under its name (capabilities.json), each name once (`duplicateCapability`); an agent provider (Agents' `AgentProvider`: `acp`, `localModel`, `remoteSession`) once per id (`duplicateAgent`). `validateHostConfig(config)` -> the issues `{key, code}` in `providers` order, then forbidden and unknown keys sorted. HOSTS (`hosts`): what each host kind provides, informative and checked against the validator (each must pass). IDENTITY (`identityCases`, `engineViewer`): `member` = the signed-in member with their team role and app access (Exponential); `local` = ONE person on one device and no account (the softcoded app, eDE Local desktops, exported apps): the viewer = `{id: the device uuid, role: owner, access: write}`, so genUI's `recordRefusal` allows every op of every kind policy (`member`, `author`, `owners` all collapse to \"me\"), and records the instance writes carry the device uuid as author; `peer` = a P2P key (Spezl), judged like `member` with the key as id and role `member`. NETWORK (`networkCases`, `networkAllows`): `off` = nothing leaves the device (upstreams, `http.fetch` and a sync transport are refused `offline`); `upstreamsOnly` = the package's declared `upstreams` and the sync transport, never `http.fetch`; `any` = all three (an `http.fetch` still needs its manifest origin and consent: capabilities.json). A refusal is `{allowed: false, reason}`.",
10
+ providers: {
11
+ host: { required: true, doc: "HostInfo: host kind, platform, form factor, locale, Engine version" },
12
+ storage: { required: true, doc: "RecordStore: query, write (writeId), history, subscribe (MemoryRecordStore, SqliteRecordStore = elocal's app-records table, SyncedRecordStore)" },
13
+ blobs: { required: true, doc: "BlobStore: put/get record files (FsBlobStore, the server's attachments)" },
14
+ identity: { required: true, doc: "Identity: the viewer (member, local or peer)" },
15
+ vault: { required: true, doc: "emcp Vault: grants, upstream tokens (Keychain, Keystore, libsecret, sealed server column)" },
16
+ window: { required: true, doc: "WindowHost: open a screen (window, nav stack, sheet), report lifecycle events" },
17
+ clock: { required: false, default: "system", doc: "Clock: now (tests pin it)" },
18
+ zone: { required: false, default: "device", doc: "HostZone: the IANA zone of today/now/month (the team's in Exponential)" },
19
+ network: { required: false, default: "upstreamsOnly", doc: "NetworkPolicy" },
20
+ sync: { required: false, default: null, doc: "SyncTransport: pull deltas, push outbox entries (Exponential: Electric in, tRPC out; Spezl: P2P); absent = writes apply locally, no outbox" },
21
+ agents: { required: false, default: [], list: true, doc: "AgentProvider (Agents): acp, localModel, remoteSession; each gets its tools only as a hub grant" },
22
+ capabilities: { required: false, default: [], list: true, doc: "Capability: a native tool provider on the in-process hub (capabilities.json)" },
23
+ consent: { required: false, default: "sheet", doc: "ConsentUi: the Engine's own genUI sheet unless the host paints it (consent.json)" }
24
+ },
25
+ forbidden: ["analytics", "crashReporter", "telemetry", "updateChannel", "updates"],
26
+ networkPolicies: ["off", "upstreamsOnly", "any"],
27
+ identityKinds: ["member", "local", "peer"],
28
+ hosts: {
29
+ exponential: { host: {}, storage: {}, blobs: {}, identity: { kind: "member" }, vault: {}, window: {}, zone: {}, network: "any", sync: {}, agents: [{ id: "acp" }, { id: "remoteSession" }], capabilities: [{ name: "files" }, { name: "share" }, { name: "notifications" }] },
30
+ softcoded: { host: {}, storage: {}, blobs: {}, identity: { kind: "local" }, vault: {}, window: {}, network: "upstreamsOnly", agents: [{ id: "localModel" }] },
31
+ ede: { host: {}, storage: {}, blobs: {}, identity: { kind: "local" }, vault: {}, window: {}, network: "any", agents: [{ id: "acp" }], capabilities: [{ name: "files" }, { name: "notifications" }, { name: "shell" }, { name: "windows" }, { name: "focus" }, { name: "launch" }] },
32
+ spezl: { host: {}, storage: {}, blobs: {}, identity: { kind: "peer" }, vault: {}, window: {}, network: "any", sync: {}, agents: [{ id: "acp" }] },
33
+ exported: { host: {}, storage: {}, blobs: {}, identity: { kind: "local" }, vault: {}, window: {}, network: "off", capabilities: [{ name: "photos" }, { name: "print" }, { name: "share" }] }
34
+ },
35
+ validateCases: [
36
+ { name: "the minimum", config: { host: {}, storage: {}, blobs: {}, identity: { kind: "local" }, vault: {}, window: {} }, expected: [] },
37
+ { name: "every required key missing", config: {}, expected: [{ key: "host", code: "missing" }, { key: "storage", code: "missing" }, { key: "blobs", code: "missing" }, { key: "identity", code: "missing" }, { key: "vault", code: "missing" }, { key: "window", code: "missing" }] },
38
+ { name: "no telemetry, no update channel", config: { host: {}, storage: {}, blobs: {}, identity: { kind: "local" }, vault: {}, window: {}, updates: {}, telemetry: {}, theme: {} }, expected: [{ key: "telemetry", code: "forbidden" }, { key: "theme", code: "unknown" }, { key: "updates", code: "forbidden" }] },
39
+ { name: "a bad network policy and identity kind", config: { host: {}, storage: {}, blobs: {}, identity: { kind: "guest" }, vault: {}, window: {}, network: "lan" }, expected: [{ key: "identity", code: "invalid" }, { key: "network", code: "invalid" }] },
40
+ { name: "a capability or agent twice", config: { host: {}, storage: {}, blobs: {}, identity: { kind: "member" }, vault: {}, window: {}, agents: [{ id: "acp" }, { id: "acp" }], capabilities: [{ name: "photos" }, { name: "photos" }] }, expected: [{ key: "agents", code: "duplicateAgent" }, { key: "capabilities", code: "duplicateCapability" }] },
41
+ { name: "a capability under a reserved or invalid name", config: { host: {}, storage: {}, blobs: {}, identity: { kind: "member" }, vault: {}, window: {}, capabilities: [{ name: "records" }, { name: "Bad_Name" }] }, expected: [{ key: "capabilities", code: "invalidCapability" }] },
42
+ { name: "an unknown agent provider", config: { host: {}, storage: {}, blobs: {}, identity: { kind: "member" }, vault: {}, window: {}, agents: [{ id: "openclaw" }] }, expected: [{ key: "agents", code: "invalid" }] }
43
+ ],
44
+ agentProviders: ["acp", "localModel", "remoteSession"],
45
+ identityCases: [
46
+ { name: "local: an author-only update of someone else's record", identity: { kind: "local", deviceId: "d-1" }, policy: { read: "author", create: "owners", update: "author", delete: "owners" }, op: "update", row: { authorId: "u-imported" }, expected: { viewer: { id: "d-1", role: "owner", access: "write" }, refusal: null } },
47
+ { name: "local: an owners-only delete", identity: { kind: "local", deviceId: "d-1" }, policy: { read: "member", create: "member", update: "author", delete: "owners" }, op: "delete", row: { authorId: "d-1" }, expected: { viewer: { id: "d-1", role: "owner", access: "write" }, refusal: null } },
48
+ { name: "member: the policy applies", identity: { kind: "member", userId: "u-1", role: "member", access: "write" }, policy: { read: "member", create: "member", update: "author", delete: "author" }, op: "update", row: { authorId: "u-2" }, expected: { viewer: { id: "u-1", role: "member", access: "write" }, refusal: "Only the record's author or a team owner can change it" } },
49
+ { name: "peer: a key judged as a member", identity: { kind: "peer", key: "pk-9" }, policy: { read: "member", create: "member", update: "author", delete: "author" }, op: "update", row: { authorId: "pk-9" }, expected: { viewer: { id: "pk-9", role: "member", access: "write" }, refusal: null } }
50
+ ],
51
+ networkCases: [
52
+ { policy: "off", target: "upstream", declared: true, expected: { allowed: false, reason: "offline" } },
53
+ { policy: "off", target: "sync", declared: true, expected: { allowed: false, reason: "offline" } },
54
+ { policy: "off", target: "http", declared: true, expected: { allowed: false, reason: "offline" } },
55
+ { policy: "upstreamsOnly", target: "upstream", declared: true, expected: { allowed: true, reason: null } },
56
+ { policy: "upstreamsOnly", target: "upstream", declared: false, expected: { allowed: false, reason: "notDeclared" } },
57
+ { policy: "upstreamsOnly", target: "sync", declared: true, expected: { allowed: true, reason: null } },
58
+ { policy: "upstreamsOnly", target: "http", declared: true, expected: { allowed: false, reason: "network" } },
59
+ { policy: "any", target: "http", declared: true, expected: { allowed: true, reason: null } },
60
+ { policy: "any", target: "upstream", declared: false, expected: { allowed: false, reason: "notDeclared" } }
61
+ ]
62
+ };
63
+
64
+ // packages/eengine/src/providers.ts
65
+ var PROVIDER_KEYS2 = Object.keys(providers_default.providers);
66
+ var FORBIDDEN_HOST_KEYS2 = providers_default.forbidden;
67
+ var NETWORK_POLICIES2 = providers_default.networkPolicies;
68
+ var IDENTITY_KINDS2 = providers_default.identityKinds;
69
+ var AGENT_PROVIDERS2 = providers_default.agentProviders;
70
+ var isObject = (v) => !!v && typeof v === `object` && !Array.isArray(v);
71
+ function validateHostConfig2(config) {
72
+ const issues = [];
73
+ for (const key of PROVIDER_KEYS2) {
74
+ const def = providers_default.providers[key];
75
+ const value = config[key];
76
+ if (value === undefined) {
77
+ if (def.required)
78
+ issues.push({ key, code: `missing` });
79
+ continue;
80
+ }
81
+ if (key === `identity` && !(isObject(value) && IDENTITY_KINDS2.includes(value.kind)))
82
+ issues.push({ key, code: `invalid` });
83
+ if (key === `network` && !NETWORK_POLICIES2.includes(value))
84
+ issues.push({ key, code: `invalid` });
85
+ if (key === `agents`) {
86
+ const ids = Array.isArray(value) ? value.map((a) => isObject(a) ? a.id : undefined) : null;
87
+ if (!ids || ids.some((id) => !AGENT_PROVIDERS2.includes(id)))
88
+ issues.push({ key, code: `invalid` });
89
+ else if (new Set(ids).size !== ids.length)
90
+ issues.push({ key, code: `duplicateAgent` });
91
+ }
92
+ if (key === `capabilities`) {
93
+ const names = Array.isArray(value) ? value.map((c) => isObject(c) ? c.name : undefined) : null;
94
+ if (!names || names.some((n) => typeof n !== `string` || !capabilityNameValid2(n)))
95
+ issues.push({ key, code: `invalidCapability` });
96
+ else if (new Set(names).size !== names.length)
97
+ issues.push({ key, code: `duplicateCapability` });
98
+ }
99
+ }
100
+ const extra = Object.keys(config).filter((k) => !PROVIDER_KEYS2.includes(k)).sort();
101
+ for (const key of extra)
102
+ issues.push({ key, code: FORBIDDEN_HOST_KEYS2.includes(key) ? `forbidden` : `unknown` });
103
+ return issues;
104
+ }
105
+ function engineViewer2(identity) {
106
+ if (identity.kind === `local`)
107
+ return { id: identity.deviceId, role: `owner`, access: `write` };
108
+ if (identity.kind === `peer`)
109
+ return { id: identity.key, role: `member`, access: `write` };
110
+ return { id: identity.userId, role: identity.role, access: identity.access ?? `write` };
111
+ }
112
+ var engineRecordRefusal2 = (identity, policy, op, row) => recordRefusal(policy, op, engineViewer2(identity), row);
113
+ function networkAllows2(policy, target, declared) {
114
+ if (policy === `off`)
115
+ return { allowed: false, reason: `offline` };
116
+ if (target === `upstream` && !declared)
117
+ return { allowed: false, reason: `notDeclared` };
118
+ if (target === `http` && policy === `upstreamsOnly`)
119
+ return { allowed: false, reason: `network` };
120
+ return { allowed: true, reason: null };
121
+ }
122
+
123
+ export { PROVIDER_KEYS2, FORBIDDEN_HOST_KEYS2, NETWORK_POLICIES2, IDENTITY_KINDS2, AGENT_PROVIDERS2, validateHostConfig2, engineViewer2, engineRecordRefusal2, networkAllows2 };
124
+
125
+ //# debugId=CEF389E0C0D8929064756E2164756E21
126
+ //# sourceMappingURL=index-sk8b0dh4.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/eengine/src/providers.ts"],
4
+ "sourcesContent": [
5
+ "// VAPP-260: what a host hands the Engine (`HostConfig`), the viewer each\n// identity kind yields, and the network policy. Locked by `fixtures/providers.json`.\n\nimport { recordRefusal, type RecordOp, type RecordOwnership, type RecordPolicy, type RecordViewer } from \"@exponential_app/eui/host\"\n\nimport fixture from \"../fixtures/providers.json\" with { type: \"json\" }\nimport { capabilityNameValid } from \"./capabilities\"\n\nexport type ProviderKey = keyof typeof fixture.providers\nexport type NetworkPolicy = `off` | `upstreamsOnly` | `any`\nexport type IdentityKind = `member` | `local` | `peer`\nexport type AgentProviderId = `acp` | `localModel` | `remoteSession`\n\nexport const PROVIDER_KEYS = Object.keys(fixture.providers) as ProviderKey[]\nexport const FORBIDDEN_HOST_KEYS: readonly string[] = fixture.forbidden\nexport const NETWORK_POLICIES = fixture.networkPolicies as readonly NetworkPolicy[]\nexport const IDENTITY_KINDS = fixture.identityKinds as readonly IdentityKind[]\nexport const AGENT_PROVIDERS = fixture.agentProviders as readonly AgentProviderId[]\n\nexport interface HostConfigIssue {\n key: string\n code: `missing` | `forbidden` | `unknown` | `invalid` | `duplicateAgent` | `duplicateCapability` | `invalidCapability`\n}\n\nconst isObject = (v: unknown): v is Record<string, unknown> => !!v && typeof v === `object` && !Array.isArray(v)\n\n/** Every problem with a host config: providers in order, then forbidden/unknown keys sorted. */\nexport function validateHostConfig(config: Record<string, unknown>): HostConfigIssue[] {\n const issues: HostConfigIssue[] = []\n for (const key of PROVIDER_KEYS) {\n const def = fixture.providers[key] as { required: boolean }\n const value = config[key]\n if (value === undefined) {\n if (def.required) issues.push({ key, code: `missing` })\n continue\n }\n if (key === `identity` && !(isObject(value) && (IDENTITY_KINDS as readonly unknown[]).includes(value.kind))) issues.push({ key, code: `invalid` })\n if (key === `network` && !(NETWORK_POLICIES as readonly unknown[]).includes(value)) issues.push({ key, code: `invalid` })\n if (key === `agents`) {\n const ids = Array.isArray(value) ? value.map((a) => (isObject(a) ? a.id : undefined)) : null\n if (!ids || ids.some((id) => !(AGENT_PROVIDERS as readonly unknown[]).includes(id))) issues.push({ key, code: `invalid` })\n else if (new Set(ids).size !== ids.length) issues.push({ key, code: `duplicateAgent` })\n }\n if (key === `capabilities`) {\n const names = Array.isArray(value) ? value.map((c) => (isObject(c) ? c.name : undefined)) : null\n if (!names || names.some((n) => typeof n !== `string` || !capabilityNameValid(n))) issues.push({ key, code: `invalidCapability` })\n else if (new Set(names).size !== names.length) issues.push({ key, code: `duplicateCapability` })\n }\n }\n const extra = Object.keys(config)\n .filter((k) => !(PROVIDER_KEYS as readonly string[]).includes(k))\n .sort()\n for (const key of extra) issues.push({ key, code: FORBIDDEN_HOST_KEYS.includes(key) ? `forbidden` : `unknown` })\n return issues\n}\n\nexport type EngineIdentity =\n | { kind: `member`; userId: string; role: string | null; access?: `none` | `read` | `write` }\n | { kind: `local`; deviceId: string }\n | { kind: `peer`; key: string }\n\n/** The records viewer of an identity: local = the one owner of everything (\"me\"). */\nexport function engineViewer(identity: EngineIdentity): Required<RecordViewer> {\n if (identity.kind === `local`) return { id: identity.deviceId, role: `owner`, access: `write` }\n if (identity.kind === `peer`) return { id: identity.key, role: `member`, access: `write` }\n return { id: identity.userId, role: identity.role, access: identity.access ?? `write` }\n}\n\n/** Why the viewer of `identity` may not `op` (null = allowed). */\nexport const engineRecordRefusal = (identity: EngineIdentity, policy: RecordPolicy, op: RecordOp, row?: RecordOwnership | null): string | null =>\n recordRefusal(policy, op, engineViewer(identity), row)\n\n/** May the instance reach `target` under `policy`? `declared` = the package declares the upstream (or the origin). */\nexport function networkAllows(policy: NetworkPolicy, target: `upstream` | `sync` | `http`, declared: boolean): { allowed: boolean; reason: string | null } {\n if (policy === `off`) return { allowed: false, reason: `offline` }\n if (target === `upstream` && !declared) return { allowed: false, reason: `notDeclared` }\n if (target === `http` && policy === `upstreamsOnly`) return { allowed: false, reason: `network` }\n return { allowed: true, reason: null }\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "CEF389E0C0D8929064756E2164756E21",
9
+ "names": []
10
+ }