@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.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -39
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- 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"
|