@homeflare/alchemy 0.55.0 → 0.57.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 (65) hide show
  1. package/dist/launchd/index.js +4 -2
  2. package/dist/launchd/index.js.map +3 -3
  3. package/dist/launchd/sudo-guard-mode.d.ts +10 -4
  4. package/dist/launchd/sudo-guard-mode.d.ts.map +1 -1
  5. package/dist/linux/fake-sudo-root.d.ts.map +1 -1
  6. package/dist/linux/fake-sudo.d.ts +9 -1
  7. package/dist/linux/fake-sudo.d.ts.map +1 -1
  8. package/dist/linux/index.js +15 -8
  9. package/dist/linux/index.js.map +6 -6
  10. package/dist/linux/sudo-guard.d.ts +10 -1
  11. package/dist/linux/sudo-guard.d.ts.map +1 -1
  12. package/dist/linux/sudo-runner.d.ts +3 -1
  13. package/dist/linux/sudo-runner.d.ts.map +1 -1
  14. package/dist/linux/sudo-write.d.ts +4 -1
  15. package/dist/linux/sudo-write.d.ts.map +1 -1
  16. package/dist/unifi/acl-rule-ordering.d.ts +1 -1
  17. package/dist/unifi/acl-rule.d.ts +1 -1
  18. package/dist/unifi/dns-policy.d.ts +1 -1
  19. package/dist/unifi/firewall-policy-create.d.ts +55 -0
  20. package/dist/unifi/firewall-policy-create.d.ts.map +1 -0
  21. package/dist/unifi/firewall-policy-form.d.ts +9 -2
  22. package/dist/unifi/firewall-policy-form.d.ts.map +1 -1
  23. package/dist/unifi/firewall-policy-ordering.d.ts +1 -1
  24. package/dist/unifi/firewall-policy.d.ts +45 -12
  25. package/dist/unifi/firewall-policy.d.ts.map +1 -1
  26. package/dist/unifi/firewall-zone.d.ts +1 -1
  27. package/dist/unifi/index.d.ts +1 -1
  28. package/dist/unifi/index.d.ts.map +1 -1
  29. package/dist/unifi/index.js +112 -21
  30. package/dist/unifi/index.js.map +10 -9
  31. package/dist/unifi/network.d.ts +1 -1
  32. package/dist/unifi/policy.d.ts +22 -0
  33. package/dist/unifi/policy.d.ts.map +1 -1
  34. package/dist/unifi/resource.d.ts +26 -7
  35. package/dist/unifi/resource.d.ts.map +1 -1
  36. package/dist/unifi/traffic-matching-list.d.ts +1 -1
  37. package/dist/unifi/update-reconcile.d.ts +36 -2
  38. package/dist/unifi/update-reconcile.d.ts.map +1 -1
  39. package/dist/unifi/wifi-broadcast.d.ts +1 -1
  40. package/dist/unifi/wire-guard.d.ts +1 -1
  41. package/dist/unifi/wire-guard.d.ts.map +1 -1
  42. package/docs/distilled-interim.md +4 -2
  43. package/docs/linux-sudo-review-2026-09-23.md +27 -0
  44. package/docs/linux-sudo.md +28 -40
  45. package/package.json +1 -1
  46. package/src/launchd/sudo-guard-mode.ts +11 -3
  47. package/src/linux/fake-sudo-root.ts +15 -0
  48. package/src/linux/fake-sudo.ts +20 -1
  49. package/src/linux/sudo-guard.ts +22 -1
  50. package/src/linux/sudo-runner-privileged-read.test.ts +109 -1
  51. package/src/linux/sudo-runner.ts +5 -3
  52. package/src/linux/sudo-write.ts +5 -1
  53. package/src/talos/talosctl.test.ts +6 -4
  54. package/src/unifi/firewall-policy-create.ts +94 -0
  55. package/src/unifi/firewall-policy-form.ts +9 -2
  56. package/src/unifi/firewall-policy.test.ts +188 -1
  57. package/src/unifi/firewall-policy.ts +108 -12
  58. package/src/unifi/index.ts +1 -0
  59. package/src/unifi/policy.ts +45 -12
  60. package/src/unifi/resource.test.ts +103 -0
  61. package/src/unifi/resource.ts +73 -21
  62. package/src/unifi/update-reconcile.ts +72 -2
  63. package/src/unifi/wire-guard.test.ts +86 -1
  64. package/src/unifi/wire-guard.ts +8 -1
  65. package/src/unifi/write-op-reference.test.ts +21 -7
@@ -129,7 +129,14 @@ interface ProbeProps {
129
129
  const probeSpec = (
130
130
  method: string,
131
131
  withUpdate = false,
132
- ): UnifiSpec<ProbeProps, ProbeProps, ProbeProps, HttpClientError.HttpClientError> => ({
132
+ ): UnifiSpec<
133
+ ProbeProps,
134
+ ProbeProps,
135
+ ProbeProps,
136
+ HttpClientError.HttpClientError,
137
+ never,
138
+ never
139
+ > => ({
133
140
  type: 'Test.Probe',
134
141
  describe: (props) => `probe/${props.id}`,
135
142
  fetchLive: (props) =>
@@ -153,6 +160,41 @@ const probeSpec = (
153
160
  : {}),
154
161
  });
155
162
 
163
+ /**
164
+ * A create-only probe: `fetchLive` always answers absent (so `resource.ts`'s create branch always
165
+ * attempts the create), and `create.write` sends one raw request of the given method -- the POST
166
+ * the wire guard is meant to let through only under the exact conditions `resource.ts`'s own
167
+ * header names (no prior state, live absent).
168
+ */
169
+ const createProbeSpec = (
170
+ writeMethod: string,
171
+ ): UnifiSpec<
172
+ ProbeProps,
173
+ ProbeProps,
174
+ ProbeProps,
175
+ HttpClientError.HttpClientError,
176
+ never,
177
+ HttpClientError.HttpClientError
178
+ > => ({
179
+ type: 'Test.CreateProbe',
180
+ describe: (props) => `probe/${props.id}`,
181
+ fetchLive: () => Effect.succeed(undefined),
182
+ attributes: (live) => live,
183
+ matches: () => false,
184
+ create: {
185
+ allowedWrite: () => ({ method: 'POST' as const, tail: '/probe' }),
186
+ checkScope: () => Effect.void,
187
+ write: (props: ProbeProps) =>
188
+ Effect.gen(function* () {
189
+ const client = yield* HttpClient.HttpClient;
190
+ yield* client.execute(HttpClientRequest.make(writeMethod as never)(`${FAKE_BASE}/probe`));
191
+ return props;
192
+ }),
193
+ identify: (props: ProbeProps) => props,
194
+ driftOf: () => [],
195
+ },
196
+ });
197
+
156
198
  const configProvider = ConfigProvider.layer(
157
199
  ConfigProvider.fromUnknown({
158
200
  UNIFI_NETWORK_API_BASE_URL: FAKE_BASE,
@@ -216,3 +258,46 @@ describe('guards are installed in unifiHandlers (IMPORTANT-1, red team 2026-09-2
216
258
  expect(fake.seen).toEqual([]);
217
259
  });
218
260
  });
261
+
262
+ /**
263
+ * The hf: create lift's own wire-level proof (`resource.ts`'s `spec.create`-aware allow list,
264
+ * 2026-10-19): a POST reaches the transport ONLY when there is no prior state at all, and never
265
+ * otherwise -- even when the family declares `create` and the attempt is "first deploy shaped".
266
+ */
267
+ describe('guards are installed in unifiHandlers for spec.create (hf: create lift)', () => {
268
+ test('reconcile lets the one allowed POST through when there is no prior state', async () => {
269
+ const fake = fakeUnifi(() => Response.json({ id: 'w1' }));
270
+ const exit = await runHandler(
271
+ unifiHandlers(createProbeSpec('POST')).reconcile(args),
272
+ fake.fetch,
273
+ );
274
+ // The read-back then fails (this probe's fetchLive always answers absent) -- irrelevant to
275
+ // this test, which only proves the POST itself reached the transport.
276
+ expect(Exit.isFailure(exit)).toBe(true);
277
+ expect(fake.seen.map((s) => s.method)).toEqual(['POST']);
278
+ });
279
+
280
+ test('reconcile refuses a PUT even when the family declares create, not update', async () => {
281
+ const fake = neverReached('PUT');
282
+ expectRefused(
283
+ await runHandler(unifiHandlers(createProbeSpec('PUT')).reconcile(args), fake.fetch),
284
+ 'PUT',
285
+ );
286
+ expect(fake.seen).toEqual([]);
287
+ });
288
+
289
+ test('a POST is refused when prior state is present (olds/output set) -- not a fresh create', async () => {
290
+ // ⚠️ A TYPED refusal here, not a wire-guard die: with `olds`/`output` set, `ops.reconcile`
291
+ // never even builds the create allow entry (`resource.ts`'s own condition) and falls to
292
+ // `updateReconcile`, which refuses "create" typed (`UnifiWriteRefused`) because this probe
293
+ // declares no `update` -- the request never reaches the transport either way.
294
+ const fake = neverReached('POST');
295
+ const withState = { ...args, olds: { id: 'w1' }, output: { id: 'w1' } };
296
+ const exit = await runHandler(
297
+ unifiHandlers(createProbeSpec('POST')).reconcile(withState),
298
+ fake.fetch,
299
+ );
300
+ expect(Exit.isFailure(exit)).toBe(true);
301
+ expect(fake.seen).toEqual([]);
302
+ });
303
+ });
@@ -14,6 +14,13 @@
14
14
  * cloud-connector base paths differ, so the base is passed in and compared exactly, `isAllowed`).
15
15
  * A PUT to another network id, the list route, or another family dies here.
16
16
  *
17
+ * ⛔ POST, ADDED 2026-10-19 FOR THE hf: CREATE LIFT (`policy.ts`'s `isHfPolicyName`). The allow
18
+ * entry `unifiHandlers` builds for a create attempt (`spec.create.allowedWrite()`) names the
19
+ * site's fixed `/v1/sites/<siteId>/firewall/policies` COLLECTION route, never a per-row path —
20
+ * there is no row id before the POST creates one. This list is still the ONLY gate on method and
21
+ * path; the hf: NAME check is `create-reconcile.ts`'s `checkScope`, run before the POST is even
22
+ * attempted, not something this guard can see (it has no view of the request body).
23
+ *
17
24
  * ⚠️ `Effect.die`, NOT A TYPED FAILURE — every generated SDK operation declares its OWN closed
18
25
  * error union from the pinned OpenAPI spec and generated files are never hand-edited, so a typed
19
26
  * `Effect.fail` here would be a failure absent from every op's declared type. A defect is the
@@ -42,7 +49,7 @@ import { UNIFI_WRITE_POLICY } from './policy.ts';
42
49
  * base URL (`/v1/sites/<site>/networks/<id>`): the guard compares the request's origin and path to
43
50
  * `baseUrl + tail` and requires an empty query and fragment (`isAllowed`).
44
51
  */
45
- export type AllowedWrite = { readonly method: 'PUT'; readonly tail: string };
52
+ export type AllowedWrite = { readonly method: 'PUT' | 'POST'; readonly tail: string };
46
53
 
47
54
  export class UnifiRefusedRequest extends Error {
48
55
  constructor(
@@ -56,14 +56,16 @@ const sdkWriteOpNames = (): ReadonlySet<string> => {
56
56
  const REAL_WRITE_OP_NAMES = sdkWriteOpNames();
57
57
 
58
58
  /**
59
- * ⛔ THE ONE FILE ALLOWED TO NAME ONE WRITE OP (Tim 2026-10-10: `Unifi.Network` update). Keyed by
60
- * path relative to `src/unifi`. Widening this object is a policy change — `policy.ts` — and a
61
- * test below pins it to exactly this entry so it cannot grow unnoticed. Everything else the SDK
62
- * exports (`createNetwork`, `deleteNetwork`, `patchFirewallPolicy`, …) stays an offense even
63
- * inside the allowlisted file.
59
+ * ⛔ THE TWO FILES ALLOWED TO NAME ONE WRITE OP EACH (Tim 2026-10-10: `Unifi.Network` update; Tim
60
+ * 2026-10-19 ~16:20Z: `Unifi.FirewallPolicy` create, hf:-named rows only). Keyed by path relative
61
+ * to `src/unifi`. Widening this object is a policy change — `policy.ts` — and a test below pins
62
+ * it to exactly these two entries so it cannot grow unnoticed. Everything else the SDK exports
63
+ * (`createNetwork`, `deleteNetwork`, `patchFirewallPolicy`, …) stays an offense even inside an
64
+ * allowlisted file.
64
65
  */
65
66
  const WRITE_OP_ALLOWLIST: Record<string, ReadonlyArray<string>> = {
66
67
  'network-update.ts': ['updateNetwork'],
68
+ 'firewall-policy-create.ts': ['createFirewallPolicy'],
67
69
  };
68
70
 
69
71
  /** Recursive by construction (IMPORTANT-2): a future `src/unifi/<subdir>/*.ts` is walked too. */
@@ -187,8 +189,11 @@ describe('src/unifi never references an SDK write operation (T12)', () => {
187
189
  }
188
190
  });
189
191
 
190
- test('the allowlist is exactly network-update.ts -> updateNetwork, nothing else', () => {
191
- expect(WRITE_OP_ALLOWLIST).toEqual({ 'network-update.ts': ['updateNetwork'] });
192
+ test('the allowlist is exactly these two files, nothing else', () => {
193
+ expect(WRITE_OP_ALLOWLIST).toEqual({
194
+ 'network-update.ts': ['updateNetwork'],
195
+ 'firewall-policy-create.ts': ['createFirewallPolicy'],
196
+ });
192
197
  });
193
198
 
194
199
  test('network-update.ts may name updateNetwork but still not create/delete/patch ops', () => {
@@ -200,6 +205,15 @@ describe('src/unifi never references an SDK write operation (T12)', () => {
200
205
  }
201
206
  });
202
207
 
208
+ test('firewall-policy-create.ts may name createFirewallPolicy but still not update/delete ops', () => {
209
+ const path = join(DIR, 'firewall-policy-create.ts');
210
+ const src = (op: string) => `import * as f from '${SDK_MARKER}/firewall';\nf.${op}({});\n`;
211
+ expect(offensesIn(path, src('createFirewallPolicy'))).toEqual([]);
212
+ for (const op of ['updateFirewallPolicy', 'deleteFirewallPolicy', 'patchFirewallPolicy']) {
213
+ expect(offensesIn(path, src(op))).toEqual([`${path}: references SDK write op '${op}'`]);
214
+ }
215
+ });
216
+
203
217
  test('any other file naming updateNetwork is an offense', () => {
204
218
  const path = join(DIR, 'network.ts');
205
219
  const src = `import * as n from '${SDK_MARKER}/networks';\nn.updateNetwork({});\n`;