@rubytech/create-maxy-code 0.1.329 → 0.1.331

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/index.js +19 -2
  2. package/package.json +1 -1
  3. package/payload/platform/config/brand.json +1 -0
  4. package/payload/platform/lib/account-enumeration/dist/index.d.ts +33 -0
  5. package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/account-enumeration/dist/index.js +76 -0
  7. package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -1
  8. package/payload/platform/lib/account-enumeration/src/index.ts +78 -0
  9. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/admin/PLUGIN.md +13 -1
  11. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js +1 -2
  12. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js.map +1 -1
  13. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts.map +1 -1
  15. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js +18 -13
  16. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js.map +1 -1
  17. package/payload/platform/plugins/admin/mcp/dist/index.js +102 -0
  18. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.d.ts +2 -0
  20. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.d.ts.map +1 -0
  21. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.js +57 -0
  22. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.js.map +1 -0
  23. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +34 -0
  24. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -0
  25. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +78 -0
  26. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -0
  27. package/payload/platform/plugins/admin/skills/plainly/SKILL.md +7 -0
  28. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
  29. package/payload/platform/plugins/contacts/mcp/dist/index.js +1 -1
  30. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  31. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.d.ts +2 -0
  32. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.d.ts.map +1 -0
  33. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js +71 -0
  34. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js.map +1 -0
  35. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +1 -0
  36. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  37. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +10 -3
  38. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  39. package/payload/platform/plugins/contacts/mcp/package.json +4 -2
  40. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  41. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  42. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/index.js +3 -1
  44. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.js +39 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.js.map +1 -1
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.d.ts.map +1 -1
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.js +13 -1
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.js.map +1 -1
  50. package/payload/platform/plugins/slides/skills/deck-system/SKILL.md +1 -1
  51. package/payload/platform/scripts/__tests__/provision-role-stamp.test.sh +40 -0
  52. package/payload/platform/scripts/__tests__/sweep-durability.test.sh +103 -0
  53. package/payload/platform/scripts/lib/provision-account-dir.sh +319 -0
  54. package/payload/platform/scripts/lib/resolve-account-dir.sh +79 -132
  55. package/payload/platform/scripts/setup-account.sh +28 -361
  56. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +4 -0
  58. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  59. package/payload/platform/services/claude-session-manager/dist/config.d.ts +18 -0
  60. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  61. package/payload/platform/services/claude-session-manager/dist/config.js +63 -7
  62. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  63. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  64. package/payload/platform/services/claude-session-manager/dist/http-server.js +44 -2
  65. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  66. package/payload/platform/services/claude-session-manager/dist/index.js +50 -2
  67. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  68. package/payload/platform/services/claude-session-manager/dist/rc-child-log.d.ts +13 -0
  69. package/payload/platform/services/claude-session-manager/dist/rc-child-log.d.ts.map +1 -0
  70. package/payload/platform/services/claude-session-manager/dist/rc-child-log.js +52 -0
  71. package/payload/platform/services/claude-session-manager/dist/rc-child-log.js.map +1 -0
  72. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +7 -0
  74. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +5 -0
  76. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +8 -0
  78. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
  79. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts +70 -0
  80. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts.map +1 -0
  81. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js +213 -0
  82. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js.map +1 -0
  83. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  84. package/payload/platform/services/webchat-channel/dist/instructions.js +25 -1
  85. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  86. package/payload/platform/services/webchat-channel/dist/notification.d.ts +17 -0
  87. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  88. package/payload/platform/services/webchat-channel/dist/notification.js +14 -0
  89. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  90. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  91. package/payload/platform/services/webchat-channel/dist/server.js +66 -2
  92. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  93. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +28 -0
  94. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -0
  95. package/payload/platform/services/webchat-channel/dist/turn-follow.js +188 -0
  96. package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -0
  97. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  98. package/payload/platform/services/whatsapp-channel/dist/notification.js +24 -1
  99. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  100. package/payload/platform/templates/account.json +2 -0
  101. package/payload/premium-plugins/management-consulting/skills/pitch-deck/SKILL.md +44 -37
  102. package/payload/server/public/assets/AdminLoginScreens-DexSvtjD.js +1 -0
  103. package/payload/server/public/assets/AdminShell-Bf9tndNg.js +1 -0
  104. package/payload/server/public/assets/{Checkbox-DWGpkJD5.js → Checkbox-D58GsKoQ.js} +1 -1
  105. package/payload/server/public/assets/{OperatorConversations-BWu6GMQM.css → OperatorConversations-CDdp2nVn.css} +1 -1
  106. package/payload/server/public/assets/OperatorConversations-RmqANYz8.js +9 -0
  107. package/payload/server/public/assets/{admin-CnIlm4Yi.js → admin-CH5Vkkt4.js} +1 -1
  108. package/payload/server/public/assets/{browser-ePr5LxPd.js → browser-BwDtzSlr.js} +1 -1
  109. package/payload/server/public/assets/chat-3GY0LmYH.js +1 -0
  110. package/payload/server/public/assets/data-CttrzhfL.js +1 -0
  111. package/payload/server/public/assets/{graph-B5uxt5YB.js → graph-CZSdF2jv.js} +3 -3
  112. package/payload/server/public/assets/{graph-labels-fx-bv3Y-.js → graph-labels-Bi0fu8Ns.js} +1 -1
  113. package/payload/server/public/assets/operator-BEivxmRy.js +1 -0
  114. package/payload/server/public/assets/page-DUvT07SB.js +30 -0
  115. package/payload/server/public/assets/{public-DXAdRKJe.js → public-B3eIlVbd.js} +1 -1
  116. package/payload/server/public/browser.html +4 -4
  117. package/payload/server/public/chat.html +5 -5
  118. package/payload/server/public/data.html +4 -4
  119. package/payload/server/public/graph.html +6 -6
  120. package/payload/server/public/index.html +6 -6
  121. package/payload/server/public/operator.html +7 -7
  122. package/payload/server/public/public.html +5 -5
  123. package/payload/server/server.js +314 -222
  124. package/payload/server/public/assets/AdminLoginScreens-DstNf4M-.js +0 -1
  125. package/payload/server/public/assets/AdminShell-B5EcXCYR.js +0 -1
  126. package/payload/server/public/assets/OperatorConversations-CSQnX7Dx.js +0 -9
  127. package/payload/server/public/assets/chat-XjXSornk.js +0 -1
  128. package/payload/server/public/assets/data-DuObS80U.js +0 -1
  129. package/payload/server/public/assets/operator-CIGXegWd.js +0 -1
  130. package/payload/server/public/assets/page-BVOAwj-e.js +0 -30
@@ -1,184 +1,131 @@
1
- # resolve-account-dir.sh — identity-aware account resolver + orphan sweeper
1
+ # resolve-account-dir.sh — house-account resolver + registry-membership reporter
2
2
  # Sourced by setup-account.sh and seed-neo4j.sh. Defines `resolve_and_sweep_account_dir`.
3
3
  #
4
+ # Multi-account managed-service model (Task 983): an install may host N accounts
5
+ # under data/accounts/ — exactly one carries role:"house", the rest role:"client".
6
+ # The registry is the set of dirs holding a parseable account.json. EVERY
7
+ # registered account is durable: this resolver never trashes a dir with an
8
+ # account.json. It resolves ACCOUNT_DIR/ACCOUNT_ID to the single role:"house"
9
+ # account (or the sole account in the pre-migration window, before
10
+ # setup-account.sh stamps role:"house").
11
+ #
12
+ # Sweep is LOG-ONLY this sprint. The destructive `mv` to .trash/ was removed to
13
+ # eliminate any chance of deleting a registered account during the multi-account
14
+ # rollout; restoring a guarded destructive sweep is tracked as a follow-up task.
15
+ # Orphan dirs (no account.json) are reported, never moved.
16
+ #
4
17
  # Invariant: the TypeScript `resolveAccount()` at platform/ui/app/lib/claude-agent.ts
5
- # is READ-ONLY. This bash helper is the SOLE sweep surface, because seed-time is
6
- # the only safe moment to mutate filesystem layout. Any future "share the logic"
7
- # refactor must preserve that asymmetry.
18
+ # is READ-ONLY. This bash helper is the SOLE filesystem-mutation surface for the
19
+ # account layout, because seed-time is the only safe moment to mutate it.
8
20
  #
9
21
  # Contract (inputs via env vars):
10
22
  # ACCOUNTS_DIR — {installDir}/data/accounts
11
- # USERS_FILE — $HOME/<brand.configDir>/users.json
23
+ # USERS_FILE — $HOME/<brand.configDir>/users.json (read-tolerant; unused for sweep)
12
24
  #
13
25
  # Contract (outputs via env vars):
14
- # ACCOUNT_ID — resolved uuid (existing kept, or freshly minted)
15
- # ACCOUNT_DIR — full path to the live account dir
26
+ # ACCOUNT_ID — resolved house uuid (existing kept, or freshly minted on fresh install)
27
+ # ACCOUNT_DIR — full path to the live house account dir
16
28
  #
17
29
  # Exit:
18
- # 0 on success; non-zero on abort (multi-candidate + no unique identity match).
30
+ # 0 on success; non-zero on abort (registry drift: zero-house with >1 candidate,
31
+ # or multiple houses).
19
32
 
20
- # Helper: classify account.json at $1 against userId $2.
21
- # Exit codes are trichotomous so callers can distinguish "definitely not a match"
22
- # from "could not determine" the latter is a doctrine-level loud failure, not
23
- # a silent fall-through to orphan-classification.
24
- # 0 = admins[] contains userId (match)
25
- # 1 = account.json readable and parsed; userId absent from admins[] (no match)
26
- # 2 = account.json missing, unreadable, or unparseable (undetermined — caller must abort)
27
- _account_dir_matches() {
33
+ # Helper: print the `role` field of the account.json at $1, or empty string when
34
+ # absent/unparseable. Trichotomy is unnecessary here a missing role just means
35
+ # "not a house candidate"; corruption discipline lives in the enumeration step.
36
+ _account_dir_role() {
28
37
  local config="$1/account.json"
29
- local uid="$2"
30
- [ -f "$config" ] || return 2
31
- python3 - "$config" "$uid" <<'PY'
38
+ [ -f "$config" ] || { echo ""; return 0; }
39
+ python3 - "$config" <<'PY' 2>/dev/null || echo ""
32
40
  import json, sys
33
41
  try:
34
42
  cfg = json.load(open(sys.argv[1]))
35
43
  except Exception:
36
- sys.exit(2)
37
- admins = cfg.get('admins') or []
38
- uid = sys.argv[2]
39
- for a in admins:
40
- if isinstance(a, dict) and a.get('userId') == uid:
41
- sys.exit(0)
42
- sys.exit(1)
44
+ print(""); sys.exit(0)
45
+ print(cfg.get("role") or "")
43
46
  PY
44
47
  }
45
48
 
46
- # Two-pass resolver: enumerate+classify all candidates, then act.
47
- # Never mutates filesystem before the full classification is complete.
49
+ # House-account resolver. Enumerate registered candidates (dirs with account.json),
50
+ # classify by role, resolve the house (or sole-unlabelled), report orphans, and
51
+ # emit the registry snapshot. Never mutates the filesystem layout.
48
52
  resolve_and_sweep_account_dir() {
49
53
  mkdir -p "$ACCOUNTS_DIR"
50
54
 
51
55
  # Enumerate candidates: direct children with account.json, basenames not starting with '.'
52
- local candidates=()
56
+ local candidates=() orphans=()
53
57
  local dir base
54
58
  for dir in "$ACCOUNTS_DIR"/*/; do
55
59
  [ -d "$dir" ] || continue
56
60
  base="$(basename "$dir")"
57
61
  case "$base" in .*) continue ;; esac
58
- [ -f "$dir/account.json" ] || continue
59
- candidates+=("$base")
62
+ if [ -f "$dir/account.json" ]; then
63
+ candidates+=("$base")
64
+ else
65
+ orphans+=("$base")
66
+ fi
60
67
  done
61
68
 
62
- # Read target userId from users.json (if present and valid)
63
- local target_user_id=""
64
- if [ -f "$USERS_FILE" ]; then
65
- target_user_id=$(python3 - "$USERS_FILE" <<'PY' 2>/dev/null || true
66
- import json, sys
67
- try:
68
- data = json.load(open(sys.argv[1]))
69
- if isinstance(data, list) and data and isinstance(data[0], dict):
70
- uid = data[0].get('userId')
71
- if uid:
72
- print(uid)
73
- except Exception:
74
- pass
75
- PY
76
- )
77
- fi
78
-
79
69
  local n=${#candidates[@]}
80
70
 
81
71
  # Case 0: no candidates — fresh install, mint new UUID.
82
- # sub-scope F — atomic-mint discipline: refuse to mint a new
83
- # accountId if any stub dir (subdir present, account.json missing) exists
84
- # under data/accounts/. The root cause is install 1 minting accountId X
85
- # then losing X/account.json (cron stamped X/logs/ first); the next
86
- # install sees zero candidates and mints Y, orphaning X/logs/. The
87
- # guard turns that silent regression into an operator-actionable abort.
72
+ # Atomic-mint discipline: refuse to mint if any stub dir (subdir present,
73
+ # account.json missing) exists a half-provisioned account from a crashed
74
+ # prior install must be operator-repaired, not silently orphaned.
88
75
  if [ "$n" -eq 0 ]; then
89
- local stub_dirs=()
90
- for dir in "$ACCOUNTS_DIR"/*/; do
91
- [ -d "$dir" ] || continue
92
- base="$(basename "$dir")"
93
- case "$base" in .*) continue ;; esac
94
- [ -f "$dir/account.json" ] && continue
95
- stub_dirs+=("$base")
96
- done
97
- if [ "${#stub_dirs[@]}" -gt 0 ]; then
98
- echo "==> [seed] FAIL phase=mint reason=\"stub-account-dirs present (no account.json): ${stub_dirs[*]}\" — refusing to mint a fresh accountId; remove or repair the stub before re-running install" >&2
76
+ if [ "${#orphans[@]}" -gt 0 ]; then
77
+ echo "==> [seed] FAIL phase=mint reason=\"stub-account-dirs present (no account.json): ${orphans[*]}\" — refusing to mint a fresh accountId; remove or repair the stub before re-running install" >&2
99
78
  return 1
100
79
  fi
101
80
  ACCOUNT_ID="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')"
102
81
  ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
82
+ echo "==> [seed] op=registry-snapshot houses=0 clients=0"
103
83
  return 0
104
84
  fi
105
85
 
106
- # Case 1: single candidate — accept (preserves legacy + fresh-install-pre-users.json).
107
- # Log identity-match on exact match; WARN on mismatch (mirrors TS resolveAccount line 653 —
108
- # accept-with-flag rather than abort because the users.json regeneration on PIN
109
- # change would otherwise brick legitimate installs).
110
- if [ "$n" -eq 1 ]; then
111
- ACCOUNT_ID="${candidates[0]}"
112
- ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
113
- if [ -n "$target_user_id" ]; then
114
- local rc=0
115
- _account_dir_matches "$ACCOUNT_DIR" "$target_user_id" || rc=$?
116
- case "$rc" in
117
- 0) echo "==> [seed] identity-match: kept=${ACCOUNT_ID:0:8} via userId=${target_user_id:0:8}" ;;
118
- 1) echo "==> [seed] WARN: single account does not match users.json userId=${target_user_id:0:8} — accepting but flagging" >&2 ;;
119
- *) echo "==> [seed] identity-match FAILED: sole candidate $ACCOUNT_ID has unreadable or unparseable account.json — aborting" >&2
120
- return 1 ;;
121
- esac
122
- fi
123
- return 0
124
- fi
125
-
126
- # Case 2+: multi-candidate — identity-match-or-abort.
127
- # First classify everything, THEN decide and act.
128
- if [ -z "$target_user_id" ]; then
129
- echo "==> [seed] identity-match FAILED: users.json missing or unreadable, $n candidates — aborting" >&2
130
- for uuid in "${candidates[@]}"; do echo " candidate: $uuid" >&2; done
131
- return 1
132
- fi
133
-
134
- local matches=()
135
- local orphans=()
136
- local rc
86
+ # Classify candidates by role.
87
+ local houses=() clients=() uuid role
137
88
  for uuid in "${candidates[@]}"; do
138
- rc=0
139
- _account_dir_matches "$ACCOUNTS_DIR/$uuid" "$target_user_id" || rc=$?
140
- case "$rc" in
141
- 0) matches+=("$uuid") ;;
142
- 1) orphans+=("$uuid") ;;
143
- *) echo "==> [seed] identity-match FAILED: candidate $uuid has unreadable or unparseable account.json — aborting" >&2
144
- for u in "${candidates[@]}"; do echo " candidate: $u" >&2; done
145
- return 1 ;;
89
+ role="$(_account_dir_role "$ACCOUNTS_DIR/$uuid")"
90
+ case "$role" in
91
+ house) houses+=("$uuid") ;;
92
+ *) clients+=("$uuid") ;; # client OR unlabelled (legacy)
146
93
  esac
147
94
  done
148
95
 
149
- if [ "${#matches[@]}" -eq 0 ]; then
150
- echo "==> [seed] identity-match FAILED: $n candidates, none match users.json userId=${target_user_id:0:8} — aborting" >&2
151
- for uuid in "${candidates[@]}"; do echo " candidate: $uuid" >&2; done
152
- return 1
153
- fi
96
+ # Pre-action registry snapshot — the count the post-seed census must match.
97
+ echo "==> [seed] op=registry-snapshot houses=${#houses[@]} clients=${#clients[@]}"
154
98
 
155
- if [ "${#matches[@]}" -gt 1 ]; then
156
- echo "==> [seed] identity-match FAILED: $n candidates, ${#matches[@]} match userId=${target_user_id:0:8} (ambiguous) — aborting" >&2
157
- for uuid in "${candidates[@]}"; do echo " candidate: $uuid" >&2; done
158
- return 1
99
+ # Report orphans (dirs with no account.json). Log-only never moved this sprint.
100
+ # Guard the empty-array expansion: under `set -u` (setup-account.sh uses
101
+ # `set -euo pipefail`), bash 3.2 treats "${orphans[@]}" on an empty array as an
102
+ # unbound variable and aborts.
103
+ local ts; ts=$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || echo unknown)
104
+ if [ "${#orphans[@]}" -gt 0 ]; then
105
+ for uuid in "${orphans[@]}"; do
106
+ echo "==> [seed] op=sweep uuid=${uuid:0:8} registered=false action=skipped-guarded ts=$ts"
107
+ done
159
108
  fi
160
109
 
161
- ACCOUNT_ID="${matches[0]}"
162
- ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
163
- echo "==> [seed] identity-match: kept=${ACCOUNT_ID:0:8} via userId=${target_user_id:0:8}"
110
+ # Resolve the house.
111
+ if [ "${#houses[@]}" -eq 1 ]; then
112
+ ACCOUNT_ID="${houses[0]}"
113
+ ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
114
+ echo "==> [seed] house-resolved: ${ACCOUNT_ID:0:8}"
115
+ return 0
116
+ fi
164
117
 
165
- # Sweep orphans. mv is the only destructive op; abort loud on failure before any further state change.
166
- local trash_dir="$ACCOUNTS_DIR/.trash"
167
- mkdir -p "$trash_dir"
168
- local ts; ts=$(date -u +%Y%m%dT%H%M%SZ)
169
- local moved=0 uuid dest
170
- for uuid in "${orphans[@]}"; do
171
- dest="$trash_dir/${uuid}-${ts}"
172
- if ! mv "$ACCOUNTS_DIR/$uuid" "$dest"; then
173
- echo "==> [seed] ERROR: failed to move $uuid → $dest — aborting" >&2
174
- return 1
175
- fi
176
- echo "==> [seed] swept orphan: ${uuid:0:8} → .trash/${uuid:0:8}-${ts}"
177
- moved=$((moved + 1))
178
- done
179
- if [ "$moved" -gt 0 ]; then
180
- echo "==> [seed] orphan sweep: moved $moved → $ACCOUNTS_DIR/.trash/"
118
+ # Pre-migration tolerance: zero labelled house but exactly one account it is
119
+ # the house (setup-account.sh stamps role:"house" on it).
120
+ if [ "${#houses[@]}" -eq 0 ] && [ "$n" -eq 1 ]; then
121
+ ACCOUNT_ID="${candidates[0]}"
122
+ ACCOUNT_DIR="$ACCOUNTS_DIR/$ACCOUNT_ID"
123
+ echo "==> [seed] house-resolved: ${ACCOUNT_ID:0:8} (sole account, pre-migration)"
124
+ return 0
181
125
  fi
182
126
 
183
- return 0
127
+ # Genuine drift — loud-fail.
128
+ echo "==> [seed] FAIL phase=resolve-house reason=\"houses=${#houses[@]} total=$n — expected exactly one role:house (or a single unlabelled account)\" — aborting" >&2
129
+ for uuid in "${candidates[@]}"; do echo " candidate: $uuid role=$(_account_dir_role "$ACCOUNTS_DIR/$uuid")" >&2; done
130
+ return 1
184
131
  }