@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,768 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Behavior tests for the bounded remote job queue and worker.
|
|
3
|
+
set -u
|
|
4
|
+
|
|
5
|
+
# shellcheck source=tests/lib.sh
|
|
6
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
7
|
+
|
|
8
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
|
|
9
|
+
TMP_ROOT=$(fm_test_tmproot fm-remote-job)
|
|
10
|
+
mkdir -p "$TMP_ROOT"
|
|
11
|
+
TMP_ROOT=$(cd "$TMP_ROOT" && pwd -P)
|
|
12
|
+
REMOTE_ROOT="$TMP_ROOT/remote-root"
|
|
13
|
+
REMOTE_HOME="$TMP_ROOT/remote-home"
|
|
14
|
+
ACCOUNT_HOME="$TMP_ROOT/account"
|
|
15
|
+
STATE_ROOT="$TMP_ROOT/remote-jobs"
|
|
16
|
+
RUNTIME_BIN="$TMP_ROOT/runtime-bin"
|
|
17
|
+
FAKE_PERL_LOG="$TMP_ROOT/perl.log"
|
|
18
|
+
REAL_GIT=$(command -v git)
|
|
19
|
+
OTHER_PID=
|
|
20
|
+
RECOVERY_WORKER_PID=
|
|
21
|
+
REPEAT_WORKER_PID=
|
|
22
|
+
RESTART_SUPERVISOR_PID=
|
|
23
|
+
mkdir -p "$REMOTE_ROOT/bin" "$REMOTE_HOME" "$ACCOUNT_HOME" "$RUNTIME_BIN"
|
|
24
|
+
# worker.pid records the serving child, not its restart supervisor, so stopping
|
|
25
|
+
# that pid alone leaves the supervisor to respawn - the leak
|
|
26
|
+
# tests/fm-remote-job-orphan-reap.test.sh pins. Stop the whole worker tree.
|
|
27
|
+
cleanup_remote_job_fixture() {
|
|
28
|
+
[ -z "$OTHER_PID" ] || kill "$OTHER_PID" 2>/dev/null || true
|
|
29
|
+
[ -z "$RECOVERY_WORKER_PID" ] || kill "$RECOVERY_WORKER_PID" 2>/dev/null || true
|
|
30
|
+
[ -z "$REPEAT_WORKER_PID" ] || kill "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
31
|
+
[ -z "$RESTART_SUPERVISOR_PID" ] || kill -KILL "$RESTART_SUPERVISOR_PID" 2>/dev/null || true
|
|
32
|
+
if [ -f "$STATE_ROOT/worker.pid" ]; then
|
|
33
|
+
fm_remote_job_stop_worker_tree "$(cat "$STATE_ROOT/worker.pid")" || true
|
|
34
|
+
fi
|
|
35
|
+
rm -rf -- "$TMP_ROOT"
|
|
36
|
+
}
|
|
37
|
+
trap cleanup_remote_job_fixture EXIT
|
|
38
|
+
|
|
39
|
+
cp "$ROOT/bin/fm-remote-job-lib.sh" "$ROOT/bin/fm-remote-job-worker.sh" \
|
|
40
|
+
"$ROOT/bin/fm-remote-delta-read.sh" "$REMOTE_ROOT/bin/"
|
|
41
|
+
printf 'fixture\n' > "$REMOTE_ROOT/AGENTS.md"
|
|
42
|
+
cat > "$REMOTE_ROOT/bin/fm-probe-job.sh" <<'SH'
|
|
43
|
+
#!/bin/bash
|
|
44
|
+
set -u
|
|
45
|
+
printf 'home=%s\nroot=%s\nactive=%s\npath=%s\n' "$FM_HOME" "$FM_ROOT_OVERRIDE" "${FM_REMOTE_JOB_ACTIVE:-}" "$PATH"
|
|
46
|
+
printf 'args:'
|
|
47
|
+
printf ' <%s>' "$@"
|
|
48
|
+
printf '\n'
|
|
49
|
+
if [ -n "${TOP_SECRET:-}" ]; then printf 'secret=leaked\n'; else printf 'secret=absent\n'; fi
|
|
50
|
+
while IFS= read -r line || [ -n "$line" ]; do printf 'stdin=%s\n' "$line"; done
|
|
51
|
+
exit "${FM_PROBE_EXIT:-0}"
|
|
52
|
+
SH
|
|
53
|
+
cat > "$REMOTE_ROOT/bin/fm-timeout-job.sh" <<'SH'
|
|
54
|
+
#!/bin/bash
|
|
55
|
+
sleep 3
|
|
56
|
+
SH
|
|
57
|
+
cat > "$REMOTE_ROOT/bin/fm-delay-job.sh" <<'SH'
|
|
58
|
+
#!/bin/bash
|
|
59
|
+
sleep "$1"
|
|
60
|
+
printf 'ran\n' > "$2"
|
|
61
|
+
SH
|
|
62
|
+
cat > "$REMOTE_ROOT/bin/fm-touch-job.sh" <<'SH'
|
|
63
|
+
#!/bin/bash
|
|
64
|
+
printf 'ran\n' > "$1"
|
|
65
|
+
SH
|
|
66
|
+
cat > "$REMOTE_ROOT/bin/fm-shutdown-job.sh" <<'SH'
|
|
67
|
+
#!/bin/bash
|
|
68
|
+
trap '' HUP INT TERM
|
|
69
|
+
printf 'started\n' > "$1"
|
|
70
|
+
sleep 3
|
|
71
|
+
printf 'ran\n' > "$2"
|
|
72
|
+
SH
|
|
73
|
+
cat > "$REMOTE_ROOT/bin/fm-output-job.sh" <<'SH'
|
|
74
|
+
#!/bin/bash
|
|
75
|
+
set -e
|
|
76
|
+
head -c 1200000 < /dev/zero
|
|
77
|
+
head -c 1200000 < /dev/zero >&2
|
|
78
|
+
exit 23
|
|
79
|
+
SH
|
|
80
|
+
chmod +x "$REMOTE_ROOT/bin"/*.sh
|
|
81
|
+
cat > "$RUNTIME_BIN/perl" <<'SH'
|
|
82
|
+
#!/bin/bash
|
|
83
|
+
printf 'invoked\n' >> "$FM_FAKE_PERL_LOG"
|
|
84
|
+
exit 127
|
|
85
|
+
SH
|
|
86
|
+
chmod +x "$RUNTIME_BIN/perl"
|
|
87
|
+
|
|
88
|
+
git -C "$REMOTE_ROOT" init -q -b main
|
|
89
|
+
git -C "$REMOTE_ROOT" config user.email test@example.com
|
|
90
|
+
git -C "$REMOTE_ROOT" config user.name Test
|
|
91
|
+
git -C "$REMOTE_ROOT" add AGENTS.md bin
|
|
92
|
+
git -C "$REMOTE_ROOT" commit -qm 'remote job fixture'
|
|
93
|
+
|
|
94
|
+
DEFAULT_STATE="$TMP_ROOT/default-timeout-jobs"
|
|
95
|
+
DEFAULT_BOUNDS=$(
|
|
96
|
+
unset FM_REMOTE_JOB_QUEUE_TIMEOUT
|
|
97
|
+
unset FM_REMOTE_JOB_TIMEOUT
|
|
98
|
+
# shellcheck disable=SC2030 # This source intentionally initializes subshell-only defaults.
|
|
99
|
+
FM_REMOTE_JOB_STATE_ROOT="$DEFAULT_STATE"
|
|
100
|
+
export FM_REMOTE_JOB_STATE_ROOT
|
|
101
|
+
# shellcheck source=bin/fm-remote-job-lib.sh
|
|
102
|
+
. "$ROOT/bin/fm-remote-job-lib.sh"
|
|
103
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-probe-job.sh </dev/null >/dev/null
|
|
104
|
+
printf '%s %s\n' \
|
|
105
|
+
"$(cat "$DEFAULT_STATE/jobs/$FM_REMOTE_JOB_ID/queue_deadline")" \
|
|
106
|
+
"$(cat "$DEFAULT_STATE/jobs/$FM_REMOTE_JOB_ID/timeout")"
|
|
107
|
+
)
|
|
108
|
+
read -r DEFAULT_QUEUE_DEADLINE DEFAULT_EXECUTION_TIMEOUT <<< "$DEFAULT_BOUNDS"
|
|
109
|
+
DEFAULT_QUEUE_REMAINING=$((DEFAULT_QUEUE_DEADLINE - $(date +%s)))
|
|
110
|
+
[ "$DEFAULT_QUEUE_REMAINING" -ge 350 ] || fail "the default queue bound is too short"
|
|
111
|
+
[ "$DEFAULT_EXECUTION_TIMEOUT" -ge 350 ] || fail "the default execution bound cannot contain a 300-second long poll"
|
|
112
|
+
pass "default queue and execution bounds independently cover long polls"
|
|
113
|
+
|
|
114
|
+
# shellcheck disable=SC2031 # The earlier assignment was confined to DEFAULT_BOUNDS.
|
|
115
|
+
export FM_REMOTE_JOB_STATE_ROOT="$STATE_ROOT"
|
|
116
|
+
export FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux
|
|
117
|
+
# shellcheck disable=SC2031 # The sourced defaults above were confined to DEFAULT_BOUNDS.
|
|
118
|
+
export FM_REMOTE_JOB_QUEUE_TIMEOUT=5
|
|
119
|
+
# shellcheck disable=SC2031 # The sourced defaults above were confined to DEFAULT_BOUNDS.
|
|
120
|
+
export FM_REMOTE_JOB_TIMEOUT=5
|
|
121
|
+
# shellcheck source=bin/fm-remote-job-lib.sh
|
|
122
|
+
. "$ROOT/bin/fm-remote-job-lib.sh"
|
|
123
|
+
|
|
124
|
+
LOCAL_BIN_PARENT="$ACCOUNT_HOME/.local"
|
|
125
|
+
LOCAL_BIN_TARGET="$TMP_ROOT/local-bin-target"
|
|
126
|
+
mkdir -p "$LOCAL_BIN_PARENT" "$LOCAL_BIN_TARGET"
|
|
127
|
+
ln -s "$LOCAL_BIN_TARGET" "$LOCAL_BIN_PARENT/bin"
|
|
128
|
+
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
|
|
129
|
+
case ":$FM_REMOTE_JOB_OPERATOR_PATH:" in
|
|
130
|
+
*":$LOCAL_BIN_PARENT/bin:"*|*":$LOCAL_BIN_TARGET:"*) fail "the composed PATH followed a symlinked local bin" ;;
|
|
131
|
+
esac
|
|
132
|
+
rm -f "$LOCAL_BIN_PARENT/bin"
|
|
133
|
+
mkdir "$LOCAL_BIN_PARENT/bin"
|
|
134
|
+
pass "operator PATH excludes a symlinked local bin"
|
|
135
|
+
|
|
136
|
+
NVM_ROOT="$ACCOUNT_HOME/.nvm"
|
|
137
|
+
NVM_V20="$NVM_ROOT/versions/node/v20.18.0/bin"
|
|
138
|
+
NVM_V24="$NVM_ROOT/versions/node/v24.14.1/bin"
|
|
139
|
+
mkdir -p "$NVM_ROOT/alias" "$NVM_V20" "$NVM_V24"
|
|
140
|
+
printf '20\n' > "$NVM_ROOT/alias/default"
|
|
141
|
+
printf '#!/bin/bash\nprintf "20\\n"\n' > "$NVM_V20/node"
|
|
142
|
+
printf '#!/bin/bash\nprintf "24\\n"\n' > "$NVM_V24/node"
|
|
143
|
+
chmod +x "$NVM_V20/node" "$NVM_V24/node"
|
|
144
|
+
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
|
|
145
|
+
NVM_SELECTED=$(PATH="$FM_REMOTE_JOB_OPERATOR_PATH" node)
|
|
146
|
+
[ "$NVM_SELECTED" = 20 ] || fail "the composed PATH ignored nvm's default alias"
|
|
147
|
+
rm -f "$NVM_ROOT/alias/default"
|
|
148
|
+
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
|
|
149
|
+
NVM_SELECTED=$(PATH="$FM_REMOTE_JOB_OPERATOR_PATH" node)
|
|
150
|
+
[ "$NVM_SELECTED" = 24 ] || fail "the nvm fallback did not select the highest installed version"
|
|
151
|
+
printf 'system\n' > "$NVM_ROOT/alias/default"
|
|
152
|
+
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
|
|
153
|
+
case ":$FM_REMOTE_JOB_OPERATOR_PATH:" in
|
|
154
|
+
*":$NVM_V20:"*|*":$NVM_V24:"*) fail "the composed PATH ignored nvm's system default" ;;
|
|
155
|
+
esac
|
|
156
|
+
printf '20\n' > "$NVM_ROOT/alias/default"
|
|
157
|
+
pass "operator PATH honors nvm defaults with a deterministic fallback"
|
|
158
|
+
|
|
159
|
+
NIX_PROFILE="$ACCOUNT_HOME/.nix-profile"
|
|
160
|
+
NIX_BIN="$TMP_ROOT/nix-profile-bin"
|
|
161
|
+
mkdir -p "$NIX_PROFILE" "$NIX_BIN"
|
|
162
|
+
ln -s "$NIX_BIN" "$NIX_PROFILE/bin"
|
|
163
|
+
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
|
|
164
|
+
case ":$FM_REMOTE_JOB_OPERATOR_PATH:" in
|
|
165
|
+
*":$NIX_BIN:"*) ;;
|
|
166
|
+
*) fail "the composed PATH omitted a resolved Nix profile bin link" ;;
|
|
167
|
+
esac
|
|
168
|
+
pass "operator PATH resolves the authorized Nix profile bin link"
|
|
169
|
+
|
|
170
|
+
# Which install of a multi-version tool a remote job resolves is decided by the
|
|
171
|
+
# order these directories land on PATH, so the composition has to be sorted
|
|
172
|
+
# rather than whatever order the filesystem returns. The fixture is created in
|
|
173
|
+
# a deliberately unsorted order, and the expectation is the shell's own
|
|
174
|
+
# pathname expansion - the mechanism the portable-PATH contract in
|
|
175
|
+
# tests/fm-on.test.sh reconstructs.
|
|
176
|
+
MISE_INSTALLS="$ACCOUNT_HOME/.local/share/mise/installs"
|
|
177
|
+
for TOOL_VERSION in node/26.7.0 node/8.1 node/26 bun/1.4 bun/1.3.14 python/3.12.7; do
|
|
178
|
+
mkdir -p "$MISE_INSTALLS/$TOOL_VERSION/bin"
|
|
179
|
+
done
|
|
180
|
+
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
|
|
181
|
+
MISE_COMPOSED=$(printf '%s\n' "$FM_REMOTE_JOB_OPERATOR_PATH" | tr ':' '\n' | grep -F "$MISE_INSTALLS/" || true)
|
|
182
|
+
MISE_EXPECTED=$(printf '%s\n' "$MISE_INSTALLS"/*/*/bin)
|
|
183
|
+
[ "$MISE_COMPOSED" = "$MISE_EXPECTED" ] \
|
|
184
|
+
|| fail "the composed operator PATH did not order tool installs like the shell's own expansion"$'\n'"expected: $MISE_EXPECTED"$'\n'"actual: $MISE_COMPOSED"
|
|
185
|
+
# This assertion detects the defect on bash 3.2 and 5.2, where compgen -G returns unsorted glob matches, but reads green on bash 5.3+ because glob sorting moved into the glob library so both mechanisms agree there.
|
|
186
|
+
rm -rf -- "$ACCOUNT_HOME/.local/share/mise"
|
|
187
|
+
pass "operator PATH orders discovered tool installs deterministically"
|
|
188
|
+
|
|
189
|
+
HOME="$ACCOUNT_HOME" PATH="$RUNTIME_BIN:/usr/bin:/bin:/usr/sbin:/sbin" FM_FAKE_PERL_LOG="$FAKE_PERL_LOG" \
|
|
190
|
+
FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$STATE_ROOT" \
|
|
191
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux FM_REMOTE_JOB_TIMEOUT=5 \
|
|
192
|
+
"$REMOTE_ROOT/bin/fm-remote-job-worker.sh" > "$TMP_ROOT/worker.out" 2> "$TMP_ROOT/worker.err" &
|
|
193
|
+
for _ in $(seq 1 100); do
|
|
194
|
+
[ -f "$STATE_ROOT/worker.ready" ] && break
|
|
195
|
+
sleep 0.05
|
|
196
|
+
done
|
|
197
|
+
assert_present "$STATE_ROOT/worker.ready" "the worker did not publish its readiness heartbeat"
|
|
198
|
+
|
|
199
|
+
file_mode() {
|
|
200
|
+
if [ "$(uname)" = Darwin ]; then
|
|
201
|
+
stat -f %Lp "$1"
|
|
202
|
+
else
|
|
203
|
+
stat -c %a "$1"
|
|
204
|
+
fi
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
printf 'first line\nsecond line\n' > "$TMP_ROOT/stdin"
|
|
208
|
+
# shellcheck disable=SC2016 # Literal shell-looking argv is an injection probe.
|
|
209
|
+
TOP_SECRET=must-not-cross fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
210
|
+
fm-probe-job.sh 'two words' '$(not executed)' < "$TMP_ROOT/stdin" > /dev/null
|
|
211
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
212
|
+
JOB_DIR="$STATE_ROOT/jobs/$JOB_ID"
|
|
213
|
+
[ "$(file_mode "$JOB_DIR")" = 700 ] \
|
|
214
|
+
|| fail "staged job directory is not mode 0700"
|
|
215
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
216
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 0 ] || fail "the completed probe did not preserve exit status"
|
|
217
|
+
OUT=$(<"$FM_REMOTE_JOB_STDOUT")
|
|
218
|
+
assert_contains "$OUT" "home=$REMOTE_HOME" "the worker did not pass the staged FM_HOME"
|
|
219
|
+
assert_contains "$OUT" "root=$REMOTE_ROOT" "the worker did not pass the configured root"
|
|
220
|
+
assert_contains "$OUT" 'active=1' "the target did not execute inside the worker environment"
|
|
221
|
+
# shellcheck disable=SC2016 # Literal shell-looking expected output is an injection probe.
|
|
222
|
+
assert_contains "$OUT" 'args: <two words> <$(not executed)>' "the worker changed argv boundaries"
|
|
223
|
+
assert_contains "$OUT" 'stdin=first line' "the worker lost staged stdin"
|
|
224
|
+
assert_contains "$OUT" 'stdin=second line' "the worker lost staged stdin"
|
|
225
|
+
assert_contains "$OUT" 'secret=absent' "ambient environment crossed into the worker child"
|
|
226
|
+
case "$OUT" in *"$REMOTE_ROOT/bin:$ACCOUNT_HOME/.local/bin:"*) : ;; *) fail "worker PATH omitted its fixed root and account head" ;; esac
|
|
227
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the completed job could not be reaped"
|
|
228
|
+
assert_absent "$JOB_DIR" "reap retained a completed job record"
|
|
229
|
+
assert_absent "$FAKE_PERL_LOG" "the worker invoked an unavailable Perl runtime"
|
|
230
|
+
pass "the worker preserves bounded argv and stdin in an empty environment"
|
|
231
|
+
|
|
232
|
+
ACTIVE_SIDE_EFFECT="$TMP_ROOT/active-side-effect"
|
|
233
|
+
FM_REMOTE_JOB_TIMEOUT=10
|
|
234
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
235
|
+
fm-delay-job.sh 4 "$ACTIVE_SIDE_EFFECT" < /dev/null > /dev/null
|
|
236
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
237
|
+
JOB_DIR="$STATE_ROOT/jobs/$JOB_ID"
|
|
238
|
+
for _ in $(seq 1 100); do
|
|
239
|
+
[ "$(fm_remote_job_read_state "$JOB_DIR" 2>/dev/null || true)" = running ] && break
|
|
240
|
+
sleep 0.05
|
|
241
|
+
done
|
|
242
|
+
[ "$(fm_remote_job_read_state "$JOB_DIR" 2>/dev/null || true)" = running ] \
|
|
243
|
+
|| fail "the active-job readiness fixture did not begin running"
|
|
244
|
+
ACTIVE_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
245
|
+
touch -t 200001010000 "$STATE_ROOT/worker.ready"
|
|
246
|
+
for _ in $(seq 1 40); do
|
|
247
|
+
fm_remote_job_probe "$ACCOUNT_HOME" && break
|
|
248
|
+
sleep 0.05
|
|
249
|
+
done
|
|
250
|
+
fm_remote_job_probe "$ACCOUNT_HOME" || fail "the active worker did not refresh its readiness heartbeat"
|
|
251
|
+
fm_remote_job_ensure_worker "$REMOTE_ROOT" "$ACCOUNT_HOME" || fail "$FM_REMOTE_JOB_ERROR"
|
|
252
|
+
[ "$(cat "$STATE_ROOT/worker.pid")" = "$ACTIVE_WORKER_PID" ] \
|
|
253
|
+
|| fail "ensure replaced a healthy worker during an active job"
|
|
254
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
255
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 0 ] || fail "the active job did not complete after the readiness probe"
|
|
256
|
+
assert_present "$ACTIVE_SIDE_EFFECT" "the active job was interrupted by the concurrent readiness check"
|
|
257
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the active readiness job could not be reaped"
|
|
258
|
+
pass "active jobs keep the worker ready for concurrent requests"
|
|
259
|
+
|
|
260
|
+
OLD_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
261
|
+
printf '\n' >> "$REMOTE_ROOT/bin/fm-remote-job-worker.sh"
|
|
262
|
+
fm_remote_job_ensure_worker "$REMOTE_ROOT" "$ACCOUNT_HOME" \
|
|
263
|
+
|| fail "$FM_REMOTE_JOB_ERROR"
|
|
264
|
+
NEW_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
265
|
+
[ "$NEW_WORKER_PID" != "$OLD_WORKER_PID" ] || fail "ensure retained a worker running stale code"
|
|
266
|
+
fm_remote_job_worker_identity_matches "$REMOTE_ROOT" "$ACCOUNT_HOME" \
|
|
267
|
+
|| fail "the replacement worker did not publish the current code identity"
|
|
268
|
+
pass "ensure replaces a live worker after its code changes"
|
|
269
|
+
|
|
270
|
+
RELOCATED_ROOT="$TMP_ROOT/relocated-root"
|
|
271
|
+
cp -R "$REMOTE_ROOT" "$RELOCATED_ROOT"
|
|
272
|
+
OLD_WORKER_PID=$NEW_WORKER_PID
|
|
273
|
+
OLD_WORKER_PGID=$(fm_remote_job_process_pgid "$OLD_WORKER_PID") \
|
|
274
|
+
|| fail "the worker replacement fixture could not resolve its process group"
|
|
275
|
+
fm_remote_job_ensure_worker "$RELOCATED_ROOT" "$ACCOUNT_HOME" \
|
|
276
|
+
|| fail "$FM_REMOTE_JOB_ERROR"
|
|
277
|
+
NEW_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
278
|
+
[ "$NEW_WORKER_PID" != "$OLD_WORKER_PID" ] || fail "ensure retained a worker bound to a different code root"
|
|
279
|
+
! kill -0 -- "-$OLD_WORKER_PGID" 2>/dev/null \
|
|
280
|
+
|| fail "ensure left the replaced worker supervisor group alive"
|
|
281
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$RELOCATED_ROOT" "$REMOTE_HOME" fm-probe-job.sh < /dev/null > /dev/null
|
|
282
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
283
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
284
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 0 ] || fail "the relocated worker rejected its configured code root"
|
|
285
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the relocated-root probe could not be reaped"
|
|
286
|
+
fm_remote_job_ensure_worker "$REMOTE_ROOT" "$ACCOUNT_HOME" || fail "$FM_REMOTE_JOB_ERROR"
|
|
287
|
+
NEW_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
288
|
+
pass "worker identity binds the canonical configured code root"
|
|
289
|
+
|
|
290
|
+
CRASHED_WORKER_PID=$NEW_WORKER_PID
|
|
291
|
+
kill -KILL "$CRASHED_WORKER_PID"
|
|
292
|
+
wait "$CRASHED_WORKER_PID" 2>/dev/null || true
|
|
293
|
+
assert_present "$STATE_ROOT/worker.lock" "an unclean exit did not retain the worker ownership lock"
|
|
294
|
+
sleep 20 &
|
|
295
|
+
OTHER_PID=$!
|
|
296
|
+
printf '%s\n' "$OTHER_PID" > "$STATE_ROOT/worker.pid"
|
|
297
|
+
printf '%s\n' "$OTHER_PID" > "$STATE_ROOT/worker.lock/pid"
|
|
298
|
+
touch -t 200001010000 "$STATE_ROOT/worker.ready" "$STATE_ROOT/worker.lock"
|
|
299
|
+
fm_remote_job_ensure_worker "$REMOTE_ROOT" "$ACCOUNT_HOME" \
|
|
300
|
+
|| fail "$FM_REMOTE_JOB_ERROR"
|
|
301
|
+
kill -0 "$OTHER_PID" 2>/dev/null || fail "stale worker state caused an unrelated process to be signaled"
|
|
302
|
+
NEW_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
303
|
+
[ "$NEW_WORKER_PID" != "$OTHER_PID" ] || fail "the replacement adopted an unrelated persisted pid"
|
|
304
|
+
fm_remote_job_worker_identity_matches "$REMOTE_ROOT" "$ACCOUNT_HOME" \
|
|
305
|
+
|| fail "stale ownership recovery did not start the current worker"
|
|
306
|
+
kill "$OTHER_PID" 2>/dev/null || true
|
|
307
|
+
wait "$OTHER_PID" 2>/dev/null || true
|
|
308
|
+
OTHER_PID=
|
|
309
|
+
pass "stale ownership is reclaimed without signaling a reused pid"
|
|
310
|
+
|
|
311
|
+
FM_REMOTE_JOB_TIMEOUT=1
|
|
312
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-timeout-job.sh < /dev/null > /dev/null
|
|
313
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
314
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
315
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 124 ] || fail "the worker did not terminate an over-time job"
|
|
316
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the timed-out job could not be reaped"
|
|
317
|
+
pass "the worker enforces the job timeout and publishes its result"
|
|
318
|
+
|
|
319
|
+
QUEUED_SIDE_EFFECT="$TMP_ROOT/queued-side-effect"
|
|
320
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-timeout-job.sh < /dev/null > /dev/null
|
|
321
|
+
FIRST_JOB_ID=$FM_REMOTE_JOB_ID
|
|
322
|
+
FIRST_JOB_DIR="$STATE_ROOT/jobs/$FIRST_JOB_ID"
|
|
323
|
+
for _ in $(seq 1 100); do
|
|
324
|
+
[ "$(fm_remote_job_read_state "$FIRST_JOB_DIR" 2>/dev/null || true)" = running ] && break
|
|
325
|
+
sleep 0.05
|
|
326
|
+
done
|
|
327
|
+
[ "$(fm_remote_job_read_state "$FIRST_JOB_DIR" 2>/dev/null || true)" = running ] \
|
|
328
|
+
|| fail "the blocking job did not begin running"
|
|
329
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-touch-job.sh "$QUEUED_SIDE_EFFECT" < /dev/null > /dev/null
|
|
330
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
331
|
+
printf '%s\n' "$(fm_remote_job_read_deadline "$FIRST_JOB_DIR")" > "$STATE_ROOT/jobs/$JOB_ID/queue_deadline"
|
|
332
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$FIRST_JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
333
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
334
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 124 ] || fail "an expired queued job did not publish a timeout result"
|
|
335
|
+
assert_absent "$QUEUED_SIDE_EFFECT" "the worker executed a queued job after its durable deadline"
|
|
336
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$FIRST_JOB_ID" || fail "the blocking job could not be reaped"
|
|
337
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the expired queued job could not be reaped"
|
|
338
|
+
pass "the worker expires queued jobs before they can mutate"
|
|
339
|
+
|
|
340
|
+
FIRST_DELAYED_SIDE_EFFECT="$TMP_ROOT/first-delayed-side-effect"
|
|
341
|
+
SECOND_DELAYED_SIDE_EFFECT="$TMP_ROOT/second-delayed-side-effect"
|
|
342
|
+
FM_REMOTE_JOB_QUEUE_TIMEOUT=5
|
|
343
|
+
FM_REMOTE_JOB_TIMEOUT=3
|
|
344
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
345
|
+
fm-delay-job.sh 1.8 "$FIRST_DELAYED_SIDE_EFFECT" < /dev/null > /dev/null
|
|
346
|
+
FIRST_JOB_ID=$FM_REMOTE_JOB_ID
|
|
347
|
+
FIRST_JOB_DIR="$STATE_ROOT/jobs/$FIRST_JOB_ID"
|
|
348
|
+
for _ in $(seq 1 100); do
|
|
349
|
+
[ "$(fm_remote_job_read_state "$FIRST_JOB_DIR" 2>/dev/null || true)" = running ] && break
|
|
350
|
+
sleep 0.05
|
|
351
|
+
done
|
|
352
|
+
[ "$(fm_remote_job_read_state "$FIRST_JOB_DIR" 2>/dev/null || true)" = running ] \
|
|
353
|
+
|| fail "the first delayed job did not begin running"
|
|
354
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
355
|
+
fm-delay-job.sh 1.8 "$SECOND_DELAYED_SIDE_EFFECT" < /dev/null > /dev/null
|
|
356
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
357
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$FIRST_JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
358
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
359
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 0 ] || fail "queue time consumed the second job's execution timeout"
|
|
360
|
+
assert_present "$SECOND_DELAYED_SIDE_EFFECT" "the queued job did not receive its full execution timeout"
|
|
361
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$FIRST_JOB_ID" || fail "the first delayed job could not be reaped"
|
|
362
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the second delayed job could not be reaped"
|
|
363
|
+
pass "queued jobs receive a fresh bounded execution window"
|
|
364
|
+
|
|
365
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
366
|
+
EMPTY_SHA=$(: | shasum -a 256 | awk '{print $1}')
|
|
367
|
+
else
|
|
368
|
+
EMPTY_SHA=$(: | sha256sum | awk '{print $1}')
|
|
369
|
+
fi
|
|
370
|
+
mkdir -p "$REMOTE_HOME/state"
|
|
371
|
+
REPLY_LOG_REL=state/parent-replies.status
|
|
372
|
+
PREEMPT_SIDE_EFFECT="$TMP_ROOT/preempt-side-effect"
|
|
373
|
+
FM_REMOTE_JOB_QUEUE_TIMEOUT=60
|
|
374
|
+
FM_REMOTE_JOB_TIMEOUT=40
|
|
375
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
376
|
+
fm-remote-delta-read.sh "$REPLY_LOG_REL" 0 "$EMPTY_SHA" 30 < /dev/null > /dev/null
|
|
377
|
+
POLL_JOB_ID=$FM_REMOTE_JOB_ID
|
|
378
|
+
POLL_JOB_DIR="$STATE_ROOT/jobs/$POLL_JOB_ID"
|
|
379
|
+
for _ in $(seq 1 100); do
|
|
380
|
+
[ "$(fm_remote_job_read_state "$POLL_JOB_DIR" 2>/dev/null || true)" = running ] && break
|
|
381
|
+
sleep 0.05
|
|
382
|
+
done
|
|
383
|
+
[ "$(fm_remote_job_read_state "$POLL_JOB_DIR" 2>/dev/null || true)" = running ] \
|
|
384
|
+
|| fail "the long-poll job did not begin running"
|
|
385
|
+
PREEMPT_BEGAN=$(date +%s)
|
|
386
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
387
|
+
fm-touch-job.sh "$PREEMPT_SIDE_EFFECT" < /dev/null > /dev/null
|
|
388
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
389
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
390
|
+
PREEMPT_ELAPSED=$(( $(date +%s) - PREEMPT_BEGAN ))
|
|
391
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 0 ] || fail "the short command behind a long poll did not complete"
|
|
392
|
+
assert_present "$PREEMPT_SIDE_EFFECT" "the short command behind a long poll did not run"
|
|
393
|
+
[ "$PREEMPT_ELAPSED" -le 10 ] || fail "a queued short command waited a full poll window behind the long poll"
|
|
394
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$POLL_JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
395
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq "$FM_REMOTE_JOB_PREEMPTED_EXIT" ] \
|
|
396
|
+
|| fail "a preempted long poll was not distinguished from an elapsed window"
|
|
397
|
+
[ ! -s "$FM_REMOTE_JOB_STDOUT" ] || fail "a preempted long poll published partial stdout"
|
|
398
|
+
[ ! -s "$FM_REMOTE_JOB_STDERR" ] || fail "a preempted long poll published partial stderr"
|
|
399
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the short command could not be reaped"
|
|
400
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$POLL_JOB_ID" || fail "the preempted poll could not be reaped"
|
|
401
|
+
pass "a queued short command preempts a running long poll instead of waiting its window"
|
|
402
|
+
|
|
403
|
+
printf 'hello after preemption\n' > "$REMOTE_HOME/$REPLY_LOG_REL"
|
|
404
|
+
FM_REMOTE_JOB_TIMEOUT=10
|
|
405
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
406
|
+
fm-remote-delta-read.sh "$REPLY_LOG_REL" 0 "$EMPTY_SHA" 5 < /dev/null > /dev/null
|
|
407
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
408
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
409
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 0 ] || fail "the re-armed poll after preemption did not complete"
|
|
410
|
+
OUT=$(<"$FM_REMOTE_JOB_STDOUT")
|
|
411
|
+
assert_contains "$OUT" 'status=delta' "the re-armed poll did not return a delta from the preserved cursor"
|
|
412
|
+
assert_contains "$OUT" 'hello after preemption' "the re-armed poll lost data appended around the preemption"
|
|
413
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the re-armed poll could not be reaped"
|
|
414
|
+
rm -f -- "$REMOTE_HOME/$REPLY_LOG_REL"
|
|
415
|
+
pass "a poll re-armed after preemption reads the same cursor with nothing lost"
|
|
416
|
+
|
|
417
|
+
FM_REMOTE_JOB_TIMEOUT=15
|
|
418
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
419
|
+
fm-remote-delta-read.sh "$REPLY_LOG_REL" 0 "$EMPTY_SHA" 6 < /dev/null > /dev/null
|
|
420
|
+
FIRST_JOB_ID=$FM_REMOTE_JOB_ID
|
|
421
|
+
FIRST_JOB_DIR="$STATE_ROOT/jobs/$FIRST_JOB_ID"
|
|
422
|
+
for _ in $(seq 1 100); do
|
|
423
|
+
[ "$(fm_remote_job_read_state "$FIRST_JOB_DIR" 2>/dev/null || true)" = running ] && break
|
|
424
|
+
sleep 0.05
|
|
425
|
+
done
|
|
426
|
+
[ "$(fm_remote_job_read_state "$FIRST_JOB_DIR" 2>/dev/null || true)" = running ] \
|
|
427
|
+
|| fail "the first sibling poll did not begin running"
|
|
428
|
+
POLL_PAIR_BEGAN=$(date +%s)
|
|
429
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
430
|
+
fm-remote-delta-read.sh "$REPLY_LOG_REL" 0 "$EMPTY_SHA" 1 < /dev/null > /dev/null
|
|
431
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
432
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$FIRST_JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
433
|
+
POLL_PAIR_ELAPSED=$(( $(date +%s) - POLL_PAIR_BEGAN ))
|
|
434
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 75 ] || fail "the first sibling poll did not close its own window"
|
|
435
|
+
[ "$POLL_PAIR_ELAPSED" -ge 4 ] || fail "a queued sibling poll preempted a running poll"
|
|
436
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
437
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 75 ] || fail "the queued sibling poll did not run after the first window"
|
|
438
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$FIRST_JOB_ID" || fail "the first sibling poll could not be reaped"
|
|
439
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the queued sibling poll could not be reaped"
|
|
440
|
+
FM_REMOTE_JOB_QUEUE_TIMEOUT=5
|
|
441
|
+
pass "sibling polls never preempt each other into a re-arm churn loop"
|
|
442
|
+
|
|
443
|
+
STARTED="$TMP_ROOT/shutdown-started"
|
|
444
|
+
SHUTDOWN_SIDE_EFFECT="$TMP_ROOT/shutdown-side-effect"
|
|
445
|
+
FM_REMOTE_JOB_TIMEOUT=5
|
|
446
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
447
|
+
fm-shutdown-job.sh "$STARTED" "$SHUTDOWN_SIDE_EFFECT" < /dev/null > /dev/null
|
|
448
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
449
|
+
for _ in $(seq 1 100); do
|
|
450
|
+
[ -f "$STARTED" ] && break
|
|
451
|
+
sleep 0.05
|
|
452
|
+
done
|
|
453
|
+
assert_present "$STARTED" "the shutdown fixture did not begin executing"
|
|
454
|
+
WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
455
|
+
kill -TERM "$WORKER_PID"
|
|
456
|
+
for _ in $(seq 1 100); do
|
|
457
|
+
kill -0 "$WORKER_PID" 2>/dev/null || break
|
|
458
|
+
sleep 0.05
|
|
459
|
+
done
|
|
460
|
+
kill -0 "$WORKER_PID" 2>/dev/null && fail "the worker did not finish its TERM shutdown"
|
|
461
|
+
HOME="$ACCOUNT_HOME" FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$STATE_ROOT" \
|
|
462
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux FM_REMOTE_JOB_TIMEOUT=1 \
|
|
463
|
+
"$REMOTE_ROOT/bin/fm-remote-job-worker.sh" >> "$TMP_ROOT/worker.out" 2>> "$TMP_ROOT/worker.err" &
|
|
464
|
+
for _ in $(seq 1 100); do
|
|
465
|
+
[ -f "$STATE_ROOT/worker.ready" ] && break
|
|
466
|
+
sleep 0.05
|
|
467
|
+
done
|
|
468
|
+
assert_present "$STATE_ROOT/worker.ready" "the replacement worker did not become ready"
|
|
469
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
470
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 125 ] || fail "the interrupted job did not publish an unknown-completion result"
|
|
471
|
+
sleep 3
|
|
472
|
+
assert_absent "$SHUTDOWN_SIDE_EFFECT" "the active command mutated after worker shutdown"
|
|
473
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the interrupted job could not be reaped"
|
|
474
|
+
pass "worker shutdown terminates the active command tree before replacement"
|
|
475
|
+
|
|
476
|
+
CRASH_STARTED="$TMP_ROOT/crash-started"
|
|
477
|
+
CRASH_SIDE_EFFECT="$TMP_ROOT/crash-side-effect"
|
|
478
|
+
FM_REMOTE_JOB_TIMEOUT=5
|
|
479
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
480
|
+
fm-shutdown-job.sh "$CRASH_STARTED" "$CRASH_SIDE_EFFECT" < /dev/null > /dev/null
|
|
481
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
482
|
+
for _ in $(seq 1 100); do
|
|
483
|
+
[ -f "$CRASH_STARTED" ] && break
|
|
484
|
+
sleep 0.05
|
|
485
|
+
done
|
|
486
|
+
assert_present "$CRASH_STARTED" "the crash fixture did not begin executing"
|
|
487
|
+
CRASHED_WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
488
|
+
kill -KILL "$CRASHED_WORKER_PID"
|
|
489
|
+
for _ in $(seq 1 200); do
|
|
490
|
+
RESTARTED_WORKER_PID=$(cat "$STATE_ROOT/worker.pid" 2>/dev/null || true)
|
|
491
|
+
[ -n "$RESTARTED_WORKER_PID" ] && [ "$RESTARTED_WORKER_PID" != "$CRASHED_WORKER_PID" ] && break
|
|
492
|
+
sleep 0.05
|
|
493
|
+
done
|
|
494
|
+
[ -n "${RESTARTED_WORKER_PID:-}" ] && [ "$RESTARTED_WORKER_PID" != "$CRASHED_WORKER_PID" ] \
|
|
495
|
+
|| fail "the Linux supervisor did not restart a crashed worker"
|
|
496
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
497
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 125 ] || fail "worker crash recovery did not publish unknown completion"
|
|
498
|
+
sleep 3
|
|
499
|
+
assert_absent "$CRASH_SIDE_EFFECT" "an orphaned command mutated after worker crash recovery"
|
|
500
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the crash-recovered job could not be reaped"
|
|
501
|
+
fm_remote_job_probe "$ACCOUNT_HOME" || fail "the restarted worker did not remain ready"
|
|
502
|
+
pass "Linux supervision recovers crashes and stops orphaned commands"
|
|
503
|
+
|
|
504
|
+
mkdir -p "$ACCOUNT_HOME/.local/bin"
|
|
505
|
+
PREEXEC_STARTED="$TMP_ROOT/preexecution-started"
|
|
506
|
+
PREEXEC_FINISHED="$TMP_ROOT/preexecution-finished"
|
|
507
|
+
cat > "$ACCOUNT_HOME/.local/bin/git" <<SH
|
|
508
|
+
#!/bin/bash
|
|
509
|
+
if [ "\${3:-}" = ls-files ]; then
|
|
510
|
+
printf 'started\n' > "$PREEXEC_STARTED"
|
|
511
|
+
sleep 30
|
|
512
|
+
printf 'finished\n' > "$PREEXEC_FINISHED"
|
|
513
|
+
fi
|
|
514
|
+
exec "$REAL_GIT" "\$@"
|
|
515
|
+
SH
|
|
516
|
+
chmod +x "$ACCOUNT_HOME/.local/bin/git"
|
|
517
|
+
FM_REMOTE_JOB_TIMEOUT=3
|
|
518
|
+
PREEXEC_BEGAN=$(date +%s)
|
|
519
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-probe-job.sh < /dev/null > /dev/null
|
|
520
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
521
|
+
JOB_DIR="$STATE_ROOT/jobs/$JOB_ID"
|
|
522
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
523
|
+
PREEXEC_ELAPSED=$(( $(date +%s) - PREEXEC_BEGAN ))
|
|
524
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 124 ] || fail "the pre-execution deadline did not publish a timeout result"
|
|
525
|
+
assert_present "$PREEXEC_STARTED" "the pre-execution timeout fixture did not enter tracked-command validation"
|
|
526
|
+
assert_absent "$PREEXEC_FINISHED" "tracked-command validation continued after the job timeout"
|
|
527
|
+
[ "$PREEXEC_ELAPSED" -le 7 ] || fail "tracked-command validation exceeded the job timeout bound"
|
|
528
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the pre-execution timeout leaked output readers or FIFOs"
|
|
529
|
+
rm -f -- "$ACCOUNT_HOME/.local/bin/git"
|
|
530
|
+
pass "pre-execution validation obeys the job timeout"
|
|
531
|
+
|
|
532
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-output-job.sh < /dev/null > /dev/null
|
|
533
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
534
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
535
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 23 ] || fail "bounded output changed the command exit status"
|
|
536
|
+
OUTPUT_BYTES=$(LC_ALL=C wc -c < "$FM_REMOTE_JOB_STDOUT" | tr -d ' ')
|
|
537
|
+
[ "$OUTPUT_BYTES" -le "$FM_REMOTE_JOB_MAX_BYTES" ] || fail "the worker retained output beyond its byte bound"
|
|
538
|
+
ERROR_BYTES=$(LC_ALL=C wc -c < "$FM_REMOTE_JOB_STDERR" | tr -d ' ')
|
|
539
|
+
[ "$ERROR_BYTES" -le "$FM_REMOTE_JOB_MAX_BYTES" ] || fail "the worker retained stderr beyond its byte bound"
|
|
540
|
+
fm_remote_job_reap "$ACCOUNT_HOME" "$JOB_ID" || fail "the bounded-output job could not be reaped"
|
|
541
|
+
pass "the worker drains bounded output without changing command results"
|
|
542
|
+
|
|
543
|
+
SIDE_EFFECT="$TMP_ROOT/side-effect"
|
|
544
|
+
WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
545
|
+
fm_remote_job_stop_worker_tree "$WORKER_PID" \
|
|
546
|
+
|| fail "the worker tree did not stop before the staged-record tamper"
|
|
547
|
+
assert_absent "$STATE_ROOT/worker.pid" "the worker did not clear its pid before the staged-record tamper"
|
|
548
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" fm-touch-job.sh "$SIDE_EFFECT" < /dev/null > /dev/null
|
|
549
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
550
|
+
JOB_DIR="$STATE_ROOT/jobs/$JOB_ID"
|
|
551
|
+
rm -f -- "$JOB_DIR/argv"
|
|
552
|
+
ln -s "$TMP_ROOT/not-an-argv" "$JOB_DIR/argv"
|
|
553
|
+
fm_remote_job_ensure_worker "$REMOTE_ROOT" "$ACCOUNT_HOME" || fail "$FM_REMOTE_JOB_ERROR"
|
|
554
|
+
fm_remote_job_wait "$ACCOUNT_HOME" "$JOB_ID" || fail "$FM_REMOTE_JOB_ERROR"
|
|
555
|
+
[ "$FM_REMOTE_JOB_EXIT" -eq 126 ] || fail "the worker accepted a symlinked argv record"
|
|
556
|
+
assert_absent "$SIDE_EFFECT" "the worker executed a job after its argv changed to a symlink"
|
|
557
|
+
pass "the worker refuses symlinked job fields before command execution"
|
|
558
|
+
|
|
559
|
+
QUARANTINE_STARTED="$TMP_ROOT/quarantine-started"
|
|
560
|
+
QUARANTINE_SIDE_EFFECT="$TMP_ROOT/quarantine-side-effect"
|
|
561
|
+
FM_REMOTE_JOB_TIMEOUT=5
|
|
562
|
+
fm_remote_job_stage "$ACCOUNT_HOME" "$REMOTE_ROOT" "$REMOTE_HOME" \
|
|
563
|
+
fm-shutdown-job.sh "$QUARANTINE_STARTED" "$QUARANTINE_SIDE_EFFECT" < /dev/null > /dev/null
|
|
564
|
+
JOB_ID=$FM_REMOTE_JOB_ID
|
|
565
|
+
JOB_DIR="$STATE_ROOT/jobs/$JOB_ID"
|
|
566
|
+
for _ in $(seq 1 100); do
|
|
567
|
+
[ -f "$QUARANTINE_STARTED" ] && break
|
|
568
|
+
sleep 0.05
|
|
569
|
+
done
|
|
570
|
+
assert_present "$QUARANTINE_STARTED" "the quarantine fixture did not begin executing"
|
|
571
|
+
GROUP_PID=$(cat "$JOB_DIR/.claim/group")
|
|
572
|
+
printf 'invalid\n' > "$JOB_DIR/.claim/group"
|
|
573
|
+
WORKER_PID=$(cat "$STATE_ROOT/worker.pid")
|
|
574
|
+
kill -TERM "$WORKER_PID"
|
|
575
|
+
wait "$WORKER_PID" 2>/dev/null || true
|
|
576
|
+
for _ in $(seq 1 100); do
|
|
577
|
+
[ -f "$STATE_ROOT/worker.lock/quarantine" ] && break
|
|
578
|
+
sleep 0.05
|
|
579
|
+
done
|
|
580
|
+
assert_present "$STATE_ROOT/worker.lock/quarantine" "failed shutdown released worker ownership"
|
|
581
|
+
fm_remote_job_probe "$ACCOUNT_HOME" && fail "quarantined worker ownership still reported ready"
|
|
582
|
+
set +e
|
|
583
|
+
HOME="$ACCOUNT_HOME" FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$STATE_ROOT" \
|
|
584
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux "$REMOTE_ROOT/bin/fm-remote-job-worker.sh" \
|
|
585
|
+
>> "$TMP_ROOT/worker.out" 2>> "$TMP_ROOT/worker.err"
|
|
586
|
+
REPLACEMENT_RC=$?
|
|
587
|
+
set -e
|
|
588
|
+
[ "$REPLACEMENT_RC" -ne 0 ] || fail "a replacement worker ignored quarantined ownership"
|
|
589
|
+
assert_present "$STATE_ROOT/worker.lock/quarantine" "a replacement removed quarantined ownership"
|
|
590
|
+
kill -KILL -- "-$GROUP_PID" 2>/dev/null || true
|
|
591
|
+
sleep 3
|
|
592
|
+
assert_absent "$QUARANTINE_SIDE_EFFECT" "the quarantined command mutated after explicit termination"
|
|
593
|
+
pass "failed shutdown quarantines ownership against replacement workers"
|
|
594
|
+
|
|
595
|
+
RECOVERY_HOME="$TMP_ROOT/recovery-account"
|
|
596
|
+
RECOVERY_STATE="$TMP_ROOT/recovery-jobs"
|
|
597
|
+
RECOVERY_JOB="$RECOVERY_STATE/jobs/job-quarantine"
|
|
598
|
+
mkdir -p "$RECOVERY_HOME" "$RECOVERY_STATE/jobs" "$RECOVERY_STATE/logs" \
|
|
599
|
+
"$RECOVERY_STATE/worker.lock" "$RECOVERY_JOB/.claim"
|
|
600
|
+
chmod 700 "$RECOVERY_HOME" "$RECOVERY_STATE" "$RECOVERY_STATE/jobs" "$RECOVERY_STATE/logs" \
|
|
601
|
+
"$RECOVERY_STATE/worker.lock" "$RECOVERY_JOB" "$RECOVERY_JOB/.claim"
|
|
602
|
+
sleep 20 &
|
|
603
|
+
QUARANTINED_PROCESS_PID=$!
|
|
604
|
+
sleep 0.01 &
|
|
605
|
+
QUARANTINE_OWNER_PID=$!
|
|
606
|
+
wait "$QUARANTINE_OWNER_PID" 2>/dev/null || true
|
|
607
|
+
printf '%s\n' "$QUARANTINE_OWNER_PID" > "$RECOVERY_STATE/worker.lock/pid"
|
|
608
|
+
printf 'stale\n' > "$RECOVERY_STATE/worker.lock/start"
|
|
609
|
+
printf 'stale\n' > "$RECOVERY_STATE/worker.lock/command"
|
|
610
|
+
printf 'active execution could not be confirmed stopped\n' > "$RECOVERY_STATE/worker.lock/quarantine"
|
|
611
|
+
printf 'running\n' > "$RECOVERY_JOB/state"
|
|
612
|
+
printf '%s\n' "$QUARANTINE_OWNER_PID" > "$RECOVERY_JOB/.claim/owner"
|
|
613
|
+
printf '%s\n' "$QUARANTINED_PROCESS_PID" > "$RECOVERY_JOB/.claim/supervisor"
|
|
614
|
+
: > "$RECOVERY_JOB/stdout"
|
|
615
|
+
: > "$RECOVERY_JOB/stderr"
|
|
616
|
+
chmod 600 "$RECOVERY_STATE/worker.lock"/* "$RECOVERY_JOB/state" "$RECOVERY_JOB/.claim"/* \
|
|
617
|
+
"$RECOVERY_JOB/stdout" "$RECOVERY_JOB/stderr"
|
|
618
|
+
touch -t 200001010000 "$RECOVERY_STATE/worker.lock"
|
|
619
|
+
set +e
|
|
620
|
+
HOME="$RECOVERY_HOME" FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$RECOVERY_STATE" \
|
|
621
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux "$REMOTE_ROOT/bin/fm-remote-job-worker.sh" \
|
|
622
|
+
> "$TMP_ROOT/recovery-refused.out" 2> "$TMP_ROOT/recovery-refused.err"
|
|
623
|
+
RECOVERY_REFUSED_RC=$?
|
|
624
|
+
set -e
|
|
625
|
+
[ "$RECOVERY_REFUSED_RC" -ne 0 ] || fail "quarantine recovery ignored a recorded live process"
|
|
626
|
+
assert_present "$RECOVERY_STATE/worker.lock/quarantine" "a live recorded process lost quarantine protection"
|
|
627
|
+
printf '%s\n' "$QUARANTINED_PROCESS_PID" > "$RECOVERY_JOB/.claim/owner"
|
|
628
|
+
printf 'stale owner identity\n' > "$RECOVERY_JOB/.claim/owner_start"
|
|
629
|
+
printf 'stale supervisor identity\n' > "$RECOVERY_JOB/.claim/supervisor_start"
|
|
630
|
+
chmod 600 "$RECOVERY_JOB/.claim/owner" "$RECOVERY_JOB/.claim/owner_start" \
|
|
631
|
+
"$RECOVERY_JOB/.claim/supervisor_start"
|
|
632
|
+
HOME="$RECOVERY_HOME" FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$RECOVERY_STATE" \
|
|
633
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux "$REMOTE_ROOT/bin/fm-remote-job-worker.sh" \
|
|
634
|
+
> "$TMP_ROOT/recovery-worker.out" 2> "$TMP_ROOT/recovery-worker.err" &
|
|
635
|
+
RECOVERY_WORKER_PID=$!
|
|
636
|
+
for _ in $(seq 1 300); do
|
|
637
|
+
[ -f "$RECOVERY_STATE/worker.ready" ] && break
|
|
638
|
+
sleep 0.05
|
|
639
|
+
done
|
|
640
|
+
assert_present "$RECOVERY_STATE/worker.ready" "a reused supervisor pid did not permit worker recovery"
|
|
641
|
+
assert_absent "$RECOVERY_STATE/worker.lock/quarantine" "recovered worker retained stale quarantine"
|
|
642
|
+
kill -0 "$QUARANTINED_PROCESS_PID" 2>/dev/null \
|
|
643
|
+
|| fail "worker recovery signalled a process whose supervisor identity did not match"
|
|
644
|
+
kill -TERM "$RECOVERY_WORKER_PID"
|
|
645
|
+
wait "$RECOVERY_WORKER_PID" 2>/dev/null || true
|
|
646
|
+
RECOVERY_WORKER_PID=
|
|
647
|
+
kill "$QUARANTINED_PROCESS_PID" 2>/dev/null || true
|
|
648
|
+
wait "$QUARANTINED_PROCESS_PID" 2>/dev/null || true
|
|
649
|
+
pass "quarantine recovery refuses unverifiable supervisors and ignores reused pids"
|
|
650
|
+
|
|
651
|
+
# A replacement stops a Linux worker by signalling its whole isolated group, and
|
|
652
|
+
# the supervisor in that group forwards a second stop signal to the same serving
|
|
653
|
+
# child, so the serving child is always signalled more than once. Signal a small
|
|
654
|
+
# bounded burst and then keep signalling until it is gone: the first signal
|
|
655
|
+
# starts the shutdown and every later one lands inside it, the same way the group
|
|
656
|
+
# signal and the forwarded signal do. A shutdown that dies part way through
|
|
657
|
+
# leaves its ownership lock behind holding a half-written temp file no later
|
|
658
|
+
# worker can clear, and every replacement then fails to report ready.
|
|
659
|
+
#
|
|
660
|
+
# The burst is bounded and the follow-up signals are paced deliberately. An
|
|
661
|
+
# unpaced signal loop delivers hundreds of thousands of signals per second,
|
|
662
|
+
# which corrupts the signalled bash's own pending-trap bookkeeping ("warning:
|
|
663
|
+
# run_pending_traps: bad value in trap_list[15]") and then kills it part way
|
|
664
|
+
# through the shutdown with SIGTERM or SIGSEGV. That reports a shutdown defect
|
|
665
|
+
# this worker does not have. Ten back-to-back signals still all land inside the
|
|
666
|
+
# shutdown's first file operation, so the repeat this pins is unchanged: with
|
|
667
|
+
# the default disposition restored instead of ignored, the ownership lock is
|
|
668
|
+
# left behind every run.
|
|
669
|
+
REPEAT_HOME="$TMP_ROOT/repeat-signal-account"
|
|
670
|
+
REPEAT_STATE="$TMP_ROOT/repeat-signal-jobs"
|
|
671
|
+
mkdir -p "$REPEAT_HOME"
|
|
672
|
+
chmod 700 "$REPEAT_HOME"
|
|
673
|
+
HOME="$REPEAT_HOME" FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$REPEAT_STATE" \
|
|
674
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux "$REMOTE_ROOT/bin/fm-remote-job-worker.sh" --serve \
|
|
675
|
+
> "$TMP_ROOT/repeat-signal.out" 2> "$TMP_ROOT/repeat-signal.err" &
|
|
676
|
+
REPEAT_WORKER_PID=$!
|
|
677
|
+
for _ in $(seq 1 300); do
|
|
678
|
+
[ -f "$REPEAT_STATE/worker.ready" ] && break
|
|
679
|
+
sleep 0.05
|
|
680
|
+
done
|
|
681
|
+
assert_present "$REPEAT_STATE/worker.ready" "the repeated-signal worker did not become ready"
|
|
682
|
+
REPEAT_DEADLINE=$((SECONDS + 30))
|
|
683
|
+
REPEAT_BURST=0
|
|
684
|
+
while [ "$REPEAT_BURST" -lt 10 ]; do
|
|
685
|
+
kill -TERM "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
686
|
+
REPEAT_BURST=$((REPEAT_BURST + 1))
|
|
687
|
+
done
|
|
688
|
+
while kill -0 "$REPEAT_WORKER_PID" 2>/dev/null && [ "$SECONDS" -lt "$REPEAT_DEADLINE" ]; do
|
|
689
|
+
kill -TERM "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
690
|
+
sleep 0.05
|
|
691
|
+
done
|
|
692
|
+
if kill -0 "$REPEAT_WORKER_PID" 2>/dev/null; then
|
|
693
|
+
kill -KILL "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
694
|
+
wait "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
695
|
+
REPEAT_WORKER_PID=
|
|
696
|
+
fail "the repeatedly signalled worker never finished its shutdown"
|
|
697
|
+
fi
|
|
698
|
+
wait "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
699
|
+
REPEAT_WORKER_PID=
|
|
700
|
+
assert_absent "$REPEAT_STATE/worker.lock" \
|
|
701
|
+
"a repeatedly signalled shutdown left its ownership lock behind"
|
|
702
|
+
assert_absent "$REPEAT_STATE/worker.ready" \
|
|
703
|
+
"a repeatedly signalled shutdown left its readiness heartbeat behind"
|
|
704
|
+
HOME="$REPEAT_HOME" FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$REPEAT_STATE" \
|
|
705
|
+
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux "$REMOTE_ROOT/bin/fm-remote-job-worker.sh" --serve \
|
|
706
|
+
>> "$TMP_ROOT/repeat-signal.out" 2>> "$TMP_ROOT/repeat-signal.err" &
|
|
707
|
+
REPEAT_WORKER_PID=$!
|
|
708
|
+
for _ in $(seq 1 600); do
|
|
709
|
+
[ -f "$REPEAT_STATE/worker.ready" ] && break
|
|
710
|
+
sleep 0.05
|
|
711
|
+
done
|
|
712
|
+
assert_present "$REPEAT_STATE/worker.ready" \
|
|
713
|
+
"the worker after a repeatedly signalled shutdown never reported ready"
|
|
714
|
+
kill -TERM "$REPEAT_WORKER_PID"
|
|
715
|
+
wait "$REPEAT_WORKER_PID" 2>/dev/null || true
|
|
716
|
+
REPEAT_WORKER_PID=
|
|
717
|
+
pass "a repeatedly signalled shutdown still releases ownership for the next worker"
|
|
718
|
+
|
|
719
|
+
# A child that stays up for FM_REMOTE_JOB_SUPERVISOR_HEALTHY_SECONDS clears the
|
|
720
|
+
# consecutive-failure backoff, so a child that dies just past that threshold
|
|
721
|
+
# used to reset the only guard the supervisor had and restart forever. The
|
|
722
|
+
# fixture below is that worker: it exits non-zero after living just longer than
|
|
723
|
+
# the healthy window, so every restart is accounted as healthy-then-failed.
|
|
724
|
+
RESTART_ROOT="$TMP_ROOT/restart-root"
|
|
725
|
+
RESTART_HOME="$TMP_ROOT/restart-account"
|
|
726
|
+
RESTART_STATE="$TMP_ROOT/restart-state"
|
|
727
|
+
RESTART_CHILD_LOG="$TMP_ROOT/restart-children"
|
|
728
|
+
mkdir -p "$RESTART_ROOT/bin" "$RESTART_HOME"
|
|
729
|
+
cp "$ROOT/bin/fm-remote-job-lib.sh" "$RESTART_ROOT/bin/"
|
|
730
|
+
cp "$ROOT/bin/fm-remote-job-worker.sh" "$RESTART_ROOT/bin/fm-remote-job-supervisor-under-test.sh"
|
|
731
|
+
printf 'fixture\n' > "$RESTART_ROOT/AGENTS.md"
|
|
732
|
+
cat > "$RESTART_ROOT/bin/fm-remote-job-worker.sh" <<'SH'
|
|
733
|
+
#!/bin/bash
|
|
734
|
+
set -u
|
|
735
|
+
[ "${1:-}" = --serve ] || exit 2
|
|
736
|
+
printf '%s\n' "${BASHPID:-$$}" >> "$FM_TEST_SUPERVISOR_CHILD_LOG"
|
|
737
|
+
sleep "$FM_TEST_SUPERVISOR_CHILD_SECONDS"
|
|
738
|
+
exit "$FM_TEST_SUPERVISOR_CHILD_STATUS"
|
|
739
|
+
SH
|
|
740
|
+
chmod +x "$RESTART_ROOT/bin"/*.sh
|
|
741
|
+
HOME="$RESTART_HOME" FM_ROOT_OVERRIDE="$RESTART_ROOT" \
|
|
742
|
+
FM_REMOTE_JOB_STATE_ROOT="$RESTART_STATE" FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux \
|
|
743
|
+
FM_REMOTE_JOB_SUPERVISOR_HEALTHY_SECONDS=1 FM_REMOTE_JOB_SUPERVISOR_MAX_RESTARTS=3 \
|
|
744
|
+
FM_REMOTE_JOB_SUPERVISOR_MAX_BACKOFF_SECONDS=0 FM_TEST_SUPERVISOR_CHILD_LOG="$RESTART_CHILD_LOG" \
|
|
745
|
+
FM_TEST_SUPERVISOR_CHILD_SECONDS=1.1 FM_TEST_SUPERVISOR_CHILD_STATUS=1 \
|
|
746
|
+
"$RESTART_ROOT/bin/fm-remote-job-supervisor-under-test.sh" \
|
|
747
|
+
> "$TMP_ROOT/restart-supervisor.out" 2> "$TMP_ROOT/restart-supervisor.err" &
|
|
748
|
+
RESTART_SUPERVISOR_PID=$!
|
|
749
|
+
for _ in $(seq 1 300); do
|
|
750
|
+
kill -0 "$RESTART_SUPERVISOR_PID" 2>/dev/null || break
|
|
751
|
+
sleep 0.1
|
|
752
|
+
done
|
|
753
|
+
if kill -0 "$RESTART_SUPERVISOR_PID" 2>/dev/null; then
|
|
754
|
+
fail "workers dying just past the healthy threshold drove an unbounded restart loop"
|
|
755
|
+
fi
|
|
756
|
+
set +e
|
|
757
|
+
wait "$RESTART_SUPERVISOR_PID"
|
|
758
|
+
RESTART_SUPERVISOR_RC=$?
|
|
759
|
+
set -e
|
|
760
|
+
RESTART_SUPERVISOR_PID=
|
|
761
|
+
[ "$RESTART_SUPERVISOR_RC" -ne 0 ] || fail "the exhausted restart guard reported success"
|
|
762
|
+
[ "$(wc -l < "$RESTART_CHILD_LOG" | tr -d ' ')" -eq 3 ] \
|
|
763
|
+
|| fail "the restart guard did not stop at the configured maximum"
|
|
764
|
+
assert_grep "remote job worker exited 3 times; stopping the supervisor" "$TMP_ROOT/restart-supervisor.err" \
|
|
765
|
+
"the restart guard did not explain why it stopped"
|
|
766
|
+
pass "barely healthy worker failures remain bounded by the restart guard"
|
|
767
|
+
|
|
768
|
+
echo "ALL TESTS PASSED"
|