@celilo/cli 2.2.0 → 2.3.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/CELILO_CORE_MODULES.md +1 -0
- package/CELILO_SUBSYSTEMS.md +1 -1
- package/README.md +1 -1
- package/drizzle/0032_module_jail_policies.sql +28 -0
- package/drizzle/0033_build_bus_hook_runs.sql +37 -0
- package/drizzle/meta/_journal.json +15 -1
- package/package.json +3 -3
- package/schemas/system_config.json +5 -0
- package/src/__integration__/container-services-cli.integration.test.ts +1 -1
- package/src/ansible/inventory.test.ts +2 -1
- package/src/api/sessions.test.ts +5 -4
- package/src/api-clients/proxmox.ts +10 -7
- package/src/cli/backup-rename.test.ts +5 -4
- package/src/cli/cli.test.ts +3 -2
- package/src/cli/commands/console-get-chain.test.ts +2 -1
- package/src/cli/commands/events.test.ts +2 -2
- package/src/cli/commands/firewall-interface-list.test.ts +4 -3
- package/src/cli/commands/machine-list.test.ts +57 -0
- package/src/cli/commands/machine-list.ts +35 -4
- package/src/cli/commands/module-config.test.ts +2 -1
- package/src/cli/commands/module-deploy.ts +8 -2
- package/src/cli/commands/module-generate.test.ts +53 -0
- package/src/cli/commands/module-generate.ts +31 -26
- package/src/cli/commands/module-health.ts +1 -0
- package/src/cli/commands/module-import-aspect.test.ts +2 -1
- package/src/cli/commands/module-import-registry.test.ts +3 -2
- package/src/cli/commands/module-jail.test.ts +242 -0
- package/src/cli/commands/module-jail.ts +227 -0
- package/src/cli/commands/module-list-jail.test.ts +136 -0
- package/src/cli/commands/module-list.ts +30 -3
- package/src/cli/commands/module-operations.test.ts +2 -1
- package/src/cli/commands/module-publish.test.ts +11 -18
- package/src/cli/commands/module-update.test.ts +28 -12
- package/src/cli/commands/module-update.ts +4 -1
- package/src/cli/commands/module-upgrade.test.ts +130 -1
- package/src/cli/commands/module-upgrade.ts +77 -3
- package/src/cli/commands/module-verify.test.ts +3 -4
- package/src/cli/commands/module-verify.ts +0 -1
- package/src/cli/commands/notify-config.test.ts +5 -3
- package/src/cli/commands/publish/execute.ts +4 -1
- package/src/cli/commands/publish/index.ts +11 -1
- package/src/cli/commands/publish/module-registry.test.ts +24 -1
- package/src/cli/commands/publish/module-registry.ts +23 -4
- package/src/cli/commands/publish/plan.ts +1 -1
- package/src/cli/commands/publish/types.ts +7 -0
- package/src/cli/commands/registry-owner.test.ts +3 -2
- package/src/cli/commands/registry-token.test.ts +3 -2
- package/src/cli/commands/system-apply-config-equivalence.test.ts +5 -7
- package/src/cli/commands/system-audit.ts +20 -6
- package/src/cli/commands/system-config.test.ts +148 -0
- package/src/cli/commands/system-config.ts +26 -1
- package/src/cli/commands/system-doctor-remediation-gate.test.ts +295 -0
- package/src/cli/commands/system-doctor.test.ts +143 -6
- package/src/cli/commands/system-doctor.ts +142 -25
- package/src/cli/commands/system-init-deprecation.test.ts +7 -4
- package/src/cli/commands/system-migrate.test.ts +2 -1
- package/src/cli/commands/system-update.ts +5 -0
- package/src/cli/completion.ts +8 -2
- package/src/cli/flag-surface-gate.test.ts +279 -0
- package/src/cli/index.ts +70 -3
- package/src/cli/parser.test.ts +37 -1
- package/src/cli/restore-command.test.ts +5 -4
- package/src/cli/restore-migration-failure.test.ts +4 -3
- package/src/cli/tui/audit-state.ts +2 -0
- package/src/config/paths.test.ts +22 -22
- package/src/db/client.test.ts +46 -1
- package/src/db/client.ts +26 -0
- package/src/db/migrate.test.ts +2 -1
- package/src/db/migrate.ts +16 -16
- package/src/db/schema.ts +63 -0
- package/src/hooks/capability-loader-firewall.test.ts +9 -1
- package/src/hooks/capability-loader.test.ts +78 -0
- package/src/hooks/capability-loader.ts +45 -2
- package/src/hooks/executor.test.ts +161 -1
- package/src/hooks/executor.ts +87 -21
- package/src/hooks/hook-jail-unreachability.test.ts +17 -18
- package/src/hooks/hook-store.test.ts +6 -0
- package/src/hooks/hook-trespass.test.ts +26 -9
- package/src/hooks/jail.test.ts +152 -5
- package/src/hooks/jail.ts +93 -4
- package/src/manifest/contracts/v1.ts +13 -0
- package/src/module/packaging/audit.ts +9 -26
- package/src/module/packaging/build-paths.test.ts +127 -0
- package/src/module/packaging/build-paths.ts +175 -0
- package/src/module/packaging/build.test.ts +71 -1
- package/src/module/packaging/build.ts +60 -0
- package/src/module/packaging/extract.ts +1 -5
- package/src/policy/module-script-scan.ts +51 -107
- package/src/policy/no-hand-built-ssh.test.ts +5 -1
- package/src/policy/no-swallowed-refusal.test.ts +14 -15
- package/src/registry/client.test.ts +2 -2
- package/src/secrets/storage.test.ts +1 -1
- package/src/services/alerting/ack.test.ts +2 -1
- package/src/services/alerting/cadence-migration.test.ts +3 -2
- package/src/services/alerting/coverage-source.test.ts +2 -1
- package/src/services/alerting/deferral.test.ts +2 -1
- package/src/services/alerting/delivery-loop.test.ts +2 -1
- package/src/services/alerting/deploy-hooks.test.ts +2 -1
- package/src/services/alerting/inbound-poller.test.ts +2 -1
- package/src/services/alerting/inbound.test.ts +2 -1
- package/src/services/alerting/keys.test.ts +4 -0
- package/src/services/alerting/keys.ts +12 -2
- package/src/services/alerting/notification-responder.test.ts +2 -1
- package/src/services/alerting/run-monitor.test.ts +2 -1
- package/src/services/alerting/store.test.ts +2 -1
- package/src/services/alerting/sweep-runner.test.ts +2 -1
- package/src/services/alerting/tokens.test.ts +2 -1
- package/src/services/aspect-approvals.test.ts +2 -1
- package/src/services/aspect-reconcile.test.ts +4 -3
- package/src/services/aspect-runner.test.ts +2 -1
- package/src/services/audit/health.test.ts +97 -2
- package/src/services/audit/health.ts +64 -2
- package/src/services/audit/index.test.ts +22 -0
- package/src/services/audit/index.ts +8 -2
- package/src/services/audit/jail-exemptions.test.ts +42 -0
- package/src/services/audit/jail-exemptions.ts +44 -0
- package/src/services/audit/module-integrity.test.ts +23 -22
- package/src/services/audit/module-integrity.ts +7 -16
- package/src/services/audit/types.ts +2 -1
- package/src/services/backup-age-agreement.test.ts +2 -1
- package/src/services/backup-create.ts +9 -0
- package/src/services/backup-envelope-roundtrip.test.ts +3 -2
- package/src/services/backup-in-flight-refusal.test.ts +3 -2
- package/src/services/build-bus/hook-dispatch-mgmt.test.ts +48 -0
- package/src/services/build-bus/hook-dispatcher.ts +49 -1
- package/src/services/bus-ensure-flow.test.ts +3 -2
- package/src/services/bus-interview-park.test.ts +4 -3
- package/src/services/bus-interview.test.ts +2 -2
- package/src/services/bus-secret-flow.test.ts +3 -2
- package/src/services/capability-table-rows.test.ts +2 -1
- package/src/services/celilo-events.test.ts +1 -1
- package/src/services/celilo-mgmt-hooks.test.ts +23 -5
- package/src/services/consumer-cleanup.test.ts +3 -2
- package/src/services/container-service.test.ts +3 -2
- package/src/services/control-plane-bootstrap.test.ts +2 -1
- package/src/services/cross-module-read.test.ts +4 -3
- package/src/services/deploy-preflight.ts +7 -0
- package/src/services/deploy-validation.test.ts +3 -2
- package/src/services/deployed-systems.ts +1 -1
- package/src/services/dns-internal-records.test.ts +3 -2
- package/src/services/dns-provider-backfill.test.ts +2 -1
- package/src/services/dns-registrations.test.ts +2 -1
- package/src/services/ensure-interview.test.ts +3 -2
- package/src/services/fleet-checks.test.ts +126 -6
- package/src/services/fleet-checks.ts +176 -3
- package/src/services/fleet-key.test.ts +3 -2
- package/src/services/health-runner.test.ts +87 -2
- package/src/services/health-runner.ts +40 -11
- package/src/services/infrastructure-selector.test.ts +3 -2
- package/src/services/infrastructure-variable-resolver.test.ts +2 -1
- package/src/services/jail-exemptions.test.ts +125 -0
- package/src/services/jail-exemptions.ts +81 -0
- package/src/services/machine-pool.test.ts +3 -2
- package/src/services/module-config.test.ts +2 -1
- package/src/services/module-deploy.dns-repoint.test.ts +2 -1
- package/src/services/module-deploy.ts +48 -24
- package/src/services/module-operations.test.ts +2 -1
- package/src/services/module-subscriptions.test.ts +4 -3
- package/src/services/network-discovery.test.ts +64 -1
- package/src/services/network-discovery.ts +30 -5
- package/src/services/port-forwards.test.ts +2 -1
- package/src/services/programmatic-responder.aspect.test.ts +2 -1
- package/src/services/proxmox-reconcile.test.ts +2 -1
- package/src/services/responder-probe.test.ts +1 -1
- package/src/services/restore-from-file.test.ts +7 -6
- package/src/services/restore-preflight.test.ts +3 -2
- package/src/services/secret-schema-loader.test.ts +2 -1
- package/src/services/ssh-key-manager.test.ts +4 -3
- package/src/services/static-content-converge.test.ts +5 -4
- package/src/services/static-content-converge.ts +33 -10
- package/src/services/system-state-stage.test.ts +4 -3
- package/src/services/trusted-sources.test.ts +3 -2
- package/src/services/update/orchestrator.test.ts +2 -0
- package/src/services/zone-detector.test.ts +2 -1
- package/src/test-utils/bus-responder.ts +6 -4
- package/src/test-utils/db-path.ts +25 -0
- package/src/test-utils/integration.ts +7 -0
- package/src/test-utils/module-fixtures.ts +5 -6
- package/src/module/packaging/generated-plane.test.ts +0 -79
- package/src/module/packaging/generated-plane.ts +0 -134
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Publish-time gate on a module build command's filesystem references.
|
|
6
|
+
*
|
|
7
|
+
* A `build:` block runs when the module is packaged (publish/package/import),
|
|
8
|
+
* inside a staged copy of the module, with `CELILO_MODULE_SOURCE_DIR` pointing
|
|
9
|
+
* back at the authored source tree. A `cd` in that command is the build's
|
|
10
|
+
* claim about where its inputs live; a target that does not resolve there
|
|
11
|
+
* means the module ships inputs it does not carry, which is how celilo-registry
|
|
12
|
+
* was unbuildable on every real install for four months without anyone finding
|
|
13
|
+
* out (celilo#1307). The packager refuses up front and names the path.
|
|
14
|
+
*
|
|
15
|
+
* The gate does not interpret shell. It finds `cd` commands lexically, knows
|
|
16
|
+
* the two variables the packager supplies, and probes what those resolve to.
|
|
17
|
+
* A target it cannot resolve (an untracked shell variable, a command
|
|
18
|
+
* substitution, a glob) is SKIPPED, not failed: guessing would reject working
|
|
19
|
+
* modules, and a guard that rejects working modules is worse than none.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface BuildPathViolation {
|
|
23
|
+
/** The path exactly as the build command wrote it, before substitution. */
|
|
24
|
+
rawPath: string;
|
|
25
|
+
/** The absolute path the packaging runtime would resolve it to. */
|
|
26
|
+
resolvedPath: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ResolveBuildCommandPathsOptions {
|
|
30
|
+
/** What `$CELILO_MODULE_SOURCE_DIR` resolves to when the command runs. */
|
|
31
|
+
moduleSourceDir: string;
|
|
32
|
+
/** The staged tree the command runs in (its initial cwd, and `$STAGE`). */
|
|
33
|
+
buildDir: string;
|
|
34
|
+
/** Injectable existence probe; defaults to existsSync. */
|
|
35
|
+
exists?: (path: string) => boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract the argument of every unquoted `cd` command in the string, with the
|
|
40
|
+
* quoting preserved so later substitution can strip it.
|
|
41
|
+
*
|
|
42
|
+
* A `cd` counts only at a command position: string start, or after `&&`, `||`,
|
|
43
|
+
* `;`, `|`, `(`, or a newline, ignoring whitespace. Quoted spans (single and
|
|
44
|
+
* double) are skipped whole, so a `cd` inside a string is not mistaken for a
|
|
45
|
+
* command.
|
|
46
|
+
*/
|
|
47
|
+
export function findCdTargets(command: string): string[] {
|
|
48
|
+
const targets: string[] = [];
|
|
49
|
+
let atCommandStart = true;
|
|
50
|
+
let i = 0;
|
|
51
|
+
while (i < command.length) {
|
|
52
|
+
const ch = command[i];
|
|
53
|
+
if (ch === "'") {
|
|
54
|
+
const end = command.indexOf("'", i + 1);
|
|
55
|
+
i = end === -1 ? command.length : end + 1;
|
|
56
|
+
atCommandStart = false;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ch === '"') {
|
|
60
|
+
const end = command.indexOf('"', i + 1);
|
|
61
|
+
i = end === -1 ? command.length : end + 1;
|
|
62
|
+
atCommandStart = false;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === '\\' && i + 1 < command.length) {
|
|
66
|
+
i += 2;
|
|
67
|
+
atCommandStart = false;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === '&' || ch === '|' || ch === ';' || ch === '\n' || ch === '(') {
|
|
71
|
+
atCommandStart = true;
|
|
72
|
+
i += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (/\s/.test(ch)) {
|
|
76
|
+
i += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (atCommandStart && command.startsWith('cd', i) && /\s/.test(command[i + 2] ?? '')) {
|
|
80
|
+
const { target, next } = readCdTarget(command, i + 2);
|
|
81
|
+
if (target.length > 0) targets.push(target);
|
|
82
|
+
i = next;
|
|
83
|
+
atCommandStart = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
atCommandStart = false;
|
|
87
|
+
i += 1;
|
|
88
|
+
}
|
|
89
|
+
return targets;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Read one `cd` argument starting just after the `cd` keyword. Stops at an
|
|
94
|
+
* unquoted command terminator. Backslash escapes are carried through so the
|
|
95
|
+
* caller sees exactly what the shell would.
|
|
96
|
+
*/
|
|
97
|
+
function readCdTarget(command: string, start: number): { target: string; next: number } {
|
|
98
|
+
let i = start;
|
|
99
|
+
while (i < command.length && /\s/.test(command[i])) i += 1;
|
|
100
|
+
const begin = i;
|
|
101
|
+
let end = i;
|
|
102
|
+
while (i < command.length) {
|
|
103
|
+
const ch = command[i];
|
|
104
|
+
if (ch === "'") {
|
|
105
|
+
const close = command.indexOf("'", i + 1);
|
|
106
|
+
i = close === -1 ? command.length : close + 1;
|
|
107
|
+
end = i;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (ch === '"') {
|
|
111
|
+
const close = command.indexOf('"', i + 1);
|
|
112
|
+
i = close === -1 ? command.length : close + 1;
|
|
113
|
+
end = i;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (ch === '\\' && i + 1 < command.length) {
|
|
117
|
+
i += 2;
|
|
118
|
+
end = i;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (ch === '&' || ch === '|' || ch === ';' || ch === '\n') break;
|
|
122
|
+
i += 1;
|
|
123
|
+
if (!/\s/.test(ch) || command.slice(begin, i).trim().length > 0) end = i;
|
|
124
|
+
}
|
|
125
|
+
return { target: command.slice(begin, end).trim(), next: i };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Substitute the packager-supplied variables, strip quoting, and drop the
|
|
130
|
+
* target to `null` when anything unresolvable remains. `null` means "this
|
|
131
|
+
* gate cannot answer whether this path exists", never "it does not exist".
|
|
132
|
+
*/
|
|
133
|
+
function resolveTarget(target: string, options: ResolveBuildCommandPathsOptions): string | null {
|
|
134
|
+
const substituted = target
|
|
135
|
+
.replaceAll('$CELILO_MODULE_SOURCE_DIR', options.moduleSourceDir)
|
|
136
|
+
.replaceAll('$(pwd)', options.buildDir)
|
|
137
|
+
.replaceAll('$STAGE', options.buildDir)
|
|
138
|
+
.replaceAll('"', '')
|
|
139
|
+
.replaceAll("'", '');
|
|
140
|
+
if (/[$`*?~[(]/.test(substituted)) return null;
|
|
141
|
+
if (substituted.startsWith('-')) return null; // `cd -` / flags, not a path
|
|
142
|
+
return substituted;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function resolveBuildCommandPaths(
|
|
146
|
+
command: string,
|
|
147
|
+
options: ResolveBuildCommandPathsOptions,
|
|
148
|
+
): BuildPathViolation[] {
|
|
149
|
+
const exists = options.exists ?? existsSync;
|
|
150
|
+
const violations: BuildPathViolation[] = [];
|
|
151
|
+
// The simulated working directory of the build. `null` means unknown: the
|
|
152
|
+
// previous cd could not be resolved, so a later relative cd cannot be either.
|
|
153
|
+
let cwd: string | null = options.buildDir;
|
|
154
|
+
for (const rawTarget of findCdTargets(command)) {
|
|
155
|
+
const target = resolveTarget(rawTarget, options);
|
|
156
|
+
if (target === null) {
|
|
157
|
+
cwd = null;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const resolved: string | null = target.startsWith('/')
|
|
161
|
+
? target
|
|
162
|
+
: cwd === null
|
|
163
|
+
? null
|
|
164
|
+
: resolve(cwd, target);
|
|
165
|
+
if (resolved === null) {
|
|
166
|
+
cwd = null;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!exists(resolved)) {
|
|
170
|
+
violations.push({ rawPath: rawTarget, resolvedPath: resolved });
|
|
171
|
+
}
|
|
172
|
+
cwd = resolved;
|
|
173
|
+
}
|
|
174
|
+
return violations;
|
|
175
|
+
}
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
10
11
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
12
|
import { tmpdir } from 'node:os';
|
|
12
13
|
import { join } from 'node:path';
|
|
13
|
-
import { computeChecksums } from './build';
|
|
14
|
+
import { buildModule, computeChecksums } from './build';
|
|
14
15
|
|
|
15
16
|
describe('computeChecksums — scripts/node_modules bundling (ISS-0046)', () => {
|
|
16
17
|
let dir: string;
|
|
@@ -73,3 +74,72 @@ describe('computeChecksums — scripts/node_modules bundling (ISS-0046)', () =>
|
|
|
73
74
|
expect(paths.some((p) => p.startsWith('e2e/'))).toBe(false);
|
|
74
75
|
});
|
|
75
76
|
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The pack-staging branch must not eat the hook runtime (celilo#1310).
|
|
80
|
+
*
|
|
81
|
+
* `buildModule` stages through `bun pm pack` whenever a module has a root
|
|
82
|
+
* package.json, and npm-pack semantics drop every `node_modules` — a `files`
|
|
83
|
+
* entry naming the path does not override it. That silently contradicted
|
|
84
|
+
* `includeNodeModulesPath`, which exists to ship `scripts/node_modules` in full
|
|
85
|
+
* so hooks resolve their deps on a target with no reachable registry.
|
|
86
|
+
*
|
|
87
|
+
* Nothing in `modules/` can reach this branch: every module there has only
|
|
88
|
+
* `scripts/package.json` and takes the `cpSync` path. So the regression is only
|
|
89
|
+
* visible against a fixture shaped like a module that is ALSO a bun project.
|
|
90
|
+
*/
|
|
91
|
+
describe('buildModule — a root package.json must not strip the hook runtime', () => {
|
|
92
|
+
let source: string;
|
|
93
|
+
let out: string;
|
|
94
|
+
|
|
95
|
+
function write(rel: string, content: string): void {
|
|
96
|
+
const full = join(source, rel);
|
|
97
|
+
mkdirSync(join(full, '..'), { recursive: true });
|
|
98
|
+
writeFileSync(full, content, 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
source = mkdtempSync(join(tmpdir(), 'celilo-pack-root-pkg-'));
|
|
103
|
+
out = join(mkdtempSync(join(tmpdir(), 'celilo-pack-out-')), 'm.netapp');
|
|
104
|
+
|
|
105
|
+
write('manifest.yml', 'celilo_contract: "1.0"\nid: root-pkg-module\nname: M\nversion: 0.1.0\n');
|
|
106
|
+
// The shape that triggers it: a module that is also a bun project. `files`
|
|
107
|
+
// deliberately names scripts/ — proving the loss is npm-pack semantics and
|
|
108
|
+
// not a missing entry.
|
|
109
|
+
write(
|
|
110
|
+
'package.json',
|
|
111
|
+
JSON.stringify({
|
|
112
|
+
name: 'root-pkg-module',
|
|
113
|
+
version: '0.1.0',
|
|
114
|
+
private: true,
|
|
115
|
+
files: ['manifest.yml', 'scripts/'],
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
write('scripts/on_install.ts', "import '@celilo/capabilities';\n");
|
|
119
|
+
write(
|
|
120
|
+
'scripts/package.json',
|
|
121
|
+
JSON.stringify({ name: 'm-hooks', dependencies: { tldts: '^7.0.0' } }),
|
|
122
|
+
);
|
|
123
|
+
write('scripts/node_modules/tldts/index.js', 'module.exports = {};\n');
|
|
124
|
+
write(
|
|
125
|
+
'scripts/node_modules/tldts/package.json',
|
|
126
|
+
JSON.stringify({ name: 'tldts', version: '7.4.12' }),
|
|
127
|
+
);
|
|
128
|
+
write('scripts/node_modules/.bin/tldts', '#!/bin/sh\n');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
afterEach(() => {
|
|
132
|
+
rmSync(source, { recursive: true, force: true });
|
|
133
|
+
rmSync(join(out, '..'), { recursive: true, force: true });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('ships scripts/node_modules even though bun pm pack dropped it', async () => {
|
|
137
|
+
const result = await buildModule({ sourceDir: source, outputPath: out });
|
|
138
|
+
expect(result.success).toBe(true);
|
|
139
|
+
|
|
140
|
+
const listed = execFileSync('tar', ['tzf', out], { encoding: 'utf-8' }).split('\n');
|
|
141
|
+
expect(listed).toContain('scripts/node_modules/tldts/index.js');
|
|
142
|
+
// The one thing the runtime closure still excludes.
|
|
143
|
+
expect(listed).not.toContain('scripts/node_modules/.bin/tldts');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -9,6 +9,7 @@ import { parse as parseYaml } from 'yaml';
|
|
|
9
9
|
import { log } from '../../cli/prompts';
|
|
10
10
|
import { formatViolations, scanModuleDirectory } from '../../policy/module-script-scan';
|
|
11
11
|
import { validateModuleDirectory } from '../import';
|
|
12
|
+
import { resolveBuildCommandPaths } from './build-paths';
|
|
12
13
|
import { computeFileChecksum } from './checksum';
|
|
13
14
|
import { classifyModulePath, includeNodeModulesPath } from './package-rules';
|
|
14
15
|
import { signChecksums } from './signature';
|
|
@@ -23,6 +24,37 @@ export interface ChecksumsData {
|
|
|
23
24
|
files: Record<string, string>; // filepath -> xxhash
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Put `scripts/node_modules` back after a `bun pm pack` staging.
|
|
29
|
+
*
|
|
30
|
+
* `bun pm pack` drops every `node_modules` unconditionally — that is npm-pack
|
|
31
|
+
* semantics, and a `files` entry naming the path does not override it. But the
|
|
32
|
+
* packager is explicit downstream (`includeNodeModulesPath`) that
|
|
33
|
+
* `scripts/node_modules` is the module's HOOK RUNTIME and ships in full, so
|
|
34
|
+
* hooks resolve their third-party deps on a target with no reachable registry.
|
|
35
|
+
*
|
|
36
|
+
* Those two rules disagreed, and which one won was decided by whether a module
|
|
37
|
+
* happened to have a root `package.json`. Every module in `modules/` has only
|
|
38
|
+
* `scripts/package.json`, so all of them take the `cpSync` branch below and
|
|
39
|
+
* keep their runtime. A module that is ALSO a bun project — a build script, a
|
|
40
|
+
* data pipeline, its own tests — takes this branch instead and shipped without
|
|
41
|
+
* one. Nothing caught it: package, publish, import and deploy all succeed, and
|
|
42
|
+
* the first symptom is the hook dying on the target with
|
|
43
|
+
* `Cannot find package '@celilo/capabilities'` (celilo#1310).
|
|
44
|
+
*
|
|
45
|
+
* The hook runtime is not build input, so `files` has no business filtering it.
|
|
46
|
+
* Symlinks are materialised for the same reason the `cpSync` branch does it: a
|
|
47
|
+
* `file:` workspace link would point at a path the target does not have.
|
|
48
|
+
*/
|
|
49
|
+
function stageHookRuntime(sourceDir: string, buildDir: string): void {
|
|
50
|
+
const from = join(sourceDir, 'scripts', 'node_modules');
|
|
51
|
+
if (!existsSync(from)) return;
|
|
52
|
+
cpSync(from, join(buildDir, 'scripts', 'node_modules'), {
|
|
53
|
+
recursive: true,
|
|
54
|
+
dereference: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
26
58
|
/**
|
|
27
59
|
* Module build options
|
|
28
60
|
*/
|
|
@@ -215,6 +247,7 @@ export async function buildModule(options: ModuleBuildOptions): Promise<ModuleBu
|
|
|
215
247
|
timeout: 60_000,
|
|
216
248
|
});
|
|
217
249
|
rmSync(tarballs[0], { force: true });
|
|
250
|
+
stageHookRuntime(sourceDir, buildDir);
|
|
218
251
|
} catch (err) {
|
|
219
252
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
220
253
|
return {
|
|
@@ -258,6 +291,33 @@ export async function buildModule(options: ModuleBuildOptions): Promise<ModuleBu
|
|
|
258
291
|
if (manifest.build?.command || manifest.build?.script) {
|
|
259
292
|
log.info(`Building module (${manifest.build.command ? 'command' : 'script'})...`);
|
|
260
293
|
|
|
294
|
+
// Publish-time gate on the build command's paths (D3 of
|
|
295
|
+
// control-plane-stops-building-modules). A `cd` whose target does not
|
|
296
|
+
// resolve where the command runs means the module depends on inputs it
|
|
297
|
+
// does not carry. celilo-registry did exactly this for four months
|
|
298
|
+
// (`cd ../../packages/registry-server`) and the failure surfaced on a
|
|
299
|
+
// production control plane, months after publish, as a /bin/sh error.
|
|
300
|
+
// Refuse here, at the only point where a human is watching, and name the
|
|
301
|
+
// path. Targets the gate cannot resolve (untracked shell variables,
|
|
302
|
+
// command substitution) are skipped, never guessed at.
|
|
303
|
+
if (manifest.build.command) {
|
|
304
|
+
const pathViolations = resolveBuildCommandPaths(manifest.build.command, {
|
|
305
|
+
moduleSourceDir: sourceDir,
|
|
306
|
+
buildDir,
|
|
307
|
+
});
|
|
308
|
+
if (pathViolations.length > 0) {
|
|
309
|
+
return {
|
|
310
|
+
success: false,
|
|
311
|
+
error: [
|
|
312
|
+
`Refusing to build ${moduleId}: the build command references a path that does not exist where the command runs.`,
|
|
313
|
+
...pathViolations.map((v) => ` ${v.rawPath}\n resolves to: ${v.resolvedPath}`),
|
|
314
|
+
'',
|
|
315
|
+
'A build input that does not resolve at publish time makes the module unbuildable wherever it is installed. Fix the path in manifest.yml (keep it module-local, or reach the monorepo through $CELILO_MODULE_SOURCE_DIR) and re-publish.',
|
|
316
|
+
].join('\n'),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
261
321
|
// Resolve `workspace:` build deps (ISS-0147 / celilo#216). The staged
|
|
262
322
|
// buildDir has no workspace root, so a sibling monorepo package pulled in
|
|
263
323
|
// via `workspace:^` won't resolve during the build's `bun install`.
|
|
@@ -19,12 +19,8 @@ export interface IntegrityViolation {
|
|
|
19
19
|
* being old rather than by the files having changed. Only `auditModule`
|
|
20
20
|
* produces it; package verification compares a package to its own manifest
|
|
21
21
|
* and cannot be stale in this sense.
|
|
22
|
-
*
|
|
23
|
-
* `stale-generated` is the second plane (D3): a verbatim role asset in
|
|
24
|
-
* `generated/` whose bytes are not the installed module's. That is what
|
|
25
|
-
* celilo#925 was, and what would ship on the next deploy.
|
|
26
22
|
*/
|
|
27
|
-
type: 'missing' | 'modified' | 'extra' | 'stale-baseline'
|
|
23
|
+
type: 'missing' | 'modified' | 'extra' | 'stale-baseline';
|
|
28
24
|
/**
|
|
29
25
|
* What the baseline says the file should hash to, and what it actually
|
|
30
26
|
* hashes to. Both optional because not every violation is about a digest —
|
|
@@ -82,28 +82,36 @@ export const SWALLOWED_REFUSAL_RULE = 'swallowed framework refusal';
|
|
|
82
82
|
*/
|
|
83
83
|
const CLI_SPAWN_RE =
|
|
84
84
|
/\b(?:execSync|execFileSync|execFile|exec|spawnSync|spawn)\s*\(\s*(?:['"`]\s*celilo(?=\s|['"`])|`[^`]*\bcelilo(?=\s|`))/;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The spawn rule runs against the WHOLE FILE, not one line at a time. The
|
|
88
|
+
* per-line pass above could not see a call whose argv wrapped onto the next
|
|
89
|
+
* line — `execFileSync(\n 'celilo', …` — and that blind spot was live:
|
|
90
|
+
* celilo-registry setup.ts carried a four-line spawn and iptables
|
|
91
|
+
* firewall-functions.ts three more, all scanning clean while the gate was
|
|
92
|
+
* green (found by ce-y0we's reach check on 2026-09-09). The regex itself
|
|
93
|
+
* needs no change — `\s*` already crosses newlines, and `[^\`]*` never
|
|
94
|
+
* matches a backtick so a template cannot swallow the rest of the file — it
|
|
95
|
+
* just has to be applied to text wider than a line. Line numbers come from
|
|
96
|
+
* the match index, so reports still name the line an author fixes.
|
|
97
|
+
*/
|
|
98
|
+
const CLI_SPAWN_BLOCK_RE = new RegExp(CLI_SPAWN_RE.source, 'g');
|
|
99
|
+
|
|
100
|
+
/** Same wording the rule carried when it ran per line; moved with the pass. */
|
|
101
|
+
const CLI_SPAWN_HINT =
|
|
102
|
+
'A jailed hook has no celilo binary and no shell, so this dies with ' +
|
|
103
|
+
'"Executable not found in $PATH" (celilo#1225). Replace the spawn with an ' +
|
|
104
|
+
'injected capability — context.secrets/context.config for self-config and ' +
|
|
105
|
+
'self-secrets (openspec/changes/hook-owned-state) — or move the operation ' +
|
|
106
|
+
'into the framework the way celilo-mgmt on_install did (5e0e624d).';
|
|
107
|
+
|
|
85
108
|
const FRAMEWORK_CALL_RE = new RegExp(`${CLI_SPAWN_RE.source}|\\brunCelilo\\s*\\(`);
|
|
86
109
|
|
|
87
110
|
const PATTERN_RULES: Array<{ rule: string; re: RegExp; hint: string }> = [
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// argv[0] of a spawn-family call ('celilo' / "celilo"), or inside a shell
|
|
93
|
-
// string passed to one, where celilo is followed by a word (a subcommand)
|
|
94
|
-
// or the closing quote — so `docker exec celilo-mgr …`, a `celilo.db` file
|
|
95
|
-
// name, and prose that merely names the CLI do not fire. What it
|
|
96
|
-
// deliberately cannot see: a binary path resolved into a variable, or a
|
|
97
|
-
// spawn reached through an indirection. Same honesty class as the
|
|
98
|
-
// namespace rule — a lint, not a boundary.
|
|
99
|
-
re: CLI_SPAWN_RE,
|
|
100
|
-
hint:
|
|
101
|
-
'A jailed hook has no celilo binary and no shell, so this dies with ' +
|
|
102
|
-
'"Executable not found in $PATH" (celilo#1225). Replace the spawn with an ' +
|
|
103
|
-
'injected capability — context.secrets/context.config for self-config and ' +
|
|
104
|
-
'self-secrets (openspec/changes/hook-owned-state) — or move the operation ' +
|
|
105
|
-
'into the framework the way celilo-mgmt on_install did (5e0e624d).',
|
|
106
|
-
},
|
|
111
|
+
// NOTE: the jailed-CLI-spawn rule is NOT in this array — it runs in its own
|
|
112
|
+
// whole-file pass in scanModuleScriptSource (CLI_SPAWN_BLOCK_RE), because a
|
|
113
|
+
// per-line match missed every spawn whose argv wrapped. Everything else
|
|
114
|
+
// stays per-line.
|
|
107
115
|
{
|
|
108
116
|
rule: 'raw ssh invocation (StrictHostKeyChecking)',
|
|
109
117
|
re: /StrictHostKeyChecking/,
|
|
@@ -200,6 +208,20 @@ export function scanModuleScriptSource(file: string, source: string): ScanViolat
|
|
|
200
208
|
const violations: ScanViolation[] = [];
|
|
201
209
|
const lines = source.split('\n');
|
|
202
210
|
|
|
211
|
+
// The jailed-CLI-spawn pass: whole-file, so a spawn whose argv wraps onto
|
|
212
|
+
// the next line is still seen (see CLI_SPAWN_BLOCK_RE for the blind spot
|
|
213
|
+
// that motivated this). Reported on the line the CALL starts, because that
|
|
214
|
+
// is the site an author fixes.
|
|
215
|
+
CLI_SPAWN_BLOCK_RE.lastIndex = 0;
|
|
216
|
+
for (let m = CLI_SPAWN_BLOCK_RE.exec(source); m !== null; m = CLI_SPAWN_BLOCK_RE.exec(source)) {
|
|
217
|
+
violations.push({
|
|
218
|
+
file,
|
|
219
|
+
line: source.slice(0, m.index).split('\n').length,
|
|
220
|
+
rule: JAILED_CLI_SPAWN_RULE,
|
|
221
|
+
hint: CLI_SPAWN_HINT,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
203
225
|
lines.forEach((text, i) => {
|
|
204
226
|
for (const { rule, re, hint } of PATTERN_RULES) {
|
|
205
227
|
if (re.test(text)) violations.push({ file, line: i + 1, rule, hint });
|
|
@@ -337,85 +359,19 @@ export interface PolicyDebtEntry {
|
|
|
337
359
|
* the test goes red, and the entry must be deleted in the same change.
|
|
338
360
|
*/
|
|
339
361
|
export const JAILED_CLI_SPAWN_DEBT: readonly PolicyDebtEntry[] = [
|
|
340
|
-
{
|
|
341
|
-
module: 'celilo-mgmt',
|
|
342
|
-
file: 'scripts/on_backup.ts',
|
|
343
|
-
matches: 1,
|
|
344
|
-
reason:
|
|
345
|
-
'hook-owned-state 5.5 / survey class G: machine list becomes a machine-pool read capability',
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
module: 'caddy-internal',
|
|
349
|
-
file: 'scripts/private-web-functions.ts',
|
|
350
|
-
matches: 1,
|
|
351
|
-
reason: 'hook-owned-state 5.5 (routes, capability-owned-tables Group B1)',
|
|
352
|
-
},
|
|
353
362
|
{
|
|
354
363
|
module: 'dnsmasq-dhcp',
|
|
355
364
|
file: 'scripts/dhcp-server-functions.ts',
|
|
356
365
|
matches: 1,
|
|
357
366
|
reason:
|
|
358
|
-
'hook
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
module: 'forgejo',
|
|
362
|
-
file: 'scripts/on-consumer-removed.ts',
|
|
363
|
-
matches: 1,
|
|
364
|
-
reason: 'hook-owned-state 5.5 (runner_registrations, Group B3)',
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
module: 'forgejo',
|
|
368
|
-
file: 'scripts/setup.ts',
|
|
369
|
-
matches: 1,
|
|
370
|
-
reason: 'hook-owned-state 5.5 (secret set)',
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
module: 'forgejo',
|
|
374
|
-
file: 'scripts/source-forge-functions.ts',
|
|
375
|
-
matches: 1,
|
|
376
|
-
reason:
|
|
377
|
-
'hook-owned-state 5.5 shape (self-config write); not in its 10-site table — reconcile there',
|
|
367
|
+
'capability provider, no hook entry imports it — ce-mqay (jail does not constrain it; conversion is dedup, not breakage)',
|
|
378
368
|
},
|
|
379
369
|
{
|
|
380
|
-
module: '
|
|
381
|
-
file: 'scripts/
|
|
382
|
-
matches: 1,
|
|
383
|
-
reason: 'hook-owned-state 5.5 (publications, Group B4)',
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
module: 'generic-cpanel-hosting-provider',
|
|
387
|
-
file: 'scripts/publish-functions.ts',
|
|
388
|
-
matches: 1,
|
|
389
|
-
reason:
|
|
390
|
-
'hook-owned-state 5.5 shape (self-config write); not in its 10-site table — reconcile there',
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
module: 'wireguard',
|
|
394
|
-
file: 'scripts/control-plane-vpn-functions.ts',
|
|
395
|
-
matches: 1,
|
|
396
|
-
reason:
|
|
397
|
-
'hook-owned-state 5.5 shape (self-config write); not in its 10-site table — reconcile there',
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
module: 'wireguard',
|
|
401
|
-
file: 'scripts/health-check.ts',
|
|
402
|
-
matches: 1,
|
|
403
|
-
reason:
|
|
404
|
-
'survey class C: system config get becomes an injected read; not in hook-owned-state 5.5 — reconcile there',
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
module: 'wireguard',
|
|
408
|
-
file: 'scripts/on_install.ts',
|
|
409
|
-
matches: 1,
|
|
410
|
-
reason:
|
|
411
|
-
'hook-owned-state 5.5 (registered_peers, Group B2); THE five-suite blocker per the wave-0 census',
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
module: 'wireguard-manager',
|
|
415
|
-
file: 'scripts/setup.ts',
|
|
370
|
+
module: 'iptables',
|
|
371
|
+
file: 'scripts/firewall-functions.ts',
|
|
416
372
|
matches: 3,
|
|
417
373
|
reason:
|
|
418
|
-
'hook-
|
|
374
|
+
'ce-mqay: capability provider, no hook entry imports it. Two config-set writes and one events emit. INVISIBLE to the scan until ce-y0we taught it continuation lines (2026-09-09) — these three were live spawns the green gate could not see',
|
|
419
375
|
},
|
|
420
376
|
];
|
|
421
377
|
|
|
@@ -427,25 +383,13 @@ export const JAILED_CLI_SPAWN_DEBT: readonly PolicyDebtEntry[] = [
|
|
|
427
383
|
* same guarded rot: when a site converts, the count drops, the debt test goes
|
|
428
384
|
* red, and the entry must be deleted in the same change.
|
|
429
385
|
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
386
|
+
* EMPTY as of ce-y0we (2026-09-09): knot-unbound-internal's on-install dropped
|
|
387
|
+
* its last swallow on 2026-09-07 (ce-pvii), wireguard's health-check converted
|
|
388
|
+
* the same evening (01575380), and celilo-mgmt's on_backup machine-pool read
|
|
389
|
+
* retired the last entry. A NEW swallowed refusal must never land here — it
|
|
390
|
+
* gets fixed, not tracked.
|
|
433
391
|
*/
|
|
434
|
-
export const SWALLOWED_REFUSAL_DEBT: readonly PolicyDebtEntry[] = [
|
|
435
|
-
{
|
|
436
|
-
module: 'celilo-mgmt',
|
|
437
|
-
file: 'scripts/on_backup.ts',
|
|
438
|
-
matches: 1,
|
|
439
|
-
reason:
|
|
440
|
-
'hook-owned-state 5.5 / survey class G: machine list becomes a typed-absent machine-pool read',
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
module: 'wireguard',
|
|
444
|
-
file: 'scripts/health-check.ts',
|
|
445
|
-
matches: 1,
|
|
446
|
-
reason: 'survey class C: system config get becomes an injected typed-absent read',
|
|
447
|
-
},
|
|
448
|
-
];
|
|
392
|
+
export const SWALLOWED_REFUSAL_DEBT: readonly PolicyDebtEntry[] = [];
|
|
449
393
|
|
|
450
394
|
/**
|
|
451
395
|
* The files inside `@celilo/capabilities` that implement the remote-exec
|
|
@@ -85,7 +85,11 @@ describe('recurrence gate: the jailed-CLI-spawn debt list is honest', () => {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test('the debt list is non-trivial (the guard actually reached the entries)', () => {
|
|
88
|
-
|
|
88
|
+
// Was >5 while the list still carried the hook-owned-state 5.5 batch. The
|
|
89
|
+
// conversions retire entries faster than new debt arrives (ce-y0we), so
|
|
90
|
+
// the floor is now "at least one tracked site" — enough to prove the test
|
|
91
|
+
// reached the list without pinning the retirement schedule.
|
|
92
|
+
expect(JAILED_CLI_SPAWN_DEBT.length).toBeGreaterThan(0);
|
|
89
93
|
});
|
|
90
94
|
});
|
|
91
95
|
|
|
@@ -212,9 +212,9 @@ describe('recurrence gate: a hook does not swallow a refused framework call', ()
|
|
|
212
212
|
}
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
test('today’s tree:
|
|
215
|
+
test('today’s tree: no swallowed refusals anywhere, and the scan still reaches every module', () => {
|
|
216
216
|
// Raw scan, deliberately NOT scanModuleDirectory: the debt list below
|
|
217
|
-
// exempts
|
|
217
|
+
// exempts the same files so the merged gate stays green while
|
|
218
218
|
// hook-owned-state converts them, and this assertion has to outlive that
|
|
219
219
|
// exemption to prove the rule still reaches them.
|
|
220
220
|
const violations = moduleDirs().flatMap((d) =>
|
|
@@ -225,15 +225,11 @@ describe('recurrence gate: a hook does not swallow a refused framework call', ()
|
|
|
225
225
|
),
|
|
226
226
|
);
|
|
227
227
|
const sites = violations.map((v) => v.file.replace(`${repoRoot()}/`, '')).sort();
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// on-
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
expect(sites).toEqual([
|
|
234
|
-
'modules/celilo-mgmt/scripts/on_backup.ts',
|
|
235
|
-
'modules/wireguard/scripts/health-check.ts',
|
|
236
|
-
]);
|
|
228
|
+
// Empty since ce-y0we (2026-09-09): on_backup.ts:52 was the last site —
|
|
229
|
+
// its machine-pool read is staged input now — and the two before it
|
|
230
|
+
// converted on 2026-09-07 (ce-pvii and 01575380). A NEW site appearing
|
|
231
|
+
// here is the next instance this scan exists to stop.
|
|
232
|
+
expect(sites, `Module script policy violations:\n${formatViolations(violations)}`).toEqual([]);
|
|
237
233
|
});
|
|
238
234
|
});
|
|
239
235
|
|
|
@@ -257,9 +253,12 @@ describe('recurrence gate: the swallow debt list is honest', () => {
|
|
|
257
253
|
}
|
|
258
254
|
});
|
|
259
255
|
|
|
260
|
-
test('the debt list is
|
|
261
|
-
// The list held three entries
|
|
262
|
-
//
|
|
263
|
-
|
|
256
|
+
test('the debt list is retired (empty), and this test pins that fact', () => {
|
|
257
|
+
// The list held three entries when the gate landed; ce-pvii and 01575380
|
|
258
|
+
// converted two on 2026-09-07 and ce-y0we retired the last (on_backup's
|
|
259
|
+
// machine-pool read) on 2026-09-09. Kept as an explicit pin so a future
|
|
260
|
+
// entry has to be justified against this sentence: a swallowed refusal is
|
|
261
|
+
// a defect to fix, never debt to track.
|
|
262
|
+
expect(SWALLOWED_REFUSAL_DEBT).toEqual([]);
|
|
264
263
|
});
|
|
265
264
|
});
|
|
@@ -48,14 +48,14 @@ describe('RegistryClient constructor', () => {
|
|
|
48
48
|
|
|
49
49
|
afterEach(() => {
|
|
50
50
|
if (origEnv === undefined) {
|
|
51
|
-
process.env.CELILO_REGISTRY_URL
|
|
51
|
+
delete process.env.CELILO_REGISTRY_URL;
|
|
52
52
|
} else {
|
|
53
53
|
process.env.CELILO_REGISTRY_URL = origEnv;
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
test('uses DEFAULT_REGISTRY when no arg and no env var', () => {
|
|
58
|
-
process.env.CELILO_REGISTRY_URL
|
|
58
|
+
delete process.env.CELILO_REGISTRY_URL;
|
|
59
59
|
const client = new RegistryClient();
|
|
60
60
|
expect(client.baseUrl).toBe(DEFAULT_REGISTRY);
|
|
61
61
|
});
|
|
@@ -36,7 +36,7 @@ describe('secret storage', () => {
|
|
|
36
36
|
afterEach(() => {
|
|
37
37
|
// Cleanup
|
|
38
38
|
rmSync(testDir, { recursive: true, force: true });
|
|
39
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
39
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test('stores and retrieves system secret', async () => {
|