@volter/world-runtime 2.0.55 → 3.0.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.
- package/dist/known-external-services.json +5 -4
- package/dist/src/actions-runner-cli.d.ts +2 -0
- package/dist/src/actions-runner-cli.js +25 -0
- package/dist/src/actions-runner.d.ts +18 -0
- package/dist/src/actions-runner.js +281 -0
- package/dist/src/ancestry.js +1 -1
- package/dist/src/app-url.js +1 -1
- package/dist/src/backings.d.ts +29 -0
- package/dist/src/backings.js +45 -0
- package/dist/src/branch.js +3 -2
- package/dist/src/browser-proxy-cli.js +1 -1
- package/dist/src/catalog-index.d.ts +38 -0
- package/dist/src/catalog-index.js +37 -0
- package/dist/src/catalog.d.ts +16 -3
- package/dist/src/catalog.js +69 -26
- package/dist/src/changeset.d.ts +1 -1
- package/dist/src/changeset.js +9 -11
- package/dist/src/cli-args.d.ts +4 -0
- package/dist/src/cli-args.js +18 -0
- package/dist/src/cli-commands.d.ts +1 -0
- package/dist/src/cli-commands.js +1216 -0
- package/dist/src/cli.js +8 -1300
- package/dist/src/configs.js +1 -1
- package/dist/src/console-apart.d.ts +2 -2
- package/dist/src/console-apart.js +5 -5
- package/dist/src/consumers.js +1 -1
- package/dist/src/covers.d.ts +1 -1
- package/dist/src/covers.js +94 -30
- package/dist/src/env-output.d.ts +1 -1
- package/dist/src/env-output.js +2 -2
- package/dist/src/fixture-env.js +18 -26
- package/dist/src/host-cli.js +4 -2
- package/dist/src/host-fault-fixture.js +6 -0
- package/dist/src/host-ready.d.ts +8 -2
- package/dist/src/host-ready.js +16 -4
- package/dist/src/host-worker.js +6 -1
- package/dist/src/host.d.ts +2 -0
- package/dist/src/host.js +9 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/init.d.ts +33 -23
- package/dist/src/init.js +228 -96
- package/dist/src/lifecycle-record.js +2 -2
- package/dist/src/local-branches.js +1 -1
- package/dist/src/origin.d.ts +1 -1
- package/dist/src/origin.js +12 -10
- package/dist/src/pack-facts.d.ts +30 -6
- package/dist/src/pack-facts.js +13 -3
- package/dist/src/pglite-backing.js +2 -1
- package/dist/src/placeholder.d.ts +1 -1
- package/dist/src/placeholder.js +12 -4
- package/dist/src/project-inspect.d.ts +1 -1
- package/dist/src/project-inspect.js +2 -2
- package/dist/src/redirect-proxy.d.ts +5 -0
- package/dist/src/redirect-proxy.js +8 -1
- package/dist/src/redis-backing.js +4 -2
- package/dist/src/reflect.js +1 -1
- package/dist/src/root.d.ts +11 -33
- package/dist/src/root.js +28 -59
- package/dist/src/runtime-test-support.js +1 -1
- package/dist/src/runtime.d.ts +7 -1
- package/dist/src/runtime.js +317 -53
- package/dist/src/schema.d.ts +27 -5
- package/dist/src/schema.js +13 -12
- package/dist/src/served-world.d.ts +11 -35
- package/dist/src/served-world.js +54 -141
- package/dist/src/tail.js +1 -1
- package/dist/src/up-cli.d.ts +3 -0
- package/dist/src/up-cli.js +55 -0
- package/dist/src/websocket-relay.d.ts +1 -1
- package/dist/src/world-origins.d.ts +1 -1
- package/dist/src/world-origins.js +1 -1
- package/dist/src/world-view.d.ts +1 -1
- package/dist/src/world-view.js +2 -2
- package/known-external-services.json +5 -4
- package/package.json +11 -11
- package/src/actions-runner-cli.ts +24 -0
- package/src/actions-runner.ts +244 -0
- package/src/ancestry.ts +1 -1
- package/src/app-url.ts +1 -1
- package/src/backings.ts +55 -0
- package/src/branch.ts +3 -2
- package/src/browser-proxy-cli.ts +1 -1
- package/src/catalog-index.ts +44 -0
- package/src/catalog.ts +67 -24
- package/src/changeset.ts +10 -13
- package/src/cli-args.ts +17 -0
- package/src/cli-commands.ts +1070 -0
- package/src/cli.ts +9 -1139
- package/src/configs.ts +1 -1
- package/src/console-apart.ts +5 -5
- package/src/consumers.ts +1 -1
- package/src/covers.ts +83 -30
- package/src/env-output.ts +2 -2
- package/src/fixture-env.ts +18 -26
- package/src/host-cli.ts +4 -2
- package/src/host-ready.ts +13 -4
- package/src/host-worker.ts +6 -2
- package/src/host.ts +11 -2
- package/src/index.ts +2 -2
- package/src/init.ts +222 -101
- package/src/lifecycle-record.ts +2 -2
- package/src/local-branches.ts +1 -1
- package/src/origin.ts +12 -10
- package/src/pack-facts.ts +22 -6
- package/src/pglite-backing.ts +2 -1
- package/src/placeholder.ts +13 -4
- package/src/project-inspect.ts +2 -2
- package/src/redirect-proxy.ts +9 -1
- package/src/redis-backing.ts +4 -2
- package/src/reflect.ts +1 -1
- package/src/root.ts +33 -65
- package/src/runtime-test-support.ts +1 -1
- package/src/runtime.ts +261 -55
- package/src/schema.ts +26 -13
- package/src/served-world.ts +50 -130
- package/src/tail.ts +1 -1
- package/src/up-cli.ts +55 -0
- package/src/websocket-relay.ts +1 -1
- package/src/world-origins.ts +1 -1
- package/src/world-view.ts +3 -3
package/dist/src/pack-facts.d.ts
CHANGED
|
@@ -8,16 +8,18 @@ export type PackAdoption = {
|
|
|
8
8
|
package: string;
|
|
9
9
|
usage: 'build' | 'deployment';
|
|
10
10
|
}>;
|
|
11
|
+
configFiles?: Array<{
|
|
12
|
+
file: string;
|
|
13
|
+
usage: 'application' | 'build' | 'deployment';
|
|
14
|
+
when?: string;
|
|
15
|
+
}>;
|
|
11
16
|
};
|
|
12
17
|
export type PackFacts = {
|
|
18
|
+
serverBacking?: {
|
|
19
|
+
command: string;
|
|
20
|
+
};
|
|
13
21
|
/** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
|
|
14
22
|
transport?: string;
|
|
15
|
-
/** Optional native frontend of the same state owner, selected explicitly on the pack CLI. */
|
|
16
|
-
nativeTransport?: {
|
|
17
|
-
protocol: string;
|
|
18
|
-
flag: string;
|
|
19
|
-
upstreamEnv: string;
|
|
20
|
-
};
|
|
21
23
|
adoption?: PackAdoption;
|
|
22
24
|
hosts?: Array<{
|
|
23
25
|
host?: string;
|
|
@@ -34,16 +36,37 @@ export type PackFacts = {
|
|
|
34
36
|
note: string;
|
|
35
37
|
};
|
|
36
38
|
endpointEnvNone?: string;
|
|
39
|
+
/** The door that issues the application a credential the twin accepts (TwinPack.credentialDoor). */
|
|
40
|
+
credentialDoor?: {
|
|
41
|
+
path: string;
|
|
42
|
+
body: Record<string, unknown>;
|
|
43
|
+
fill: Record<string, string>;
|
|
44
|
+
};
|
|
45
|
+
credentialIssuer?: string;
|
|
37
46
|
/** The pack's colocatable `create<Name>TwinServer` factory export, derived (or descriptor-
|
|
38
47
|
* declared under ambiguity) by scripts/pack-facts.ts. Consumed by `init` to emit
|
|
39
48
|
* `colocate:` service entries so a world's twins boot inside ONE host process (R2a). */
|
|
40
49
|
serveExport?: string;
|
|
50
|
+
/** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
|
|
51
|
+
managedService?: {
|
|
52
|
+
kind: 'redis' | 'mongodb';
|
|
53
|
+
scheme: string;
|
|
54
|
+
note: string;
|
|
55
|
+
};
|
|
41
56
|
/** The World's managed database the runtime binds the twin to at boot (TwinPack.managedDatabase). */
|
|
42
57
|
managedDatabase?: {
|
|
43
58
|
kind: 'postgres';
|
|
44
59
|
arg: string;
|
|
45
60
|
note: string;
|
|
46
61
|
};
|
|
62
|
+
/** Listeners the twin opens beside its main port, allocated and recorded by the runtime (TwinPack.sidePorts). */
|
|
63
|
+
sidePorts?: Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
option: string;
|
|
66
|
+
flag: string;
|
|
67
|
+
path?: string;
|
|
68
|
+
note: string;
|
|
69
|
+
}>;
|
|
47
70
|
/** R16: the protocol major the package targets and its standing against the kernel that compiled the artifact. */
|
|
48
71
|
protocol?: {
|
|
49
72
|
declared?: string;
|
|
@@ -52,6 +75,7 @@ export type PackFacts = {
|
|
|
52
75
|
};
|
|
53
76
|
};
|
|
54
77
|
export declare function packFacts(): Record<string, PackFacts>;
|
|
78
|
+
export declare function overlayPackFacts(packs: Record<string, PackFacts>): void;
|
|
55
79
|
/** Overlay descriptor-declared npm SDK names onto the central SDK_TWINS map (project-inspect.ts). */
|
|
56
80
|
/** The PyPI half of the SDK map, built the same way from each descriptor's `adoption.pypi`
|
|
57
81
|
* (PEP 503 names). One name belongs to one pack; a second claim refuses at load. */
|
package/dist/src/pack-facts.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// (packages/world-core/generated/pack-facts.json, compiled by scripts/pack-facts.ts
|
|
3
3
|
// from each pack's `pack: TwinPack` descriptor).
|
|
4
4
|
//
|
|
5
|
-
// During the descriptor-first migration (
|
|
5
|
+
// During the descriptor-first migration (architecture.md, "The descriptor": the descriptor is the one home), the
|
|
6
6
|
// adoption tables here are the UNION of the legacy central maps and the pack declarations.
|
|
7
7
|
// The overlay MUTATES the central tables at module init — every use-site keeps reading the
|
|
8
8
|
// object it always read — and a fact declared in BOTH places throws: dual declaration is the
|
|
@@ -13,7 +13,16 @@ import factsDocument from '@volter/world-core/generated/pack-facts.json' with {
|
|
|
13
13
|
// core's artifact, imported as a JSON module: the published runtime resolves it beside an installed
|
|
14
14
|
// core, a bundle carries it, and no host needs a filesystem to read it
|
|
15
15
|
export function packFacts() {
|
|
16
|
-
return factsDocument.packs;
|
|
16
|
+
return merged ??= { ...factsDocument.packs, ...overlaid };
|
|
17
|
+
}
|
|
18
|
+
/** The facts of packs the kernel was not built with: a released pack carries its own (`generated/pack-facts.json`), and
|
|
19
|
+
* the catalog overlays them here, vendor by vendor, over the built-in ones (architecture, "The catalog: where twins
|
|
20
|
+
* come from"). The central tables overlaid at module init keep the built-in adoption facts. */
|
|
21
|
+
const overlaid = {};
|
|
22
|
+
let merged;
|
|
23
|
+
export function overlayPackFacts(packs) {
|
|
24
|
+
Object.assign(overlaid, packs);
|
|
25
|
+
merged = undefined;
|
|
17
26
|
}
|
|
18
27
|
const dual = (vendor, kind, key, holder) => new Error(`pack-facts: ${kind} "${key}" is declared BOTH on the ${vendor} pack descriptor AND in the central ${holder} table — ` +
|
|
19
28
|
`the migration rule is one home per fact. Delete the central entry (the descriptor wins).`);
|
|
@@ -65,7 +74,8 @@ export function overlayCoversMaps(maps) {
|
|
|
65
74
|
maps.scopeVendors[scope] = vendor;
|
|
66
75
|
}
|
|
67
76
|
for (const stemRaw of facts.adoption?.envStems ?? []) {
|
|
68
|
-
|
|
77
|
+
// normalized as covers.ts reads a name's stem (`UPSTASH_REDIS_REST_URL` → `upstashredis`)
|
|
78
|
+
const stem = stemRaw.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
69
79
|
if (stem in maps.envStemVendors)
|
|
70
80
|
throw dual(vendor, 'env stem', stem, 'ENV_STEM_VENDORS');
|
|
71
81
|
maps.envStemVendors[stem] = vendor;
|
|
@@ -18,6 +18,7 @@ import { dirname, join } from 'node:path';
|
|
|
18
18
|
import { spawn, spawnSync } from 'node:child_process';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
import { findInstalledPackage, packCli } from "./catalog.js";
|
|
21
|
+
import { vendorPackageDir } from '@volter/world-core/runtime';
|
|
21
22
|
/** Parse the services out of world.infrastructure.yml. This is NOT a YAML
|
|
22
23
|
* parser: init's renderComposeFile emits a deterministic shape, and this
|
|
23
24
|
* reads exactly that shape back (service key at 2-space indent = the kind;
|
|
@@ -83,7 +84,7 @@ function pidPath(dataDir, kind) {
|
|
|
83
84
|
* this runtime was installed with (its dependency, or the checkout's workspace link). */
|
|
84
85
|
export function mongodbTwinCli(worldConfig, froms = [...(worldConfig ? [dirname(worldConfig)] : []), here()]) {
|
|
85
86
|
for (const from of froms) {
|
|
86
|
-
const dir = findInstalledPackage(from, MONGODB_TWIN);
|
|
87
|
+
const dir = vendorPackageDir(from, 'mongodb') ?? findInstalledPackage(from, MONGODB_TWIN);
|
|
87
88
|
const cli = dir === undefined ? undefined : packCli(dir);
|
|
88
89
|
if (cli !== undefined)
|
|
89
90
|
return cli;
|
|
@@ -12,7 +12,7 @@ export type SeedWorldOutcome = {
|
|
|
12
12
|
};
|
|
13
13
|
/** The seed entry `init` emits beside the world config, unless the caller names one. */
|
|
14
14
|
export declare function seedEntryFor(instance: WorldInstance, entry?: string): string;
|
|
15
|
-
/** Pull from the placeholder: every twin's window open around the seed entry. */
|
|
15
|
+
/** Pull from the placeholder: every twin's window open around the seed entry (a twin with a root excepted). */
|
|
16
16
|
export declare function seedWorld(name: string, options?: SeedWorldOptions): Promise<SeedWorldOutcome>;
|
|
17
17
|
/** Back to the defaults: down with purge, up from the same config and env file, seed. */
|
|
18
18
|
export declare function resetWorld(name: string, options?: SeedWorldOptions): Promise<SeedWorldOutcome & {
|
package/dist/src/placeholder.js
CHANGED
|
@@ -9,7 +9,7 @@ import { materializeRoots } from "./root.js";
|
|
|
9
9
|
import { loadWorldConfig } from "./configs.js";
|
|
10
10
|
import { existsSync, readdirSync } from 'node:fs';
|
|
11
11
|
import { dirname, join, resolve } from 'node:path';
|
|
12
|
-
import { beginPlaceholderPull, endPlaceholderPull, listEvents, PLACEHOLDER_REMOTE, worldStateRoot } from '@volter/world-core';
|
|
12
|
+
import { beginPlaceholderPull, endPlaceholderPull, listEvents, PLACEHOLDER_REMOTE, worldStateRoot } from '@volter/world-core/runtime';
|
|
13
13
|
import { downWorld, runWithWorldEnv, saveWorldInstance, statusWorld, upWorld } from "./runtime.js";
|
|
14
14
|
/** The seed entry `init` emits beside the world config, unless the caller names one. */
|
|
15
15
|
export function seedEntryFor(instance, entry) {
|
|
@@ -36,12 +36,20 @@ function placeholderCount(controlRoot) {
|
|
|
36
36
|
}
|
|
37
37
|
return n;
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/** The twins of a World's config that have a root (a vendor's real account behind them). */
|
|
40
|
+
function rootedTwins(instance, root) {
|
|
41
|
+
const { config } = loadWorldConfig(existsSync(instance.configPath) ? instance.configPath : instance.config, root);
|
|
42
|
+
return config.services.filter((s) => s.root).map((s) => s.id);
|
|
43
|
+
}
|
|
44
|
+
/** Pull from the placeholder: every twin's window open around the seed entry (a twin with a root excepted). */
|
|
40
45
|
export async function seedWorld(name, options = {}) {
|
|
41
46
|
const root = resolve(options.root ?? process.cwd());
|
|
42
47
|
const instance = statusWorld(name, root);
|
|
43
48
|
const entry = seedEntryFor(instance, options.entry);
|
|
44
|
-
|
|
49
|
+
// a vendor-backed twin is not seeded (architecture, "Refresh"): its state is its root's, and the default data's runner
|
|
50
|
+
// skips the twins named in VOLTER_SEED_SKIP
|
|
51
|
+
const rooted = new Set(rootedTwins(instance, root));
|
|
52
|
+
const services = Object.keys(instance.services).filter((s) => !rooted.has(s)).sort();
|
|
45
53
|
const at = new Date().toISOString();
|
|
46
54
|
for (const service of services)
|
|
47
55
|
beginPlaceholderPull(service, join(instance.dirs.data, service), at);
|
|
@@ -56,7 +64,7 @@ export async function seedWorld(name, options = {}) {
|
|
|
56
64
|
throw new Error(`${entry} is TypeScript: seeding it under Node needs Node 22.6 or newer (type stripping), or Bun; this is Node ${process.versions.node}`);
|
|
57
65
|
}
|
|
58
66
|
const runner = typeof Bun !== 'undefined' ? ['bun', entry] : [process.execPath, '--experimental-strip-types', '--no-warnings', entry];
|
|
59
|
-
exitCode = await runWithWorldEnv(name, runner, root, { cwd: options.cwd ?? dirname(entry) });
|
|
67
|
+
exitCode = await runWithWorldEnv(name, runner, root, { cwd: options.cwd ?? dirname(entry), env: { VOLTER_SEED_SKIP: [...rooted].sort().join(',') } });
|
|
60
68
|
}
|
|
61
69
|
finally {
|
|
62
70
|
for (const service of services)
|
|
@@ -19,7 +19,7 @@ export type ProjectInspection = {
|
|
|
19
19
|
/** npm package → vendor/twin mapping — the shared vendor↔SDK knowledge used by both
|
|
20
20
|
* `inspect-project` (adoption discovery) and `covers` (room-setup coverage proof).
|
|
21
21
|
* The canonical entry for each pack is the real client package that pack's own tests
|
|
22
|
-
* prove the twin against (see each
|
|
22
|
+
* prove the twin against (see each pack's package.json devDependencies);
|
|
23
23
|
* the extra aliases are the vendor's other official/first-party clients of the same
|
|
24
24
|
* API surface. */
|
|
25
25
|
export declare const SDK_TWINS: Record<string, {
|
|
@@ -5,13 +5,13 @@ import { normalizePypiName, overlayPypiTwins, overlaySdkTwins } from "./pack-fac
|
|
|
5
5
|
/** npm package → vendor/twin mapping — the shared vendor↔SDK knowledge used by both
|
|
6
6
|
* `inspect-project` (adoption discovery) and `covers` (room-setup coverage proof).
|
|
7
7
|
* The canonical entry for each pack is the real client package that pack's own tests
|
|
8
|
-
* prove the twin against (see each
|
|
8
|
+
* prove the twin against (see each pack's package.json devDependencies);
|
|
9
9
|
* the extra aliases are the vendor's other official/first-party clients of the same
|
|
10
10
|
* API surface. */
|
|
11
11
|
export const SDK_TWINS = {
|
|
12
12
|
// (empty — every entry has moved onto its pack descriptor. See the note below.)
|
|
13
13
|
};
|
|
14
|
-
// descriptor-first migration (
|
|
14
|
+
// descriptor-first migration (architecture.md, "The descriptor"), COMPLETE for this table: every pack declares its own sdk names
|
|
15
15
|
// (`adoption.sdks` on its descriptor, compiled into the committed pack-facts artifact), and the
|
|
16
16
|
// literal above is now empty — the map is populated entirely by the overlay below. It is kept as
|
|
17
17
|
// the overlay's target, not as a home: the exported object identity is what every use site already
|
|
@@ -48,6 +48,11 @@ type CaPaths = {
|
|
|
48
48
|
caKey: string;
|
|
49
49
|
caCert: string;
|
|
50
50
|
};
|
|
51
|
+
/** Whether this host opens raw sockets to other hosts, which the ambient TLS redirect proxy exists to intercept. A
|
|
52
|
+
* browser tab's Node (tabnode, which declares `process.versions.webcontainer`) opens none: its TLS is loopback pairing
|
|
53
|
+
* with no handshake, and a remote HTTPS call leaves through the page's HTTP transport. There no proxy can run and none
|
|
54
|
+
* is needed: the World's network policy is enforced by the injector in every Node program (world-core inject.cjs). */
|
|
55
|
+
export declare function hostHasRawEgress(versions?: Record<string, string | undefined>): boolean;
|
|
51
56
|
export declare function opensslAvailable(): boolean;
|
|
52
57
|
/** Generate (once) a throwaway CA in `tlsDir`. Idempotent: reuses an existing CA so a re-activate in
|
|
53
58
|
* the same world trusts the same cert. Session/project-scoped — it lives under the instance dir and
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { serveHttp } from '@volter/world-core';
|
|
1
|
+
import { serveHttp } from '@volter/world-core/runtime';
|
|
2
2
|
import { worldNetworkPolicyFromEnv, allowsWorldNetworkEgress } from '@volter/world-core/network-policy';
|
|
3
3
|
import { websocketRelay } from "./websocket-relay.js";
|
|
4
4
|
// The ambient TLS redirect proxy — the substance of "any CLI, zero config" (WORLD_ACTIVATE.md §"How
|
|
@@ -191,6 +191,13 @@ function shouldBlockUntwinned(value, env) {
|
|
|
191
191
|
function caPaths(tlsDir) {
|
|
192
192
|
return { dir: tlsDir, caKey: join(tlsDir, 'ca-key.pem'), caCert: join(tlsDir, 'ca-cert.pem') };
|
|
193
193
|
}
|
|
194
|
+
/** Whether this host opens raw sockets to other hosts, which the ambient TLS redirect proxy exists to intercept. A
|
|
195
|
+
* browser tab's Node (tabnode, which declares `process.versions.webcontainer`) opens none: its TLS is loopback pairing
|
|
196
|
+
* with no handshake, and a remote HTTPS call leaves through the page's HTTP transport. There no proxy can run and none
|
|
197
|
+
* is needed: the World's network policy is enforced by the injector in every Node program (world-core inject.cjs). */
|
|
198
|
+
export function hostHasRawEgress(versions = process.versions) {
|
|
199
|
+
return versions.webcontainer === undefined;
|
|
200
|
+
}
|
|
194
201
|
export function opensslAvailable() {
|
|
195
202
|
// TEST-ONLY seam (TWIN-64): forces "openssl unavailable" deterministically. A PATH-shim (fake
|
|
196
203
|
// `openssl` on PATH) doesn't reliably work here because `spawnSync('openssl', …)` below is called
|
|
@@ -6,19 +6,20 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Why the twin and not a container: a World's state branches, checkpoints and rolls back as one tree, and a Redis
|
|
8
8
|
// in a container is state outside it (a branch of the World would share the container's queue and cache). The
|
|
9
|
-
// twin keeps each key a subject of the World (
|
|
9
|
+
// twin keeps each key a subject of the World (the Redis pack's storage module). It needs nothing
|
|
10
10
|
// installed: no container runtime, no redis-server binary.
|
|
11
11
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { connect } from 'node:net';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { spawn } from 'node:child_process';
|
|
15
15
|
import { findInstalledPackage, packCli } from "./catalog.js";
|
|
16
|
+
import { vendorPackageDir } from '@volter/world-core/runtime';
|
|
16
17
|
const PACKAGE = '@volter/twin-redis';
|
|
17
18
|
const READY_TIMEOUT_MS = 60_000;
|
|
18
19
|
/** The redis twin's cli: installed beside this runtime (its node_modules walk), else beside the World's config. */
|
|
19
20
|
export function redisTwinCli(from) {
|
|
20
21
|
for (const dir of from) {
|
|
21
|
-
const found = findInstalledPackage(dir, PACKAGE);
|
|
22
|
+
const found = vendorPackageDir(dir, 'redis') ?? findInstalledPackage(dir, PACKAGE);
|
|
22
23
|
const cli = found === undefined ? undefined : packCli(found);
|
|
23
24
|
if (cli !== undefined && existsSync(cli))
|
|
24
25
|
return cli;
|
|
@@ -69,6 +70,7 @@ export async function redisTwinUp(services, dataDir, cli, launch = {}) {
|
|
|
69
70
|
const child = spawn(process.execPath, [...runtime, cli, 'serve', '--port', String(service.hostPort), '--root', root], {
|
|
70
71
|
env: launch.env ?? process.env,
|
|
71
72
|
detached: true,
|
|
73
|
+
windowsHide: true,
|
|
72
74
|
stdio: ['ignore', logFd, logFd],
|
|
73
75
|
});
|
|
74
76
|
child.unref();
|
package/dist/src/reflect.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { serveHttp } from '@volter/world-core';
|
|
1
|
+
import { serveHttp } from '@volter/world-core/runtime';
|
|
2
2
|
import { websocketRelay } from "./websocket-relay.js";
|
|
3
3
|
import { sessionTrustEnv } from "./ca-trust.js";
|
|
4
4
|
// The reflect attachment (docs/guides/route-a-cli-through-the-world.md): attach an UNMODIFIED consumer —
|
package/dist/src/root.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type Check, type CredentialPayload, type PerformAction, type RemoteExecute, type RootConfig } from '@volter/world-core';
|
|
2
|
-
import { loadChecks, type DeployReport, type ObserveReport } from '@volter/world-core';
|
|
1
|
+
import { type Check, type CredentialPayload, type PerformAction, type RemoteExecute, type RootConfig } from '@volter/world-core/runtime';
|
|
2
|
+
import { loadChecks, type DeployReport, type ObserveReport } from '@volter/world-core/runtime';
|
|
3
3
|
import type { WorldConfig } from './schema.js';
|
|
4
4
|
/** `$XDG_CONFIG_HOME/volter/kek`, else `~/.config/volter/kek`: the key every credential this user
|
|
5
5
|
* seals is wrapped under. Made on first use, 0600, never printed. */
|
|
6
|
-
export { userKek, userKekPath } from '@volter/world-core';
|
|
6
|
+
export { userKek, userKekPath } from '@volter/world-core/runtime';
|
|
7
7
|
export declare function credentialPath(worldRoot: string, vendor: string): string;
|
|
8
8
|
/** Rewrap every credential sealed beside the World (each vendor's and each link's) under the newest version of the key
|
|
9
9
|
* that wrapped it: an operator's act after rotating the key, so no record still needs an old version. `current` is only
|
|
@@ -15,11 +15,15 @@ export declare function rewrapTwinCredentials(worldRoot: string): Promise<{
|
|
|
15
15
|
current: string[];
|
|
16
16
|
left: string[];
|
|
17
17
|
}>;
|
|
18
|
-
/** Seal a vendor's credential beside the World,
|
|
19
|
-
*
|
|
18
|
+
/** Seal a vendor's credential beside the World, under the current sealer (the host's vault, else the key: company
|
|
19
|
+
* decision 0034), bound to `bound` (destinationOf the root's or link's origin): it opens for that destination only, so
|
|
20
|
+
* pointing the root or link elsewhere never sends it there. Its ingest door's signing secret is sealed with it
|
|
21
|
+
* (architecture, "Ingest"): the one the vendor issued, given in the payload (`signingSecret`), else one minted here and
|
|
22
|
+
* answered once, to enter at the vendor where the operator chooses the webhook's secret. */
|
|
20
23
|
export declare function sealTwinCredential(worldRoot: string, vendor: string, payload: CredentialPayload, at: string | undefined, bound: string): Promise<{
|
|
21
24
|
placedAt: string;
|
|
22
25
|
fingerprint: string;
|
|
26
|
+
mintedSigningSecret?: string;
|
|
23
27
|
}>;
|
|
24
28
|
/** Bind each credential sealed before bindings to its destination, keeping its placedAt, fingerprint and rotatedAt,
|
|
25
29
|
* and only over the record that was read; and move every record still under the host's own key (v1) to the vault,
|
|
@@ -73,34 +77,7 @@ export declare function materializeRoots(config: WorldConfig, dataDir: string, w
|
|
|
73
77
|
/** The root a state service under `controlRoot` is bound to — its own root.json, else a sibling
|
|
74
78
|
* state's (slack records under `chat`; the root was written under `slack`). */
|
|
75
79
|
export declare function rootForControlRoot(controlRoot: string, service: string): MaterializedRoot | null;
|
|
76
|
-
/** The
|
|
77
|
-
* hosted mirror build reads it (apps/cloud/scripts/mirrors.ts); a pack's index need not re-export them. */
|
|
78
|
-
export declare function packMirrorExports(vendor: string, from: string): Promise<Record<string, unknown> | null>;
|
|
79
|
-
/** A pack's mirror as the pack builds it (`<vendor>MirrorHtml`, `build<Vendor>MirrorClient`,
|
|
80
|
-
* `<vendor>MirrorStyles`), found the way a World finds the pack; null when the pack has none. The
|
|
81
|
-
* client builds at serve under Bun and is served prebuilt under Node, by the pack's own builder. */
|
|
82
|
-
export type PackMirror = {
|
|
83
|
-
html: () => string;
|
|
84
|
-
client: () => Promise<string>;
|
|
85
|
-
styles?: () => Promise<string>; /** the account and a credential its screens sign in with (`<vendor>MirrorSignIn`): the account found in the twin's
|
|
86
|
-
* state at `root`, the credential minted for it through the twin's own doors (`twin`), never a stored one read back */
|
|
87
|
-
signIn?: (as: string, ctx: MirrorSignInContext) => Promise<{
|
|
88
|
-
account: string;
|
|
89
|
-
token: string;
|
|
90
|
-
} | null> | {
|
|
91
|
-
account: string;
|
|
92
|
-
token: string;
|
|
93
|
-
} | null;
|
|
94
|
-
};
|
|
95
|
-
/** What a pack's `<vendor>MirrorSignIn` is given: the twin's state root (to find the account) and the twin's own doors
|
|
96
|
-
* (to mint a credential for it, as the World's seed does through `/_twin/…`). */
|
|
97
|
-
export type MirrorSignInContext = {
|
|
98
|
-
root: string;
|
|
99
|
-
twin: (path: string, init?: RequestInit) => Promise<Response>;
|
|
100
|
-
};
|
|
101
|
-
export declare function packMirror(vendor: string, from: string): Promise<PackMirror | null>;
|
|
102
|
-
/** The real state system's adapters for a vendor: registered with the pack (protocol 2), else the
|
|
103
|
-
* pack's anchored exports (protocol 1: `perform<Name>Action`, `sync<Name>FromRemote`). */
|
|
80
|
+
/** The real state system's adapters for a vendor: the ones its pack names on its descriptor. */
|
|
104
81
|
export type IngestAdapter = (request: Request, ctx: {
|
|
105
82
|
root?: string;
|
|
106
83
|
secret?: string;
|
|
@@ -111,6 +88,7 @@ export declare function adaptersFor(vendor: string, from: string): Promise<{
|
|
|
111
88
|
root?: string;
|
|
112
89
|
origin?: string;
|
|
113
90
|
credential?: string;
|
|
91
|
+
scope?: string;
|
|
114
92
|
}) => Promise<unknown>;
|
|
115
93
|
ingest?: IngestAdapter;
|
|
116
94
|
}>;
|
package/dist/src/root.js
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
// never in a pack, never an env var). `volter world deploy` performs landed entries against each
|
|
8
8
|
// root through the pack's adapters over the kernel executor, with the world's checks in front;
|
|
9
9
|
// `deploy: auto` does the same the moment an entry is appended (the host installs the hook).
|
|
10
|
+
import { randomBytes } from 'node:crypto';
|
|
10
11
|
import { packEntry } from "./catalog.js";
|
|
11
|
-
import { existsSync,
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
12
13
|
import { homedir } from 'node:os';
|
|
13
14
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
14
|
-
import { authStrategyFor, currentSealer, destinationOf, sealsThroughVault, openSealedAt, getActiveWorldStore, getPack, buildRemoteExecute, clearRoot, readRoot, registerStateSystem, stateDirName, stateSystemFor, validateRemoteOrigin, writeRoot, } from '@volter/world-core';
|
|
15
|
+
import { authStrategyFor, currentSealer, destinationOf, sealsThroughVault, openSealedAt, getActiveWorldStore, getPack, buildRemoteExecute, clearRoot, readRoot, registerStateSystem, stateDirName, stateSystemFor, validateRemoteOrigin, writeRoot, } from '@volter/world-core/runtime';
|
|
15
16
|
import { loadWorldConfig, worldChangesetRules, writeWorldConfig } from "./configs.js";
|
|
16
|
-
import { changesetReadiness, collectObservations, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody,
|
|
17
|
+
import { changesetReadiness, collectObservations, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, vendorPackageDir } from '@volter/world-core/runtime';
|
|
17
18
|
import { findWorldChangeset, listWorldChangesets } from "./changeset.js";
|
|
18
19
|
import { importModule } from "./import-module.js";
|
|
19
20
|
import { statusWorld } from "./runtime.js";
|
|
@@ -21,11 +22,10 @@ import { envNameVendor } from "./covers.js";
|
|
|
21
22
|
import { parseEnvFile } from "./init.js";
|
|
22
23
|
import { fakeEnvValue } from "./fixture-env.js";
|
|
23
24
|
import { projectManifestDirs } from "./project-inspect.js";
|
|
24
|
-
import { findInstalledPackage, twinPackageName } from "./catalog.js";
|
|
25
25
|
// ── the user's sealing key ──────────────────────────────────────────────────────────────────
|
|
26
26
|
/** `$XDG_CONFIG_HOME/volter/kek`, else `~/.config/volter/kek`: the key every credential this user
|
|
27
27
|
* seals is wrapped under. Made on first use, 0600, never printed. */
|
|
28
|
-
export { userKek, userKekPath } from '@volter/world-core';
|
|
28
|
+
export { userKek, userKekPath } from '@volter/world-core/runtime';
|
|
29
29
|
// ── the sealed credential, beside the world ─────────────────────────────────────────────────
|
|
30
30
|
export function credentialPath(worldRoot, vendor) {
|
|
31
31
|
return join(resolve(worldRoot), stateDirName(), 'credentials', `${vendor}.json`);
|
|
@@ -77,14 +77,18 @@ export async function rewrapTwinCredentials(worldRoot) {
|
|
|
77
77
|
}
|
|
78
78
|
return out;
|
|
79
79
|
}
|
|
80
|
-
/** Seal a vendor's credential beside the World,
|
|
81
|
-
*
|
|
80
|
+
/** Seal a vendor's credential beside the World, under the current sealer (the host's vault, else the key: company
|
|
81
|
+
* decision 0034), bound to `bound` (destinationOf the root's or link's origin): it opens for that destination only, so
|
|
82
|
+
* pointing the root or link elsewhere never sends it there. Its ingest door's signing secret is sealed with it
|
|
83
|
+
* (architecture, "Ingest"): the one the vendor issued, given in the payload (`signingSecret`), else one minted here and
|
|
84
|
+
* answered once, to enter at the vendor where the operator chooses the webhook's secret. */
|
|
82
85
|
export async function sealTwinCredential(worldRoot, vendor, payload, at, bound) {
|
|
83
86
|
if (!/^origin=https?:\/\/[^/]+$/.test(bound))
|
|
84
87
|
throw new Error(`a credential is sealed for a destination (destinationOf its root's or link's origin), not ${JSON.stringify(bound)}`);
|
|
85
|
-
const
|
|
88
|
+
const minted = typeof payload.signingSecret === 'string' && payload.signingSecret.length >= 8 ? undefined : randomBytes(24).toString('hex');
|
|
89
|
+
const sealed = await currentSealer().seal(minted ? { ...payload, signingSecret: minted } : payload, at ?? new Date().toISOString(), vendor, bound);
|
|
86
90
|
getActiveWorldStore().write(credentialPath(worldRoot, vendor), `${JSON.stringify(sealed, null, 2)}\n`, { secret: true });
|
|
87
|
-
return { placedAt: sealed.placedAt, fingerprint: sealed.fingerprint };
|
|
91
|
+
return { placedAt: sealed.placedAt, fingerprint: sealed.fingerprint, ...(minted ? { mintedSigningSecret: minted } : {}) };
|
|
88
92
|
}
|
|
89
93
|
/** The inventory of credentials sealed before bindings, kept sealed beside them (`.legacy-inventory.json`, its own
|
|
90
94
|
* slot): `pending` maps each such credential to the destination it is bound to, or null when it had none and so is
|
|
@@ -424,70 +428,35 @@ async function packModule(vendor, from) {
|
|
|
424
428
|
const entry = packEntryPath(vendor, from);
|
|
425
429
|
return entry ? await importModule(entry) : null;
|
|
426
430
|
}
|
|
427
|
-
/** The pack's
|
|
428
|
-
*
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
if (installed)
|
|
432
|
-
return packEntry(installed);
|
|
433
|
-
const checkout = resolve(import.meta.dir, '..', '..', 'twin', vendor, 'src', 'index.ts');
|
|
434
|
-
return existsSync(checkout) ? checkout : null;
|
|
435
|
-
}
|
|
436
|
-
/** The pack's mirror module (`src/<vendor>-mirror-ui.ts`: its shell, client and stylesheet), read as the
|
|
437
|
-
* hosted mirror build reads it (apps/cloud/scripts/mirrors.ts); a pack's index need not re-export them. */
|
|
438
|
-
export async function packMirrorExports(vendor, from) {
|
|
439
|
-
const entry = packEntryPath(vendor, from);
|
|
440
|
-
if (!entry)
|
|
441
|
-
return null;
|
|
442
|
-
const dir = dirname(entry);
|
|
443
|
-
const file = existsSync(dir) ? readdirSync(dir).find((f) => /-mirror-ui\.(ts|js)$/.test(f) && !f.includes('.test.')) : undefined;
|
|
444
|
-
return importModule(file ? join(dir, file) : entry);
|
|
431
|
+
/** The pack's directory: installed beside the World, in its pack repository, or in the pack repository beside this
|
|
432
|
+
* platform checkout. */
|
|
433
|
+
function packDir(vendor, from) {
|
|
434
|
+
return vendorPackageDir(from, vendor) ?? vendorPackageDir(import.meta.dir, vendor);
|
|
445
435
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const dir =
|
|
449
|
-
|
|
450
|
-
const src = join(dir, 'src');
|
|
451
|
-
const file = existsSync(src) ? readdirSync(src).find((f) => f.endsWith('-mirror-ui.ts')) : undefined;
|
|
452
|
-
const entry = file ? join(src, file) : installed ? packEntry(installed) : join(src, 'index.ts');
|
|
453
|
-
if (!existsSync(entry))
|
|
454
|
-
return null;
|
|
455
|
-
const mod = await importModule(entry);
|
|
456
|
-
const pick = (re) => { const k = Object.keys(mod).find((key) => re.test(key) && typeof mod[key] === 'function'); return k ? mod[k] : undefined; };
|
|
457
|
-
const html = pick(/^\w+MirrorHtml$/);
|
|
458
|
-
const client = pick(/^build\w+MirrorClient$/);
|
|
459
|
-
const styles = pick(/^\w+MirrorStyles$/);
|
|
460
|
-
const signIn = pick(/^\w+MirrorSignIn$/);
|
|
461
|
-
if (!html || !client)
|
|
462
|
-
return null;
|
|
463
|
-
return { html: html, client: client, ...(styles ? { styles: styles } : {}), ...(signIn ? { signIn: signIn } : {}) };
|
|
436
|
+
/** The pack's module itself: its every export, where the registry holds only the descriptor. */
|
|
437
|
+
function packEntryPath(vendor, from) {
|
|
438
|
+
const dir = packDir(vendor, from);
|
|
439
|
+
return dir === undefined ? null : packEntry(dir);
|
|
464
440
|
}
|
|
465
441
|
export async function adaptersFor(vendor, from) {
|
|
466
442
|
const registered = stateSystemFor(vendor);
|
|
467
443
|
// a registration is the whole state system for that twin (a stub in a test, a pack's own
|
|
468
|
-
// descriptor);
|
|
444
|
+
// descriptor); an unregistered twin's are read from its pack's descriptor
|
|
469
445
|
if (registered)
|
|
470
446
|
return registered;
|
|
471
447
|
const mod = await packModule(vendor, from);
|
|
472
448
|
if (!mod)
|
|
473
449
|
return {};
|
|
474
450
|
const pack = mod.pack;
|
|
451
|
+
if (pack?.vendorBacked?.none)
|
|
452
|
+
throw new Error(`the ${vendor} pack has no vendor-backed half: ${pack.vendorBacked.none}`);
|
|
475
453
|
if (pack?.stateSystem?.perform) {
|
|
476
454
|
registerStateSystem(vendor, pack.stateSystem);
|
|
477
455
|
return pack.stateSystem;
|
|
478
456
|
}
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
throw new Error(`the ${vendor} pack declares protocol 2 but names no adapters — \`stateSystem: { perform, refresh?, ingest? }\` on its descriptor`);
|
|
483
|
-
const name = (re) => Object.keys(mod).find((k) => re.test(k) && typeof mod[k] === 'function');
|
|
484
|
-
const performName = name(/^perform\w+Action$/);
|
|
485
|
-
const refreshName = name(/^refresh\w+$/) ?? name(/^sync\w+FromRemote$/);
|
|
486
|
-
const ingestName = name(/^ingest\w+Event$/);
|
|
487
|
-
const out = { ...(performName ? { perform: mod[performName] } : {}), ...(refreshName ? { refresh: mod[refreshName] } : {}), ...(ingestName ? { ingest: mod[ingestName] } : {}) };
|
|
488
|
-
if (out.perform)
|
|
489
|
-
registerStateSystem(vendor, out);
|
|
490
|
-
return out;
|
|
457
|
+
// the kernel derives a pack's adapters from its manifest and surface (docs/contributing/architecture.md, "The
|
|
458
|
+
// real-system adapters"): its index registers packOf(manifest, surface)
|
|
459
|
+
throw new Error(`the ${vendor} pack derives no adapters: its index registers \`packOf(manifest)\` without its surface (\`create-pack <vendor> --index\` writes it with it), and its resources' \`refresh\` scopes say what a refresh reads`);
|
|
491
460
|
}
|
|
492
461
|
// ── the world's checks ──────────────────────────────────────────────────────────────────────
|
|
493
462
|
/** `.volter/checks/*.ts|js`: each exports `check` (or default) as `{ name, run }`. The shipped
|
|
@@ -550,7 +519,7 @@ export async function refreshTwin(opts) {
|
|
|
550
519
|
let value;
|
|
551
520
|
let observations;
|
|
552
521
|
try {
|
|
553
|
-
({ value, observations } = await collectObservations(() => adapters.refresh(buildRemoteExecute(origin, credential, authStrategyFor(opts.vendor), getPack(opts.vendor)?.hosts, { custody: rootCustody(opts.root, opts.vendor) }), { root: opts.controlRoot, origin: adapterOrigin, ...(credentialFingerprint ? { credential: credentialFingerprint } : {}) })));
|
|
522
|
+
({ value, observations } = await collectObservations(() => adapters.refresh(buildRemoteExecute(origin, credential, authStrategyFor(opts.vendor), getPack(opts.vendor)?.hosts, { custody: rootCustody(opts.root, opts.vendor), vendor: opts.vendor }), { root: opts.controlRoot, origin: adapterOrigin, ...(opts.root.scope ? { scope: opts.root.scope } : {}), ...(credentialFingerprint ? { credential: credentialFingerprint } : {}) })));
|
|
554
523
|
}
|
|
555
524
|
catch (error) {
|
|
556
525
|
// the vendor was asked and did not answer: the marker keeps the last fold's numbers and records the
|
|
@@ -7,7 +7,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import net from 'node:net';
|
|
9
9
|
import { downWorld, statusWorld } from "./runtime.js";
|
|
10
|
-
import { stateDirName } from '@volter/world-core';
|
|
10
|
+
import { stateDirName } from '@volter/world-core/runtime';
|
|
11
11
|
/** Test-only seam (see runtime.ts `proxyDaemonDeadlineMs`): force the daemon-poll deadline to `ms`
|
|
12
12
|
* for the duration of `fn`, then restore whatever was there before — deterministic, no sleeps. */
|
|
13
13
|
export async function withProxyDeadline(ms, fn) {
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -78,8 +78,10 @@ export declare function sharingClaimAlive(claim: SharingClaim): boolean;
|
|
|
78
78
|
* run code that is not Volter's (a World's apps, a command attached to a World, a tunnel, a dependency install) are
|
|
79
79
|
* given an environment without it. */
|
|
80
80
|
export declare function withoutVaultSignIn<T extends Record<string, string | undefined>>(env: T): T;
|
|
81
|
-
/** The process's environment as the world admits it: what `stripEnv` names stays outside. */
|
|
82
81
|
export declare function admittedEnv(strip: string[] | undefined, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
82
|
+
/** The names a twin of this World was to issue and did not (its door failed): kept out of every command's env, so a
|
|
83
|
+
* caller's own value (a real key in their shell) never stands in for the credential the World meant to issue. */
|
|
84
|
+
export declare const UNISSUED_ENV = "VOLTER_WORLD_UNISSUED";
|
|
83
85
|
export declare function saveWorldInstance(instance: WorldInstance): void;
|
|
84
86
|
/**
|
|
85
87
|
* THE MANAGED-DATABASE BINDING (architecture.md, managed infrastructure: the data plane's declared exception): for a
|
|
@@ -197,12 +199,16 @@ export declare function listWorlds(root?: string): Array<{
|
|
|
197
199
|
degraded?: boolean;
|
|
198
200
|
config?: string;
|
|
199
201
|
}>;
|
|
202
|
+
/** The env with each `$app-url` value made the application URL its booter recorded. Unrecorded, the name is left
|
|
203
|
+
* unset (the application's own env files decide) and the command is told how to record it: never a guess. */
|
|
204
|
+
export declare function withAppUrl<E extends Record<string, string | undefined>>(name: string, root: string, env: E): E;
|
|
200
205
|
/** Runs the command in the world's env in the foreground and resolves to its exit code. The child is
|
|
201
206
|
* awaited, not blocked on: a runtime that runs no synchronous child (the browser engine) still runs
|
|
202
207
|
* this one, and the caller sees no difference — stdio is inherited either way. */
|
|
203
208
|
export declare function runWithWorldEnv(name: string, command: string[], root?: string, options?: {
|
|
204
209
|
cwd?: string;
|
|
205
210
|
verbose?: boolean;
|
|
211
|
+
env?: Record<string, string>;
|
|
206
212
|
}): Promise<number>;
|
|
207
213
|
/** Local CLI attachment with one instance-bound consumer record per concurrent command.
|
|
208
214
|
* The synchronous SDK method remains compatible; its consumers are explicitly untracked. */
|