sh3-core 0.20.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BrandSlot.svelte +2 -2
- package/dist/actions/ctx-actions.svelte.test.js +2 -2
- package/dist/api.d.ts +2 -2
- package/dist/api.js +1 -1
- package/dist/app/store/StoreView.svelte +26 -35
- package/dist/app/store/storeShard.svelte.js +35 -49
- package/dist/app/store/verbs.js +24 -55
- package/dist/artifact.d.ts +2 -0
- package/dist/boot/satellitePayload.d.ts +2 -0
- package/dist/boot/satellitePayload.test.js +19 -0
- package/dist/build.d.ts +7 -1
- package/dist/build.js +34 -9
- package/dist/build.test.js +27 -1
- package/dist/createShell.js +34 -9
- package/dist/documents/browse.d.ts +20 -0
- package/dist/documents/browse.js +35 -0
- package/dist/documents/browse.test.js +125 -0
- package/dist/documents/config.d.ts +0 -4
- package/dist/documents/config.js +0 -8
- package/dist/documents/http-backend.d.ts +5 -0
- package/dist/documents/http-backend.js +25 -0
- package/dist/documents/http-backend.test.js +66 -0
- package/dist/documents/index.d.ts +1 -1
- package/dist/documents/index.js +1 -1
- package/dist/documents/types.d.ts +11 -0
- package/dist/env/client.d.ts +6 -10
- package/dist/env/client.js +11 -21
- package/dist/env/index.d.ts +2 -1
- package/dist/env/index.js +1 -1
- package/dist/host-entry.d.ts +1 -1
- package/dist/host-entry.js +1 -1
- package/dist/host.d.ts +1 -1
- package/dist/host.js +1 -1
- package/dist/layout/slotHostPool.svelte.js +2 -2
- package/dist/overlays/FloatFrame.svelte +1 -0
- package/dist/projects/session-state.svelte.d.ts +3 -0
- package/dist/projects/session-state.svelte.js +25 -0
- package/dist/projects/session-state.test.js +43 -2
- package/dist/projects-shard/ProjectsSection.svelte +14 -18
- package/dist/registry/archive.d.ts +12 -0
- package/dist/registry/archive.js +80 -0
- package/dist/registry/archive.test.d.ts +1 -0
- package/dist/registry/archive.test.js +84 -0
- package/dist/registry/client.d.ts +9 -29
- package/dist/registry/client.js +14 -60
- package/dist/registry/client.test.js +31 -21
- package/dist/registry/index.d.ts +2 -2
- package/dist/registry/index.js +1 -1
- package/dist/registry/installer.d.ts +4 -4
- package/dist/registry/installer.js +74 -45
- package/dist/registry/schema.js +4 -27
- package/dist/registry/schema.test.d.ts +1 -0
- package/dist/registry/schema.test.js +41 -0
- package/dist/registry/types.d.ts +16 -41
- package/dist/runtime/runVerb-shell.test.js +2 -2
- package/dist/runtime/runVerb.test.js +2 -2
- package/dist/sh3core-shard/appActions.js +5 -2
- package/dist/shards/activate-browse.test.js +2 -2
- package/dist/shards/activate-contributions.test.js +2 -2
- package/dist/shards/activate-error-isolation.test.js +3 -3
- package/dist/shards/activate-on-key-revoked.test.js +2 -2
- package/dist/shards/activate-runtime.test.js +2 -2
- package/dist/shards/activate.svelte.js +4 -4
- package/dist/shards/ctx-fetch.test.js +4 -4
- package/dist/shell-shard/verbs/xfer.js +13 -27
- package/dist/shell-shard/verbs/xfer.test.js +36 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { loadBundleModule } from './loader';
|
|
18
18
|
import { savePackage, loadBundle, listInstalled, removePackage } from './storage';
|
|
19
|
-
import { verifyIntegrity } from './integrity';
|
|
20
19
|
import { deactivateShard } from '../shards/activate.svelte';
|
|
21
20
|
import { unregisterApp } from '../apps/lifecycle';
|
|
22
21
|
import { registerLoadedBundle } from './register';
|
|
23
22
|
import { extractBundlePermissions } from './permission-descriptions';
|
|
23
|
+
import { fetchServerPackages } from '../env/client';
|
|
24
24
|
/**
|
|
25
25
|
* Install a package from raw bundle bytes and metadata.
|
|
26
26
|
*
|
|
@@ -35,25 +35,8 @@ import { extractBundlePermissions } from './permission-descriptions';
|
|
|
35
35
|
* @returns Result object indicating success/failure and hot-load status.
|
|
36
36
|
*/
|
|
37
37
|
export async function installPackage(bundle, meta, options) {
|
|
38
|
-
// 1.
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
success: false,
|
|
42
|
-
hotLoaded: false,
|
|
43
|
-
error: 'Missing integrity hash — refusing to install unverified bundle',
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
try {
|
|
47
|
-
await verifyIntegrity(bundle, meta.integrity);
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
return {
|
|
51
|
-
success: false,
|
|
52
|
-
hotLoaded: false,
|
|
53
|
-
error: `Integrity check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// 2. Load the module from verified bytes (or reuse the caller's copy).
|
|
38
|
+
// 1. Load the module from bytes (or reuse the caller's copy).
|
|
39
|
+
// Archive integrity is verified upstream in fetchArchive() before extraction.
|
|
57
40
|
let loaded;
|
|
58
41
|
if (options === null || options === void 0 ? void 0 : options.loaded) {
|
|
59
42
|
loaded = options.loaded;
|
|
@@ -160,40 +143,86 @@ export async function listInstalledPackages() {
|
|
|
160
143
|
return listInstalled();
|
|
161
144
|
}
|
|
162
145
|
/**
|
|
163
|
-
*
|
|
146
|
+
* Sync installed packages against the server list and load all into the framework.
|
|
164
147
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
148
|
+
* Server list is authoritative. Packages on server but missing from IndexedDB
|
|
149
|
+
* are fetched from the server's /packages/:id/client.js endpoint and cached.
|
|
150
|
+
* Packages in IndexedDB but absent from the server are evicted.
|
|
168
151
|
*/
|
|
169
152
|
export async function loadInstalledPackages() {
|
|
170
|
-
let
|
|
153
|
+
let serverPackages = [];
|
|
171
154
|
try {
|
|
172
|
-
|
|
155
|
+
serverPackages = await fetchServerPackages();
|
|
173
156
|
}
|
|
174
157
|
catch (err) {
|
|
175
|
-
console.warn('[sh3]
|
|
158
|
+
console.warn('[sh3] Could not reach server for package sync, loading from local cache:', err instanceof Error ? err.message : err);
|
|
159
|
+
const fallback = await listInstalled().catch(() => []);
|
|
160
|
+
for (const pkg of fallback) {
|
|
161
|
+
await _loadFromIndexedDB(pkg);
|
|
162
|
+
}
|
|
176
163
|
return;
|
|
177
164
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
contractVersion: pkg.contractVersion,
|
|
190
|
-
});
|
|
191
|
-
if (loaded.shards.length === 0 && loaded.apps.length === 0) {
|
|
192
|
-
console.warn(`[sh3] Package "${pkg.id}" contains no valid shards or apps, skipping`);
|
|
193
|
-
}
|
|
165
|
+
const serverIds = new Set(serverPackages.map(p => p.id));
|
|
166
|
+
let localPackages = [];
|
|
167
|
+
try {
|
|
168
|
+
localPackages = await listInstalled();
|
|
169
|
+
}
|
|
170
|
+
catch ( /* treat as empty */_a) { /* treat as empty */ }
|
|
171
|
+
const localIds = new Set(localPackages.map(p => p.id));
|
|
172
|
+
// Evict packages no longer on the server
|
|
173
|
+
for (const pkg of localPackages) {
|
|
174
|
+
if (!serverIds.has(pkg.id)) {
|
|
175
|
+
await removePackage(pkg.id).catch(() => { });
|
|
194
176
|
}
|
|
195
|
-
|
|
196
|
-
|
|
177
|
+
}
|
|
178
|
+
// Load packages from server — use IndexedDB cache if available, else fetch from server
|
|
179
|
+
for (const serverPkg of serverPackages) {
|
|
180
|
+
if (localIds.has(serverPkg.id)) {
|
|
181
|
+
const localPkg = localPackages.find(p => p.id === serverPkg.id);
|
|
182
|
+
await _loadFromIndexedDB(localPkg);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
await _fetchAndCacheFromServer(serverPkg);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function _loadFromIndexedDB(pkg) {
|
|
190
|
+
try {
|
|
191
|
+
const bytes = await loadBundle(pkg.id);
|
|
192
|
+
if (!bytes) {
|
|
193
|
+
console.warn(`[sh3] No bundle in IndexedDB for "${pkg.id}", skipping`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const loaded = await loadBundleModule(bytes);
|
|
197
|
+
registerLoadedBundle(loaded, { version: pkg.version, sourceRegistry: pkg.sourceRegistry, contractVersion: pkg.contractVersion });
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
console.warn(`[sh3] Failed to load "${pkg.id}" from cache:`, err instanceof Error ? err.message : err);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function _fetchAndCacheFromServer(serverPkg) {
|
|
204
|
+
var _a, _b;
|
|
205
|
+
try {
|
|
206
|
+
const res = await fetch(serverPkg.bundleUrl);
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
console.warn(`[sh3] Failed to fetch bundle for "${serverPkg.id}": HTTP ${res.status}`);
|
|
209
|
+
return;
|
|
197
210
|
}
|
|
211
|
+
const bundle = await res.arrayBuffer();
|
|
212
|
+
const record = {
|
|
213
|
+
id: serverPkg.id,
|
|
214
|
+
type: serverPkg.type,
|
|
215
|
+
version: serverPkg.version,
|
|
216
|
+
sourceRegistry: (_a = serverPkg.sourceRegistry) !== null && _a !== void 0 ? _a : '',
|
|
217
|
+
contractVersion: (_b = serverPkg.contractVersion) !== null && _b !== void 0 ? _b : '',
|
|
218
|
+
installedAt: new Date().toISOString(),
|
|
219
|
+
permissions: [],
|
|
220
|
+
};
|
|
221
|
+
await savePackage(serverPkg.id, bundle, record);
|
|
222
|
+
const loaded = await loadBundleModule(bundle);
|
|
223
|
+
registerLoadedBundle(loaded, { version: record.version, sourceRegistry: record.sourceRegistry, contractVersion: record.contractVersion });
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
console.warn(`[sh3] Failed to fetch/cache "${serverPkg.id}" from server:`, err instanceof Error ? err.message : err);
|
|
198
227
|
}
|
|
199
228
|
}
|
package/dist/registry/schema.js
CHANGED
|
@@ -113,29 +113,8 @@ function validatePackageVersion(data, path) {
|
|
|
113
113
|
const obj = data;
|
|
114
114
|
requireString(obj, 'version', path);
|
|
115
115
|
requireString(obj, 'contractVersion', path);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const hasIntegrity = 'integrity' in obj && obj.integrity !== undefined;
|
|
119
|
-
if (hasBundleUrl)
|
|
120
|
-
requireString(obj, 'bundleUrl', path);
|
|
121
|
-
if (hasIntegrity)
|
|
122
|
-
requireString(obj, 'integrity', path);
|
|
123
|
-
if (hasBundleUrl !== hasIntegrity) {
|
|
124
|
-
throw new RegistryValidationError(path, 'bundleUrl and integrity must be provided together');
|
|
125
|
-
}
|
|
126
|
-
// Optional server bundle URL.
|
|
127
|
-
const hasServerBundleUrl = 'serverBundleUrl' in obj && obj.serverBundleUrl !== undefined;
|
|
128
|
-
if (hasServerBundleUrl) {
|
|
129
|
-
requireString(obj, 'serverBundleUrl', path);
|
|
130
|
-
}
|
|
131
|
-
// Optional server bundle integrity hash — provisional, see ADR-015.
|
|
132
|
-
if ('serverIntegrity' in obj && obj.serverIntegrity !== undefined) {
|
|
133
|
-
requireString(obj, 'serverIntegrity', path);
|
|
134
|
-
}
|
|
135
|
-
// A version must ship at least one bundle.
|
|
136
|
-
if (!hasBundleUrl && !hasServerBundleUrl) {
|
|
137
|
-
throw new RegistryValidationError(path, 'expected at least one of bundleUrl+integrity or serverBundleUrl');
|
|
138
|
-
}
|
|
116
|
+
requireString(obj, 'archiveUrl', path);
|
|
117
|
+
requireString(obj, 'integrity', path);
|
|
139
118
|
let requires;
|
|
140
119
|
if (obj['requires'] !== undefined) {
|
|
141
120
|
if (!Array.isArray(obj['requires'])) {
|
|
@@ -146,10 +125,8 @@ function validatePackageVersion(data, path) {
|
|
|
146
125
|
return {
|
|
147
126
|
version: obj['version'],
|
|
148
127
|
contractVersion: obj['contractVersion'],
|
|
149
|
-
|
|
150
|
-
integrity:
|
|
151
|
-
serverBundleUrl: typeof obj['serverBundleUrl'] === 'string' ? obj['serverBundleUrl'] : undefined,
|
|
152
|
-
serverIntegrity: typeof obj['serverIntegrity'] === 'string' ? obj['serverIntegrity'] : undefined,
|
|
128
|
+
archiveUrl: obj['archiveUrl'],
|
|
129
|
+
integrity: obj['integrity'],
|
|
153
130
|
requires,
|
|
154
131
|
};
|
|
155
132
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateRegistryIndex, RegistryValidationError } from './schema.js';
|
|
3
|
+
const VALID_VERSION = {
|
|
4
|
+
version: '1.0.0',
|
|
5
|
+
contractVersion: '1',
|
|
6
|
+
archiveUrl: 'https://example.com/pkg-1.0.0.sh3pkg',
|
|
7
|
+
integrity: 'sha384-abc123',
|
|
8
|
+
};
|
|
9
|
+
const VALID_ENTRY = {
|
|
10
|
+
id: 'my-shard',
|
|
11
|
+
type: 'shard',
|
|
12
|
+
label: 'My Shard',
|
|
13
|
+
description: 'A test shard',
|
|
14
|
+
author: { name: 'Test Author' },
|
|
15
|
+
versions: [VALID_VERSION],
|
|
16
|
+
};
|
|
17
|
+
const VALID_INDEX = { version: 1, packages: [VALID_ENTRY] };
|
|
18
|
+
describe('validateRegistryIndex', () => {
|
|
19
|
+
it('accepts a valid index with archiveUrl and integrity', () => {
|
|
20
|
+
const result = validateRegistryIndex(VALID_INDEX);
|
|
21
|
+
expect(result.packages[0].versions[0].archiveUrl).toBe('https://example.com/pkg-1.0.0.sh3pkg');
|
|
22
|
+
expect(result.packages[0].versions[0].integrity).toBe('sha384-abc123');
|
|
23
|
+
});
|
|
24
|
+
it('rejects a version missing archiveUrl', () => {
|
|
25
|
+
const bad = Object.assign(Object.assign({}, VALID_INDEX), { packages: [Object.assign(Object.assign({}, VALID_ENTRY), { versions: [{ version: '1.0.0', contractVersion: '1', integrity: 'sha384-abc' }] })] });
|
|
26
|
+
expect(() => validateRegistryIndex(bad)).toThrow(RegistryValidationError);
|
|
27
|
+
});
|
|
28
|
+
it('rejects a version missing integrity', () => {
|
|
29
|
+
const bad = Object.assign(Object.assign({}, VALID_INDEX), { packages: [Object.assign(Object.assign({}, VALID_ENTRY), { versions: [{ version: '1.0.0', contractVersion: '1', archiveUrl: 'https://x.com/a.sh3pkg' }] })] });
|
|
30
|
+
expect(() => validateRegistryIndex(bad)).toThrow(RegistryValidationError);
|
|
31
|
+
});
|
|
32
|
+
it('accepts optional requires array', () => {
|
|
33
|
+
const withRequires = Object.assign(Object.assign({}, VALID_INDEX), { packages: [Object.assign(Object.assign({}, VALID_ENTRY), { versions: [Object.assign(Object.assign({}, VALID_VERSION), { requires: [{ id: 'dep-shard', versionRange: '^1.0.0' }] })] })] });
|
|
34
|
+
const result = validateRegistryIndex(withRequires);
|
|
35
|
+
expect(result.packages[0].versions[0].requires[0].id).toBe('dep-shard');
|
|
36
|
+
});
|
|
37
|
+
it('rejects a version with requires that has an invalid entry', () => {
|
|
38
|
+
const bad = Object.assign(Object.assign({}, VALID_INDEX), { packages: [Object.assign(Object.assign({}, VALID_ENTRY), { versions: [Object.assign(Object.assign({}, VALID_VERSION), { requires: [{ id: 'dep' }] })] })] });
|
|
39
|
+
expect(() => validateRegistryIndex(bad)).toThrow(RegistryValidationError);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/dist/registry/types.d.ts
CHANGED
|
@@ -81,9 +81,9 @@ export interface PackageEntry {
|
|
|
81
81
|
/**
|
|
82
82
|
* A specific published version of a package.
|
|
83
83
|
*
|
|
84
|
-
* Each version ships a
|
|
85
|
-
* The `
|
|
86
|
-
* the download before execution.
|
|
84
|
+
* Each version ships a `.sh3pkg` ZIP archive at `archiveUrl`.
|
|
85
|
+
* The archive contains `manifest.json` and at least one of `client.js` / `server.js`.
|
|
86
|
+
* The `integrity` SRI hash verifies the download before execution.
|
|
87
87
|
*/
|
|
88
88
|
export interface PackageVersion {
|
|
89
89
|
/**
|
|
@@ -98,36 +98,17 @@ export interface PackageVersion {
|
|
|
98
98
|
*/
|
|
99
99
|
contractVersion: string;
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* `serverBundleUrl` — in that case `integrity` must also be omitted.
|
|
101
|
+
* URL to the `.sh3pkg` ZIP archive for this version.
|
|
102
|
+
* Absolute or registry-relative. The archive contains `manifest.json`
|
|
103
|
+
* plus at least one of `client.js` / `server.js`.
|
|
105
104
|
*/
|
|
106
|
-
|
|
105
|
+
archiveUrl: string;
|
|
107
106
|
/**
|
|
108
|
-
* SRI integrity hash
|
|
107
|
+
* SRI integrity hash of the archive ZIP.
|
|
109
108
|
* Format: `"<algorithm>-<base64digest>"` (e.g. `"sha384-abc123..."`).
|
|
110
109
|
* Algorithms: sha256, sha384 (recommended), sha512.
|
|
111
|
-
* See: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
|
112
|
-
* Omitted when the package has no client bundle.
|
|
113
110
|
*/
|
|
114
|
-
integrity
|
|
115
|
-
/**
|
|
116
|
-
* Optional URL to the server-side bundle for shards that have a backend
|
|
117
|
-
* component. Same resolution rules as `bundleUrl` (absolute or registry-
|
|
118
|
-
* relative). Only present when the shard declares `serverBundle` in its
|
|
119
|
-
* manifest.
|
|
120
|
-
*/
|
|
121
|
-
serverBundleUrl?: string;
|
|
122
|
-
/**
|
|
123
|
-
* SRI integrity hash for the server bundle. Same format as `integrity`.
|
|
124
|
-
* Optional in contract v1 for back-compat with registries that predate
|
|
125
|
-
* server bundles. Will become required when the formal registry spec
|
|
126
|
-
* lands (see ADR-015 proposal). When absent, the client skips the SRI
|
|
127
|
-
* check at download time and logs a warning — the unverified bundle is
|
|
128
|
-
* still installed.
|
|
129
|
-
*/
|
|
130
|
-
serverIntegrity?: string;
|
|
111
|
+
integrity: string;
|
|
131
112
|
/**
|
|
132
113
|
* Other shards that must be installed and active before this package
|
|
133
114
|
* can be loaded. Optional — omit if the package has no dependencies.
|
|
@@ -260,9 +241,7 @@ export interface PackageMeta {
|
|
|
260
241
|
*/
|
|
261
242
|
sourceRegistry: string;
|
|
262
243
|
/**
|
|
263
|
-
* SRI hash
|
|
264
|
-
* Must match `PackageVersion.integrity`. Omitted for server-only packages
|
|
265
|
-
* that ship no client bundle.
|
|
244
|
+
* SRI hash of the archive this package was installed from. Kept for audit purposes.
|
|
266
245
|
*/
|
|
267
246
|
integrity?: string;
|
|
268
247
|
/**
|
|
@@ -270,14 +249,10 @@ export interface PackageMeta {
|
|
|
270
249
|
* Undefined if no dependencies.
|
|
271
250
|
*/
|
|
272
251
|
requires?: RequiredDependency[];
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
* Whether this package includes a server bundle that needs to be pushed
|
|
280
|
-
* to the server after client-side installation.
|
|
281
|
-
*/
|
|
282
|
-
hasServerBundle?: boolean;
|
|
252
|
+
}
|
|
253
|
+
/** Payload sent to the server to trigger a server-side install. */
|
|
254
|
+
export interface RemoteInstallRequest {
|
|
255
|
+
registryUrl: string;
|
|
256
|
+
packageId: string;
|
|
257
|
+
version: string;
|
|
283
258
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
13
13
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
14
|
-
import { __setDocumentBackend,
|
|
14
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
15
15
|
import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
|
|
16
16
|
import { __resetViewRegistryForTest } from '../shards/registry';
|
|
17
17
|
import { __resetActionsRegistryForTest } from '../actions/registry';
|
|
@@ -25,7 +25,7 @@ describe('shell-shard programmatic verbs (integration)', () => {
|
|
|
25
25
|
__resetActionsRegistryForTest();
|
|
26
26
|
__resetAppRegistryForTest();
|
|
27
27
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
28
|
-
|
|
28
|
+
__setActiveScope('tenant-test');
|
|
29
29
|
registerShard(shellShard);
|
|
30
30
|
await activateShard('shell');
|
|
31
31
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
|
|
5
5
|
import { __resetViewRegistryForTest } from '../shards/registry';
|
|
6
6
|
import { runVerbProgrammatic } from './runVerb';
|
|
@@ -19,7 +19,7 @@ describe('runVerbProgrammatic', () => {
|
|
|
19
19
|
__resetShardRegistryForTest();
|
|
20
20
|
__resetViewRegistryForTest();
|
|
21
21
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
22
|
-
|
|
22
|
+
__setActiveScope('tenant-test');
|
|
23
23
|
});
|
|
24
24
|
it('rejects on unknown shard', async () => {
|
|
25
25
|
await expect(runVerbProgrammatic('missing', 'echo', [])).rejects.toThrow('unknown shard: missing');
|
|
@@ -30,6 +30,7 @@ import AppInfoView from './AppInfoView.svelte';
|
|
|
30
30
|
import { spawnSatellite } from '../sh3Api/window';
|
|
31
31
|
import { activeApp, getActiveApp } from '../apps/registry.svelte';
|
|
32
32
|
import { returnToHome } from '../apps/lifecycle';
|
|
33
|
+
import { sessionState } from '../projects/session-state.svelte';
|
|
33
34
|
const isTauri = typeof globalThis.__TAURI_INTERNALS__ !== 'undefined';
|
|
34
35
|
export function computeAppActionDisabled(g) {
|
|
35
36
|
return !g.admin || g.builtin;
|
|
@@ -111,7 +112,7 @@ async function runCheckUpdate(_ctx) {
|
|
|
111
112
|
modalManager.open(AppUpdateAvailableModal, props);
|
|
112
113
|
}
|
|
113
114
|
function runPopOut(_ctx) {
|
|
114
|
-
var _a;
|
|
115
|
+
var _a, _b;
|
|
115
116
|
const ref = readSelection();
|
|
116
117
|
if (!ref)
|
|
117
118
|
return;
|
|
@@ -122,10 +123,11 @@ function runPopOut(_ctx) {
|
|
|
122
123
|
kind: 'app',
|
|
123
124
|
appId: ref.appId,
|
|
124
125
|
activateShards: (_a = manifest.requiredShards) !== null && _a !== void 0 ? _a : [],
|
|
126
|
+
projectId: (_b = sessionState.activeProjectId) !== null && _b !== void 0 ? _b : undefined,
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
129
|
async function runPopOutCurrent(_ctx) {
|
|
128
|
-
var _a;
|
|
130
|
+
var _a, _b;
|
|
129
131
|
const current = getActiveApp();
|
|
130
132
|
if (!current)
|
|
131
133
|
return;
|
|
@@ -136,6 +138,7 @@ async function runPopOutCurrent(_ctx) {
|
|
|
136
138
|
kind: 'app',
|
|
137
139
|
appId,
|
|
138
140
|
activateShards: requiredShards,
|
|
141
|
+
projectId: (_b = sessionState.activeProjectId) !== null && _b !== void 0 ? _b : undefined,
|
|
139
142
|
});
|
|
140
143
|
}
|
|
141
144
|
function runUninstall(_ctx) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, __resetShardRegistryForTest } from './activate.svelte';
|
|
5
5
|
import { PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ, PERMISSION_DOCUMENTS_WRITE, } from '../documents/types';
|
|
6
6
|
describe('ctx.browse permission gating', () => {
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
__resetShardRegistryForTest();
|
|
9
9
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
10
|
-
|
|
10
|
+
__setActiveScope('tenant-a');
|
|
11
11
|
});
|
|
12
12
|
it('is undefined when no documents permission is declared', async () => {
|
|
13
13
|
let captured = null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, deactivateShard, __resetShardRegistryForTest, } from './activate.svelte';
|
|
5
5
|
import { __resetContributionsForTest, list, listPoints } from '../contributions';
|
|
6
6
|
describe('ctx.contributions', () => {
|
|
@@ -8,7 +8,7 @@ describe('ctx.contributions', () => {
|
|
|
8
8
|
__resetShardRegistryForTest();
|
|
9
9
|
__resetContributionsForTest();
|
|
10
10
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
11
|
-
|
|
11
|
+
__setActiveScope('tenant-a');
|
|
12
12
|
});
|
|
13
13
|
it('is always present on ShardContext (no permission required)', async () => {
|
|
14
14
|
let captured = null;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, registeredShards, activeShards, __resetShardRegistryForTest, erroredShards, } from './activate.svelte';
|
|
5
5
|
describe('erroredShards map', () => {
|
|
6
6
|
beforeEach(() => {
|
|
7
7
|
__resetShardRegistryForTest();
|
|
8
8
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
9
|
-
|
|
9
|
+
__setActiveScope('tenant-a');
|
|
10
10
|
});
|
|
11
11
|
it('is empty after reset', () => {
|
|
12
12
|
expect(erroredShards.size).toBe(0);
|
|
@@ -21,7 +21,7 @@ describe('activateShard — unwind on activation failure', () => {
|
|
|
21
21
|
beforeEach(() => {
|
|
22
22
|
__resetShardRegistryForTest();
|
|
23
23
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
24
|
-
|
|
24
|
+
__setActiveScope('tenant-a');
|
|
25
25
|
});
|
|
26
26
|
it('unwinds partial state and records the error when activate throws', async () => {
|
|
27
27
|
const shard = {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, deactivateShard, __resetShardRegistryForTest } from './activate.svelte';
|
|
5
5
|
import { emit } from '../keys/revocation-bus.svelte';
|
|
6
6
|
describe('onKeyRevoked hook wiring', () => {
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
__resetShardRegistryForTest();
|
|
9
9
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
10
|
-
|
|
10
|
+
__setActiveScope('tenant-a');
|
|
11
11
|
});
|
|
12
12
|
it('fires onKeyRevoked when the bus emits for the shard', async () => {
|
|
13
13
|
const received = [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { registerShard, activateShard, __resetShardRegistryForTest, } from './activate.svelte';
|
|
5
5
|
import { __resetViewRegistryForTest } from './registry';
|
|
6
6
|
function programmaticVerb(name, summary, body) {
|
|
@@ -22,7 +22,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
22
22
|
__resetShardRegistryForTest();
|
|
23
23
|
__resetViewRegistryForTest();
|
|
24
24
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
25
|
-
|
|
25
|
+
__setActiveScope('tenant-test');
|
|
26
26
|
});
|
|
27
27
|
it('listVerbs returns every verb across active shards with shardId', async () => {
|
|
28
28
|
registerShard({
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { sh3 } from '../sh3Runtime.svelte';
|
|
20
20
|
import { registerView, unregisterView, registerVerb as fwRegisterVerb, unregisterVerb as fwUnregisterVerb } from './registry';
|
|
21
21
|
import { makeSh3Api } from '../sh3Api/headless';
|
|
22
|
-
import { createDocumentHandle,
|
|
22
|
+
import { createDocumentHandle, getDocumentBackend, getActiveScopeId } from '../documents';
|
|
23
23
|
import { fetchEnvState, putEnvState } from '../env/client';
|
|
24
24
|
import { getEnvServerUrl } from '../env/index';
|
|
25
25
|
import { apiFetch } from '../transport/apiFetch';
|
|
@@ -153,7 +153,7 @@ export async function activateShard(id, opts) {
|
|
|
153
153
|
};
|
|
154
154
|
const hasBrowse = (_a = shard.manifest.permissions) === null || _a === void 0 ? void 0 : _a.includes(PERMISSION_DOCUMENTS_BROWSE);
|
|
155
155
|
const browseCap = hasBrowse
|
|
156
|
-
? createBrowseCapability(
|
|
156
|
+
? createBrowseCapability(getActiveScopeId, getDocumentBackend(), {
|
|
157
157
|
canRead: (_c = (_b = shard.manifest.permissions) === null || _b === void 0 ? void 0 : _b.includes(PERMISSION_DOCUMENTS_READ)) !== null && _c !== void 0 ? _c : false,
|
|
158
158
|
canWrite: (_e = (_d = shard.manifest.permissions) === null || _d === void 0 ? void 0 : _d.includes(PERMISSION_DOCUMENTS_WRITE)) !== null && _e !== void 0 ? _e : false,
|
|
159
159
|
})
|
|
@@ -225,7 +225,7 @@ export async function activateShard(id, opts) {
|
|
|
225
225
|
return checkIsAdmin();
|
|
226
226
|
},
|
|
227
227
|
get tenantId() {
|
|
228
|
-
return
|
|
228
|
+
return getActiveScopeId();
|
|
229
229
|
},
|
|
230
230
|
getScope: () => { var _a; return (_a = scopeResolver === null || scopeResolver === void 0 ? void 0 : scopeResolver()) !== null && _a !== void 0 ? _a : 'tenant'; },
|
|
231
231
|
zones: ((_f = shard.manifest.permissions) === null || _f === void 0 ? void 0 : _f.includes(PERMISSION_STATE_MANAGE))
|
|
@@ -235,7 +235,7 @@ export async function activateShard(id, opts) {
|
|
|
235
235
|
documentPicker: browseCap
|
|
236
236
|
? createDocumentPicker(() => browseCap.listDocuments())
|
|
237
237
|
: createDocumentPicker(async () => {
|
|
238
|
-
const docs = await getDocumentBackend().list(
|
|
238
|
+
const docs = await getDocumentBackend().list(getActiveScopeId(), id);
|
|
239
239
|
return docs.map(d => (Object.assign(Object.assign({}, d), { shardId: id })));
|
|
240
240
|
}),
|
|
241
241
|
keys: ((_g = shard.manifest.permissions) === null || _g === void 0 ? void 0 : _g.includes(PERMISSION_KEYS_MINT))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend,
|
|
3
|
+
import { __setDocumentBackend, __setActiveScope } from '../documents/config';
|
|
4
4
|
import { __setEnvServerUrl } from '../env/index';
|
|
5
5
|
import { registerShard, activateShard, __resetShardRegistryForTest, } from './activate.svelte';
|
|
6
6
|
import { __resetViewRegistryForTest } from './registry';
|
|
@@ -11,7 +11,7 @@ describe('ctx.fetch', () => {
|
|
|
11
11
|
__resetShardRegistryForTest();
|
|
12
12
|
__resetViewRegistryForTest();
|
|
13
13
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
14
|
-
|
|
14
|
+
__setActiveScope('tenant-test');
|
|
15
15
|
__setEnvServerUrl('https://example.com');
|
|
16
16
|
});
|
|
17
17
|
afterEach(() => {
|
|
@@ -69,7 +69,7 @@ describe('ctx.serverUrl', () => {
|
|
|
69
69
|
__resetShardRegistryForTest();
|
|
70
70
|
__resetViewRegistryForTest();
|
|
71
71
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
72
|
-
|
|
72
|
+
__setActiveScope('tenant-test');
|
|
73
73
|
__setEnvServerUrl('https://example.com');
|
|
74
74
|
});
|
|
75
75
|
afterEach(() => {
|
|
@@ -100,7 +100,7 @@ describe('ctx.resolveUrl', () => {
|
|
|
100
100
|
__resetShardRegistryForTest();
|
|
101
101
|
__resetViewRegistryForTest();
|
|
102
102
|
__setDocumentBackend(new MemoryDocumentBackend());
|
|
103
|
-
|
|
103
|
+
__setActiveScope('tenant-test');
|
|
104
104
|
__setEnvServerUrl('https://example.com');
|
|
105
105
|
});
|
|
106
106
|
afterEach(() => {
|