@homeflare/alchemy 0.7.0 → 0.8.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.
Files changed (137) hide show
  1. package/README.md +26 -37
  2. package/dist/caddy/config-lifecycle.d.ts +49 -3
  3. package/dist/caddy/config-lifecycle.d.ts.map +1 -1
  4. package/dist/caddy/config.d.ts +6 -2
  5. package/dist/caddy/config.d.ts.map +1 -1
  6. package/dist/caddy/fake-caddy.d.ts +11 -0
  7. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  8. package/dist/caddy/index.d.ts +2 -1
  9. package/dist/caddy/index.d.ts.map +1 -1
  10. package/dist/caddy/index.js +71 -13
  11. package/dist/caddy/index.js.map +9 -9
  12. package/dist/caddy/with-file.d.ts +7 -3
  13. package/dist/caddy/with-file.d.ts.map +1 -1
  14. package/dist/launchd/fake-sudo.d.ts +50 -0
  15. package/dist/launchd/fake-sudo.d.ts.map +1 -0
  16. package/dist/launchd/host-effect.d.ts +18 -9
  17. package/dist/launchd/host-effect.d.ts.map +1 -1
  18. package/dist/launchd/host-file-form.d.ts +5 -0
  19. package/dist/launchd/host-file-form.d.ts.map +1 -1
  20. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  21. package/dist/launchd/index.d.ts +2 -0
  22. package/dist/launchd/index.d.ts.map +1 -1
  23. package/dist/launchd/index.js +342 -8
  24. package/dist/launchd/index.js.map +15 -11
  25. package/dist/launchd/job-preflight.d.ts.map +1 -1
  26. package/dist/launchd/job-validate.d.ts +2 -0
  27. package/dist/launchd/job-validate.d.ts.map +1 -1
  28. package/dist/launchd/local-runner.d.ts +2 -0
  29. package/dist/launchd/local-runner.d.ts.map +1 -1
  30. package/dist/launchd/providers.d.ts +2 -1
  31. package/dist/launchd/providers.d.ts.map +1 -1
  32. package/dist/launchd/runner.d.ts +7 -5
  33. package/dist/launchd/runner.d.ts.map +1 -1
  34. package/dist/launchd/sudo-allowlist.d.ts +47 -0
  35. package/dist/launchd/sudo-allowlist.d.ts.map +1 -0
  36. package/dist/launchd/sudo-guard.d.ts +56 -0
  37. package/dist/launchd/sudo-guard.d.ts.map +1 -0
  38. package/dist/launchd/sudo-runner.d.ts +60 -0
  39. package/dist/launchd/sudo-runner.d.ts.map +1 -0
  40. package/dist/launchd/sudo-stage.d.ts +7 -0
  41. package/dist/launchd/sudo-stage.d.ts.map +1 -0
  42. package/dist/openbao/auth-role-form.d.ts +0 -9
  43. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  44. package/dist/openbao/auth-role.d.ts +3 -2
  45. package/dist/openbao/auth-role.d.ts.map +1 -1
  46. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  47. package/dist/openbao/fake-engines-roles.d.ts +32 -0
  48. package/dist/openbao/fake-engines-roles.d.ts.map +1 -0
  49. package/dist/openbao/fake-engines.d.ts +8 -2
  50. package/dist/openbao/fake-engines.d.ts.map +1 -1
  51. package/dist/openbao/fake-families.d.ts +49 -0
  52. package/dist/openbao/fake-families.d.ts.map +1 -0
  53. package/dist/openbao/host-approles.d.ts.map +1 -1
  54. package/dist/openbao/index.js +359 -280
  55. package/dist/openbao/index.js.map +21 -19
  56. package/dist/openbao/jwt-config.d.ts +5 -0
  57. package/dist/openbao/jwt-config.d.ts.map +1 -1
  58. package/dist/openbao/jwt-role.d.ts +2 -1
  59. package/dist/openbao/jwt-role.d.ts.map +1 -1
  60. package/dist/openbao/kubernetes-role.d.ts +2 -1
  61. package/dist/openbao/kubernetes-role.d.ts.map +1 -1
  62. package/dist/openbao/mfa-enforcement.d.ts +6 -3
  63. package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
  64. package/dist/openbao/mfa-totp.d.ts.map +1 -1
  65. package/dist/openbao/pki-role.d.ts.map +1 -1
  66. package/dist/openbao/plugin.d.ts.map +1 -1
  67. package/dist/openbao/policy.d.ts.map +1 -1
  68. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  69. package/dist/openbao/rename-identity.d.ts +69 -0
  70. package/dist/openbao/rename-identity.d.ts.map +1 -0
  71. package/dist/openbao/rename.d.ts +13 -12
  72. package/dist/openbao/rename.d.ts.map +1 -1
  73. package/dist/openbao/ssh-role-erase.d.ts +11 -0
  74. package/dist/openbao/ssh-role-erase.d.ts.map +1 -0
  75. package/dist/openbao/ssh-role.d.ts.map +1 -1
  76. package/docs/caddy-admin.md +24 -0
  77. package/docs/caddy.md +73 -54
  78. package/docs/launchd-sudo.md +122 -0
  79. package/docs/launchd.md +7 -4
  80. package/docs/peers.md +39 -0
  81. package/package.json +1 -1
  82. package/src/caddy/adopt-scope.test.ts +82 -0
  83. package/src/caddy/config-adopt.test.ts +235 -0
  84. package/src/caddy/config-lifecycle.test.ts +31 -53
  85. package/src/caddy/config-lifecycle.ts +98 -7
  86. package/src/caddy/config.ts +48 -11
  87. package/src/caddy/fake-caddy.ts +19 -0
  88. package/src/caddy/index.ts +2 -1
  89. package/src/caddy/providers.test.ts +8 -23
  90. package/src/caddy/with-file.ts +7 -3
  91. package/src/launchd/fake-sudo.ts +136 -0
  92. package/src/launchd/host-effect.ts +33 -1
  93. package/src/launchd/host-file-form.ts +10 -2
  94. package/src/launchd/host-file-lifecycle.ts +2 -1
  95. package/src/launchd/index.ts +2 -0
  96. package/src/launchd/job-preflight.ts +3 -2
  97. package/src/launchd/job-validate.ts +1 -1
  98. package/src/launchd/local-runner.ts +6 -3
  99. package/src/launchd/providers.test.ts +3 -0
  100. package/src/launchd/providers.ts +2 -1
  101. package/src/launchd/runner.ts +7 -5
  102. package/src/launchd/sudo-allowlist.test.ts +191 -0
  103. package/src/launchd/sudo-allowlist.ts +213 -0
  104. package/src/launchd/sudo-guard.test.ts +100 -0
  105. package/src/launchd/sudo-guard.ts +151 -0
  106. package/src/launchd/sudo-lifecycle.test.ts +172 -0
  107. package/src/launchd/sudo-runner-files.test.ts +240 -0
  108. package/src/launchd/sudo-runner.test.ts +126 -0
  109. package/src/launchd/sudo-runner.ts +235 -0
  110. package/src/launchd/sudo-stage.ts +28 -0
  111. package/src/openbao/README.md +8 -2
  112. package/src/openbao/REPLACE.md +65 -37
  113. package/src/openbao/auth-role-form.test.ts +2 -16
  114. package/src/openbao/auth-role-form.ts +0 -11
  115. package/src/openbao/auth-role.ts +35 -14
  116. package/src/openbao/cloudflare-role.ts +6 -14
  117. package/src/openbao/fake-engines-roles.ts +114 -0
  118. package/src/openbao/fake-engines.ts +18 -11
  119. package/src/openbao/fake-families.ts +182 -0
  120. package/src/openbao/host-approles.test.ts +15 -2
  121. package/src/openbao/host-approles.ts +19 -4
  122. package/src/openbao/jwt-config.ts +26 -5
  123. package/src/openbao/jwt-role.ts +24 -8
  124. package/src/openbao/kubernetes-role.ts +20 -8
  125. package/src/openbao/mfa-enforcement.ts +23 -8
  126. package/src/openbao/mfa-totp.ts +21 -8
  127. package/src/openbao/pki-role.ts +19 -7
  128. package/src/openbao/plugin.ts +41 -15
  129. package/src/openbao/policy.ts +7 -7
  130. package/src/openbao/proxmox-role.ts +7 -16
  131. package/src/openbao/rename-adoption.test.ts +82 -0
  132. package/src/openbao/rename-families.test.ts +122 -0
  133. package/src/openbao/rename-identity.test.ts +129 -0
  134. package/src/openbao/rename-identity.ts +117 -0
  135. package/src/openbao/rename.ts +19 -15
  136. package/src/openbao/ssh-role-erase.ts +50 -0
  137. package/src/openbao/ssh-role.ts +16 -54
package/README.md CHANGED
@@ -119,17 +119,37 @@ delete-first.
119
119
  - ⛔ **No silent sudo:** the system domain needs a deploy started as root, or a `privileged` runner.
120
120
  - ⚠️ `org.nixos.*` jobs are never adopted. Guide and nix-darwin cutover: [docs/launchd.md](./docs/launchd.md).
121
121
 
122
+ ### Deploying as yourself — `sudoRunner()`
123
+
124
+ ```ts
125
+ launchdProviders(sudoRunner({ prefixes: ['/Library/LaunchDaemons', '/opt/example'] }));
126
+ ```
127
+
128
+ The deploy runs as you. Only `launchctl bootstrap | bootout | kickstart` in the system domain, and
129
+ `install` / `rm` of a file under a declared prefix, go through `sudo -n`, in exact argv shapes. Each
130
+ one is logged (argv only, never content) before it runs. A plan never calls sudo.
131
+
132
+ - ⛔ **Opt-in, never a fallback**, and it never prompts: if sudo needs a password, the call fails at
133
+ once and says to run `sudo -v`, or to grant exactly these commands `NOPASSWD`.
134
+ - ⚠️ Refused before sudo is asked: any other argv, a prefix that is not a real directory only root
135
+ may write, a path outside every prefix that needs root, a symlink on the way, a file you could
136
+ not read back, and another user's `gui/<uid>`.
137
+ - Full list, sudoers cautions and limits: [docs/launchd-sudo.md](./docs/launchd-sudo.md).
138
+
122
139
  ## Caddy — `@homeflare/alchemy/caddy`
123
140
 
124
141
  `CaddyConfig` declares a running Caddy's config as Caddyfile text, applied through Caddy's own admin
125
142
  API: `POST /load` (graceful reload; Caddy keeps the old config if it refuses the new one, and the
126
- deploy says why), `GET /config/` for drift. A running Caddy is adopted on first read.
127
- `caddyWithFile()` also writes the file Caddy starts from, with `HostFile`; `caddyProviders()` provides
128
- the transport, `http://127.0.0.1:2019` by default.
143
+ deploy says why), `GET /config/` for drift. `caddyWithFile()` also writes the file Caddy starts from,
144
+ with `HostFile`; `caddyProviders()` provides the transport, `http://127.0.0.1:2019` by default.
129
145
 
146
+ - ⛔ **Nothing is adopted silently:** a running Caddy whose config is not the declared one plans as
147
+ `Unowned`, and the deploy needs `--adopt`.
130
148
  - ⛔ **No secrets in the Caddyfile** (state is unencrypted): `{env.NAME}` / `{file./path}` placeholders.
131
149
  - ⛔ **The admin API stays on loopback or a unix socket**, and a Caddyfile that would move it is refused.
132
- - ⛔ **Delete never unloads or stops Caddy.** Order of file and load, `--resume`: [docs/caddy.md](./docs/caddy.md).
150
+ - ⛔ **Delete never unloads or stops Caddy.** Order of file and load: [docs/caddy.md](./docs/caddy.md).
151
+ - ★ **Managed Caddies run `--resume` with their own `XDG_CONFIG_HOME`**, so a restart runs the last
152
+ config Caddy accepted — and after one, SIGUSR1 has no file to reload. Why, and the rest: same doc.
133
153
 
134
154
  ## Credentials
135
155
 
@@ -144,12 +164,6 @@ like a bad credential.
144
164
 
145
165
  ## Peers — and one override you need
146
166
 
147
- ```sh
148
- bun add @homeflare/alchemy alchemy@2.0.0-beta.79 effect@4.0.0-rc.115 \
149
- @effect/platform-node@4.0.0-rc.115 cloudflare@4.5.0 mime@4.1.0 \
150
- @distilled.cloud/cloudflare@1.0.0-rc.12
151
- ```
152
-
153
167
  ⚠️ Peers, not dependencies: Alchemy's resource registry and Effect's context both break if
154
168
  two copies load in one process.
155
169
 
@@ -167,33 +181,8 @@ two copies load in one process.
167
181
  }
168
182
  ```
169
183
 
170
- 🔴 **Why, measured 2026-09-16 on 0.1.0 and re-checked 2026-09-17 against Alchemy 78.**
171
- Effect's `rc` line is not semver-compatible with itself. Alchemy 78's peer is
172
- `effect >= 4.0.0-rc.115`; an unlocked `@effect/platform-node-shared` still floats to the
173
- next rc and breaks at import. Pin the whole set.
174
-
175
- | what resolves | what happens |
176
- | ------------------------------------------------------------- | ------------------------------------------------------ |
177
- | Alchemy 77 + `effect` → rc.115 (measured 2026-09-16) | `TypeError: Config.string is not a function` at import |
178
- | `@effect/platform-node-shared` newer than the pinned `effect` | `Cannot find module 'effect/ByteSize'` |
179
- | `rolldown` → 1.2.9 via vite's `~1.2.6` (measured 2026-09-16) | `GET …/rolldown-1.2.9.tgz - 404` at `bun add` |
180
- | no `mime` (measured 2026-09-17 against Alchemy 78) | `Cannot find package 'mime'` from cloudflare-runtime |
181
-
182
- Alchemy 78 adapted to rc.115 — that first row is why we used to pin 112, not a reason to
183
- stay there. The override is still the only thing that holds the set together.
184
-
185
- ⚠️ `@effect/platform-node` is **required, not optional**: Alchemy's module graph reaches
186
- `Cloudflare/Workers/WorkerBridge → @effect/platform-node/NodeServices` even when you only
187
- import the Proxmox subpath.
188
-
189
- ⚠️ So is `mime`. Alchemy 78's `@alchemy.run/cloudflare-runtime` imported it without
190
- declaring it (measured 2026-09-17). 79 declares it; the peer stays so a consumer
191
- that followed the 78 README does not drop a required line.
192
-
193
- ⚠️ So is `cloudflare`. It was marked optional in 0.1.1, which claimed the `/cloudflare`
194
- subpath would degrade without it — measured 2026-09-16, the subpath does not load at all:
195
- `Cannot find package 'cloudflare'`. An optional peer should mean a feature is absent, not
196
- that an import fails.
184
+ Why each peer is required and each pin exists — the measurements, and the error a missing one
185
+ produces: [docs/peers.md](./docs/peers.md).
197
186
 
198
187
  ## License
199
188
 
@@ -9,9 +9,11 @@
9
9
  * · live ≠ stored — DRIFT: a hand `curl` to the API, or a restart that loaded a different file
10
10
  * (or an `autosave.json` holding someone else's change, under `--resume`).
11
11
  * · declared = live — nothing to load, whatever the state says.
12
+ * With NO state there is no stored digest, and the question is whose config is running: see
13
+ * `claimable` below, which both the adoption probe and the apply ask.
12
14
  */
13
15
  import type { Diff } from 'alchemy/Diff';
14
- import type { CaddyAdmin } from './admin.ts';
16
+ import { type CaddyAdmin } from './admin.ts';
15
17
  import { type CaddyConfigAttributes, type CaddyConfigProps } from './config-form.ts';
16
18
  type Desired = {
17
19
  readonly digest: string;
@@ -26,6 +28,31 @@ type Desired = {
26
28
  export declare const desiredConfig: (admin: CaddyAdmin, props: CaddyConfigProps) => Promise<Desired>;
27
29
  /** What is running now, as attributes. `sourceFile` is carried over, never discovered. */
28
30
  export declare const readLive: (admin: CaddyAdmin, sourceFile?: string) => Promise<CaddyConfigAttributes>;
31
+ /**
32
+ * ⛔ WITH NO STATE, A RUNNING CONFIG IS THIS STACK'S ONLY IF IT IS THE DECLARED ONE (decision,
33
+ * 2026-09-21: CaddyConfig must not adopt silently — the house rule HostFile and LaunchdJob keep).
34
+ * Anything else was put there by a person, another tool or another stack, and loading over it
35
+ * replaces every site it serves. ★ A config that serves nothing (`null`, or no apps) is claimable
36
+ * too: there is nothing to take over, as an empty R2 lock rule set reads as no lock.
37
+ */
38
+ export declare const claimable: (running: unknown, declaredDigest: string) => boolean;
39
+ export type Probe = {
40
+ readonly attributes: CaddyConfigAttributes;
41
+ /** The live config IS the declared one: adopting it changes nothing. */
42
+ readonly ours: boolean;
43
+ /** Why the declaration could not be compared, when it could not — the Caddy is then not ours. */
44
+ readonly unchecked?: string;
45
+ };
46
+ /**
47
+ * The adoption probe — `read` with no state: `undefined` when Caddy serves nothing (plan a create),
48
+ * else the live attributes and whether they are provably ours (the provider brands the rest
49
+ * `Unowned`, and the plan refuses them without `--adopt`).
50
+ * ⛔ IT NEVER THROWS OVER THE DECLARATION. The engine also runs this read to recover an interrupted
51
+ * create, with THAT deploy's props (Plan.ts, `status: "creating"`) — so a Caddyfile that failed to
52
+ * adapt then would fail every later plan, the one carrying the fix included. What cannot be
53
+ * compared is "not proven ours", with the reason; the next diff or apply validates the Caddyfile.
54
+ */
55
+ export declare const probeLive: (admin: CaddyAdmin, caddyfile: string | undefined, sourceFile?: string) => Promise<Probe | undefined>;
29
56
  /**
30
57
  * ★ NEVER `replace`. Nothing in the props names a different object: a new Caddyfile is a reload of
31
58
  * the same Caddy, and `sourceFile` is a header. (Which Caddy is the transport's business — see
@@ -39,7 +66,26 @@ export type Applied = {
39
66
  /** Whether a `POST /load` was sent (false when Caddy already ran this config). */
40
67
  readonly loaded: boolean;
41
68
  };
42
- /** Load the Caddyfile unless Caddy already runs it, then read it back and insist it matches. */
43
- export declare const reconcileConfig: (admin: CaddyAdmin, props: CaddyConfigProps) => Promise<Applied>;
69
+ /**
70
+ * What this apply may load over — config.ts decides from the state and the adopt setting:
71
+ * · `takeOver` — any running config: the state was applied to THIS Caddy (an update, drift
72
+ * correction, or a create the engine adopted), or adoption is on (`--adopt`, or the
73
+ * resource's own `adopt(true)` — host-effect.ts adoptEnabled).
74
+ * · otherwise only a claimable one — plus, as `stored`, the digest the state last recorded: a
75
+ * Caddy reached at a NEW endpoint that runs exactly that is the same config under another name.
76
+ */
77
+ export type Authority = {
78
+ readonly takeOver: boolean;
79
+ readonly stored?: string;
80
+ };
81
+ /**
82
+ * Load the Caddyfile unless Caddy already runs it, then read it back and insist it matches.
83
+ * ⛔ WITHOUT `takeOver`, A CONFIG THE STACK CANNOT CLAIM IS NEVER LOADED OVER. The engine's adoption
84
+ * probe cannot guard every create: it is skipped while `news` holds an Output — ALWAYS on
85
+ * caddyWithFile's first deploy (`sourceFile` is its HostFile's path) — and a Caddy that was down at
86
+ * plan time read as nothing. Nor does state vouch for a Caddy the transport NOW reaches at another
87
+ * endpoint (diff plans that as an update). Refused here, before any `/load`, like HostFile's.
88
+ */
89
+ export declare const reconcileConfig: (admin: CaddyAdmin, props: CaddyConfigProps, authority: Authority) => Promise<Applied>;
44
90
  export {};
45
91
  //# sourceMappingURL=config-lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-lifecycle.d.ts","sourceRoot":"","sources":["../../src/caddy/config-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAQ7C,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EAEtB,MAAM,kBAAkB,CAAC;AAQ1B,KAAK,OAAO,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAcjF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,UACjB,UAAU,SACV,gBAAgB,KACtB,OAAO,CAAC,OAAO,CAUjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,UACZ,UAAU,eACJ,MAAM,KAClB,OAAO,CAAC,qBAAqB,CAI9B,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU,UACd,UAAU,QACX,gBAAgB,UACd,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,iGAAiG;IACjG,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,eAAe,UACnB,UAAU,SACV,gBAAgB,KACtB,OAAO,CAAC,OAAO,CAmCjB,CAAC"}
1
+ {"version":3,"file":"config-lifecycle.d.ts","sourceRoot":"","sources":["../../src/caddy/config-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,YAAY,CAAC;AAQpE,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EAEtB,MAAM,kBAAkB,CAAC;AAQ1B,KAAK,OAAO,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAcjF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,UACjB,UAAU,SACV,gBAAgB,KACtB,OAAO,CAAC,OAAO,CAUjB,CAAC;AAYF,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,UACZ,UAAU,eACJ,MAAM,KAClB,OAAO,CAAC,qBAAqB,CACiC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,YAAa,OAAO,kBAAkB,MAAM,KAAG,OACD,CAAC;AAErE,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,iGAAiG;IACjG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,UACb,UAAU,aACN,MAAM,GAAG,SAAS,eAChB,MAAM,KAClB,OAAO,CAAC,KAAK,GAAG,SAAS,CAmB3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,UACd,UAAU,QACX,gBAAgB,UACd,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAYd,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,iGAAiG;IACjG,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,UACnB,UAAU,SACV,gBAAgB,aACZ,SAAS,KACnB,OAAO,CAAC,OAAO,CA8CjB,CAAC"}
@@ -5,8 +5,7 @@
5
5
  * - create / update — `POST /adapt` (validate), `POST /load` with `text/caddyfile` (a graceful
6
6
  * reload; Caddy keeps the old config if it refuses the new one, and the error names why), then
7
7
  * `GET /config/` and insist it matches. Skipped when Caddy already runs this config.
8
- * - read — the digest of `GET /config/`. ★ WITH NO STATE, A RUNNING CADDY IS ADOPTED: its live
9
- * config becomes the starting point, and the deploy's forced update loads the declared one.
8
+ * - read — the digest of `GET /config/`. With no state it is the adoption probe (below).
10
9
  * - diff — declared, live and stored digests (config-lifecycle.ts). Never `replace`.
11
10
  * - delete — ⛔ NOTHING. See below.
12
11
  *
@@ -15,6 +14,11 @@
15
14
  * servers. `retain` is the default (the engine never calls delete); with `destroy` the handler
16
15
  * still only forgets. It never calls `/stop` (which exits the process) or `DELETE /config/`.
17
16
  * To empty a Caddy, do it by hand at its admin API.
17
+ * ⛔ NOTHING IS ADOPTED SILENTLY (decision, 2026-09-21 — the house rule HostFile and LaunchdJob
18
+ * keep). With no state, a running Caddy whose live config IS the declared one reads as ours (the
19
+ * adoption changes nothing); any other config reads as `Unowned`, and the plan refuses it unless
20
+ * the deploy runs with `--adopt`. A Caddy serving nothing reads as absent: a create. Where the
21
+ * engine skips that probe, reconcile refuses the same takeover (config-lifecycle.ts).
18
22
  * ⛔ `caddyfile` IS NEVER A SECRET — see config-form.ts; refused before anything is sent.
19
23
  * ⚠️ WHICH CADDY is the transport's (providers.ts), not a prop: one CaddyAdmin per stack.
20
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/caddy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAE,iBAAiB,EAAyB,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGhF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,gBAAgB,EAChB,qBAAqB,CACtB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAEtB,CAAC;AAsBH,eAAO,MAAM,mBAAmB,8FAsD7B,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/caddy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAE,iBAAiB,EAAyB,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGhF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,gBAAgB,EAChB,qBAAqB,CACtB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAEtB,CAAC;AAsBH,eAAO,MAAM,mBAAmB,8FAsF7B,CAAC"}
@@ -17,6 +17,7 @@
17
17
  * ★ `listenPort` is the port Caddy BELIEVES it listens on — its Host check uses it — while the
18
18
  * fake really listens on an ephemeral one: a Caddy on its default :2019 behind a forward.
19
19
  */
20
+ import type { CaddyAdmin } from './admin.ts';
20
21
  export type Seen = {
21
22
  readonly method: string;
22
23
  readonly path: string;
@@ -40,4 +41,14 @@ export declare const fakeCaddy: (options?: {
40
41
  origins?: string[];
41
42
  listenPort?: number;
42
43
  }) => FakeCaddy;
44
+ /**
45
+ * A fake Caddy on its DEFAULT :2019 behind a forward (`hostHeader`), and the transport to it.
46
+ * ★ Addressed that way so a Caddyfile with no `admin` line — the common case — passes the guard.
47
+ * A Caddy reached on any other port needs `admin <address>` declared, or the first load would
48
+ * move it (admin-guard.ts).
49
+ */
50
+ export declare const fakeDefaultCaddy: (running?: unknown) => {
51
+ admin: CaddyAdmin;
52
+ caddy: FakeCaddy;
53
+ };
43
54
  //# sourceMappingURL=fake-caddy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fake-caddy.d.ts","sourceRoot":"","sources":["../../src/caddy/fake-caddy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACtB,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAqDF,eAAO,MAAM,SAAS,aACX;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KACrF,SAoEF,CAAC"}
1
+ {"version":3,"file":"fake-caddy.d.ts","sourceRoot":"","sources":["../../src/caddy/fake-caddy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACtB,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAqDF,eAAO,MAAM,SAAS,aACX;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KACrF,SAoEF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,aAAc,OAAO,KAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAQzF,CAAC"}
@@ -6,7 +6,8 @@
6
6
  * the digest, the guards and the admin calls are internals; an `export *` would publish them.
7
7
  * ⛔ THE ADMIN API STAYS ON LOOPBACK OR A UNIX SOCKET. It has no authentication; localCaddyAdmin()
8
8
  * refuses any other address, and a Caddyfile that would move or expose it is refused before load.
9
- * Guide, order of file and load, `--resume` and secrets: docs/caddy.md.
9
+ * ⛔ NOTHING IS ADOPTED SILENTLY: a running config that is not the declared one needs `--adopt`.
10
+ * Guide, order of file and load, adoption, `--resume` and secrets: docs/caddy.md.
10
11
  */
11
12
  export type { CaddyAdmin, CaddyAdminListener, CaddyAdminRequest, CaddyAdminResponse, } from './admin.ts';
12
13
  export { CaddyAdminService, CaddyUnreachableError, caddyAdminLayer } from './admin.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/caddy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/caddy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -292,12 +292,17 @@ var loadCaddyfile = async (admin, caddyfile, sourceFile) => {
292
292
  };
293
293
  // src/caddy/config.ts
294
294
  import { Resource } from "alchemy";
295
+ import { Unowned } from "alchemy/AdoptPolicy";
295
296
  import { isResolved } from "alchemy/Diff";
296
297
  import * as Provider from "alchemy/Provider";
297
298
  import * as Effect2 from "effect/Effect";
298
299
 
299
300
  // src/launchd/host-effect.ts
301
+ import { AdoptPolicy } from "alchemy/AdoptPolicy";
302
+ import { AlchemyContext } from "alchemy/AlchemyContext";
303
+ import { Stack } from "alchemy/Stack";
300
304
  import * as Effect from "effect/Effect";
305
+ import * as Option from "effect/Option";
301
306
  var lift = (step) => Effect.tryPromise({
302
307
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
303
308
  try: step
@@ -308,6 +313,17 @@ var resolvedString = (news, key) => {
308
313
  const value = news[key];
309
314
  return typeof value === "string" ? value : undefined;
310
315
  };
316
+ var adoptEnabled = (fqn) => Effect.gen(function* () {
317
+ const stack = yield* Effect.serviceOption(Stack);
318
+ const scoped = Option.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
319
+ if (scoped !== undefined)
320
+ return scoped;
321
+ const policy = yield* Effect.serviceOption(AdoptPolicy);
322
+ if (Option.isSome(policy))
323
+ return policy.value;
324
+ const context = yield* Effect.serviceOption(AlchemyContext);
325
+ return Option.isSome(context) ? context.value.adopt : false;
326
+ });
311
327
 
312
328
  // src/caddy/admin-guard.ts
313
329
  var DEFAULT_ADMIN_PORT = 2019;
@@ -544,20 +560,48 @@ var desiredConfig = async (admin, props) => {
544
560
  throw refuse(admin, guard.join("; "));
545
561
  return { digest: configDigest(adapted.config), warnings: adapted.warnings };
546
562
  };
547
- var readLive = async (admin, sourceFile) => ({
548
- configSha256: configDigest(await readRunningConfig(admin)),
563
+ var attributesOf = (admin, running, sourceFile) => ({
564
+ configSha256: configDigest(running),
549
565
  endpoint: admin.endpoint,
550
566
  ...sourceFile === undefined ? {} : { sourceFile }
551
567
  });
568
+ var readLive = async (admin, sourceFile) => attributesOf(admin, await readRunningConfig(admin), sourceFile);
569
+ var claimable = (running, declaredDigest) => servesNothing(running) || configDigest(running) === declaredDigest;
570
+ var probeLive = async (admin, caddyfile, sourceFile) => {
571
+ const running = await readRunningConfig(admin);
572
+ if (servesNothing(running))
573
+ return;
574
+ const attributes = attributesOf(admin, running, sourceFile);
575
+ if (caddyfile === undefined) {
576
+ return { attributes, ours: false, unchecked: "the stored Caddyfile is not plain text" };
577
+ }
578
+ try {
579
+ const want = await desiredConfig(admin, { caddyfile });
580
+ return { attributes, ours: claimable(running, want.digest) };
581
+ } catch (cause) {
582
+ if (cause instanceof CaddyUnreachableError)
583
+ throw cause;
584
+ return {
585
+ attributes,
586
+ ours: false,
587
+ unchecked: cause instanceof Error ? cause.message : String(cause)
588
+ };
589
+ }
590
+ };
552
591
  var diffConfig = async (admin, news, output) => {
553
592
  const want = await desiredConfig(admin, news);
554
593
  const live = await readLive(admin);
555
594
  const converged = want.digest === live.configSha256 && live.configSha256 === output.configSha256 && news.sourceFile === output.sourceFile && admin.endpoint === output.endpoint;
556
595
  return converged ? { action: "noop" } : { action: "update" };
557
596
  };
558
- var reconcileConfig = async (admin, props) => {
597
+ var reconcileConfig = async (admin, props, authority) => {
559
598
  const want = await desiredConfig(admin, props);
560
- const before = configDigest(await readRunningConfig(admin));
599
+ const running = await readRunningConfig(admin);
600
+ const before = configDigest(running);
601
+ const known = before === authority.stored || claimable(running, want.digest);
602
+ if (!authority.takeOver && !known) {
603
+ throw refuse(admin, `Caddy runs config ${short(before)}, which this stack did not load, and the Caddyfile adapts ` + `to ${short(want.digest)} — loading it would replace every site that config serves. ` + "Deploy with --adopt (or wrap the resource in adopt(true)) to take this Caddy over, or " + "point caddyProviders() at the Caddy you meant.");
604
+ }
561
605
  let warnings = want.warnings;
562
606
  const loaded = before !== want.digest;
563
607
  if (loaded) {
@@ -588,9 +632,17 @@ var unreachable = (error) => error instanceof CaddyUnreachableError;
588
632
  var planWithoutCaddy = (admin, fallback) => (error) => Effect2.as(Effect2.logWarning(`Caddy.Config: no Caddy at ${admin.endpoint} (${error.message}); planning without it — ` + "the Caddyfile is validated when the deploy reaches Caddy"), fallback);
589
633
  var CaddyConfigProvider = () => Provider.effect(CaddyConfig, Effect2.gen(function* () {
590
634
  const admin = yield* CaddyAdminService;
635
+ const probe = (caddyfile, sourceFile) => Effect2.flatMap(lift(() => probeLive(admin, caddyfile, sourceFile)), (found) => {
636
+ if (found === undefined)
637
+ return Effect2.succeed(undefined);
638
+ if (found.ours)
639
+ return Effect2.succeed(found.attributes);
640
+ const unowned = Unowned(found.attributes);
641
+ return found.unchecked === undefined ? Effect2.succeed(unowned) : Effect2.as(Effect2.logWarning(`Caddy.Config at ${admin.endpoint}: the running config could not be compared ` + `with the declared Caddyfile (${found.unchecked}), so it is not treated as ours`), unowned);
642
+ });
591
643
  return CaddyConfig.Provider.of({
592
644
  list: () => Effect2.succeed([]),
593
- read: ({ olds }) => lift(() => readLive(admin, resolvedString(olds, "sourceFile"))).pipe(Effect2.catchIf(unreachable, planWithoutCaddy(admin, undefined))),
645
+ read: ({ olds, output }) => (output === undefined ? probe(resolvedString(olds, "caddyfile"), resolvedString(olds, "sourceFile")) : lift(() => readLive(admin, resolvedString(olds, "sourceFile")))).pipe(Effect2.catchIf(unreachable, planWithoutCaddy(admin, undefined))),
594
646
  diff: ({ news, output }) => {
595
647
  if (output === undefined)
596
648
  return Effect2.succeed(undefined);
@@ -606,8 +658,11 @@ var CaddyConfigProvider = () => Provider.effect(CaddyConfig, Effect2.gen(functio
606
658
  return { action: "update" };
607
659
  }).pipe(Effect2.catchIf(unreachable, update));
608
660
  },
609
- reconcile: ({ news }) => Effect2.gen(function* () {
610
- const applied = yield* lift(() => reconcileConfig(admin, news));
661
+ reconcile: ({ fqn, news, output }) => Effect2.gen(function* () {
662
+ const sameCaddy = output !== undefined && output.endpoint === admin.endpoint;
663
+ const takeOver = sameCaddy || (yield* adoptEnabled(fqn));
664
+ const stored = output === undefined ? {} : { stored: output.configSha256 };
665
+ const applied = yield* lift(() => reconcileConfig(admin, news, { takeOver, ...stored }));
611
666
  for (const warning of applied.warnings) {
612
667
  yield* Effect2.logWarning(`Caddy.Config at ${admin.endpoint}: ${warning}`);
613
668
  }
@@ -716,7 +771,7 @@ import * as RemovalPolicy from "alchemy/RemovalPolicy";
716
771
 
717
772
  // src/launchd/host-file.ts
718
773
  import { Resource as Resource2 } from "alchemy";
719
- import { Unowned } from "alchemy/AdoptPolicy";
774
+ import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
720
775
  import { isResolved as isResolved2 } from "alchemy/Diff";
721
776
  import * as Provider2 from "alchemy/Provider";
722
777
  import * as Effect3 from "effect/Effect";
@@ -731,9 +786,8 @@ var identityProblems = (name, value) => {
731
786
  }
732
787
  return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value) || /^\d+$/.test(value) ? [] : [`${name} ${JSON.stringify(value)} is not a valid user or group name`];
733
788
  };
734
- var fileProblems = (props) => {
789
+ var pathProblems = (path) => {
735
790
  const found = [];
736
- const { path } = props;
737
791
  if (!path.startsWith("/"))
738
792
  found.push("path must be absolute");
739
793
  if (path.endsWith("/") || path.split("/").slice(1).some((part) => part === "" || part === "." || part === "..")) {
@@ -741,6 +795,10 @@ var fileProblems = (props) => {
741
795
  }
742
796
  if (path.includes("\x00"))
743
797
  found.push("path contains NUL");
798
+ return found;
799
+ };
800
+ var fileProblems = (props) => {
801
+ const found = pathProblems(props.path);
744
802
  const mode = props.mode ?? DEFAULT_MODE;
745
803
  if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
746
804
  found.push("mode must be 0–0o7777");
@@ -814,7 +872,7 @@ var diffFile = async (runner, news, output) => {
814
872
  var reconcileFile = async (runner, props, output) => {
815
873
  const want = await desiredFile(runner, props);
816
874
  if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
817
- throw refuse3(props.path, `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy as root ` + "or through a privileged HostRunner (privileged: true). This provider never calls sudo.");
875
+ throw refuse3(props.path, `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy as root ` + "or through a privileged HostRunner (sudoRunner() with this path under a prefix). This " + "provider never calls sudo itself.");
818
876
  }
819
877
  const stat = await runner.stat(props.path);
820
878
  if (stat !== undefined && stat.kind !== "file") {
@@ -864,7 +922,7 @@ var HostFileProvider = () => Provider2.effect(HostFile, Effect3.gen(function* ()
864
922
  const found = await readFileAttributes(runner, olds.path);
865
923
  if (found === undefined)
866
924
  return;
867
- return output === undefined ? Unowned(found) : found;
925
+ return output === undefined ? Unowned2(found) : found;
868
926
  }),
869
927
  diff: ({ news, output }) => {
870
928
  if (output === undefined)
@@ -908,5 +966,5 @@ export {
908
966
  localCaddyAdmin
909
967
  };
910
968
 
911
- //# debugId=205F5FF536DDDFA164756E2164756E21
969
+ //# debugId=7B177B132A41844D64756E2164756E21
912
970
  //# sourceMappingURL=index.js.map