@zerwiz/ymir 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -39
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Parent-owned secondmate pending-reply guards (bin/fm-pending-reply-lib.sh).
|
|
3
|
+
#
|
|
4
|
+
# Reproduces the missed-report experience: a marked request is delivered, the
|
|
5
|
+
# target turn completes, and no correlated parent report arrives. The parent
|
|
6
|
+
# must notice without scraping conversation, send exactly one recovery repost,
|
|
7
|
+
# and escalate once if that recovery turn is also missed.
|
|
8
|
+
#
|
|
9
|
+
# Coverage:
|
|
10
|
+
# 1. Normal correlated reply resolves once
|
|
11
|
+
# 2. Completed turn with no report triggers one recovery only
|
|
12
|
+
# 3. Recovery reply resolves the original expectation
|
|
13
|
+
# 4. Second missed turn escalates once and remains durable
|
|
14
|
+
# 5. Transport success cannot masquerade as reply success
|
|
15
|
+
# 6. Unrelated events and stale correlation ids cannot resolve a request
|
|
16
|
+
# 7. Restart/compaction preserves the expectation and exact parent destination
|
|
17
|
+
# 8. Wrong-home reports are detected but do not silently acknowledge
|
|
18
|
+
# 9. Direct unmarked captain input creates no expectation
|
|
19
|
+
# 10. fm-send secondmate path embeds corr and creates durable pending records
|
|
20
|
+
# 11. Backend busy/idle observation works through the shared busy abstraction
|
|
21
|
+
# used by Pi/Claude secondmate backends (no conversation scrape)
|
|
22
|
+
# 12. A remote mate's repost waits for its asynchronous reply mirror to be read
|
|
23
|
+
# past the turn, so a mirrored reply is never nagged and a real miss still
|
|
24
|
+
# gets its one repost
|
|
25
|
+
set -u
|
|
26
|
+
|
|
27
|
+
# shellcheck source=tests/lib.sh
|
|
28
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
29
|
+
# shellcheck source=bin/fm-marker-lib.sh
|
|
30
|
+
. "$ROOT/bin/fm-marker-lib.sh"
|
|
31
|
+
# shellcheck source=bin/fm-pending-reply-lib.sh
|
|
32
|
+
. "$ROOT/bin/fm-pending-reply-lib.sh"
|
|
33
|
+
|
|
34
|
+
SEND="$ROOT/bin/fm-send.sh"
|
|
35
|
+
REPORT="$ROOT/bin/fm-secondmate-report.sh"
|
|
36
|
+
TMP_ROOT=$(fm_test_tmproot fm-pending-reply)
|
|
37
|
+
|
|
38
|
+
export FM_PENDING_REPLY_GRACE_SECS=0
|
|
39
|
+
export FM_SEND_SETTLE=0
|
|
40
|
+
|
|
41
|
+
# --- fixtures ---------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
make_stubs() { # <dir> -> fakebin
|
|
44
|
+
local dir=$1 fb="$1/fakebin"
|
|
45
|
+
mkdir -p "$fb"
|
|
46
|
+
cat > "$fb/tmux" <<'SH'
|
|
47
|
+
#!/usr/bin/env bash
|
|
48
|
+
set -u
|
|
49
|
+
case "${1:-}" in
|
|
50
|
+
send-keys)
|
|
51
|
+
shift
|
|
52
|
+
literal=0
|
|
53
|
+
while [ $# -gt 0 ]; do
|
|
54
|
+
case "$1" in
|
|
55
|
+
-t) shift 2 ;;
|
|
56
|
+
-l) literal=1; shift ;;
|
|
57
|
+
*) break ;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
if [ "$literal" = 1 ]; then
|
|
61
|
+
printf '%s' "${1:-}" >> "$FM_SEND_LOG"
|
|
62
|
+
fi
|
|
63
|
+
exit 0 ;;
|
|
64
|
+
display-message)
|
|
65
|
+
for a in "$@"; do case "$a" in *cursor_y*) printf '1\n'; exit 0 ;; esac; done
|
|
66
|
+
printf 'fakepane\n'; exit 0 ;;
|
|
67
|
+
capture-pane) printf '╭────╮\n│ │\n╰────╯\n'; exit 0 ;;
|
|
68
|
+
list-windows) exit 0 ;;
|
|
69
|
+
esac
|
|
70
|
+
exit 0
|
|
71
|
+
SH
|
|
72
|
+
chmod +x "$fb/tmux"
|
|
73
|
+
cat > "$fb/sleep" <<'SH'
|
|
74
|
+
#!/usr/bin/env bash
|
|
75
|
+
exit 0
|
|
76
|
+
SH
|
|
77
|
+
chmod +x "$fb/sleep"
|
|
78
|
+
printf '%s\n' "$fb"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setup_parent() { # <name> -> home
|
|
82
|
+
local home="$TMP_ROOT/$1-$RANDOM"
|
|
83
|
+
mkdir -p "$home/state"
|
|
84
|
+
printf '%s\n' "$home"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
run_send() {
|
|
88
|
+
local fb=$1 home=$2 log=$3; shift 3
|
|
89
|
+
: > "$log"
|
|
90
|
+
env PATH="$fb:$PATH" \
|
|
91
|
+
FM_ROOT_OVERRIDE="$home" FM_HOME="$home" FM_SEND_LOG="$log" FM_SEND_SETTLE=0 \
|
|
92
|
+
FM_PENDING_REPLY_GRACE_SECS=0 \
|
|
93
|
+
"$SEND" "$@" 2>/dev/null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
phase_of() { # <state> <corr>
|
|
97
|
+
fm_pending_reply_get "$(fm_pending_reply_path "$1" "$2")" phase
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# A local steer now rides the durable steering inbox rather than the typed
|
|
101
|
+
# channel, so marker/corr assertions read the latest enqueued record through
|
|
102
|
+
# the production owner (bin/fm-task-inbox-lib.sh).
|
|
103
|
+
latest_record_body() { # <home> <task>
|
|
104
|
+
local rec
|
|
105
|
+
rec=$(find "$1/state/$2.inbox" -maxdepth 1 -name '*.msg' 2>/dev/null | sort | tail -1)
|
|
106
|
+
[ -n "$rec" ] || return 1
|
|
107
|
+
bash -c '. "$1"; fm_task_inbox_body "$2"' _ "$ROOT/bin/fm-task-inbox-lib.sh" "$rec"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# --- tests ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
test_normal_correlated_reply_resolves_once() {
|
|
113
|
+
local home state corr status rec
|
|
114
|
+
home=$(setup_parent resolve-once)
|
|
115
|
+
state="$home/state"
|
|
116
|
+
export FM_PENDING_REPLY_NOW=1000
|
|
117
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "audit the ledger")
|
|
118
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
119
|
+
status="$state/hibit.status"
|
|
120
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
121
|
+
fail "missing status must not resolve"
|
|
122
|
+
fi
|
|
123
|
+
printf 'done [corr=%s]: ledger clean\n' "$corr" > "$status"
|
|
124
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "correlated status should resolve"
|
|
125
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] || fail "phase should be resolved"
|
|
126
|
+
# Idempotent second resolve.
|
|
127
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "second resolve must stay successful"
|
|
128
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] || fail "phase must remain resolved"
|
|
129
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
130
|
+
[ "$(fm_pending_reply_get "$rec" resolved_via)" = status ] \
|
|
131
|
+
|| fail "resolved_via should be status"
|
|
132
|
+
pass "normal correlated reply resolves once (idempotent)"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
test_completed_turn_no_report_triggers_one_recovery() {
|
|
136
|
+
local home state corr hook_log rec
|
|
137
|
+
home=$(setup_parent one-recovery)
|
|
138
|
+
state="$home/state"
|
|
139
|
+
hook_log="$TMP_ROOT/recovery-hook.log"
|
|
140
|
+
: > "$hook_log"
|
|
141
|
+
export FM_PENDING_REPLY_NOW=2000
|
|
142
|
+
export FM_PENDING_REPLY_SEND_HOOK='printf "%s\t%s\n" >>"'"$hook_log"'"'
|
|
143
|
+
# The hook above is wrong for eval form - use a function.
|
|
144
|
+
# Invoked indirectly through FM_PENDING_REPLY_SEND_HOOK.
|
|
145
|
+
# shellcheck disable=SC2329
|
|
146
|
+
recovery_hook() {
|
|
147
|
+
printf '%s\t%s\n' "$1" "$2" >> "$hook_log"
|
|
148
|
+
}
|
|
149
|
+
export -f recovery_hook
|
|
150
|
+
export FM_PENDING_REPLY_SEND_HOOK='recovery_hook'
|
|
151
|
+
|
|
152
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "status of phase 7")
|
|
153
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
154
|
+
# Turn completes with no parent report (the Hi Bit missed-report shape).
|
|
155
|
+
fm_pending_reply_observe_busy "$state" "$corr" busy
|
|
156
|
+
fm_pending_reply_observe_busy "$state" "$corr" idle
|
|
157
|
+
fm_pending_reply_send_recovery "$state" "$corr" \
|
|
158
|
+
|| fail "recovery should send after completed turn + grace"
|
|
159
|
+
[ "$(phase_of "$state" "$corr")" = recovery_sent ] \
|
|
160
|
+
|| fail "phase should be recovery_sent, got $(phase_of "$state" "$corr")"
|
|
161
|
+
[ -s "$hook_log" ] || fail "recovery hook should have been invoked once"
|
|
162
|
+
# Second attempt must not re-send.
|
|
163
|
+
if fm_pending_reply_send_recovery "$state" "$corr" 2>/dev/null; then
|
|
164
|
+
fail "second recovery must refuse"
|
|
165
|
+
fi
|
|
166
|
+
lines=$(wc -l < "$hook_log" | tr -d ' ')
|
|
167
|
+
[ "$lines" = 1 ] || fail "expected exactly one recovery send, got $lines"
|
|
168
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
169
|
+
case "$(cat "$hook_log")" in
|
|
170
|
+
*"corr=$corr"*) : ;;
|
|
171
|
+
*) fail "recovery message must carry the original corr"$'\n'"$(cat "$hook_log")" ;;
|
|
172
|
+
esac
|
|
173
|
+
case "$(cat "$hook_log")" in
|
|
174
|
+
*REPOST\ REQUIRED*) : ;;
|
|
175
|
+
*) fail "recovery message must ask for a repost"$'\n'"$(cat "$hook_log")" ;;
|
|
176
|
+
esac
|
|
177
|
+
pass "completed turn with no report triggers exactly one recovery"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
test_recovery_attempt_is_never_reinjected() {
|
|
181
|
+
local home state corr rec hook_log lines live_corr live_rec live_pid live_identity
|
|
182
|
+
home=$(setup_parent recovery-at-most-once)
|
|
183
|
+
state="$home/state"
|
|
184
|
+
hook_log="$TMP_ROOT/recovery-at-most-once.log"
|
|
185
|
+
: > "$hook_log"
|
|
186
|
+
export FM_PENDING_REPLY_NOW=2500
|
|
187
|
+
recovery_fail_hook() {
|
|
188
|
+
printf 'attempted\n' >> "$hook_log"
|
|
189
|
+
return 1
|
|
190
|
+
}
|
|
191
|
+
export -f recovery_fail_hook
|
|
192
|
+
export FM_PENDING_REPLY_SEND_HOOK=recovery_fail_hook
|
|
193
|
+
corr=$(fm_pending_reply_create "$home" "$state" hibit "at most once")
|
|
194
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
195
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
196
|
+
if fm_pending_reply_send_recovery "$state" "$corr"; then
|
|
197
|
+
fail "failed recovery transport should report failure"
|
|
198
|
+
fi
|
|
199
|
+
[ "$(phase_of "$state" "$corr")" = recovery_failed ] \
|
|
200
|
+
|| fail "failed recovery attempt should preserve failed delivery"
|
|
201
|
+
[ -z "$(fm_pending_reply_get "$(fm_pending_reply_path "$state" "$corr")" recovery_sent_epoch)" ] \
|
|
202
|
+
|| fail "failed recovery must not record a sent epoch"
|
|
203
|
+
if fm_pending_reply_send_recovery "$state" "$corr" 2>/dev/null; then
|
|
204
|
+
fail "committed recovery attempt must refuse reinjection"
|
|
205
|
+
fi
|
|
206
|
+
lines=$(wc -l < "$hook_log" | tr -d ' ')
|
|
207
|
+
[ "$lines" = 1 ] || fail "recovery transport should be attempted once, got $lines"
|
|
208
|
+
fm_pending_reply_maybe_escalate "$state" "$corr" \
|
|
209
|
+
|| fail "failed recovery delivery should escalate explicitly"
|
|
210
|
+
grep -Fq "pending-reply-recovery-delivery-failed:" "$state/hibit.status" \
|
|
211
|
+
|| fail "failed recovery escalation should name delivery failure"
|
|
212
|
+
live_corr=$(fm_pending_reply_create "$home" "$state" hibit "live recovery")
|
|
213
|
+
fm_pending_reply_mark_delivered "$state" "$live_corr"
|
|
214
|
+
fm_pending_reply_mark_turn_completed "$state" "$live_corr" request
|
|
215
|
+
live_rec=$(fm_pending_reply_path "$state" "$live_corr")
|
|
216
|
+
live_pid=${BASHPID:-$$}
|
|
217
|
+
live_identity=$(fm_pending_reply_pid_identity "$live_pid") \
|
|
218
|
+
|| fail "live sender identity should be observable"
|
|
219
|
+
fm_pending_reply_set "$live_rec" recovery_attempted_epoch 2500 || fail "live attempt precommit failed"
|
|
220
|
+
fm_pending_reply_set "$live_rec" recovery_sender_pid "$live_pid" || fail "live sender pid commit failed"
|
|
221
|
+
fm_pending_reply_set "$live_rec" recovery_sender_identity "$live_identity" \
|
|
222
|
+
|| fail "live sender identity commit failed"
|
|
223
|
+
fm_pending_reply_set "$live_rec" phase recovery_sending || fail "live sending phase failed"
|
|
224
|
+
fm_pending_reply_tick_one "$state" "$live_corr" unknown || fail "live recovery tick failed"
|
|
225
|
+
[ "$(phase_of "$state" "$live_corr")" = recovery_sending ] \
|
|
226
|
+
|| fail "live recovery must remain in progress without elapsed-time inference"
|
|
227
|
+
corr=$(fm_pending_reply_create "$home" "$state" hibit "crashed recovery")
|
|
228
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
229
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
230
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
231
|
+
fm_pending_reply_set "$rec" recovery_attempted_epoch 2500 || fail "attempt precommit failed"
|
|
232
|
+
fm_pending_reply_set "$rec" phase recovery_sending || fail "sending phase precommit failed"
|
|
233
|
+
fm_pending_reply_tick_one "$state" "$corr" unknown || fail "recovery reconciliation failed"
|
|
234
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] \
|
|
235
|
+
|| fail "interrupted recovery attempt should escalate unknown delivery"
|
|
236
|
+
[ "$(fm_pending_reply_get "$rec" recovery_delivery_outcome)" = unknown ] \
|
|
237
|
+
|| fail "interrupted recovery should preserve unknown delivery outcome"
|
|
238
|
+
[ -z "$(fm_pending_reply_get "$rec" recovery_sent_epoch)" ] \
|
|
239
|
+
|| fail "unknown recovery must not record a sent epoch"
|
|
240
|
+
grep -Fq "pending-reply-recovery-delivery-unknown:" "$state/hibit.status" \
|
|
241
|
+
|| fail "unknown recovery escalation should name delivery uncertainty"
|
|
242
|
+
lines=$(wc -l < "$hook_log" | tr -d ' ')
|
|
243
|
+
[ "$lines" = 1 ] || fail "reconciliation must not call recovery transport, got $lines attempts"
|
|
244
|
+
unset FM_PENDING_REPLY_SEND_HOOK
|
|
245
|
+
pass "recovery attempts reconcile without reinjection"
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
test_recovery_reply_resolves_original() {
|
|
249
|
+
local home state corr hook_log
|
|
250
|
+
home=$(setup_parent recovery-resolve)
|
|
251
|
+
state="$home/state"
|
|
252
|
+
hook_log="$TMP_ROOT/recovery-resolve-hook.log"
|
|
253
|
+
: > "$hook_log"
|
|
254
|
+
# Invoked indirectly through FM_PENDING_REPLY_SEND_HOOK.
|
|
255
|
+
# shellcheck disable=SC2329
|
|
256
|
+
recovery_hook() { printf '%s\n' "$2" >> "$hook_log"; }
|
|
257
|
+
export -f recovery_hook
|
|
258
|
+
export FM_PENDING_REPLY_SEND_HOOK='recovery_hook'
|
|
259
|
+
export FM_PENDING_REPLY_NOW=3000
|
|
260
|
+
|
|
261
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "phase 7 status")
|
|
262
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
263
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
264
|
+
fm_pending_reply_send_recovery "$state" "$corr" || fail "recovery send failed"
|
|
265
|
+
printf 'done [corr=%s]: phase 7 is Done (reposted)\n' "$corr" > "$state/hibit.status"
|
|
266
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "recovery reply should resolve original"
|
|
267
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] || fail "expected resolved after recovery reply"
|
|
268
|
+
pass "recovery reply resolves the original expectation"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
test_second_missed_turn_escalates_once_and_stays_durable() {
|
|
272
|
+
local home state corr hook_log rec status_line escalations
|
|
273
|
+
home=$(setup_parent escalate-once)
|
|
274
|
+
state="$home/state"
|
|
275
|
+
hook_log="$TMP_ROOT/escalate-hook.log"
|
|
276
|
+
: > "$hook_log"
|
|
277
|
+
# Invoked indirectly through FM_PENDING_REPLY_SEND_HOOK.
|
|
278
|
+
# shellcheck disable=SC2329
|
|
279
|
+
recovery_hook() { printf '%s\n' ok >> "$hook_log"; }
|
|
280
|
+
export -f recovery_hook
|
|
281
|
+
export FM_PENDING_REPLY_SEND_HOOK='recovery_hook'
|
|
282
|
+
export FM_PENDING_REPLY_NOW=4000
|
|
283
|
+
# Do not export STATE into the test process: fm-send resolves
|
|
284
|
+
# FM_STATE_OVERRIDE/STATE from the environment and a leak breaks later cases.
|
|
285
|
+
|
|
286
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "why is phase 7 stuck")
|
|
287
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
288
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
289
|
+
fm_pending_reply_send_recovery "$state" "$corr" || fail "recovery send failed"
|
|
290
|
+
# Recovery turn also completes with no correlated report.
|
|
291
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" recovery
|
|
292
|
+
fm_pending_reply_maybe_escalate "$state" "$corr" || fail "escalation should fire"
|
|
293
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] || fail "phase should be escalated"
|
|
294
|
+
status_line=$(tail -1 "$state/hibit.status")
|
|
295
|
+
case "$status_line" in
|
|
296
|
+
"blocked [key=pending-reply-$corr]:"*pending-reply-missed:*pending-reply-id=$corr*) : ;;
|
|
297
|
+
*) fail "parent status should carry one blocked missed-report line"$'\n'"$status_line" ;;
|
|
298
|
+
esac
|
|
299
|
+
[ ! -s "$state/.wake-queue" ] || fail "direct escalation must not enqueue a duplicate check wake"
|
|
300
|
+
# Second escalate must be a no-op (phase no longer recovery_sent).
|
|
301
|
+
if fm_pending_reply_maybe_escalate "$state" "$corr" 2>/dev/null; then
|
|
302
|
+
# Function returns 1 when phase is not recovery_sent - good.
|
|
303
|
+
:
|
|
304
|
+
fi
|
|
305
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] || fail "phase must stay escalated"
|
|
306
|
+
escalations=$(grep -Fc "blocked [key=pending-reply-$corr]:" "$state/hibit.status")
|
|
307
|
+
[ "$escalations" = 1 ] || fail "missed recovery should publish one escalation, got $escalations"
|
|
308
|
+
# Durable record retained (never silently expired).
|
|
309
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
310
|
+
[ -f "$rec" ] || fail "escalated record must remain on disk"
|
|
311
|
+
[ "$(fm_pending_reply_get "$rec" parent_status)" = "$state/hibit.status" ] \
|
|
312
|
+
|| fail "parent destination must remain exact"
|
|
313
|
+
# Unrelated status activity still does not resolve.
|
|
314
|
+
printf 'working: unrelated churn\n' >> "$state/hibit.status"
|
|
315
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
316
|
+
fail "unrelated status must not resolve an escalated miss"
|
|
317
|
+
fi
|
|
318
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] || fail "must remain escalated after unrelated status"
|
|
319
|
+
pass "second missed turn escalates once and remains durable"
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# Wake-gate helpers reading the production seen-signature owner directly, so
|
|
323
|
+
# these assertions consume the exact gate the watcher's signal scan uses.
|
|
324
|
+
seen_gate() { # <state> <file>: 0 when every byte is already announced
|
|
325
|
+
FM_STATE_OVERRIDE="$1" bash -c '. "$1"; fm_wake_signal_seen_current "$2" "$3"' \
|
|
326
|
+
_ "$ROOT/bin/fm-wake-lib.sh" "$1" "$2"
|
|
327
|
+
}
|
|
328
|
+
prime_seen() { # <state> <file>
|
|
329
|
+
FM_STATE_OVERRIDE="$1" bash -c '
|
|
330
|
+
. "$1"; fm_wake_status_mark_current "$2" "$3"
|
|
331
|
+
' _ "$ROOT/bin/fm-wake-lib.sh" "$1" "$2"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
test_escalation_wakes_and_its_close_stays_quiet() {
|
|
335
|
+
local home state corr
|
|
336
|
+
home=$(setup_parent escalation-wake-gate)
|
|
337
|
+
state="$home/state"
|
|
338
|
+
export FM_PENDING_REPLY_SEND_HOOK='true'
|
|
339
|
+
export FM_PENDING_REPLY_NOW=4200
|
|
340
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "confirm the notarization")
|
|
341
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
342
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
343
|
+
fm_pending_reply_send_recovery "$state" "$corr" || fail "recovery send failed"
|
|
344
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" recovery
|
|
345
|
+
: > "$state/hibit.status"
|
|
346
|
+
prime_seen "$state" "$state/hibit.status" || fail "could not prime the announced baseline"
|
|
347
|
+
# A NEW blocker must wake: the escalation append leaves unannounced bytes.
|
|
348
|
+
fm_pending_reply_maybe_escalate "$state" "$corr" || fail "escalation should fire"
|
|
349
|
+
if seen_gate "$state" "$state/hibit.status"; then
|
|
350
|
+
fail "a new pending-reply escalation was hidden from the watcher's signal gate"
|
|
351
|
+
fi
|
|
352
|
+
prime_seen "$state" "$state/hibit.status" || fail "could not mark the escalation announced"
|
|
353
|
+
# A genuinely new correlated reply must wake too.
|
|
354
|
+
printf 'done [corr=%s]: notarization confirmed\n' "$corr" >> "$state/hibit.status"
|
|
355
|
+
if seen_gate "$state" "$state/hibit.status"; then
|
|
356
|
+
fail "a new correlated reply was hidden from the watcher's signal gate"
|
|
357
|
+
fi
|
|
358
|
+
prime_seen "$state" "$state/hibit.status" || fail "could not mark the reply announced"
|
|
359
|
+
# The home's own escalation CLOSE is bookkeeping and stays quiet.
|
|
360
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "correlated reply should resolve"
|
|
361
|
+
grep -Fq "resolved [key=pending-reply-$corr]" "$state/hibit.status" \
|
|
362
|
+
|| fail "resolution did not close the escalation decision"
|
|
363
|
+
seen_gate "$state" "$state/hibit.status" \
|
|
364
|
+
|| fail "the home's own escalation close re-woke its own watcher gate"
|
|
365
|
+
pass "escalations and replies wake; the home's own escalation close stays quiet"
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
test_escalation_publication_failure_retries() {
|
|
369
|
+
local home state corr rec target escalations
|
|
370
|
+
home=$(setup_parent escalation-retry)
|
|
371
|
+
state="$home/state"
|
|
372
|
+
export FM_PENDING_REPLY_NOW=4500
|
|
373
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "retry escalation")
|
|
374
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
375
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
376
|
+
export FM_PENDING_REPLY_SEND_HOOK='true'
|
|
377
|
+
fm_pending_reply_send_recovery "$state" "$corr" || fail "recovery send failed"
|
|
378
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" recovery
|
|
379
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
380
|
+
target="$state/escalation-target"
|
|
381
|
+
mkdir -p "$target"
|
|
382
|
+
fm_pending_reply_set "$rec" parent_status "$target" || fail "failed to set escalation target"
|
|
383
|
+
if fm_pending_reply_maybe_escalate "$state" "$corr" 2>/dev/null; then
|
|
384
|
+
fail "escalation should fail when its durable status cannot be written"
|
|
385
|
+
fi
|
|
386
|
+
[ "$(phase_of "$state" "$corr")" = recovery_sent ] \
|
|
387
|
+
|| fail "publication failure must leave escalation retryable"
|
|
388
|
+
rmdir "$target"
|
|
389
|
+
fm_pending_reply_maybe_escalate "$state" "$corr" || fail "escalation retry should succeed"
|
|
390
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] || fail "successful retry should commit escalation"
|
|
391
|
+
escalations=$(grep -Fc "blocked [key=pending-reply-$corr]:" "$target")
|
|
392
|
+
[ "$escalations" = 1 ] || fail "successful retry should publish exactly once, got $escalations"
|
|
393
|
+
pass "failed escalation publication remains retryable and publishes once"
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
test_legacy_escalation_closes_default_decision() {
|
|
397
|
+
local home state corr rec open
|
|
398
|
+
home=$(setup_parent legacy-close)
|
|
399
|
+
state="$home/state"
|
|
400
|
+
export FM_PENDING_REPLY_NOW=4725
|
|
401
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "legacy close")
|
|
402
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
403
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
404
|
+
fm_pending_reply_set "$rec" phase escalated
|
|
405
|
+
fm_pending_reply_set "$rec" escalated_epoch 4700
|
|
406
|
+
printf 'blocked: pending-reply-missed: task=hibit pending-reply-id=%s request=legacy close\n' "$corr" \
|
|
407
|
+
> "$state/hibit.status"
|
|
408
|
+
printf 'done [corr=%s]: delayed legacy reply\n' "$corr" >> "$state/hibit.status"
|
|
409
|
+
|
|
410
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "legacy reply should resolve its record"
|
|
411
|
+
[ "$(grep -Fc "resolved [key=default]: pending-reply-resolved: task=hibit pending-reply-id=$corr" "$state/hibit.status")" -eq 1 ] \
|
|
412
|
+
|| fail "legacy escalation did not append one guarded default-key resolution"
|
|
413
|
+
open=$(status_open_decisions "$state/hibit.status")
|
|
414
|
+
[ -z "$open" ] || fail "resolved legacy escalation remained open: $open"
|
|
415
|
+
[ -n "$(fm_pending_reply_get "$rec" escalation_closed_epoch)" ] \
|
|
416
|
+
|| fail "legacy escalation closure was not recorded"
|
|
417
|
+
pass "legacy escalation closes under the shared default key"
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
test_legacy_escalation_does_not_close_taken_default_decision() {
|
|
421
|
+
local home state corr rec open
|
|
422
|
+
home=$(setup_parent legacy-escalation)
|
|
423
|
+
state="$home/state"
|
|
424
|
+
export FM_PENDING_REPLY_NOW=4750
|
|
425
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "legacy escalation")
|
|
426
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
427
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
428
|
+
fm_pending_reply_set "$rec" phase escalated
|
|
429
|
+
fm_pending_reply_set "$rec" escalated_epoch 4700
|
|
430
|
+
printf 'blocked: pending-reply-missed: task=hibit pending-reply-id=%s request=legacy escalation\n' "$corr" \
|
|
431
|
+
> "$state/hibit.status"
|
|
432
|
+
printf 'blocked: unrelated operator decision\n' >> "$state/hibit.status"
|
|
433
|
+
printf 'done [corr=%s]: delayed legacy reply\n' "$corr" >> "$state/hibit.status"
|
|
434
|
+
|
|
435
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "legacy reply should resolve its record"
|
|
436
|
+
if grep -Fq 'resolved [key=default]: pending-reply-resolved:' "$state/hibit.status"; then
|
|
437
|
+
fail "legacy escalation emitted an unsafe default-key resolution"
|
|
438
|
+
fi
|
|
439
|
+
fm_pending_reply_tick "$state" || fail "legacy close retry failed"
|
|
440
|
+
open=$(status_open_decisions "$state/hibit.status")
|
|
441
|
+
assert_contains "$open" "unrelated operator decision" \
|
|
442
|
+
"legacy escalation closure hid an unrelated default-key decision"
|
|
443
|
+
pass "legacy escalation cannot close an unrelated default-key decision"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
test_foreign_blocker_is_not_selected_as_escalation() {
|
|
447
|
+
local home state corr rec open
|
|
448
|
+
home=$(setup_parent foreign-blocker)
|
|
449
|
+
state="$home/state"
|
|
450
|
+
export FM_PENDING_REPLY_NOW=4775
|
|
451
|
+
export FM_PENDING_REPLY_SEND_HOOK=true
|
|
452
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "foreign blocker")
|
|
453
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
454
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
455
|
+
fm_pending_reply_send_recovery "$state" "$corr" || fail "recovery send failed"
|
|
456
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" recovery
|
|
457
|
+
fm_pending_reply_maybe_escalate "$state" "$corr" || fail "genuine escalation failed"
|
|
458
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
459
|
+
printf 'blocked [key=release]: foreign decision pending-reply-id=%s corr=%s\n' \
|
|
460
|
+
"$corr" "$corr" >> "$state/hibit.status"
|
|
461
|
+
|
|
462
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "correlated foreign blocker should resolve the record"
|
|
463
|
+
open=$(status_open_decisions "$state/hibit.status")
|
|
464
|
+
assert_contains "$open" $'release\tblocked\tforeign decision' \
|
|
465
|
+
"pending-reply closure cleared the foreign release decision"
|
|
466
|
+
assert_not_contains "$open" "pending-reply-$corr" \
|
|
467
|
+
"genuine keyed escalation remained open"
|
|
468
|
+
assert_no_grep 'resolved [key=release]: pending-reply-resolved:' "$state/hibit.status" \
|
|
469
|
+
"foreign release decision was selected as the pending-reply escalation"
|
|
470
|
+
[ -n "$(fm_pending_reply_get "$rec" escalation_closed_epoch)" ] \
|
|
471
|
+
|| fail "genuine keyed escalation closure was not recorded"
|
|
472
|
+
pass "foreign correlated blocker cannot impersonate a pending-reply escalation"
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
test_concurrent_resolution_closes_escalation_once() {
|
|
476
|
+
local home state corr rec
|
|
477
|
+
home=$(setup_parent concurrent-resolution)
|
|
478
|
+
state="$home/state"
|
|
479
|
+
export FM_PENDING_REPLY_NOW=4800
|
|
480
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "concurrent resolution")
|
|
481
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
482
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
483
|
+
fm_pending_reply_set "$rec" phase escalated
|
|
484
|
+
fm_pending_reply_set "$rec" escalated_epoch 4750
|
|
485
|
+
printf 'blocked [key=pending-reply-%s]: pending-reply-missed: task=hibit pending-reply-id=%s request=concurrent resolution\n' \
|
|
486
|
+
"$corr" "$corr" > "$state/hibit.status"
|
|
487
|
+
printf 'done [corr=%s]: concurrent delayed reply\n' "$corr" >> "$state/hibit.status"
|
|
488
|
+
|
|
489
|
+
for _ in 1 2 3 4 5 6 7 8; do
|
|
490
|
+
fm_pending_reply_try_resolve "$state" "$corr" &
|
|
491
|
+
done
|
|
492
|
+
wait
|
|
493
|
+
|
|
494
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] \
|
|
495
|
+
|| fail "concurrent resolvers left the expectation unresolved"
|
|
496
|
+
[ "$(grep -Fc "pending-reply-resolved: task=hibit pending-reply-id=$corr" "$state/hibit.status")" -eq 1 ] \
|
|
497
|
+
|| fail "concurrent resolvers did not append exactly one decision close"
|
|
498
|
+
[ -n "$(fm_pending_reply_get "$rec" escalation_closed_epoch)" ] \
|
|
499
|
+
|| fail "concurrent resolution did not record the closed escalation"
|
|
500
|
+
pass "concurrent resolution closes one keyed escalation exactly once"
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
test_concurrent_escalation_yields_to_late_reply() {
|
|
504
|
+
local home state corr rec
|
|
505
|
+
home=$(setup_parent concurrent-escalation)
|
|
506
|
+
state="$home/state"
|
|
507
|
+
export FM_PENDING_REPLY_NOW=4900
|
|
508
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "concurrent escalation")
|
|
509
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
510
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
511
|
+
fm_pending_reply_set "$rec" phase recovery_sent
|
|
512
|
+
fm_pending_reply_set "$rec" recovery_turn_completed_epoch 4850
|
|
513
|
+
printf 'done [corr=%s]: late concurrent reply\n' "$corr" > "$state/hibit.status"
|
|
514
|
+
|
|
515
|
+
for _ in 1 2 3 4 5 6 7 8; do
|
|
516
|
+
fm_pending_reply_maybe_escalate "$state" "$corr" &
|
|
517
|
+
fm_pending_reply_try_resolve "$state" "$corr" &
|
|
518
|
+
done
|
|
519
|
+
wait
|
|
520
|
+
|
|
521
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] \
|
|
522
|
+
|| fail "concurrent escalation overwrote a resolved expectation"
|
|
523
|
+
assert_no_grep "pending-reply-id=$corr" "$state/hibit.status" \
|
|
524
|
+
"concurrent escalation published a false missed-reply blocker"
|
|
525
|
+
[ -z "$(fm_pending_reply_get "$rec" escalated_epoch)" ] \
|
|
526
|
+
|| fail "concurrent escalation committed after the reply resolved"
|
|
527
|
+
pass "concurrent escalation yields to a late correlated reply"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
test_transport_success_is_not_reply_success() {
|
|
531
|
+
local home state corr
|
|
532
|
+
home=$(setup_parent transport-not-reply)
|
|
533
|
+
state="$home/state"
|
|
534
|
+
export FM_PENDING_REPLY_NOW=5000
|
|
535
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "ping")
|
|
536
|
+
fm_pending_reply_mark_delivered "$state" "$corr" || fail "mark delivered failed"
|
|
537
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] \
|
|
538
|
+
|| fail "delivery must leave phase awaiting_report, got $(phase_of "$state" "$corr")"
|
|
539
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
540
|
+
fail "delivery alone must not resolve"
|
|
541
|
+
fi
|
|
542
|
+
pass "transport success cannot masquerade as reply success"
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
test_undelivered_records_are_scan_immutable() {
|
|
546
|
+
(
|
|
547
|
+
local home state sm_home corr rec before after
|
|
548
|
+
home=$(setup_parent undelivered-scan)
|
|
549
|
+
state="$home/state"
|
|
550
|
+
sm_home="$home/sm"
|
|
551
|
+
mkdir -p "$sm_home/state"
|
|
552
|
+
# This fixture clock is intentionally scoped to the isolated subshell.
|
|
553
|
+
# shellcheck disable=SC2030,SC2031
|
|
554
|
+
export FM_PENDING_REPLY_NOW=5500
|
|
555
|
+
corr=$(fm_pending_reply_create "$home" "$state" hibit "not delivered yet")
|
|
556
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
557
|
+
printf 'done [corr=%s]: arrived too early\n' "$corr" > "$state/hibit.status"
|
|
558
|
+
printf 'done [corr=%s]: wrong home too early\n' "$corr" > "$sm_home/state/hibit.status"
|
|
559
|
+
fm_write_secondmate_meta "$state/hibit.meta" "$sm_home" "sess:fm-hibit"
|
|
560
|
+
before=$(cat "$rec")
|
|
561
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
562
|
+
fail "undelivered expectation must not resolve"
|
|
563
|
+
fi
|
|
564
|
+
fm_pending_reply_detect_wrong_home "$state" "$corr" "$sm_home" \
|
|
565
|
+
|| fail "undelivered wrong-home check should be inert"
|
|
566
|
+
fm_pending_reply_tick_one "$state" "$corr" busy "$sm_home" \
|
|
567
|
+
|| fail "undelivered direct tick should be inert"
|
|
568
|
+
fm_backend_busy_state() { fail "undelivered watcher tick must not probe the backend"; }
|
|
569
|
+
fm_backend_capture() { fail "undelivered watcher tick must not capture the backend"; }
|
|
570
|
+
fm_pending_reply_tick "$state" || fail "undelivered watcher tick should succeed"
|
|
571
|
+
after=$(cat "$rec")
|
|
572
|
+
[ "$after" = "$before" ] || fail "scan paths must not mutate an undelivered record"
|
|
573
|
+
fm_pending_reply_mark_delivered "$state" "$corr" || fail "delivery marker should succeed"
|
|
574
|
+
fm_pending_reply_tick_one "$state" "$corr" unknown "$sm_home" \
|
|
575
|
+
|| fail "delivered direct tick should succeed"
|
|
576
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] \
|
|
577
|
+
|| fail "correlated parent status should resolve after delivery"
|
|
578
|
+
) || fail "undelivered scan immutability regression failed"
|
|
579
|
+
pass "undelivered records remain immutable across scan paths"
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
test_delivery_confirmation_fallback_reconciles() {
|
|
583
|
+
(
|
|
584
|
+
local home state corr rec marker rc prepared_corr prepared_rec prepared_marker escalations
|
|
585
|
+
local reported_corr reported_rec reported_marker
|
|
586
|
+
home=$(setup_parent delivery-confirmation)
|
|
587
|
+
state="$home/state"
|
|
588
|
+
# This fixture clock is intentionally scoped to the isolated subshell.
|
|
589
|
+
# shellcheck disable=SC2030,SC2031
|
|
590
|
+
export FM_PENDING_REPLY_NOW=5750
|
|
591
|
+
corr=$(fm_pending_reply_create "$home" "$state" hibit "confirmed delivery")
|
|
592
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
593
|
+
fm_pending_reply_mark_delivered() { return 1; }
|
|
594
|
+
if fm_pending_reply_confirm_delivery "$state" "$corr"; then
|
|
595
|
+
fail "primary delivery commit failure should be reported"
|
|
596
|
+
else
|
|
597
|
+
rc=$?
|
|
598
|
+
fi
|
|
599
|
+
[ "$rc" = 2 ] || fail "durable fallback should return status 2, got $rc"
|
|
600
|
+
marker=$(fm_pending_reply_delivery_confirmation_path "$state" "$corr")
|
|
601
|
+
[ -f "$marker" ] || fail "delivery confirmation fallback marker should persist"
|
|
602
|
+
[ -z "$(fm_pending_reply_get "$rec" delivered_epoch)" ] \
|
|
603
|
+
|| fail "failed primary commit should leave delivered_epoch empty"
|
|
604
|
+
. "$ROOT/bin/fm-pending-reply-lib.sh"
|
|
605
|
+
fm_pending_reply_tick_one "$state" "$corr" unknown \
|
|
606
|
+
|| fail "watcher should reconcile the delivery marker"
|
|
607
|
+
[ "$(fm_pending_reply_get "$rec" delivered_epoch)" = 5750 ] \
|
|
608
|
+
|| fail "watcher should restore the confirmed delivery epoch"
|
|
609
|
+
[ ! -e "$marker" ] || fail "reconciled delivery marker should be removed"
|
|
610
|
+
prepared_corr=$(fm_pending_reply_create "$home" "$state" hibit "prepared delivery")
|
|
611
|
+
prepared_rec=$(fm_pending_reply_path "$state" "$prepared_corr")
|
|
612
|
+
fm_pending_reply_prepare_delivery "$state" "$prepared_corr" \
|
|
613
|
+
|| fail "delivery preparation should persist before transport"
|
|
614
|
+
fm_pending_reply_set "$prepared_rec" grace_secs 10 \
|
|
615
|
+
|| fail "delivery-unknown grace fixture should persist"
|
|
616
|
+
prepared_marker=$(fm_pending_reply_delivery_confirmation_path "$state" "$prepared_corr")
|
|
617
|
+
[ -f "$prepared_marker" ] || fail "prepared delivery marker should persist"
|
|
618
|
+
fm_pending_reply_tick_one "$state" "$prepared_corr" unknown \
|
|
619
|
+
|| fail "watcher should preserve interrupted delivery state"
|
|
620
|
+
[ "$(phase_of "$state" "$prepared_corr")" = awaiting_report ] \
|
|
621
|
+
|| fail "attempted delivery should remain pending during bounded grace"
|
|
622
|
+
[ -z "$(fm_pending_reply_get "$prepared_rec" delivered_epoch)" ] \
|
|
623
|
+
|| fail "attempted delivery must never be promoted without confirmation"
|
|
624
|
+
[ -e "$prepared_marker" ] || fail "unknown delivery marker should remain durable"
|
|
625
|
+
export FM_PENDING_REPLY_NOW=5760
|
|
626
|
+
fm_pending_reply_write_delivery_confirmation \
|
|
627
|
+
"$state" "$prepared_corr" attempted 5750 \
|
|
628
|
+
|| fail "orphaned attempt fixture should persist"
|
|
629
|
+
fm_pending_reply_tick_one "$state" "$prepared_corr" unknown \
|
|
630
|
+
|| fail "orphaned delivery attempt should escalate"
|
|
631
|
+
[ "$(phase_of "$state" "$prepared_corr")" = escalated ] \
|
|
632
|
+
|| fail "orphaned delivery attempt should become one durable escalation"
|
|
633
|
+
[ -z "$(fm_pending_reply_get "$prepared_rec" delivered_epoch)" ] \
|
|
634
|
+
|| fail "delivery-unknown escalation must not manufacture delivery"
|
|
635
|
+
grep -Fq "pending-reply-delivery-unknown:" "$state/hibit.status" \
|
|
636
|
+
|| fail "delivery uncertainty should use its distinct escalation"
|
|
637
|
+
fm_pending_reply_tick_one "$state" "$prepared_corr" unknown \
|
|
638
|
+
|| fail "repeated delivery-unknown tick should be inert"
|
|
639
|
+
escalations=$(grep -Fc "blocked [key=pending-reply-$prepared_corr]:" "$state/hibit.status")
|
|
640
|
+
[ "$escalations" = 1 ] \
|
|
641
|
+
|| fail "delivery-unknown escalation should publish once, got $escalations"
|
|
642
|
+
printf 'done [corr=%s]: late report proves delivery\n' "$prepared_corr" >> "$state/hibit.status"
|
|
643
|
+
fm_pending_reply_tick "$state" || fail "watcher should accept a late delivery report"
|
|
644
|
+
[ "$(phase_of "$state" "$prepared_corr")" = resolved ] \
|
|
645
|
+
|| fail "late report should resolve escalated delivery-unknown"
|
|
646
|
+
[ "$(fm_pending_reply_get "$prepared_rec" delivered_epoch)" = 5760 ] \
|
|
647
|
+
|| fail "late report should provide delivery evidence"
|
|
648
|
+
escalations=$(grep -Fc "blocked [key=pending-reply-$prepared_corr]:" "$state/hibit.status")
|
|
649
|
+
[ "$escalations" = 1 ] || fail "late report must not re-escalate delivery-unknown"
|
|
650
|
+
fm_pending_reply_tick "$state" || fail "resolved late report should remain idempotent"
|
|
651
|
+
[ "$(phase_of "$state" "$prepared_corr")" = resolved ] \
|
|
652
|
+
|| fail "late report resolution should remain durable"
|
|
653
|
+
export FM_PENDING_REPLY_NOW=5800
|
|
654
|
+
reported_corr=$(fm_pending_reply_create "$home" "$state" hibit "reported delivery")
|
|
655
|
+
reported_rec=$(fm_pending_reply_path "$state" "$reported_corr")
|
|
656
|
+
fm_pending_reply_prepare_delivery "$state" "$reported_corr" \
|
|
657
|
+
|| fail "reported delivery attempt should persist"
|
|
658
|
+
reported_marker=$(fm_pending_reply_delivery_confirmation_path "$state" "$reported_corr")
|
|
659
|
+
printf 'done [corr=%s]: report proves delivery\n' "$reported_corr" >> "$state/hibit.status"
|
|
660
|
+
fm_pending_reply_try_resolve "$state" "$reported_corr" \
|
|
661
|
+
|| fail "attempted delivery with a report should resolve directly"
|
|
662
|
+
[ "$(phase_of "$state" "$reported_corr")" = resolved ] \
|
|
663
|
+
|| fail "correlated report should resolve attempted delivery"
|
|
664
|
+
[ "$(fm_pending_reply_get "$reported_rec" delivered_epoch)" = 5800 ] \
|
|
665
|
+
|| fail "correlated report should provide delivery evidence"
|
|
666
|
+
[ ! -e "$reported_marker" ] || fail "resolved delivery marker should be removed"
|
|
667
|
+
fm_pending_reply_tick_one "$state" "$reported_corr" unknown \
|
|
668
|
+
|| fail "resolved attempted delivery should remain inert in watcher"
|
|
669
|
+
if grep -Fq "pending-reply-id=$reported_corr" "$state/hibit.status"; then
|
|
670
|
+
fail "reported attempted delivery must not escalate as delivery-unknown"
|
|
671
|
+
fi
|
|
672
|
+
) || fail "delivery confirmation fallback regression failed"
|
|
673
|
+
pass "delivery confirmation fallback reconciles durably"
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
test_delivery_confirmation_serializes_with_reconciliation() {
|
|
677
|
+
(
|
|
678
|
+
local home state corr rec calls entered release confirm_pid reconcile_pid count i
|
|
679
|
+
home=$(setup_parent delivery-confirm-reconcile-race)
|
|
680
|
+
state="$home/state"
|
|
681
|
+
# This fixture clock is intentionally scoped to the isolated subshell.
|
|
682
|
+
# shellcheck disable=SC2030,SC2031
|
|
683
|
+
export FM_PENDING_REPLY_NOW=5900
|
|
684
|
+
corr=$(fm_pending_reply_create "$home" "$state" hibit "serialized delivery")
|
|
685
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
686
|
+
calls="$home/mark-delivered.calls"
|
|
687
|
+
entered="$home/mark-delivered.entered"
|
|
688
|
+
release="$home/mark-delivered.release"
|
|
689
|
+
fm_pending_reply_mark_delivered() {
|
|
690
|
+
local pending_state=$1 pending_corr=$2 epoch=$3 pending_rec phase
|
|
691
|
+
printf '%s\n' "$BASHPID" >> "$calls"
|
|
692
|
+
: > "$entered"
|
|
693
|
+
while [ ! -e "$release" ]; do /bin/sleep 0.01; done
|
|
694
|
+
pending_rec=$(fm_pending_reply_path "$pending_state" "$pending_corr")
|
|
695
|
+
fm_pending_reply_set "$pending_rec" delivered_epoch "$epoch" || return 1
|
|
696
|
+
phase=$(fm_pending_reply_get "$pending_rec" phase)
|
|
697
|
+
[ "$phase" != delivery_unknown ] \
|
|
698
|
+
|| fm_pending_reply_set "$pending_rec" phase awaiting_report
|
|
699
|
+
}
|
|
700
|
+
fm_pending_reply_confirm_delivery "$state" "$corr" &
|
|
701
|
+
# The background PID is consumed within this isolated test subshell.
|
|
702
|
+
# shellcheck disable=SC2031
|
|
703
|
+
confirm_pid=$!
|
|
704
|
+
for i in $(seq 1 100); do
|
|
705
|
+
[ -e "$entered" ] && break
|
|
706
|
+
/bin/sleep 0.01
|
|
707
|
+
done
|
|
708
|
+
[ -e "$entered" ] || fail "delivery confirmation did not reach its commit boundary"
|
|
709
|
+
fm_pending_reply_reconcile_delivery "$state" "$corr" &
|
|
710
|
+
# The background PID is consumed within this isolated test subshell.
|
|
711
|
+
# shellcheck disable=SC2031
|
|
712
|
+
reconcile_pid=$!
|
|
713
|
+
/bin/sleep 0.1
|
|
714
|
+
: > "$release"
|
|
715
|
+
wait "$confirm_pid" || fail "delivery confirmation should commit"
|
|
716
|
+
wait "$reconcile_pid" || fail "reconciliation should observe committed delivery"
|
|
717
|
+
count=$(wc -l < "$calls" | tr -d ' ')
|
|
718
|
+
[ "$count" = 1 ] \
|
|
719
|
+
|| fail "confirmation and reconciliation raced through $count delivery commits"
|
|
720
|
+
[ "$(fm_pending_reply_get "$rec" delivered_epoch)" = 5900 ] \
|
|
721
|
+
|| fail "serialized confirmation should retain delivered_epoch"
|
|
722
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] \
|
|
723
|
+
|| fail "serialized confirmation should retain awaiting_report phase"
|
|
724
|
+
[ ! -e "$(fm_pending_reply_delivery_confirmation_path "$state" "$corr")" ] \
|
|
725
|
+
|| fail "serialized delivery marker should be removed"
|
|
726
|
+
) || fail "delivery confirmation serialization regression failed"
|
|
727
|
+
pass "delivery confirmation serializes with reconciliation"
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
test_unrelated_and_stale_corr_cannot_resolve() {
|
|
731
|
+
local home state corr other
|
|
732
|
+
home=$(setup_parent stale-corr)
|
|
733
|
+
state="$home/state"
|
|
734
|
+
# Reset the fixture clock after isolated subshell tests.
|
|
735
|
+
# shellcheck disable=SC2031
|
|
736
|
+
export FM_PENDING_REPLY_NOW=6000
|
|
737
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "need answer")
|
|
738
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
739
|
+
other=$(fm_pending_reply_new_id)
|
|
740
|
+
printf 'done [corr=%s]: wrong token\n' "$other" > "$state/hibit.status"
|
|
741
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
742
|
+
fail "stale/wrong corr must not resolve"
|
|
743
|
+
fi
|
|
744
|
+
printf 'working: still thinking\n' >> "$state/hibit.status"
|
|
745
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
746
|
+
fail "unrelated working line must not resolve"
|
|
747
|
+
fi
|
|
748
|
+
printf 'done: finished without corr\n' >> "$state/hibit.status"
|
|
749
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
750
|
+
fail "status without corr must not resolve"
|
|
751
|
+
fi
|
|
752
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] || fail "phase must stay awaiting_report"
|
|
753
|
+
pass "unrelated events and stale correlation ids cannot resolve"
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
test_restart_preserves_expectation_and_parent_destination() {
|
|
757
|
+
local home state corr rec parent_status parent_home
|
|
758
|
+
home=$(setup_parent restart)
|
|
759
|
+
state="$home/state"
|
|
760
|
+
export FM_PENDING_REPLY_NOW=7000
|
|
761
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "survive restart")
|
|
762
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
763
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
764
|
+
parent_status=$(fm_pending_reply_get "$rec" parent_status)
|
|
765
|
+
parent_home=$(fm_pending_reply_get "$rec" parent_home)
|
|
766
|
+
# Simulate process restart: re-source library and re-read the same record.
|
|
767
|
+
# shellcheck source=bin/fm-pending-reply-lib.sh
|
|
768
|
+
. "$ROOT/bin/fm-pending-reply-lib.sh"
|
|
769
|
+
[ -f "$rec" ] || fail "record must survive restart"
|
|
770
|
+
[ "$(fm_pending_reply_get "$rec" parent_status)" = "$parent_status" ] \
|
|
771
|
+
|| fail "parent_status must be stable across restart"
|
|
772
|
+
[ "$(fm_pending_reply_get "$rec" parent_home)" = "$parent_home" ] \
|
|
773
|
+
|| fail "parent_home must be stable across restart"
|
|
774
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] || fail "phase preserved"
|
|
775
|
+
# Compaction-safe: destination is absolute path fields, not chat memory.
|
|
776
|
+
case "$parent_status" in
|
|
777
|
+
/*.status) : ;;
|
|
778
|
+
*) fail "parent_status should be an absolute status path, got $parent_status" ;;
|
|
779
|
+
esac
|
|
780
|
+
pass "restart preserves expectation and exact parent destination"
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
test_wrong_home_detected_not_acknowledged() {
|
|
784
|
+
local home state sm_home corr rec hits
|
|
785
|
+
home=$(setup_parent wrong-home)
|
|
786
|
+
state="$home/state"
|
|
787
|
+
sm_home="$TMP_ROOT/sm-home-$RANDOM"
|
|
788
|
+
mkdir -p "$sm_home/state"
|
|
789
|
+
export FM_PENDING_REPLY_NOW=8000
|
|
790
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "report to parent")
|
|
791
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
792
|
+
# Historical incident shape: report written under the secondmate home.
|
|
793
|
+
printf 'done [corr=%s]: stranded in self-home\n' "$corr" > "$sm_home/state/hibit.status"
|
|
794
|
+
fm_pending_reply_detect_wrong_home "$state" "$corr" "$sm_home" \
|
|
795
|
+
|| fail "wrong-home detect should succeed"
|
|
796
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
797
|
+
hits=$(fm_pending_reply_get "$rec" wrong_home_hits)
|
|
798
|
+
[ "$hits" = 1 ] || fail "first wrong-home sighting should count once, got $hits"
|
|
799
|
+
fm_pending_reply_detect_wrong_home "$state" "$corr" "$sm_home" \
|
|
800
|
+
|| fail "repeated wrong-home detect should succeed"
|
|
801
|
+
hits=$(fm_pending_reply_get "$rec" wrong_home_hits)
|
|
802
|
+
[ "$hits" = 1 ] || fail "unchanged wrong-home history should remain one hit, got $hits"
|
|
803
|
+
printf 'done [corr=%s]: second stranded report\n' "$corr" >> "$sm_home/state/hibit.status"
|
|
804
|
+
fm_pending_reply_detect_wrong_home "$state" "$corr" "$sm_home" \
|
|
805
|
+
|| fail "new wrong-home sighting detect should succeed"
|
|
806
|
+
hits=$(fm_pending_reply_get "$rec" wrong_home_hits)
|
|
807
|
+
[ "$hits" = 2 ] || fail "distinct wrong-home reports should each count once, got $hits"
|
|
808
|
+
fm_pending_reply_detect_wrong_home "$state" "$corr" "$sm_home" \
|
|
809
|
+
|| fail "repeated distinct wrong-home detect should succeed"
|
|
810
|
+
hits=$(fm_pending_reply_get "$rec" wrong_home_hits)
|
|
811
|
+
[ "$hits" = 2 ] || fail "repeated polling should preserve two distinct hits, got $hits"
|
|
812
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] \
|
|
813
|
+
|| fail "wrong-home must not silently acknowledge (phase=$(phase_of "$state" "$corr"))"
|
|
814
|
+
if fm_pending_reply_try_resolve "$state" "$corr"; then
|
|
815
|
+
fail "wrong-home status must not resolve via parent path"
|
|
816
|
+
fi
|
|
817
|
+
pass "wrong-home reports are detected but do not silently acknowledge"
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
test_unmarked_captain_input_creates_no_expectation() {
|
|
821
|
+
local dir fb log home rc pending_count
|
|
822
|
+
dir="$TMP_ROOT/unmarked"; mkdir -p "$dir"
|
|
823
|
+
fb=$(make_stubs "$dir"); log="$dir/send.log"
|
|
824
|
+
home=$(setup_parent unmarked)
|
|
825
|
+
# Crewmate target stays unmarked and creates no pending-reply record.
|
|
826
|
+
fm_write_meta "$home/state/build.meta" \
|
|
827
|
+
"window=sess:fm-build" "worktree=$home/wt" "project=$home/p" \
|
|
828
|
+
"harness=echo" "kind=ship" "mode=no-mistakes" "yolo=off"
|
|
829
|
+
run_send "$fb" "$home" "$log" "build" "captain says hello"; rc=$?
|
|
830
|
+
expect_code 0 "$rc" "unmarked crewmate send should succeed"
|
|
831
|
+
[ "$(latest_record_body "$home" build)" = "captain says hello" ] \
|
|
832
|
+
|| fail "crewmate steer should be recorded unmarked"$'\n'"$(latest_record_body "$home" build | od -An -c)"
|
|
833
|
+
pending_count=$(find "$home/state/pending-replies" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
834
|
+
[ "$pending_count" = 0 ] || fail "unmarked input must create no pending-reply records (got $pending_count)"
|
|
835
|
+
pass "direct unmarked captain input creates no expectation"
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
test_fm_send_marked_secondmate_creates_pending_and_embeds_corr() {
|
|
839
|
+
local dir fb log home rc got corr rec
|
|
840
|
+
dir="$TMP_ROOT/send-pending"; mkdir -p "$dir"
|
|
841
|
+
fb=$(make_stubs "$dir"); log="$dir/send.log"
|
|
842
|
+
home=$(setup_parent send-pending)
|
|
843
|
+
fm_write_secondmate_meta "$home/state/hibit.meta" "$home/sm" "sess:fm-hibit"
|
|
844
|
+
run_send "$fb" "$home" "$log" "hibit" "audit the build"; rc=$?
|
|
845
|
+
expect_code 0 "$rc" "secondmate send should succeed"
|
|
846
|
+
got=$(latest_record_body "$home" hibit)
|
|
847
|
+
case "$got" in
|
|
848
|
+
"$FM_FROMFIRST_MARK"corr=*) : ;;
|
|
849
|
+
*) fail "secondmate steer record must embed marker+corr"$'\n'"$(printf '%s' "$got" | od -An -c)" ;;
|
|
850
|
+
esac
|
|
851
|
+
corr=$(fm_pending_reply_extract_corr "$got")
|
|
852
|
+
[ "${#corr}" -eq 16 ] || fail "corr id should be 16 hex chars, got '$corr'"
|
|
853
|
+
rec=$(fm_pending_reply_path "$home/state" "$corr")
|
|
854
|
+
[ -f "$rec" ] || fail "pending-reply record must exist after marked send"
|
|
855
|
+
[ "$(fm_pending_reply_get "$rec" phase)" = awaiting_report ] \
|
|
856
|
+
|| fail "phase should be awaiting_report after delivery"
|
|
857
|
+
[ -n "$(fm_pending_reply_get "$rec" delivered_epoch)" ] \
|
|
858
|
+
|| fail "delivered_epoch must be set after successful send"
|
|
859
|
+
[ "$(fm_pending_reply_get "$rec" task_id)" = hibit ] \
|
|
860
|
+
|| fail "task_id must match secondmate id"
|
|
861
|
+
pass "fm-send marked secondmate path creates pending and embeds corr"
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
test_document_pointer_resolves() {
|
|
865
|
+
local home state corr
|
|
866
|
+
home=$(setup_parent doc-pointer)
|
|
867
|
+
state="$home/state"
|
|
868
|
+
export FM_PENDING_REPLY_NOW=9000
|
|
869
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "deep audit")
|
|
870
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
871
|
+
printf 'done [corr=%s]: see data/hibit/report.md\n' "$corr" > "$state/hibit.status"
|
|
872
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "document pointer status should resolve"
|
|
873
|
+
[ "$(fm_pending_reply_get "$(fm_pending_reply_path "$state" "$corr")" resolved_via)" = document ] \
|
|
874
|
+
|| fail "resolved_via should be document"
|
|
875
|
+
pass "status-pointed document resolves the expectation"
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
test_helper_report_resolves() {
|
|
879
|
+
local home state corr
|
|
880
|
+
home=$(setup_parent helper)
|
|
881
|
+
state="$home/state"
|
|
882
|
+
export FM_PENDING_REPLY_NOW=9100
|
|
883
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "quick answer")
|
|
884
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
885
|
+
"$REPORT" "$state/hibit.status" "done" "$corr" "all good" \
|
|
886
|
+
|| fail "helper report failed"
|
|
887
|
+
fm_pending_reply_try_resolve "$state" "$corr" || fail "helper report should resolve"
|
|
888
|
+
[ "$(fm_pending_reply_get "$(fm_pending_reply_path "$state" "$corr")" resolved_via)" = helper ] \
|
|
889
|
+
|| fail "resolved_via should be helper"
|
|
890
|
+
pass "optional helper report resolves without being required for correctness"
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
test_busy_idle_observation_via_backend_abstraction() {
|
|
894
|
+
local home state corr
|
|
895
|
+
home=$(setup_parent busy-idle)
|
|
896
|
+
state="$home/state"
|
|
897
|
+
export FM_PENDING_REPLY_NOW=9200
|
|
898
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "backend turn")
|
|
899
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
900
|
+
# Simulates Pi/Claude secondmate busy_state from fm_backend_busy_state without
|
|
901
|
+
# reading conversation text (herdr native idle/busy or tmux unknown fallback).
|
|
902
|
+
fm_pending_reply_observe_busy "$state" "$corr" unknown
|
|
903
|
+
[ -z "$(fm_pending_reply_get "$(fm_pending_reply_path "$state" "$corr")" request_turn_completed_epoch)" ] \
|
|
904
|
+
|| fail "unknown busy_state must not prove turn completion"
|
|
905
|
+
fm_pending_reply_observe_busy "$state" "$corr" busy
|
|
906
|
+
fm_pending_reply_observe_busy "$state" "$corr" idle
|
|
907
|
+
[ -n "$(fm_pending_reply_get "$(fm_pending_reply_path "$state" "$corr")" request_turn_completed_epoch)" ] \
|
|
908
|
+
|| fail "busy->idle must prove turn completion"
|
|
909
|
+
pass "backend busy/idle observation covers Pi/Claude paths without conversation scrape"
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
test_unknown_backend_state_uses_capture_fallback() {
|
|
913
|
+
local backend
|
|
914
|
+
for backend in tmux zellij; do
|
|
915
|
+
(
|
|
916
|
+
local home state corr rec sm_home
|
|
917
|
+
home=$(setup_parent "fallback-$backend")
|
|
918
|
+
state="$home/state"
|
|
919
|
+
sm_home="$home/sm"
|
|
920
|
+
mkdir -p "$sm_home/state"
|
|
921
|
+
export FM_PENDING_REPLY_GRACE_SECS=10
|
|
922
|
+
# These fixture overrides are intentionally scoped to the isolated subshell.
|
|
923
|
+
# shellcheck disable=SC2030,SC2031
|
|
924
|
+
export FM_PENDING_REPLY_NOW=10000
|
|
925
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "$backend fallback")
|
|
926
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
927
|
+
fm_write_secondmate_meta "$state/hibit.meta" "$sm_home" "session:fm-hibit" alpha pi
|
|
928
|
+
[ "$backend" = tmux ] || printf 'backend=%s\n' "$backend" >> "$state/hibit.meta"
|
|
929
|
+
fm_backend_busy_state() { printf 'unknown'; }
|
|
930
|
+
fm_backend_capture() { printf '%s' "$FM_PENDING_TEST_CAPTURE"; }
|
|
931
|
+
# Invoked indirectly through FM_PENDING_REPLY_SEND_HOOK.
|
|
932
|
+
# shellcheck disable=SC2329
|
|
933
|
+
recovery_hook() { :; }
|
|
934
|
+
# This hook override is intentionally scoped to the isolated subshell.
|
|
935
|
+
# shellcheck disable=SC2030,SC2031
|
|
936
|
+
export FM_PENDING_REPLY_SEND_HOOK=recovery_hook
|
|
937
|
+
export FM_PENDING_TEST_CAPTURE='idle footer'
|
|
938
|
+
fm_pending_reply_tick "$state"
|
|
939
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
940
|
+
[ -z "$(fm_pending_reply_get "$rec" request_turn_completed_epoch)" ] \
|
|
941
|
+
|| fail "$backend fallback must not accept stale idle before grace"
|
|
942
|
+
# Continue advancing the subshell-local fixture clock.
|
|
943
|
+
# shellcheck disable=SC2030,SC2031
|
|
944
|
+
export FM_PENDING_REPLY_NOW=10010
|
|
945
|
+
fm_pending_reply_tick "$state"
|
|
946
|
+
[ "$(phase_of "$state" "$corr")" = recovery_sent ] \
|
|
947
|
+
|| fail "$backend fallback idle should trigger recovery after grace"
|
|
948
|
+
export FM_PENDING_REPLY_NOW=10011
|
|
949
|
+
export FM_PENDING_TEST_CAPTURE='Working...'
|
|
950
|
+
fm_pending_reply_tick "$state"
|
|
951
|
+
export FM_PENDING_REPLY_NOW=10012
|
|
952
|
+
export FM_PENDING_TEST_CAPTURE='idle footer'
|
|
953
|
+
fm_pending_reply_tick "$state"
|
|
954
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] \
|
|
955
|
+
|| fail "$backend capture busy-to-idle should complete recovery turn"
|
|
956
|
+
) || fail "$backend unknown-state capture fallback failed"
|
|
957
|
+
done
|
|
958
|
+
pass "tmux and zellij unknown states use bounded capture fallback"
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
test_kimi_capture_fallback_uses_recorded_harness() (
|
|
962
|
+
local home state corr rec sm_home
|
|
963
|
+
home=$(setup_parent kimi-fallback)
|
|
964
|
+
state="$home/state"
|
|
965
|
+
sm_home="$home/sm"
|
|
966
|
+
mkdir -p "$sm_home/state"
|
|
967
|
+
# This fixture clock is intentionally scoped to the isolated subshell.
|
|
968
|
+
# shellcheck disable=SC2030,SC2031
|
|
969
|
+
export FM_PENDING_REPLY_NOW=10020
|
|
970
|
+
corr=$(fm_pending_reply_create "$home" "$state" hibit "kimi fallback")
|
|
971
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
972
|
+
fm_write_secondmate_meta "$state/hibit.meta" "$sm_home" "session:fm-hibit" alpha kimi
|
|
973
|
+
fm_backend_busy_state() { printf 'unknown'; }
|
|
974
|
+
fm_backend_capture() { printf '%s' "$FM_PENDING_KIMI_CAPTURE"; }
|
|
975
|
+
export FM_PENDING_KIMI_CAPTURE=' 🌑 · Tip: ask Kimi to schedule tasks, e.g. "remind me at 5pm"'
|
|
976
|
+
|
|
977
|
+
[ "$(fm_pending_reply_backend_observation tmux session:fm-hibit fm-hibit codex)" = fallback-idle ] \
|
|
978
|
+
|| fail "Kimi spinner leaked into another harness"
|
|
979
|
+
export FM_PENDING_KIMI_CAPTURE='Ctrl+c:cancel'
|
|
980
|
+
[ "$(fm_pending_reply_backend_observation tmux session:fm-hibit fm-hibit kimi)" = fallback-idle ] \
|
|
981
|
+
|| fail "Grok's exact busy token leaked into Kimi pending-reply observation"
|
|
982
|
+
export FM_PENDING_KIMI_CAPTURE=' 🌑 · Tip: ask Kimi to schedule tasks, e.g. "remind me at 5pm"'
|
|
983
|
+
fm_pending_reply_tick "$state"
|
|
984
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
985
|
+
[ "$(fm_pending_reply_get "$rec" turn_seen_busy)" = 1 ] \
|
|
986
|
+
|| fail "recorded Kimi spinner was not observed as busy"
|
|
987
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] \
|
|
988
|
+
|| fail "working Kimi secondmate entered recovery"
|
|
989
|
+
pass "pending replies scope Kimi capture fallback by recorded harness"
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
test_tick_skips_terminal_and_reuses_target_observation() {
|
|
993
|
+
(
|
|
994
|
+
local home state open1 open2 resolved escalated rec probe_log probes scan_log scans snapshot
|
|
995
|
+
home=$(setup_parent observation-cache)
|
|
996
|
+
state="$home/state"
|
|
997
|
+
probe_log="$home/backend-probes.log"
|
|
998
|
+
scan_log="$home/status-scans.log"
|
|
999
|
+
: > "$probe_log"
|
|
1000
|
+
: > "$scan_log"
|
|
1001
|
+
# This fixture clock is intentionally scoped to the isolated subshell.
|
|
1002
|
+
# shellcheck disable=SC2030,SC2031
|
|
1003
|
+
export FM_PENDING_REPLY_NOW=10100
|
|
1004
|
+
open1=$(fm_pending_reply_create "$home" "$state" hibit "first open request")
|
|
1005
|
+
open2=$(fm_pending_reply_create "$home" "$state" hibit "second open request")
|
|
1006
|
+
fm_pending_reply_mark_delivered "$state" "$open1"
|
|
1007
|
+
fm_pending_reply_mark_delivered "$state" "$open2"
|
|
1008
|
+
resolved=$(fm_pending_reply_create "$home" "$state" resolved "resolved request")
|
|
1009
|
+
fm_pending_reply_mark_delivered "$state" "$resolved"
|
|
1010
|
+
printf 'done [corr=%s]: complete\n' "$resolved" > "$state/resolved.status"
|
|
1011
|
+
fm_pending_reply_try_resolve "$state" "$resolved" || fail "resolved fixture should resolve"
|
|
1012
|
+
escalated=$(fm_pending_reply_create "$home" "$state" escalated "escalated request")
|
|
1013
|
+
fm_pending_reply_mark_delivered "$state" "$escalated"
|
|
1014
|
+
rec=$(fm_pending_reply_path "$state" "$escalated")
|
|
1015
|
+
fm_pending_reply_set "$rec" phase escalated || fail "escalated fixture should transition"
|
|
1016
|
+
mkdir -p "$home/escalated/state"
|
|
1017
|
+
printf 'done [corr=%s]: wrong home\n' "$escalated" > "$home/escalated/state/escalated.status"
|
|
1018
|
+
fm_write_secondmate_meta "$state/hibit.meta" "$home/hibit" "sess:fm-hibit"
|
|
1019
|
+
fm_write_secondmate_meta "$state/resolved.meta" "$home/resolved" "sess:fm-resolved"
|
|
1020
|
+
fm_write_secondmate_meta "$state/escalated.meta" "$home/escalated" "sess:fm-escalated"
|
|
1021
|
+
# Runtime overrides called indirectly by the pending-reply tick.
|
|
1022
|
+
# shellcheck disable=SC2329
|
|
1023
|
+
fm_backend_busy_state() {
|
|
1024
|
+
printf '%s\t%s\n' "$1" "$2" >> "$probe_log"
|
|
1025
|
+
printf 'busy'
|
|
1026
|
+
}
|
|
1027
|
+
# shellcheck disable=SC2329
|
|
1028
|
+
fm_backend_capture() { fail "native busy observations should not capture"; }
|
|
1029
|
+
# shellcheck disable=SC2329
|
|
1030
|
+
fm_pending_reply_find_resolve_line() {
|
|
1031
|
+
local status_file=$1 corr=$2 line
|
|
1032
|
+
printf '%s\t%s\n' "$status_file" "$corr" >> "$scan_log"
|
|
1033
|
+
[ -f "$status_file" ] || return 0
|
|
1034
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
1035
|
+
fm_pending_reply_line_resolves "$line" "$corr" || continue
|
|
1036
|
+
printf '%s' "$line"
|
|
1037
|
+
return 0
|
|
1038
|
+
done < "$status_file"
|
|
1039
|
+
return 0
|
|
1040
|
+
}
|
|
1041
|
+
fm_pending_reply_tick "$state"
|
|
1042
|
+
probes=$(wc -l < "$probe_log" | tr -d ' ')
|
|
1043
|
+
[ "$probes" = 1 ] || fail "two open records for one target should use one probe, got $probes"
|
|
1044
|
+
rec=$(fm_pending_reply_path "$state" "$open1")
|
|
1045
|
+
[ "$(fm_pending_reply_get "$rec" turn_seen_busy)" = 1 ] \
|
|
1046
|
+
|| fail "cached observation should update the first open record"
|
|
1047
|
+
rec=$(fm_pending_reply_path "$state" "$open2")
|
|
1048
|
+
[ "$(fm_pending_reply_get "$rec" turn_seen_busy)" = 1 ] \
|
|
1049
|
+
|| fail "cached observation should update the second open record"
|
|
1050
|
+
rec=$(fm_pending_reply_path "$state" "$escalated")
|
|
1051
|
+
snapshot=$(fm_pending_reply_get "$rec" wrong_home_scan_signature)
|
|
1052
|
+
[ -n "$snapshot" ] || fail "wrong-home scan should persist its file-set signature"
|
|
1053
|
+
fm_pending_reply_tick "$state"
|
|
1054
|
+
scans=$(wc -l < "$scan_log" | tr -d ' ')
|
|
1055
|
+
[ "$scans" = 3 ] \
|
|
1056
|
+
|| fail "unchanged records should scan two open and one escalated status only once, got $scans"
|
|
1057
|
+
[ "$(fm_pending_reply_get "$rec" wrong_home_scan_signature)" = "$snapshot" ] \
|
|
1058
|
+
|| fail "unchanged wrong-home logs should retain their scan signature"
|
|
1059
|
+
) || fail "terminal-skip and observation-cache regression failed"
|
|
1060
|
+
pass "tick skips terminal records and reuses target observations"
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
test_correlations_reuse_only_for_matching_open_task() {
|
|
1064
|
+
local dir fb log home state got corr1 corr2 corr3 rec
|
|
1065
|
+
dir="$TMP_ROOT/corr-reuse"; mkdir -p "$dir"
|
|
1066
|
+
fb=$(make_stubs "$dir"); log="$dir/send.log"
|
|
1067
|
+
home=$(setup_parent corr-reuse)
|
|
1068
|
+
state="$home/state"
|
|
1069
|
+
fm_write_secondmate_meta "$state/domain.meta" "$home/domain" "sess:fm-domain"
|
|
1070
|
+
fm_write_secondmate_meta "$state/other.meta" "$home/other" "sess:fm-other"
|
|
1071
|
+
run_send "$fb" "$home" "$log" domain "first request" || fail "first marked send failed"
|
|
1072
|
+
got=$(latest_record_body "$home" domain)
|
|
1073
|
+
corr1=$(fm_pending_reply_extract_corr "$got")
|
|
1074
|
+
export FM_PENDING_REPLY_EXISTING_CORR=$corr1
|
|
1075
|
+
if run_send "$fb" "$home" "$log" other "forwarded request"; then
|
|
1076
|
+
fail "an explicit cross-task correlation must be refused"
|
|
1077
|
+
fi
|
|
1078
|
+
unset FM_PENDING_REPLY_EXISTING_CORR
|
|
1079
|
+
if latest_record_body "$home" other >/dev/null 2>&1; then
|
|
1080
|
+
fail "a refused cross-task correlation must not enqueue a steer"
|
|
1081
|
+
fi
|
|
1082
|
+
run_send "$fb" "$home" "$log" other "forwarded request" \
|
|
1083
|
+
|| fail "fresh cross-task send failed"
|
|
1084
|
+
corr2=$(fm_pending_reply_extract_corr "$(latest_record_body "$home" other)")
|
|
1085
|
+
[ -n "$corr2" ] && [ "$corr2" != "$corr1" ] \
|
|
1086
|
+
|| fail "cross-task send must receive a new correlation"
|
|
1087
|
+
rec=$(fm_pending_reply_path "$state" "$corr2")
|
|
1088
|
+
[ "$(fm_pending_reply_get "$rec" task_id)" = other ] \
|
|
1089
|
+
|| fail "cross-task expectation must belong to the new target"
|
|
1090
|
+
printf 'done [corr=%s]: complete\n' "$corr1" > "$state/domain.status"
|
|
1091
|
+
fm_pending_reply_try_resolve "$state" "$corr1" || fail "first expectation should resolve"
|
|
1092
|
+
run_send "$fb" "$home" "$log" domain "${FM_FROMFIRST_MARK}corr=${corr1} follow-up" \
|
|
1093
|
+
|| fail "resolved-correlation follow-up failed"
|
|
1094
|
+
corr3=$(fm_pending_reply_extract_corr "$(latest_record_body "$home" domain)")
|
|
1095
|
+
[ -n "$corr3" ] && [ "$corr3" != "$corr1" ] \
|
|
1096
|
+
|| fail "resolved correlation must not guard a new send"
|
|
1097
|
+
rec=$(fm_pending_reply_path "$state" "$corr3")
|
|
1098
|
+
[ "$(fm_pending_reply_get "$rec" task_id)" = domain ] \
|
|
1099
|
+
|| fail "replacement expectation must belong to the current target"
|
|
1100
|
+
pass "correlations are reused only for matching open task records"
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
test_tick_end_to_end_missed_then_escalate() {
|
|
1104
|
+
local home state corr hook_log sm_home
|
|
1105
|
+
home=$(setup_parent tick-e2e)
|
|
1106
|
+
state="$home/state"
|
|
1107
|
+
sm_home="$home/sm"
|
|
1108
|
+
mkdir -p "$sm_home/state"
|
|
1109
|
+
hook_log="$TMP_ROOT/tick-hook.log"
|
|
1110
|
+
: > "$hook_log"
|
|
1111
|
+
recovery_hook() { printf 'recovered\n' >> "$hook_log"; }
|
|
1112
|
+
export -f recovery_hook
|
|
1113
|
+
# Reset hook and clock fixtures after isolated subshell tests.
|
|
1114
|
+
# shellcheck disable=SC2031
|
|
1115
|
+
export FM_PENDING_REPLY_SEND_HOOK='recovery_hook'
|
|
1116
|
+
# shellcheck disable=SC2031
|
|
1117
|
+
export FM_PENDING_REPLY_NOW=9300
|
|
1118
|
+
|
|
1119
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "e2e miss")
|
|
1120
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
1121
|
+
fm_write_secondmate_meta "$state/hibit.meta" "$sm_home" "sess:fm-hibit"
|
|
1122
|
+
# Override backend busy via direct tick_one (backend may be unknown in hermetic home).
|
|
1123
|
+
fm_pending_reply_tick_one "$state" "$corr" busy "$sm_home"
|
|
1124
|
+
fm_pending_reply_tick_one "$state" "$corr" idle "$sm_home"
|
|
1125
|
+
[ "$(phase_of "$state" "$corr")" = recovery_sent ] \
|
|
1126
|
+
|| fail "tick should send recovery after idle+grace, got $(phase_of "$state" "$corr")"
|
|
1127
|
+
[ -s "$hook_log" ] || fail "recovery should have been sent via tick"
|
|
1128
|
+
# Recovery turn completes empty.
|
|
1129
|
+
fm_pending_reply_tick_one "$state" "$corr" busy "$sm_home"
|
|
1130
|
+
fm_pending_reply_tick_one "$state" "$corr" idle "$sm_home"
|
|
1131
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] \
|
|
1132
|
+
|| fail "tick should escalate after second miss, got $(phase_of "$state" "$corr")"
|
|
1133
|
+
# Expired age must not erase the unresolved record.
|
|
1134
|
+
export FM_PENDING_REPLY_NOW=999999
|
|
1135
|
+
fm_pending_reply_tick_one "$state" "$corr" idle "$sm_home"
|
|
1136
|
+
[ -f "$(fm_pending_reply_path "$state" "$corr")" ] \
|
|
1137
|
+
|| fail "expiration must never silently erase an unresolved reply"
|
|
1138
|
+
[ "$(phase_of "$state" "$corr")" = escalated ] || fail "must stay escalated"
|
|
1139
|
+
pass "tick end-to-end: miss -> one recovery -> escalate -> durable"
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
test_remote_repost_waits_for_the_reply_channel() {
|
|
1143
|
+
local home state corr hook_log rec lines
|
|
1144
|
+
home=$(setup_parent remote-repost)
|
|
1145
|
+
state="$home/state"
|
|
1146
|
+
hook_log="$TMP_ROOT/remote-repost.log"
|
|
1147
|
+
: > "$hook_log"
|
|
1148
|
+
export FM_PENDING_REPLY_NOW=5000
|
|
1149
|
+
# Invoked indirectly through FM_PENDING_REPLY_SEND_HOOK.
|
|
1150
|
+
# shellcheck disable=SC2329
|
|
1151
|
+
remote_repost_hook() {
|
|
1152
|
+
printf '%s\t%s\n' "$1" "$2" >> "$hook_log"
|
|
1153
|
+
}
|
|
1154
|
+
export -f remote_repost_hook
|
|
1155
|
+
export FM_PENDING_REPLY_SEND_HOOK=remote_repost_hook
|
|
1156
|
+
|
|
1157
|
+
fm_write_meta "$state/ios.meta" \
|
|
1158
|
+
"window=fm-remote:w1:p1" "harness=claude" "kind=secondmate" "mode=secondmate" \
|
|
1159
|
+
"remote_host=remote-mac" "remote_root=/remote/root" "remote_backend=herdr"
|
|
1160
|
+
corr=$(fm_pending_reply_create "$home" "$state" "ios" "status of the iOS build")
|
|
1161
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
1162
|
+
fm_pending_reply_observe_busy "$state" "$corr" busy
|
|
1163
|
+
fm_pending_reply_observe_busy "$state" "$corr" idle
|
|
1164
|
+
rec=$(fm_pending_reply_path "$state" "$corr")
|
|
1165
|
+
|
|
1166
|
+
# The mate's turn ended, but nothing proves the parent has read the remote
|
|
1167
|
+
# reply log since: a repost here would nag for a reply already written there.
|
|
1168
|
+
if fm_pending_reply_send_recovery "$state" "$corr" 2>/dev/null; then
|
|
1169
|
+
fail "a remote repost must not fire before the reply channel is known caught up"
|
|
1170
|
+
fi
|
|
1171
|
+
[ ! -s "$hook_log" ] || fail "no repost may be sent while the reply channel is behind"
|
|
1172
|
+
[ "$(phase_of "$state" "$corr")" = awaiting_report ] \
|
|
1173
|
+
|| fail "the expectation must stay armed while the reply channel is behind"
|
|
1174
|
+
|
|
1175
|
+
# A watermark from BEFORE the turn ended is still not evidence.
|
|
1176
|
+
fm_pending_reply_note_remote_channel_caught_up "$state" ios 4000
|
|
1177
|
+
if fm_pending_reply_send_recovery "$state" "$corr" 2>/dev/null; then
|
|
1178
|
+
fail "a stale reply-channel watermark must not license a repost"
|
|
1179
|
+
fi
|
|
1180
|
+
[ ! -s "$hook_log" ] || fail "a stale watermark must not release a repost"
|
|
1181
|
+
|
|
1182
|
+
# Read through the end of the remote log after the turn: the report really is
|
|
1183
|
+
# missing, so the one recovery repost fires.
|
|
1184
|
+
fm_pending_reply_note_remote_channel_caught_up "$state" ios \
|
|
1185
|
+
"$(fm_pending_reply_get "$rec" request_turn_completed_epoch)"
|
|
1186
|
+
fm_pending_reply_send_recovery "$state" "$corr" \
|
|
1187
|
+
|| fail "a genuinely missed remote report must still trigger its recovery repost"
|
|
1188
|
+
[ "$(phase_of "$state" "$corr")" = recovery_sent ] \
|
|
1189
|
+
|| fail "phase should be recovery_sent, got $(phase_of "$state" "$corr")"
|
|
1190
|
+
lines=$(wc -l < "$hook_log" | tr -d ' ')
|
|
1191
|
+
[ "$lines" = 1 ] || fail "expected exactly one repost, got $lines"
|
|
1192
|
+
case "$(cat "$hook_log")" in
|
|
1193
|
+
*REPOST\ REQUIRED*) : ;;
|
|
1194
|
+
*) fail "the recovery message must ask for a repost"$'\n'"$(cat "$hook_log")" ;;
|
|
1195
|
+
esac
|
|
1196
|
+
unset FM_PENDING_REPLY_SEND_HOOK
|
|
1197
|
+
pass "a remote repost waits for the reply channel and still fires on a real miss"
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
test_mirrored_remote_reply_never_triggers_a_repost() {
|
|
1201
|
+
local home state corr hook_log
|
|
1202
|
+
home=$(setup_parent remote-mirrored-reply)
|
|
1203
|
+
state="$home/state"
|
|
1204
|
+
hook_log="$TMP_ROOT/remote-mirrored-reply.log"
|
|
1205
|
+
: > "$hook_log"
|
|
1206
|
+
export FM_PENDING_REPLY_NOW=6000
|
|
1207
|
+
# Invoked indirectly through FM_PENDING_REPLY_SEND_HOOK.
|
|
1208
|
+
# shellcheck disable=SC2329
|
|
1209
|
+
mirrored_reply_hook() {
|
|
1210
|
+
printf '%s\t%s\n' "$1" "$2" >> "$hook_log"
|
|
1211
|
+
}
|
|
1212
|
+
export -f mirrored_reply_hook
|
|
1213
|
+
export FM_PENDING_REPLY_SEND_HOOK=mirrored_reply_hook
|
|
1214
|
+
|
|
1215
|
+
fm_write_meta "$state/ios.meta" \
|
|
1216
|
+
"window=fm-remote:w1:p1" "harness=claude" "kind=secondmate" "mode=secondmate" \
|
|
1217
|
+
"remote_host=remote-mac" "remote_root=/remote/root" "remote_backend=herdr"
|
|
1218
|
+
corr=$(fm_pending_reply_create "$home" "$state" "ios" "did the build go green")
|
|
1219
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
1220
|
+
fm_pending_reply_mark_turn_completed "$state" "$corr" request
|
|
1221
|
+
# The mirror caught up AND carried the mate's correlated answer.
|
|
1222
|
+
printf 'done [corr=%s]: build is green\n' "$corr" > "$state/ios.status"
|
|
1223
|
+
fm_pending_reply_note_remote_channel_caught_up "$state" ios 6000
|
|
1224
|
+
|
|
1225
|
+
fm_pending_reply_tick_one "$state" "$corr" idle || fail "tick should succeed"
|
|
1226
|
+
[ "$(phase_of "$state" "$corr")" = resolved ] \
|
|
1227
|
+
|| fail "a mirrored correlated reply must resolve, got $(phase_of "$state" "$corr")"
|
|
1228
|
+
[ ! -s "$hook_log" ] || fail "a correlated remote reply must never trigger a repost"
|
|
1229
|
+
unset FM_PENDING_REPLY_SEND_HOOK
|
|
1230
|
+
pass "a mirrored correlated remote reply resolves without any repost"
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
test_failed_send_discards_undelivered_expectation() {
|
|
1234
|
+
local home state corr
|
|
1235
|
+
home=$(setup_parent discard)
|
|
1236
|
+
state="$home/state"
|
|
1237
|
+
export FM_PENDING_REPLY_NOW=9400
|
|
1238
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "never lands")
|
|
1239
|
+
# Not delivered: discard is allowed.
|
|
1240
|
+
fm_pending_reply_discard_undelivered "$state" "$corr" || fail "discard undelivered failed"
|
|
1241
|
+
[ ! -f "$(fm_pending_reply_path "$state" "$corr")" ] \
|
|
1242
|
+
|| fail "undelivered record should be removed"
|
|
1243
|
+
# Delivered records must not be discarded by this path.
|
|
1244
|
+
corr=$(fm_pending_reply_create "$home" "$state" "hibit" "landed")
|
|
1245
|
+
fm_pending_reply_mark_delivered "$state" "$corr"
|
|
1246
|
+
if fm_pending_reply_discard_undelivered "$state" "$corr" 2>/dev/null; then
|
|
1247
|
+
fail "delivered record must not be discarded"
|
|
1248
|
+
fi
|
|
1249
|
+
[ -f "$(fm_pending_reply_path "$state" "$corr")" ] || fail "delivered record must remain"
|
|
1250
|
+
pass "failed transport discards undelivered expectation only"
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
# --- run --------------------------------------------------------------------
|
|
1254
|
+
|
|
1255
|
+
test_normal_correlated_reply_resolves_once
|
|
1256
|
+
test_completed_turn_no_report_triggers_one_recovery
|
|
1257
|
+
test_recovery_attempt_is_never_reinjected
|
|
1258
|
+
test_recovery_reply_resolves_original
|
|
1259
|
+
test_second_missed_turn_escalates_once_and_stays_durable
|
|
1260
|
+
test_escalation_wakes_and_its_close_stays_quiet
|
|
1261
|
+
test_escalation_publication_failure_retries
|
|
1262
|
+
test_legacy_escalation_closes_default_decision
|
|
1263
|
+
test_legacy_escalation_does_not_close_taken_default_decision
|
|
1264
|
+
test_foreign_blocker_is_not_selected_as_escalation
|
|
1265
|
+
test_concurrent_resolution_closes_escalation_once
|
|
1266
|
+
test_concurrent_escalation_yields_to_late_reply
|
|
1267
|
+
test_transport_success_is_not_reply_success
|
|
1268
|
+
test_undelivered_records_are_scan_immutable
|
|
1269
|
+
test_delivery_confirmation_fallback_reconciles
|
|
1270
|
+
test_delivery_confirmation_serializes_with_reconciliation
|
|
1271
|
+
test_unrelated_and_stale_corr_cannot_resolve
|
|
1272
|
+
test_restart_preserves_expectation_and_parent_destination
|
|
1273
|
+
test_wrong_home_detected_not_acknowledged
|
|
1274
|
+
test_unmarked_captain_input_creates_no_expectation
|
|
1275
|
+
test_fm_send_marked_secondmate_creates_pending_and_embeds_corr
|
|
1276
|
+
test_document_pointer_resolves
|
|
1277
|
+
test_helper_report_resolves
|
|
1278
|
+
test_busy_idle_observation_via_backend_abstraction
|
|
1279
|
+
test_unknown_backend_state_uses_capture_fallback
|
|
1280
|
+
test_kimi_capture_fallback_uses_recorded_harness
|
|
1281
|
+
test_tick_skips_terminal_and_reuses_target_observation
|
|
1282
|
+
test_correlations_reuse_only_for_matching_open_task
|
|
1283
|
+
test_tick_end_to_end_missed_then_escalate
|
|
1284
|
+
test_failed_send_discards_undelivered_expectation
|
|
1285
|
+
test_remote_repost_waits_for_the_reply_channel
|
|
1286
|
+
test_mirrored_remote_reply_never_triggers_a_repost
|
|
1287
|
+
|
|
1288
|
+
printf 'ok - all pending-reply tests passed\n'
|