@camstack/system 1.2.455 → 1.2.456

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 (98) hide show
  1. package/dist/{agent-https-CLWrOO3o.mjs → agent-https-fm4AR8XL.mjs} +32 -5
  2. package/dist/{agent-https-CCvlRQKV.js → agent-https-yjxdfJDA.js} +37 -4
  3. package/dist/auth/auth-manager.d.ts +9 -0
  4. package/dist/auth/legacy-token-tables.d.ts +16 -0
  5. package/dist/auth/user-management-internal.d.ts +9 -28
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/alerts/alerts.addon.js +2 -2
  11. package/dist/builtins/alerts/alerts.addon.mjs +2 -2
  12. package/dist/builtins/autotrack/index.js +1 -1
  13. package/dist/builtins/autotrack/index.mjs +1 -1
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  15. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  16. package/dist/builtins/calendars/calendars.addon.js +1 -1
  17. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  18. package/dist/builtins/camera-grid/index.js +1 -1
  19. package/dist/builtins/camera-grid/index.mjs +1 -1
  20. package/dist/builtins/composer/composer.addon.js +2 -2
  21. package/dist/builtins/composer/composer.addon.mjs +2 -2
  22. package/dist/builtins/console-logging/index.js +1 -1
  23. package/dist/builtins/console-logging/index.mjs +1 -1
  24. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  25. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  26. package/dist/builtins/device-history/device-history.addon.js +1 -1
  27. package/dist/builtins/device-history/device-history.addon.mjs +1 -1
  28. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  29. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  30. package/dist/builtins/hub-forwarder/index.js +1 -1
  31. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  32. package/dist/builtins/keychain/index.js +4 -4
  33. package/dist/builtins/keychain/index.mjs +4 -4
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  36. package/dist/builtins/local-auth/auth-schema.d.ts +4 -7
  37. package/dist/builtins/local-auth/local-auth.addon.d.ts +7 -8
  38. package/dist/builtins/local-auth/local-auth.addon.js +1086 -1329
  39. package/dist/builtins/local-auth/local-auth.addon.mjs +1085 -1326
  40. package/dist/builtins/local-auth/preferences-record-kinds.d.ts +27 -0
  41. package/dist/builtins/local-auth/record-kinds.d.ts +4 -3
  42. package/dist/builtins/local-auth/user-preferences-store.d.ts +56 -0
  43. package/dist/builtins/local-network/local-network.addon.d.ts +8 -0
  44. package/dist/builtins/local-network/local-network.addon.js +30 -5
  45. package/dist/builtins/local-network/local-network.addon.mjs +30 -5
  46. package/dist/builtins/loki-logging/index.js +1 -1
  47. package/dist/builtins/loki-logging/index.mjs +1 -1
  48. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  49. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  50. package/dist/builtins/platform-probe/index.js +1 -1
  51. package/dist/builtins/platform-probe/index.mjs +1 -1
  52. package/dist/builtins/record-entries/index.js +1 -1
  53. package/dist/builtins/record-entries/index.mjs +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  55. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  56. package/dist/builtins/snapshot/index.js +1 -1
  57. package/dist/builtins/snapshot/index.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  60. package/dist/builtins/sqlite-storage/index.js +1 -1
  61. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  62. package/dist/builtins/sqlite-storage/records/records-engine-types.d.ts +1 -0
  63. package/dist/builtins/sqlite-storage/records/records-provider.d.ts +13 -0
  64. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +52 -6
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +52 -6
  66. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  67. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  68. package/dist/builtins/system-config/system-config.addon.js +3 -3
  69. package/dist/builtins/system-config/system-config.addon.mjs +3 -3
  70. package/dist/builtins/winston-logging/index.js +1 -1
  71. package/dist/builtins/winston-logging/index.mjs +1 -1
  72. package/dist/{composition-sources-CSzD5_qX.mjs → composition-sources-Ds0Vr6eD.mjs} +1 -1
  73. package/dist/{composition-sources-BxUkLNSo.js → composition-sources-gB4coDJR.js} +1 -1
  74. package/dist/{constants-DruwCkfp.js → constants-CavyKfHZ.js} +1 -1
  75. package/dist/{constants-DOlYt1t1.mjs → constants-i5oD_gLB.mjs} +1 -1
  76. package/dist/{device-ca-QPBgfqVY.js → device-ca-bpoEr0zG.js} +1 -1
  77. package/dist/{device-ca-uFbTyY_o.mjs → device-ca-qz1Pmeu1.mjs} +1 -1
  78. package/dist/{dist-BpSENkRJ.mjs → dist-BSF_C8W2.mjs} +216 -109
  79. package/dist/{dist-DjQ8C-ku.js → dist-BpqA2CT0.js} +251 -108
  80. package/dist/file-host-worker.js +1 -1
  81. package/dist/file-host-worker.mjs +1 -1
  82. package/dist/{hub-mdns-ciao-DlgkFJVY.mjs → hub-mdns-ciao-CHaGDzyz.mjs} +20 -13
  83. package/dist/{hub-mdns-ciao-Bkrvgnma.js → hub-mdns-ciao-CZlZRxxA.js} +20 -13
  84. package/dist/index.d.ts +2 -4
  85. package/dist/index.js +17 -11
  86. package/dist/index.mjs +17 -9
  87. package/dist/{phase6-declare-gate-JQUqgQgq.mjs → phase6-declare-gate-B4yfsf2n.mjs} +1 -1
  88. package/dist/{phase6-declare-gate-DrmTtz7F.js → phase6-declare-gate-n-IZRklX.js} +1 -1
  89. package/dist/{restore-archive-4OP0EYg6.js → restore-archive-B4QG7XmX.js} +1 -1
  90. package/dist/{restore-archive-YFSJRtKy.mjs → restore-archive-BGUM4FKf.mjs} +1 -1
  91. package/dist/tls/cert-issue.d.ts +16 -1
  92. package/dist/tls/cert-manager.d.ts +6 -0
  93. package/dist/tls/index.d.ts +1 -1
  94. package/dist/{wal-maintenance-TyuOEVy1.mjs → wal-maintenance-AerF4XE6.mjs} +0 -0
  95. package/dist/{wal-maintenance-Bl0ZKjVR.js → wal-maintenance-CNEs_I94.js} +0 -0
  96. package/package.json +1 -1
  97. package/dist/auth/admin-token-manager.d.ts +0 -51
  98. package/dist/auth/scoped-token-manager.d.ts +0 -25
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The per-user account preferences kind (D894), owned by `local-auth` because
3
+ * it owns the users the rows belong to — a user's delete wipes them in the
4
+ * same call (`endAllAccess`).
5
+ *
6
+ * Declared on its OWN, never folded into `localAuthRecordKinds`: that map is
7
+ * the phase-6 adoption set, and a kind missing from the catalog there sends
8
+ * the whole auth builtin back to its legacy tables.
9
+ */
10
+ import { type DefinedRecordKind, type PreferenceValue } from "@camstack/types";
11
+ import { z } from "zod";
12
+ /** One `(user, namespace)` row. The record id is {@link userPreferenceRecordId}. */
13
+ export type UserPreferenceBody = {
14
+ readonly userId: string;
15
+ readonly namespace: string;
16
+ readonly value: PreferenceValue;
17
+ readonly revision: number;
18
+ readonly updatedAt: number;
19
+ readonly updatedByDevice: string | null;
20
+ };
21
+ export declare const UserPreferenceBodySchema: z.ZodType<UserPreferenceBody>;
22
+ export type LocalAuthPreferencesRecordKindsMap = {
23
+ readonly "user-preferences": DefinedRecordKind<z.ZodType<UserPreferenceBody>>;
24
+ };
25
+ export declare const localAuthPreferencesRecordKinds: LocalAuthPreferencesRecordKindsMap;
26
+ /** Namespaces cannot hold `:`, so the id splits unambiguously. */
27
+ export declare function userPreferenceRecordId(userId: string, namespace: string): string;
@@ -5,7 +5,10 @@
5
5
  * use `storageAdmin.dropCollection` with `expectRows` after verify (see
6
6
  * `docs/design/records-phase-6-auth-migration.md`).
7
7
  *
8
- * Before hub restart: mint and test an admin token (`csa_`); snapshot every kind.
8
+ * Before hub restart: mint and test an admin token (`auth.mintToken` with `'*'`); snapshot every kind.
9
+ *
10
+ * `scoped-tokens` and `admin-tokens` hold pre-D892 rows read by nothing since
11
+ * D895; they stay declared so the layout is unchanged.
9
12
  */
10
13
  import { ApiKeyRecordSchema, type DefinedRecordKind, type RecordKindHandle, type RecordsAccess, ScopedTokenSchema, TokenScopeSchema, UserRecordSchema } from "@camstack/types";
11
14
  import { z } from "zod";
@@ -128,8 +131,6 @@ export declare const LOCAL_AUTH_RECORDS_LEGACY_COLLECTIONS: readonly string[];
128
131
  export interface LocalAuthRecordStores {
129
132
  readonly users: RecordKindHandle<UserBody>;
130
133
  readonly apiKeys: RecordKindHandle<ApiKeyBody>;
131
- readonly scopedTokens: RecordKindHandle<ScopedTokenBody>;
132
- readonly adminTokens: RecordKindHandle<AdminTokenBody>;
133
134
  readonly loginSessions: RecordKindHandle<LoginSessionBody>;
134
135
  readonly loginSessionCutoffs: RecordKindHandle<LoginSessionCutoffBody>;
135
136
  readonly oauthSessions: RecordKindHandle<OauthSessionBody>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Per-user account preferences (D894): one row per `(userId, namespace)`.
3
+ *
4
+ * Every method takes the OWNER's id as its first argument and touches only
5
+ * that user's rows; the hub router stamps it from the principal, and nothing
6
+ * here accepts a second user id.
7
+ *
8
+ * The poll (`changedSince`) is answered from an in-memory mirror of each
9
+ * user's `namespace → revision`, loaded once on first touch. This store is the
10
+ * kind's only writer (one in-process instance in `local-auth`), so the mirror
11
+ * cannot fall behind the rows; a poll that finds nothing changed reads no row.
12
+ * Writes of one user are serialized, so a read-modify-write never interleaves.
13
+ */
14
+ import { type PreferenceRow, type PreferencesChangedSinceOutput, type PreferencesPutInput, type PreferencesPutOutput, type RecordKindHandle } from "@camstack/types";
15
+ import { type UserPreferenceBody } from "./preferences-record-kinds.js";
16
+ export type PreferencesLimitReason = "value-too-large" | "too-many-namespaces";
17
+ /** A write the limits refuse; nothing was stored. */
18
+ export declare class PreferencesLimitError extends Error {
19
+ readonly reason: PreferencesLimitReason;
20
+ constructor(reason: PreferencesLimitReason, message: string);
21
+ }
22
+ /**
23
+ * The limit refusal behind `error`, or `null`. By name and field, not
24
+ * `instanceof`: the store and its caller may load different copies of this
25
+ * module.
26
+ */
27
+ export declare function preferencesLimitReasonOf(error: unknown): PreferencesLimitReason | null;
28
+ export interface UserPreferencesStoreOptions {
29
+ readonly now?: () => number;
30
+ }
31
+ export declare class UserPreferencesStore {
32
+ private readonly kind;
33
+ private readonly now;
34
+ /** `userId → namespace → revision`; absent until the user is first touched. */
35
+ private readonly mirrors;
36
+ private readonly locks;
37
+ constructor(kind: RecordKindHandle<UserPreferenceBody>, options?: UserPreferencesStoreOptions);
38
+ /** The caller's rows; `namespaces` narrows them (unknown ones are simply absent). */
39
+ get(userId: string, namespaces?: readonly string[]): Promise<readonly PreferenceRow[]>;
40
+ put(userId: string, input: PreferencesPutInput): Promise<PreferencesPutOutput>;
41
+ /** `false` when the namespace did not exist. */
42
+ delete(userId: string, namespace: string): Promise<boolean>;
43
+ /**
44
+ * What the client is missing: every row whose revision differs from the one
45
+ * it holds (or that it does not list), and every listed namespace now gone.
46
+ */
47
+ changedSince(userId: string, known: Readonly<Record<string, number>>): Promise<PreferencesChangedSinceOutput>;
48
+ /** Every row of `userId` goes (user delete). Returns how many. */
49
+ wipeUser(userId: string): Promise<number>;
50
+ /** Rows whose user is not in `liveUserIds` go (boot sweep). Returns how many. */
51
+ wipeOrphans(liveUserIds: ReadonlySet<string>): Promise<number>;
52
+ private mirrorOf;
53
+ private scanUser;
54
+ /** Run `task` after every earlier write of the same user has settled. */
55
+ private serialized;
56
+ }
@@ -164,6 +164,14 @@ export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
164
164
  private reconcileMeshEndpoint;
165
165
  private resolvedHttpPort;
166
166
  private tlsDataDir;
167
+ /**
168
+ * The hub's name (D861) for a local root this action may CREATE (an existing root is
169
+ * never renamed). Hub only: on an agent the hub's name would mislabel the agent's own
170
+ * material, so it gets the plain name. A failed read falls back to the machine
171
+ * hostname (what the hub shows when unnamed) and says so: a certificate action must
172
+ * not fail over a label.
173
+ */
174
+ private caHubName;
167
175
  private snapshotTls;
168
176
  /**
169
177
  * Hot-swap the HTTPS listener onto the pair now on disk. A failure is not
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DjQ8C-ku.js");
4
- const require_hub_mdns_ciao = require("../../hub-mdns-ciao-Bkrvgnma.js");
3
+ const require_dist = require("../../dist-BpqA2CT0.js");
4
+ const require_hub_mdns_ciao = require("../../hub-mdns-ciao-CZlZRxxA.js");
5
5
  let zod = require("zod");
6
6
  let node_net = require("node:net");
7
7
  let node_os = require("node:os");
@@ -887,7 +887,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
887
887
  getTlsStatus: async () => this.snapshotTls(),
888
888
  regenerateCertificate: async (_input) => {
889
889
  const extraSans = [...require_hub_mdns_ciao.readExtraSans(this.tlsDataDir())];
890
- await require_hub_mdns_ciao.regenerateCertificate(this.tlsDataDir(), { extraSans });
890
+ await require_hub_mdns_ciao.regenerateCertificate(this.tlsDataDir(), {
891
+ extraSans,
892
+ ...await this.caHubName()
893
+ });
891
894
  this.putCertificateOnListener("regenerate");
892
895
  return this.snapshotTls();
893
896
  },
@@ -910,7 +913,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
910
913
  },
911
914
  revertToGeneratedCertificate: async () => {
912
915
  const extraSans = [...require_hub_mdns_ciao.readExtraSans(this.tlsDataDir())];
913
- await require_hub_mdns_ciao.revertToGeneratedCertificate(this.tlsDataDir(), { extraSans });
916
+ await require_hub_mdns_ciao.revertToGeneratedCertificate(this.tlsDataDir(), {
917
+ extraSans,
918
+ ...await this.caHubName()
919
+ });
914
920
  this.putCertificateOnListener("revert");
915
921
  return this.snapshotTls();
916
922
  }
@@ -1117,6 +1123,22 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
1117
1123
  tlsDataDir() {
1118
1124
  return this.ctx.nodeDataDir ?? this.ctx.dataDir;
1119
1125
  }
1126
+ /**
1127
+ * The hub's name (D861) for a local root this action may CREATE (an existing root is
1128
+ * never renamed). Hub only: on an agent the hub's name would mislabel the agent's own
1129
+ * material, so it gets the plain name. A failed read falls back to the machine
1130
+ * hostname (what the hub shows when unnamed) and says so: a certificate action must
1131
+ * not fail over a label.
1132
+ */
1133
+ async caHubName() {
1134
+ if ((this.ctx.kernel.localNodeId ?? "hub") !== "hub") return {};
1135
+ try {
1136
+ return { hubName: (await this.ctx.api.system.info.query()).serverName.name };
1137
+ } catch (err) {
1138
+ this.ctx.logger.warn("local-network: server name unreadable - naming the CA after the hostname", { meta: { error: require_dist.errMsg(err) } });
1139
+ return { hubName: require_dist.shortHostname(node_os.hostname()) };
1140
+ }
1141
+ }
1120
1142
  snapshotTls() {
1121
1143
  const status = require_hub_mdns_ciao.readTlsAccessStatus(this.tlsDataDir(), require_hub_mdns_ciao.servedHttpsLeafFingerprint());
1122
1144
  return {
@@ -1165,7 +1187,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
1165
1187
  if (hostname !== "") {
1166
1188
  require_hub_mdns_ciao.writeExtraSans(this.tlsDataDir(), [hostname]);
1167
1189
  if (require_hub_mdns_ciao.readTlsMode(this.tlsDataDir()) === "generated") {
1168
- if ((await require_hub_mdns_ciao.ensureTlsCert(this.tlsDataDir(), { extraSans: [hostname] })).generated) this.putCertificateOnListener("hostname");
1190
+ if ((await require_hub_mdns_ciao.ensureTlsCert(this.tlsDataDir(), {
1191
+ extraSans: [hostname],
1192
+ hubName: require_dist.shortHostname(node_os.hostname())
1193
+ })).generated) this.putCertificateOnListener("hostname");
1169
1194
  }
1170
1195
  } else require_hub_mdns_ciao.writeExtraSans(this.tlsDataDir(), []);
1171
1196
  }
@@ -1,5 +1,5 @@
1
- import { $n as resolveAppConnections, B as HUB_MDNS_TXT_KEYS, Er as validateCustomAddress, On as localNetworkCapability, Wn as pickAppLinkHub, Y as NetworkTunnelStartedDataSchema, hn as isAppLinkBase, kt as checkRoleAddress, l as CAMSTACK_APP_LINK_ORIGIN, or as resolveNotificationTapAddress, qr as BaseAddon, rr as resolveHubAddressRoles, vn as isDefaultAppConnectionEndpoint, ya as EventCategory, z as HUB_MDNS_SERVICE_TYPE } from "../../dist-BpSENkRJ.mjs";
2
- import { P as readLanHttpState, a as storeUploadedCertificate, d as servedHttpsLeafFingerprint, g as readTlsMode, h as readTlsAccessStatus, i as revertToGeneratedCertificate, j as applyLanHttp, k as DEFAULT_LAN_HTTP_PORT, m as readExtraSans, n as applyCertificateToListener, o as useStoredUploadedCertificate, r as regenerateCertificate, t as createCiaoPublisher, v as writeExtraSans, w as ensureTlsCert } from "../../hub-mdns-ciao-DlgkFJVY.mjs";
1
+ import { $r as BaseAddon, B as HUB_MDNS_SERVICE_TYPE, Mr as validateCustomAddress, Pn as localNetworkCapability, Pt as checkRoleAddress, Qr as errMsg, Ta as EventCategory, V as HUB_MDNS_TXT_KEYS, X as NetworkTunnelStartedDataSchema, Xn as pickAppLinkHub, ar as resolveAppConnections, bn as isAppLinkBase, fr as resolveNotificationTapAddress, l as CAMSTACK_APP_LINK_ORIGIN, lr as resolveHubAddressRoles, vr as shortHostname, wn as isDefaultAppConnectionEndpoint } from "../../dist-BSF_C8W2.mjs";
2
+ import { P as readLanHttpState, a as storeUploadedCertificate, d as servedHttpsLeafFingerprint, g as readTlsMode, h as readTlsAccessStatus, i as revertToGeneratedCertificate, j as applyLanHttp, k as DEFAULT_LAN_HTTP_PORT, m as readExtraSans, n as applyCertificateToListener, o as useStoredUploadedCertificate, r as regenerateCertificate, t as createCiaoPublisher, v as writeExtraSans, w as ensureTlsCert } from "../../hub-mdns-ciao-CHaGDzyz.mjs";
3
3
  import { z } from "zod";
4
4
  import { isIP } from "node:net";
5
5
  import * as os$1 from "node:os";
@@ -884,7 +884,10 @@ var LocalNetworkAddon = class extends BaseAddon {
884
884
  getTlsStatus: async () => this.snapshotTls(),
885
885
  regenerateCertificate: async (_input) => {
886
886
  const extraSans = [...readExtraSans(this.tlsDataDir())];
887
- await regenerateCertificate(this.tlsDataDir(), { extraSans });
887
+ await regenerateCertificate(this.tlsDataDir(), {
888
+ extraSans,
889
+ ...await this.caHubName()
890
+ });
888
891
  this.putCertificateOnListener("regenerate");
889
892
  return this.snapshotTls();
890
893
  },
@@ -907,7 +910,10 @@ var LocalNetworkAddon = class extends BaseAddon {
907
910
  },
908
911
  revertToGeneratedCertificate: async () => {
909
912
  const extraSans = [...readExtraSans(this.tlsDataDir())];
910
- await revertToGeneratedCertificate(this.tlsDataDir(), { extraSans });
913
+ await revertToGeneratedCertificate(this.tlsDataDir(), {
914
+ extraSans,
915
+ ...await this.caHubName()
916
+ });
911
917
  this.putCertificateOnListener("revert");
912
918
  return this.snapshotTls();
913
919
  }
@@ -1114,6 +1120,22 @@ var LocalNetworkAddon = class extends BaseAddon {
1114
1120
  tlsDataDir() {
1115
1121
  return this.ctx.nodeDataDir ?? this.ctx.dataDir;
1116
1122
  }
1123
+ /**
1124
+ * The hub's name (D861) for a local root this action may CREATE (an existing root is
1125
+ * never renamed). Hub only: on an agent the hub's name would mislabel the agent's own
1126
+ * material, so it gets the plain name. A failed read falls back to the machine
1127
+ * hostname (what the hub shows when unnamed) and says so: a certificate action must
1128
+ * not fail over a label.
1129
+ */
1130
+ async caHubName() {
1131
+ if ((this.ctx.kernel.localNodeId ?? "hub") !== "hub") return {};
1132
+ try {
1133
+ return { hubName: (await this.ctx.api.system.info.query()).serverName.name };
1134
+ } catch (err) {
1135
+ this.ctx.logger.warn("local-network: server name unreadable - naming the CA after the hostname", { meta: { error: errMsg(err) } });
1136
+ return { hubName: shortHostname(os$1.hostname()) };
1137
+ }
1138
+ }
1117
1139
  snapshotTls() {
1118
1140
  const status = readTlsAccessStatus(this.tlsDataDir(), servedHttpsLeafFingerprint());
1119
1141
  return {
@@ -1162,7 +1184,10 @@ var LocalNetworkAddon = class extends BaseAddon {
1162
1184
  if (hostname !== "") {
1163
1185
  writeExtraSans(this.tlsDataDir(), [hostname]);
1164
1186
  if (readTlsMode(this.tlsDataDir()) === "generated") {
1165
- if ((await ensureTlsCert(this.tlsDataDir(), { extraSans: [hostname] })).generated) this.putCertificateOnListener("hostname");
1187
+ if ((await ensureTlsCert(this.tlsDataDir(), {
1188
+ extraSans: [hostname],
1189
+ hubName: shortHostname(os$1.hostname())
1190
+ })).generated) this.putCertificateOnListener("hostname");
1166
1191
  }
1167
1192
  } else writeExtraSans(this.tlsDataDir(), []);
1168
1193
  }
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { An as logDestinationCapability, qr as BaseAddon } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, In as logDestinationCapability } from "../../dist-BSF_C8W2.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  let node_fs_promises = require("node:fs/promises");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,4 +1,4 @@
1
- import { In as metricsProviderCapability, Kr as errMsg, Ni as createEvent, Zn as reducePoints, cn as foldSnapshotByFunction, er as resolveBucketMs, qr as BaseAddon, ya as EventCategory } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, Hn as metricsProviderCapability, Qr as errMsg, Ta as EventCategory, or as resolveBucketMs, pn as foldSnapshotByFunction, rr as reducePoints, zi as createEvent } from "../../dist-BSF_C8W2.mjs";
2
2
  import { access, readFile, readdir } from "node:fs/promises";
3
3
  import { promisify } from "node:util";
4
4
  import * as os$1 from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  let node_util = require("node:util");
8
8
  let node_os = require("node:os");
9
9
  node_os = require_chunk.__toESM(node_os);
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM, r as __exportAll } from "../../chunk-CNf5ZN-e.mjs";
2
- import { $t as enumerateInferenceDevices, Kn as platformProbeCapability, Kr as errMsg, qr as BaseAddon, ur as scoreRuntimes, ya as EventCategory, zi as emitReadiness } from "../../dist-BpSENkRJ.mjs";
2
+ import { $r as BaseAddon, Gi as emitReadiness, Qn as platformProbeCapability, Qr as errMsg, Ta as EventCategory, gr as scoreRuntimes, in as enumerateInferenceDevices } from "../../dist-BSF_C8W2.mjs";
3
3
  import { promisify } from "node:util";
4
4
  import * as os$1 from "node:os";
5
5
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  let zod = require("zod");
8
8
  let node_crypto = require("node:crypto");
9
9
  //#region src/builtins/record-entries/entries-service.ts
@@ -1,4 +1,4 @@
1
- import { Si as UnknownRecordKindError, Yn as recordEntriesCapability, Zi as mapSecretLeaves, qr as BaseAddon, ri as InvalidRecordError, sa as recordSecretRef } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, Oi as UnknownRecordKindError, li as InvalidRecordError, pa as recordSecretRef, ra as mapSecretLeaves, tr as recordEntriesCapability } from "../../dist-BSF_C8W2.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  //#region src/builtins/record-entries/entries-service.ts
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { qr as BaseAddon, ya as EventCategory } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, Ta as EventCategory } from "../../dist-BSF_C8W2.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  const require_snapshot_courtesy = require("../../snapshot-courtesy-D_C5-nC0.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { Fn as memoizePinnedFfmpeg, Gr as DeviceType, Kr as errMsg, Qi as nodePin, Sr as streamQualityLabel, Ur as DeviceFeature, Wt as deriveBatteryPresence, bt as bareAddonId, mr as snapshotCapability, qr as BaseAddon, s as BatteryStatusSchema, xt as batteryCapability, ya as EventCategory } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, Et as batteryCapability, Or as streamQualityLabel, Qr as errMsg, Ta as EventCategory, Tt as bareAddonId, Vn as memoizePinnedFfmpeg, Yr as DeviceFeature, Yt as deriveBatteryPresence, Zr as DeviceType, br as snapshotCapability, ia as nodePin, s as BatteryStatusSchema } from "../../dist-BSF_C8W2.mjs";
2
2
  import { i as renderCourtesyOverlayJpeg, n as courtesyCacheKey, r as renderCourtesyJpeg } from "../../snapshot-courtesy-CyFs1M3I.mjs";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { br as storageProviderCapability, qr as BaseAddon, sn as filesystemBrowseCapability } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, Er as storageProviderCapability, fn as filesystemBrowseCapability } from "../../dist-BSF_C8W2.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -4,7 +4,7 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
6
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./filesystem-storage.addon.js");
7
- const require_wal_maintenance = require("../../wal-maintenance-Bl0ZKjVR.js");
7
+ const require_wal_maintenance = require("../../wal-maintenance-CNEs_I94.js");
8
8
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./sqlite-settings.addon.js");
9
9
  //#region src/builtins/sqlite-storage/config-store.ts
10
10
  var ConfigStore = class {
@@ -1,5 +1,5 @@
1
1
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./filesystem-storage.addon.mjs";
2
- import { A as SQLITE_MMAP_CEILING_BYTES, B as SQLITE_SLOW_CALL_MS, F as sqlitePageCacheKibFor, H as sqliteEngineStorm, I as SQLITE_ENGINE_STORM_WALL_SHARE, L as SQLITE_PROFILE_REPORT_FLOOR_MS, M as SQLITE_PAGE_CACHE_CEILING_KIB, N as SQLITE_PAGE_CACHE_FLOOR_KIB, P as sqliteMmapSizeFor, R as SQLITE_PROFILE_REPORT_INTERVAL_MS, V as SqliteOpProfiler, a as WalMaintenance, i as WAL_MAINTENANCE_INTERVAL_MS, j as SQLITE_MMAP_FLOOR_BYTES, k as SQLITE_JOURNAL_SIZE_LIMIT_BYTES, l as SqliteSettingsBackend, n as WAL_HIGH_WATER_BYTES, r as WAL_IDLE_QUIET_MS, t as WAL_CHECKPOINT_BUSY_TIMEOUT_MS, z as SQLITE_PROFILE_TOP_N } from "../../wal-maintenance-TyuOEVy1.mjs";
2
+ import { A as SQLITE_MMAP_CEILING_BYTES, B as SQLITE_SLOW_CALL_MS, F as sqlitePageCacheKibFor, H as sqliteEngineStorm, I as SQLITE_ENGINE_STORM_WALL_SHARE, L as SQLITE_PROFILE_REPORT_FLOOR_MS, M as SQLITE_PAGE_CACHE_CEILING_KIB, N as SQLITE_PAGE_CACHE_FLOOR_KIB, P as sqliteMmapSizeFor, R as SQLITE_PROFILE_REPORT_INTERVAL_MS, V as SqliteOpProfiler, a as WalMaintenance, i as WAL_MAINTENANCE_INTERVAL_MS, j as SQLITE_MMAP_FLOOR_BYTES, k as SQLITE_JOURNAL_SIZE_LIMIT_BYTES, l as SqliteSettingsBackend, n as WAL_HIGH_WATER_BYTES, r as WAL_IDLE_QUIET_MS, t as WAL_CHECKPOINT_BUSY_TIMEOUT_MS, z as SQLITE_PROFILE_TOP_N } from "../../wal-maintenance-AerF4XE6.mjs";
3
3
  import { SqliteSettingsAddon } from "./sqlite-settings.addon.mjs";
4
4
  //#region src/builtins/sqlite-storage/config-store.ts
5
5
  var ConfigStore = class {
@@ -14,6 +14,7 @@ export interface RecordsEngineOps extends OwnedOps {
14
14
  /** Every kind this file holds, with rows and a size estimate (System -> Data). */
15
15
  listKinds(input: {
16
16
  readonly owner?: string | undefined;
17
+ readonly counts?: boolean | undefined;
17
18
  }): Promise<readonly StorageKind[]>;
18
19
  /** Delete rows past `expires_at`, in batches. */
19
20
  sweepExpired(input: {
@@ -29,6 +29,19 @@ export declare class RecordsProvider implements IRecordsProvider, AdminRecords {
29
29
  init(): Promise<void>;
30
30
  private fileOf;
31
31
  declareKinds(input: Parameters<IRecordsProvider["declareKinds"]>[0]): ReturnType<IRecordsProvider["declareKinds"]>;
32
+ /**
33
+ * Rows of a legacy table in EVERY open records-capable file. A legacy table lives
34
+ * in one file, but which one is the layout's business: the phase-3 NC ledgers moved
35
+ * to `events.db` with the P5 split, and a guard that asked `camstack.db` alone
36
+ * answered "no legacy rows" for 522 outbox rows (2026-10-20 audit).
37
+ */
38
+ private legacyRowsAnywhere;
39
+ /**
40
+ * `transactFamily` (plan risk 8): kinds that must agree through one `transact` sit
41
+ * in ONE file. A family split across files is refused whole, whether the split is
42
+ * inside this declaration or against a kind of the same owner declared before.
43
+ */
44
+ private assertFamiliesInOneFile;
32
45
  /** The boot guard: empty kind + a legacy table that still has rows = adoption pending. */
33
46
  private withAdoption;
34
47
  get(input: Parameters<IRecordsProvider["get"]>[0]): ReturnType<IRecordsProvider["get"]>;
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  const require_retired_settings_keys = require("../../retired-settings-keys-D_VwZiDt.js");
8
- const require_wal_maintenance = require("../../wal-maintenance-Bl0ZKjVR.js");
8
+ const require_wal_maintenance = require("../../wal-maintenance-CNEs_I94.js");
9
9
  const require_sqlite_snapshot_protocol = require("../../sqlite-snapshot-protocol-B9OndqZv.js");
10
10
  const require_wal_checkpoint_policy = require("../../wal-checkpoint-policy-d2z2qPqx.js");
11
11
  let node_fs_promises = require("node:fs/promises");
@@ -1349,8 +1349,10 @@ var RecordsProvider = class {
1349
1349
  async declareKinds(input) {
1350
1350
  const owner = recordsCallerOf(input);
1351
1351
  const byDatabase = /* @__PURE__ */ new Map();
1352
- for (const [kind, raw] of Object.entries(input.kinds)) {
1353
- const declaration = require_dist.RecordKindDeclarationSchema.parse(raw);
1352
+ const parsed = /* @__PURE__ */ new Map();
1353
+ for (const [kind, raw] of Object.entries(input.kinds)) parsed.set(kind, require_dist.RecordKindDeclarationSchema.parse(raw));
1354
+ await this.assertFamiliesInOneFile(owner, parsed);
1355
+ for (const [kind, declaration] of parsed) {
1354
1356
  const known = this.placement.get(keyOf(owner, kind));
1355
1357
  if (known !== void 0 && known !== declaration.database) throw new require_dist.DeclarationRefusedError(`kind "${kind}" of "${owner}" lives in "${known}", declared for "${declaration.database}" - the engine never moves data`);
1356
1358
  const group = byDatabase.get(declaration.database) ?? {};
@@ -1372,6 +1374,50 @@ var RecordsProvider = class {
1372
1374
  }
1373
1375
  return { kinds: await this.withAdoption(owner, input.kinds, merged) };
1374
1376
  }
1377
+ /**
1378
+ * Rows of a legacy table in EVERY open records-capable file. A legacy table lives
1379
+ * in one file, but which one is the layout's business: the phase-3 NC ledgers moved
1380
+ * to `events.db` with the P5 split, and a guard that asked `camstack.db` alone
1381
+ * answered "no legacy rows" for 522 outbox rows (2026-10-20 audit).
1382
+ */
1383
+ async legacyRowsAnywhere(table) {
1384
+ let total = 0;
1385
+ for (const file of this.router.openFiles()) {
1386
+ if (file.databaseId === "history") continue;
1387
+ const rows = await file.records("legacyRowCount", { table });
1388
+ if (rows !== null) total += rows;
1389
+ }
1390
+ return total;
1391
+ }
1392
+ /**
1393
+ * `transactFamily` (plan risk 8): kinds that must agree through one `transact` sit
1394
+ * in ONE file. A family split across files is refused whole, whether the split is
1395
+ * inside this declaration or against a kind of the same owner declared before.
1396
+ */
1397
+ async assertFamiliesInOneFile(owner, declared) {
1398
+ const families = /* @__PURE__ */ new Map();
1399
+ const note = (family, kind, database) => {
1400
+ const members = families.get(family) ?? /* @__PURE__ */ new Map();
1401
+ members.set(kind, database);
1402
+ families.set(family, members);
1403
+ };
1404
+ for (const file of this.router.openFiles()) {
1405
+ if (file.databaseId === "history") continue;
1406
+ for (const k of await file.records("listKinds", {
1407
+ owner,
1408
+ counts: false
1409
+ })) {
1410
+ if (k.state === "dropped" || declared.has(k.kind)) continue;
1411
+ const family = k.declaration.transactFamily;
1412
+ if (family !== void 0) note(family, k.kind, file.databaseId);
1413
+ }
1414
+ }
1415
+ for (const [kind, declaration] of declared) if (declaration.transactFamily !== void 0) note(declaration.transactFamily, kind, declaration.database);
1416
+ for (const [family, members] of families) {
1417
+ if (new Set(members.values()).size <= 1) continue;
1418
+ throw new require_dist.DeclarationRefusedError(`transactFamily "${family}" of "${owner}" is split across files (${[...members.entries()].map(([k, db]) => `${k}@${db}`).join(", ")}); a family lives in ONE file`);
1419
+ }
1420
+ }
1375
1421
  /** The boot guard: empty kind + a legacy table that still has rows = adoption pending. */
1376
1422
  async withAdoption(owner, declared, results) {
1377
1423
  const out = { ...results };
@@ -1407,8 +1453,8 @@ var RecordsProvider = class {
1407
1453
  };
1408
1454
  continue;
1409
1455
  }
1410
- const legacyRows = await (await this.router.recordsFileFor("settings")).records("legacyRowCount", { table: legacy });
1411
- if (legacyRows !== null && legacyRows > 0) out[kind] = {
1456
+ const legacyRows = await this.legacyRowsAnywhere(legacy);
1457
+ if (legacyRows > 0) out[kind] = {
1412
1458
  ...result,
1413
1459
  adoptionPending: {
1414
1460
  legacyTable: legacy,
@@ -1,6 +1,6 @@
1
- import { $ as STORAGE_ADMIN_MOVED_COLLECTIONS, $r as DeclarationRefusedError, Bt as databaseSnapshotCapability, C as DATA_STORE_DATABASES, Kr as errMsg, S as DATABASE_SNAPSHOT_TIMEOUT_MS, Si as UnknownRecordKindError, Xn as recordsCapability, aa as parseSettingsBrokersAdoptsFrom, b as DATABASE_SNAPSHOT_PARTIAL_SUFFIX, bt as bareAddonId, ea as normalizeSecretOwner, et as STORAGE_ADMIN_SCOPED_BASES, fi as RUNTIME_DEFAULTS, gr as storageAdminCapability, hi as RecordKindDeclarationSchema, ia as parseSettingsArrayAdoptsFrom, ii as NoCallerError, kr as vectorStoreCapability, li as RECORD_SWEEP_INTERVAL_MS, qr as BaseAddon, x as DATABASE_SNAPSHOT_SUFFIX, zt as dataStoreProviderCapability } from "../../dist-BpSENkRJ.mjs";
1
+ import { $r as BaseAddon, C as DATABASE_SNAPSHOT_TIMEOUT_MS, Fr as vectorStoreCapability, Gt as databaseSnapshotCapability, Oi as UnknownRecordKindError, Qr as errMsg, S as DATABASE_SNAPSHOT_SUFFIX, Sr as storageAdminCapability, Tt as bareAddonId, Wt as dataStoreProviderCapability, ai as DeclarationRefusedError, at as STORAGE_ADMIN_SCOPED_BASES, da as parseSettingsBrokersAdoptsFrom, hi as RECORD_SWEEP_INTERVAL_MS, it as STORAGE_ADMIN_MOVED_COLLECTIONS, nr as recordsCapability, oa as normalizeSecretOwner, ua as parseSettingsArrayAdoptsFrom, ui as NoCallerError, vi as RUNTIME_DEFAULTS, w as DATA_STORE_DATABASES, x as DATABASE_SNAPSHOT_PARTIAL_SUFFIX, xi as RecordKindDeclarationSchema } from "../../dist-BSF_C8W2.mjs";
2
2
  import { i as retiredKeyStoreOf, n as purgeRetiredSettingsKeys } from "../../retired-settings-keys-Bhwe4ymb.mjs";
3
- import { C as RecordsEngine, D as callVector, E as callRecords, O as controlTargetOf, S as trustResult, T as callData, _ as collectTransferables, a as WalMaintenance, c as vectorProviderOfOwnedFile, d as CrossDatabaseTransactError, f as DatabaseBusyError, l as SqliteSettingsBackend, m as rehydrateError, o as createVectorIndex, p as DuplicatePlacementError, s as vectorProviderOf, u as CollectionPlacementError, v as isFileHostMessage, w as callControl } from "../../wal-maintenance-TyuOEVy1.mjs";
3
+ import { C as RecordsEngine, D as callVector, E as callRecords, O as controlTargetOf, S as trustResult, T as callData, _ as collectTransferables, a as WalMaintenance, c as vectorProviderOfOwnedFile, d as CrossDatabaseTransactError, f as DatabaseBusyError, l as SqliteSettingsBackend, m as rehydrateError, o as createVectorIndex, p as DuplicatePlacementError, s as vectorProviderOf, u as CollectionPlacementError, v as isFileHostMessage, w as callControl } from "../../wal-maintenance-AerF4XE6.mjs";
4
4
  import { r as isSnapshotWorkerMessage } from "../../sqlite-snapshot-protocol-2QaTouWV.mjs";
5
5
  import { a as isWorkerMessage, t as WAL_CHECKPOINT_LARGE_PASS_FRAMES } from "../../wal-checkpoint-policy-BahrTtyD.mjs";
6
6
  import { createRequire } from "node:module";
@@ -1343,8 +1343,10 @@ var RecordsProvider = class {
1343
1343
  async declareKinds(input) {
1344
1344
  const owner = recordsCallerOf(input);
1345
1345
  const byDatabase = /* @__PURE__ */ new Map();
1346
- for (const [kind, raw] of Object.entries(input.kinds)) {
1347
- const declaration = RecordKindDeclarationSchema.parse(raw);
1346
+ const parsed = /* @__PURE__ */ new Map();
1347
+ for (const [kind, raw] of Object.entries(input.kinds)) parsed.set(kind, RecordKindDeclarationSchema.parse(raw));
1348
+ await this.assertFamiliesInOneFile(owner, parsed);
1349
+ for (const [kind, declaration] of parsed) {
1348
1350
  const known = this.placement.get(keyOf(owner, kind));
1349
1351
  if (known !== void 0 && known !== declaration.database) throw new DeclarationRefusedError(`kind "${kind}" of "${owner}" lives in "${known}", declared for "${declaration.database}" - the engine never moves data`);
1350
1352
  const group = byDatabase.get(declaration.database) ?? {};
@@ -1366,6 +1368,50 @@ var RecordsProvider = class {
1366
1368
  }
1367
1369
  return { kinds: await this.withAdoption(owner, input.kinds, merged) };
1368
1370
  }
1371
+ /**
1372
+ * Rows of a legacy table in EVERY open records-capable file. A legacy table lives
1373
+ * in one file, but which one is the layout's business: the phase-3 NC ledgers moved
1374
+ * to `events.db` with the P5 split, and a guard that asked `camstack.db` alone
1375
+ * answered "no legacy rows" for 522 outbox rows (2026-10-20 audit).
1376
+ */
1377
+ async legacyRowsAnywhere(table) {
1378
+ let total = 0;
1379
+ for (const file of this.router.openFiles()) {
1380
+ if (file.databaseId === "history") continue;
1381
+ const rows = await file.records("legacyRowCount", { table });
1382
+ if (rows !== null) total += rows;
1383
+ }
1384
+ return total;
1385
+ }
1386
+ /**
1387
+ * `transactFamily` (plan risk 8): kinds that must agree through one `transact` sit
1388
+ * in ONE file. A family split across files is refused whole, whether the split is
1389
+ * inside this declaration or against a kind of the same owner declared before.
1390
+ */
1391
+ async assertFamiliesInOneFile(owner, declared) {
1392
+ const families = /* @__PURE__ */ new Map();
1393
+ const note = (family, kind, database) => {
1394
+ const members = families.get(family) ?? /* @__PURE__ */ new Map();
1395
+ members.set(kind, database);
1396
+ families.set(family, members);
1397
+ };
1398
+ for (const file of this.router.openFiles()) {
1399
+ if (file.databaseId === "history") continue;
1400
+ for (const k of await file.records("listKinds", {
1401
+ owner,
1402
+ counts: false
1403
+ })) {
1404
+ if (k.state === "dropped" || declared.has(k.kind)) continue;
1405
+ const family = k.declaration.transactFamily;
1406
+ if (family !== void 0) note(family, k.kind, file.databaseId);
1407
+ }
1408
+ }
1409
+ for (const [kind, declaration] of declared) if (declaration.transactFamily !== void 0) note(declaration.transactFamily, kind, declaration.database);
1410
+ for (const [family, members] of families) {
1411
+ if (new Set(members.values()).size <= 1) continue;
1412
+ throw new DeclarationRefusedError(`transactFamily "${family}" of "${owner}" is split across files (${[...members.entries()].map(([k, db]) => `${k}@${db}`).join(", ")}); a family lives in ONE file`);
1413
+ }
1414
+ }
1369
1415
  /** The boot guard: empty kind + a legacy table that still has rows = adoption pending. */
1370
1416
  async withAdoption(owner, declared, results) {
1371
1417
  const out = { ...results };
@@ -1401,8 +1447,8 @@ var RecordsProvider = class {
1401
1447
  };
1402
1448
  continue;
1403
1449
  }
1404
- const legacyRows = await (await this.router.recordsFileFor("settings")).records("legacyRowCount", { table: legacy });
1405
- if (legacyRows !== null && legacyRows > 0) out[kind] = {
1450
+ const legacyRows = await this.legacyRowsAnywhere(legacy);
1451
+ if (legacyRows > 0) out[kind] = {
1406
1452
  ...result,
1407
1453
  adoptionPending: {
1408
1454
  legacyTable: legacy,
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DjQ8C-ku.js");
6
+ const require_dist = require("../../dist-BpqA2CT0.js");
7
7
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");