@zerwiz/ymir 0.1.3 → 0.1.5

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 (850) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +9 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -39
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,971 @@
1
+ #!/usr/bin/env bash
2
+ # Behavioral coverage for per-home summary publication through the real
3
+ # producer, writer, watcher-carried status trigger, and unchanged snapshot path.
4
+ set -u
5
+
6
+ # shellcheck source=tests/lib.sh
7
+ # shellcheck disable=SC1091
8
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
9
+
10
+ WRITER="$ROOT/bin/fm-home-summary-refresh.sh"
11
+ SNAPSHOT="$ROOT/bin/fm-fleet-snapshot.sh"
12
+ WATCH="$ROOT/bin/fm-watch.sh"
13
+ TMP_ROOT=$(fm_test_tmproot fm-home-summary-refresh)
14
+ HOME_DIR="$TMP_ROOT/mate-home"
15
+ CADENCE_HOME="$TMP_ROOT/cadence-home"
16
+ PARENT_HOME="$TMP_ROOT/parent-home"
17
+ FAKEBIN=$(fm_fakebin "$TMP_ROOT")
18
+ WATCH_PID=
19
+ SLOW_WRITER_PID=
20
+ SLOW_WORKER_PGID=
21
+ SLOW_NM_PID=
22
+ LOCK_HOLDER_PID=
23
+
24
+ cleanup() {
25
+ local pid
26
+ case "$SLOW_WORKER_PGID" in
27
+ ''|*[!0-9]*) ;;
28
+ *) kill -KILL -- "-$SLOW_WORKER_PGID" >/dev/null 2>&1 || true ;;
29
+ esac
30
+ for pid in "$WATCH_PID" "$SLOW_WRITER_PID" "$SLOW_NM_PID" "$LOCK_HOLDER_PID"; do
31
+ [ -n "$pid" ] || continue
32
+ kill -KILL "$pid" >/dev/null 2>&1 || true
33
+ done
34
+ fm_test_cleanup
35
+ }
36
+ trap cleanup EXIT
37
+ trap 'cleanup; exit 130' INT
38
+ trap 'cleanup; exit 143' TERM
39
+
40
+ command -v jq >/dev/null 2>&1 || { echo "skip: jq not found"; exit 0; }
41
+
42
+ cat > "$FAKEBIN/tmux" <<'SH'
43
+ #!/usr/bin/env bash
44
+ case "${1:-}" in
45
+ display-message) printf '%%1\n' ;;
46
+ capture-pane) printf 'fixture pane\n> \n' ;;
47
+ esac
48
+ exit 0
49
+ SH
50
+ cat > "$FAKEBIN/no-mistakes" <<'SH'
51
+ #!/usr/bin/env bash
52
+ if [ -n "${FM_TEST_NM_MARKER:-}" ]; then
53
+ printf '%s\n' "$$" > "$FM_TEST_NM_MARKER"
54
+ sleep "${FM_TEST_NM_SLEEP:-30}"
55
+ fi
56
+ exit 0
57
+ SH
58
+ chmod +x "$FAKEBIN/tmux" "$FAKEBIN/no-mistakes"
59
+
60
+ mkdir -p "$HOME_DIR/state" "$HOME_DIR/data" "$HOME_DIR/config" \
61
+ "$HOME_DIR/projects/task" "$HOME_DIR/bin"
62
+ printf '# Seeded Firstmate home\n' > "$HOME_DIR/AGENTS.md"
63
+ printf 'mate\n' > "$HOME_DIR/.fm-secondmate-home"
64
+ fm_git_init_commit "$HOME_DIR/projects/task"
65
+ git -C "$HOME_DIR/projects/task" checkout -q -b fm/ledger-task
66
+ cat > "$HOME_DIR/data/backlog.md" <<'EOF'
67
+ ## In flight
68
+ - [ ] ledger-task - Publish the home ledger (repo: firstmate) (kind: ship) (since 2026-08-28)
69
+
70
+ ## Queued
71
+
72
+ ## Done
73
+ EOF
74
+ fm_write_meta "$HOME_DIR/state/ledger-task.meta" \
75
+ "window=fmtest:fm-ledger-task" \
76
+ "worktree=$HOME_DIR/projects/task" \
77
+ "project=firstmate" \
78
+ "harness=claude" \
79
+ "kind=ship" \
80
+ "mode=no-mistakes" \
81
+ "spawn_gen=fm.ledger123456"
82
+ busy_gen=$("$ROOT/bin/fm-busy-event.sh" arm "$HOME_DIR/state" ledger-task)
83
+ "$ROOT/bin/fm-busy-event.sh" apply "$HOME_DIR/state" ledger-task idle \
84
+ --gen "$busy_gen" --source claude-hook --event stop
85
+
86
+ NOW_ONE=2026-08-28T10:00:00Z
87
+ EPOCH_ONE=1787911200
88
+ NOW_TWO=2026-08-28T10:01:00Z
89
+ EPOCH_TWO=1787911260
90
+ NOW_THREE=2026-08-28T10:02:00Z
91
+ EPOCH_THREE=1787911320
92
+
93
+ run_writer() { # <now> <epoch> [writer args...]
94
+ local now=$1 epoch=$2
95
+ shift 2
96
+ PATH="$FAKEBIN:$PATH" \
97
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
98
+ FM_SNAPSHOT_NOW="$now" FM_SNAPSHOT_NOW_EPOCH="$epoch" \
99
+ "$WRITER" "$@"
100
+ }
101
+
102
+ run_producer() { # <now> <epoch>
103
+ PATH="$FAKEBIN:$PATH" \
104
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
105
+ FM_SNAPSHOT_NOW="$1" FM_SNAPSHOT_NOW_EPOCH="$2" \
106
+ "$SNAPSHOT" --secondmate-home-summary
107
+ }
108
+
109
+ wait_for_ledger_generation() { # <generated> [tenths]
110
+ local want=$1 attempts=${2:-150} i=0 got
111
+ while [ "$i" -lt "$attempts" ]; do
112
+ got=$(jq -r '.generated // ""' "$HOME_DIR/state/home-summary.json" 2>/dev/null || true)
113
+ [ "$got" = "$want" ] && return 0
114
+ sleep 0.1
115
+ i=$((i + 1))
116
+ done
117
+ return 1
118
+ }
119
+
120
+ run_writer "$NOW_ONE" "$EPOCH_ONE" || fail "initial home-summary publication failed"
121
+ jq -e --arg home "$HOME_DIR" --arg now "$NOW_ONE" --argjson epoch "$EPOCH_ONE" '
122
+ .schema == "fm-secondmate-home-summary.v1"
123
+ and .home == $home
124
+ and .generated == $now
125
+ and .generated_epoch == $epoch
126
+ ' "$HOME_DIR/state/home-summary.json" >/dev/null \
127
+ || fail "initial ledger did not expose the extended producer schema"
128
+
129
+ PATH="$FAKEBIN:$PATH" \
130
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
131
+ FM_SNAPSHOT_NOW="$NOW_TWO" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_TWO" \
132
+ FM_POLL=1 FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
133
+ "$WATCH" > "$TMP_ROOT/watch.out" 2> "$TMP_ROOT/watch.err" &
134
+ WATCH_PID=$!
135
+ i=0
136
+ while [ ! -e "$HOME_DIR/state/.last-watcher-beat" ] && [ "$i" -lt 100 ]; do
137
+ kill -0 "$WATCH_PID" 2>/dev/null || break
138
+ sleep 0.05
139
+ i=$((i + 1))
140
+ done
141
+ [ -e "$HOME_DIR/state/.last-watcher-beat" ] \
142
+ || fail "the real watcher did not begin polling: $(cat "$TMP_ROOT/watch.err" 2>/dev/null)"
143
+ printf 'blocked [key=fixture-dependency]: waiting for the fixture dependency\n' \
144
+ >> "$HOME_DIR/state/ledger-task.status"
145
+ wait_for_ledger_generation "$NOW_TWO" \
146
+ || fail "a status append did not refresh the ledger within the watcher cadence"
147
+ wait "$WATCH_PID" >/dev/null 2>&1 || true
148
+ WATCH_PID=
149
+
150
+ run_producer "$NOW_TWO" "$EPOCH_TWO" > "$TMP_ROOT/fresh-summary.json" \
151
+ || fail "fresh secondmate-home-summary production failed"
152
+ jq -S 'del(.generated, .generated_epoch)' "$HOME_DIR/state/home-summary.json" \
153
+ > "$TMP_ROOT/published-normalized.json"
154
+ jq -S 'del(.generated, .generated_epoch)' "$TMP_ROOT/fresh-summary.json" \
155
+ > "$TMP_ROOT/fresh-normalized.json"
156
+ cmp -s "$TMP_ROOT/published-normalized.json" "$TMP_ROOT/fresh-normalized.json" \
157
+ || fail "the status-triggered ledger differed from the real fresh producer"
158
+ pass "watcher-carried status append publishes the real home summary"
159
+
160
+ mkdir -p "$CADENCE_HOME/state" "$CADENCE_HOME/data" "$CADENCE_HOME/config" \
161
+ "$CADENCE_HOME/projects"
162
+ printf '# Seeded Firstmate home\n' > "$CADENCE_HOME/AGENTS.md"
163
+ printf 'cadence\n' > "$CADENCE_HOME/.fm-secondmate-home"
164
+ cat > "$CADENCE_HOME/data/backlog.md" <<'EOF'
165
+ ## In flight
166
+
167
+ ## Queued
168
+
169
+ ## Done
170
+ EOF
171
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$CADENCE_HOME" \
172
+ FM_SNAPSHOT_NOW="$NOW_TWO" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_TWO" \
173
+ "$WRITER" || fail "could not seed the cadence ledger"
174
+ touch -t 203801010000 "$CADENCE_HOME/state/home-summary.json"
175
+ PATH="$FAKEBIN:$PATH" \
176
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$CADENCE_HOME" \
177
+ FM_SNAPSHOT_NOW="$NOW_THREE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_THREE" \
178
+ FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=1 FM_SIGNAL_GRACE=0 \
179
+ FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
180
+ "$WATCH" > "$TMP_ROOT/cadence-watch.out" 2> "$TMP_ROOT/cadence-watch.err" &
181
+ WATCH_PID=$!
182
+ i=0
183
+ while [ ! -e "$CADENCE_HOME/state/.last-watcher-beat" ] && [ "$i" -lt 100 ]; do
184
+ kill -0 "$WATCH_PID" 2>/dev/null || break
185
+ sleep 0.05
186
+ i=$((i + 1))
187
+ done
188
+ [ -e "$CADENCE_HOME/state/.last-watcher-beat" ] \
189
+ || fail "the cadence watcher did not complete its initial cycle"
190
+ python3 - "$CADENCE_HOME/data/backlog.md" <<'PY'
191
+ from pathlib import Path
192
+ import sys
193
+ path = Path(sys.argv[1])
194
+ text = path.read_text()
195
+ path.write_text(text.replace("## Queued\n\n## Done", "## Queued\n- [ ] cadence-task - Publish without a status signal (repo: firstmate) (kind: ship)\n\n## Done"))
196
+ PY
197
+ i=0
198
+ while ! jq -e 'any(.queued[]; .id == "cadence-task")' \
199
+ "$CADENCE_HOME/state/home-summary.json" >/dev/null 2>&1; do
200
+ kill -0 "$WATCH_PID" 2>/dev/null \
201
+ || fail "the cadence watcher exited before publishing the backlog-only change"
202
+ [ "$i" -lt 80 ] \
203
+ || fail "a backlog-only change did not refresh within the configured watcher cadence"
204
+ sleep 0.1
205
+ i=$((i + 1))
206
+ done
207
+ kill "$WATCH_PID" >/dev/null 2>&1 || true
208
+ wait "$WATCH_PID" >/dev/null 2>&1 || true
209
+ WATCH_PID=
210
+ pass "live watcher cadence bounds publication staleness without signals"
211
+
212
+ # Publication-only boundary: poison the ledger with a structurally complete but
213
+ # semantically false state, then prove the current parent snapshot still computes
214
+ # the home summary from the owning home instead of consuming this file.
215
+ jq '.state = "no_active_work" | .active_children = [] | .holds = []
216
+ | .counts.active_children = 0 | .counts.holds = 0' \
217
+ "$HOME_DIR/state/home-summary.json" > "$HOME_DIR/state/home-summary.poisoned"
218
+ mv -f "$HOME_DIR/state/home-summary.poisoned" "$HOME_DIR/state/home-summary.json"
219
+ mkdir -p "$PARENT_HOME/state" "$PARENT_HOME/data" "$PARENT_HOME/config" "$PARENT_HOME/projects"
220
+ printf -- '- mate - fixture domain (home: %s; scope: fixture work; projects: firstmate; added 2026-08-28)\n' \
221
+ "$HOME_DIR" > "$PARENT_HOME/data/secondmates.md"
222
+ cat > "$PARENT_HOME/data/backlog.md" <<'EOF'
223
+ ## In flight
224
+
225
+ ## Queued
226
+
227
+ ## Done
228
+ EOF
229
+ fm_write_secondmate_meta "$PARENT_HOME/state/mate.meta" "$HOME_DIR" \
230
+ "fmtest:fm-mate" firstmate claude
231
+ PATH="$FAKEBIN:$PATH" \
232
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$PARENT_HOME" \
233
+ FM_SNAPSHOT_NOW="$NOW_TWO" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_TWO" \
234
+ "$SNAPSHOT" --json > "$TMP_ROOT/parent-snapshot.json" \
235
+ || fail "parent fleet snapshot failed"
236
+ jq -e '
237
+ .secondmate_current.records[0].provenance.selected == "structured-home"
238
+ and .secondmate_current.records[0].current.state == "externally_held"
239
+ and any(.secondmate_current.records[0].holds[]; .id == "ledger-task")
240
+ ' "$TMP_ROOT/parent-snapshot.json" >/dev/null \
241
+ || fail "fleet snapshot consumed the poisoned publication instead of recomputing its established path"
242
+ pass "fleet snapshot remains a non-consumer of the ledger"
243
+
244
+ # Restore the established ledger, then stop a real writer while its real producer
245
+ # is blocked in a current-state read. The prior ledger must remain byte-identical
246
+ # and valid because no partial producer output is ever published at its path.
247
+ run_writer "$NOW_TWO" "$EPOCH_TWO" || fail "could not restore the real ledger"
248
+ printf 'working: replacement summary is being computed\n' \
249
+ >> "$HOME_DIR/state/ledger-task.status"
250
+ cp "$HOME_DIR/state/home-summary.json" "$TMP_ROOT/prior-ledger.json"
251
+ SLOW_MARKER="$TMP_ROOT/slow-no-mistakes.pid"
252
+ PATH="$FAKEBIN:$PATH" \
253
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
254
+ FM_SNAPSHOT_NOW="$NOW_THREE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_THREE" \
255
+ FM_TEST_NM_MARKER="$SLOW_MARKER" FM_TEST_NM_SLEEP=30 \
256
+ "$WRITER" > "$TMP_ROOT/killed-writer.out" 2> "$TMP_ROOT/killed-writer.err" &
257
+ SLOW_WRITER_PID=$!
258
+ i=0
259
+ while [ ! -s "$SLOW_MARKER" ] && [ "$i" -lt 100 ]; do
260
+ kill -0 "$SLOW_WRITER_PID" 2>/dev/null || break
261
+ sleep 0.05
262
+ i=$((i + 1))
263
+ done
264
+ [ -s "$SLOW_MARKER" ] || fail "the real producer did not reach the controlled slow current-state read"
265
+ SLOW_NM_PID=$(cat "$SLOW_MARKER" 2>/dev/null || true)
266
+ writer_pgid=$(ps -o pgid= -p "$SLOW_WRITER_PID" 2>/dev/null | tr -d '[:space:]')
267
+ ancestor=$SLOW_NM_PID
268
+ child_pgid=
269
+ i=0
270
+ while [ "$i" -lt 20 ]; do
271
+ ancestor_pgid=$(ps -o pgid= -p "$ancestor" 2>/dev/null | tr -d '[:space:]')
272
+ parent=$(ps -o ppid= -p "$ancestor" 2>/dev/null | tr -d '[:space:]')
273
+ if [ "$parent" = "$SLOW_WRITER_PID" ]; then
274
+ if [ "$ancestor_pgid" != "$writer_pgid" ]; then
275
+ SLOW_WORKER_PGID=$ancestor_pgid
276
+ else
277
+ SLOW_WORKER_PGID=$child_pgid
278
+ fi
279
+ break
280
+ fi
281
+ child_pgid=$ancestor_pgid
282
+ ancestor=$parent
283
+ i=$((i + 1))
284
+ done
285
+ case "$SLOW_WORKER_PGID" in
286
+ ''|*[!0-9]*) fail "the bounded writer did not expose its worker process group" ;;
287
+ esac
288
+ [ "$SLOW_WORKER_PGID" != "$writer_pgid" ] \
289
+ || fail "the bounded worker did not have an isolated process group"
290
+ kill -KILL -- "-$SLOW_WORKER_PGID" >/dev/null 2>&1 \
291
+ || fail "the bounded writer process group could not be terminated"
292
+ wait "$SLOW_WRITER_PID" >/dev/null 2>&1 || true
293
+ SLOW_WRITER_PID=
294
+ SLOW_WORKER_PGID=
295
+ SLOW_NM_PID=
296
+ jq -e . "$HOME_DIR/state/home-summary.json" >/dev/null \
297
+ || fail "killing the writer exposed invalid JSON at the ledger path"
298
+ cmp -s "$TMP_ROOT/prior-ledger.json" "$HOME_DIR/state/home-summary.json" \
299
+ || fail "killing the writer replaced the prior complete ledger"
300
+
301
+ # Observe the ledger continuously through one successful replacement. Every read
302
+ # must parse, and the final document must be the newly computed complete summary.
303
+ READER_FAILURE="$TMP_ROOT/reader-failure"
304
+ SUCCESS_MARKER="$TMP_ROOT/success-no-mistakes.pid"
305
+ PATH="$FAKEBIN:$PATH" \
306
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
307
+ FM_SNAPSHOT_NOW="$NOW_THREE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_THREE" \
308
+ FM_TEST_NM_MARKER="$SUCCESS_MARKER" FM_TEST_NM_SLEEP=1 \
309
+ "$WRITER" > "$TMP_ROOT/success-writer.out" 2> "$TMP_ROOT/success-writer.err" &
310
+ SLOW_WRITER_PID=$!
311
+ while kill -0 "$SLOW_WRITER_PID" 2>/dev/null; do
312
+ if ! jq -e . "$HOME_DIR/state/home-summary.json" >/dev/null 2>&1; then
313
+ : > "$READER_FAILURE"
314
+ break
315
+ fi
316
+ done
317
+ if ! wait "$SLOW_WRITER_PID"; then
318
+ SLOW_WRITER_PID=
319
+ fail "successful atomic replacement failed: $(cat "$TMP_ROOT/success-writer.err" 2>/dev/null)"
320
+ fi
321
+ SLOW_WRITER_PID=
322
+ [ ! -e "$READER_FAILURE" ] || fail "a reader observed torn JSON during atomic replacement"
323
+ jq -e --arg now "$NOW_THREE" --argjson epoch "$EPOCH_THREE" '
324
+ .generated == $now and .generated_epoch == $epoch
325
+ ' "$HOME_DIR/state/home-summary.json" >/dev/null \
326
+ || fail "the successful replacement did not publish the new complete document"
327
+ pass "writer kill and replacement preserve an atomic JSON ledger"
328
+
329
+ # Best-effort mode is the contract used by every lifecycle trigger. A failed
330
+ # producer records the failure and returns success without touching the ledger.
331
+ FAILBIN="$TMP_ROOT/failbin"
332
+ mkdir -p "$FAILBIN"
333
+ cat > "$FAILBIN/jq" <<'SH'
334
+ #!/usr/bin/env bash
335
+ exit 7
336
+ SH
337
+ chmod +x "$FAILBIN/jq"
338
+ cp "$HOME_DIR/state/home-summary.json" "$TMP_ROOT/before-best-effort.json"
339
+ PATH="$FAILBIN:$FAKEBIN:$PATH" \
340
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
341
+ "$WRITER" --best-effort \
342
+ || fail "best-effort refresh propagated its producer failure"
343
+ cmp -s "$TMP_ROOT/before-best-effort.json" "$HOME_DIR/state/home-summary.json" \
344
+ || fail "failed best-effort refresh changed the prior ledger"
345
+ grep -F 'summary producer failed' "$HOME_DIR/state/.home-summary-refresh.log" >/dev/null \
346
+ || fail "best-effort refresh did not log its failure"
347
+ pass "best-effort publication logs and continues"
348
+
349
+ LOCK_MARKER="$TMP_ROOT/lock-held"
350
+ rm -f "$HOME_DIR/state/.home-summary-refresh.log"
351
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" bash -c '
352
+ . "$1/bin/fm-wake-lib.sh"
353
+ fm_lock_acquire_wait "$2/state/.home-summary-refresh.lock"
354
+ : > "$3"
355
+ sleep 30
356
+ ' _ "$ROOT" "$HOME_DIR" "$LOCK_MARKER" &
357
+ LOCK_HOLDER_PID=$!
358
+ i=0
359
+ while [ ! -e "$LOCK_MARKER" ] && [ "$i" -lt 100 ]; do
360
+ kill -0 "$LOCK_HOLDER_PID" 2>/dev/null || break
361
+ sleep 0.05
362
+ i=$((i + 1))
363
+ done
364
+ [ -e "$LOCK_MARKER" ] || fail "could not hold the publication lock for timeout coverage"
365
+ started=$(date +%s)
366
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
367
+ FM_HOME_SUMMARY_TIMEOUT=1 "$WRITER" --best-effort \
368
+ || fail "lock timeout changed the best-effort caller result"
369
+ elapsed=$(( $(date +%s) - started ))
370
+ [ "$elapsed" -lt 4 ] || fail "best-effort refresh waited $elapsed seconds on its lock"
371
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
372
+ FM_HOME_SUMMARY_TIMEOUT=1 "$WRITER" --best-effort \
373
+ || fail "repeated lock timeout changed the best-effort caller result"
374
+ [ "$(grep -c 'refresh exceeded its 1-second deadline' "$HOME_DIR/state/.home-summary-refresh.log" 2>/dev/null || true)" -ge 2 ] \
375
+ || fail "repeated publication lock timeouts vanished from failure reporting"
376
+ kill "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
377
+ wait "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
378
+ LOCK_HOLDER_PID=
379
+ pass "best-effort refresh bounds publication lock acquisition"
380
+
381
+ HANGBIN="$TMP_ROOT/hangbin"
382
+ REAL_JQ=$(command -v jq)
383
+ mkdir -p "$HANGBIN"
384
+ cat > "$HANGBIN/jq" <<'SH'
385
+ #!/usr/bin/env bash
386
+ for arg in "$@"; do
387
+ case "$arg" in
388
+ */.home-summary.json.*) sleep 30 ;;
389
+ esac
390
+ done
391
+ exec "$FM_TEST_REAL_JQ" "$@"
392
+ SH
393
+ chmod +x "$HANGBIN/jq"
394
+ started=$(date +%s)
395
+ PATH="$HANGBIN:$FAKEBIN:$PATH" FM_TEST_REAL_JQ="$REAL_JQ" \
396
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" FM_HOME_SUMMARY_TIMEOUT=1 \
397
+ "$WRITER" --best-effort \
398
+ || fail "validation timeout changed the best-effort caller result"
399
+ elapsed=$(( $(date +%s) - started ))
400
+ [ "$elapsed" -lt 4 ] || fail "best-effort refresh waited $elapsed seconds on validation"
401
+ grep -F 'refresh exceeded its 1-second deadline' \
402
+ "$HOME_DIR/state/.home-summary-refresh.log" >/dev/null \
403
+ || fail "publication validation timeout was not logged"
404
+ pass "best-effort refresh bounds validation and publication"
405
+
406
+ MKBIN="$TMP_ROOT/mkdir-hangbin"
407
+ REAL_MKDIR=$(command -v mkdir)
408
+ mkdir -p "$MKBIN"
409
+ cat > "$MKBIN/mkdir" <<'SH'
410
+ #!/usr/bin/env bash
411
+ for arg in "$@"; do
412
+ if [ "$arg" = "$FM_TEST_STALLED_STATE" ]; then
413
+ sleep 30
414
+ fi
415
+ done
416
+ exec "$FM_TEST_REAL_MKDIR" "$@"
417
+ SH
418
+ chmod +x "$MKBIN/mkdir"
419
+ started=$(date +%s)
420
+ PATH="$MKBIN:$FAKEBIN:$PATH" FM_TEST_REAL_MKDIR="$REAL_MKDIR" \
421
+ FM_TEST_STALLED_STATE="$HOME_DIR/state" FM_ROOT_OVERRIDE="$ROOT" \
422
+ FM_HOME="$HOME_DIR" FM_HOME_SUMMARY_TIMEOUT=1 \
423
+ "$WRITER" --best-effort >/dev/null 2>"$TMP_ROOT/stalled-state.err" \
424
+ || fail "state initialization timeout changed the best-effort caller result"
425
+ elapsed=$(( $(date +%s) - started ))
426
+ [ "$elapsed" -lt 6 ] \
427
+ || fail "best-effort refresh waited $elapsed seconds before bounded state initialization"
428
+ pass "best-effort refresh bounds state initialization"
429
+
430
+ SIGNALBIN="$TMP_ROOT/signalbin"
431
+ SIGNAL_MARKER="$TMP_ROOT/worker-signaled"
432
+ REAL_ENV=$(command -v env)
433
+ mkdir -p "$SIGNALBIN"
434
+ cat > "$SIGNALBIN/env" <<'SH'
435
+ #!/usr/bin/env bash
436
+ if [ ! -e "$FM_TEST_SIGNAL_MARKER" ]; then
437
+ : > "$FM_TEST_SIGNAL_MARKER"
438
+ exit 143
439
+ fi
440
+ exec "$FM_TEST_REAL_ENV" "$@"
441
+ SH
442
+ chmod +x "$SIGNALBIN/env"
443
+ rm -f "$HOME_DIR/state/.home-summary-refresh.log"
444
+ PATH="$SIGNALBIN:$FAKEBIN:$PATH" FM_TEST_REAL_ENV="$REAL_ENV" \
445
+ FM_TEST_SIGNAL_MARKER="$SIGNAL_MARKER" FM_TIMEOUT_MECHANISM_OVERRIDE=bash \
446
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" "$WRITER" --best-effort \
447
+ || fail "worker termination changed the best-effort caller result"
448
+ grep -F 'refresh worker failed with exit 143' \
449
+ "$HOME_DIR/state/.home-summary-refresh.log" >/dev/null \
450
+ || fail "worker termination was not logged at the parent boundary"
451
+ pass "best-effort refresh logs worker termination"
452
+
453
+ rm -f "$SIGNAL_MARKER" "$HOME_DIR/state/.home-summary-refresh.log"
454
+ mkdir "$HOME_DIR/state/.home-summary-refresh.log"
455
+ if ! PATH="$SIGNALBIN:$FAKEBIN:$PATH" FM_TEST_REAL_ENV="$REAL_ENV" \
456
+ FM_TEST_SIGNAL_MARKER="$SIGNAL_MARKER" FM_TIMEOUT_MECHANISM_OVERRIDE=bash \
457
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" WRITER="$WRITER" python3 - <<'PY'
458
+ import os
459
+ import subprocess
460
+ import time
461
+
462
+ read_fd, write_fd = os.pipe()
463
+ os.set_blocking(write_fd, False)
464
+ try:
465
+ while True:
466
+ os.write(write_fd, b"x" * 4096)
467
+ except BlockingIOError:
468
+ pass
469
+ os.set_blocking(write_fd, True)
470
+ started = time.monotonic()
471
+ try:
472
+ result = subprocess.run(
473
+ [os.environ["WRITER"], "--best-effort"],
474
+ stdin=subprocess.DEVNULL,
475
+ stdout=subprocess.DEVNULL,
476
+ stderr=write_fd,
477
+ env=os.environ,
478
+ timeout=7,
479
+ )
480
+ finally:
481
+ os.close(write_fd)
482
+ os.close(read_fd)
483
+ elapsed = time.monotonic() - started
484
+ if result.returncode != 0:
485
+ raise SystemExit(f"blocked failure logger changed caller result: {result.returncode}")
486
+ if elapsed >= 6:
487
+ raise SystemExit(f"blocked failure logger exceeded its bound: {elapsed:.2f}s")
488
+ PY
489
+ then
490
+ fail "best-effort failure reporting was not fully bounded"
491
+ fi
492
+ pass "best-effort refresh bounds failure reporting fallback"
493
+
494
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$HOME_DIR" \
495
+ FM_SNAPSHOT_NOW="$NOW_ONE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_ONE" \
496
+ "$WRITER" || fail "an unavailable failure record blocked valid publication"
497
+ jq -e --arg now "$NOW_ONE" '.generated == $now' \
498
+ "$HOME_DIR/state/home-summary.json" >/dev/null \
499
+ || fail "valid publication did not replace the ledger with an unavailable failure record"
500
+ rmdir "$HOME_DIR/state/.home-summary-refresh.log"
501
+ pass "valid publication ignores an unavailable failure record"
502
+
503
+ # --- publication cost, beacon isolation, and failure discoverability ---------
504
+ #
505
+ # The three regressions below all came from one live incident: in a real home
506
+ # whose tasks had accumulated ordinary status history, the producer needed
507
+ # minutes, so publication burned its whole deadline on every attempt, never
508
+ # published, starved the watcher's liveness beacon while it did, and said
509
+ # nothing about any of it because --best-effort is deliberately non-fatal.
510
+
511
+ # Publication cost must scale with what a home actually accumulates. Status
512
+ # history is append-only and unbounded, and the producer folds every task's
513
+ # whole stream, so an ordinary long-lived home is the real input - not the
514
+ # one-line log a freshly seeded fixture has. This home carries a status log of
515
+ # realistic width and depth and must still publish inside a deadline well under
516
+ # the default one.
517
+ COST_HOME="$TMP_ROOT/cost-home"
518
+ mkdir -p "$COST_HOME/state" "$COST_HOME/data" "$COST_HOME/config" \
519
+ "$COST_HOME/projects/task"
520
+ printf '# Seeded Firstmate home\n' > "$COST_HOME/AGENTS.md"
521
+ printf 'cost\n' > "$COST_HOME/.fm-secondmate-home"
522
+ fm_git_init_commit "$COST_HOME/projects/task"
523
+ cat > "$COST_HOME/data/backlog.md" <<'EOF'
524
+ ## In flight
525
+ - [ ] cost-task - Publish from an accumulated home (repo: firstmate) (kind: ship) (since 2026-08-28)
526
+
527
+ ## Queued
528
+
529
+ ## Done
530
+ EOF
531
+ fm_write_meta "$COST_HOME/state/cost-task.meta" \
532
+ "window=fmtest:fm-cost-task" \
533
+ "worktree=$COST_HOME/projects/task" \
534
+ "project=firstmate" \
535
+ "harness=claude" \
536
+ "kind=ship" \
537
+ "mode=no-mistakes" \
538
+ "spawn_gen=fm.cost123456"
539
+ cost_busy_gen=$("$ROOT/bin/fm-busy-event.sh" arm "$COST_HOME/state" cost-task)
540
+ "$ROOT/bin/fm-busy-event.sh" apply "$COST_HOME/state" cost-task idle \
541
+ --gen "$cost_busy_gen" --source claude-hook --event stop
542
+ python3 - "$COST_HOME/state/cost-task.status" <<'PY'
543
+ import sys
544
+ note = ("the crewmate ran validation and reported checks on the branch "
545
+ "after review ") * 25
546
+ with open(sys.argv[1], "w") as handle:
547
+ for i in range(300):
548
+ handle.write(f"working: {note}({i})\n")
549
+ handle.write("needs-decision [key=cost-gate]: which base to rebuild from\n")
550
+ PY
551
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$COST_HOME" \
552
+ FM_SNAPSHOT_NOW="$NOW_ONE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_ONE" \
553
+ FM_HOME_SUMMARY_TIMEOUT=30 "$WRITER" --best-effort \
554
+ || fail "accumulated-home publication changed the best-effort caller result"
555
+ [ -f "$COST_HOME/state/home-summary.json" ] \
556
+ || fail "an accumulated home did not publish within a 30-second deadline: $(cat "$COST_HOME/state/.home-summary-refresh.log" 2>/dev/null)"
557
+ jq -e --arg home "$COST_HOME" '
558
+ .schema == "fm-secondmate-home-summary.v1"
559
+ and .home == $home
560
+ and any(.decisions_open[]; .key == "cost-gate")
561
+ ' "$COST_HOME/state/home-summary.json" >/dev/null \
562
+ || fail "the accumulated home published a ledger missing its open decision"
563
+ pass "publication completes on a home carrying accumulated status history"
564
+
565
+ # One unreachable home must not extend publication without limit. A remote
566
+ # secondmate's current state is read over ssh, and ssh's own dead-peer detection
567
+ # deliberately never kills a slow-but-alive remote command, so nothing under the
568
+ # producer bounds that read on its own. Point the transport at a stub that never
569
+ # answers and require the producer to return anyway, reporting that home as
570
+ # unknown rather than waiting on it.
571
+ REMOTE_HOME="$TMP_ROOT/remote-home"
572
+ mkdir -p "$REMOTE_HOME/state" "$REMOTE_HOME/data" "$REMOTE_HOME/config" \
573
+ "$REMOTE_HOME/projects" "$TMP_ROOT/sshbin"
574
+ printf '# Seeded Firstmate home\n' > "$REMOTE_HOME/AGENTS.md"
575
+ printf 'remote\n' > "$REMOTE_HOME/.fm-secondmate-home"
576
+ cat > "$REMOTE_HOME/data/backlog.md" <<'EOF'
577
+ ## In flight
578
+ - [ ] rsm - Read remote current state (repo: firstmate) (kind: ship) (since 2026-08-28)
579
+
580
+ ## Queued
581
+
582
+ ## Done
583
+ EOF
584
+ cat > "$REMOTE_HOME/data/secondmates.md" <<'EOF'
585
+ - rsm - remote test domain (host: remote-mac; root: /remote/root; home: /remote/home; scope: remote testing; projects: alpha; added 2026-08-02)
586
+ EOF
587
+ fm_write_meta "$REMOTE_HOME/state/rsm.meta" \
588
+ "window=remote:rsm" \
589
+ "endpoint_task_id=rsm" \
590
+ "worktree=/remote/home/never-locally-present" \
591
+ "harness=claude" \
592
+ "kind=secondmate" \
593
+ "mode=secondmate" \
594
+ "home=/remote/home" \
595
+ "remote_host=remote-mac" \
596
+ "remote_root=/remote/root" \
597
+ "remote_backend=herdr" \
598
+ "remote_herdr_session=fm-remote" \
599
+ "remote_target=fm-remote:w1:p1"
600
+ cat > "$TMP_ROOT/sshbin/stalled-ssh" <<'SH'
601
+ #!/usr/bin/env bash
602
+ cat > /dev/null
603
+ sleep 60
604
+ SH
605
+ chmod +x "$TMP_ROOT/sshbin/stalled-ssh"
606
+ started=$(date +%s)
607
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$REMOTE_HOME" \
608
+ FM_SSH_BIN="$TMP_ROOT/sshbin/stalled-ssh" \
609
+ FM_SNAPSHOT_NOW="$NOW_TWO" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_TWO" \
610
+ FM_SNAPSHOT_CREW_STATE_TIMEOUT=2 FM_SNAPSHOT_SECONDMATE_TIMEOUT=2 \
611
+ "$SNAPSHOT" --secondmate-home-summary > "$TMP_ROOT/stalled-summary.json" \
612
+ || fail "an unreachable remote home failed the whole producer"
613
+ elapsed=$(( $(date +%s) - started ))
614
+ [ "$elapsed" -lt 40 ] \
615
+ || fail "the producer waited $elapsed seconds on one unreachable remote home"
616
+ jq -e '
617
+ .schema == "fm-secondmate-home-summary.v1"
618
+ and .valid == false
619
+ and .state == "unknown"
620
+ and .invalidity.kind == "child_current_unavailable"
621
+ and (.invalidity.ids == ["rsm"])
622
+ and any(.endpoints[]; .id == "rsm" and .state == "unknown")
623
+ ' "$TMP_ROOT/stalled-summary.json" >/dev/null \
624
+ || fail "an unreachable remote task was not reported as unknown"
625
+ pass "producer bounds each per-task current-state read"
626
+
627
+ # The watcher's beacon is what the rest of supervision reads as proof it is
628
+ # alive. Publication is side-band, so no matter how long it takes, the beacon
629
+ # must keep advancing. Hold the publication lock for the whole observation
630
+ # window, then require the beacon to keep ticking anyway.
631
+ BEAT_HOME="$TMP_ROOT/beat-home"
632
+ mkdir -p "$BEAT_HOME/state" "$BEAT_HOME/data" "$BEAT_HOME/config" \
633
+ "$BEAT_HOME/projects"
634
+ printf '# Seeded Firstmate home\n' > "$BEAT_HOME/AGENTS.md"
635
+ printf 'beat\n' > "$BEAT_HOME/.fm-secondmate-home"
636
+ cat > "$BEAT_HOME/data/backlog.md" <<'EOF'
637
+ ## In flight
638
+
639
+ ## Queued
640
+
641
+ ## Done
642
+ EOF
643
+ BEAT_LOCK_MARKER="$TMP_ROOT/beat-lock-held"
644
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$BEAT_HOME" bash -c '
645
+ . "$1/bin/fm-wake-lib.sh"
646
+ fm_lock_acquire_wait "$2/state/.home-summary-refresh.lock"
647
+ : > "$3"
648
+ sleep 120
649
+ ' _ "$ROOT" "$BEAT_HOME" "$BEAT_LOCK_MARKER" &
650
+ LOCK_HOLDER_PID=$!
651
+ i=0
652
+ while [ ! -e "$BEAT_LOCK_MARKER" ] && [ "$i" -lt 100 ]; do
653
+ kill -0 "$LOCK_HOLDER_PID" 2>/dev/null || break
654
+ sleep 0.05
655
+ i=$((i + 1))
656
+ done
657
+ [ -e "$BEAT_LOCK_MARKER" ] || fail "could not stall publication for beacon coverage"
658
+ PATH="$FAKEBIN:$PATH" \
659
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$BEAT_HOME" \
660
+ FM_SNAPSHOT_NOW="$NOW_THREE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_THREE" \
661
+ FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=1 FM_HOME_SUMMARY_TIMEOUT=90 \
662
+ FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
663
+ "$WATCH" > "$TMP_ROOT/beat-watch.out" 2> "$TMP_ROOT/beat-watch.err" &
664
+ WATCH_PID=$!
665
+ i=0
666
+ while [ ! -e "$BEAT_HOME/state/.last-watcher-beat" ] && [ "$i" -lt 200 ]; do
667
+ kill -0 "$WATCH_PID" 2>/dev/null || break
668
+ sleep 0.05
669
+ i=$((i + 1))
670
+ done
671
+ [ -e "$BEAT_HOME/state/.last-watcher-beat" ] \
672
+ || fail "the stalled-publication watcher never beat: $(cat "$TMP_ROOT/beat-watch.err" 2>/dev/null)"
673
+ beat_mtime() { python3 -c 'import os,sys; print(os.stat(sys.argv[1]).st_mtime)' "$1"; }
674
+ seen=0
675
+ last=$(beat_mtime "$BEAT_HOME/state/.last-watcher-beat")
676
+ i=0
677
+ while [ "$seen" -lt 3 ] && [ "$i" -lt 200 ]; do
678
+ kill -0 "$WATCH_PID" 2>/dev/null \
679
+ || fail "the stalled-publication watcher exited: $(cat "$TMP_ROOT/beat-watch.err" 2>/dev/null)"
680
+ sleep 0.1
681
+ now=$(beat_mtime "$BEAT_HOME/state/.last-watcher-beat")
682
+ if [ "$now" != "$last" ]; then
683
+ seen=$((seen + 1))
684
+ last=$now
685
+ fi
686
+ i=$((i + 1))
687
+ done
688
+ [ "$seen" -ge 3 ] \
689
+ || fail "the beacon advanced only $seen time(s) in 20 seconds while publication was stalled"
690
+ kill "$WATCH_PID" >/dev/null 2>&1 || true
691
+ wait "$WATCH_PID" >/dev/null 2>&1 || true
692
+ WATCH_PID=
693
+ kill "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
694
+ wait "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
695
+ LOCK_HOLDER_PID=
696
+ pass "a stalled publication does not delay the watcher liveness beacon"
697
+
698
+ RESTART_HOME="$TMP_ROOT/restart-home"
699
+ mkdir -p "$RESTART_HOME/state" "$RESTART_HOME/data" "$RESTART_HOME/config" \
700
+ "$RESTART_HOME/projects/task"
701
+ printf '# Seeded Firstmate home\n' > "$RESTART_HOME/AGENTS.md"
702
+ printf 'restart\n' > "$RESTART_HOME/.fm-secondmate-home"
703
+ fm_git_init_commit "$RESTART_HOME/projects/task"
704
+ cat > "$RESTART_HOME/data/backlog.md" <<'EOF'
705
+ ## In flight
706
+ - [ ] restart-task - Preserve publication single flight (repo: firstmate) (kind: ship) (since 2026-08-28)
707
+
708
+ ## Queued
709
+
710
+ ## Done
711
+ EOF
712
+ fm_write_meta "$RESTART_HOME/state/restart-task.meta" \
713
+ "window=fmtest:fm-restart-task" \
714
+ "worktree=$RESTART_HOME/projects/task" \
715
+ "project=firstmate" \
716
+ "harness=claude" \
717
+ "kind=ship" \
718
+ "mode=no-mistakes" \
719
+ "spawn_gen=fm.restart123456"
720
+ RESTART_LOCK_MARKER="$TMP_ROOT/restart-lock-held"
721
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" bash -c '
722
+ . "$1/bin/fm-wake-lib.sh"
723
+ fm_lock_acquire_wait "$2/state/.home-summary-refresh.lock"
724
+ : > "$3"
725
+ sleep 30
726
+ ' _ "$ROOT" "$RESTART_HOME" "$RESTART_LOCK_MARKER" &
727
+ LOCK_HOLDER_PID=$!
728
+ i=0
729
+ while [ ! -e "$RESTART_LOCK_MARKER" ] && [ "$i" -lt 100 ]; do
730
+ kill -0 "$LOCK_HOLDER_PID" 2>/dev/null || break
731
+ sleep 0.05
732
+ i=$((i + 1))
733
+ done
734
+ [ -e "$RESTART_LOCK_MARKER" ] || fail "could not hold the publication lock for restart coverage"
735
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
736
+ FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=2 \
737
+ FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
738
+ "$WATCH" > "$TMP_ROOT/restart-watch-one.out" 2> "$TMP_ROOT/restart-watch-one.err" &
739
+ WATCH_PID=$!
740
+ i=0
741
+ while [ ! -e "$RESTART_HOME/state/.last-watcher-beat" ] && [ "$i" -lt 100 ]; do
742
+ kill -0 "$WATCH_PID" 2>/dev/null || break
743
+ sleep 0.05
744
+ i=$((i + 1))
745
+ done
746
+ [ -e "$RESTART_HOME/state/.last-watcher-beat" ] \
747
+ || fail "the first restart watcher did not begin polling"
748
+ printf 'needs-decision [key=restart-gate]: restart the watcher\n' \
749
+ > "$RESTART_HOME/state/restart-task.status"
750
+ i=0
751
+ while kill -0 "$WATCH_PID" 2>/dev/null && [ "$i" -lt 100 ]; do
752
+ sleep 0.05
753
+ i=$((i + 1))
754
+ done
755
+ kill -0 "$WATCH_PID" 2>/dev/null \
756
+ && fail "the first restart watcher did not surface its actionable signal"
757
+ wait "$WATCH_PID" >/dev/null 2>&1 || true
758
+ WATCH_PID=
759
+ rm -f "$RESTART_HOME/state/.last-watcher-beat"
760
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
761
+ FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=2 \
762
+ FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
763
+ "$WATCH" > "$TMP_ROOT/restart-watch-two.out" 2> "$TMP_ROOT/restart-watch-two.err" &
764
+ WATCH_PID=$!
765
+ i=0
766
+ while [ ! -e "$RESTART_HOME/state/.last-watcher-beat" ] && [ "$i" -lt 100 ]; do
767
+ kill -0 "$WATCH_PID" 2>/dev/null || break
768
+ sleep 0.05
769
+ i=$((i + 1))
770
+ done
771
+ [ -e "$RESTART_HOME/state/.last-watcher-beat" ] \
772
+ || fail "the replacement restart watcher did not begin polling"
773
+ sleep 4
774
+ [ ! -s "$RESTART_HOME/state/.home-summary-refresh.log" ] \
775
+ || fail "watcher restart queued refreshes behind a live publication lock: $(cat "$RESTART_HOME/state/.home-summary-refresh.log")"
776
+ if ! kill -0 "$WATCH_PID" 2>/dev/null; then
777
+ wait "$WATCH_PID" >/dev/null 2>&1 || true
778
+ rm -f "$RESTART_HOME/state/.last-watcher-beat"
779
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
780
+ FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=2 \
781
+ FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
782
+ "$WATCH" > "$TMP_ROOT/restart-watch-three.out" 2> "$TMP_ROOT/restart-watch-three.err" &
783
+ WATCH_PID=$!
784
+ i=0
785
+ while [ ! -e "$RESTART_HOME/state/.last-watcher-beat" ] && [ "$i" -lt 100 ]; do
786
+ kill -0 "$WATCH_PID" 2>/dev/null || break
787
+ sleep 0.05
788
+ i=$((i + 1))
789
+ done
790
+ [ -e "$RESTART_HOME/state/.last-watcher-beat" ] \
791
+ || fail "the recovery replacement watcher did not begin polling"
792
+ fi
793
+ kill -KILL "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
794
+ wait "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
795
+ LOCK_HOLDER_PID=
796
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
797
+ FM_HOME_SUMMARY_IF_IDLE=1 "$WRITER" --best-effort \
798
+ || fail "stale-lock recovery changed the best-effort caller result"
799
+ i=0
800
+ while [ ! -e "$RESTART_HOME/state/home-summary.json" ] && [ "$i" -lt 200 ]; do
801
+ sleep 0.05
802
+ i=$((i + 1))
803
+ done
804
+ [ -e "$RESTART_HOME/state/home-summary.json" ] \
805
+ || fail "a dead publication lock wedged publication"
806
+ kill "$WATCH_PID" >/dev/null 2>&1 || true
807
+ wait "$WATCH_PID" >/dev/null 2>&1 || true
808
+ WATCH_PID=
809
+ pass "publication remains single-flight across watcher restart"
810
+
811
+ # A publication that keeps failing is deliberately non-fatal to its caller, so
812
+ # the only way an operator learns about it is a session start saying so. Seed
813
+ # the home-local failure record a real failing home would have, and require the
814
+ # check a session start already runs to name it - then go quiet once the ledger
815
+ # is published again.
816
+ REPORT_HOME="$TMP_ROOT/report-home"
817
+ mkdir -p "$REPORT_HOME/state" "$REPORT_HOME/data" "$REPORT_HOME/config" \
818
+ "$REPORT_HOME/projects"
819
+ printf '# Seeded Firstmate home\n' > "$REPORT_HOME/AGENTS.md"
820
+ printf 'report\n' > "$REPORT_HOME/.fm-secondmate-home"
821
+ cat > "$REPORT_HOME/data/backlog.md" <<'EOF'
822
+ ## In flight
823
+
824
+ ## Queued
825
+
826
+ ## Done
827
+ EOF
828
+ cat > "$REPORT_HOME/state/.home-summary-refresh.log" <<'EOF'
829
+ [2026-08-28T09:58:00Z] refresh exceeded its 60-second deadline
830
+ [2026-08-28T09:59:00Z] refresh exceeded its 60-second deadline
831
+ EOF
832
+ run_bootstrap_detect() {
833
+ local threshold=${2:-2}
834
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$1" \
835
+ FM_HOME_SUMMARY_FAILURE_REPORT="$threshold" \
836
+ FM_BOOTSTRAP_DETECT_ONLY=1 FM_BOOTSTRAP_NETWORK=skip \
837
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null
838
+ }
839
+
840
+ COMPAT_HOME="$TMP_ROOT/compat-home"
841
+ mkdir -p "$COMPAT_HOME/state" "$COMPAT_HOME/data" "$COMPAT_HOME/config" \
842
+ "$COMPAT_HOME/projects"
843
+ printf '# Seeded Firstmate home\n' > "$COMPAT_HOME/AGENTS.md"
844
+ printf 'compat\n' > "$COMPAT_HOME/.fm-secondmate-home"
845
+ cat > "$COMPAT_HOME/data/backlog.md" <<'EOF'
846
+ ## In flight
847
+
848
+ ## Queued
849
+
850
+ ## Done
851
+ EOF
852
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$COMPAT_HOME" \
853
+ FM_SNAPSHOT_NOW="$NOW_ONE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_ONE" \
854
+ "$WRITER" || fail "could not seed the compatibility ledger"
855
+ cat > "$COMPAT_HOME/state/.home-summary-refresh.log" <<'EOF'
856
+ [2026-08-28T09:58:00Z] historical failure before publication
857
+ [2026-08-28T09:59:00Z] historical failure before publication
858
+ [2026-08-28T10:01:00Z] first failure after publication
859
+ EOF
860
+ compat_out=$(run_bootstrap_detect "$COMPAT_HOME")
861
+ case "$compat_out" in
862
+ *HOME_SUMMARY:*)
863
+ fail "historical failures satisfied the current publication threshold: $compat_out"
864
+ ;;
865
+ esac
866
+ printf '[2026-08-28T10:02:00Z] second failure after publication\n' \
867
+ >> "$COMPAT_HOME/state/.home-summary-refresh.log"
868
+ compat_out=$(run_bootstrap_detect "$COMPAT_HOME")
869
+ printf '%s\n' "$compat_out" | grep -F '2 failed attempt(s)' >/dev/null \
870
+ || fail "current publication failures did not satisfy the report threshold: $compat_out"
871
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$COMPAT_HOME" \
872
+ FM_SNAPSHOT_NOW="$NOW_THREE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_THREE" \
873
+ "$WRITER" || fail "could not republish the compatibility ledger"
874
+ compat_out=$(run_bootstrap_detect "$COMPAT_HOME")
875
+ case "$compat_out" in
876
+ *HOME_SUMMARY:*)
877
+ fail "republishing did not scope retained failure history: $compat_out"
878
+ ;;
879
+ esac
880
+ pass "bootstrap scopes retained failures to the current publication"
881
+
882
+ # A timed-out attempt can finish recording after a newer ledger is published.
883
+ # Its record must retain the attempt's ordering rather than look like a failure
884
+ # of the newer publication and keep the session-start diagnostic active.
885
+ ORDER_HOME="$TMP_ROOT/order-home"
886
+ ORDER_DATE_BIN="$TMP_ROOT/order-date-bin"
887
+ mkdir -p "$ORDER_HOME/state" "$ORDER_HOME/data" "$ORDER_HOME/config" \
888
+ "$ORDER_HOME/projects" "$ORDER_DATE_BIN"
889
+ printf '# Seeded Firstmate home\n' > "$ORDER_HOME/AGENTS.md"
890
+ printf 'order\n' > "$ORDER_HOME/.fm-secondmate-home"
891
+ cat > "$ORDER_HOME/data/backlog.md" <<'EOF'
892
+ ## In flight
893
+
894
+ ## Queued
895
+
896
+ ## Done
897
+ EOF
898
+ REAL_DATE=$(command -v date)
899
+ cat > "$ORDER_DATE_BIN/date" <<'SH'
900
+ #!/usr/bin/env bash
901
+ if [ "$#" -eq 2 ] && [ "$1" = -u ] && [ "$2" = +%Y-%m-%dT%H:%M:%SZ ]; then
902
+ python3 - "$FM_TEST_ORDER_START" "$FM_TEST_ORDER_EARLY" "$FM_TEST_ORDER_LATE" <<'PY'
903
+ import sys
904
+ import time
905
+
906
+ started = float(sys.argv[1])
907
+ print(sys.argv[2] if time.time() - started < 1 else sys.argv[3])
908
+ PY
909
+ exit 0
910
+ fi
911
+ exec "$FM_TEST_REAL_DATE" "$@"
912
+ SH
913
+ chmod +x "$ORDER_DATE_BIN/date"
914
+ ORDER_LOCK_MARKER="$TMP_ROOT/order-lock-held"
915
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$ORDER_HOME" bash -c '
916
+ . "$1/bin/fm-wake-lib.sh"
917
+ fm_lock_acquire_wait "$2/state/.home-summary-refresh.lock"
918
+ : > "$3"
919
+ sleep 30
920
+ ' _ "$ROOT" "$ORDER_HOME" "$ORDER_LOCK_MARKER" &
921
+ LOCK_HOLDER_PID=$!
922
+ i=0
923
+ while [ ! -e "$ORDER_LOCK_MARKER" ] && [ "$i" -lt 100 ]; do
924
+ kill -0 "$LOCK_HOLDER_PID" 2>/dev/null || break
925
+ sleep 0.05
926
+ i=$((i + 1))
927
+ done
928
+ [ -e "$ORDER_LOCK_MARKER" ] || fail "could not hold the publication lock for ordering coverage"
929
+ order_started=$(python3 -c 'import time; print(time.time())')
930
+ PATH="$ORDER_DATE_BIN:$FAKEBIN:$PATH" FM_TEST_REAL_DATE="$REAL_DATE" \
931
+ FM_TEST_ORDER_START="$order_started" FM_TEST_ORDER_EARLY="$NOW_ONE" \
932
+ FM_TEST_ORDER_LATE="$NOW_THREE" FM_ROOT_OVERRIDE="$ROOT" \
933
+ FM_HOME="$ORDER_HOME" FM_HOME_SUMMARY_TIMEOUT=2 \
934
+ "$WRITER" --best-effort \
935
+ || fail "ordered timeout changed the best-effort caller result"
936
+ kill "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
937
+ wait "$LOCK_HOLDER_PID" >/dev/null 2>&1 || true
938
+ LOCK_HOLDER_PID=
939
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$ORDER_HOME" \
940
+ FM_SNAPSHOT_NOW="$NOW_TWO" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_TWO" \
941
+ "$WRITER" || fail "could not publish after the ordered timeout"
942
+ order_out=$(run_bootstrap_detect "$ORDER_HOME" 1)
943
+ case "$order_out" in
944
+ *HOME_SUMMARY:*)
945
+ fail "a pre-publication attempt was reported after the newer ledger: $order_out"
946
+ ;;
947
+ esac
948
+ pass "failure records preserve refresh attempt ordering"
949
+
950
+ report_out=$(run_bootstrap_detect "$REPORT_HOME")
951
+ printf '%s\n' "$report_out" \
952
+ | grep -F 'HOME_SUMMARY: this home has never published state/home-summary.json' \
953
+ >/dev/null \
954
+ || fail "a home that never published its ledger was reported as silent: $report_out"
955
+ printf '%s\n' "$report_out" \
956
+ | grep -F '2 failed attempt(s)' >/dev/null \
957
+ || fail "the publication report omitted the recorded failure count: $report_out"
958
+ printf '%s\n' "$report_out" \
959
+ | grep -F 'refresh exceeded its 60-second deadline' >/dev/null \
960
+ || fail "the publication report omitted the recorded reason: $report_out"
961
+
962
+ PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$REPORT_HOME" \
963
+ FM_SNAPSHOT_NOW="$NOW_THREE" FM_SNAPSHOT_NOW_EPOCH="$EPOCH_THREE" \
964
+ "$WRITER" || fail "could not publish the ledger that clears the report"
965
+ report_out=$(run_bootstrap_detect "$REPORT_HOME")
966
+ case "$report_out" in
967
+ *HOME_SUMMARY:*)
968
+ fail "a published ledger still reported stale publication failures: $report_out"
969
+ ;;
970
+ esac
971
+ pass "repeated publication failure is reported at session start until it clears"