@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,821 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tests/fm-watch-arm.test.sh - the arm layer's cycle-close contract when the arm
|
|
3
|
+
# did not own the cycle.
|
|
4
|
+
#
|
|
5
|
+
# The watcher prints its one reason line to its OWN stdout, so only the arm that
|
|
6
|
+
# forked it ever reads that line. An arm that ATTACHED to an existing cycle holds
|
|
7
|
+
# no handle on it and can observe only a released lock, which is why a completely
|
|
8
|
+
# successful cycle used to be reported as
|
|
9
|
+
# "watcher: FAILED - cycle ended without an actionable reason" on every harness
|
|
10
|
+
# whose protocol reads that line. These are real-process tests: a real
|
|
11
|
+
# bin/fm-watch.sh holds the singleton, a real bin/fm-watch-arm.sh attaches to it,
|
|
12
|
+
# and a real status change drives a real wake through the watcher-bound delivery
|
|
13
|
+
# record and durable queue.
|
|
14
|
+
set -u
|
|
15
|
+
|
|
16
|
+
# shellcheck source=tests/wake-helpers.sh
|
|
17
|
+
. "$(dirname "${BASH_SOURCE[0]}")/wake-helpers.sh"
|
|
18
|
+
|
|
19
|
+
WATCH="$ROOT/bin/fm-watch.sh"
|
|
20
|
+
WATCH_ARM="$ROOT/bin/fm-watch-arm.sh"
|
|
21
|
+
DRAIN="$ROOT/bin/fm-wake-drain.sh"
|
|
22
|
+
|
|
23
|
+
TMP_ROOT=$(fm_test_tmproot fm-watch-arm-tests)
|
|
24
|
+
|
|
25
|
+
# Both starters background a real process the test later waits on, so they set a
|
|
26
|
+
# global instead of echoing: a command substitution would make the pid a child of
|
|
27
|
+
# a subshell this shell can no longer wait for.
|
|
28
|
+
SEED_PID=
|
|
29
|
+
ARM_PID=
|
|
30
|
+
|
|
31
|
+
# Start the real watcher as the singleton holder.
|
|
32
|
+
start_seed_watcher() { # <state> <fakebin> <watch-out>
|
|
33
|
+
local state=$1 fakebin=$2 out=$3 i
|
|
34
|
+
PATH="$fakebin:$PATH" FM_STATE_OVERRIDE="$state" FM_POLL=5 FM_SIGNAL_GRACE=1 \
|
|
35
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 "$WATCH" > "$out" &
|
|
36
|
+
SEED_PID=$!
|
|
37
|
+
i=0
|
|
38
|
+
while [ "$i" -lt 60 ]; do
|
|
39
|
+
[ "$(cat "$state/.watch.lock/pid" 2>/dev/null || true)" = "$SEED_PID" ] \
|
|
40
|
+
&& [ -e "$state/.last-watcher-beat" ] && break
|
|
41
|
+
sleep 0.1
|
|
42
|
+
i=$((i + 1))
|
|
43
|
+
done
|
|
44
|
+
[ "$(cat "$state/.watch.lock/pid" 2>/dev/null || true)" = "$SEED_PID" ] \
|
|
45
|
+
|| fail "seed watcher did not take the lock"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Attach a real arm to the live cycle.
|
|
49
|
+
start_attached_arm() { # <state> <fakebin> <arm-out> <confirm-timeout>
|
|
50
|
+
local state=$1 fakebin=$2 armout=$3 confirm=$4 i
|
|
51
|
+
PATH="$fakebin:$PATH" FM_STATE_OVERRIDE="$state" FM_ARM_ATTACH_POLL=0.1 \
|
|
52
|
+
FM_ARM_CONFIRM_TIMEOUT="$confirm" "$WATCH_ARM" > "$armout" &
|
|
53
|
+
ARM_PID=$!
|
|
54
|
+
i=0
|
|
55
|
+
while [ "$i" -lt 80 ]; do
|
|
56
|
+
grep -qF "watcher: attached pid=$SEED_PID" "$armout" 2>/dev/null && break
|
|
57
|
+
sleep 0.1
|
|
58
|
+
i=$((i + 1))
|
|
59
|
+
done
|
|
60
|
+
grep -qF "watcher: attached pid=$SEED_PID" "$armout" \
|
|
61
|
+
|| fail "arm did not attach to the live watcher: $(cat "$armout")"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
sha256_file() { # <path>
|
|
65
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
66
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
67
|
+
else
|
|
68
|
+
sha256sum "$1" | awk '{print $1}'
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
write_remote_delta() { # <result-path> <status-line>
|
|
73
|
+
local result=$1 line=$2 payload empty payload_bytes payload_hash empty_hash
|
|
74
|
+
payload="$result.payload"
|
|
75
|
+
empty="$result.empty"
|
|
76
|
+
printf '%s\n' "$line" > "$payload"
|
|
77
|
+
: > "$empty"
|
|
78
|
+
payload_bytes=$(LC_ALL=C wc -c < "$payload" | tr -d '[:space:]')
|
|
79
|
+
payload_hash=$(sha256_file "$payload") || fail "could not hash remote delta payload"
|
|
80
|
+
empty_hash=$(sha256_file "$empty") || fail "could not hash empty remote delta prefix"
|
|
81
|
+
{
|
|
82
|
+
printf 'schema=fm-remote-delta.v1\n'
|
|
83
|
+
printf 'status=delta\n'
|
|
84
|
+
printf 'path=state/parent-replies.status\n'
|
|
85
|
+
printf 'from_offset=0\n'
|
|
86
|
+
printf 'to_offset=%s\n' "$payload_bytes"
|
|
87
|
+
printf 'from_prefix_sha256=%s\n' "$empty_hash"
|
|
88
|
+
printf 'to_prefix_sha256=%s\n' "$payload_hash"
|
|
89
|
+
printf 'payload_sha256=%s\n' "$payload_hash"
|
|
90
|
+
printf 'payload_bytes=%s\n' "$payload_bytes"
|
|
91
|
+
printf 'reason=fixture\n\n'
|
|
92
|
+
cat "$payload"
|
|
93
|
+
} > "$result"
|
|
94
|
+
rm -f "$payload" "$empty"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
status_signature() { # <status-path>
|
|
98
|
+
bash -c '
|
|
99
|
+
. "$1"
|
|
100
|
+
reported=$(status_observed_signature "$2") || exit 1
|
|
101
|
+
size=$(_fm_status_file_size "$2") || exit 1
|
|
102
|
+
ident=$(_fm_open_decisions_file_ident "$2") || exit 1
|
|
103
|
+
printf "v2\t%s\t%s@%s" "$reported" "$size" "$ident"
|
|
104
|
+
' _ "$ROOT/bin/fm-classify-lib.sh" "$1"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
wait_for_file_text() { # <file> <fixed-text>
|
|
108
|
+
local file=$1 expected=$2 i=0
|
|
109
|
+
while [ "$i" -lt 100 ]; do
|
|
110
|
+
grep -F "$expected" "$file" >/dev/null 2>&1 && return 0
|
|
111
|
+
sleep 0.05
|
|
112
|
+
i=$((i + 1))
|
|
113
|
+
done
|
|
114
|
+
return 1
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
ack_wakes() { # <state>
|
|
118
|
+
local state=$1 sequence generation err
|
|
119
|
+
err="$state/.test-ack.err"
|
|
120
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" >/dev/null 2> "$err" || return 1
|
|
121
|
+
sequence=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through \([0-9][0-9]*\) --recovery-generation [A-Za-z0-9._-][A-Za-z0-9._-]*$/\1/p' "$err")
|
|
122
|
+
generation=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through [0-9][0-9]* --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' "$err")
|
|
123
|
+
rm -f "$err"
|
|
124
|
+
if [ -z "$sequence" ] || [ -z "$generation" ]; then
|
|
125
|
+
[ ! -s "$state/.wake-queue" ] || return 1
|
|
126
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in pending:*|announced:*) return 1 ;; esac
|
|
127
|
+
return 0
|
|
128
|
+
fi
|
|
129
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" \
|
|
130
|
+
--recovery-generation "$generation"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Print "<sequence>\t<generation>" from the acknowledgement command a drain
|
|
134
|
+
# printed, so a case can replay that exact pair later.
|
|
135
|
+
drain_ack_pair() { # <drain-stderr>
|
|
136
|
+
local err=$1 sequence generation
|
|
137
|
+
sequence=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through \([0-9][0-9]*\) --recovery-generation [A-Za-z0-9._-][A-Za-z0-9._-]*$/\1/p' "$err")
|
|
138
|
+
generation=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through [0-9][0-9]* --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' "$err")
|
|
139
|
+
[ -n "$sequence" ] && [ -n "$generation" ] || return 1
|
|
140
|
+
printf '%s\t%s\n' "$sequence" "$generation"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
start_rearm_arm() { # <home> <state> <fakebin> <arm-out> [predecessor-arm-pid]
|
|
144
|
+
local home=$1 state=$2 fakebin=$3 armout=$4 predecessor=${5:-} i
|
|
145
|
+
PATH="$fakebin:$PATH" FM_HOME="$home" FM_STATE_OVERRIDE="$state" \
|
|
146
|
+
FM_POLL=1 FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
147
|
+
FM_WATCH_PREDECESSOR_ARM_PID="$predecessor" \
|
|
148
|
+
"$WATCH_ARM" --restart > "$armout" &
|
|
149
|
+
ARM_PID=$!
|
|
150
|
+
i=0
|
|
151
|
+
while [ "$i" -lt 80 ]; do
|
|
152
|
+
grep -q '^watcher: started ' "$armout" 2>/dev/null && return 0
|
|
153
|
+
is_live_non_zombie "$ARM_PID" || return 0
|
|
154
|
+
sleep 0.05
|
|
155
|
+
i=$((i + 1))
|
|
156
|
+
done
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
test_attached_arm_reports_the_delivered_wake() {
|
|
161
|
+
local dir state fakebin out armout status
|
|
162
|
+
dir=$(make_case attached-delivered-wake)
|
|
163
|
+
state="$dir/state"
|
|
164
|
+
fakebin="$dir/fakebin"
|
|
165
|
+
out="$dir/watch.out"
|
|
166
|
+
armout="$dir/arm.out"
|
|
167
|
+
start_seed_watcher "$state" "$fakebin" "$out"
|
|
168
|
+
start_attached_arm "$state" "$fakebin" "$armout" 1
|
|
169
|
+
|
|
170
|
+
# A real captain-relevant status change: the watcher records it in the durable
|
|
171
|
+
# queue, prints its one reason line to its own stdout, and exits.
|
|
172
|
+
printf 'done: fixture finished\n' > "$state/demo.status"
|
|
173
|
+
wait_for_exit "$SEED_PID" 120
|
|
174
|
+
grep -q '^signal:' "$out" || fail "seed watcher did not surface the signal wake: $(cat "$out")"
|
|
175
|
+
|
|
176
|
+
wait_for_exit "$ARM_PID" 120
|
|
177
|
+
status=$?
|
|
178
|
+
grep -q 'demo.status' "$state/.wake-queue" \
|
|
179
|
+
|| fail "the wake was not durably recorded, so this case proves nothing"
|
|
180
|
+
! grep -qF 'watcher: FAILED' "$armout" \
|
|
181
|
+
|| fail "attached arm reported a delivered wake as a failed cycle: $(cat "$armout")"
|
|
182
|
+
grep -q '^signal:' "$armout" \
|
|
183
|
+
|| fail "attached arm did not report the durably recorded wake reason: $(cat "$armout")"
|
|
184
|
+
expect_code 0 "$status" "an attached arm whose cycle delivered a wake must close successfully"
|
|
185
|
+
grep -q 'reason=attached-delivered-wake' "$state/.watch-cycle-exits.log" \
|
|
186
|
+
|| fail "the delivered-wake close was not classified in the lifecycle ledger"
|
|
187
|
+
pass "watch-arm: an attached arm reports the wake its cycle delivered instead of a false failure"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
test_attached_arm_reports_the_delivered_wake_after_drain() {
|
|
191
|
+
local dir state fakebin out armout status
|
|
192
|
+
dir=$(make_case attached-drained-wake)
|
|
193
|
+
state="$dir/state"
|
|
194
|
+
fakebin="$dir/fakebin"
|
|
195
|
+
out="$dir/watch.out"
|
|
196
|
+
armout="$dir/arm.out"
|
|
197
|
+
start_seed_watcher "$state" "$fakebin" "$out"
|
|
198
|
+
# A wider confirmation budget keeps the arm in its successor wait while the
|
|
199
|
+
# handling turn drains, which is the ordering this case exists to cover.
|
|
200
|
+
start_attached_arm "$state" "$fakebin" "$armout" 5
|
|
201
|
+
|
|
202
|
+
printf 'done: fixture finished\n' > "$state/demo.status"
|
|
203
|
+
wait_for_exit "$SEED_PID" 120
|
|
204
|
+
# The handling turn consumes the records before the attached arm closes: the
|
|
205
|
+
# queue is empty again, while the watcher's identity-bound terminal record
|
|
206
|
+
# still proves which cycle delivered the reason.
|
|
207
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" >/dev/null 2>&1 || fail "drain failed"
|
|
208
|
+
ack_wakes "$state" || fail "handling acknowledgement failed"
|
|
209
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledgement left records behind"
|
|
210
|
+
|
|
211
|
+
wait_for_exit "$ARM_PID" 200
|
|
212
|
+
status=$?
|
|
213
|
+
! grep -qF 'watcher: FAILED' "$armout" \
|
|
214
|
+
|| fail "attached arm reported an already-handled wake as a failed cycle: $(cat "$armout")"
|
|
215
|
+
grep -q '^signal:' "$armout" \
|
|
216
|
+
|| fail "attached arm did not report the delivered reason after the queue drain: $(cat "$armout")"
|
|
217
|
+
expect_code 0 "$status" "an attached arm whose wake was already drained must close successfully"
|
|
218
|
+
pass "watch-arm: a delivered wake consumed by the handling turn still closes the attached arm cleanly"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
test_attached_arm_still_fails_on_a_wake_it_did_not_deliver() {
|
|
222
|
+
local dir state fakebin out armout status
|
|
223
|
+
dir=$(make_case attached-no-delivery)
|
|
224
|
+
state="$dir/state"
|
|
225
|
+
fakebin="$dir/fakebin"
|
|
226
|
+
out="$dir/watch.out"
|
|
227
|
+
armout="$dir/arm.out"
|
|
228
|
+
start_seed_watcher "$state" "$fakebin" "$out"
|
|
229
|
+
start_attached_arm "$state" "$fakebin" "$armout" 1
|
|
230
|
+
|
|
231
|
+
# A process-event producer advances the same home-wide queue while the
|
|
232
|
+
# observed watcher remains uninvolved, so only watcher-bound evidence can
|
|
233
|
+
# distinguish this from a delivered watcher cycle.
|
|
234
|
+
append_wake "$state" check process-event "check: process-event result captured: fixture"
|
|
235
|
+
kill "$SEED_PID" 2>/dev/null || true
|
|
236
|
+
wait "$SEED_PID" 2>/dev/null || true
|
|
237
|
+
wait_for_exit "$ARM_PID" 120
|
|
238
|
+
status=$?
|
|
239
|
+
grep -qF 'watcher: FAILED - cycle ended without an actionable reason' "$armout" \
|
|
240
|
+
|| fail "a cycle that delivered nothing must still fail loudly: $(cat "$armout")"
|
|
241
|
+
[ "$status" -ne 0 ] && [ "$status" -ne 124 ] \
|
|
242
|
+
|| fail "arm did not exit nonzero for a cycle that delivered nothing (status $status)"
|
|
243
|
+
pass "watch-arm: a cycle that delivered no wake of its own still fails loudly"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
test_rearm_resurfaces_durable_queue_and_remote_open_decision() {
|
|
247
|
+
local dir home state fakebin result armout drainout status watcher_pid sequence generation decision_recovery_arm decision_successor
|
|
248
|
+
dir=$(make_case rearm-resurface)
|
|
249
|
+
home="$dir/home"
|
|
250
|
+
state="$dir/state"
|
|
251
|
+
fakebin="$dir/fakebin"
|
|
252
|
+
result="$dir/remote.result"
|
|
253
|
+
armout="$dir/arm.out"
|
|
254
|
+
drainout="$dir/drain.out"
|
|
255
|
+
mkdir -p "$home/data"
|
|
256
|
+
|
|
257
|
+
# This is the real remote parent-reply ingest boundary. It writes the remote
|
|
258
|
+
# secondmate's decision onto the parent status surface the shared fold owns.
|
|
259
|
+
write_remote_delta "$result" \
|
|
260
|
+
'needs-decision [key=remote-signoff]: remote secondmate is held for captain sign-off'
|
|
261
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$home/data" \
|
|
262
|
+
"$ROOT/bin/fm-procevent-remote-reply.sh" ingest ios "$result" >/dev/null \
|
|
263
|
+
|| fail "remote parent-reply ingest failed"
|
|
264
|
+
|
|
265
|
+
# Drain once before the outage to establish the incremental cursor and the
|
|
266
|
+
# signal suppressor that a watcher had already observed. The decision remains
|
|
267
|
+
# intentionally open across the watcher-down interval.
|
|
268
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/baseline-drain.out" \
|
|
269
|
+
|| fail "baseline drain failed"
|
|
270
|
+
ack_wakes "$state" || fail "baseline handling acknowledgement failed"
|
|
271
|
+
grep -F 'remote secondmate is held for captain sign-off' "$dir/baseline-drain.out" >/dev/null \
|
|
272
|
+
|| fail "baseline fold did not expose the remote decision"
|
|
273
|
+
printf '%s' "$(status_signature "$state/ios.status")" > "$state/.seen-ios_status"
|
|
274
|
+
|
|
275
|
+
# A real watcher is then interrupted before the next two durable updates.
|
|
276
|
+
# This is the accepted blocking-tool shape: no watcher runs during the gap.
|
|
277
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/down-arm.out"
|
|
278
|
+
is_live_non_zombie "$ARM_PID" || fail "pre-outage watcher did not stay live"
|
|
279
|
+
watcher_pid=$(cat "$state/.watch.lock/pid" 2>/dev/null || true)
|
|
280
|
+
kill -KILL "$watcher_pid" 2>/dev/null || fail "could not abruptly stop pre-outage watcher"
|
|
281
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
282
|
+
[ ! -e "$state/.watcher-down" ] || fail "abrupt watcher exit unexpectedly ran cleanup"
|
|
283
|
+
|
|
284
|
+
# Two independent durable wakes arrive while no watcher exists. Neither gets
|
|
285
|
+
# a later status change to rescue it, which is the down-window loss shape.
|
|
286
|
+
append_wake "$state" check remote-reply-ios \
|
|
287
|
+
'check: process-event result captured: remote-reply-ios:7'
|
|
288
|
+
append_wake "$state" check startup-network 'check: startup-network'
|
|
289
|
+
|
|
290
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$armout"
|
|
291
|
+
sleep 0.25
|
|
292
|
+
if is_live_non_zombie "$ARM_PID"; then
|
|
293
|
+
# End the fixture through an ordinary actionable status transition so this
|
|
294
|
+
# failing pre-fix path leaves no child behind.
|
|
295
|
+
printf 'done: fixture cleanup\n' > "$state/cleanup.status"
|
|
296
|
+
wait_for_exit "$ARM_PID" 80 || true
|
|
297
|
+
fail "re-arm stayed live instead of surfacing durable wakes and the still-open remote decision"
|
|
298
|
+
fi
|
|
299
|
+
wait "$ARM_PID"
|
|
300
|
+
status=$?
|
|
301
|
+
expect_code 0 "$status" "re-arm re-surface wake must close successfully"
|
|
302
|
+
grep -F 'check: rearm-resurface' "$armout" >/dev/null \
|
|
303
|
+
|| fail "re-arm did not report the durable recovery wake: $(cat "$armout")"
|
|
304
|
+
|
|
305
|
+
# The normal wake-handling drain is the one owner of both queue consumption
|
|
306
|
+
# and the cursor-backed fold. It must expose every queued record and the
|
|
307
|
+
# already-open remote decision without relying on another user message.
|
|
308
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$drainout" \
|
|
309
|
+
|| fail "drain after re-arm recovery failed"
|
|
310
|
+
grep "$(printf '\tcheck\tremote-reply-ios\t')" "$drainout" >/dev/null \
|
|
311
|
+
|| fail "remote-reply wake queued during downtime was not drained"
|
|
312
|
+
grep "$(printf '\tcheck\tstartup-network\t')" "$drainout" >/dev/null \
|
|
313
|
+
|| fail "second durable wake queued during downtime was not drained"
|
|
314
|
+
grep -F 'ios [key=remote-signoff] needs-decision: remote secondmate is held for captain sign-off' "$drainout" >/dev/null \
|
|
315
|
+
|| fail "remote parent-reply decision was not re-folded after watcher re-arm"
|
|
316
|
+
ack_wakes "$state" || fail "recovery handling acknowledgement failed"
|
|
317
|
+
[ ! -s "$state/.wake-queue" ] || fail "re-arm recovery acknowledgement left durable wakes behind"
|
|
318
|
+
|
|
319
|
+
# Persistent adapters establish a successor after the handling drain. Once
|
|
320
|
+
# the durable wake is acknowledged, that successor must remain live instead
|
|
321
|
+
# of replaying the completed recovery cycle.
|
|
322
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/recovery-successor-arm.out"
|
|
323
|
+
is_live_non_zombie "$ARM_PID" || fail "recovery successor did not stay live after the drain"
|
|
324
|
+
|
|
325
|
+
# A later down interval can have no new queue rows at all. The unchanged
|
|
326
|
+
# remote decision must still trigger a recovery wake and be folded again.
|
|
327
|
+
kill "$ARM_PID" 2>/dev/null || true
|
|
328
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
329
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/decision-only-arm.out"
|
|
330
|
+
wait_for_exit "$ARM_PID" 80 || fail "decision-only re-arm did not surface the open decision"
|
|
331
|
+
decision_recovery_arm=$ARM_PID
|
|
332
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/decision-handling-successor.out" "$decision_recovery_arm"
|
|
333
|
+
is_live_non_zombie "$ARM_PID" || fail "decision handling successor re-triggered before the drain"
|
|
334
|
+
decision_successor=$ARM_PID
|
|
335
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/decision-only-drain.out" \
|
|
336
|
+
2> "$dir/decision-only-drain.err" || fail "decision-only drain after re-arm recovery failed"
|
|
337
|
+
grep -F 'ios [key=remote-signoff] needs-decision: remote secondmate is held for captain sign-off' \
|
|
338
|
+
"$dir/decision-only-drain.out" >/dev/null \
|
|
339
|
+
|| fail "unchanged remote decision was not re-folded after a later down interval"
|
|
340
|
+
sequence=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through \([0-9][0-9]*\) --recovery-generation [A-Za-z0-9._-][A-Za-z0-9._-]*$/\1/p' "$dir/decision-only-drain.err")
|
|
341
|
+
generation=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through [0-9][0-9]* --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' "$dir/decision-only-drain.err")
|
|
342
|
+
[ "$sequence" = 0 ] && [ -n "$generation" ] \
|
|
343
|
+
|| fail "decision-only recovery did not require generation-bound post-handling acknowledgement"
|
|
344
|
+
is_live_non_zombie "$decision_successor" \
|
|
345
|
+
|| fail "decision-only drain spuriously re-triggered its live handling successor"
|
|
346
|
+
! grep -F 'check: rearm-resurface' "$dir/decision-handling-successor.out" >/dev/null \
|
|
347
|
+
|| fail "decision-only handling successor emitted recursive recovery"
|
|
348
|
+
|
|
349
|
+
kill -TERM "$decision_successor" 2>/dev/null || fail "could not interrupt decision handling successor"
|
|
350
|
+
wait "$decision_successor" 2>/dev/null || true
|
|
351
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/interrupted-decision-arm.out"
|
|
352
|
+
wait_for_exit "$ARM_PID" 80 || fail "interrupted decision handling was not recovered on successor re-arm"
|
|
353
|
+
grep -F 'check: rearm-resurface' "$dir/interrupted-decision-arm.out" >/dev/null \
|
|
354
|
+
|| fail "successor did not re-surface the unacknowledged decision recovery"
|
|
355
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/replayed-decision-drain.out" \
|
|
356
|
+
2> "$dir/replayed-decision-drain.err" || fail "replayed decision recovery drain failed"
|
|
357
|
+
grep -F 'ios [key=remote-signoff] needs-decision: remote secondmate is held for captain sign-off' \
|
|
358
|
+
"$dir/replayed-decision-drain.out" >/dev/null \
|
|
359
|
+
|| fail "interrupted decision recovery did not re-fold the open decision"
|
|
360
|
+
sequence=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through \([0-9][0-9]*\) --recovery-generation [A-Za-z0-9._-][A-Za-z0-9._-]*$/\1/p' "$dir/replayed-decision-drain.err")
|
|
361
|
+
generation=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through [0-9][0-9]* --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' "$dir/replayed-decision-drain.err")
|
|
362
|
+
[ "$sequence" = 0 ] && [ -n "$generation" ] \
|
|
363
|
+
|| fail "replayed decision recovery omitted its current acknowledgement generation"
|
|
364
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
365
|
+
|| fail "completed decision handling could not acknowledge current recovery"
|
|
366
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/decision-successor-arm.out"
|
|
367
|
+
is_live_non_zombie "$ARM_PID" || fail "acknowledged decision recovery did not leave a live successor"
|
|
368
|
+
kill "$ARM_PID" 2>/dev/null || true
|
|
369
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
370
|
+
pass "watch-arm: re-arm surfaces every queued wake and an open remote decision after downtime"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
test_marker_publish_failure_retains_recovery_evidence() {
|
|
374
|
+
local dir home state fakebin first_arm watcher_pid armout
|
|
375
|
+
dir=$(make_case downtime-marker-publish-failure)
|
|
376
|
+
home="$dir/home"
|
|
377
|
+
state="$dir/state"
|
|
378
|
+
fakebin="$dir/fakebin"
|
|
379
|
+
mkdir -p "$home/data"
|
|
380
|
+
|
|
381
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/first-arm.out"
|
|
382
|
+
first_arm=$ARM_PID
|
|
383
|
+
is_live_non_zombie "$first_arm" || fail "marker-failure fixture watcher did not stay live"
|
|
384
|
+
watcher_pid=$(cat "$state/.watch.lock/pid" 2>/dev/null || true)
|
|
385
|
+
mkdir "$state/.watcher-down"
|
|
386
|
+
kill -TERM "$watcher_pid" 2>/dev/null || fail "could not stop marker-failure fixture watcher"
|
|
387
|
+
wait "$first_arm" 2>/dev/null || true
|
|
388
|
+
|
|
389
|
+
[ "$(cat "$state/.watch.lock/pid" 2>/dev/null || true)" = "$watcher_pid" ] \
|
|
390
|
+
|| fail "marker publication failure discarded stale-lock recovery evidence"
|
|
391
|
+
! is_live_non_zombie "$watcher_pid" \
|
|
392
|
+
|| fail "marker-failure fixture watcher remained live"
|
|
393
|
+
|
|
394
|
+
rmdir "$state/.watcher-down"
|
|
395
|
+
armout="$dir/recovery-arm.out"
|
|
396
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$armout"
|
|
397
|
+
wait_for_exit "$ARM_PID" 80 || fail "stale-lock recovery did not surface downtime"
|
|
398
|
+
grep -F 'check: rearm-resurface' "$armout" >/dev/null \
|
|
399
|
+
|| fail "stale-lock recovery did not emit the recovery wake: $(cat "$armout")"
|
|
400
|
+
pass "watch-arm: marker publication failure retains stale-lock recovery evidence"
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
test_delivery_gap_wake_is_recovered_once() {
|
|
404
|
+
local dir home state fakebin first_arm
|
|
405
|
+
dir=$(make_case delivery-gap-recovery)
|
|
406
|
+
home="$dir/home"
|
|
407
|
+
state="$dir/state"
|
|
408
|
+
fakebin="$dir/fakebin"
|
|
409
|
+
mkdir -p "$home/data"
|
|
410
|
+
|
|
411
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/first-arm.out"
|
|
412
|
+
first_arm=$ARM_PID
|
|
413
|
+
is_live_non_zombie "$first_arm" || fail "delivery-gap fixture watcher did not stay live"
|
|
414
|
+
printf 'done: first delivered wake\n' > "$state/first.status"
|
|
415
|
+
wait_for_exit "$first_arm" 120 || fail "first watcher did not deliver its status wake"
|
|
416
|
+
grep -q '^signal:' "$dir/first-arm.out" \
|
|
417
|
+
|| fail "first watcher did not report its delivered wake"
|
|
418
|
+
|
|
419
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/first-drain.out" \
|
|
420
|
+
|| fail "first handling drain failed"
|
|
421
|
+
ack_wakes "$state" || fail "first handling acknowledgement failed"
|
|
422
|
+
append_wake "$state" check startup-network 'check: startup-network during handling gap'
|
|
423
|
+
|
|
424
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/gap-arm.out"
|
|
425
|
+
wait_for_exit "$ARM_PID" 80 || fail "successor missed the wake queued in the delivery gap"
|
|
426
|
+
grep -F 'check: rearm-resurface' "$dir/gap-arm.out" >/dev/null \
|
|
427
|
+
|| fail "delivery-gap successor did not emit one recovery wake: $(cat "$dir/gap-arm.out")"
|
|
428
|
+
|
|
429
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/gap-drain.out" \
|
|
430
|
+
|| fail "delivery-gap recovery drain failed"
|
|
431
|
+
grep "$(printf '\tcheck\tstartup-network\t')" "$dir/gap-drain.out" >/dev/null \
|
|
432
|
+
|| fail "wake queued in the delivery gap was not drained"
|
|
433
|
+
ack_wakes "$state" || fail "delivery-gap handling acknowledgement failed"
|
|
434
|
+
|
|
435
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/stable-successor.out"
|
|
436
|
+
is_live_non_zombie "$ARM_PID" || fail "successor looped after the delivery gap was drained"
|
|
437
|
+
kill "$ARM_PID" 2>/dev/null || true
|
|
438
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
439
|
+
pass "watch-arm: a wake queued after handling drain is recovered once at successor arm"
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
test_interrupted_handling_is_redrained_on_rearm() {
|
|
443
|
+
local dir home state fakebin first_arm recovery_arm sequence generation handling_watcher_pid handling_generation generation_replay
|
|
444
|
+
dir=$(make_case interrupted-handling-redrain)
|
|
445
|
+
home="$dir/home"
|
|
446
|
+
state="$dir/state"
|
|
447
|
+
fakebin="$dir/fakebin"
|
|
448
|
+
mkdir -p "$home/data"
|
|
449
|
+
|
|
450
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/first-arm.out"
|
|
451
|
+
first_arm=$ARM_PID
|
|
452
|
+
is_live_non_zombie "$first_arm" || fail "interrupted-handling fixture watcher did not stay live"
|
|
453
|
+
printf 'done: wake whose handling is interrupted\n' > "$state/interrupted.status"
|
|
454
|
+
wait_for_exit "$first_arm" 120 || fail "fixture watcher did not deliver its wake"
|
|
455
|
+
grep "$(printf '\tsignal\tinterrupted.status\t')" "$state/.wake-queue" >/dev/null \
|
|
456
|
+
|| fail "delivered wake was not durable before handling"
|
|
457
|
+
|
|
458
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/crash-gap-recovery-arm.out"
|
|
459
|
+
wait_for_exit "$ARM_PID" 80 || fail "re-arm after a pre-successor crash stranded the durable wake"
|
|
460
|
+
recovery_arm=$ARM_PID
|
|
461
|
+
grep -F 'check: rearm-resurface' "$dir/crash-gap-recovery-arm.out" >/dev/null \
|
|
462
|
+
|| fail "re-arm after a pre-successor crash did not re-surface the durable wake"
|
|
463
|
+
grep "$(printf '\tsignal\tinterrupted.status\t')" "$state/.wake-queue" >/dev/null \
|
|
464
|
+
|| fail "pre-successor crash recovery removed the unacknowledged durable wake"
|
|
465
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
466
|
+
pending:downtime:*|announced:downtime:*) ;;
|
|
467
|
+
*) fail "reason emission marked recovery handled before a successor was established" ;;
|
|
468
|
+
esac
|
|
469
|
+
generation_before=$(recovery_marker_generation "$state/.watcher-down")
|
|
470
|
+
[ -n "$generation_before" ] || fail "crash-gap recovery left no recovery generation"
|
|
471
|
+
|
|
472
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/reason-emit-crash-replay.out"
|
|
473
|
+
wait_for_exit "$ARM_PID" 80 || fail "a crash after reason emission stranded the durable wake"
|
|
474
|
+
recovery_arm=$ARM_PID
|
|
475
|
+
grep -F 'check: rearm-resurface' "$dir/reason-emit-crash-replay.out" >/dev/null \
|
|
476
|
+
|| fail "a crash after reason emission did not re-drain recovery"
|
|
477
|
+
generation_replay=$(recovery_marker_generation "$state/.watcher-down")
|
|
478
|
+
[ -n "$generation_replay" ] \
|
|
479
|
+
|| fail "reason-emission replay left no recovery generation"
|
|
480
|
+
grep "$(printf '\tsignal\tinterrupted.status\t')" "$state/.wake-queue" >/dev/null \
|
|
481
|
+
|| fail "reason-emission replay removed the unacknowledged durable wake"
|
|
482
|
+
|
|
483
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/handling-successor-arm.out" "$recovery_arm"
|
|
484
|
+
is_live_non_zombie "$ARM_PID" \
|
|
485
|
+
|| fail "expected handling successor looped on the pending durable wake"
|
|
486
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
487
|
+
announced:downtime:*|pending:downtime:*) ;;
|
|
488
|
+
*) fail "successor launch marked recovery handled before prompt delivery" ;;
|
|
489
|
+
esac
|
|
490
|
+
handling_generation=$(recovery_marker_generation "$state/.watcher-down")
|
|
491
|
+
handling_watcher_pid=$(sed -n 's/^watcher: started pid=\([0-9][0-9]*\).* recovery-generation=.*$/\1/p' "$dir/handling-successor-arm.out")
|
|
492
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$WATCH_ARM" --handling-delivered "$handling_generation" \
|
|
493
|
+
--watcher-pid "$handling_watcher_pid" \
|
|
494
|
+
|| fail "confirmed prompt delivery did not begin handling"
|
|
495
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
496
|
+
pending:handling:"$handling_generation"|announced:handling:"$handling_generation") ;;
|
|
497
|
+
*) fail "confirmed prompt delivery did not transition its recovery generation" ;;
|
|
498
|
+
esac
|
|
499
|
+
! grep -F 'check: rearm-resurface' "$dir/handling-successor-arm.out" >/dev/null \
|
|
500
|
+
|| fail "expected handling successor emitted a recursive recovery wake"
|
|
501
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/interrupted-drain.out" \
|
|
502
|
+
2> "$dir/interrupted-drain.err" || fail "handling drain did not expose the durable wake"
|
|
503
|
+
grep "$(printf '\tsignal\tinterrupted.status\t')" "$dir/interrupted-drain.out" >/dev/null \
|
|
504
|
+
|| fail "handling drain did not present the durable wake"
|
|
505
|
+
grep "$(printf '\tsignal\tinterrupted.status\t')" "$state/.wake-queue" >/dev/null \
|
|
506
|
+
|| fail "interrupted handling removed the unacknowledged durable wake"
|
|
507
|
+
is_live_non_zombie "$ARM_PID" || fail "handling drain stopped its live successor"
|
|
508
|
+
|
|
509
|
+
kill -TERM "$ARM_PID" 2>/dev/null || fail "could not interrupt the handling successor"
|
|
510
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
511
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
512
|
+
pending:downtime:*|announced:downtime:*) ;;
|
|
513
|
+
*) fail "interrupted pre-handling successor did not persist downtime recovery" ;;
|
|
514
|
+
esac
|
|
515
|
+
|
|
516
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/recovery-arm.out"
|
|
517
|
+
wait_for_exit "$ARM_PID" 80 || fail "successor after interruption did not re-surface the pending wake"
|
|
518
|
+
grep -F 'check: rearm-resurface' "$dir/recovery-arm.out" >/dev/null \
|
|
519
|
+
|| fail "successor after interruption did not emit durable recovery"
|
|
520
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/replay-drain.out" \
|
|
521
|
+
2> "$dir/replay-drain.err" || fail "successor could not re-drain the interrupted wake"
|
|
522
|
+
grep "$(printf '\tsignal\tinterrupted.status\t')" "$dir/replay-drain.out" >/dev/null \
|
|
523
|
+
|| fail "successor did not re-drain the still-durable wake"
|
|
524
|
+
sequence=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through \([0-9][0-9]*\) --recovery-generation [A-Za-z0-9._-][A-Za-z0-9._-]*$/\1/p' "$dir/replay-drain.err")
|
|
525
|
+
generation=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through [0-9][0-9]* --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' "$dir/replay-drain.err")
|
|
526
|
+
[ -n "$sequence" ] && [ -n "$generation" ] \
|
|
527
|
+
|| fail "re-drain did not emit a generation-bound post-handling acknowledgement command"
|
|
528
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" \
|
|
529
|
+
--recovery-generation "$generation" \
|
|
530
|
+
|| fail "completed replay could not acknowledge the handled wake"
|
|
531
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledged replay remained in the durable queue"
|
|
532
|
+
pass "watch-arm: interrupted handling leaves its wake durable for successor re-drain"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
test_malformed_marker_is_quarantined_once() {
|
|
536
|
+
local dir home state fakebin invalid_count
|
|
537
|
+
dir=$(make_case malformed-downtime-marker)
|
|
538
|
+
home="$dir/home"
|
|
539
|
+
state="$dir/state"
|
|
540
|
+
fakebin="$dir/fakebin"
|
|
541
|
+
mkdir -p "$home/data" "$state/.watcher-down"
|
|
542
|
+
printf 'foreign state\n' > "$state/.watcher-down/payload"
|
|
543
|
+
|
|
544
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/recovery-arm.out"
|
|
545
|
+
wait_for_exit "$ARM_PID" 80 || fail "malformed marker did not produce a bounded recovery wake"
|
|
546
|
+
grep -F 'check: rearm-resurface' "$dir/recovery-arm.out" >/dev/null \
|
|
547
|
+
|| fail "malformed marker did not emit the recovery wake"
|
|
548
|
+
invalid_count=$(find "$state" -maxdepth 1 -type d -name '.watcher-down.invalid.*' | wc -l | tr -d '[:space:]')
|
|
549
|
+
[ "$invalid_count" -eq 1 ] || fail "malformed marker was not quarantined exactly once"
|
|
550
|
+
|
|
551
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/recovery-drain.out" \
|
|
552
|
+
|| fail "malformed-marker recovery drain failed"
|
|
553
|
+
ack_wakes "$state" || fail "malformed-marker handling acknowledgement failed"
|
|
554
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/stable-successor.out"
|
|
555
|
+
is_live_non_zombie "$ARM_PID" || fail "malformed marker caused a persistent recovery loop"
|
|
556
|
+
kill "$ARM_PID" 2>/dev/null || true
|
|
557
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
558
|
+
pass "watch-arm: malformed recovery state is quarantined without a successor loop"
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
test_recovery_consumption_serializes_queue_publication() {
|
|
562
|
+
local dir home state fakebin
|
|
563
|
+
dir=$(make_case recovery-consumption-race)
|
|
564
|
+
home="$dir/home"
|
|
565
|
+
state="$dir/state"
|
|
566
|
+
fakebin="$dir/fakebin"
|
|
567
|
+
mkdir -p "$home/data"
|
|
568
|
+
printf 'acked:handling:fixture\n' > "$state/.watcher-down"
|
|
569
|
+
|
|
570
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/arm.out"
|
|
571
|
+
is_live_non_zombie "$ARM_PID" || fail "acknowledged recovery fixture did not remain live"
|
|
572
|
+
append_wake "$state" check startup-network 'check: concurrent startup-network' \
|
|
573
|
+
|| fail "concurrent queue publication failed"
|
|
574
|
+
wait_for_exit "$ARM_PID" 80 \
|
|
575
|
+
|| fail "watcher missed publication after an acknowledged recovery handoff"
|
|
576
|
+
grep -F 'check: rearm-resurface' "$dir/arm.out" >/dev/null \
|
|
577
|
+
|| fail "publisher did not restore recovery evidence"
|
|
578
|
+
grep "$(printf '\tcheck\tstartup-network\t')" "$state/.wake-queue" >/dev/null \
|
|
579
|
+
|| fail "publisher did not durably append its wake"
|
|
580
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/drain.out" \
|
|
581
|
+
|| fail "publisher recovery drain failed"
|
|
582
|
+
grep "$(printf '\tcheck\tstartup-network\t')" "$dir/drain.out" >/dev/null \
|
|
583
|
+
|| fail "publisher wake was not surfaced and drained"
|
|
584
|
+
ack_wakes "$state" || fail "publisher handling acknowledgement failed"
|
|
585
|
+
pass "watch-arm: publication after recovery handoff is surfaced"
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
test_restart_preserves_recovery_across_reused_pid_lock() {
|
|
589
|
+
local dir home state fakebin armout unrelated owner
|
|
590
|
+
dir=$(make_case restart-reused-pid-recovery)
|
|
591
|
+
home="$dir/home"
|
|
592
|
+
state="$dir/state"
|
|
593
|
+
fakebin="$dir/fakebin"
|
|
594
|
+
armout="$dir/arm.out"
|
|
595
|
+
owner="$state/.watch.lock.owner.fixture"
|
|
596
|
+
mkdir -p "$home/data" "$owner"
|
|
597
|
+
|
|
598
|
+
sleep 300 &
|
|
599
|
+
unrelated=$!
|
|
600
|
+
printf '%s\n' "$unrelated" > "$owner/pid"
|
|
601
|
+
printf '%s\n' "$home" > "$owner/fm-home"
|
|
602
|
+
printf '%s\n' "$WATCH" > "$owner/watcher-path"
|
|
603
|
+
printf '%s\n' 'reused-pid-does-not-match' > "$owner/pid-identity"
|
|
604
|
+
ln -s "$owner" "$state/.watch.lock"
|
|
605
|
+
|
|
606
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$armout"
|
|
607
|
+
wait_for_exit "$ARM_PID" 80 || fail "restart did not surface recovery after clearing a reused-pid lock"
|
|
608
|
+
grep -F 'check: rearm-resurface' "$armout" >/dev/null \
|
|
609
|
+
|| fail "restart cleared reused-pid lock evidence without a recovery wake: $(cat "$armout")"
|
|
610
|
+
is_live_non_zombie "$unrelated" || fail "restart signaled the unrelated process whose pid was reused"
|
|
611
|
+
kill "$unrelated" 2>/dev/null || true
|
|
612
|
+
wait "$unrelated" 2>/dev/null || true
|
|
613
|
+
pass "watch-arm: restart publishes recovery before clearing a reused-pid watcher lock"
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
test_markerless_legacy_queue_is_recovered_on_arm() {
|
|
617
|
+
local dir home state fakebin row
|
|
618
|
+
dir=$(make_case markerless-legacy-arm)
|
|
619
|
+
home="$dir/home"
|
|
620
|
+
state="$dir/state"
|
|
621
|
+
fakebin="$dir/fakebin"
|
|
622
|
+
mkdir -p "$home/data"
|
|
623
|
+
row=$(printf '1700000000\t7\tcheck\tlegacy-process-event\tcheck: legacy process-event')
|
|
624
|
+
printf '%s\n' "$row" > "$state/.wake-queue"
|
|
625
|
+
|
|
626
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/arm.out"
|
|
627
|
+
wait_for_exit "$ARM_PID" 80 || fail "markerless legacy queue was stranded at re-arm"
|
|
628
|
+
grep -F 'check: rearm-resurface' "$dir/arm.out" >/dev/null \
|
|
629
|
+
|| fail "markerless legacy queue did not trigger recovery"
|
|
630
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
631
|
+
pending:downtime:*|announced:downtime:*) ;;
|
|
632
|
+
*) fail "markerless legacy queue was not adopted into downtime recovery" ;;
|
|
633
|
+
esac
|
|
634
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/drain.out" \
|
|
635
|
+
|| fail "adopted legacy queue could not be drained"
|
|
636
|
+
grep -F "$row" "$dir/drain.out" >/dev/null \
|
|
637
|
+
|| fail "adopted legacy wake was not presented"
|
|
638
|
+
ack_wakes "$state" || fail "adopted legacy wake could not be acknowledged"
|
|
639
|
+
pass "watch-arm: markerless legacy queues are adopted and recovered"
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
# Exercise the handling-window recovery invariant owned by
|
|
643
|
+
# docs/watcher-continuity.md through real watcher processes.
|
|
644
|
+
test_handling_window_close_keeps_the_acknowledgement_valid() {
|
|
645
|
+
local dir home state fakebin pair sequence generation
|
|
646
|
+
dir=$(make_case handling-window-close-acknowledgement)
|
|
647
|
+
home="$dir/home"
|
|
648
|
+
state="$dir/state"
|
|
649
|
+
fakebin="$dir/fakebin"
|
|
650
|
+
mkdir -p "$home/data"
|
|
651
|
+
|
|
652
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/first-arm.out"
|
|
653
|
+
is_live_non_zombie "$ARM_PID" || fail "handling-window fixture watcher did not stay live"
|
|
654
|
+
printf 'done: wake handled while a watcher cycle closes\n' > "$state/handled.status"
|
|
655
|
+
wait_for_exit "$ARM_PID" 120 || fail "fixture watcher did not deliver its wake"
|
|
656
|
+
grep "$(printf '\tsignal\thandled.status\t')" "$state/.wake-queue" >/dev/null \
|
|
657
|
+
|| fail "delivered wake was not durable before handling"
|
|
658
|
+
|
|
659
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/drain.out" 2> "$dir/drain.err" \
|
|
660
|
+
|| fail "handling drain did not present the durable wake"
|
|
661
|
+
pair=$(drain_ack_pair "$dir/drain.err") \
|
|
662
|
+
|| fail "drain did not print a generation-bound acknowledgement command"
|
|
663
|
+
sequence=${pair%%$'\t'*}
|
|
664
|
+
generation=${pair##*$'\t'}
|
|
665
|
+
|
|
666
|
+
# One full watcher cycle appends a wake and then closes inside the handling window.
|
|
667
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/handling-window-arm.out"
|
|
668
|
+
is_live_non_zombie "$ARM_PID" || fail "handling-window watcher did not stay live"
|
|
669
|
+
printf 'done: wake published during handling\n' > "$state/during-handling.status"
|
|
670
|
+
wait_for_exit "$ARM_PID" 120 || fail "handling-window watcher did not deliver its wake"
|
|
671
|
+
grep "$(printf '\tsignal\tduring-handling.status\t')" "$state/.wake-queue" >/dev/null \
|
|
672
|
+
|| fail "handling-window watcher did not durably append its wake"
|
|
673
|
+
|
|
674
|
+
[ "$(cat "$state/.watcher-down" 2>/dev/null || true)" = "pending:downtime:$generation" ] \
|
|
675
|
+
|| fail "repeated publications during handling replaced the outstanding recovery generation"
|
|
676
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" \
|
|
677
|
+
--recovery-generation "$generation" 2> "$dir/ack.err" \
|
|
678
|
+
|| fail "the printed acknowledgement was rejected after repeated publications: $(cat "$dir/ack.err")"
|
|
679
|
+
! grep "$(printf '\tsignal\thandled.status\t')" "$state/.wake-queue" >/dev/null \
|
|
680
|
+
|| fail "the acknowledged wake was not consumed"
|
|
681
|
+
grep "$(printf '\tsignal\tduring-handling.status\t')" "$state/.wake-queue" >/dev/null \
|
|
682
|
+
|| fail "the newer handling-window wake was over-consumed"
|
|
683
|
+
|
|
684
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/remaining-drain.out" \
|
|
685
|
+
2> "$dir/remaining-drain.err" || fail "remaining wake could not be re-drained"
|
|
686
|
+
pair=$(drain_ack_pair "$dir/remaining-drain.err") \
|
|
687
|
+
|| fail "remaining drain did not print an acknowledgement command"
|
|
688
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "${pair%%$'\t'*}" \
|
|
689
|
+
--recovery-generation "${pair##*$'\t'}" \
|
|
690
|
+
|| fail "remaining handling-window wake could not be acknowledged"
|
|
691
|
+
[ ! -s "$state/.wake-queue" ] || fail "remaining wake was not consumed"
|
|
692
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
693
|
+
acked:*) ;;
|
|
694
|
+
*) fail "the handled recovery episode was not retired" ;;
|
|
695
|
+
esac
|
|
696
|
+
|
|
697
|
+
# The next arm must supervise rather than spend its whole cycle on recovery.
|
|
698
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/next-arm.out"
|
|
699
|
+
is_live_non_zombie "$ARM_PID" \
|
|
700
|
+
|| fail "the watcher armed after acknowledgement died inside its first cycle"
|
|
701
|
+
! grep -F 'check: rearm-resurface' "$dir/next-arm.out" >/dev/null \
|
|
702
|
+
|| fail "the watcher armed after acknowledgement re-announced a retired recovery"
|
|
703
|
+
printf 'blocked: a later wake the live watcher must still surface\n' > "$state/later.status"
|
|
704
|
+
wait_for_exit "$ARM_PID" 120 || fail "the live watcher did not surface a later wake"
|
|
705
|
+
grep -q '^signal:' "$dir/next-arm.out" \
|
|
706
|
+
|| fail "the watcher armed after acknowledgement never reached real supervision work: $(cat "$dir/next-arm.out")"
|
|
707
|
+
pass "watch-arm: a watcher close during handling keeps the printed acknowledgement valid"
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
# Exercise the moved-generation recovery invariant owned by
|
|
711
|
+
# docs/watcher-continuity.md through real watcher processes.
|
|
712
|
+
test_moved_generation_acknowledgement_is_self_healing() {
|
|
713
|
+
local dir home state fakebin pair first_sequence first_generation second_generation
|
|
714
|
+
dir=$(make_case moved-generation-acknowledgement)
|
|
715
|
+
home="$dir/home"
|
|
716
|
+
state="$dir/state"
|
|
717
|
+
fakebin="$dir/fakebin"
|
|
718
|
+
mkdir -p "$home/data"
|
|
719
|
+
|
|
720
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/first-arm.out"
|
|
721
|
+
is_live_non_zombie "$ARM_PID" || fail "moved-generation fixture watcher did not stay live"
|
|
722
|
+
printf 'done: first handled wake\n' > "$state/first.status"
|
|
723
|
+
wait_for_exit "$ARM_PID" 120 || fail "fixture watcher did not deliver its first wake"
|
|
724
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/first-drain.out" \
|
|
725
|
+
2> "$dir/first-drain.err" || fail "first drain did not present the durable wake"
|
|
726
|
+
pair=$(drain_ack_pair "$dir/first-drain.err") \
|
|
727
|
+
|| fail "first drain did not print a generation-bound acknowledgement command"
|
|
728
|
+
first_sequence=${pair%%$'\t'*}
|
|
729
|
+
first_generation=${pair##*$'\t'}
|
|
730
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$first_sequence" \
|
|
731
|
+
--recovery-generation "$first_generation" \
|
|
732
|
+
|| fail "the first handled wake could not be acknowledged"
|
|
733
|
+
|
|
734
|
+
# A retired episode does not freeze the generation: the next one is its own.
|
|
735
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$dir/second-arm.out"
|
|
736
|
+
is_live_non_zombie "$ARM_PID" || fail "second fixture watcher did not stay live"
|
|
737
|
+
printf 'done: second wake in a newer recovery episode\n' > "$state/second.status"
|
|
738
|
+
wait_for_exit "$ARM_PID" 120 || fail "second fixture watcher did not deliver its wake"
|
|
739
|
+
second_generation=$(sed -n 's/^pending:downtime:\(.*\)$/\1/p' "$state/.watcher-down")
|
|
740
|
+
[ -n "$second_generation" ] || fail "a wake after acknowledgement did not open a recovery episode"
|
|
741
|
+
[ "$second_generation" != "$first_generation" ] \
|
|
742
|
+
|| fail "an acknowledged episode kept its generation instead of opening a new one"
|
|
743
|
+
|
|
744
|
+
# Replaying the stale pair must not fail, must not over-consume, and must not
|
|
745
|
+
# retire the newer episode.
|
|
746
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$first_sequence" \
|
|
747
|
+
--recovery-generation "$first_generation" 2> "$dir/stale-ack.err" \
|
|
748
|
+
|| fail "a replayed stale acknowledgement was rejected instead of degrading safely"
|
|
749
|
+
if ! grep -F 'WAKE_ACK_REQUIRED' "$dir/stale-ack.err" >/dev/null \
|
|
750
|
+
|| ! grep -F 're-run' "$dir/stale-ack.err" >/dev/null; then
|
|
751
|
+
fail "a moved recovery generation did not name its own remedy: $(cat "$dir/stale-ack.err")"
|
|
752
|
+
fi
|
|
753
|
+
grep "$(printf '\tsignal\tsecond.status\t')" "$state/.wake-queue" >/dev/null \
|
|
754
|
+
|| fail "a stale acknowledgement consumed a wake above its sequence"
|
|
755
|
+
[ "$(cat "$state/.watcher-down" 2>/dev/null || true)" = "pending:downtime:$second_generation" ] \
|
|
756
|
+
|| fail "a stale acknowledgement retired the newer recovery episode"
|
|
757
|
+
|
|
758
|
+
# The sequence alone owns consumption, so the handled rows go even while the
|
|
759
|
+
# generation is stale, and only the episode stays pending.
|
|
760
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through 999 \
|
|
761
|
+
--recovery-generation "$first_generation" 2> "$dir/stale-consume.err" \
|
|
762
|
+
|| fail "a stale acknowledgement refused to consume the rows it was given"
|
|
763
|
+
[ ! -s "$state/.wake-queue" ] \
|
|
764
|
+
|| fail "a stale acknowledgement left its handled rows on the durable queue"
|
|
765
|
+
[ "$(cat "$state/.watcher-down" 2>/dev/null || true)" = "pending:downtime:$second_generation" ] \
|
|
766
|
+
|| fail "row consumption under a stale generation retired the pending episode"
|
|
767
|
+
|
|
768
|
+
# Following the printed remedy closes the episode, so the loop is self-healing.
|
|
769
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/redrain.out" \
|
|
770
|
+
2> "$dir/redrain.err" || fail "the remedy re-drain did not run"
|
|
771
|
+
pair=$(drain_ack_pair "$dir/redrain.err") \
|
|
772
|
+
|| fail "the remedy re-drain did not print the newer acknowledgement command"
|
|
773
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "${pair%%$'\t'*}" \
|
|
774
|
+
--recovery-generation "${pair##*$'\t'}" \
|
|
775
|
+
|| fail "the newer recovery episode could not be acknowledged"
|
|
776
|
+
case "$(cat "$state/.watcher-down" 2>/dev/null || true)" in
|
|
777
|
+
acked:*) ;;
|
|
778
|
+
*) fail "following the printed remedy did not retire the newer recovery episode" ;;
|
|
779
|
+
esac
|
|
780
|
+
pass "watch-arm: a moved recovery generation consumes handled rows and names its remedy"
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
test_downtime_marker_does_not_follow_symlink() {
|
|
784
|
+
local dir home state fakebin armout watcher_pid sentinel
|
|
785
|
+
dir=$(make_case downtime-marker-symlink)
|
|
786
|
+
home="$dir/home"
|
|
787
|
+
state="$dir/state"
|
|
788
|
+
fakebin="$dir/fakebin"
|
|
789
|
+
armout="$dir/arm.out"
|
|
790
|
+
sentinel="$dir/sentinel"
|
|
791
|
+
mkdir -p "$home/data"
|
|
792
|
+
|
|
793
|
+
start_rearm_arm "$home" "$state" "$fakebin" "$armout"
|
|
794
|
+
is_live_non_zombie "$ARM_PID" || fail "symlink fixture watcher did not stay live"
|
|
795
|
+
watcher_pid=$(cat "$state/.watch.lock/pid" 2>/dev/null || true)
|
|
796
|
+
printf 'must remain intact\n' > "$sentinel"
|
|
797
|
+
ln -s "$sentinel" "$state/.watcher-down"
|
|
798
|
+
kill -TERM "$watcher_pid" 2>/dev/null || fail "could not stop symlink fixture watcher"
|
|
799
|
+
wait "$ARM_PID" 2>/dev/null || true
|
|
800
|
+
|
|
801
|
+
[ "$(cat "$sentinel")" = "must remain intact" ] \
|
|
802
|
+
|| fail "downtime marker publication followed and truncated a symlink"
|
|
803
|
+
[ -f "$state/.watcher-down" ] && [ ! -L "$state/.watcher-down" ] \
|
|
804
|
+
|| fail "downtime marker was not safely published as a regular file"
|
|
805
|
+
pass "watch-arm: downtime marker publication does not follow symlinks"
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
test_attached_arm_reports_the_delivered_wake
|
|
809
|
+
test_attached_arm_reports_the_delivered_wake_after_drain
|
|
810
|
+
test_attached_arm_still_fails_on_a_wake_it_did_not_deliver
|
|
811
|
+
test_rearm_resurfaces_durable_queue_and_remote_open_decision
|
|
812
|
+
test_marker_publish_failure_retains_recovery_evidence
|
|
813
|
+
test_delivery_gap_wake_is_recovered_once
|
|
814
|
+
test_interrupted_handling_is_redrained_on_rearm
|
|
815
|
+
test_malformed_marker_is_quarantined_once
|
|
816
|
+
test_recovery_consumption_serializes_queue_publication
|
|
817
|
+
test_restart_preserves_recovery_across_reused_pid_lock
|
|
818
|
+
test_markerless_legacy_queue_is_recovered_on_arm
|
|
819
|
+
test_handling_window_close_keeps_the_acknowledgement_valid
|
|
820
|
+
test_moved_generation_acknowledgement_is_self_healing
|
|
821
|
+
test_downtime_marker_does_not_follow_symlink
|