@zerwiz/ymir 0.1.4 → 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 -59
- 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,1224 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tests/fm-wake-queue.test.sh - wake-queue losslessness (the queue safety matrix):
|
|
3
|
+
# concurrent append/drain, bounded structural enrichment, interruption safety,
|
|
4
|
+
# signal catch-up while no watcher runs, stale/check enqueue-before-suppressor
|
|
5
|
+
# ordering, atomic double-drain, duplicate collapse, and liveness assertion.
|
|
6
|
+
# Nothing is lost and nothing is double-consumed. General watcher/lock liveness
|
|
7
|
+
# lives in fm-watcher-lock.test.sh; daemon classification/injection in
|
|
8
|
+
# fm-daemon.test.sh.
|
|
9
|
+
set -u
|
|
10
|
+
|
|
11
|
+
# shellcheck source=tests/wake-helpers.sh
|
|
12
|
+
. "$(dirname "${BASH_SOURCE[0]}")/wake-helpers.sh"
|
|
13
|
+
|
|
14
|
+
WATCH="$ROOT/bin/fm-watch.sh"
|
|
15
|
+
DRAIN="$ROOT/bin/fm-wake-drain.sh"
|
|
16
|
+
GRANT="$ROOT/bin/fm-wake-grant.sh"
|
|
17
|
+
|
|
18
|
+
TMP_ROOT=$(fm_test_tmproot fm-wake-tests)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
test_concurrent_append_and_drain() {
|
|
22
|
+
local dir state out1 out2 pids i pid count unique malformed sequence generation
|
|
23
|
+
dir=$(make_case concurrent)
|
|
24
|
+
state="$dir/state"
|
|
25
|
+
out1="$dir/drain-one.out"
|
|
26
|
+
out2="$dir/drain-two.out"
|
|
27
|
+
pids=
|
|
28
|
+
i=1
|
|
29
|
+
while [ "$i" -le 40 ]; do
|
|
30
|
+
append_wake "$state" signal "status-$i" "signal: $state/status-$i.status" &
|
|
31
|
+
pids="$pids $!"
|
|
32
|
+
i=$((i + 1))
|
|
33
|
+
done
|
|
34
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out1" &
|
|
35
|
+
pids="$pids $!"
|
|
36
|
+
for pid in $pids; do
|
|
37
|
+
wait "$pid" || fail "concurrent append/drain subprocess failed"
|
|
38
|
+
done
|
|
39
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out2" 2> "$dir/drain-two.err" || fail "final drain failed"
|
|
40
|
+
count=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$out2")
|
|
41
|
+
[ "$count" -eq 40 ] || fail "expected final replay of 40 durable records, got $count"
|
|
42
|
+
malformed=$(awk -F '\t' 'NF && NF != 5 { bad++ } END { print bad + 0 }' "$out2")
|
|
43
|
+
[ "$malformed" -eq 0 ] || fail "drained records had malformed fields"
|
|
44
|
+
unique=$(awk -F '\t' 'NF == 5 { keys[$4] = 1 } END { for (k in keys) count++; print count + 0 }' "$out2")
|
|
45
|
+
[ "$unique" -eq 40 ] || fail "expected 40 unique keys, got $unique"
|
|
46
|
+
[ -s "$state/.wake-queue" ] || fail "concurrent drain consumed records before handling acknowledgement"
|
|
47
|
+
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/drain-two.err")
|
|
48
|
+
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/drain-two.err")
|
|
49
|
+
[ -n "$sequence" ] && [ -n "$generation" ] || fail "final replay omitted its acknowledgement boundary"
|
|
50
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
51
|
+
|| fail "concurrent records could not be acknowledged"
|
|
52
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledged concurrent records remained queued"
|
|
53
|
+
pass "concurrent append plus drain preserves durable records through acknowledgement"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test_signal_catchup_without_running_watcher() {
|
|
57
|
+
local dir state fakebin out drain_out drain_err status_file sequence generation
|
|
58
|
+
dir=$(make_case signal)
|
|
59
|
+
state="$dir/state"
|
|
60
|
+
fakebin="$dir/fakebin"
|
|
61
|
+
out="$dir/watch.out"
|
|
62
|
+
drain_out="$dir/drain.out"
|
|
63
|
+
drain_err="$dir/drain.err"
|
|
64
|
+
status_file="$state/task.status"
|
|
65
|
+
# The durable-queue catch-up contract applies to ACTIONABLE wakes (the always-on
|
|
66
|
+
# watcher can absorb no-verb working: notes when the crew is provably working).
|
|
67
|
+
# Use a captain-relevant verb so the wake is surfaced and the catch-up path is
|
|
68
|
+
# tested.
|
|
69
|
+
printf 'blocked: first\n' > "$status_file"
|
|
70
|
+
PATH="$fakebin:$PATH" FM_STATE_OVERRIDE="$state" FM_POLL=1 FM_SIGNAL_GRACE=1 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 "$WATCH" > "$out" &
|
|
71
|
+
wait_for_exit "$!" 40 || fail "watcher did not exit for first signal"
|
|
72
|
+
grep -F "signal: $status_file" "$out" >/dev/null || fail "watcher did not print first signal"
|
|
73
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$drain_out" 2> "$drain_err" || fail "drain after first signal failed"
|
|
74
|
+
grep "$(printf '\tsignal\t')" "$drain_out" | grep -F "$status_file" >/dev/null || fail "first signal was not queued"
|
|
75
|
+
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' "$drain_err")
|
|
76
|
+
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' "$drain_err")
|
|
77
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
78
|
+
|| fail "first signal handling acknowledgement failed"
|
|
79
|
+
|
|
80
|
+
printf 'done: second\n' >> "$status_file"
|
|
81
|
+
: > "$out"
|
|
82
|
+
PATH="$fakebin:$PATH" FM_STATE_OVERRIDE="$state" FM_POLL=1 FM_SIGNAL_GRACE=1 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 "$WATCH" > "$out" &
|
|
83
|
+
wait_for_exit "$!" 40 || fail "watcher did not exit for second signal"
|
|
84
|
+
grep -F "signal: $status_file" "$out" >/dev/null || fail "signal written with no watcher was not caught"
|
|
85
|
+
pass "signal written while no watcher runs is caught on next run"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test_stale_enqueue_before_suppressor() {
|
|
89
|
+
local dir state fakebin out drain_out capture_file window key pane_hash
|
|
90
|
+
dir=$(make_case stale)
|
|
91
|
+
state="$dir/state"
|
|
92
|
+
fakebin="$dir/fakebin"
|
|
93
|
+
out="$dir/watch.out"
|
|
94
|
+
drain_out="$dir/drain.out"
|
|
95
|
+
capture_file="$dir/pane.txt"
|
|
96
|
+
window="test:fm-stale"
|
|
97
|
+
printf 'idle prompt' > "$capture_file"
|
|
98
|
+
printf 'window=%s\nkind=ship\n' "$window" > "$state/stale.meta"
|
|
99
|
+
# A stale pane sitting on a captain-relevant status is actionable when the crew
|
|
100
|
+
# is not provably working, so give the window one and prime the .seen-* marker
|
|
101
|
+
# to its current signature so the per-poll signal scan does not pre-empt the
|
|
102
|
+
# stale wake with a signal wake.
|
|
103
|
+
printf 'done: ready in branch fm/stale\n' > "$state/stale.status"
|
|
104
|
+
prime_status_seen "$state" "$state/stale.status"
|
|
105
|
+
key=$(printf '%s' "$window" | tr ':/.' '___')
|
|
106
|
+
pane_hash=$(hash_text "idle prompt")
|
|
107
|
+
printf '%s' "$pane_hash" > "$state/.hash-$key"
|
|
108
|
+
printf '1\n' > "$state/.count-$key"
|
|
109
|
+
PATH="$fakebin:$PATH" FM_FAKE_TMUX_WINDOW="$window" FM_FAKE_TMUX_CAPTURE="$capture_file" FM_STATE_OVERRIDE="$state" FM_POLL=1 FM_SIGNAL_GRACE=1 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 "$WATCH" > "$out" &
|
|
110
|
+
wait_for_exit "$!" 40 || fail "watcher did not exit for stale pane"
|
|
111
|
+
grep -Fx "stale: $window" "$out" >/dev/null || fail "watcher did not print stale wake"
|
|
112
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$drain_out" || fail "drain after stale wake failed"
|
|
113
|
+
grep "$(printf '\tstale\t')" "$drain_out" | grep -F "$window" >/dev/null || fail "stale wake was not queued"
|
|
114
|
+
[ "$(cat "$state/.stale-$key" 2>/dev/null || true)" = "$pane_hash" ] || fail "stale suppressor was not written"
|
|
115
|
+
pass "stale wake is queued before suppressor state is advanced"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Absorb-only-when-provably-working adds a new actionable wake: a non-terminal stale
|
|
119
|
+
# whose crew is NOT provably working is surfaced immediately. That new path must keep
|
|
120
|
+
# the queue-safety invariant - enqueue the stale wake BEFORE advancing the .stale-*
|
|
121
|
+
# suppressor - so a watcher killed between the two never swallows the surfaced finish.
|
|
122
|
+
test_not_working_stale_enqueue_before_suppressor() {
|
|
123
|
+
local dir state fakebin out drain_out capture_file window key pane_hash
|
|
124
|
+
dir=$(make_case stale-stopped)
|
|
125
|
+
state="$dir/state"
|
|
126
|
+
fakebin="$dir/fakebin"
|
|
127
|
+
out="$dir/watch.out"
|
|
128
|
+
drain_out="$dir/drain.out"
|
|
129
|
+
capture_file="$dir/pane.txt"
|
|
130
|
+
window="test:fm-stopped"
|
|
131
|
+
printf 'idle prompt, finished' > "$capture_file"
|
|
132
|
+
printf 'window=%s\nkind=ship\n' "$window" > "$state/stopped.meta"
|
|
133
|
+
# Non-terminal status (no captain-relevant verb); prime .seen-* so the per-poll
|
|
134
|
+
# signal scan does not pre-empt the stale path.
|
|
135
|
+
printf 'working: implementing\n' > "$state/stopped.status"
|
|
136
|
+
prime_status_seen "$state" "$state/stopped.status"
|
|
137
|
+
key=$(printf '%s' "$window" | tr ':/.' '___')
|
|
138
|
+
pane_hash=$(hash_text "idle prompt, finished")
|
|
139
|
+
printf '%s' "$pane_hash" > "$state/.hash-$key"
|
|
140
|
+
printf '1\n' > "$state/.count-$key"
|
|
141
|
+
# NOT provably working: no running pipeline, idle pane. (make_case installed the
|
|
142
|
+
# fake fm-crew-state.sh the watcher reads via FM_CREW_STATE_BIN.)
|
|
143
|
+
export FM_FAKE_CREW_STATE='state: unknown · source: none · no current-state source available'
|
|
144
|
+
PATH="$fakebin:$PATH" FM_FAKE_TMUX_WINDOW="$window" FM_FAKE_TMUX_CAPTURE="$capture_file" \
|
|
145
|
+
FM_STATE_OVERRIDE="$state" FM_CREW_STATE_BIN="$fakebin/fm-crew-state.sh" \
|
|
146
|
+
FM_STALE_ESCALATE_SECS=999 FM_POLL=1 FM_SIGNAL_GRACE=1 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 "$WATCH" > "$out" &
|
|
147
|
+
wait_for_exit "$!" 40 || fail "watcher did not surface a not-provably-working stale"
|
|
148
|
+
grep -Fx "stale: $window" "$out" >/dev/null || fail "watcher did not print the immediate stale wake"
|
|
149
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$drain_out" || fail "drain after the immediate stale wake failed"
|
|
150
|
+
grep "$(printf '\tstale\t')" "$drain_out" | grep -F "$window" >/dev/null || fail "immediate stale wake was not queued"
|
|
151
|
+
[ "$(cat "$state/.stale-$key" 2>/dev/null || true)" = "$pane_hash" ] || fail "stale suppressor was not advanced after the enqueue"
|
|
152
|
+
unset FM_FAKE_CREW_STATE
|
|
153
|
+
pass "a not-provably-working stale wake is queued before its suppressor is advanced"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
test_check_output_is_queued() {
|
|
157
|
+
local dir state fakebin out drain_out check_file
|
|
158
|
+
dir=$(make_case check)
|
|
159
|
+
state="$dir/state"
|
|
160
|
+
fakebin="$dir/fakebin"
|
|
161
|
+
out="$dir/watch.out"
|
|
162
|
+
drain_out="$dir/drain.out"
|
|
163
|
+
check_file="$state/task.check.sh"
|
|
164
|
+
cat > "$check_file" <<'SH'
|
|
165
|
+
#!/usr/bin/env bash
|
|
166
|
+
printf 'merged: https://example.test/pr/1\n'
|
|
167
|
+
SH
|
|
168
|
+
chmod 0700 "$check_file"
|
|
169
|
+
FM_STATE_OVERRIDE="$state" "$ROOT/bin/fm-check-register.sh" task >/dev/null \
|
|
170
|
+
|| fail "could not register queue custom check"
|
|
171
|
+
PATH="$fakebin:$PATH" FM_STATE_OVERRIDE="$state" FM_POLL=1 FM_SIGNAL_GRACE=1 FM_CHECK_INTERVAL=0 FM_HEARTBEAT=999999 "$WATCH" > "$out" &
|
|
172
|
+
wait_for_exit "$!" 40 || fail "watcher did not exit for check output"
|
|
173
|
+
grep -F "check: $check_file: merged: https://example.test/pr/1" "$out" >/dev/null || fail "watcher did not print check wake"
|
|
174
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$drain_out" || fail "drain after check wake failed"
|
|
175
|
+
grep "$(printf '\tcheck\t')" "$drain_out" | grep -F "$check_file" | grep -F 'merged: https://example.test/pr/1' >/dev/null || fail "check wake was not queued"
|
|
176
|
+
[ -e "$state/.last-check" ] || fail "check cadence marker was not written after queue append"
|
|
177
|
+
pass "registered custom check output is queued before cadence suppression"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
test_atomic_double_drain() {
|
|
181
|
+
local dir state out1 out2 count1 count2 sequence generation leftover
|
|
182
|
+
dir=$(make_case double-drain)
|
|
183
|
+
state="$dir/state"
|
|
184
|
+
out1="$dir/drain-one.out"
|
|
185
|
+
out2="$dir/drain-two.out"
|
|
186
|
+
append_wake "$state" heartbeat heartbeat heartbeat || fail "heartbeat append failed"
|
|
187
|
+
append_wake "$state" signal task "signal: $state/task.status" || fail "signal append failed"
|
|
188
|
+
append_wake "$state" stale 's:fm-task' 'stale: s:fm-task' || fail "stale append failed"
|
|
189
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out1" 2> "$dir/drain-one.err" &
|
|
190
|
+
pid1=$!
|
|
191
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out2" 2> "$dir/drain-two.err" &
|
|
192
|
+
pid2=$!
|
|
193
|
+
wait "$pid1" || fail "first drain failed"
|
|
194
|
+
wait "$pid2" || fail "second drain failed"
|
|
195
|
+
count1=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$out1")
|
|
196
|
+
count2=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$out2")
|
|
197
|
+
[ "$count1" -eq 3 ] && [ "$count2" -eq 3 ] \
|
|
198
|
+
|| fail "unacknowledged concurrent drains did not replay all three records"
|
|
199
|
+
cmp -s "$out1" "$out2" || fail "concurrent pre-ack replays were not deterministic"
|
|
200
|
+
[ -s "$state/.wake-queue" ] || fail "concurrent drains consumed records before acknowledgement"
|
|
201
|
+
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/drain-two.err")
|
|
202
|
+
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/drain-two.err")
|
|
203
|
+
[ -n "$sequence" ] && [ -n "$generation" ] || fail "concurrent replay omitted its acknowledgement boundary"
|
|
204
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
205
|
+
|| fail "concurrent replay acknowledgement failed"
|
|
206
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledgement did not consume replayed records"
|
|
207
|
+
leftover=$(FM_STATE_OVERRIDE="$state" "$DRAIN" | awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }')
|
|
208
|
+
[ "$leftover" -eq 0 ] || fail "acknowledged records replayed again"
|
|
209
|
+
pass "concurrent drains replay until one post-handling acknowledgement consumes records"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
test_drain_dedupes_obvious_duplicates() {
|
|
213
|
+
local dir state out count
|
|
214
|
+
dir=$(make_case dedupe)
|
|
215
|
+
state="$dir/state"
|
|
216
|
+
out="$dir/drain.out"
|
|
217
|
+
append_wake "$state" heartbeat heartbeat heartbeat || fail "first heartbeat append failed"
|
|
218
|
+
append_wake "$state" signal task.status "signal: $state/task.status" || fail "first signal append failed"
|
|
219
|
+
append_wake "$state" heartbeat heartbeat heartbeat || fail "second heartbeat append failed"
|
|
220
|
+
append_wake "$state" signal task.status "signal: $state/task.status $state/task.turn-ended" || fail "second signal append failed"
|
|
221
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" || fail "dedupe drain failed"
|
|
222
|
+
count=$(awk 'NF { count++ } END { print count + 0 }' "$out")
|
|
223
|
+
[ "$count" -eq 2 ] || fail "expected 2 deduped records, got $count"
|
|
224
|
+
grep "$(printf '\theartbeat\theartbeat\theartbeat')" "$out" >/dev/null || fail "heartbeat was not preserved"
|
|
225
|
+
grep "$(printf '\tsignal\ttask.status\t')" "$out" | grep -F "$state/task.turn-ended" >/dev/null || fail "latest signal payload was not preserved"
|
|
226
|
+
pass "drain collapses obvious duplicate heartbeat and signal records"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# The drain runs at the top of every wake-handling turn, so it also asserts
|
|
230
|
+
# watcher liveness via fm-guard.sh: a lapsed re-arm chain then surfaces even on a
|
|
231
|
+
# plain drain-and-handle turn that runs no other supervision script. It must warn
|
|
232
|
+
# when work is in flight with no live watcher, and stay silent right after a
|
|
233
|
+
# normal fire from a live watcher with a fresh beacon, so it never false-alarms.
|
|
234
|
+
test_secondmate_foreign_queue_stall_is_one_shot_and_read_only() {
|
|
235
|
+
local dir state sub fakebin out row_before row_after stall_count
|
|
236
|
+
dir=$(make_case secondmate-foreign-stall)
|
|
237
|
+
state="$dir/state"
|
|
238
|
+
sub="$dir/secondmate"
|
|
239
|
+
mkdir -p "$sub/state" "$sub/data" "$sub/bin"
|
|
240
|
+
printf '# Firstmate\n' > "$sub/AGENTS.md"
|
|
241
|
+
printf 'mate\n' > "$sub/.fm-secondmate-home"
|
|
242
|
+
printf 'window=firstmate:fm-mate\nkind=secondmate\nharness=claude\nbackend=tmux\nhome=%s\n' \
|
|
243
|
+
"$sub" > "$state/mate.meta"
|
|
244
|
+
printf '%s\t7\tcheck\trouted\tcheck: routed row\n' "$(( $(date +%s) - 10 ))" > "$sub/state/.wake-queue"
|
|
245
|
+
row_before="$dir/foreign-before"
|
|
246
|
+
row_after="$dir/foreign-after"
|
|
247
|
+
cp "$sub/state/.wake-queue" "$row_before"
|
|
248
|
+
fakebin="$dir/fakebin"
|
|
249
|
+
cat > "$fakebin/tmux" <<'SH'
|
|
250
|
+
#!/usr/bin/env bash
|
|
251
|
+
case "${1:-}" in
|
|
252
|
+
list-windows) printf '%s\n' "${FM_FAKE_TMUX_WINDOW:-}" ;;
|
|
253
|
+
capture-pane) cat "${FM_FAKE_TMUX_CAPTURE:-/dev/null}" ;;
|
|
254
|
+
display-message) printf '0\n' ;;
|
|
255
|
+
*) exit 0 ;;
|
|
256
|
+
esac
|
|
257
|
+
SH
|
|
258
|
+
chmod +x "$fakebin/tmux"
|
|
259
|
+
out="$dir/watch.out"
|
|
260
|
+
|
|
261
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
262
|
+
FM_STATE_OVERRIDE="$state" FM_FAKE_TMUX_WINDOW='firstmate:fm-mate' \
|
|
263
|
+
FM_FAKE_TMUX_LOG="$dir/tmux.log" FM_FAKE_TMUX_CAPTURE="$dir/fake-tmux/pane.txt" \
|
|
264
|
+
FM_SECONDMATE_WAKE_STALL_SECS=1 FM_POLL=1 FM_SIGNAL_GRACE=0 \
|
|
265
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
266
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 3 > "$out" 2> "$dir/watch.err" || true
|
|
267
|
+
grep -F 'check: secondmate wake-loop stalled: mate=mate row=7' "$out" >/dev/null \
|
|
268
|
+
|| fail "an aged foreign row did not wake the parent checkpoint: $(cat "$out"); err=$(cat "$dir/watch.err"); meta=$(cat "$state/mate.meta"); foreign=$(cat "$sub/state/.wake-queue")"
|
|
269
|
+
[ -s "$state/.wake-queue" ] || fail "the parent notification was not durable"
|
|
270
|
+
stall_count=$(grep -c 'secondmate-wake-loop-mate-' "$state/.wake-queue" || true)
|
|
271
|
+
[ "$stall_count" -eq 1 ] || fail "the first parent checkpoint did not publish exactly one stall notification"
|
|
272
|
+
|
|
273
|
+
cmp -s "$row_before" "$sub/state/.wake-queue" \
|
|
274
|
+
|| fail "foreign queue row changed during read-only stall detection"
|
|
275
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/drain.out" 2> "$dir/drain.err" \
|
|
276
|
+
|| fail "parent drain failed after the stall notification"
|
|
277
|
+
ack_drain_err "$state" "$dir/drain.err" \
|
|
278
|
+
|| fail "parent stall notification could not be acknowledged"
|
|
279
|
+
|
|
280
|
+
sleep 1
|
|
281
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
282
|
+
FM_STATE_OVERRIDE="$state" FM_FAKE_TMUX_WINDOW='firstmate:fm-mate' \
|
|
283
|
+
FM_FAKE_TMUX_LOG="$dir/tmux.log" FM_FAKE_TMUX_CAPTURE="$dir/fake-tmux/pane.txt" \
|
|
284
|
+
FM_SECONDMATE_WAKE_STALL_SECS=1 FM_POLL=1 FM_SIGNAL_GRACE=0 \
|
|
285
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
286
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 2 > "$dir/watch-second.out" 2> "$dir/watch-second.err" || true
|
|
287
|
+
[ ! -s "$state/.wake-queue" ] || {
|
|
288
|
+
stall_count=$(grep -c 'secondmate-wake-loop-mate-' "$state/.wake-queue" || true)
|
|
289
|
+
[ "$stall_count" -eq 0 ] || fail "repeated checkpoint re-published the same stall notification"
|
|
290
|
+
}
|
|
291
|
+
cp "$sub/state/.wake-queue" "$row_after"
|
|
292
|
+
cmp -s "$row_before" "$row_after" || fail "foreign queue changed after idempotent re-check"
|
|
293
|
+
|
|
294
|
+
: > "$sub/state/.wake-queue"
|
|
295
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
296
|
+
FM_STATE_OVERRIDE="$state" FM_FAKE_TMUX_WINDOW='firstmate:fm-mate' \
|
|
297
|
+
FM_FAKE_TMUX_LOG="$dir/tmux.log" FM_FAKE_TMUX_CAPTURE="$dir/fake-tmux/pane.txt" \
|
|
298
|
+
FM_SECONDMATE_WAKE_STALL_SECS=1 FM_POLL=1 FM_SIGNAL_GRACE=0 \
|
|
299
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
300
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 2 > "$dir/watch-empty.out" 2> "$dir/watch-empty.err" || true
|
|
301
|
+
! grep -F 'secondmate wake-loop stalled' "$dir/watch-empty.out" >/dev/null \
|
|
302
|
+
|| fail "an empty foreign queue produced a stall notification"
|
|
303
|
+
|
|
304
|
+
printf '%s\t8\tcheck\thealthy\tcheck: healthy row\n' "$(date +%s)" > "$sub/state/.wake-queue"
|
|
305
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
306
|
+
FM_STATE_OVERRIDE="$state" FM_FAKE_TMUX_WINDOW='firstmate:fm-mate' \
|
|
307
|
+
FM_FAKE_TMUX_LOG="$dir/tmux.log" FM_FAKE_TMUX_CAPTURE="$dir/fake-tmux/pane.txt" \
|
|
308
|
+
FM_SECONDMATE_WAKE_STALL_SECS=60 FM_POLL=1 FM_SIGNAL_GRACE=0 \
|
|
309
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
310
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 2 > "$dir/watch-healthy.out" 2> "$dir/watch-healthy.err" || true
|
|
311
|
+
! grep -F 'secondmate wake-loop stalled' "$dir/watch-healthy.out" >/dev/null \
|
|
312
|
+
|| fail "a healthy foreign queue produced a stall notification"
|
|
313
|
+
pass "foreign secondmate queue stalls notify once, remain byte-stable, and stay quiet when empty or healthy"
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
test_secondmate_stall_marker_rejects_symlink() {
|
|
317
|
+
local dir state sub fakebin marker outside expected
|
|
318
|
+
dir=$(make_case secondmate-stall-marker-symlink)
|
|
319
|
+
state="$dir/state"
|
|
320
|
+
sub="$dir/secondmate"
|
|
321
|
+
mkdir -p "$sub/state"
|
|
322
|
+
printf 'mate\n' > "$sub/.fm-secondmate-home"
|
|
323
|
+
printf 'window=firstmate:fm-mate\nkind=secondmate\nhome=%s\n' "$sub" > "$state/mate.meta"
|
|
324
|
+
printf '%s\t7\tcheck\trouted\tcheck: routed row\n' "$(( $(date +%s) - 10 ))" > "$sub/state/.wake-queue"
|
|
325
|
+
outside="$dir/outside"
|
|
326
|
+
expected='must remain unchanged'
|
|
327
|
+
printf '%s\n' "$expected" > "$outside"
|
|
328
|
+
marker="$state/.secondmate-wake-stall-mate"
|
|
329
|
+
ln -s "$outside" "$marker"
|
|
330
|
+
fakebin="$dir/fakebin"
|
|
331
|
+
cat > "$fakebin/tmux" <<'SH'
|
|
332
|
+
#!/usr/bin/env bash
|
|
333
|
+
case "${1:-}" in
|
|
334
|
+
list-windows) printf '%s\n' 'firstmate:fm-mate' ;;
|
|
335
|
+
capture-pane) : ;;
|
|
336
|
+
display-message) printf '0\n' ;;
|
|
337
|
+
*) exit 0 ;;
|
|
338
|
+
esac
|
|
339
|
+
SH
|
|
340
|
+
chmod +x "$fakebin/tmux"
|
|
341
|
+
|
|
342
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
343
|
+
FM_STATE_OVERRIDE="$state" FM_SECONDMATE_WAKE_STALL_SECS=1 FM_POLL=1 \
|
|
344
|
+
FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
345
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 2 \
|
|
346
|
+
> "$dir/watch.out" 2> "$dir/watch.err" || true
|
|
347
|
+
[ "$(cat "$outside")" = "$expected" ] || fail "stall marker write followed an unsafe symlink"
|
|
348
|
+
[ -L "$marker" ] || fail "stall marker write replaced rather than rejected an unsafe path"
|
|
349
|
+
[ ! -s "$state/.wake-queue" ] || fail "unsafe stall marker path still published a parent notification"
|
|
350
|
+
pass "secondmate stall markers reject symlinks without touching their targets"
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
test_acknowledged_stall_publication_survives_pre_marker_crash() {
|
|
354
|
+
local dir state sub fakebin out epoch row_before
|
|
355
|
+
dir=$(make_case secondmate-stall-crash)
|
|
356
|
+
state="$dir/state"
|
|
357
|
+
sub="$dir/secondmate"
|
|
358
|
+
mkdir -p "$sub/state" "$sub/data"
|
|
359
|
+
printf 'mate\n' > "$sub/.fm-secondmate-home"
|
|
360
|
+
printf 'window=firstmate:fm-mate\nkind=secondmate\nharness=claude\nbackend=tmux\nhome=%s\n' \
|
|
361
|
+
"$sub" > "$state/mate.meta"
|
|
362
|
+
epoch=$(( $(date +%s) - 10 ))
|
|
363
|
+
printf '%s\t7\tcheck\trouted\tcheck: routed row\n' "$epoch" > "$sub/state/.wake-queue"
|
|
364
|
+
row_before="$dir/foreign-before"
|
|
365
|
+
cp "$sub/state/.wake-queue" "$row_before"
|
|
366
|
+
append_wake "$state" check "secondmate-wake-loop-mate-$epoch-7" \
|
|
367
|
+
"check: secondmate wake-loop stalled: mate=mate row=7 age=10s" \
|
|
368
|
+
|| fail "could not seed the pre-marker crash publication"
|
|
369
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/drain.out" 2> "$dir/drain.err" \
|
|
370
|
+
|| fail "pre-marker crash publication could not be drained"
|
|
371
|
+
ack_drain_err "$state" "$dir/drain.err" \
|
|
372
|
+
|| fail "pre-marker crash publication could not be acknowledged"
|
|
373
|
+
|
|
374
|
+
fakebin="$dir/fakebin"
|
|
375
|
+
out="$dir/watch.out"
|
|
376
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
377
|
+
FM_STATE_OVERRIDE="$state" FM_FAKE_TMUX_WINDOW='firstmate:fm-mate' \
|
|
378
|
+
FM_FAKE_TMUX_LOG="$dir/tmux.log" FM_FAKE_TMUX_CAPTURE="$dir/fake-tmux/pane.txt" \
|
|
379
|
+
FM_SECONDMATE_WAKE_STALL_SECS=1 FM_POLL=1 FM_SIGNAL_GRACE=0 \
|
|
380
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
381
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 2 > "$out" 2> "$dir/watch.err" || true
|
|
382
|
+
! grep -F 'secondmate wake-loop stalled' "$out" >/dev/null \
|
|
383
|
+
|| fail "an acknowledged publication was duplicated after the pre-marker crash state"
|
|
384
|
+
[ ! -s "$state/.wake-queue" ] \
|
|
385
|
+
|| fail "the replacement watcher re-published an acknowledged stall notification"
|
|
386
|
+
cmp -s "$row_before" "$sub/state/.wake-queue" \
|
|
387
|
+
|| fail "pre-marker crash recovery changed the foreign queue row"
|
|
388
|
+
pass "stall publication acknowledgement closes the pre-marker crash window"
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
test_empty_prefix_mate_preserves_other_mate_receipt() {
|
|
392
|
+
local dir state empty stalled fakebin epoch row_before round
|
|
393
|
+
dir=$(make_case secondmate-prefix-receipt)
|
|
394
|
+
state="$dir/state"
|
|
395
|
+
empty="$dir/ios"
|
|
396
|
+
stalled="$dir/ios-ui"
|
|
397
|
+
mkdir -p "$empty/state" "$stalled/state"
|
|
398
|
+
printf 'ios\n' > "$empty/.fm-secondmate-home"
|
|
399
|
+
printf 'ios-ui\n' > "$stalled/.fm-secondmate-home"
|
|
400
|
+
printf 'window=firstmate:fm-ios\nkind=secondmate\nhome=%s\n' "$empty" > "$state/ios.meta"
|
|
401
|
+
printf 'window=firstmate:fm-ios-ui\nkind=secondmate\nhome=%s\n' "$stalled" > "$state/ios-ui.meta"
|
|
402
|
+
: > "$empty/state/.wake-queue"
|
|
403
|
+
epoch=$(( $(date +%s) - 10 ))
|
|
404
|
+
printf '%s\t9\tcheck\trouted\tcheck: routed row\n' "$epoch" > "$stalled/state/.wake-queue"
|
|
405
|
+
row_before="$dir/foreign-before"
|
|
406
|
+
cp "$stalled/state/.wake-queue" "$row_before"
|
|
407
|
+
append_wake "$state" check "secondmate-wake-loop-ios-ui-$epoch-9" \
|
|
408
|
+
"check: secondmate wake-loop stalled: mate=ios-ui row=9 age=10s" \
|
|
409
|
+
|| fail "could not seed the ios-ui stall publication"
|
|
410
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/drain.out" 2> "$dir/drain.err" \
|
|
411
|
+
|| fail "ios-ui stall publication could not be drained"
|
|
412
|
+
ack_drain_err "$state" "$dir/drain.err" \
|
|
413
|
+
|| fail "ios-ui stall publication could not be acknowledged"
|
|
414
|
+
|
|
415
|
+
fakebin="$dir/fakebin"
|
|
416
|
+
round=1
|
|
417
|
+
while [ "$round" -le 2 ]; do
|
|
418
|
+
PATH="$fakebin:$PATH" FM_HOME="$dir" FM_ROOT_OVERRIDE="$ROOT" \
|
|
419
|
+
FM_STATE_OVERRIDE="$state" FM_FAKE_TMUX_WINDOW='' \
|
|
420
|
+
FM_FAKE_TMUX_LOG="$dir/tmux.log" FM_FAKE_TMUX_CAPTURE="$dir/fake-tmux/pane.txt" \
|
|
421
|
+
FM_SECONDMATE_WAKE_STALL_SECS=1 FM_POLL=1 FM_SIGNAL_GRACE=0 \
|
|
422
|
+
FM_CHECK_INTERVAL=999999 FM_HEARTBEAT=999999 \
|
|
423
|
+
"$ROOT/bin/fm-watch-checkpoint.sh" --seconds 2 \
|
|
424
|
+
> "$dir/watch-$round.out" 2> "$dir/watch-$round.err" || true
|
|
425
|
+
! grep -F 'secondmate wake-loop stalled' "$dir/watch-$round.out" >/dev/null \
|
|
426
|
+
|| fail "empty ios queue erased ios-ui idempotency on checkpoint $round"
|
|
427
|
+
round=$((round + 1))
|
|
428
|
+
done
|
|
429
|
+
[ ! -s "$state/.wake-queue" ] \
|
|
430
|
+
|| fail "overlapping mate ids re-published the acknowledged ios-ui stall"
|
|
431
|
+
cmp -s "$row_before" "$stalled/state/.wake-queue" \
|
|
432
|
+
|| fail "overlapping mate receipt checks changed the foreign row"
|
|
433
|
+
pass "empty prefix mate cleanup preserves another mate's stall receipt"
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
test_drain_asserts_watcher_liveness() {
|
|
437
|
+
local dir state err identity
|
|
438
|
+
dir=$(make_case drain-liveness)
|
|
439
|
+
state="$dir/state"
|
|
440
|
+
err="$dir/drain.err"
|
|
441
|
+
printf 'window=test:fm-x\nkind=ship\n' > "$state/x.meta"
|
|
442
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" >/dev/null 2> "$err" || fail "drain failed while asserting liveness"
|
|
443
|
+
grep -F 'WATCHER DOWN' "$err" >/dev/null || fail "drain did not surface the watcher-down banner with work in flight and no live watcher"
|
|
444
|
+
: > "$err"
|
|
445
|
+
identity=$(FM_STATE_OVERRIDE="$state" bash -c '. "$1"; fm_pid_identity "$2"' _ "$ROOT/bin/fm-wake-lib.sh" "$$") \
|
|
446
|
+
|| fail "could not identify the live watcher fixture"
|
|
447
|
+
mkdir "$state/.watch.lock"
|
|
448
|
+
printf '%s\n' "$$" > "$state/.watch.lock/pid"
|
|
449
|
+
printf '%s\n' "$dir" > "$state/.watch.lock/fm-home"
|
|
450
|
+
printf '%s\n' "$WATCH" > "$state/.watch.lock/watcher-path"
|
|
451
|
+
printf '%s\n' "$identity" > "$state/.watch.lock/pid-identity"
|
|
452
|
+
touch "$state/.last-watcher-beat"
|
|
453
|
+
FM_HOME="$dir" FM_STATE_OVERRIDE="$state" FM_GUARD_GRACE=300 "$DRAIN" >/dev/null 2> "$err" \
|
|
454
|
+
|| fail "drain failed with a live watcher and fresh beacon"
|
|
455
|
+
if grep -F 'WATCHER DOWN' "$err" >/dev/null; then
|
|
456
|
+
fail "drain false-alarmed with a live watcher and fresh beacon"
|
|
457
|
+
fi
|
|
458
|
+
pass "drain asserts watcher liveness: warns on a lapse, stays silent for a live watcher with a fresh beacon"
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
test_structural_signal_enrichment_preserves_raw_rows() {
|
|
462
|
+
local dir state out expected actual annotation_count outside perl_bin
|
|
463
|
+
dir=$(make_case enrichment)
|
|
464
|
+
state="$dir/state"
|
|
465
|
+
out="$dir/drain.out"
|
|
466
|
+
expected="$dir/expected.out"
|
|
467
|
+
actual="$dir/actual.out"
|
|
468
|
+
outside="$dir/outside-secret"
|
|
469
|
+
printf 'working: first\n\ndone: latest event\n' > "$state/task.status"
|
|
470
|
+
printf 'working: old turn-end context\n' > "$state/turn-only.status"
|
|
471
|
+
printf 'must-not-be-read\n' > "$outside"
|
|
472
|
+
ln -s "$outside" "$state/escape.status"
|
|
473
|
+
perl_bin=$(command -v perl) || fail "perl is required for safe status reads"
|
|
474
|
+
cat > "$dir/fakebin/perl" <<'SH'
|
|
475
|
+
#!/usr/bin/env bash
|
|
476
|
+
if [ "${1:-}" = -MFcntl=:DEFAULT ]; then
|
|
477
|
+
for arg in "$@"; do
|
|
478
|
+
if [ "$arg" = "${FM_WAKE_ENRICH_SWAP_PATH:-}" ]; then
|
|
479
|
+
rm -f "$arg"
|
|
480
|
+
ln -s "$FM_WAKE_ENRICH_SWAP_TARGET" "$arg"
|
|
481
|
+
break
|
|
482
|
+
fi
|
|
483
|
+
done
|
|
484
|
+
fi
|
|
485
|
+
exec "$FM_WAKE_ENRICH_REAL_PERL" "$@"
|
|
486
|
+
SH
|
|
487
|
+
chmod +x "$dir/fakebin/perl"
|
|
488
|
+
|
|
489
|
+
append_wake "$state" signal task.status "signal: $outside" || fail "direct status wake append failed"
|
|
490
|
+
append_wake "$state" signal task.turn-ended "signal: $outside" || fail "coalesced turn-end wake append failed"
|
|
491
|
+
append_wake "$state" signal turn-only.turn-ended "signal: $outside" || fail "bare turn-end wake append failed"
|
|
492
|
+
append_wake "$state" signal escape.status "signal: $outside" || fail "symlink status wake append failed"
|
|
493
|
+
append_wake "$state" signal arbitrary-key "signal: $outside" || fail "non-status signal wake append failed"
|
|
494
|
+
append_wake "$state" check task.check.sh "check: complete payload" || fail "check wake append failed"
|
|
495
|
+
append_wake "$state" stale test:fm-task "stale: test:fm-task" || fail "stale wake append failed"
|
|
496
|
+
append_wake "$state" heartbeat heartbeat heartbeat || fail "heartbeat wake append failed"
|
|
497
|
+
|
|
498
|
+
FM_STATE_OVERRIDE="$state" bash -c '. "$1"; fm_wake_print_deduped "$2"' _ \
|
|
499
|
+
"$ROOT/bin/fm-wake-lib.sh" "$state/.wake-queue" > "$expected"
|
|
500
|
+
PATH="$dir/fakebin:$PATH" FM_STATE_OVERRIDE="$state" FM_WAKE_ENRICH_SWAP_PATH="$state/task.status" \
|
|
501
|
+
FM_WAKE_ENRICH_SWAP_TARGET="$outside" FM_WAKE_ENRICH_REAL_PERL="$perl_bin" "$DRAIN" > "$out" \
|
|
502
|
+
|| fail "structural enrichment drain failed"
|
|
503
|
+
awk -F '\t' 'NF == 5 { print }' "$out" > "$actual"
|
|
504
|
+
cmp -s "$expected" "$actual" || fail "enrichment changed or reordered an authoritative raw row"
|
|
505
|
+
|
|
506
|
+
annotation_count=$(grep -c '^wake annotation:' "$out" || true)
|
|
507
|
+
[ "$annotation_count" -eq 1 ] || fail "expected only the unreadable-race-safe status annotation, got $annotation_count"
|
|
508
|
+
if grep -E '^wake annotation:.*: task\.status:' "$out" >/dev/null; then
|
|
509
|
+
fail "replaced status file produced an annotation"
|
|
510
|
+
fi
|
|
511
|
+
grep -F 'latest wake-EVENT observed at drain, not current state; historical / not necessarily the triggering event: turn-only.status:' "$out" >/dev/null \
|
|
512
|
+
|| fail "bare turn-end mapping did not carry the historical warning"
|
|
513
|
+
if grep -F 'must-not-be-read' "$out" >/dev/null; then
|
|
514
|
+
fail "drain trusted a payload path or followed an out-of-state status symlink"
|
|
515
|
+
fi
|
|
516
|
+
pass "structural signal enrichment is separate, deduped, home-local, and tier-zero for other wakes"
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
test_enrichment_preserves_all_unread_lines_and_status_file_failures() {
|
|
520
|
+
local dir state out i raw_count expected
|
|
521
|
+
dir=$(make_case complete-enrichment)
|
|
522
|
+
state="$dir/state"
|
|
523
|
+
out="$dir/drain.out"
|
|
524
|
+
awk 'BEGIN { printf "done: "; for (i = 0; i < 20000; i++) printf "x"; printf "\n" }' > "$state/huge.status"
|
|
525
|
+
append_wake "$state" signal huge.status "signal: huge" || fail "huge status wake append failed"
|
|
526
|
+
i=1
|
|
527
|
+
while [ "$i" -le 8 ]; do
|
|
528
|
+
awk -v n="$i" 'BEGIN { printf "working-%d: ", n; for (j = 0; j < 3000; j++) printf "y"; printf "\n" }' > "$state/many-$i.status"
|
|
529
|
+
append_wake "$state" signal "many-$i.status" "signal: many-$i" || fail "many-status wake append failed"
|
|
530
|
+
i=$((i + 1))
|
|
531
|
+
done
|
|
532
|
+
: > "$state/empty.status"
|
|
533
|
+
append_wake "$state" signal empty.status "signal: empty" || fail "empty status wake append failed"
|
|
534
|
+
append_wake "$state" signal missing.status "signal: missing" || fail "missing status wake append failed"
|
|
535
|
+
mkdir "$state/malformed.status"
|
|
536
|
+
append_wake "$state" signal malformed.status "signal: malformed" || fail "malformed status wake append failed"
|
|
537
|
+
printf 'done: unreadable\n' > "$state/unreadable.status"
|
|
538
|
+
chmod 000 "$state/unreadable.status"
|
|
539
|
+
append_wake "$state" signal unreadable.status "signal: unreadable" || fail "unreadable status wake append failed"
|
|
540
|
+
|
|
541
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" \
|
|
542
|
+
|| fail "complete enrichment drain failed"
|
|
543
|
+
raw_count=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$out")
|
|
544
|
+
[ "$raw_count" -eq 13 ] || fail "missing, unreadable, malformed, empty, or oversized status input hid a raw row"
|
|
545
|
+
|
|
546
|
+
expected="wake annotation: latest wake-EVENT observed at drain, not current state: huge.status: $(cat "$state/huge.status")"
|
|
547
|
+
grep -Fx "$expected" "$out" >/dev/null \
|
|
548
|
+
|| fail "the oversized unread status line was truncated or omitted"
|
|
549
|
+
i=1
|
|
550
|
+
while [ "$i" -le 8 ]; do
|
|
551
|
+
expected="wake annotation: latest wake-EVENT observed at drain, not current state: many-$i.status: $(cat "$state/many-$i.status")"
|
|
552
|
+
grep -Fx "$expected" "$out" >/dev/null \
|
|
553
|
+
|| fail "readable status many-$i was truncated or omitted"
|
|
554
|
+
i=$((i + 1))
|
|
555
|
+
done
|
|
556
|
+
if grep -E '^wake annotation:.*(truncated|omitted)' "$out" >/dev/null; then
|
|
557
|
+
fail "complete unread annotation output still reported dropped content"
|
|
558
|
+
fi
|
|
559
|
+
if grep -E ': (empty|missing|malformed|unreadable)\.status:' "$out" >/dev/null; then
|
|
560
|
+
fail "missing, unreadable, malformed, or empty status file produced an annotation"
|
|
561
|
+
fi
|
|
562
|
+
pass "every readable unread status line is annotated in full while invalid status files preserve their raw wakes"
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
wait_for_file_text() { # <file> <fixed-text>
|
|
566
|
+
local file=$1 expected=$2 i=0
|
|
567
|
+
while [ "$i" -lt 100 ]; do
|
|
568
|
+
grep -F "$expected" "$file" >/dev/null 2>&1 && return 0
|
|
569
|
+
sleep 0.05
|
|
570
|
+
i=$((i + 1))
|
|
571
|
+
done
|
|
572
|
+
return 1
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
test_slow_annotation_does_not_block_append_and_deleted_file_fails_open() {
|
|
576
|
+
local dir state out1 out2 pid
|
|
577
|
+
dir=$(make_case slow-annotation)
|
|
578
|
+
state="$dir/state"
|
|
579
|
+
out1="$dir/drain-one.out"
|
|
580
|
+
out2="$dir/drain-two.out"
|
|
581
|
+
printf 'done: disappears before bounded read\n' > "$state/slow.status"
|
|
582
|
+
append_wake "$state" signal slow.status "signal: slow" || fail "slow status wake append failed"
|
|
583
|
+
|
|
584
|
+
FM_STATE_OVERRIDE="$state" FM_WAKE_ENRICH_TEST_DELAY=3 "$DRAIN" > "$out1" &
|
|
585
|
+
pid=$!
|
|
586
|
+
wait_for_file_text "$out1" "$(printf '\tsignal\tslow.status\t')" \
|
|
587
|
+
|| { kill "$pid" 2>/dev/null || true; fail "slow drain did not commit its raw row"; }
|
|
588
|
+
printf 'done: appended while first drain annotates\n' > "$state/next.status"
|
|
589
|
+
append_wake "$state" signal next.status "signal: next" || fail "append blocked or failed during annotation"
|
|
590
|
+
kill -0 "$pid" 2>/dev/null || fail "slow annotation finished before the concurrent append proved lock independence"
|
|
591
|
+
rm -f "$state/slow.status"
|
|
592
|
+
wait "$pid" || fail "deleted status file made the committed drain fail"
|
|
593
|
+
grep -F "$(printf '\tsignal\tslow.status\t')" "$out1" >/dev/null || fail "deleted status file hid the committed raw row"
|
|
594
|
+
if grep -F ': slow.status:' "$out1" >/dev/null; then
|
|
595
|
+
fail "status deleted during annotation still produced an annotation"
|
|
596
|
+
fi
|
|
597
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out2" || fail "follow-up drain after concurrent append failed"
|
|
598
|
+
grep -F "$(printf '\tsignal\tnext.status\t')" "$out2" >/dev/null || fail "concurrent append was not left for the next drain"
|
|
599
|
+
pass "slow annotation releases the append lock and a deleted status file fails open"
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
# Per-actor consume (docs/watcher-continuity.md "Per-actor acknowledgement").
|
|
603
|
+
# Drives a MIXED queue snapshot - an unacked main-only check row alongside two
|
|
604
|
+
# task-local rows the Pi supervision branch was granted - directly against
|
|
605
|
+
# the real bin/fm-wake-drain.sh, independent of the Pi SDK. This is the core
|
|
606
|
+
# safety property: a scoped actor's ack must never remove a row outside its
|
|
607
|
+
# own eligible snapshot, no matter that row's sequence number relative to
|
|
608
|
+
# what the actor presents or acks itself. Do not regress it.
|
|
609
|
+
test_branch_actor_scoped_ack_never_swallows_a_main_owned_row() {
|
|
610
|
+
local dir state out err sequence generation count
|
|
611
|
+
dir=$(make_case actor-scope)
|
|
612
|
+
state="$dir/state"
|
|
613
|
+
|
|
614
|
+
append_wake "$state" check "some-poll.check.sh" "check: some-poll.check.sh: merged" \
|
|
615
|
+
|| fail "main-only append failed"
|
|
616
|
+
append_wake "$state" signal "task-a.status" "signal: task-a" || fail "signal append failed"
|
|
617
|
+
append_wake "$state" stale "fm-window" "stale: fm-window" || fail "stale append failed"
|
|
618
|
+
|
|
619
|
+
# The extension's own job (fm-branch-dispatch.ts) is granting exactly the
|
|
620
|
+
# two task-local rows; this test drives the bash consume contract those
|
|
621
|
+
# sequence numbers gate, independent of the Pi SDK.
|
|
622
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" activate "$$" actor-scope || fail "branch owner activation failed"
|
|
623
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" publish actor-scope 2 3 || fail "branch grant publication failed"
|
|
624
|
+
|
|
625
|
+
out="$dir/branch-drain.out"
|
|
626
|
+
err="$dir/branch-drain.err"
|
|
627
|
+
FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" > "$out" 2> "$err" \
|
|
628
|
+
|| fail "branch-scoped drain failed: $(cat "$err")"
|
|
629
|
+
grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$out" || fail "branch drain omitted its eligible signal row"
|
|
630
|
+
grep -Fq "$(printf '\tstale\tfm-window\t')" "$out" || fail "branch drain omitted its eligible stale row"
|
|
631
|
+
grep -Fq "$(printf '\tcheck\tsome-poll.check.sh\t')" "$out" && fail "branch drain presented the main-owned row"
|
|
632
|
+
|
|
633
|
+
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")
|
|
634
|
+
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")
|
|
635
|
+
[ -n "$sequence" ] && [ -n "$generation" ] || fail "branch drain omitted its acknowledgement boundary"
|
|
636
|
+
[ "$sequence" -eq 3 ] || fail "branch ack cutoff must be the max ELIGIBLE seq (3), got $sequence"
|
|
637
|
+
|
|
638
|
+
FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
639
|
+
|| fail "branch-scoped ack failed"
|
|
640
|
+
|
|
641
|
+
# The core no-swallow property: the main-only row - seq 1, BELOW the
|
|
642
|
+
# branch's own ack cutoff of 3 - must still be there.
|
|
643
|
+
grep -Fq "$(printf '\tcheck\tsome-poll.check.sh\t')" "$state/.wake-queue" \
|
|
644
|
+
|| fail "branch's scoped ack swallowed a main-owned row below its own cutoff"
|
|
645
|
+
grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$state/.wake-queue" \
|
|
646
|
+
&& fail "branch's own eligible signal row was not consumed"
|
|
647
|
+
grep -Fq "$(printf '\tstale\tfm-window\t')" "$state/.wake-queue" \
|
|
648
|
+
&& fail "branch's own eligible stale row was not consumed"
|
|
649
|
+
|
|
650
|
+
# Main's own later, ordinary (unscoped) drain sees exactly what remains.
|
|
651
|
+
out="$dir/main-drain.out"
|
|
652
|
+
err="$dir/main-drain.err"
|
|
653
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" 2> "$err" || fail "main drain failed: $(cat "$err")"
|
|
654
|
+
count=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$out")
|
|
655
|
+
[ "$count" -eq 1 ] || fail "main's later drain should see exactly the one remaining main-owned row: $(cat "$out")"
|
|
656
|
+
grep -Fq "$(printf '\tcheck\tsome-poll.check.sh\t')" "$out" || fail "main's later drain lost the main-owned row"
|
|
657
|
+
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")
|
|
658
|
+
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")
|
|
659
|
+
[ -n "$sequence" ] && [ -n "$generation" ] || fail "main's drain omitted its acknowledgement boundary"
|
|
660
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
661
|
+
|| fail "main's ack failed"
|
|
662
|
+
[ ! -s "$state/.wake-queue" ] || fail "the main-owned row survived main's own ack"
|
|
663
|
+
|
|
664
|
+
pass "a branch-actor scoped ack never swallows an unacked main-owned row, and main's later drain sees exactly what remains"
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
test_main_drain_excludes_rows_already_granted_to_branch() {
|
|
668
|
+
local dir state out err sequence generation
|
|
669
|
+
dir=$(make_case main-excludes-branch-grant)
|
|
670
|
+
state="$dir/state"
|
|
671
|
+
|
|
672
|
+
append_wake "$state" check "some-poll.check.sh" "check: some-poll.check.sh: merged" \
|
|
673
|
+
|| fail "main-only append failed"
|
|
674
|
+
append_wake "$state" signal "task-a.status" "signal: task-a" || fail "signal append failed"
|
|
675
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" activate "$$" main-excludes || fail "branch owner activation failed"
|
|
676
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" publish main-excludes 2 || fail "branch grant publication failed"
|
|
677
|
+
|
|
678
|
+
out="$dir/main-drain.out"
|
|
679
|
+
err="$dir/main-drain.err"
|
|
680
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" 2> "$err" || fail "main drain failed: $(cat "$err")"
|
|
681
|
+
grep -Fq "$(printf '\tcheck\tsome-poll.check.sh\t')" "$out" || fail "main drain omitted its main-owned row"
|
|
682
|
+
! grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$out" || fail "main drain presented a branch-granted row"
|
|
683
|
+
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")
|
|
684
|
+
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")
|
|
685
|
+
[ "$sequence" = 1 ] && [ -n "$generation" ] || fail "main acknowledgement did not bind only its presented row"
|
|
686
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
687
|
+
|| fail "main acknowledgement failed"
|
|
688
|
+
grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$state/.wake-queue" \
|
|
689
|
+
|| fail "main acknowledgement consumed the branch-granted row"
|
|
690
|
+
|
|
691
|
+
out="$dir/branch-drain.out"
|
|
692
|
+
err="$dir/branch-drain.err"
|
|
693
|
+
FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" > "$out" 2> "$err" \
|
|
694
|
+
|| fail "branch drain failed: $(cat "$err")"
|
|
695
|
+
grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$out" || fail "branch lost its granted row"
|
|
696
|
+
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")
|
|
697
|
+
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")
|
|
698
|
+
FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
699
|
+
|| fail "branch acknowledgement failed"
|
|
700
|
+
[ ! -s "$state/.wake-queue" ] || fail "branch acknowledgement left its handled row queued"
|
|
701
|
+
[ ! -e "$state/.branch-eligible-rows" ] || fail "branch acknowledgement retained its completed grant"
|
|
702
|
+
|
|
703
|
+
pass "main drain and acknowledgement exclude an active branch grant"
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
test_branch_grant_refuses_rows_already_claimed_by_main() {
|
|
707
|
+
local dir state rc
|
|
708
|
+
dir=$(make_case branch-refuses-main-claim)
|
|
709
|
+
state="$dir/state"
|
|
710
|
+
|
|
711
|
+
append_wake "$state" signal "task-a.status" "signal: task-a" || fail "signal append failed"
|
|
712
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/main.out" 2> "$dir/main.err" \
|
|
713
|
+
|| fail "main presentation failed"
|
|
714
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" activate "$$" branch-refuses || fail "branch owner activation failed"
|
|
715
|
+
rc=0
|
|
716
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" publish branch-refuses 1 || rc=$?
|
|
717
|
+
[ "$rc" -eq 3 ] || fail "branch grant did not report the existing main ownership: rc=$rc"
|
|
718
|
+
[ ! -e "$state/.branch-eligible-rows" ] || fail "refused branch grant published an ownership snapshot"
|
|
719
|
+
grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$dir/main.out" \
|
|
720
|
+
|| fail "the main owner did not present its claimed row"
|
|
721
|
+
|
|
722
|
+
pass "branch grant cannot take a row already claimed by main"
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
test_actor_filter_precedes_same_key_deduplication() {
|
|
726
|
+
local dir state main_sequence main_generation branch_sequence branch_generation
|
|
727
|
+
dir=$(make_case actor-dedup-order)
|
|
728
|
+
state="$dir/state"
|
|
729
|
+
|
|
730
|
+
append_wake "$state" signal "task-a.status" "signal: branch version" || fail "branch row append failed"
|
|
731
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" activate "$$" actor-dedup || fail "branch owner activation failed"
|
|
732
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" publish actor-dedup 1 || fail "branch grant publication failed"
|
|
733
|
+
append_wake "$state" signal "task-a.status" "signal: main version" || fail "main row append failed"
|
|
734
|
+
|
|
735
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/main.out" 2> "$dir/main.err" || fail "main drain failed"
|
|
736
|
+
[ "$(awk -F '\t' '$3 == "signal" { print $2 }' "$dir/main.out")" = 2 ] \
|
|
737
|
+
|| fail "main did not present its same-key claimed row"
|
|
738
|
+
FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" > "$dir/branch.out" 2> "$dir/branch.err" \
|
|
739
|
+
|| fail "branch drain failed"
|
|
740
|
+
[ "$(awk -F '\t' '$3 == "signal" { print $2 }' "$dir/branch.out")" = 1 ] \
|
|
741
|
+
|| fail "global deduplication hid the branch's older same-key row"
|
|
742
|
+
|
|
743
|
+
main_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/main.err")
|
|
744
|
+
main_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/main.err")
|
|
745
|
+
branch_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/branch.err")
|
|
746
|
+
branch_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/branch.err")
|
|
747
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$main_sequence" --recovery-generation "$main_generation" \
|
|
748
|
+
|| fail "main same-key acknowledgement failed"
|
|
749
|
+
FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" --ack-through "$branch_sequence" --recovery-generation "$branch_generation" \
|
|
750
|
+
|| fail "branch same-key acknowledgement failed"
|
|
751
|
+
[ ! -s "$state/.wake-queue" ] || fail "same-key actor rows remained stranded"
|
|
752
|
+
|
|
753
|
+
pass "actor ownership filtering precedes same-key deduplication"
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
test_main_reclaims_a_grant_whose_branch_owner_exited() {
|
|
757
|
+
local dir state owner sequence generation
|
|
758
|
+
dir=$(make_case stale-branch-owner)
|
|
759
|
+
state="$dir/state"
|
|
760
|
+
|
|
761
|
+
append_wake "$state" signal "task-a.status" "signal: task-a" || fail "signal append failed"
|
|
762
|
+
sleep 30 &
|
|
763
|
+
owner=$!
|
|
764
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" activate "$owner" stale-owner || {
|
|
765
|
+
kill "$owner" 2>/dev/null || true
|
|
766
|
+
fail "branch owner activation failed"
|
|
767
|
+
}
|
|
768
|
+
FM_STATE_OVERRIDE="$state" "$GRANT" publish stale-owner 1 || {
|
|
769
|
+
kill "$owner" 2>/dev/null || true
|
|
770
|
+
fail "branch grant publication failed"
|
|
771
|
+
}
|
|
772
|
+
kill "$owner" 2>/dev/null || true
|
|
773
|
+
wait "$owner" 2>/dev/null || true
|
|
774
|
+
|
|
775
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/main.out" 2> "$dir/main.err" || fail "main reclaim drain failed"
|
|
776
|
+
grep -Fq "$(printf '\tsignal\ttask-a.status\t')" "$dir/main.out" \
|
|
777
|
+
|| fail "main did not reclaim the dead branch owner's row"
|
|
778
|
+
[ ! -e "$state/.branch-eligible-rows" ] && [ ! -e "$state/.branch-eligible-owner" ] \
|
|
779
|
+
|| fail "dead branch ownership evidence survived reclaim"
|
|
780
|
+
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/main.err")
|
|
781
|
+
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/main.err")
|
|
782
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
783
|
+
|| fail "reclaimed row acknowledgement failed"
|
|
784
|
+
[ ! -s "$state/.wake-queue" ] || fail "reclaimed branch row remained queued"
|
|
785
|
+
|
|
786
|
+
pass "main reclaims rows granted to an exited branch owner"
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
# A branch-actor drain or ack without a snapshot is a wiring bug, never
|
|
790
|
+
# "nothing eligible": it must refuse loudly rather than silently draining or
|
|
791
|
+
# acking nothing.
|
|
792
|
+
test_branch_actor_without_eligible_snapshot_refuses() {
|
|
793
|
+
local dir state
|
|
794
|
+
dir=$(make_case actor-no-snapshot)
|
|
795
|
+
state="$dir/state"
|
|
796
|
+
append_wake "$state" signal "task-a.status" "signal: task-a" || fail "append failed"
|
|
797
|
+
if FM_STATE_OVERRIDE="$state" FM_SUPERVISION_ACTOR=branch "$DRAIN" >/dev/null 2>"$dir/err"; then
|
|
798
|
+
fail "a branch-actor drain with no eligible-row snapshot must refuse, not silently drain"
|
|
799
|
+
fi
|
|
800
|
+
grep -q "no branch-eligible row snapshot" "$dir/err" || fail "the refusal did not name the missing snapshot: $(cat "$dir/err")"
|
|
801
|
+
[ -s "$state/.wake-queue" ] || fail "the refused drain must leave the queue untouched"
|
|
802
|
+
pass "a branch-actor drain with no eligible-row snapshot refuses loudly instead of draining nothing"
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
test_wake_publish_requires_atomic_recovery_evidence() {
|
|
806
|
+
local dir state fakebin real_mv rc out
|
|
807
|
+
dir=$(make_case wake-publish-recovery-evidence)
|
|
808
|
+
state="$dir/state"
|
|
809
|
+
fakebin="$dir/fakebin"
|
|
810
|
+
real_mv=$(command -v mv) || fail "could not locate mv for recovery publication fixture"
|
|
811
|
+
printf 'pending:handling:existing\n' > "$state/.watcher-down"
|
|
812
|
+
cat > "$fakebin/mv" <<'SH'
|
|
813
|
+
#!/usr/bin/env bash
|
|
814
|
+
last=${!#}
|
|
815
|
+
if [ "$last" = "${FM_TEST_PUBLISH_MARKER:-}" ]; then
|
|
816
|
+
exit 1
|
|
817
|
+
fi
|
|
818
|
+
exec "$FM_TEST_REAL_MV" "$@"
|
|
819
|
+
SH
|
|
820
|
+
chmod +x "$fakebin/mv"
|
|
821
|
+
|
|
822
|
+
set +e
|
|
823
|
+
PATH="$fakebin:$PATH" FM_TEST_REAL_MV="$real_mv" FM_TEST_PUBLISH_MARKER="$state/.watcher-down" \
|
|
824
|
+
append_wake "$state" signal task.status "signal: publish failure"
|
|
825
|
+
rc=$?
|
|
826
|
+
set -e
|
|
827
|
+
[ "$rc" -ne 0 ] || fail "recovery publication failure allowed wake append to succeed"
|
|
828
|
+
[ "$(cat "$state/.watcher-down")" = 'pending:handling:existing' ] \
|
|
829
|
+
|| fail "failed atomic publication erased existing recovery evidence"
|
|
830
|
+
[ ! -s "$state/.wake-queue" ] \
|
|
831
|
+
|| fail "wake became durable before its recovery evidence"
|
|
832
|
+
|
|
833
|
+
PATH="$fakebin:$PATH" FM_TEST_REAL_MV="$real_mv" \
|
|
834
|
+
append_wake "$state" signal task.status "signal: recovered retry" \
|
|
835
|
+
|| fail "wake retry did not publish durable recovery evidence"
|
|
836
|
+
out="$dir/drain.out"
|
|
837
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" \
|
|
838
|
+
|| fail "wake retry did not drain"
|
|
839
|
+
grep -F "signal: recovered retry" "$out" >/dev/null \
|
|
840
|
+
|| fail "retried wake was not recovered by the durable drain"
|
|
841
|
+
pass "wake append publishes atomic recovery evidence before durable rows"
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
test_legacy_generationless_wake_is_adopted() {
|
|
845
|
+
local dir state row sequence generation
|
|
846
|
+
dir=$(make_case legacy-generationless-wake)
|
|
847
|
+
state="$dir/state"
|
|
848
|
+
row=$(printf '1700000000\t7\tcheck\tlegacy-process-event\tcheck: legacy process-event')
|
|
849
|
+
printf '%s\n' "$row" > "$state/.wake-queue"
|
|
850
|
+
|
|
851
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/first.out" 2> "$dir/first.err" \
|
|
852
|
+
|| fail "generation-less legacy wake could not be adopted"
|
|
853
|
+
grep -F "$row" "$dir/first.out" >/dev/null \
|
|
854
|
+
|| fail "adopted legacy wake was not presented"
|
|
855
|
+
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/first.err")
|
|
856
|
+
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/first.err")
|
|
857
|
+
[ "$sequence" = 7 ] && [ -n "$generation" ] \
|
|
858
|
+
|| fail "legacy wake adoption omitted its generation-bound acknowledgement"
|
|
859
|
+
[ "$(cat "$state/.watcher-down" 2>/dev/null || true)" = "pending:handling:$generation" ] \
|
|
860
|
+
|| fail "legacy wake was not adopted into durable handling recovery"
|
|
861
|
+
|
|
862
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/replay.out" 2> "$dir/replay.err" \
|
|
863
|
+
|| fail "unacknowledged adopted wake could not be re-drained"
|
|
864
|
+
grep -F "$row" "$dir/replay.out" >/dev/null \
|
|
865
|
+
|| fail "unacknowledged adopted wake was lost"
|
|
866
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" \
|
|
867
|
+
--recovery-generation "$generation" \
|
|
868
|
+
|| fail "adopted legacy wake could not be acknowledged"
|
|
869
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledged legacy wake remained queued"
|
|
870
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/after-ack.out" 2> "$dir/after-ack.err" \
|
|
871
|
+
|| fail "post-acknowledgement legacy drain failed"
|
|
872
|
+
! grep -F "$row" "$dir/after-ack.out" >/dev/null \
|
|
873
|
+
|| fail "acknowledged legacy wake was consumed more than once"
|
|
874
|
+
pass "wake drain: generation-less legacy wakes are adopted and acknowledged"
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
# Pin the recovery acknowledgement contract from docs/watcher-continuity.md at
|
|
878
|
+
# the queue-library boundary.
|
|
879
|
+
test_stale_recovery_generation_cannot_touch_a_newer_episode() {
|
|
880
|
+
local dir state first_err replay_err sequence generation handling_marker
|
|
881
|
+
local newer_marker newer_sequence newer_generation rc
|
|
882
|
+
dir=$(make_case stale-recovery-generation)
|
|
883
|
+
state="$dir/state"
|
|
884
|
+
|
|
885
|
+
append_wake "$state" check first 'check: first generation' \
|
|
886
|
+
|| fail "first generation wake append failed"
|
|
887
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/first.out" 2> "$dir/first.err" \
|
|
888
|
+
|| fail "first generation drain failed"
|
|
889
|
+
first_err="$dir/first.err"
|
|
890
|
+
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' "$first_err")
|
|
891
|
+
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' "$first_err")
|
|
892
|
+
[ -n "$sequence" ] && [ -n "$generation" ] \
|
|
893
|
+
|| fail "first drain did not emit a generation-bound acknowledgement"
|
|
894
|
+
|
|
895
|
+
append_wake "$state" check second 'check: same episode' \
|
|
896
|
+
|| fail "first same-episode wake append failed"
|
|
897
|
+
append_wake "$state" check third 'check: same episode again' \
|
|
898
|
+
|| fail "second same-episode wake append failed"
|
|
899
|
+
handling_marker=$(cat "$state/.watcher-down")
|
|
900
|
+
[ "${handling_marker##*:}" = "$generation" ] \
|
|
901
|
+
|| fail "repeated publications replaced the outstanding recovery generation"
|
|
902
|
+
|
|
903
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" \
|
|
904
|
+
--recovery-generation "$generation" > "$dir/handled-ack.out" 2> "$dir/handled-ack.err" \
|
|
905
|
+
|| fail "a publication during handling invalidated the printed acknowledgement"
|
|
906
|
+
! grep "$(printf '\tcheck\tfirst\t')" "$state/.wake-queue" >/dev/null \
|
|
907
|
+
|| fail "the handled row was not consumed"
|
|
908
|
+
grep "$(printf '\tcheck\tsecond\t')" "$state/.wake-queue" >/dev/null \
|
|
909
|
+
|| fail "a row above the acknowledged sequence was consumed"
|
|
910
|
+
grep "$(printf '\tcheck\tthird\t')" "$state/.wake-queue" >/dev/null \
|
|
911
|
+
|| fail "the second row above the acknowledged sequence was consumed"
|
|
912
|
+
case "$(cat "$state/.watcher-down")" in
|
|
913
|
+
pending:*) ;;
|
|
914
|
+
*) fail "an episode with rows still queued was retired" ;;
|
|
915
|
+
esac
|
|
916
|
+
|
|
917
|
+
# Retire that episode, then let a genuinely newer one open.
|
|
918
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/replay.out" 2> "$dir/replay.err" \
|
|
919
|
+
|| fail "remaining wake could not be re-drained"
|
|
920
|
+
replay_err="$dir/replay.err"
|
|
921
|
+
grep "$(printf '\tcheck\tsecond\t')" "$dir/replay.out" >/dev/null \
|
|
922
|
+
|| fail "remaining wake did not re-surface"
|
|
923
|
+
grep "$(printf '\tcheck\tthird\t')" "$dir/replay.out" >/dev/null \
|
|
924
|
+
|| fail "second remaining wake did not re-surface"
|
|
925
|
+
newer_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' "$replay_err")
|
|
926
|
+
newer_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' "$replay_err")
|
|
927
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$newer_sequence" \
|
|
928
|
+
--recovery-generation "$newer_generation" \
|
|
929
|
+
|| fail "the handled episode could not be acknowledged"
|
|
930
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledgement left durable wakes queued"
|
|
931
|
+
|
|
932
|
+
append_wake "$state" check fourth 'check: newer recovery generation' \
|
|
933
|
+
|| fail "newer generation wake append failed"
|
|
934
|
+
newer_marker=$(cat "$state/.watcher-down")
|
|
935
|
+
[ "${newer_marker##*:}" != "$generation" ] \
|
|
936
|
+
|| fail "a retired episode did not open a new recovery generation"
|
|
937
|
+
|
|
938
|
+
rc=0
|
|
939
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" \
|
|
940
|
+
--recovery-generation "$generation" > "$dir/stale-ack.out" 2> "$dir/stale-ack.err" || rc=$?
|
|
941
|
+
[ "$rc" -eq 0 ] \
|
|
942
|
+
|| fail "a stale acknowledgement failed instead of degrading safely: $(cat "$dir/stale-ack.err")"
|
|
943
|
+
if ! grep -F 'WAKE_ACK_REQUIRED' "$dir/stale-ack.err" >/dev/null \
|
|
944
|
+
|| ! grep -F 're-run' "$dir/stale-ack.err" >/dev/null; then
|
|
945
|
+
fail "a stale acknowledgement did not name its own remedy: $(cat "$dir/stale-ack.err")"
|
|
946
|
+
fi
|
|
947
|
+
[ "$(cat "$state/.watcher-down")" = "$newer_marker" ] \
|
|
948
|
+
|| fail "a stale acknowledgement retired the newer recovery episode"
|
|
949
|
+
grep "$(printf '\tcheck\tfourth\t')" "$state/.wake-queue" >/dev/null \
|
|
950
|
+
|| fail "a stale acknowledgement consumed the newer durable wake"
|
|
951
|
+
pass "wake drain: a stale acknowledgement cannot retire or consume a newer recovery episode"
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
test_recovery_ack_failure_is_reported() {
|
|
955
|
+
local dir state fakebin real_mv rc generation
|
|
956
|
+
dir=$(make_case recovery-ack-failure)
|
|
957
|
+
state="$dir/state"
|
|
958
|
+
fakebin="$dir/fakebin"
|
|
959
|
+
real_mv=$(command -v mv) || fail "could not locate mv for recovery acknowledgement fixture"
|
|
960
|
+
printf 'pending:handling:fixture\n' > "$state/.watcher-down"
|
|
961
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$dir/initial.out" 2> "$dir/initial.err" \
|
|
962
|
+
|| fail "initial recovery drain failed"
|
|
963
|
+
generation=$(sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through 0 --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' "$dir/initial.err")
|
|
964
|
+
[ -n "$generation" ] || fail "initial recovery drain omitted its generation"
|
|
965
|
+
cat > "$fakebin/mv" <<'SH'
|
|
966
|
+
#!/usr/bin/env bash
|
|
967
|
+
last=${!#}
|
|
968
|
+
if [ "$last" = "${FM_TEST_ACK_MARKER:-}" ]; then
|
|
969
|
+
exit 1
|
|
970
|
+
fi
|
|
971
|
+
exec "$FM_TEST_REAL_MV" "$@"
|
|
972
|
+
SH
|
|
973
|
+
chmod +x "$fakebin/mv"
|
|
974
|
+
|
|
975
|
+
set +e
|
|
976
|
+
PATH="$fakebin:$PATH" FM_TEST_REAL_MV="$real_mv" FM_TEST_ACK_MARKER="$state/.watcher-down" \
|
|
977
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through 0 --recovery-generation "$generation" \
|
|
978
|
+
> "$dir/drain.out" 2> "$dir/drain.err"
|
|
979
|
+
rc=$?
|
|
980
|
+
set -e
|
|
981
|
+
[ "$rc" -ne 0 ] || fail "recovery acknowledgement failure was reported as success"
|
|
982
|
+
grep -F 'recovery episode could not be retired safely' "$dir/drain.err" >/dev/null \
|
|
983
|
+
|| fail "recovery acknowledgement failure had no explicit diagnostic"
|
|
984
|
+
grep -F 'WAKE_ACK_REQUIRED' "$dir/drain.err" >/dev/null \
|
|
985
|
+
|| fail "recovery acknowledgement failure did not name its own remedy"
|
|
986
|
+
[ "$(cat "$state/.watcher-down")" = "pending:handling:$generation" ] \
|
|
987
|
+
|| fail "failed acknowledgement corrupted the pending recovery marker"
|
|
988
|
+
|
|
989
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through 0 --recovery-generation "$generation" \
|
|
990
|
+
> "$dir/retry.out" 2> "$dir/retry.err" \
|
|
991
|
+
|| fail "recovery acknowledgement did not succeed on retry"
|
|
992
|
+
[ "$(cat "$state/.watcher-down")" = "acked:handling:$generation" ] \
|
|
993
|
+
|| fail "successful retry did not acknowledge pending recovery state"
|
|
994
|
+
pass "wake drain: recovery acknowledgement failures are explicit and retryable"
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
test_interruption_before_and_after_raw_commit() {
|
|
998
|
+
local dir state before_out after_out replay_out empty_out pid rc count i sequence generation
|
|
999
|
+
dir=$(make_case interruption)
|
|
1000
|
+
state="$dir/state"
|
|
1001
|
+
before_out="$dir/before.out"
|
|
1002
|
+
after_out="$dir/after.out"
|
|
1003
|
+
replay_out="$dir/replay.out"
|
|
1004
|
+
empty_out="$dir/empty.out"
|
|
1005
|
+
printf 'done: interruption fixture\n' > "$state/task.status"
|
|
1006
|
+
append_wake "$state" signal task.status "signal: task" || fail "pre-commit interruption wake append failed"
|
|
1007
|
+
|
|
1008
|
+
FM_STATE_OVERRIDE="$state" FM_WAKE_DRAIN_TEST_DELAY_BEFORE_COMMIT=5 "$DRAIN" > "$before_out" &
|
|
1009
|
+
pid=$!
|
|
1010
|
+
i=0
|
|
1011
|
+
while [ "$i" -lt 100 ] && [ ! -e "$state/.wake-queue.lock" ]; do
|
|
1012
|
+
sleep 0.05
|
|
1013
|
+
i=$((i + 1))
|
|
1014
|
+
done
|
|
1015
|
+
[ -e "$state/.wake-queue.lock" ] || { kill "$pid" 2>/dev/null || true; fail "pre-commit drain never entered its serialized read boundary"; }
|
|
1016
|
+
kill -TERM "$pid" 2>/dev/null || fail "could not interrupt drain before raw commitment"
|
|
1017
|
+
set +e
|
|
1018
|
+
wait "$pid"
|
|
1019
|
+
rc=$?
|
|
1020
|
+
set -e
|
|
1021
|
+
[ "$rc" -ne 0 ] || fail "pre-commit interruption unexpectedly succeeded"
|
|
1022
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$replay_out" 2> "$dir/replay.err" || fail "restored pre-commit wake did not drain"
|
|
1023
|
+
count=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$replay_out")
|
|
1024
|
+
[ "$count" -eq 1 ] || fail "pre-commit interruption lost or duplicated the durable row"
|
|
1025
|
+
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.err")
|
|
1026
|
+
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.err")
|
|
1027
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
1028
|
+
|| fail "pre-commit replay acknowledgement failed"
|
|
1029
|
+
|
|
1030
|
+
append_wake "$state" signal task.status "signal: task after commit" || fail "post-commit interruption wake append failed"
|
|
1031
|
+
FM_STATE_OVERRIDE="$state" FM_WAKE_ENRICH_TEST_DELAY=5 "$DRAIN" > "$after_out" &
|
|
1032
|
+
pid=$!
|
|
1033
|
+
wait_for_file_text "$after_out" "$(printf '\tsignal\ttask.status\t')" \
|
|
1034
|
+
|| { kill "$pid" 2>/dev/null || true; fail "post-commit drain did not print its raw row"; }
|
|
1035
|
+
[ -s "$state/.wake-queue" ] \
|
|
1036
|
+
|| { kill "$pid" 2>/dev/null || true; fail "post-commit drain consumed its raw row before handling acknowledgement"; }
|
|
1037
|
+
kill -TERM "$pid" 2>/dev/null || fail "could not interrupt drain after raw presentation"
|
|
1038
|
+
set +e
|
|
1039
|
+
wait "$pid"
|
|
1040
|
+
set -e
|
|
1041
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$empty_out" 2> "$dir/after-replay.err" \
|
|
1042
|
+
|| fail "drain after post-presentation interruption failed"
|
|
1043
|
+
count=$(awk -F '\t' 'NF == 5 { count++ } END { print count + 0 }' "$empty_out")
|
|
1044
|
+
[ "$count" -eq 1 ] || fail "interrupted handling did not replay its durable row exactly once"
|
|
1045
|
+
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/after-replay.err")
|
|
1046
|
+
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/after-replay.err")
|
|
1047
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" --ack-through "$sequence" --recovery-generation "$generation" \
|
|
1048
|
+
|| fail "post-interruption replay acknowledgement failed"
|
|
1049
|
+
[ ! -s "$state/.wake-queue" ] || fail "acknowledged interrupted wake remained durable"
|
|
1050
|
+
pass "interruptions preserve durable rows until post-handling acknowledgement"
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
# The guarded self-announced status append (fm_wake_status_append_self_announced)
|
|
1054
|
+
# and the seen-signature gate it shares with the watcher's signal scan. Both
|
|
1055
|
+
# directions of the dedup contract are pinned through the real library
|
|
1056
|
+
# functions: a fully announced file plus the home's own bookkeeping close stays
|
|
1057
|
+
# announced (no wake), while ANY unannounced byte - a pending foreign line, a
|
|
1058
|
+
# missing marker, a later different note - reads as wake-worthy.
|
|
1059
|
+
test_self_announced_append_guards() {
|
|
1060
|
+
local dir state status
|
|
1061
|
+
dir=$(make_case self-announced-append)
|
|
1062
|
+
state="$dir/state"
|
|
1063
|
+
status="$state/t.status"
|
|
1064
|
+
|
|
1065
|
+
run_wake_lib() {
|
|
1066
|
+
FM_STATE_OVERRIDE="$state" bash -c '
|
|
1067
|
+
. "$1"; shift; "$@"
|
|
1068
|
+
' _ "$ROOT/bin/fm-wake-lib.sh" "$@"
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
# FIRST status change: a fresh file with no marker is unannounced (wakes).
|
|
1072
|
+
printf 'working: first line\n' > "$status"
|
|
1073
|
+
run_wake_lib fm_wake_signal_seen_current "$state" "$status" \
|
|
1074
|
+
&& fail "a never-announced status file read as already announced"
|
|
1075
|
+
|
|
1076
|
+
# Prime the marker to current (the watcher just surfaced/absorbed everything).
|
|
1077
|
+
prime_status_seen "$state" "$status" || fail "could not prime the seen marker"
|
|
1078
|
+
|
|
1079
|
+
# A self-announced bookkeeping close on a fully announced file is suppressed.
|
|
1080
|
+
run_wake_lib fm_wake_status_append_self_announced "$state" "$status" \
|
|
1081
|
+
'resolved [key=k1]: answered: closed by this home' \
|
|
1082
|
+
|| fail "self-announced append on an announced file was not suppressed (rc=$?)"
|
|
1083
|
+
grep -Fq 'resolved [key=k1]: answered: closed by this home' "$status" \
|
|
1084
|
+
|| fail "the suppressed close was not appended"
|
|
1085
|
+
run_wake_lib fm_wake_signal_seen_current "$state" "$status" \
|
|
1086
|
+
|| fail "the self-announced close left unannounced bytes behind"
|
|
1087
|
+
|
|
1088
|
+
# A later DIFFERENT note from any other writer still wakes.
|
|
1089
|
+
printf 'needs-decision [key=k2]: a new decision\n' >> "$status"
|
|
1090
|
+
run_wake_lib fm_wake_signal_seen_current "$state" "$status" \
|
|
1091
|
+
&& fail "a later different note on the same task read as already announced"
|
|
1092
|
+
|
|
1093
|
+
# With that foreign line pending, a bookkeeping close must NOT advance the
|
|
1094
|
+
# marker over it: the close appends but the file stays wake-worthy.
|
|
1095
|
+
local rc=0
|
|
1096
|
+
run_wake_lib fm_wake_status_append_self_announced "$state" "$status" \
|
|
1097
|
+
'resolved [key=k1]: answered: second close' || rc=$?
|
|
1098
|
+
[ "$rc" -eq 1 ] || fail "a close over pending foreign bytes did not fail toward waking (rc=$rc)"
|
|
1099
|
+
grep -Fq 'resolved [key=k1]: answered: second close' "$status" \
|
|
1100
|
+
|| fail "the fail-toward-waking close was not appended"
|
|
1101
|
+
run_wake_lib fm_wake_signal_seen_current "$state" "$status" \
|
|
1102
|
+
&& fail "a close over pending foreign bytes swallowed the pending wake"
|
|
1103
|
+
|
|
1104
|
+
# UTF-8 close on an announced file: byte accounting must hold for multibyte.
|
|
1105
|
+
prime_status_seen "$state" "$status" || fail "could not re-prime the seen marker"
|
|
1106
|
+
run_wake_lib fm_wake_status_append_self_announced "$state" "$status" \
|
|
1107
|
+
"$(printf 'resolved [key=k2]: answered: caf\xc3\xa9 rentr\xc3\xa9e')" \
|
|
1108
|
+
|| fail "a multibyte self-announced close was not suppressed (rc=$?)"
|
|
1109
|
+
run_wake_lib fm_wake_signal_seen_current "$state" "$status" \
|
|
1110
|
+
|| fail "multibyte byte accounting broke the self-announce guard"
|
|
1111
|
+
|
|
1112
|
+
pass "self-announced appends suppress only their own bytes and fail toward waking"
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
# A trap that fires inside a lock's critical section abandons the holding
|
|
1116
|
+
# frame, and the exit path then re-acquires the same lock (a TERM inside a
|
|
1117
|
+
# recovery-marker section is the reproduced case: the watcher's reap wedged
|
|
1118
|
+
# forever spinning against its own pid). The same-process re-acquire must
|
|
1119
|
+
# reclaim the abandoned hold, while a SUBSHELL still waits on its parent's
|
|
1120
|
+
# live hold exactly as before.
|
|
1121
|
+
test_self_held_lock_reclaims_instead_of_deadlocking() {
|
|
1122
|
+
local dir state rc
|
|
1123
|
+
dir=$(make_case self-held-lock)
|
|
1124
|
+
state="$dir/state"
|
|
1125
|
+
rc=0
|
|
1126
|
+
FM_STATE_OVERRIDE="$state" bash -c '
|
|
1127
|
+
. "$1"
|
|
1128
|
+
lock="$2/.fixture.lock"
|
|
1129
|
+
fm_lock_acquire_wait "$lock" || exit 10
|
|
1130
|
+
fm_lock_try_acquire "$lock" || exit 11
|
|
1131
|
+
fm_lock_release "$lock"
|
|
1132
|
+
[ ! -e "$lock" ] && [ ! -L "$lock" ] || exit 12
|
|
1133
|
+
' _ "$ROOT/bin/fm-wake-lib.sh" "$state" || rc=$?
|
|
1134
|
+
[ "$rc" -eq 0 ] || fail "self-held lock was not reclaimed cleanly (rc=$rc)"
|
|
1135
|
+
rc=0
|
|
1136
|
+
FM_STATE_OVERRIDE="$state" bash -c '
|
|
1137
|
+
. "$1"
|
|
1138
|
+
lock="$2/.fixture2.lock"
|
|
1139
|
+
fm_lock_acquire_wait "$lock" || exit 10
|
|
1140
|
+
( fm_lock_try_acquire "$lock" && exit 13; exit 0 ) || exit 13
|
|
1141
|
+
fm_lock_release "$lock"
|
|
1142
|
+
' _ "$ROOT/bin/fm-wake-lib.sh" "$state" || rc=$?
|
|
1143
|
+
[ "$rc" -eq 0 ] || fail "a subshell reclaimed its parent's live hold (rc=$rc)"
|
|
1144
|
+
pass "an abandoned same-process lock hold is reclaimed; a parent's live hold is not"
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
# Drain-time historical annotation staleness: a turn-ended-only wake row must
|
|
1148
|
+
# not present an already-announced status line as a new update, while a status
|
|
1149
|
+
# file with unannounced bytes keeps its annotation and a direct status row is
|
|
1150
|
+
# always annotated. Driven through the real drain executable.
|
|
1151
|
+
test_historical_annotation_skips_announced_status() {
|
|
1152
|
+
local dir state out err
|
|
1153
|
+
dir=$(make_case historical-annotation)
|
|
1154
|
+
state="$dir/state"
|
|
1155
|
+
out="$dir/drain.out"
|
|
1156
|
+
err="$dir/drain.err"
|
|
1157
|
+
|
|
1158
|
+
printf 'working: long scout still going\n' > "$state/scout.status"
|
|
1159
|
+
prime_status_seen "$state" "$state/scout.status" \
|
|
1160
|
+
|| fail "could not prime the scout seen marker"
|
|
1161
|
+
: > "$state/scout.turn-ended"
|
|
1162
|
+
append_wake "$state" signal scout.turn-ended "signal: $state/scout.turn-ended" \
|
|
1163
|
+
|| fail "turn-ended wake append failed"
|
|
1164
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" 2> "$err" || fail "drain failed"
|
|
1165
|
+
if grep -F 'scout.status: working: long scout still going' "$out" >/dev/null; then
|
|
1166
|
+
fail "a fully announced status line was replayed as a historical annotation"
|
|
1167
|
+
fi
|
|
1168
|
+
grep -F 'scout.turn-ended' "$out" >/dev/null \
|
|
1169
|
+
|| fail "suppressing the stale annotation dropped the turn-ended wake row itself"
|
|
1170
|
+
ack_drain_err "$state" "$err" || fail "could not acknowledge the first drain"
|
|
1171
|
+
|
|
1172
|
+
# Unannounced status bytes: the historical annotation is genuinely new
|
|
1173
|
+
# information and must stay.
|
|
1174
|
+
printf 'working: fresh unannounced progress\n' >> "$state/scout.status"
|
|
1175
|
+
: > "$state/scout.turn-ended"
|
|
1176
|
+
append_wake "$state" signal scout.turn-ended "signal: $state/scout.turn-ended" \
|
|
1177
|
+
|| fail "second turn-ended wake append failed"
|
|
1178
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" 2> "$err" || fail "second drain failed"
|
|
1179
|
+
grep -F 'historical / not necessarily the triggering event: scout.status: working: fresh unannounced progress' "$out" >/dev/null \
|
|
1180
|
+
|| fail "an unannounced status line lost its historical annotation"
|
|
1181
|
+
ack_drain_err "$state" "$err" || fail "could not acknowledge the second drain"
|
|
1182
|
+
|
|
1183
|
+
# A direct status row is the announcement itself and is always annotated,
|
|
1184
|
+
# even when the seen marker already covers the file.
|
|
1185
|
+
printf 'done: scout finished\n' >> "$state/scout.status"
|
|
1186
|
+
prime_status_seen "$state" "$state/scout.status" \
|
|
1187
|
+
|| fail "could not prime the marker for the direct-row leg"
|
|
1188
|
+
append_wake "$state" signal scout.status "signal: $state/scout.status" \
|
|
1189
|
+
|| fail "direct status wake append failed"
|
|
1190
|
+
FM_STATE_OVERRIDE="$state" "$DRAIN" > "$out" 2> "$err" || fail "third drain failed"
|
|
1191
|
+
grep -F 'scout.status: done: scout finished' "$out" >/dev/null \
|
|
1192
|
+
|| fail "a direct status row lost its annotation"
|
|
1193
|
+
pass "historical annotations replay nothing already announced and keep everything new"
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
test_self_held_lock_reclaims_instead_of_deadlocking
|
|
1197
|
+
test_secondmate_foreign_queue_stall_is_one_shot_and_read_only
|
|
1198
|
+
test_secondmate_stall_marker_rejects_symlink
|
|
1199
|
+
test_acknowledged_stall_publication_survives_pre_marker_crash
|
|
1200
|
+
test_empty_prefix_mate_preserves_other_mate_receipt
|
|
1201
|
+
test_self_announced_append_guards
|
|
1202
|
+
test_historical_annotation_skips_announced_status
|
|
1203
|
+
test_concurrent_append_and_drain
|
|
1204
|
+
test_signal_catchup_without_running_watcher
|
|
1205
|
+
test_stale_enqueue_before_suppressor
|
|
1206
|
+
test_not_working_stale_enqueue_before_suppressor
|
|
1207
|
+
test_check_output_is_queued
|
|
1208
|
+
test_atomic_double_drain
|
|
1209
|
+
test_drain_dedupes_obvious_duplicates
|
|
1210
|
+
test_drain_asserts_watcher_liveness
|
|
1211
|
+
test_structural_signal_enrichment_preserves_raw_rows
|
|
1212
|
+
test_enrichment_preserves_all_unread_lines_and_status_file_failures
|
|
1213
|
+
test_slow_annotation_does_not_block_append_and_deleted_file_fails_open
|
|
1214
|
+
test_branch_actor_scoped_ack_never_swallows_a_main_owned_row
|
|
1215
|
+
test_main_drain_excludes_rows_already_granted_to_branch
|
|
1216
|
+
test_branch_grant_refuses_rows_already_claimed_by_main
|
|
1217
|
+
test_actor_filter_precedes_same_key_deduplication
|
|
1218
|
+
test_main_reclaims_a_grant_whose_branch_owner_exited
|
|
1219
|
+
test_branch_actor_without_eligible_snapshot_refuses
|
|
1220
|
+
test_wake_publish_requires_atomic_recovery_evidence
|
|
1221
|
+
test_legacy_generationless_wake_is_adopted
|
|
1222
|
+
test_stale_recovery_generation_cannot_touch_a_newer_episode
|
|
1223
|
+
test_recovery_ack_failure_is_reported
|
|
1224
|
+
test_interruption_before_and_after_raw_commit
|