@homeflare/alchemy 0.44.0 → 0.46.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 (130) hide show
  1. package/dist/postgres/fake-role-sql.d.ts +2 -1
  2. package/dist/postgres/fake-role-sql.d.ts.map +1 -1
  3. package/dist/postgres/fake-sql.d.ts +3 -3
  4. package/dist/postgres/fake-sql.d.ts.map +1 -1
  5. package/dist/postgres/index.d.ts +1 -1
  6. package/dist/postgres/index.d.ts.map +1 -1
  7. package/dist/postgres/index.js +117 -94
  8. package/dist/postgres/index.js.map +11 -11
  9. package/dist/postgres/role-attrs.d.ts +7 -4
  10. package/dist/postgres/role-attrs.d.ts.map +1 -1
  11. package/dist/postgres/role-diff.d.ts +2 -2
  12. package/dist/postgres/role-diff.d.ts.map +1 -1
  13. package/dist/postgres/role-errors.d.ts +13 -7
  14. package/dist/postgres/role-errors.d.ts.map +1 -1
  15. package/dist/postgres/role-membership-sql.d.ts +30 -27
  16. package/dist/postgres/role-membership-sql.d.ts.map +1 -1
  17. package/dist/postgres/role-provider.d.ts.map +1 -1
  18. package/dist/postgres/role-scram.d.ts +2 -0
  19. package/dist/postgres/role-scram.d.ts.map +1 -1
  20. package/dist/postgres/role-secrets.d.ts +8 -0
  21. package/dist/postgres/role-secrets.d.ts.map +1 -1
  22. package/dist/postgres/role-sql.d.ts.map +1 -1
  23. package/dist/postgres/role.d.ts +2 -2
  24. package/dist/postgres/role.d.ts.map +1 -1
  25. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  26. package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
  27. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
  28. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
  29. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
  30. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
  31. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
  32. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
  33. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
  34. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
  35. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
  36. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
  37. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
  38. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
  39. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
  40. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts.map +1 -1
  41. package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts +1 -1
  42. package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
  43. package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
  44. package/dist/proxmox/index.d.ts +1 -0
  45. package/dist/proxmox/index.d.ts.map +1 -1
  46. package/dist/proxmox/index.js +283 -130
  47. package/dist/proxmox/index.js.map +31 -27
  48. package/dist/proxmox/qemu-dns.d.ts +2 -0
  49. package/dist/proxmox/qemu-dns.d.ts.map +1 -0
  50. package/dist/proxmox/qemu-errors.d.ts +1 -0
  51. package/dist/proxmox/qemu-errors.d.ts.map +1 -1
  52. package/dist/proxmox/qemu-form.d.ts.map +1 -1
  53. package/dist/proxmox/qemu-judge.d.ts.map +1 -1
  54. package/dist/proxmox/qemu-lifecycle.d.ts +15 -10
  55. package/dist/proxmox/qemu-lifecycle.d.ts.map +1 -1
  56. package/dist/proxmox/qemu-props.d.ts +10 -0
  57. package/dist/proxmox/qemu-props.d.ts.map +1 -1
  58. package/dist/proxmox/qemu-resize.d.ts +10 -0
  59. package/dist/proxmox/qemu-resize.d.ts.map +1 -0
  60. package/dist/proxmox/qemu-size.d.ts +20 -0
  61. package/dist/proxmox/qemu-size.d.ts.map +1 -0
  62. package/dist/proxmox/qemu-spec.d.ts +25 -0
  63. package/dist/proxmox/qemu-spec.d.ts.map +1 -0
  64. package/dist/verify/fake-engine.d.ts +4 -2
  65. package/dist/verify/fake-engine.d.ts.map +1 -1
  66. package/docs/postgres-role.md +18 -6
  67. package/docs/proxmox-qemu-resize.md +62 -0
  68. package/package.json +1 -1
  69. package/src/ceph/ceph-argv.test.ts +1 -1
  70. package/src/launchd/sudo-allowlist.test.ts +2 -2
  71. package/src/linux/sudo-allowlist.test.ts +3 -3
  72. package/src/linux/sudo-runner.test.ts +2 -2
  73. package/src/postgres/fake-role-sql.ts +26 -6
  74. package/src/postgres/fake-sql.ts +10 -5
  75. package/src/postgres/index.ts +1 -0
  76. package/src/postgres/role-attrs.ts +7 -4
  77. package/src/postgres/role-diff.ts +7 -3
  78. package/src/postgres/role-errors.ts +22 -14
  79. package/src/postgres/role-guard.test.ts +3 -3
  80. package/src/postgres/role-identity.test.ts +14 -2
  81. package/src/postgres/role-lifecycle.test.ts +2 -2
  82. package/src/postgres/role-membership-options.test.ts +73 -0
  83. package/src/postgres/role-membership-repair.test.ts +153 -0
  84. package/src/postgres/role-membership-sql.ts +88 -63
  85. package/src/postgres/role-password-ascii.test.ts +53 -0
  86. package/src/postgres/role-password-transports.test.ts +123 -0
  87. package/src/postgres/role-privilege.test.ts +37 -10
  88. package/src/postgres/role-provenance.test.ts +1 -1
  89. package/src/postgres/role-provider.ts +3 -2
  90. package/src/postgres/role-refusals.test.ts +0 -25
  91. package/src/postgres/role-scram.ts +2 -0
  92. package/src/postgres/role-secrets.ts +15 -0
  93. package/src/postgres/role-sql.ts +3 -1
  94. package/src/postgres/role.ts +9 -1
  95. package/src/postgres/schema-socket.test.ts +1 -0
  96. package/src/proxmox/generated/constraints/index.ts +1 -1
  97. package/src/proxmox/generated/constraints/pve-access.ts +1 -1
  98. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
  99. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
  100. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
  101. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
  102. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
  103. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
  104. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
  105. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
  106. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
  107. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
  108. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
  109. package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
  110. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +6 -1
  111. package/src/proxmox/generated/constraints/pve-nodes-storage.ts +1 -1
  112. package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
  113. package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
  114. package/src/proxmox/index.ts +1 -0
  115. package/src/proxmox/qemu-dns.test.ts +73 -0
  116. package/src/proxmox/qemu-dns.ts +32 -0
  117. package/src/proxmox/qemu-errors.ts +1 -0
  118. package/src/proxmox/qemu-form.ts +6 -3
  119. package/src/proxmox/qemu-judge.ts +2 -1
  120. package/src/proxmox/qemu-lifecycle.ts +28 -47
  121. package/src/proxmox/qemu-props.ts +14 -2
  122. package/src/proxmox/qemu-resize-lock.test.ts +121 -0
  123. package/src/proxmox/qemu-resize-recovery.test.ts +81 -0
  124. package/src/proxmox/qemu-resize.test.ts +199 -0
  125. package/src/proxmox/qemu-resize.ts +86 -0
  126. package/src/proxmox/qemu-size.test.ts +64 -0
  127. package/src/proxmox/qemu-size.ts +95 -0
  128. package/src/proxmox/qemu-spec.ts +44 -0
  129. package/src/proxmox/qemu-volume.ts +5 -5
  130. package/src/verify/fake-engine.ts +6 -3
@@ -0,0 +1,2 @@
1
+ export declare const dnsRefusals: (nameserver: unknown) => string[];
2
+ //# sourceMappingURL=qemu-dns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qemu-dns.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-dns.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,WAAW,eAAgB,OAAO,KAAG,MAAM,EAQvD,CAAC"}
@@ -1,5 +1,6 @@
1
1
  /** A QEMU write or observation this provider will not turn into a create or a delete. */
2
2
  export declare class QemuRefusedError extends Error {
3
+ readonly _tag = "QemuRefusedError";
3
4
  constructor(message: string);
4
5
  }
5
6
  //# sourceMappingURL=qemu-errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"qemu-errors.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-errors.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF"}
1
+ {"version":3,"file":"qemu-errors.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-errors.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,sBAAsB;IACnC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"qemu-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-form.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,iBAAiB,CAAC;AAEzB;;;;GAIG;AACH,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,EAG6C,CAAC;AAElG,2FAA2F;AAC3F,eAAO,MAAM,IAAI,UAAW,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAO1D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,UAAW,OAAO;;CAGvC,CAAC;AAEH,qGAAqG;AACrG,eAAO,MAAM,UAAU,aAAO,CAAC"}
1
+ {"version":3,"file":"qemu-form.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-form.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,iBAAiB,CAAC;AAGzB;;;;GAIG;AACH,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,EAKnD,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,IAAI,UAAW,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAO1D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,UAAW,OAAO;;CAGvC,CAAC;AAEH,qGAAqG;AACrG,eAAO,MAAM,UAAU,aAAO,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"qemu-judge.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-judge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,KAAK,OAAO,EAAsD,MAAM,iBAAiB,CAAC;AAInG,MAAM,MAAM,QAAQ,GAAG;IACrB,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,KAAK,UAAW,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,QA8CtE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,OAAO,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAI1B,CAAC"}
1
+ {"version":3,"file":"qemu-judge.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-judge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,KAAK,OAAO,EAAsD,MAAM,iBAAiB,CAAC;AAKnG,MAAM,MAAM,QAAQ,GAAG;IACrB,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,KAAK,UAAW,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,QA8CtE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,OAAO,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAI1B,CAAC"}
@@ -1,40 +1,45 @@
1
1
  import type { Input } from 'alchemy/Input';
2
2
  import * as nodes from '@distilled.cloud/proxmox/nodes';
3
3
  import * as Effect from 'effect/Effect';
4
- import { type VmAttributes, type VmProps } from './qemu-props.ts';
5
- export declare const readQemu: (props: VmProps) => Effect.Effect<VmAttributes | undefined, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
4
+ import type * as HttpClient from 'effect/unstable/http/HttpClient';
5
+ import { QemuRefusedError } from './qemu-errors.ts';
6
+ import type { VmAttributes, VmProps } from './qemu-props.ts';
7
+ import { type QemuDiskResizeRefused } from './qemu-size.ts';
8
+ type QemuLifecycleError = Effect.Error<ReturnType<typeof readQemu>> | nodes.PutNodeQemuResizeError | QemuRefusedError | QemuDiskResizeRefused;
9
+ export declare const readQemu: (props: VmProps) => Effect.Effect<VmAttributes | undefined, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, HttpClient.HttpClient>;
6
10
  /** No purge, no skiplock, no destroy-unreferenced-disks. A missing config is already gone. */
7
- export declare const deleteQemu: (props: VmProps) => Effect.Effect<void | undefined, Error | import("./credential-errors.ts").PveCredentialDenied | nodes.ProxmoxOpError, import("effect/unstable/http/HttpClient").HttpClient>;
11
+ export declare const deleteQemu: (props: VmProps) => Effect.Effect<void | undefined, Error | import("./credential-errors.ts").PveCredentialDenied | nodes.ProxmoxOpError, HttpClient.HttpClient>;
8
12
  export declare const qemuHandlers: {
9
13
  list: () => Effect.Effect<never[], never, never>;
10
14
  /**
11
15
  * ⛔ `Unowned` UNLESS STATE ALREADY VOUCHES FOR IT (C2). With no attributes this is Alchemy's
12
16
  * adoption probe or the recovery read for an interrupted create (ownership/probe.ts); `settled`
13
- * is the same `judge` reconcile uses, so "proven ours" and "reconcile would write nothing" are
14
- * the same question asked once.
17
+ * uses the same config comparison as reconcile. Disk growth is a post-create step, so its
18
+ * drift cannot disown a VM after an interrupted/failed resize (measured 2026-10-05).
15
19
  */
16
20
  read: (args_0: {
17
21
  fqn: string;
18
22
  instanceId: string;
19
23
  olds: VmProps;
20
24
  output: VmAttributes | undefined;
21
- }) => Effect.Effect<VmAttributes | undefined, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
25
+ }) => Effect.Effect<VmAttributes | undefined, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, HttpClient.HttpClient>;
22
26
  diff: (args_0: {
23
27
  news: Input<VmProps>;
24
28
  olds: VmProps;
25
29
  output: VmAttributes | undefined;
26
30
  }) => Effect.Effect<{
27
- readonly action: "noop" | "update";
28
- } | undefined, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
31
+ readonly action: 'noop' | 'update';
32
+ } | undefined, QemuLifecycleError, HttpClient.HttpClient>;
29
33
  reconcile: (args_0: {
30
34
  fqn: string;
31
35
  instanceId: string;
32
36
  news: VmProps;
33
37
  olds: VmProps | undefined;
34
38
  output: VmAttributes | undefined;
35
- }) => Effect.Effect<VmAttributes, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
39
+ }) => Effect.Effect<VmAttributes, QemuLifecycleError, HttpClient.HttpClient>;
36
40
  delete: ({ olds }: {
37
41
  olds: VmProps;
38
- }) => Effect.Effect<void | undefined, Error | import("./credential-errors.ts").PveCredentialDenied | nodes.ProxmoxOpError, import("effect/unstable/http/HttpClient").HttpClient>;
42
+ }) => Effect.Effect<void | undefined, Error | import("./credential-errors.ts").PveCredentialDenied | nodes.ProxmoxOpError, HttpClient.HttpClient>;
39
43
  };
44
+ export {};
40
45
  //# sourceMappingURL=qemu-lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"qemu-lifecycle.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-lifecycle.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,KAAK,MAAM,gCAAgC,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAOxC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,OAAO,EAMb,MAAM,iBAAiB,CAAC;AAwCzB,eAAO,MAAM,QAAQ,UAAW,OAAO,iOAapC,CAAC;AAEJ,8FAA8F;AAC9F,eAAO,MAAM,UAAU,UAAW,OAAO,+KAMyB,CAAC;AAmBnE,eAAO,MAAM,YAAY;IACvB,IAAI;IACJ;;;;;OAKG;IACH,IAAI;aAMG,MAAM;oBACC,MAAM;cACZ,OAAO;gBACL,YAAY,GAAG,SAAS;;IAQlC,IAAI;cAKI,KAAK,CAAC,OAAO,CAAC;cACd,OAAO;gBACL,YAAY,GAAG,SAAS;;;;IAwBlC,SAAS;aAOF,MAAM;oBACC,MAAM;cACZ,OAAO;cACP,OAAO,GAAG,SAAS;gBACjB,YAAY,GAAG,SAAS;;IAoDlC,MAAM,aAAa;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE;CACrC,CAAC"}
1
+ {"version":3,"file":"qemu-lifecycle.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-lifecycle.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,KAAK,MAAM,gCAAgC,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAKnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI7D,OAAO,EAAE,KAAK,qBAAqB,EAAyC,MAAM,gBAAgB,CAAC;AAOnG,KAAK,kBAAkB,GACnB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,GACzC,KAAK,CAAC,sBAAsB,GAC5B,gBAAgB,GAChB,qBAAqB,CAAC;AAE1B,eAAO,MAAM,QAAQ,UAAW,OAAO,kMAapC,CAAC;AAEJ,8FAA8F;AAC9F,eAAO,MAAM,UAAU,UAAW,OAAO,gJAMyB,CAAC;AAmBnE,eAAO,MAAM,YAAY;IACvB,IAAI;IACJ;;;;;OAKG;IACH,IAAI;aAMG,MAAM;oBACC,MAAM;cACZ,OAAO;gBACL,YAAY,GAAG,SAAS;;IAUlC,IAAI;cAKI,KAAK,CAAC,OAAO,CAAC;cACd,OAAO;gBACL,YAAY,GAAG,SAAS;;yBAEb,MAAM,GAAG,QAAQ;;IA4BtC,SAAS;aAOF,MAAM;oBACC,MAAM;cACZ,OAAO;cACP,OAAO,GAAG,SAAS;gBACjB,YAAY,GAAG,SAAS;;IAuDlC,MAAM,aAAa;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE;CACrC,CAAC"}
@@ -46,6 +46,16 @@ export interface VmProps extends WithTarget {
46
46
  boot?: string;
47
47
  /** e.g. `virtio-scsi-single`. */
48
48
  scsihw?: string;
49
+ /** Cloud-init DNS servers: PVE's `address-list` (IP addresses or DNS names). */
50
+ nameserver?: string;
51
+ /** Cloud-init DNS search domains, in PVE's wire spelling. */
52
+ searchdomain?: string;
53
+ /**
54
+ * Minimum disk sizes in GiB, e.g. `{ scsi0: 64 }`, grown after import completes.
55
+ * Existing larger disks are left alone. Lowering a previously declared target is refused.
56
+ * These are resize targets, never config fields; declare the corresponding disk slot too.
57
+ */
58
+ diskSizesGiB?: Partial<Record<`scsi${number}` | `ide${number}`, number>>;
49
59
  /**
50
60
  * `local-zfs:32` to create a new volume, or the live volume string as adopted —
51
61
  * `cephtb4:vm-101-disk-0,size=32G` (n is 0 to 30, pve-qemu-server's own `$MAX_SCSI_DISKS`).
@@ -1 +1 @@
1
- {"version":3,"file":"qemu-props.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-props.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,kGAAkG;AAClG,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;AAEtC,MAAM,WAAW,OAAQ,SAAQ,UAAU;IACzC,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,CAAC,IAAI,EAAE,OAAO,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,CAAC,GAAG,EAAE,MAAM,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;OAIG;IACH,CAAC,SAAS,EAAE,MAAM,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,mFAAmF;IACnF,CAAC,QAAQ,EAAE,WAAW,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD,gFAAgF;IAChF,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,kCAAkC;IAClC,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,wCAAwC;IACxC,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAKD,8FAA8F;AAC9F,eAAO,MAAM,OAAO;aAClB,GAAG,EAAE,CAAC;aACN,QAAQ,EAAE,EAAE;aACZ,GAAG,EAAE,EAAE;aACP,IAAI,EAAE,EAAE;aACR,MAAM,EAAE,CAAC;CACD,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,OAAO,CAAC;AAEjD,4FAA4F;AAC5F,eAAO,MAAM,UAAU,QAAS,MAAM,KAAG,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,GAAG,SAM3E,CAAC;AAeF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAS,MAAM,KAAG,OACY,CAAC;AAExD,6GAA6G;AAC7G,eAAO,MAAM,aAAa,UAAW,OAAO,OAAO,MAAM,KAAG,OACR,CAAC;AAErD,wFAAwF;AACxF,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,EAGzC,CAAC;AAEZ,iGAAiG;AACjG,eAAO,MAAM,SAAS,UAAW,OAAO,KAAG,MACmB,CAAC;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,SAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAQjF,CAAC"}
1
+ {"version":3,"file":"qemu-props.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-props.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,kGAAkG;AAClG,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;AAEtC,MAAM,WAAW,OAAQ,SAAQ,UAAU;IACzC,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,MAAM,EAAE,GAAG,MAAM,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACzE;;;OAGG;IACH,CAAC,IAAI,EAAE,OAAO,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,CAAC,GAAG,EAAE,MAAM,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;OAIG;IACH,CAAC,SAAS,EAAE,MAAM,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,mFAAmF;IACnF,CAAC,QAAQ,EAAE,WAAW,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD,gFAAgF;IAChF,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,kCAAkC;IAClC,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,wCAAwC;IACxC,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAKD,8FAA8F;AAC9F,eAAO,MAAM,OAAO;aAClB,GAAG,EAAE,CAAC;aACN,QAAQ,EAAE,EAAE;aACZ,GAAG,EAAE,EAAE;aACP,IAAI,EAAE,EAAE;aACR,MAAM,EAAE,CAAC;CACD,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,OAAO,CAAC;AAEjD,4FAA4F;AAC5F,eAAO,MAAM,UAAU,QAAS,MAAM,KAAG,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,GAAG,SAM3E,CAAC;AAiBF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAS,MAAM,KAAG,OACY,CAAC;AAExD,6GAA6G;AAC7G,eAAO,MAAM,aAAa,UAAW,OAAO,OAAO,MAAM,KAAG,OACR,CAAC;AAErD,wFAAwF;AACxF,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,EAGzC,CAAC;AAEZ,iGAAiG;AACjG,eAAO,MAAM,SAAS,UAAW,OAAO,KAAG,MACmB,CAAC;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,SAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAQjF,CAAC"}
@@ -0,0 +1,10 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import { type VmProps } from './qemu-props.ts';
3
+ /**
4
+ * ⛔ Observe the unlocked, allocated disk before each resize, including recovery after import.
5
+ * Absolute targets make retries safe; `+60G` would grow again after interrupted state writes.
6
+ * This resource never starts a guest. Its caller can power on only after reconcile returns.
7
+ * SDK failures retain their generated tags; provider refusals are QemuDiskResizeRefused.
8
+ */
9
+ export declare const resizeDisks: (props: VmProps) => Effect.Effect<undefined, Error | import("effect/unstable/http/HttpClientError").HttpClientError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
10
+ //# sourceMappingURL=qemu-resize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qemu-resize.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-resize.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,OAAO,EAAgB,MAAM,iBAAiB,CAAC;AAqC7D;;;;;GAKG;AACH,eAAO,MAAM,WAAW,UAAW,OAAO,kNAuCtC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import { type VmProps } from './qemu-props.ts';
3
+ declare const QemuDiskResizeRefused_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "QemuDiskResizeRefused";
5
+ } & Readonly<A>;
6
+ export declare class QemuDiskResizeRefused extends QemuDiskResizeRefused_base<{
7
+ readonly disk: string;
8
+ readonly message: string;
9
+ }> {
10
+ }
11
+ /** PVE config reports binary sizes, including fractional G and integer M/K for imported images. */
12
+ export declare const diskBytes: (config: string | undefined) => number | undefined;
13
+ /** Validate the whole declaration before any write, including a later attempted target decrease. */
14
+ export declare const validateDiskSizes: (props: VmProps, olds?: VmProps) => Effect.Effect<undefined, QemuDiskResizeRefused, never>;
15
+ /** Capacity drift drives reconcile; recovery ownership ignores it because resize follows create. */
16
+ export declare const diskSizeDrift: (props: VmProps, config: Record<string, string>) => string[];
17
+ /** Missing slots may be imported by the upcoming config write; unreadable existing sizes fail closed. */
18
+ export declare const checkLiveDiskSizes: (props: VmProps, config: Record<string, string>, allowMissing?: boolean) => Effect.Effect<undefined, QemuDiskResizeRefused, never>;
19
+ export {};
20
+ //# sourceMappingURL=qemu-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qemu-size.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-size.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,OAAO,EAA6B,MAAM,iBAAiB,CAAC;;;;AAG1E,qBAAa,qBAAsB,SAAQ,2BAA0C;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL,mGAAmG;AACnG,eAAO,MAAM,SAAS,WAAY,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAO/D,CAAC;AAEF,oGAAoG;AACpG,eAAO,MAAM,iBAAiB,UAAW,OAAO,SAAS,OAAO,2DAwC5D,CAAC;AAEL,oGAAoG;AACpG,eAAO,MAAM,aAAa,UAAW,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,MAAM,EAKzC,CAAC;AAE7C,yGAAyG;AACzG,eAAO,MAAM,kBAAkB,UACtB,OAAO,UACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,mFAmB5B,CAAC"}
@@ -0,0 +1,25 @@
1
+ /** QEMU config forms and vendor guards, separated from lifecycle to keep both reviewable. */
2
+ import { createForm } from './qemu-form.ts';
3
+ import { type VmAttributes, type VmProps } from './qemu-props.ts';
4
+ export declare const attributesOf: (live: Record<string, unknown>, props: VmProps) => VmAttributes;
5
+ /**
6
+ * ⚠️ KEPT ONLY TO SATISFY `PveSpec`'S REQUIRED FIELD — `qemu-judge.ts`'s `judge` is what diff and
7
+ * reconcile actually decide by, since a plain string compare is exactly the bug C1 found (it
8
+ * never matches the "new disk" or MAC-less spellings a declaration may use).
9
+ */
10
+ declare const matches: (attributes: VmAttributes, props: VmProps) => boolean;
11
+ export declare const spec: {
12
+ attributes: typeof attributesOf;
13
+ collection: (props: VmProps) => string;
14
+ createForm: typeof createForm;
15
+ endpoint: {
16
+ create: string;
17
+ update: string;
18
+ };
19
+ matches: typeof matches;
20
+ path: (props: VmProps) => string;
21
+ updateForm: typeof import("./qemu-form.ts").form;
22
+ };
23
+ export declare const guardCreate: (props: VmProps, presence: boolean) => import("effect/Effect").Effect<void>, guardUpdate: (props: VmProps) => import("effect/Effect").Effect<void>;
24
+ export {};
25
+ //# sourceMappingURL=qemu-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qemu-spec.d.ts","sourceRoot":"","sources":["../../src/proxmox/qemu-spec.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,OAAO,EAAE,UAAU,EAAc,MAAM,gBAAgB,CAAC;AACxD,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,OAAO,EAMb,MAAM,iBAAiB,CAAC;AAIzB,eAAO,MAAM,YAAY,SAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,OAAO,KAAG,YAI3E,CAAC;AAEH;;;;GAIG;AACH,QAAA,MAAM,OAAO,eAAgB,YAAY,SAAS,OAAO,YAG2B,CAAC;AAErF,eAAO,MAAM,IAAI;;wBAEK,OAAO;;;QAGzB,MAAM;QACN,MAAM;;;kBAGM,OAAO;;CAEmB,CAAC;AAE3C,eAAO,MAAQ,WAAW,+EAAE,WAAW,0DAAqB,CAAC"}
@@ -53,7 +53,7 @@ export type DriftRow = {
53
53
  readonly action: string;
54
54
  };
55
55
  export interface FakeEngine {
56
- /** Plan and apply `body`, as `alchemy deploy --adopt` would; resolves to the planned actions. */
56
+ /** Plan and apply `body` with the configured adoption policy; resolves to planned actions. */
57
57
  readonly deploy: (body: Body) => Promise<Record<string, string>>;
58
58
  /** Verify `body` against what the earlier deploys recorded. */
59
59
  readonly verify: (body: Body, options?: VerifyOptions) => Promise<AdoptReport>;
@@ -78,7 +78,9 @@ export interface FakeEngine {
78
78
  * hands in its real provider over a fake transport (proxmox/ceph-pool-adopt.test.ts).
79
79
  * ⚠️ THE CASTS ARE AT THE ENGINE'S TYPED BOUNDARY — openbao/fake-stack.ts has the reasoning.
80
80
  */
81
- export declare const engineOver: <ROut, E, RIn>(layer: Layer.Layer<ROut, E, RIn>) => FakeEngine;
81
+ export declare const engineOver: <ROut, E, RIn>(layer: Layer.Layer<ROut, E, RIn>, options?: {
82
+ readonly adopt?: boolean;
83
+ }) => FakeEngine;
82
84
  /** The fake family over `cloud`. */
83
85
  export declare const fakeEngine: (cloud: Cloud) => FakeEngine;
84
86
  //# sourceMappingURL=fake-engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fake-engine.d.ts","sourceRoot":"","sources":["../../src/verify/fake-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAKnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAiB,MAAM,aAAa,CAAC;AAElF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,KAAM,SAAQ,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,eAAe,CAAC;CAAG;AAC7F,eAAO,MAAM,KAAK,8BAAwC,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,WAAW,KAAM,SAAQ,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,eAAe,CAAC;CAAG;AAC7F,eAAO,MAAM,KAAK,8BAAwC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,QAAS,SAAQ,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE,UAAU,EAAE,eAAe,CAAC;CAAG;AACnG,eAAO,MAAM,QAAQ,iCAA8C,CAAC;AAEpE,gGAAgG;AAChG,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAQ,KAI1D,CAAC;AAEH,8CAA8C;AAC9C,eAAO,MAAM,QAAQ,UAAW,KAAK,KAAG,MAAM,EAC2C,CAAC;AAyB1F,4FAA4F;AAC5F,eAAO,MAAM,cAAc,UAAW,KAAK,8GAmCxC,CAAC;AAEJ,oFAAoF;AACpF,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAK5D,gGAAgG;AAChG,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,iGAAiG;IACjG,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjE,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/E;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClE;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,IAAI,EAAE,CAAC,EAAE,GAAG,SAAS,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,KAAG,UAmD3E,CAAC;AAEF,oCAAoC;AACpC,eAAO,MAAM,UAAU,UAAW,KAAK,KAAG,UAA+C,CAAC"}
1
+ {"version":3,"file":"fake-engine.d.ts","sourceRoot":"","sources":["../../src/verify/fake-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAKnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAiB,MAAM,aAAa,CAAC;AAElF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,KAAM,SAAQ,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,eAAe,CAAC;CAAG;AAC7F,eAAO,MAAM,KAAK,8BAAwC,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,WAAW,KAAM,SAAQ,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,eAAe,CAAC;CAAG;AAC7F,eAAO,MAAM,KAAK,8BAAwC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,QAAS,SAAQ,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE,UAAU,EAAE,eAAe,CAAC;CAAG;AACnG,eAAO,MAAM,QAAQ,iCAA8C,CAAC;AAEpE,gGAAgG;AAChG,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAQ,KAI1D,CAAC;AAEH,8CAA8C;AAC9C,eAAO,MAAM,QAAQ,UAAW,KAAK,KAAG,MAAM,EAC2C,CAAC;AAyB1F,4FAA4F;AAC5F,eAAO,MAAM,cAAc,UAAW,KAAK,8GAmCxC,CAAC;AAEJ,oFAAoF;AACpF,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAK5D,gGAAgG;AAChG,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjE,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/E;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClE;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,IAAI,EAAE,CAAC,EAAE,GAAG,SAC9B,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,YACvB;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,KACpC,UAmDF,CAAC;AAEF,oCAAoC;AACpC,eAAO,MAAM,UAAU,UAAW,KAAK,KAAG,UAA+C,CAAC"}
@@ -26,16 +26,26 @@ run yourself. The `Postgres.Database` resource in this kit already binds `@effec
26
26
  never re-sends a secret that already matches, and never stores one. A create that declares a
27
27
  password but whose variable is unset refuses before any write
28
28
  (`PostgresRolePasswordEnvUnsetError`), including the retry of a create whose password statement
29
- never sealed.
29
+ never sealed. Non-ASCII passwords fail typed before writes (`PostgresRolePasswordNonAsciiRefused`):
30
+ PostgreSQL applies SASLprep, and this provider does not implement that normalization.
30
31
  - **Membership is `pg_auth_members`, compared as a sorted set.** `memberOf` omitted leaves live
31
32
  memberships alone; `[]` ensures none. Moves run through one-parent `GRANT`/`REVOKE`
32
33
  (`alter_role.sgml`: "there are no options for adding or removing memberships; use GRANT and
33
34
  REVOKE"). A plain `REVOKE` only removes the session grantor's row (`plan_single_revoke` matches
34
35
  `grantor`); each unwanted row is `REVOKE … GRANTED BY` its own grantor. A wanted row that is
35
- `WITH ADMIN` or still `SET TRUE` is repaired in place with one
36
- `GRANT … WITH ADMIN FALSE, SET FALSE` (`AddRoleMems` updates that grantor's tuple). Another
37
- grantor's unsafe row is revoked by name afterwards. A row whose grantor role is gone cannot be
38
- named and fails `PostgresRoleMembershipUnrepaired`. A missing parent fails
36
+ `WITH ADMIN`, still `SET TRUE`, or `INHERIT TRUE` when `inherit:false` is declared is repaired
37
+ with `REVOKE ADMIN/SET/INHERIT OPTION FOR … GRANTED BY … RESTRICT`. Unwanted-row revokes commit
38
+ first, then each retained row's options share a repair transaction. SQLSTATE 2BP01/42501 becomes
39
+ `PostgresRoleMembershipUnrepaired` naming that parent and grantor, preserving refused options
40
+ and dependent rows without rolling back unwanted-row revokes. No CASCADE is issued.
41
+ Option repair was measured once by hand on a throwaway PostgreSQL 17.11 (2026-10-02);
42
+ automated tests stub the 2BP01/42501 responses. Semantics: PostgreSQL
43
+ [16 GRANT](https://www.postgresql.org/docs/16/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/16/sql-revoke.html),
44
+ [17 GRANT](https://www.postgresql.org/docs/17/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/17/sql-revoke.html),
45
+ [18 GRANT](https://www.postgresql.org/docs/18/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/18/sql-revoke.html).
46
+ `buildRepairMembershipSql` now requires the grantor and option (`ADMIN`, `SET` or `INHERIT`).
47
+ A null grantor is a defensive inconsistent-catalog case, not an ordinary DROP ROLE outcome,
48
+ and fails `PostgresRoleMembershipUnrepaired`. A missing parent fails
39
49
  `PostgresRoleParentMissing` before any statement.
40
50
  - **`defaultRemovalPolicy: 'retain'`.** A seat group role may own objects and be granted across
41
51
  databases. A destroy is still implemented in full (`DROP ROLE IF EXISTS`, idempotent), opted in
@@ -47,7 +57,9 @@ run yourself. The `Postgres.Database` resource in this kit already binds `@effec
47
57
  says `WITH SET FALSE`. Create runs `CREATE`, the password `ALTER` and the seat `GRANT`s in one
48
58
  transaction, so a later statement failure does not leave a LOGIN role behind. `diff` reads
49
59
  `pg_auth_members` live — stored attributes omit the option rows, and the engine passes stored
50
- attributes — so `ADMIN` or `SET TRUE` plans as `update`. A live oid that is not the stored oid
60
+ attributes — so unsafe `ADMIN`, `SET` or `INHERIT` plans as `update`. Privileged catalog flags also plan
61
+ `update`, reaching the typed reconcile refusal. `read` omits membership option rows just as
62
+ reconcile does, so an unchanged role does not report structural drift. A live oid that is not the stored oid
51
63
  is `PostgresRoleIdentityRefused` on read, diff, reconcile and delete: the name was recreated
52
64
  out of band and is not dropped or altered.
53
65
 
@@ -0,0 +1,62 @@
1
+ # Cloud-init DNS and imported disk growth
2
+
3
+ ```ts
4
+ Proxmox.Vm('talos', {
5
+ target,
6
+ node: 'n2',
7
+ vmid: 10000,
8
+ scsi0: 'cephtb4:0,import-from=cephfs-tb4:import/talos.raw',
9
+ diskSizesGiB: { scsi0: 64 },
10
+ ide2: 'cephtb4:cloudinit',
11
+ ipconfig0: 'ip=10.20.16.20/24,gw=10.20.16.254',
12
+ nameserver: '10.20.16.254',
13
+ searchdomain: 'example.internal',
14
+ });
15
+ ```
16
+
17
+ `diskSizesGiB` declares minimum capacity in positive integer GiB. Declare the disk
18
+ slot alongside its target. Equal or larger live disks need no resize; lowering a
19
+ previously declared target is refused with `QemuDiskResizeRefused`, which callers
20
+ can handle with `Effect.catchTag`. Removing a target leaves capacity unmanaged.
21
+ Unsupported slots, CD-ROM/cloud-init drives and unreadable live sizes are refused.
22
+
23
+ The provider waits for import, reads the allocated disk, sends an absolute target
24
+ through the generated `putNodeQemuResize` operation, waits for its task, and verifies
25
+ the resulting capacity. An interrupted create resumes without `--adopt` when its
26
+ config matches, even if disk growth remains unfinished. Each resize checks the raw
27
+ VM lock first; lock waits and resize task polling share a 60-observation budget per
28
+ disk. A persistent lock raises `QemuDiskResizeRefused` without a resize write.
29
+ Absolute targets let retries finish growth without adding capacity twice.
30
+ `size=` in a volume config string is still observed metadata: omit it when
31
+ using a resize target. An unrelated update never resends the import source.
32
+
33
+ This resource never starts a VM. Complete its reconciliation before the separate
34
+ power-on step. Resizing an existing guest does not grow its partitions/filesystems.
35
+
36
+ DNS keys use the same declared-key comparison as `ipconfig0`: omitted keys remain
37
+ unmanaged. `nameserver` enforces the generated `address-list` constraint; PVE permits
38
+ IP addresses or DNS names, separated by whitespace, commas or semicolons. The provider
39
+ preserves the supplied spelling. `searchdomain` has no extra schema constraint.
40
+
41
+ Schema provenance: `codegen/manifest.json`, pve-manager **9.2.11/f6997e698c7933ea**,
42
+ SHA-256 `9def8f13611184ee1c7d0399713130dfc4a065701d0d91a69b9c03df929344e9`.
43
+ QEMU resize already exists in both generated surfaces: distilled's `nodes.ts` and
44
+ the legacy generator's packed `nodes-node-qemu-vmid-mtunnel.ts`. A filename search
45
+ for `qemu-vmid-resize.ts` misses the latter; neither output needs hand editing.
46
+ Both caches were recovered byte-for-byte from public vendor sources, then
47
+ `bun codegen/constraints.ts` generated the resize guard. The existing type generator
48
+ already supports the operation. No generated file was edited by hand.
49
+
50
+ Recovery used the vendor Makefile's concatenation order: `api-viewer/apidata.js`
51
+ and `api-viewer/PVEAPI.js` from proxmox/pve-docs
52
+ `4e7ff22c70eb35c63e65b0ba65c98980cb4d7bf5`, then `src/api-viewer/APIViewer.js`
53
+ from proxmox/proxmox-widget-toolkit `9204a77880eb31aff6ffc8bcf65ca48539685ab1`.
54
+ The result is exactly 4,337,847 bytes and the manifest SHA-256 above. PBS's 1,508,860
55
+ bytes were recovered from the public `proxmox-backup-docs_4.2.3-1_all.deb`, matching
56
+ the manifest's full SHA-256 (the recorded server version is 4.2.6-1, running 4.2.3).
57
+ No host was contacted. Tests cover generated constraints/types and fake SDK transport.
58
+
59
+ The named format's semantics come from
60
+ [PVE::JSONSchema](https://github.com/proxmox/pve-common/blob/defd246f31f327463f901a2daaf8dc52efcc5a97/src/PVE/JSONSchema.pm)
61
+ and `PVE::ParseUtils` at the same commit, inspected 2026-10-05. This is vendor-source
62
+ and fake-transport evidence; no live PVE call or Talos boot was performed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homeflare/alchemy",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo, Talos, launchd and Caddy, and a verifier that proves adoptions are no-ops.",
5
5
  "homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
6
6
  "bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
@@ -208,7 +208,7 @@ describe('K-A4 finding 4: configValueProblem never accepts a flag-shaped value',
208
208
  ['a short flag', '-n'],
209
209
  ['a file-overwrite flag glued to its argument', '-o/etc/ceph/ceph.conf'],
210
210
  ['a long flag with =', '--admin-daemon=/var/run/ceph/ceph-mon.a.asok'],
211
- ['another long flag', '--conf=/tmp/x'],
211
+ ['another long flag', '--conf=/tmp/x'], // tmp-allow: refused argv, not a directory this test creates
212
212
  ['the empty string', ''],
213
213
  ['a bare dash', '-'],
214
214
  ])('refuses %s (%j)', (_name, value) => {
@@ -15,7 +15,7 @@ import {
15
15
  } from './sudo-allowlist.ts';
16
16
 
17
17
  const DAEMONS = '/Library/LaunchDaemons';
18
- const context = { prefixes: [DAEMONS, '/opt/example'], staged: '/tmp/hf-sudo-x/staged' };
18
+ const context = { prefixes: [DAEMONS, '/opt/example'], staged: '/tmp/hf-sudo-x/staged' }; // tmp-allow: in-memory staging path, not a directory this test creates
19
19
  const allowed = (argv: string[]) => privilegedProblem(argv, context);
20
20
  const PLIST = `${DAEMONS}/com.example.a.plist`;
21
21
 
@@ -66,7 +66,7 @@ describe('launchctl shapes', () => {
66
66
  ['bootstrap of two plists', [LAUNCHCTL, 'bootstrap', 'system', PLIST, PLIST]],
67
67
  [
68
68
  'bootstrap outside every prefix',
69
- [LAUNCHCTL, 'bootstrap', 'system', '/tmp/com.example.a.plist'],
69
+ [LAUNCHCTL, 'bootstrap', 'system', '/tmp/com.example.a.plist'], // tmp-allow: refused argv, not a directory this test creates
70
70
  ],
71
71
  ['bootstrap of a non-plist', [LAUNCHCTL, 'bootstrap', 'system', `${DAEMONS}/com.example.a`]],
72
72
  // ★ Only where launchd loads daemons at boot: anywhere else is a job that vanishes on restart.
@@ -22,7 +22,7 @@ import {
22
22
  } from './sudo-allowlist.ts';
23
23
 
24
24
  const PREFIXES = ['/etc/systemd/system', '/usr/local/bin'];
25
- const STAGED = '/tmp/hf-sudo-1/staged';
25
+ const STAGED = '/tmp/hf-sudo-1/staged'; // tmp-allow: in-memory staging path, not a directory this test creates
26
26
  const TEMP = '/etc/systemd/system/.thing.service.hf-abcdef012345.tmp';
27
27
  const context: AllowContext = { prefixes: PREFIXES, staged: STAGED, temp: TEMP };
28
28
  const allowed = (argv: string[]) => privilegedProblem(argv, context);
@@ -51,7 +51,7 @@ describe('install shapes', () => {
51
51
  'a target that is not the derived temp',
52
52
  [INSTALL, '-m', '0644', '-T', '--', STAGED, '/etc/systemd/system/x.service'],
53
53
  ],
54
- ['a temp outside every prefix', [INSTALL, '-m', '0644', '-T', '--', STAGED, '/tmp/x.tmp']],
54
+ ['a temp outside every prefix', [INSTALL, '-m', '0644', '-T', '--', STAGED, '/tmp/x.tmp']], // tmp-allow: refused argv, not a directory this test creates
55
55
  ['no -T', [INSTALL, '-m', '0644', '--', STAGED, TEMP]],
56
56
  ['a three-digit mode', [INSTALL, '-m', '644', '-T', '--', STAGED, TEMP]],
57
57
  ['-S (the Mac flag)', [INSTALL, '-S', '-m', '0644', '-T', '--', STAGED, TEMP]],
@@ -103,7 +103,7 @@ describe('mv and rm shapes', () => {
103
103
  ['mv without -T', [MV, '-f', '--', TEMP, '/etc/systemd/system/thing.service']],
104
104
  ['rm -r', [RM, '-rf', '--', TEMP]],
105
105
  ['rm without --', [RM, '-f', TEMP]],
106
- ['rm outside every prefix', [RM, '-f', '--', '/tmp/x']],
106
+ ['rm outside every prefix', [RM, '-f', '--', '/tmp/x']], // tmp-allow: refused argv, not a directory this test creates
107
107
  ['sudo in argv', ['/usr/bin/sudo', '-n', RM, '-f', '--', TEMP]],
108
108
  ])('refuses %s', (_name, argv) => {
109
109
  expect(allowed(argv)).toBeString();
@@ -52,7 +52,7 @@ describe('writeFileAtomic under a prefix', () => {
52
52
  for (const line of logs) expect(line.startsWith('homeflare/linux sudo -n ')).toBe(true);
53
53
  // No temp file left behind, and the staging directory is gone too.
54
54
  expect([...fake.files.keys()].some((path) => path.includes('.hf-'))).toBe(false);
55
- expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false);
55
+ expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false); // tmp-allow: in-memory fake path from fake-sudo, not a directory this test creates
56
56
  });
57
57
 
58
58
  test('a failed mv removes the temp it just installed, then still throws', async () => {
@@ -68,7 +68,7 @@ describe('writeFileAtomic under a prefix', () => {
68
68
  expect(calls[2]?.at(-1)).toBe(calls[0]?.at(-1));
69
69
  expect(fake.files.has(DEST)).toBe(false);
70
70
  expect([...fake.files.keys()].some((path) => path.includes('.hf-'))).toBe(false);
71
- expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false);
71
+ expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false); // tmp-allow: in-memory fake path from fake-sudo, not a directory this test creates
72
72
  });
73
73
 
74
74
  // 🔴 Adversarial review, round 2: a failed CLEANUP rm after a failed mv used to be swallowed
@@ -22,11 +22,14 @@ export interface FakeRoleState {
22
22
  /** Bare role names, backing the `roleExists` check the database family's owner probe uses. */
23
23
  readonly roleNames: Set<string>;
24
24
  readonly roleRows: Map<string, PostgresRoleAttributes>;
25
- /** `member\0parent\0grantor` triples backing `pg_auth_members` (`''` grantor = dropped
25
+ /** `member\0parent\0grantor` triples backing `pg_auth_members` (`''` grantor = unresolvable
26
26
  * grantor); `GRANT`/`REVOKE` mutate it. */
27
27
  readonly memberships: Set<string>;
28
28
  /** Options a name-only compare would hide, keyed `member\0parent\0grantor`. */
29
- readonly membershipOptions: Map<string, { readonly admin: boolean; readonly set: boolean }>;
29
+ readonly membershipOptions: Map<
30
+ string,
31
+ { readonly admin: boolean; readonly set: boolean; readonly inherit?: boolean }
32
+ >;
30
33
  /** Hands out the next `oid` a real cluster would assign. */
31
34
  nextOid(): number;
32
35
  }
@@ -106,6 +109,7 @@ export const applyRoleStatement = <A extends object>(
106
109
  grantor: parts[2] === undefined || parts[2] === '' ? null : (parts[2] as string),
107
110
  admin: options?.admin === true,
108
111
  set: options?.set === true,
112
+ inherit: options?.inherit === true,
109
113
  };
110
114
  })
111
115
  .sort(
@@ -165,17 +169,33 @@ export const applyRoleStatement = <A extends object>(
165
169
  // `grant.sgml` (REL_18_6): altering an existing membership retains every option the new
166
170
  // GRANT omits, so only the full-options form clears ADMIN; `WITH SET FALSE` alone keeps a
167
171
  // current ADMIN. A bare `GRANT` (never issued by this family) leaves options untouched.
168
- const options = state.membershipOptions.get(key) ?? { admin: false, set: false };
172
+ const options = state.membershipOptions.get(key) ?? {
173
+ admin: false,
174
+ set: false,
175
+ inherit: state.roleRows.get(member)?.inherit ?? true,
176
+ };
169
177
  if (text.endsWith('WITH ADMIN FALSE, SET FALSE')) {
170
- state.membershipOptions.delete(key);
178
+ state.membershipOptions.set(key, { ...options, admin: false, set: false });
171
179
  } else if (text.endsWith('WITH SET FALSE')) {
172
- if (options.admin) state.membershipOptions.set(key, { admin: true, set: false });
173
- else state.membershipOptions.delete(key);
180
+ state.membershipOptions.set(key, { ...options, set: false });
174
181
  }
175
182
  return Effect.succeed([] as unknown as ReadonlyArray<A>);
176
183
  }
177
184
 
178
185
  if (text.startsWith('REVOKE')) {
186
+ const option =
187
+ /^REVOKE (ADMIN|SET|INHERIT) OPTION FOR "((?:[^"]|"")*)" FROM "((?:[^"]|"")*)" GRANTED BY "((?:[^"]|"")*)" RESTRICT$/.exec(
188
+ text,
189
+ );
190
+ if (option !== null) {
191
+ const key = `${unquoteIdent(option[3] as string)}\0${unquoteIdent(option[2] as string)}\0${unquoteIdent(option[4] as string)}`;
192
+ const options = state.membershipOptions.get(key) ?? { admin: false, set: false };
193
+ const next = { ...options, [(option[1] as string).toLowerCase()]: false };
194
+ if (next.admin || next.set || next.inherit !== undefined)
195
+ state.membershipOptions.set(key, next);
196
+ else state.membershipOptions.delete(key);
197
+ return Effect.succeed([] as unknown as ReadonlyArray<A>);
198
+ }
179
199
  const granted =
180
200
  /^REVOKE "((?:[^"]|"")*)" FROM "((?:[^"]|"")*)" GRANTED BY "((?:[^"]|"")*)"$/.exec(text);
181
201
  if (granted !== null) {
@@ -35,12 +35,14 @@ export interface FakeSql extends PgExecutor {
35
35
  readonly roleRows: Map<string, PostgresRoleAttributes>;
36
36
  /** `member\0parent\0grantor` triples backing `pg_auth_members` (a plain `pg_auth_members`
37
37
  * read, a grant, a revoke and a DROP ROLE cascade all go through `fake-role-sql.ts`);
38
- * `GRANT`/`REVOKE` mutate it. An empty grantor segment is a grantor role that no longer
39
- * exists. */
38
+ * `GRANT`/`REVOKE` mutate it. An empty grantor segment is an inconsistent catalog/test row. */
40
39
  readonly memberships: Set<string>;
41
40
  /** Options on a membership the name alone hides, keyed `member\0parent\0grantor`. Absent
42
- * means neither ADMIN nor SET. */
43
- readonly membershipOptions: Map<string, { readonly admin: boolean; readonly set: boolean }>;
41
+ * means neither ADMIN, SET nor an explicit per-grant INHERIT. */
42
+ readonly membershipOptions: Map<
43
+ string,
44
+ { readonly admin: boolean; readonly set: boolean; readonly inherit?: boolean }
45
+ >;
44
46
  /** Mutable on purpose: a test seeds a live schema (adoption, drift) or clears one (drop). */
45
47
  readonly schemas: Map<string, PostgresSchemaAttributes>;
46
48
  /** Mutable on purpose: names of schemas this fake pretends hold at least one relation, so a
@@ -85,7 +87,10 @@ export const makeFakeSql = (options: FakeSqlOptions = {}): FakeSql => {
85
87
  const roleRows = new Map(options.roleRows?.map((r) => [r.name, r] as const) ?? []);
86
88
  const databases = new Map(options.databases?.map((d) => [d.name, d] as const) ?? []);
87
89
  const memberships = new Set<string>();
88
- const membershipOptions = new Map<string, { readonly admin: boolean; readonly set: boolean }>();
90
+ const membershipOptions = new Map<
91
+ string,
92
+ { readonly admin: boolean; readonly set: boolean; readonly inherit?: boolean }
93
+ >();
89
94
  const schemas = seedSchemas(options.schemas, options.database ?? 'postgres');
90
95
  const relationsIn = new Set(options.schemasWithRelations ?? []);
91
96
  let raceRemaining = options.raceNextCreate === true ? 1 : 0;
@@ -147,6 +147,7 @@ export {
147
147
  PostgresRoleNameRefused,
148
148
  PostgresRoleParentMissing,
149
149
  PostgresRolePasswordEnvUnsetError,
150
+ PostgresRolePasswordNonAsciiRefused,
150
151
  PostgresRolePrivilegedRefused,
151
152
  PostgresRoleRenameRefused,
152
153
  PostgresRoleValidUntilRefused,
@@ -62,7 +62,8 @@ export interface PostgresRoleProps {
62
62
  readonly login: boolean;
63
63
  /** `CONNECTION LIMIT n`, `-1` for unlimited — maps to `pg_roles.rolconnlimit`. */
64
64
  readonly connectionLimit: number;
65
- /** `INHERIT` or `NOINHERIT` — maps to `pg_roles.rolinherit`. */
65
+ /** `INHERIT` or `NOINHERIT` — maps to `pg_roles.rolinherit`; false also clears per-grant
66
+ * INHERIT on declared memberships (PG 16+ uses the role flag only to default new grants). */
66
67
  readonly inherit: boolean;
67
68
  /**
68
69
  * `VALID UNTIL 'timestamp'` — maps to `pg_roles.rolvaliduntil`. ISO 8601 without a timezone is
@@ -112,18 +113,20 @@ export interface PostgresRoleAttributes {
112
113
  */
113
114
  readonly memberOf: readonly string[] | null;
114
115
  /**
115
- * The membership rows behind `memberOf`, including `admin_option` and `set_option`. Present
116
+ * The membership rows behind `memberOf`, including `admin_option`, `set_option` and
117
+ * `inherit_option`. Present
116
118
  * on a catalog read; stripped before attributes are stored (`storedAttributes`), because the
117
119
  * engine's diff receives stored attributes and would never see them there. The provider's
118
120
  * diff reads these rows live. `grantor` is the role that made the grant
119
- * (`pg_auth_members.grantor`), null when that role was dropped — a dropped-grantor row cannot
120
- * be named in `REVOKE … GRANTED BY`, so reconcile can only fail typed on it.
121
+ * (`pg_auth_members.grantor`). Null is a defensive inconsistent-catalog case, not an ordinary
122
+ * DROP ROLE outcome; such a row cannot be named in `REVOKE … GRANTED BY`.
121
123
  */
122
124
  readonly memberships?: readonly {
123
125
  readonly parent: string;
124
126
  readonly grantor: string | null;
125
127
  readonly admin: boolean;
126
128
  readonly set: boolean;
129
+ readonly inherit: boolean;
127
130
  }[];
128
131
  /**
129
132
  * A seal of the last password value written, or `''` if none was ever written (`seal()` from