@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,2302 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Behavior tests for the backlog<->record pairing invariant:
|
|
3
|
+
# `state/<id>.meta` exists <=> this home's backlog row for that id is In flight.
|
|
4
|
+
#
|
|
5
|
+
# bin/fm-backlog-transition-lib.sh states the contract; the three scripts that
|
|
6
|
+
# own a task's physical record enforce it. These tests drive those real scripts
|
|
7
|
+
# against a real backlog file and the real tasks-axi CLI, and assert the
|
|
8
|
+
# resulting RECORD STATE - never the wording of a reminder a later turn was
|
|
9
|
+
# expected to act on, which is exactly what let the two records drift before.
|
|
10
|
+
#
|
|
11
|
+
# dispatch bin/fm-spawn.sh moves the row In flight in the same run that
|
|
12
|
+
# publishes the record, so a live worker the backlog does not own
|
|
13
|
+
# cannot arise on the ordinary path.
|
|
14
|
+
# completion bin/fm-teardown.sh closes the row before it reports success, so
|
|
15
|
+
# a finished task cannot be left showing as running.
|
|
16
|
+
# recovery bin/fm-bootstrap.sh reconciles THIS home's own books at session
|
|
17
|
+
# start, covering the millisecond crash window inside those two
|
|
18
|
+
# scripts and any drift a home was already carrying.
|
|
19
|
+
#
|
|
20
|
+
# The invariant is single-host: a home's backlog and its records live together,
|
|
21
|
+
# so a persistent secondmate keeps its own books through its own copies of these
|
|
22
|
+
# scripts. A parent's view of a mate lagging is a freshness question and is
|
|
23
|
+
# deliberately not asserted here.
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
# shellcheck source=tests/lib.sh
|
|
27
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
28
|
+
|
|
29
|
+
SPAWN="$ROOT/bin/fm-spawn.sh"
|
|
30
|
+
TEARDOWN="$ROOT/bin/fm-teardown.sh"
|
|
31
|
+
BOOTSTRAP="$ROOT/bin/fm-bootstrap.sh"
|
|
32
|
+
TMP_ROOT=$(fm_test_tmproot fm-backlog-atomicity)
|
|
33
|
+
|
|
34
|
+
command -v tasks-axi >/dev/null 2>&1 || {
|
|
35
|
+
printf 'ok - skipped (tasks-axi is not installed; the fused transitions are inert without it)\n'
|
|
36
|
+
exit 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# --- fixture ----------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
# A home with a real backlog, a real project clone with an origin, a pooled
|
|
42
|
+
# worktree, and stubs for every tool the spawn path shells out to.
|
|
43
|
+
make_home() { # <name> [task-id...]
|
|
44
|
+
local name=$1 case_dir home fakebin id
|
|
45
|
+
shift
|
|
46
|
+
case_dir="$TMP_ROOT/$name"
|
|
47
|
+
home="$case_dir/home"
|
|
48
|
+
fakebin=$(fm_fakebin "$case_dir")
|
|
49
|
+
mkdir -p "$home/state" "$home/config" "$home/data" "$home/projects"
|
|
50
|
+
touch "$home/state/.last-watcher-beat"
|
|
51
|
+
printf '%s\n' claude > "$home/config/crew-harness"
|
|
52
|
+
printf '%s\n' '# Backlog' '' '## In flight' '' '## Queued' '' '## Done' \
|
|
53
|
+
> "$home/data/backlog.md"
|
|
54
|
+
for id in "$@"; do
|
|
55
|
+
mkdir -p "$home/data/$id"
|
|
56
|
+
printf 'Delivery contract: mode=no-mistakes\nbrief for %s\n' "$id" > "$home/data/$id/brief.md"
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
cat > "$fakebin/tmux" <<'SH'
|
|
60
|
+
#!/usr/bin/env bash
|
|
61
|
+
case "$*" in *"#{pane_current_path}"*) printf '%s\n' "${FM_FAKE_PANE_PATH:-}"; exit 0 ;; esac
|
|
62
|
+
case "${1:-}" in display-message) printf 'firstmate\n'; exit 0 ;; esac
|
|
63
|
+
exit 0
|
|
64
|
+
SH
|
|
65
|
+
chmod +x "$fakebin/tmux"
|
|
66
|
+
fm_fake_exit0 "$fakebin" treehouse gh gh-axi no-mistakes
|
|
67
|
+
|
|
68
|
+
fm_git_init_commit "$case_dir/project"
|
|
69
|
+
fm_git_add_origin "$case_dir/project" "$case_dir/project.origin.git"
|
|
70
|
+
git -C "$case_dir/project" worktree add --quiet -b pooled "$case_dir/wt"
|
|
71
|
+
|
|
72
|
+
printf '%s\n' "$case_dir"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
home_of() { printf '%s/home\n' "$1"; }
|
|
76
|
+
backlog_of() { printf '%s/home/data/backlog.md\n' "$1"; }
|
|
77
|
+
|
|
78
|
+
add_item() { # <case-dir> <id> [kind]
|
|
79
|
+
tasks-axi add "$2" "item for $2" --kind "${3:-ship}" --file "$(backlog_of "$1")" >/dev/null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
start_item() { # <case-dir> <id>
|
|
83
|
+
tasks-axi start "$2" --file "$(backlog_of "$1")" >/dev/null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
row_state() { # <case-dir> <id>
|
|
87
|
+
tasks-axi show "$2" --file "$(backlog_of "$1")" 2>/dev/null |
|
|
88
|
+
sed -n 's/^ state: *//p' | head -1
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Shadow tasks-axi with a wrapper that fails one verb and delegates every other
|
|
92
|
+
# verb to the real binary, so a test can drive a genuine mid-transition failure
|
|
93
|
+
# without faking the reads around it.
|
|
94
|
+
require_show_cwd() { # <case-dir> <expected-dir>
|
|
95
|
+
local case_dir=$1 expected=$2 real
|
|
96
|
+
real=$(command -v tasks-axi)
|
|
97
|
+
cat > "$case_dir/fakebin/tasks-axi" <<SH
|
|
98
|
+
#!/usr/bin/env bash
|
|
99
|
+
case "\${1:-}" in
|
|
100
|
+
show|start|done)
|
|
101
|
+
if [ "\$PWD" != "$expected" ]; then
|
|
102
|
+
echo "error: wrong tasks root: \$PWD" >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
exec "$real" "\$@"
|
|
108
|
+
SH
|
|
109
|
+
chmod +x "$case_dir/fakebin/tasks-axi"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
make_tasks_axi_incompatible() { # <case-dir>
|
|
113
|
+
local case_dir=$1 real
|
|
114
|
+
real=$(command -v tasks-axi)
|
|
115
|
+
cat > "$case_dir/fakebin/tasks-axi" <<SH
|
|
116
|
+
#!/usr/bin/env bash
|
|
117
|
+
[ "\${1:-}" != --version ] || exit 1
|
|
118
|
+
exec "$real" "\$@"
|
|
119
|
+
SH
|
|
120
|
+
chmod +x "$case_dir/fakebin/tasks-axi"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
break_verb() { # <case-dir> <verb>
|
|
124
|
+
local case_dir=$1 verb=$2 real
|
|
125
|
+
real=$(command -v tasks-axi)
|
|
126
|
+
cat > "$case_dir/fakebin/tasks-axi" <<SH
|
|
127
|
+
#!/usr/bin/env bash
|
|
128
|
+
if [ "\${1:-}" = "$verb" ]; then
|
|
129
|
+
echo 'error: "backlog is unwritable"' >&2
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
exec "$real" "\$@"
|
|
133
|
+
SH
|
|
134
|
+
chmod +x "$case_dir/fakebin/tasks-axi"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interrupt_spawn_during_start() { # <case-dir> <before|after>
|
|
138
|
+
local case_dir=$1 timing=$2 real
|
|
139
|
+
real=$(command -v tasks-axi)
|
|
140
|
+
cat > "$case_dir/fakebin/tasks-axi" <<SH
|
|
141
|
+
#!/usr/bin/env bash
|
|
142
|
+
if [ "\${1:-}" = start ] && [ ! -f "$case_dir/start-interrupted" ]; then
|
|
143
|
+
: > "$case_dir/start-interrupted"
|
|
144
|
+
spawn_pid=\$(ps -o ppid= -p "\$PPID" | tr -d ' ')
|
|
145
|
+
case "\$spawn_pid" in ''|*[!0-9]*) exit 1 ;; esac
|
|
146
|
+
if [ "$timing" = before ]; then
|
|
147
|
+
kill -TERM "\$spawn_pid"
|
|
148
|
+
kill -TERM "\$\$"
|
|
149
|
+
fi
|
|
150
|
+
"$real" "\$@" || exit \$?
|
|
151
|
+
if [ "$timing" = after ]; then
|
|
152
|
+
kill -TERM "\$spawn_pid"
|
|
153
|
+
kill -TERM "\$\$"
|
|
154
|
+
fi
|
|
155
|
+
exit 0
|
|
156
|
+
fi
|
|
157
|
+
exec "$real" "\$@"
|
|
158
|
+
SH
|
|
159
|
+
chmod +x "$case_dir/fakebin/tasks-axi"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
change_row_on_second_show() { # <case-dir> <done|rm>
|
|
163
|
+
local case_dir=$1 action=$2 real
|
|
164
|
+
real=$(command -v tasks-axi)
|
|
165
|
+
cat > "$case_dir/fakebin/tasks-axi" <<SH
|
|
166
|
+
#!/usr/bin/env bash
|
|
167
|
+
if [ "\${1:-}" = show ]; then
|
|
168
|
+
count=0
|
|
169
|
+
[ ! -f "$case_dir/show-count" ] || count=\$(cat "$case_dir/show-count")
|
|
170
|
+
count=\$((count + 1))
|
|
171
|
+
printf '%s\n' "\$count" > "$case_dir/show-count"
|
|
172
|
+
if [ "\$count" -eq 2 ]; then
|
|
173
|
+
"$real" "$action" "\$2" --file "\$4" >/dev/null || exit 1
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
exec "$real" "\$@"
|
|
177
|
+
SH
|
|
178
|
+
chmod +x "$case_dir/fakebin/tasks-axi"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
break_launch_delivery() { # <case-dir>
|
|
182
|
+
local case_dir=$1
|
|
183
|
+
cat > "$case_dir/fakebin/tmux" <<'SH'
|
|
184
|
+
#!/usr/bin/env bash
|
|
185
|
+
case "$*" in *"#{pane_current_path}"*) printf '%s\n' "${FM_FAKE_PANE_PATH:-}"; exit 0 ;; esac
|
|
186
|
+
case "${1:-}" in
|
|
187
|
+
display-message) printf 'firstmate\n'; exit 0 ;;
|
|
188
|
+
send-keys) exit 1 ;;
|
|
189
|
+
esac
|
|
190
|
+
exit 0
|
|
191
|
+
SH
|
|
192
|
+
chmod +x "$case_dir/fakebin/tmux"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
track_teardown_resource_actions() { # <case-dir>
|
|
196
|
+
local case_dir=$1
|
|
197
|
+
cat > "$case_dir/fakebin/tmux" <<SH
|
|
198
|
+
#!/usr/bin/env bash
|
|
199
|
+
: > "$case_dir/backend-resource-action"
|
|
200
|
+
exit 0
|
|
201
|
+
SH
|
|
202
|
+
cat > "$case_dir/fakebin/treehouse" <<SH
|
|
203
|
+
#!/usr/bin/env bash
|
|
204
|
+
: > "$case_dir/local-copy-resource-action"
|
|
205
|
+
exit 0
|
|
206
|
+
SH
|
|
207
|
+
chmod +x "$case_dir/fakebin/tmux" "$case_dir/fakebin/treehouse"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interrupt_teardown_during_treehouse_return() { # <case-dir>
|
|
211
|
+
local case_dir=$1
|
|
212
|
+
cat > "$case_dir/fakebin/treehouse" <<SH
|
|
213
|
+
#!/usr/bin/env bash
|
|
214
|
+
if [ "\${1:-}" = return ] && [ ! -f "$case_dir/teardown-interrupted" ]; then
|
|
215
|
+
: > "$case_dir/teardown-interrupted"
|
|
216
|
+
teardown_pid=\$(ps -o ppid= -p "\$PPID" | tr -d ' ')
|
|
217
|
+
case "\$teardown_pid" in ''|*[!0-9]*) exit 1 ;; esac
|
|
218
|
+
kill -TERM "\$teardown_pid"
|
|
219
|
+
kill -TERM "\$\$"
|
|
220
|
+
fi
|
|
221
|
+
exit 0
|
|
222
|
+
SH
|
|
223
|
+
chmod +x "$case_dir/fakebin/treehouse"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interrupt_kimi_readiness() { # <case-dir>
|
|
227
|
+
local case_dir=$1 home
|
|
228
|
+
home=$(home_of "$case_dir")
|
|
229
|
+
mkdir -p "$home/.kimi-code"
|
|
230
|
+
printf '# test config\n' > "$home/.kimi-code/config.toml"
|
|
231
|
+
fm_fake_exit0 "$case_dir/fakebin" kimi
|
|
232
|
+
cat > "$case_dir/fakebin/tmux" <<SH
|
|
233
|
+
#!/usr/bin/env bash
|
|
234
|
+
case "\$*" in
|
|
235
|
+
*"#{pane_current_path}"*) printf '%s\\n' "\${FM_FAKE_PANE_PATH:-}"; exit 0 ;;
|
|
236
|
+
*"#{cursor_y}"*) printf '1\\n'; exit 0 ;;
|
|
237
|
+
esac
|
|
238
|
+
case "\${1:-}" in
|
|
239
|
+
display-message) printf 'firstmate\\n'; exit 0 ;;
|
|
240
|
+
capture-pane)
|
|
241
|
+
if [ ! -f "$case_dir/kimi-interrupted" ]; then
|
|
242
|
+
: > "$case_dir/kimi-interrupted"
|
|
243
|
+
spawn_pid=\$(ps -o ppid= -p "\$PPID" | tr -d ' ')
|
|
244
|
+
case "\$spawn_pid" in ''|*[!0-9]*) exit 1 ;; esac
|
|
245
|
+
kill -TERM "\$spawn_pid"
|
|
246
|
+
fi
|
|
247
|
+
printf 'shell starting\\n$ \\n'
|
|
248
|
+
exit 0
|
|
249
|
+
;;
|
|
250
|
+
esac
|
|
251
|
+
exit 0
|
|
252
|
+
SH
|
|
253
|
+
chmod +x "$case_dir/fakebin/tmux"
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
break_meta_removal() { # <case-dir> <meta-path>
|
|
257
|
+
local case_dir=$1 meta=$2 real
|
|
258
|
+
real=$(command -v rm)
|
|
259
|
+
cat > "$case_dir/fakebin/rm" <<SH
|
|
260
|
+
#!/usr/bin/env bash
|
|
261
|
+
for arg in "\$@"; do
|
|
262
|
+
[ "\$arg" != "$meta" ] || exit 1
|
|
263
|
+
done
|
|
264
|
+
exec "$real" "\$@"
|
|
265
|
+
SH
|
|
266
|
+
chmod +x "$case_dir/fakebin/rm"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
break_busy_removal() { # <case-dir> <id>
|
|
270
|
+
local case_dir=$1 id=$2 real state
|
|
271
|
+
real=$(command -v rm)
|
|
272
|
+
state="$(home_of "$case_dir")/state"
|
|
273
|
+
cat > "$case_dir/fakebin/rm" <<SH
|
|
274
|
+
#!/usr/bin/env bash
|
|
275
|
+
for arg in "\$@"; do
|
|
276
|
+
case "\$arg" in
|
|
277
|
+
"$state/$id.busy-state"|"$state/$id.busy-gen") exit 1 ;;
|
|
278
|
+
esac
|
|
279
|
+
done
|
|
280
|
+
exec "$real" "\$@"
|
|
281
|
+
SH
|
|
282
|
+
chmod +x "$case_dir/fakebin/rm"
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
remove_data_during_startup_budget_check() { # <case-dir>
|
|
286
|
+
local case_dir=$1 real data saved budget
|
|
287
|
+
real=$(command -v stat)
|
|
288
|
+
data="$(home_of "$case_dir")/data"
|
|
289
|
+
saved="$case_dir/bootstrap-data"
|
|
290
|
+
budget="$(home_of "$case_dir")/config/startup-memory-budget"
|
|
291
|
+
printf '7500\n' > "$budget"
|
|
292
|
+
cat > "$case_dir/fakebin/stat" <<SH
|
|
293
|
+
#!/usr/bin/env bash
|
|
294
|
+
for arg in "\$@"; do
|
|
295
|
+
if [ "\$arg" = "$budget" ] && [ ! -e "$case_dir/data-removed" ]; then
|
|
296
|
+
mv "$data" "$saved" || exit 1
|
|
297
|
+
: > "$case_dir/data-removed"
|
|
298
|
+
fi
|
|
299
|
+
done
|
|
300
|
+
exec "$real" "\$@"
|
|
301
|
+
SH
|
|
302
|
+
chmod +x "$case_dir/fakebin/stat"
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
break_meta_publication() { # <case-dir> <meta-path>
|
|
306
|
+
local case_dir=$1 meta=$2 real
|
|
307
|
+
real=$(command -v mv)
|
|
308
|
+
cat > "$case_dir/fakebin/mv" <<SH
|
|
309
|
+
#!/usr/bin/env bash
|
|
310
|
+
for arg in "\$@"; do
|
|
311
|
+
[ "\$arg" != "$meta" ] || exit 1
|
|
312
|
+
done
|
|
313
|
+
exec "$real" "\$@"
|
|
314
|
+
SH
|
|
315
|
+
chmod +x "$case_dir/fakebin/mv"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
write_task_meta() { # <case-dir> <id> <kind> <mode> [extra-line...]
|
|
319
|
+
local case_dir=$1 id=$2 kind=$3 mode=$4
|
|
320
|
+
shift 4
|
|
321
|
+
fm_write_meta "$(home_of "$case_dir")/state/$id.meta" \
|
|
322
|
+
"window=firstmate:fm-$id" \
|
|
323
|
+
"endpoint_task_id=$id" \
|
|
324
|
+
"worktree=$case_dir/absent-worktree" \
|
|
325
|
+
"project=$case_dir/absent-project" \
|
|
326
|
+
"harness=claude" \
|
|
327
|
+
"kind=$kind" \
|
|
328
|
+
"mode=$mode" \
|
|
329
|
+
"yolo=off" \
|
|
330
|
+
"$@"
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
run_spawn() { # <case-dir> <args...>
|
|
334
|
+
local case_dir=$1
|
|
335
|
+
shift
|
|
336
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
337
|
+
FM_SPAWN_NO_GUARD=1 FM_FAKE_PANE_PATH="$case_dir/wt" TMUX="fake,1,0" \
|
|
338
|
+
CLAUDE_CONFIG_DIR='' \
|
|
339
|
+
PATH="$case_dir/fakebin:$PATH" \
|
|
340
|
+
"$SPAWN" "$@" 2>&1
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
run_ship_spawn() { # <case-dir> <id>
|
|
344
|
+
local case_dir=$1 id=$2
|
|
345
|
+
run_spawn "$case_dir" "$id" "$case_dir/project" --mode no-mistakes --yolo off
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
# Teardown against a recorded worktree that no longer exists: the landed-work and
|
|
349
|
+
# worktree-return steps are then no-ops, which keeps these cases about the
|
|
350
|
+
# backlog transition rather than re-testing tests/fm-teardown.test.sh's matrix.
|
|
351
|
+
run_teardown() { # <case-dir> <id> [args...]
|
|
352
|
+
local case_dir=$1
|
|
353
|
+
shift
|
|
354
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
355
|
+
PATH="$case_dir/fakebin:$PATH" \
|
|
356
|
+
"$TEARDOWN" "$@" 2>&1
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
run_bootstrap() { # <case-dir>
|
|
360
|
+
local case_dir=$1
|
|
361
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
362
|
+
FM_BOOTSTRAP_NETWORK=skip \
|
|
363
|
+
PATH="$case_dir/fakebin:$PATH" \
|
|
364
|
+
"$BOOTSTRAP" 2>&1
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# --- dispatch ---------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
test_dispatch_moves_the_item_in_flight_in_the_same_run() {
|
|
370
|
+
local case_dir id out
|
|
371
|
+
id=atomic-dispatch-b1
|
|
372
|
+
case_dir=$(make_home dispatch-ok "$id")
|
|
373
|
+
add_item "$case_dir" "$id"
|
|
374
|
+
|
|
375
|
+
out=$(run_ship_spawn "$case_dir" "$id") || fail "spawn failed: $out"
|
|
376
|
+
assert_contains "$out" "spawned $id" "spawn did not report success"
|
|
377
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" "spawn published no record"
|
|
378
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
379
|
+
|| fail "spawn reported success with its backlog item still $(row_state "$case_dir" "$id")"
|
|
380
|
+
pass "dispatch publishes the record and moves the backlog item In flight in one run"
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
test_dispatch_refuses_a_pending_authoritative_close() {
|
|
384
|
+
local case_dir id marker out rc=0
|
|
385
|
+
id=atomic-dispatch-pending-close-b1
|
|
386
|
+
case_dir=$(make_home dispatch-pending-close "$id")
|
|
387
|
+
add_item "$case_dir" "$id"
|
|
388
|
+
start_item "$case_dir" "$id"
|
|
389
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
390
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-closing\narg=--pr\narg=https://github.com/example/repo/pull/12\n' \
|
|
391
|
+
"$id" "$(home_of "$case_dir")/data" > "$marker"
|
|
392
|
+
cat > "$case_dir/fakebin/tmux" <<SH
|
|
393
|
+
#!/usr/bin/env bash
|
|
394
|
+
case "\$*" in
|
|
395
|
+
*new-window*) : > "$case_dir/task-endpoint-created" ;;
|
|
396
|
+
*treehouse\\ get*) : > "$case_dir/local-copy-requested" ;;
|
|
397
|
+
*"#{pane_current_path}"*) printf '%s\n' "\${FM_FAKE_PANE_PATH:-}"; exit 0 ;;
|
|
398
|
+
esac
|
|
399
|
+
case "\${1:-}" in display-message) printf 'firstmate\n'; exit 0 ;; esac
|
|
400
|
+
exit 0
|
|
401
|
+
SH
|
|
402
|
+
chmod +x "$case_dir/fakebin/tmux"
|
|
403
|
+
|
|
404
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
405
|
+
[ "$rc" -ne 0 ] || fail "spawn accepted work with an authoritative close still pending"
|
|
406
|
+
assert_contains "$out" "pending authoritative backlog close" \
|
|
407
|
+
"spawn did not explain why the pending close blocks dispatch"
|
|
408
|
+
assert_present "$marker" "spawn discarded the pending authoritative close"
|
|
409
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
410
|
+
"spawn published a new worker over a pending close"
|
|
411
|
+
assert_absent "$case_dir/task-endpoint-created" \
|
|
412
|
+
"spawn created an unowned endpoint before refusing the pending close"
|
|
413
|
+
assert_absent "$case_dir/local-copy-requested" \
|
|
414
|
+
"spawn requested an unowned local copy before refusing the pending close"
|
|
415
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
416
|
+
|| fail "refused dispatch changed the pending close's backlog row"
|
|
417
|
+
pass "dispatch refuses to supersede a pending authoritative close"
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
test_dispatch_refuses_a_held_row_before_creating_resources() {
|
|
421
|
+
local case_dir id out rc=0
|
|
422
|
+
id=atomic-dispatch-held-b1
|
|
423
|
+
case_dir=$(make_home dispatch-held "$id")
|
|
424
|
+
add_item "$case_dir" "$id"
|
|
425
|
+
tasks-axi hold "$id" --reason "captain decision pending" --kind captain \
|
|
426
|
+
--file "$(backlog_of "$case_dir")" >/dev/null
|
|
427
|
+
cat > "$case_dir/fakebin/tmux" <<SH
|
|
428
|
+
#!/usr/bin/env bash
|
|
429
|
+
case "\$*" in
|
|
430
|
+
*new-window*) : > "$case_dir/task-endpoint-created" ;;
|
|
431
|
+
*treehouse\\ get*) : > "$case_dir/local-copy-requested" ;;
|
|
432
|
+
*"#{pane_current_path}"*) printf '%s\n' "\${FM_FAKE_PANE_PATH:-}"; exit 0 ;;
|
|
433
|
+
esac
|
|
434
|
+
case "\${1:-}" in display-message) printf 'firstmate\n'; exit 0 ;; esac
|
|
435
|
+
exit 0
|
|
436
|
+
SH
|
|
437
|
+
chmod +x "$case_dir/fakebin/tmux"
|
|
438
|
+
|
|
439
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
440
|
+
[ "$rc" -ne 0 ] || fail "spawn accepted a held backlog row"
|
|
441
|
+
assert_contains "$out" "state queued yes" \
|
|
442
|
+
"held-row refusal did not name the actual ineligible state"
|
|
443
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
444
|
+
"held-row refusal published a task record"
|
|
445
|
+
assert_absent "$case_dir/task-endpoint-created" \
|
|
446
|
+
"held-row refusal created an unowned endpoint"
|
|
447
|
+
assert_absent "$case_dir/local-copy-requested" \
|
|
448
|
+
"held-row refusal requested an unowned local copy"
|
|
449
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
450
|
+
|| fail "held-row refusal changed the backlog state"
|
|
451
|
+
pass "dispatch refuses held rows before creating resources"
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
test_dispatch_refuses_a_blocked_row_before_creating_resources() {
|
|
455
|
+
local case_dir id blocker out rc=0
|
|
456
|
+
id=atomic-dispatch-blocked-b16
|
|
457
|
+
blocker=atomic-dispatch-blocker-b16
|
|
458
|
+
case_dir=$(make_home dispatch-blocked "$id" "$blocker")
|
|
459
|
+
add_item "$case_dir" "$blocker"
|
|
460
|
+
tasks-axi add "$id" "item for $id" --kind ship --blocked-by "$blocker" \
|
|
461
|
+
--file "$(backlog_of "$case_dir")" >/dev/null
|
|
462
|
+
cat > "$case_dir/fakebin/tmux" <<SH
|
|
463
|
+
#!/usr/bin/env bash
|
|
464
|
+
case "\$*" in
|
|
465
|
+
*new-window*) : > "$case_dir/task-endpoint-created" ;;
|
|
466
|
+
*treehouse\\ get*) : > "$case_dir/local-copy-requested" ;;
|
|
467
|
+
*"#{pane_current_path}"*) printf '%s\n' "\${FM_FAKE_PANE_PATH:-}"; exit 0 ;;
|
|
468
|
+
esac
|
|
469
|
+
case "\${1:-}" in display-message) printf 'firstmate\n'; exit 0 ;; esac
|
|
470
|
+
exit 0
|
|
471
|
+
SH
|
|
472
|
+
chmod +x "$case_dir/fakebin/tmux"
|
|
473
|
+
|
|
474
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
475
|
+
[ "$rc" -ne 0 ] || fail "spawn accepted a dependency-blocked backlog row"
|
|
476
|
+
assert_contains "$out" "state queued no yes" \
|
|
477
|
+
"blocked-row refusal did not name the actual ineligible state"
|
|
478
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
479
|
+
"blocked-row refusal published a task record"
|
|
480
|
+
assert_absent "$case_dir/task-endpoint-created" \
|
|
481
|
+
"blocked-row refusal created an unowned endpoint"
|
|
482
|
+
assert_absent "$case_dir/local-copy-requested" \
|
|
483
|
+
"blocked-row refusal requested an unowned local copy"
|
|
484
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
485
|
+
|| fail "blocked-row refusal changed the backlog state"
|
|
486
|
+
pass "dispatch refuses dependency-blocked rows before creating resources"
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
test_dispatch_refuses_a_held_in_flight_row_before_relaunch() {
|
|
490
|
+
local case_dir id out rc=0
|
|
491
|
+
id=atomic-dispatch-held-in-flight-b16
|
|
492
|
+
case_dir=$(make_home dispatch-held-in-flight "$id")
|
|
493
|
+
add_item "$case_dir" "$id"
|
|
494
|
+
start_item "$case_dir" "$id"
|
|
495
|
+
tasks-axi hold "$id" --reason "captain decision pending" --kind captain \
|
|
496
|
+
--file "$(backlog_of "$case_dir")" >/dev/null
|
|
497
|
+
cat > "$case_dir/fakebin/tmux" <<SH
|
|
498
|
+
#!/usr/bin/env bash
|
|
499
|
+
case "\$*" in
|
|
500
|
+
*new-window*) : > "$case_dir/task-endpoint-created" ;;
|
|
501
|
+
*treehouse\\ get*) : > "$case_dir/local-copy-requested" ;;
|
|
502
|
+
*"#{pane_current_path}"*) printf '%s\n' "\${FM_FAKE_PANE_PATH:-}"; exit 0 ;;
|
|
503
|
+
esac
|
|
504
|
+
case "\${1:-}" in display-message) printf 'firstmate\n'; exit 0 ;; esac
|
|
505
|
+
exit 0
|
|
506
|
+
SH
|
|
507
|
+
chmod +x "$case_dir/fakebin/tmux"
|
|
508
|
+
|
|
509
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
510
|
+
[ "$rc" -ne 0 ] || fail "spawn accepted a held In-flight backlog row"
|
|
511
|
+
assert_contains "$out" "state in_flight yes no" \
|
|
512
|
+
"held In-flight refusal did not name the actual ineligible state"
|
|
513
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
514
|
+
"held In-flight refusal published a task record"
|
|
515
|
+
assert_absent "$case_dir/task-endpoint-created" \
|
|
516
|
+
"held In-flight refusal created a replacement endpoint"
|
|
517
|
+
assert_absent "$case_dir/local-copy-requested" \
|
|
518
|
+
"held In-flight refusal requested a replacement local copy"
|
|
519
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
520
|
+
|| fail "held In-flight refusal changed the backlog state"
|
|
521
|
+
pass "dispatch refuses held In-flight rows before relaunch"
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
test_dispatch_reads_the_row_from_the_backlog_root() {
|
|
525
|
+
local case_dir id out
|
|
526
|
+
id=atomic-dispatch-root-b2
|
|
527
|
+
case_dir=$(make_home dispatch-root "$id")
|
|
528
|
+
add_item "$case_dir" "$id"
|
|
529
|
+
require_show_cwd "$case_dir" "$(cd "$(home_of "$case_dir")" && pwd -P)"
|
|
530
|
+
|
|
531
|
+
out=$(run_ship_spawn "$case_dir" "$id") || fail "spawn read outside the backlog root: $out"
|
|
532
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
533
|
+
|| fail "root-addressed dispatch left the backlog row queued"
|
|
534
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
535
|
+
"root-addressed dispatch did not publish its task record"
|
|
536
|
+
pass "dispatch reads backlog rows from the backlog addressing root"
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
test_recovery_uses_the_parent_of_a_trailing_slash_data_record() {
|
|
540
|
+
local case_dir id relocated backlog marker out
|
|
541
|
+
id=atomic-recovery-relocated-root-b2
|
|
542
|
+
case_dir=$(make_home recovery-relocated-root)
|
|
543
|
+
relocated="$case_dir/fm-records"
|
|
544
|
+
mkdir -p "$relocated"
|
|
545
|
+
backlog="$relocated/backlog.md"
|
|
546
|
+
printf '%s\n' '# Backlog' '' '## In flight' '' '## Queued' '' '## Done' > "$backlog"
|
|
547
|
+
tasks-axi add "$id" "item for $id" --kind ship --file "$backlog" >/dev/null
|
|
548
|
+
tasks-axi start "$id" --file "$backlog" >/dev/null
|
|
549
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
550
|
+
printf 'id=%s\ndata=%s/\nspawn_gen=spawn-relocated-recovery\narg=--note\narg=local%%20main\n' "$id" "$relocated" > "$marker"
|
|
551
|
+
require_show_cwd "$case_dir" "$(cd "$case_dir" && pwd -P)"
|
|
552
|
+
|
|
553
|
+
out=$(FM_DATA_OVERRIDE="$relocated/" run_bootstrap "$case_dir")
|
|
554
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = "done" ] \
|
|
555
|
+
|| fail "relocated-data recovery used the wrong addressing root: $out"
|
|
556
|
+
assert_absent "$marker" "relocated-data recovery retained its close marker"
|
|
557
|
+
pass "recovery uses the parent of a trailing-slash data record"
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
test_completion_targets_a_nested_relative_data_directory() {
|
|
561
|
+
local case_dir id relative_data data data_resolved backlog out
|
|
562
|
+
id=atomic-close-relative-data-b2
|
|
563
|
+
case_dir=$(make_home close-relative-data)
|
|
564
|
+
relative_data=relocated/data
|
|
565
|
+
data="$case_dir/$relative_data"
|
|
566
|
+
mkdir -p "$case_dir/relocated"
|
|
567
|
+
mv "$(home_of "$case_dir")/data" "$data"
|
|
568
|
+
data_resolved=$(cd "$data" && pwd -P)
|
|
569
|
+
backlog="$data/backlog.md"
|
|
570
|
+
tasks-axi add "$id" "item for $id" --kind ship --file "$backlog" >/dev/null
|
|
571
|
+
tasks-axi start "$id" --file "$backlog" >/dev/null
|
|
572
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-relative-data"
|
|
573
|
+
|
|
574
|
+
out=$(cd "$case_dir" && \
|
|
575
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
576
|
+
FM_DATA_OVERRIDE="$relative_data" PATH="$case_dir/fakebin:$PATH" \
|
|
577
|
+
"$TEARDOWN" "$id" 2>&1) \
|
|
578
|
+
|| fail "relative-data teardown failed: $out"
|
|
579
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = "done" ] \
|
|
580
|
+
|| fail "relative-data teardown mutated a different backlog file"
|
|
581
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
582
|
+
"relative-data teardown retained its task record"
|
|
583
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
584
|
+
"relative-data teardown retained its close marker"
|
|
585
|
+
assert_contains "$out" "closed in $data_resolved/backlog.md" \
|
|
586
|
+
"relative-data completion collapsed the configured backlog path"
|
|
587
|
+
pass "completion targets nested relative data from the caller directory"
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
test_immediate_child_absolute_data_dispatches_and_completes() {
|
|
591
|
+
local case_dir id data data_resolved backlog out
|
|
592
|
+
id=atomic-immediate-child-data-b2
|
|
593
|
+
case_dir=$(make_home immediate-child-data "$id")
|
|
594
|
+
data="$case_dir/fm-records"
|
|
595
|
+
mv "$(home_of "$case_dir")/data" "$data"
|
|
596
|
+
data_resolved=$(cd "$data" && pwd -P)
|
|
597
|
+
backlog="$data/backlog.md"
|
|
598
|
+
tasks-axi add "$id" "item for $id" --kind ship --file "$backlog" >/dev/null
|
|
599
|
+
|
|
600
|
+
out=$(FM_DATA_OVERRIDE="$data" run_ship_spawn "$case_dir" "$id") \
|
|
601
|
+
|| fail "immediate-child-data spawn failed: $out"
|
|
602
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = in_flight ] \
|
|
603
|
+
|| fail "immediate-child absolute dispatch mutated a different backlog"
|
|
604
|
+
rm -f "$(home_of "$case_dir")/state/$id.meta"
|
|
605
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-immediate-child"
|
|
606
|
+
out=$(FM_DATA_OVERRIDE="$data" run_teardown "$case_dir" "$id") \
|
|
607
|
+
|| fail "immediate-child-data teardown failed: $out"
|
|
608
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = "done" ] \
|
|
609
|
+
|| fail "immediate-child absolute completion mutated a different backlog"
|
|
610
|
+
assert_contains "$out" "closed in $data_resolved/backlog.md" \
|
|
611
|
+
"relocated completion confirmed the wrong backlog path"
|
|
612
|
+
pass "an immediate-child absolute data path keeps one paired backlog"
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
test_bare_relative_data_dispatches_and_completes() {
|
|
616
|
+
local case_dir id data backlog out
|
|
617
|
+
id=atomic-bare-relative-data-b2
|
|
618
|
+
case_dir=$(make_home bare-relative-data "$id")
|
|
619
|
+
data="$case_dir/records"
|
|
620
|
+
mv "$(home_of "$case_dir")/data" "$data"
|
|
621
|
+
backlog="$data/backlog.md"
|
|
622
|
+
tasks-axi add "$id" "item for $id" --kind ship --file "$backlog" >/dev/null
|
|
623
|
+
|
|
624
|
+
out=$(cd "$case_dir" && FM_DATA_OVERRIDE=records run_ship_spawn "$case_dir" "$id") \
|
|
625
|
+
|| fail "bare-relative-data spawn failed: $out"
|
|
626
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = in_flight ] \
|
|
627
|
+
|| fail "bare relative dispatch mutated a different backlog"
|
|
628
|
+
rm -f "$(home_of "$case_dir")/state/$id.meta"
|
|
629
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-bare-relative"
|
|
630
|
+
out=$(cd "$case_dir" && FM_DATA_OVERRIDE=records run_teardown "$case_dir" "$id") \
|
|
631
|
+
|| fail "bare-relative-data teardown failed: $out"
|
|
632
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = "done" ] \
|
|
633
|
+
|| fail "bare relative completion mutated a different backlog"
|
|
634
|
+
pass "bare relative data addresses one backlog through dispatch and completion"
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
test_dispatch_refuses_a_symlinked_backlog_without_crossing_homes() {
|
|
638
|
+
local case_dir foreign_case id local_backlog foreign_backlog out rc=0
|
|
639
|
+
id=atomic-dispatch-symlink-backlog-b2
|
|
640
|
+
case_dir=$(make_home dispatch-symlink-backlog "$id")
|
|
641
|
+
foreign_case=$(make_home dispatch-symlink-backlog-foreign)
|
|
642
|
+
add_item "$foreign_case" "$id"
|
|
643
|
+
local_backlog=$(backlog_of "$case_dir")
|
|
644
|
+
foreign_backlog=$(backlog_of "$foreign_case")
|
|
645
|
+
rm -f "$local_backlog"
|
|
646
|
+
ln -s "$foreign_backlog" "$local_backlog"
|
|
647
|
+
|
|
648
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
649
|
+
[ "$rc" -ne 0 ] || fail "spawn accepted a symlinked backlog"
|
|
650
|
+
assert_contains "$out" "backlog file resolves outside its authorized directory" \
|
|
651
|
+
"spawn did not identify the unsafe backlog boundary"
|
|
652
|
+
[ -L "$local_backlog" ] || fail "spawn replaced the local backlog symlink"
|
|
653
|
+
[ "$(row_state "$foreign_case" "$id")" = queued ] \
|
|
654
|
+
|| fail "spawn mutated the foreign backlog row"
|
|
655
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
656
|
+
"unsafe backlog dispatch published a local task record"
|
|
657
|
+
pass "dispatch refuses symlinked backlogs without crossing homes"
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
test_automatic_backend_refuses_incompatible_tasks_axi_before_mutation() {
|
|
661
|
+
local spawn_case teardown_case id out rc=0
|
|
662
|
+
id=atomic-incompatible-tasks-axi-b2
|
|
663
|
+
spawn_case=$(make_home incompatible-tasks-axi-spawn "$id")
|
|
664
|
+
add_item "$spawn_case" "$id"
|
|
665
|
+
make_tasks_axi_incompatible "$spawn_case"
|
|
666
|
+
|
|
667
|
+
out=$(run_ship_spawn "$spawn_case" "$id") || rc=$?
|
|
668
|
+
[ "$rc" -ne 0 ] || fail "automatic spawn succeeded without compatible tasks-axi"
|
|
669
|
+
assert_contains "$out" "automatic backlog transitions require tasks-axi" \
|
|
670
|
+
"automatic spawn did not report its unavailable transition tool"
|
|
671
|
+
assert_absent "$(home_of "$spawn_case")/state/$id.meta" \
|
|
672
|
+
"automatic spawn published a record without transition tooling"
|
|
673
|
+
rm -f "$spawn_case/fakebin/tasks-axi"
|
|
674
|
+
[ "$(row_state "$spawn_case" "$id")" = queued ] \
|
|
675
|
+
|| fail "automatic spawn changed the row without transition tooling"
|
|
676
|
+
|
|
677
|
+
teardown_case=$(make_home incompatible-tasks-axi-teardown)
|
|
678
|
+
add_item "$teardown_case" "$id"
|
|
679
|
+
start_item "$teardown_case" "$id"
|
|
680
|
+
write_task_meta "$teardown_case" "$id" ship local-only "spawn_gen=spawn-incompatible"
|
|
681
|
+
make_tasks_axi_incompatible "$teardown_case"
|
|
682
|
+
rc=0
|
|
683
|
+
out=$(run_teardown "$teardown_case" "$id") || rc=$?
|
|
684
|
+
[ "$rc" -ne 0 ] || fail "automatic teardown succeeded without compatible tasks-axi"
|
|
685
|
+
assert_contains "$out" "automatic backlog transitions require tasks-axi" \
|
|
686
|
+
"automatic teardown did not report its unavailable transition tool"
|
|
687
|
+
assert_present "$(home_of "$teardown_case")/state/$id.meta" \
|
|
688
|
+
"automatic teardown removed its record without transition tooling"
|
|
689
|
+
rm -f "$teardown_case/fakebin/tasks-axi"
|
|
690
|
+
[ "$(row_state "$teardown_case" "$id")" = in_flight ] \
|
|
691
|
+
|| fail "automatic teardown changed the row without transition tooling"
|
|
692
|
+
pass "automatic homes refuse lifecycle mutation without compatible tasks-axi"
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
test_dispatch_refuses_an_unresolvable_data_directory() {
|
|
696
|
+
local case_dir id saved out rc=0
|
|
697
|
+
id=atomic-dispatch-missing-data-b2
|
|
698
|
+
case_dir=$(make_home dispatch-missing-data "$id")
|
|
699
|
+
add_item "$case_dir" "$id"
|
|
700
|
+
saved="$case_dir/backlog-data"
|
|
701
|
+
mv "$(home_of "$case_dir")/data" "$saved"
|
|
702
|
+
|
|
703
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
704
|
+
[ "$rc" -ne 0 ] || fail "spawn succeeded with an unresolvable data directory"
|
|
705
|
+
assert_contains "$out" "task $id" \
|
|
706
|
+
"spawn did not identify the task blocked by fatal backlog addressing"
|
|
707
|
+
assert_contains "$out" "$(home_of "$case_dir")/data" \
|
|
708
|
+
"spawn did not identify the inaccessible data directory"
|
|
709
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
710
|
+
"fatal backlog addressing created a task record"
|
|
711
|
+
[ "$(tasks-axi show "$id" --file "$saved/backlog.md" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = queued ] \
|
|
712
|
+
|| fail "fatal backlog addressing changed the queued row"
|
|
713
|
+
pass "dispatch refuses an unresolvable backlog data directory"
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
test_completion_refuses_an_unresolvable_data_directory() {
|
|
717
|
+
local case_dir id saved meta out rc=0
|
|
718
|
+
id=atomic-close-missing-data-b2
|
|
719
|
+
case_dir=$(make_home close-missing-data)
|
|
720
|
+
add_item "$case_dir" "$id"
|
|
721
|
+
start_item "$case_dir" "$id"
|
|
722
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-missing-data"
|
|
723
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
724
|
+
saved="$case_dir/backlog-data"
|
|
725
|
+
mv "$(home_of "$case_dir")/data" "$saved"
|
|
726
|
+
|
|
727
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
728
|
+
[ "$rc" -ne 0 ] || fail "teardown succeeded with an unresolvable data directory"
|
|
729
|
+
assert_contains "$out" "task $id cannot be torn down" \
|
|
730
|
+
"teardown did not identify the task blocked by fatal backlog addressing"
|
|
731
|
+
assert_present "$meta" "fatal backlog addressing removed the task record"
|
|
732
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
733
|
+
"fatal backlog addressing wrote a close marker"
|
|
734
|
+
[ "$(tasks-axi show "$id" --file "$saved/backlog.md" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = in_flight ] \
|
|
735
|
+
|| fail "fatal backlog addressing changed the In-flight row"
|
|
736
|
+
pass "completion refuses before mutation when backlog data is unresolvable"
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
test_dispatch_refuses_an_id_this_home_has_no_item_for() {
|
|
740
|
+
local case_dir id out rc=0
|
|
741
|
+
id=atomic-dispatch-b2
|
|
742
|
+
case_dir=$(make_home dispatch-no-item "$id")
|
|
743
|
+
|
|
744
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
745
|
+
[ "$rc" -ne 0 ] || fail "spawn dispatched work no backlog item owns"
|
|
746
|
+
assert_contains "$out" "no backlog item in this home" \
|
|
747
|
+
"spawn refused without naming the missing backlog item"
|
|
748
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
749
|
+
"refused dispatch still left a record behind"
|
|
750
|
+
pass "dispatch refuses, before creating anything, when the home has no item for the id"
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
test_dispatch_reports_a_backlog_read_failure() {
|
|
754
|
+
local case_dir id out rc=0
|
|
755
|
+
id=atomic-dispatch-read-failure-b3
|
|
756
|
+
case_dir=$(make_home dispatch-read-failure "$id")
|
|
757
|
+
add_item "$case_dir" "$id"
|
|
758
|
+
break_verb "$case_dir" show
|
|
759
|
+
|
|
760
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
761
|
+
[ "$rc" -ne 0 ] || fail "spawn succeeded though backlog preflight could not read its item"
|
|
762
|
+
assert_contains "$out" "backlog item could not be read before dispatch" \
|
|
763
|
+
"spawn misreported a backlog read failure"
|
|
764
|
+
assert_contains "$out" "backlog is unwritable" \
|
|
765
|
+
"spawn discarded the backlog reader's diagnostic"
|
|
766
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
767
|
+
"failed backlog preflight created a task record"
|
|
768
|
+
pass "dispatch distinguishes backlog read failures from missing items"
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
test_dispatch_refuses_a_closed_item() {
|
|
772
|
+
local case_dir id out rc=0
|
|
773
|
+
id=atomic-dispatch-b3
|
|
774
|
+
case_dir=$(make_home dispatch-closed "$id")
|
|
775
|
+
add_item "$case_dir" "$id"
|
|
776
|
+
tasks-axi "done" "$id" --file "$(backlog_of "$case_dir")" >/dev/null
|
|
777
|
+
|
|
778
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
779
|
+
[ "$rc" -ne 0 ] || fail "spawn dispatched onto an item the backlog already closed"
|
|
780
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
781
|
+
|| fail "refused dispatch silently reopened a closed item"
|
|
782
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
783
|
+
"refused dispatch onto a closed item still left a record behind"
|
|
784
|
+
pass "dispatch refuses a closed item instead of silently reopening it"
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
test_dispatch_refuses_to_commit_without_a_published_record() {
|
|
788
|
+
local case_dir id meta out rc=0
|
|
789
|
+
id=atomic-dispatch-publish-failure-b4
|
|
790
|
+
case_dir=$(make_home dispatch-publish-failure "$id")
|
|
791
|
+
add_item "$case_dir" "$id"
|
|
792
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
793
|
+
break_meta_publication "$case_dir" "$meta"
|
|
794
|
+
|
|
795
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
796
|
+
[ "$rc" -ne 0 ] || fail "spawn succeeded without publishing its task record"
|
|
797
|
+
assert_contains "$out" "task record for $id could not be published" \
|
|
798
|
+
"spawn did not report task-record publication failure"
|
|
799
|
+
assert_absent "$meta" "failed publication left a task record"
|
|
800
|
+
assert_absent "$(home_of "$case_dir")/state/$id.busy-state" \
|
|
801
|
+
"failed publication retained its busy state"
|
|
802
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
803
|
+
|| fail "failed publication moved the backlog row"
|
|
804
|
+
pass "dispatch cannot commit without a verified task-record publication"
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
test_dispatch_leaves_no_record_when_the_transition_fails() {
|
|
808
|
+
local case_dir id out rc=0
|
|
809
|
+
id=atomic-dispatch-b4
|
|
810
|
+
case_dir=$(make_home dispatch-transition-fails "$id")
|
|
811
|
+
add_item "$case_dir" "$id"
|
|
812
|
+
break_verb "$case_dir" start
|
|
813
|
+
|
|
814
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
815
|
+
[ "$rc" -ne 0 ] || fail "spawn reported success though the backlog transition failed"
|
|
816
|
+
assert_contains "$out" "could not be moved to In flight" \
|
|
817
|
+
"spawn failed without explaining the backlog transition failure"
|
|
818
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
819
|
+
"a failed backlog transition left an orphaned record behind"
|
|
820
|
+
assert_absent "$(home_of "$case_dir")/state/$id.busy-state" \
|
|
821
|
+
"a failed backlog transition left the task's armed busy generation behind"
|
|
822
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
823
|
+
|| fail "a failed dispatch left the backlog item in $(row_state "$case_dir" "$id")"
|
|
824
|
+
pass "a failed backlog transition fails the dispatch loudly and leaves no record"
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
test_dispatch_reports_an_incomplete_record_rollback() {
|
|
828
|
+
local case_dir id meta out rc=0
|
|
829
|
+
id=atomic-dispatch-remove-failure-b5
|
|
830
|
+
case_dir=$(make_home dispatch-remove-failure "$id")
|
|
831
|
+
add_item "$case_dir" "$id"
|
|
832
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
833
|
+
break_verb "$case_dir" start
|
|
834
|
+
break_meta_removal "$case_dir" "$meta"
|
|
835
|
+
|
|
836
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
837
|
+
[ "$rc" -ne 0 ] || fail "spawn reported success though transition and rollback failed"
|
|
838
|
+
assert_contains "$out" "failed-dispatch cleanup is incomplete" \
|
|
839
|
+
"spawn did not report that its provisional record remained"
|
|
840
|
+
assert_present "$meta" "failed record removal was reported as successful"
|
|
841
|
+
assert_absent "$(home_of "$case_dir")/state/$id.busy-state" \
|
|
842
|
+
"record-removal failure prevented busy-state rollback"
|
|
843
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
844
|
+
|| fail "failed rollback changed the backlog row"
|
|
845
|
+
pass "dispatch reports when failed-transition rollback cannot remove its record"
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
test_dispatch_reports_an_incomplete_busy_rollback() {
|
|
849
|
+
local case_dir id out rc=0
|
|
850
|
+
id=atomic-dispatch-busy-remove-failure-b5
|
|
851
|
+
case_dir=$(make_home dispatch-busy-remove-failure "$id")
|
|
852
|
+
add_item "$case_dir" "$id"
|
|
853
|
+
break_verb "$case_dir" start
|
|
854
|
+
break_busy_removal "$case_dir" "$id"
|
|
855
|
+
|
|
856
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
857
|
+
[ "$rc" -ne 0 ] || fail "spawn succeeded though busy rollback failed"
|
|
858
|
+
assert_contains "$out" "did not remove both task and busy records" \
|
|
859
|
+
"spawn did not report incomplete busy rollback"
|
|
860
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
861
|
+
"busy rollback failure retained the provisional task record"
|
|
862
|
+
assert_present "$(home_of "$case_dir")/state/$id.busy-state" \
|
|
863
|
+
"busy removal failure was reported as successful"
|
|
864
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
865
|
+
|| fail "failed busy rollback changed the backlog row"
|
|
866
|
+
pass "dispatch verifies both task and busy records during rollback"
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
test_dispatch_rolls_back_before_a_failed_launch_delivery() {
|
|
870
|
+
local case_dir id out rc=0
|
|
871
|
+
id=atomic-dispatch-delivery-fails-b5
|
|
872
|
+
case_dir=$(make_home dispatch-delivery-fails "$id")
|
|
873
|
+
add_item "$case_dir" "$id"
|
|
874
|
+
break_launch_delivery "$case_dir"
|
|
875
|
+
|
|
876
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
877
|
+
[ "$rc" -ne 0 ] || fail "spawn reported success though launch delivery failed"
|
|
878
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
879
|
+
"a failed launch delivery left its provisional record behind"
|
|
880
|
+
assert_absent "$(home_of "$case_dir")/state/$id.busy-state" \
|
|
881
|
+
"a failed launch delivery left its provisional busy generation behind"
|
|
882
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
883
|
+
|| fail "launch delivery failed after committing backlog state $(row_state "$case_dir" "$id")"
|
|
884
|
+
pass "dispatch commits neither record nor backlog state before launch delivery succeeds"
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
test_dispatch_defers_interruption_across_backlog_commit() {
|
|
888
|
+
local timing case_dir id out rc
|
|
889
|
+
for timing in before after; do
|
|
890
|
+
id="atomic-dispatch-interrupted-$timing-b5"
|
|
891
|
+
case_dir=$(make_home "dispatch-interrupted-$timing" "$id")
|
|
892
|
+
add_item "$case_dir" "$id"
|
|
893
|
+
interrupt_spawn_during_start "$case_dir" "$timing"
|
|
894
|
+
|
|
895
|
+
rc=0
|
|
896
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
897
|
+
[ "$rc" -ne 0 ] || fail "a $timing-commit interruption was reported as success"
|
|
898
|
+
assert_contains "$out" "paired task record and In-flight backlog state were preserved" \
|
|
899
|
+
"a $timing-commit interruption did not report its atomic outcome"
|
|
900
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
901
|
+
|| fail "a $timing-commit interruption left the backlog row queued"
|
|
902
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
903
|
+
"a $timing-commit interruption removed the paired task record"
|
|
904
|
+
done
|
|
905
|
+
pass "dispatch retries interrupted transitions before honoring termination"
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
test_dispatch_interruption_during_kimi_readiness_fails_before_commit() {
|
|
909
|
+
local case_dir home id out rc=0
|
|
910
|
+
id=atomic-dispatch-kimi-readiness-signal-b5
|
|
911
|
+
case_dir=$(make_home dispatch-kimi-readiness-signal "$id")
|
|
912
|
+
home=$(home_of "$case_dir")
|
|
913
|
+
add_item "$case_dir" "$id"
|
|
914
|
+
interrupt_kimi_readiness "$case_dir"
|
|
915
|
+
|
|
916
|
+
out=$(HOME="$home" FM_KIMI_READY_POLLS=2 FM_KIMI_POLL_INTERVAL=0 \
|
|
917
|
+
run_spawn "$case_dir" "$id" "$case_dir/project" --harness kimi \
|
|
918
|
+
--mode no-mistakes --yolo off) || rc=$?
|
|
919
|
+
[ "$rc" -ne 0 ] || fail "Kimi readiness interruption was reported as success"
|
|
920
|
+
assert_absent "$home/state/$id.meta" \
|
|
921
|
+
"Kimi readiness interruption retained an unconfirmed task record"
|
|
922
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
923
|
+
|| fail "Kimi readiness interruption committed unconfirmed work In flight: $out"
|
|
924
|
+
pass "Kimi readiness interruptions fail before backlog commit"
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
test_dispatch_does_not_resurrect_a_row_closed_after_preflight() {
|
|
928
|
+
local case_dir id out rc=0
|
|
929
|
+
id=atomic-dispatch-closed-race-b5
|
|
930
|
+
case_dir=$(make_home dispatch-closed-race "$id")
|
|
931
|
+
add_item "$case_dir" "$id"
|
|
932
|
+
change_row_on_second_show "$case_dir" "done"
|
|
933
|
+
|
|
934
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
935
|
+
[ "$rc" -ne 0 ] || fail "spawn succeeded after its backlog row was closed"
|
|
936
|
+
assert_contains "$out" "state done" "spawn did not report the row's ineligible state"
|
|
937
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
938
|
+
|| fail "spawn resurrected a row closed after preflight"
|
|
939
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
940
|
+
"spawn retained a record after its row was closed"
|
|
941
|
+
pass "dispatch does not resurrect a row closed after preflight"
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
test_dispatch_fails_when_its_row_vanishes_after_preflight() {
|
|
945
|
+
local case_dir id out rc=0
|
|
946
|
+
id=atomic-dispatch-removed-race-b6
|
|
947
|
+
case_dir=$(make_home dispatch-removed-race "$id")
|
|
948
|
+
add_item "$case_dir" "$id"
|
|
949
|
+
change_row_on_second_show "$case_dir" rm
|
|
950
|
+
|
|
951
|
+
out=$(run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
952
|
+
[ "$rc" -ne 0 ] || fail "spawn succeeded after its backlog row vanished"
|
|
953
|
+
assert_contains "$out" "vanished before dispatch commit" \
|
|
954
|
+
"spawn did not report that its backlog row vanished"
|
|
955
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
956
|
+
"spawn retained a record after its backlog row vanished"
|
|
957
|
+
[ -z "$(row_state "$case_dir" "$id")" ] || fail "spawn recreated a removed backlog row"
|
|
958
|
+
pass "dispatch fails when its backlog row vanishes after preflight"
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
# --- completion -------------------------------------------------------------
|
|
962
|
+
|
|
963
|
+
test_completion_closes_a_local_only_ship_before_reporting_success() {
|
|
964
|
+
local case_dir id out
|
|
965
|
+
id=atomic-close-b5
|
|
966
|
+
case_dir=$(make_home close-local-only)
|
|
967
|
+
add_item "$case_dir" "$id"
|
|
968
|
+
start_item "$case_dir" "$id"
|
|
969
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-close-local"
|
|
970
|
+
|
|
971
|
+
out=$(run_teardown "$case_dir" "$id") || fail "teardown failed: $out"
|
|
972
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
973
|
+
|| fail "teardown reported success with the item still $(row_state "$case_dir" "$id")"
|
|
974
|
+
assert_grep 'local main' "$(backlog_of "$case_dir")" \
|
|
975
|
+
"a local-only landing was closed without its local-main note"
|
|
976
|
+
pass "completion closes a local-only ship, with its landing note, before reporting success"
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
test_completion_closes_a_scout_with_its_report() {
|
|
980
|
+
local case_dir id out
|
|
981
|
+
id=atomic-close-b6
|
|
982
|
+
case_dir=$(make_home close-scout)
|
|
983
|
+
add_item "$case_dir" "$id" scout
|
|
984
|
+
start_item "$case_dir" "$id"
|
|
985
|
+
write_task_meta "$case_dir" "$id" scout '' "spawn_gen=spawn-close-scout"
|
|
986
|
+
# A scout's deliverable is its report, and teardown also enforces the shared
|
|
987
|
+
# captain-call completion gate; satisfy both the way a real scout does.
|
|
988
|
+
mkdir -p "$(home_of "$case_dir")/data/$id"
|
|
989
|
+
printf 'findings\n' > "$(home_of "$case_dir")/data/$id/report.md"
|
|
990
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
991
|
+
PATH="$case_dir/fakebin:$PATH" \
|
|
992
|
+
"$ROOT/bin/fm-captain-hold.sh" complete "$id" --none >/dev/null \
|
|
993
|
+
|| fail "could not record the scout's completed captain-call inventory"
|
|
994
|
+
|
|
995
|
+
out=$(run_teardown "$case_dir" "$id") || fail "teardown failed: $out"
|
|
996
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
997
|
+
|| fail "teardown reported success with the scout item still $(row_state "$case_dir" "$id")"
|
|
998
|
+
assert_grep "data/$id/report.md" "$(backlog_of "$case_dir")" \
|
|
999
|
+
"a closed scout item did not record its report"
|
|
1000
|
+
pass "completion closes a scout item against its report"
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
test_completion_refuses_a_legacy_record_without_an_incarnation() {
|
|
1004
|
+
local case_dir id meta out rc=0
|
|
1005
|
+
id=atomic-close-legacy-no-incarnation-b7
|
|
1006
|
+
case_dir=$(make_home close-legacy-no-incarnation)
|
|
1007
|
+
add_item "$case_dir" "$id"
|
|
1008
|
+
start_item "$case_dir" "$id"
|
|
1009
|
+
write_task_meta "$case_dir" "$id" ship local-only
|
|
1010
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
1011
|
+
|
|
1012
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1013
|
+
[ "$rc" -ne 0 ] || fail "teardown accepted a record with no durable incarnation"
|
|
1014
|
+
assert_contains "$out" "record has no spawn_gen" \
|
|
1015
|
+
"teardown did not explain why the legacy record cannot close automatically"
|
|
1016
|
+
assert_present "$meta" "legacy-record refusal removed the task record"
|
|
1017
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1018
|
+
"legacy-record refusal wrote an unrecoverable close marker"
|
|
1019
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1020
|
+
|| fail "legacy-record refusal changed the backlog row"
|
|
1021
|
+
pass "completion leaves legacy records open when no incarnation can be recorded"
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
test_completion_refuses_ambiguous_incarnation_metadata() {
|
|
1025
|
+
local case_dir id meta marker out rc=0
|
|
1026
|
+
id=atomic-close-ambiguous-incarnation-b7
|
|
1027
|
+
case_dir=$(make_home close-ambiguous-incarnation)
|
|
1028
|
+
add_item "$case_dir" "$id"
|
|
1029
|
+
start_item "$case_dir" "$id"
|
|
1030
|
+
write_task_meta "$case_dir" "$id" ship local-only \
|
|
1031
|
+
"spawn_gen=spawn-old" "spawn_gen=spawn-current"
|
|
1032
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
1033
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1034
|
+
|
|
1035
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1036
|
+
[ "$rc" -ne 0 ] || fail "teardown accepted ambiguous incarnation metadata"
|
|
1037
|
+
assert_contains "$out" "has 2 spawn generation fields" \
|
|
1038
|
+
"teardown did not report the ambiguous incarnation"
|
|
1039
|
+
assert_present "$meta" "ambiguous-incarnation refusal removed the task record"
|
|
1040
|
+
assert_absent "$marker" "ambiguous-incarnation refusal published a close"
|
|
1041
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1042
|
+
|| fail "ambiguous-incarnation refusal changed the backlog row"
|
|
1043
|
+
pass "completion refuses ambiguous task incarnations"
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
test_completion_records_a_relative_report_for_relocated_data() {
|
|
1047
|
+
local case_dir id relocated backlog out
|
|
1048
|
+
id=atomic-close-relocated-scout-b7
|
|
1049
|
+
case_dir=$(make_home close-relocated-scout)
|
|
1050
|
+
relocated="$case_dir/relocated/data"
|
|
1051
|
+
mkdir -p "$case_dir/relocated"
|
|
1052
|
+
mv "$(home_of "$case_dir")/data" "$relocated"
|
|
1053
|
+
backlog="$relocated/backlog.md"
|
|
1054
|
+
tasks-axi add "$id" "item for $id" --kind scout --file "$backlog" >/dev/null
|
|
1055
|
+
tasks-axi start "$id" --file "$backlog" >/dev/null
|
|
1056
|
+
write_task_meta "$case_dir" "$id" scout '' "spawn_gen=spawn-relocated-scout"
|
|
1057
|
+
mkdir -p "$relocated/$id"
|
|
1058
|
+
printf 'findings\n' > "$relocated/$id/report.md"
|
|
1059
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
1060
|
+
FM_DATA_OVERRIDE="$relocated////" PATH="$case_dir/fakebin:$PATH" \
|
|
1061
|
+
"$ROOT/bin/fm-captain-hold.sh" complete "$id" --none >/dev/null \
|
|
1062
|
+
|| fail "could not record the relocated scout's captain-call inventory"
|
|
1063
|
+
|
|
1064
|
+
out=$(FM_DATA_OVERRIDE="$relocated////" run_teardown "$case_dir" "$id") \
|
|
1065
|
+
|| fail "relocated scout teardown failed: $out"
|
|
1066
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = "done" ] \
|
|
1067
|
+
|| fail "relocated scout backlog row was not closed"
|
|
1068
|
+
assert_grep "data/$id/report.md" "$backlog" \
|
|
1069
|
+
"relocated scout close did not record a relative report path"
|
|
1070
|
+
pass "completion records relocated scout reports relative to the backlog root"
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
test_space_containing_scout_report_marker_replays() {
|
|
1074
|
+
local case_dir id data backlog marker out rc=0
|
|
1075
|
+
id=atomic-space-report-replay-b7
|
|
1076
|
+
case_dir=$(make_home space-report-replay)
|
|
1077
|
+
data="$case_dir/crew space/data"
|
|
1078
|
+
mkdir -p "$case_dir/crew space"
|
|
1079
|
+
mv "$(home_of "$case_dir")/data" "$data"
|
|
1080
|
+
backlog="$data/backlog.md"
|
|
1081
|
+
tasks-axi add "$id" "item for $id" --kind scout --file "$backlog" >/dev/null
|
|
1082
|
+
tasks-axi start "$id" --file "$backlog" >/dev/null
|
|
1083
|
+
write_task_meta "$case_dir" "$id" scout '' "spawn_gen=spawn-space-report"
|
|
1084
|
+
mkdir -p "$data/$id"
|
|
1085
|
+
printf 'findings\n' > "$data/$id/report.md"
|
|
1086
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$(home_of "$case_dir")" \
|
|
1087
|
+
FM_DATA_OVERRIDE="$data" PATH="$case_dir/fakebin:$PATH" \
|
|
1088
|
+
"$ROOT/bin/fm-captain-hold.sh" complete "$id" --none >/dev/null \
|
|
1089
|
+
|| fail "could not record the space-path scout's captain-call inventory"
|
|
1090
|
+
break_verb "$case_dir" "done"
|
|
1091
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1092
|
+
|
|
1093
|
+
out=$(FM_DATA_OVERRIDE="$data" run_teardown "$case_dir" "$id") || rc=$?
|
|
1094
|
+
[ "$rc" -ne 0 ] || fail "space-path scout teardown unexpectedly completed"
|
|
1095
|
+
assert_present "$marker" "space-path scout teardown recorded no pending close"
|
|
1096
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
1097
|
+
"space-path scout teardown retained meta after recording its close"
|
|
1098
|
+
rm -f "$case_dir/fakebin/tasks-axi"
|
|
1099
|
+
|
|
1100
|
+
out=$(FM_DATA_OVERRIDE="$data" run_bootstrap "$case_dir")
|
|
1101
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = "done" ] \
|
|
1102
|
+
|| fail "space-containing report marker did not replay: $out"
|
|
1103
|
+
assert_grep "data/$id/report.md" "$backlog" \
|
|
1104
|
+
"report path from a space-containing data directory was lost during replay"
|
|
1105
|
+
assert_absent "$marker" "space-containing report marker remained after replay"
|
|
1106
|
+
pass "space-containing scout report paths round-trip through recovery"
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
test_trailing_newline_data_path_fails_closed() {
|
|
1110
|
+
local case_dir home id data backlog_alias out rc=0
|
|
1111
|
+
id=atomic-newline-data-refusal-c8
|
|
1112
|
+
case_dir=$(make_home newline-data-refusal "$id")
|
|
1113
|
+
home=$(home_of "$case_dir")
|
|
1114
|
+
data="$home/data"$'\n'
|
|
1115
|
+
mv "$home/data" "$data"
|
|
1116
|
+
mkdir -p "$home/data/$id"
|
|
1117
|
+
cp "$data/$id/brief.md" "$home/data/$id/brief.md"
|
|
1118
|
+
ln -s "$data" "$case_dir/data-alias"
|
|
1119
|
+
backlog_alias="$case_dir/data-alias/backlog.md"
|
|
1120
|
+
tasks-axi add "$id" "item for $id" --kind ship --file "$backlog_alias" >/dev/null
|
|
1121
|
+
|
|
1122
|
+
out=$(FM_DATA_OVERRIDE="$data" run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
1123
|
+
[ "$rc" -ne 0 ] || fail "trailing-newline data path bypassed dispatch transition"
|
|
1124
|
+
assert_absent "$home/state/$id.meta" \
|
|
1125
|
+
"trailing-newline dispatch published a task record"
|
|
1126
|
+
[ "$(tasks-axi show "$id" --file "$backlog_alias" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = queued ] \
|
|
1127
|
+
|| fail "trailing-newline dispatch changed the real backlog row: $out"
|
|
1128
|
+
|
|
1129
|
+
tasks-axi start "$id" --file "$backlog_alias" >/dev/null
|
|
1130
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-newline-data"
|
|
1131
|
+
rc=0
|
|
1132
|
+
out=$(FM_DATA_OVERRIDE="$data" run_teardown "$case_dir" "$id") || rc=$?
|
|
1133
|
+
[ "$rc" -ne 0 ] || fail "trailing-newline data path bypassed completion transition"
|
|
1134
|
+
assert_present "$home/state/$id.meta" \
|
|
1135
|
+
"trailing-newline teardown removed the task record"
|
|
1136
|
+
assert_absent "$home/state/$id.backlog-close" \
|
|
1137
|
+
"trailing-newline teardown published a close marker"
|
|
1138
|
+
[ "$(tasks-axi show "$id" --file "$backlog_alias" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = in_flight ] \
|
|
1139
|
+
|| fail "trailing-newline teardown changed the real backlog row: $out"
|
|
1140
|
+
pass "control-byte data paths fail closed before paired transitions"
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
test_control_character_data_path_is_refused_before_cleanup() {
|
|
1144
|
+
local case_dir id data backlog marker out rc=0
|
|
1145
|
+
id=atomic-control-data-refusal-b7
|
|
1146
|
+
case_dir=$(make_home control-data-refusal "$id")
|
|
1147
|
+
data="$case_dir/crew"$'\t'"data"
|
|
1148
|
+
mv "$(home_of "$case_dir")/data" "$data"
|
|
1149
|
+
backlog="$data/backlog.md"
|
|
1150
|
+
tasks-axi add "$id" "item for $id" --kind ship --file "$backlog" >/dev/null
|
|
1151
|
+
|
|
1152
|
+
out=$(FM_DATA_OVERRIDE="$data" run_ship_spawn "$case_dir" "$id") || rc=$?
|
|
1153
|
+
[ "$rc" -ne 0 ] || fail "control-character data path passed dispatch preflight"
|
|
1154
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
1155
|
+
"control-character dispatch published a task record"
|
|
1156
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = queued ] \
|
|
1157
|
+
|| fail "control-character dispatch changed the backlog row: $out"
|
|
1158
|
+
|
|
1159
|
+
tasks-axi start "$id" --file "$backlog" >/dev/null
|
|
1160
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-control-data"
|
|
1161
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1162
|
+
rc=0
|
|
1163
|
+
out=$(FM_DATA_OVERRIDE="$data" run_teardown "$case_dir" "$id") || rc=$?
|
|
1164
|
+
[ "$rc" -ne 0 ] || fail "control-character data path passed close preflight"
|
|
1165
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1166
|
+
"control-character close preflight removed the task record"
|
|
1167
|
+
assert_absent "$marker" "control-character close preflight published a marker"
|
|
1168
|
+
[ "$(tasks-axi show "$id" --file "$backlog" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = in_flight ] \
|
|
1169
|
+
|| fail "control-character close preflight changed the backlog row: $out"
|
|
1170
|
+
pass "unreplayable data paths are refused before destructive cleanup"
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
test_completion_preserves_records_when_meta_removal_fails() {
|
|
1174
|
+
local case_dir id meta marker out rc=0
|
|
1175
|
+
id=atomic-close-meta-remove-failure-b7
|
|
1176
|
+
case_dir=$(make_home close-meta-remove-failure)
|
|
1177
|
+
add_item "$case_dir" "$id"
|
|
1178
|
+
start_item "$case_dir" "$id"
|
|
1179
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-one"
|
|
1180
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
1181
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1182
|
+
break_meta_removal "$case_dir" "$meta"
|
|
1183
|
+
|
|
1184
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1185
|
+
[ "$rc" -ne 0 ] || fail "teardown succeeded though task-record removal failed"
|
|
1186
|
+
assert_contains "$out" "task record could not be removed" \
|
|
1187
|
+
"teardown did not report task-record removal failure"
|
|
1188
|
+
assert_present "$meta" "teardown lost meta after its removal failed"
|
|
1189
|
+
assert_present "$marker" "teardown discarded recovery after meta removal failed"
|
|
1190
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1191
|
+
|| fail "teardown closed the row before verifying meta removal"
|
|
1192
|
+
pass "completion preserves recovery state when task-record removal fails"
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
test_completion_fails_loudly_and_records_the_close_it_still_owes() {
|
|
1196
|
+
local case_dir id out rc=0
|
|
1197
|
+
id=atomic-close-b7
|
|
1198
|
+
case_dir=$(make_home close-fails)
|
|
1199
|
+
add_item "$case_dir" "$id"
|
|
1200
|
+
start_item "$case_dir" "$id"
|
|
1201
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-close-fails"
|
|
1202
|
+
break_verb "$case_dir" "done"
|
|
1203
|
+
|
|
1204
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1205
|
+
[ "$rc" -ne 0 ] || fail "teardown reported success while its item was still In flight"
|
|
1206
|
+
assert_contains "$out" "could not be closed" \
|
|
1207
|
+
"teardown failed without explaining the unclosed backlog item"
|
|
1208
|
+
assert_present "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1209
|
+
"teardown lost the close it still owes"
|
|
1210
|
+
pass "completion refuses to report success while its item is still open, and records what it owes"
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
test_interrupted_destructive_cleanup_leaves_a_recoverable_close() {
|
|
1214
|
+
local case_dir home id marker out rc=0
|
|
1215
|
+
id=atomic-close-destructive-interrupt-b8
|
|
1216
|
+
case_dir=$(make_home close-destructive-interrupt "$id")
|
|
1217
|
+
home=$(home_of "$case_dir")
|
|
1218
|
+
add_item "$case_dir" "$id"
|
|
1219
|
+
out=$(run_ship_spawn "$case_dir" "$id") || fail "spawn failed: $out"
|
|
1220
|
+
marker="$home/state/$id.backlog-close"
|
|
1221
|
+
interrupt_teardown_during_treehouse_return "$case_dir"
|
|
1222
|
+
|
|
1223
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1224
|
+
[ "$rc" -ne 0 ] || fail "interrupted destructive cleanup reported success"
|
|
1225
|
+
assert_present "$marker" \
|
|
1226
|
+
"destructive cleanup began before recording its authoritative close"
|
|
1227
|
+
assert_present "$home/state/$id.meta" \
|
|
1228
|
+
"interrupted destructive cleanup lost the task incarnation"
|
|
1229
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1230
|
+
|| fail "interrupted cleanup changed the backlog before recovery"
|
|
1231
|
+
|
|
1232
|
+
out=$(run_bootstrap "$case_dir")
|
|
1233
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1234
|
+
|| fail "restart left interrupted cleanup In flight: $out"
|
|
1235
|
+
assert_absent "$marker" "restart retained the recovered close marker"
|
|
1236
|
+
assert_absent "$home/state/$id.meta" "restart retained the interrupted task record"
|
|
1237
|
+
assert_contains "$out" "endpoint or local copy may remain" \
|
|
1238
|
+
"restart silently hid potentially incomplete physical cleanup"
|
|
1239
|
+
pass "restart recovers closes recorded before destructive cleanup"
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
test_completion_refuses_a_close_target_symlinked_to_a_directory() {
|
|
1243
|
+
local case_dir home id marker external out rc=0
|
|
1244
|
+
id=atomic-close-target-directory-symlink-b8
|
|
1245
|
+
case_dir=$(make_home close-target-directory-symlink)
|
|
1246
|
+
home=$(home_of "$case_dir")
|
|
1247
|
+
add_item "$case_dir" "$id"
|
|
1248
|
+
start_item "$case_dir" "$id"
|
|
1249
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-target-symlink"
|
|
1250
|
+
marker="$home/state/$id.backlog-close"
|
|
1251
|
+
external="$case_dir/external-directory"
|
|
1252
|
+
mkdir -p "$external"
|
|
1253
|
+
ln -s "$external" "$marker"
|
|
1254
|
+
|
|
1255
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1256
|
+
[ "$rc" -ne 0 ] || fail "teardown published through a directory symlink"
|
|
1257
|
+
assert_contains "$out" "pending-close record target resolves outside its authorized directory" \
|
|
1258
|
+
"teardown did not report the unsafe publication target"
|
|
1259
|
+
[ -L "$marker" ] || fail "teardown replaced the unsafe close target"
|
|
1260
|
+
[ -z "$(find "$external" -mindepth 1 -maxdepth 1 -print -quit)" ] \
|
|
1261
|
+
|| fail "teardown wrote a staged close outside the home"
|
|
1262
|
+
assert_present "$home/state/$id.meta" \
|
|
1263
|
+
"unsafe close publication removed the task record"
|
|
1264
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1265
|
+
|| fail "unsafe close publication changed the backlog row"
|
|
1266
|
+
pass "completion refuses directory-symlink close targets"
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
test_completion_fails_when_its_close_marker_cannot_be_removed() {
|
|
1270
|
+
local case_dir id marker out rc=0
|
|
1271
|
+
id=atomic-close-marker-remove-failure-b8
|
|
1272
|
+
case_dir=$(make_home close-marker-remove-failure)
|
|
1273
|
+
add_item "$case_dir" "$id"
|
|
1274
|
+
start_item "$case_dir" "$id"
|
|
1275
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-marker-fails"
|
|
1276
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1277
|
+
break_meta_removal "$case_dir" "$marker"
|
|
1278
|
+
|
|
1279
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
1280
|
+
[ "$rc" -ne 0 ] || fail "teardown reported success while its close marker remained"
|
|
1281
|
+
assert_contains "$out" "pending-close record could not be removed" \
|
|
1282
|
+
"teardown did not report its incomplete marker cleanup"
|
|
1283
|
+
assert_present "$marker" "teardown hid a close-marker removal failure"
|
|
1284
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1285
|
+
|| fail "marker cleanup failure lost the completed backlog transition"
|
|
1286
|
+
pass "completion reports failure until its durable close marker is removed"
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
# --- same-home recovery -----------------------------------------------------
|
|
1290
|
+
|
|
1291
|
+
test_recovery_retries_when_a_close_marker_cannot_be_removed() {
|
|
1292
|
+
local case_dir id marker out
|
|
1293
|
+
id=atomic-heal-marker-remove-failure-b8
|
|
1294
|
+
case_dir=$(make_home heal-marker-remove-failure)
|
|
1295
|
+
add_item "$case_dir" "$id"
|
|
1296
|
+
start_item "$case_dir" "$id"
|
|
1297
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1298
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-marker-retry\narg=--note\narg=local%%20main\n' \
|
|
1299
|
+
"$id" "$(home_of "$case_dir")/data" > "$marker"
|
|
1300
|
+
break_meta_removal "$case_dir" "$marker"
|
|
1301
|
+
|
|
1302
|
+
out=$(run_bootstrap "$case_dir")
|
|
1303
|
+
assert_contains "$out" "pending-close record could not be removed" \
|
|
1304
|
+
"session start did not report close-marker removal failure"
|
|
1305
|
+
assert_present "$marker" "recovery hid a close-marker removal failure"
|
|
1306
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1307
|
+
|| fail "recovery did not land the close before marker cleanup"
|
|
1308
|
+
|
|
1309
|
+
rm -f "$case_dir/fakebin/rm"
|
|
1310
|
+
out=$(run_bootstrap "$case_dir")
|
|
1311
|
+
assert_absent "$marker" "recovery did not retry close-marker cleanup: $out"
|
|
1312
|
+
pass "session start retries a close whose marker could not be removed"
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
test_recovery_reports_an_owned_row_read_failure() {
|
|
1316
|
+
local case_dir id out
|
|
1317
|
+
id=atomic-heal-read-failure-b8
|
|
1318
|
+
case_dir=$(make_home heal-owned-read-failure)
|
|
1319
|
+
add_item "$case_dir" "$id"
|
|
1320
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes
|
|
1321
|
+
break_verb "$case_dir" show
|
|
1322
|
+
|
|
1323
|
+
out=$(run_bootstrap "$case_dir")
|
|
1324
|
+
assert_contains "$out" "worker record exists but its backlog item could not be read" \
|
|
1325
|
+
"session start silently ignored an owned-row read failure"
|
|
1326
|
+
assert_contains "$out" "backlog is unwritable" \
|
|
1327
|
+
"session start discarded the backlog reader's diagnostic"
|
|
1328
|
+
rm -f "$case_dir/fakebin/tasks-axi"
|
|
1329
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1330
|
+
|| fail "owned-row read failure changed the backlog state"
|
|
1331
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1332
|
+
"owned-row read failure removed the worker record"
|
|
1333
|
+
pass "session start reports owned backlog rows it cannot read"
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
test_orca_cleanup_recovery_never_transitions_the_backlog() {
|
|
1337
|
+
local case_dir id meta out
|
|
1338
|
+
id=atomic-orca-cleanup-recovery-b8
|
|
1339
|
+
case_dir=$(make_home orca-cleanup-recovery)
|
|
1340
|
+
add_item "$case_dir" "$id"
|
|
1341
|
+
write_task_meta "$case_dir" "$id" ship local-only "cleanup_recovery=orca"
|
|
1342
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
1343
|
+
|
|
1344
|
+
out=$(run_bootstrap "$case_dir")
|
|
1345
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1346
|
+
|| fail "session start treated cleanup recovery as a launched worker: $out"
|
|
1347
|
+
assert_present "$meta" "session start removed the cleanup recovery record"
|
|
1348
|
+
|
|
1349
|
+
out=$(run_teardown "$case_dir" "$id") \
|
|
1350
|
+
|| fail "cleanup recovery teardown failed: $out"
|
|
1351
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1352
|
+
|| fail "cleanup recovery teardown completed work that never launched"
|
|
1353
|
+
assert_absent "$meta" "cleanup recovery teardown retained its task record"
|
|
1354
|
+
pass "Orca cleanup recovery is excluded from backlog lifecycle transitions"
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
test_recovery_marks_an_owned_record_in_flight() {
|
|
1358
|
+
local case_dir id out
|
|
1359
|
+
id=atomic-heal-b8
|
|
1360
|
+
case_dir=$(make_home heal-queued)
|
|
1361
|
+
add_item "$case_dir" "$id"
|
|
1362
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes
|
|
1363
|
+
|
|
1364
|
+
out=$(run_bootstrap "$case_dir")
|
|
1365
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1366
|
+
|| fail "session start left an owned record's item at $(row_state "$case_dir" "$id"): $out"
|
|
1367
|
+
pass "session start marks an item In flight when this home already owns a worker for it"
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
test_recovery_rejects_an_internal_worker_record_symlink() {
|
|
1371
|
+
local case_dir home id target_id out rc=0
|
|
1372
|
+
id=atomic-heal-internal-symlink-b8
|
|
1373
|
+
target_id=atomic-heal-internal-target-b8
|
|
1374
|
+
case_dir=$(make_home heal-internal-symlink)
|
|
1375
|
+
home=$(home_of "$case_dir")
|
|
1376
|
+
add_item "$case_dir" "$id"
|
|
1377
|
+
write_task_meta "$case_dir" "$target_id" ship no-mistakes "spawn_gen=internal-target"
|
|
1378
|
+
ln -s "$target_id.meta" "$home/state/$id.meta"
|
|
1379
|
+
|
|
1380
|
+
out=$(run_bootstrap "$case_dir") || rc=$?
|
|
1381
|
+
[ "$rc" -ne 0 ] || fail "session start accepted an internal worker-record symlink"
|
|
1382
|
+
assert_contains "$out" "task record resolves through a different final path" \
|
|
1383
|
+
"session start did not report the aliased worker record"
|
|
1384
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1385
|
+
|| fail "session start paired the aliased worker record with its backlog row"
|
|
1386
|
+
[ -L "$home/state/$id.meta" ] \
|
|
1387
|
+
|| fail "session start replaced or removed the aliased worker record"
|
|
1388
|
+
assert_present "$home/state/$target_id.meta" \
|
|
1389
|
+
"session start removed the internal symlink target"
|
|
1390
|
+
pass "session start rejects internal worker-record symlinks"
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
test_recovery_ignores_a_symlinked_worker_record() {
|
|
1394
|
+
local case_dir home id target out rc=0
|
|
1395
|
+
id=atomic-heal-symlink-meta-b8
|
|
1396
|
+
case_dir=$(make_home heal-symlink-meta)
|
|
1397
|
+
home=$(home_of "$case_dir")
|
|
1398
|
+
add_item "$case_dir" "$id"
|
|
1399
|
+
target="$case_dir/symlink-meta-target"
|
|
1400
|
+
printf 'kind=ship\nspawn_gen=unpublished\n' > "$target"
|
|
1401
|
+
ln -s "$target" "$home/state/$id.meta"
|
|
1402
|
+
|
|
1403
|
+
out=$(run_bootstrap "$case_dir") || rc=$?
|
|
1404
|
+
[ "$rc" -ne 0 ] || fail "session start accepted a symlinked worker record"
|
|
1405
|
+
assert_contains "$out" "bootstrap refused unsafe worker record" \
|
|
1406
|
+
"session start did not report the unsafe worker record"
|
|
1407
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1408
|
+
|| fail "session start treated a symlink as an owned worker record: $out"
|
|
1409
|
+
[ -L "$home/state/$id.meta" ] \
|
|
1410
|
+
|| fail "session start replaced or removed the inert symlinked record"
|
|
1411
|
+
pass "session start rejects symlinked worker records"
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
test_recovery_replays_a_close_an_interrupted_cleanup_left_open() {
|
|
1415
|
+
local case_dir id out
|
|
1416
|
+
id=atomic-heal-b9
|
|
1417
|
+
case_dir=$(make_home heal-pending-close)
|
|
1418
|
+
add_item "$case_dir" "$id"
|
|
1419
|
+
start_item "$case_dir" "$id"
|
|
1420
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-heal-pr\narg=--pr\narg=https://github.com/example/repo/pull/11\n' \
|
|
1421
|
+
"$id" "$(home_of "$case_dir")/data" \
|
|
1422
|
+
> "$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1423
|
+
|
|
1424
|
+
out=$(run_bootstrap "$case_dir")
|
|
1425
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1426
|
+
|| fail "session start left an interrupted cleanup's item at $(row_state "$case_dir" "$id"): $out"
|
|
1427
|
+
assert_grep 'https://github.com/example/repo/pull/11' "$(backlog_of "$case_dir")" \
|
|
1428
|
+
"the replayed close dropped the completion link the cleanup had recorded"
|
|
1429
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1430
|
+
"a replayed close left its record behind"
|
|
1431
|
+
assert_not_contains "$out" "endpoint or local copy may remain" \
|
|
1432
|
+
"recovery claimed incomplete cleanup without task metadata"
|
|
1433
|
+
pass "session start finishes a close an interrupted cleanup recorded but never landed"
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
test_recovery_backfills_a_recorded_link_on_an_already_done_item() {
|
|
1437
|
+
local case_dir id marker out
|
|
1438
|
+
id=atomic-heal-done-backfill-b9
|
|
1439
|
+
case_dir=$(make_home heal-done-backfill)
|
|
1440
|
+
add_item "$case_dir" "$id"
|
|
1441
|
+
start_item "$case_dir" "$id"
|
|
1442
|
+
tasks-axi "done" "$id" --file "$(backlog_of "$case_dir")" >/dev/null
|
|
1443
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1444
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-heal-done\narg=--pr\narg=https://github.com/example/repo/pull/13\n' \
|
|
1445
|
+
"$id" "$(home_of "$case_dir")/data" > "$marker"
|
|
1446
|
+
|
|
1447
|
+
out=$(run_bootstrap "$case_dir")
|
|
1448
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1449
|
+
|| fail "replaying a completion link changed the closed row: $out"
|
|
1450
|
+
assert_grep 'https://github.com/example/repo/pull/13' "$(backlog_of "$case_dir")" \
|
|
1451
|
+
"recovery discarded the recorded link because the item was already Done"
|
|
1452
|
+
assert_absent "$marker" "recovery retained an applied completion-link marker"
|
|
1453
|
+
pass "recovery backfills recorded links onto already Done items"
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
test_recovery_preserves_a_close_when_the_backlog_cannot_be_read() {
|
|
1457
|
+
local case_dir id out
|
|
1458
|
+
id=atomic-heal-read-error-b10
|
|
1459
|
+
case_dir=$(make_home heal-read-error)
|
|
1460
|
+
add_item "$case_dir" "$id"
|
|
1461
|
+
start_item "$case_dir" "$id"
|
|
1462
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-heal-read\narg=--note\narg=local%%20main\n' \
|
|
1463
|
+
"$id" "$(home_of "$case_dir")/data" \
|
|
1464
|
+
> "$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1465
|
+
break_verb "$case_dir" show
|
|
1466
|
+
|
|
1467
|
+
out=$(run_bootstrap "$case_dir")
|
|
1468
|
+
assert_present "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1469
|
+
"a transient backlog read failure discarded the pending close"
|
|
1470
|
+
rm -f "$case_dir/fakebin/tasks-axi"
|
|
1471
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1472
|
+
|| fail "a failed recovery changed the backlog row: $out"
|
|
1473
|
+
|
|
1474
|
+
out=$(run_bootstrap "$case_dir")
|
|
1475
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1476
|
+
|| fail "the preserved close was not retried after the read recovered: $out"
|
|
1477
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1478
|
+
"a successfully retried close left its marker behind"
|
|
1479
|
+
pass "session start preserves a pending close across a transient backlog read failure"
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
test_recovery_retry_preserves_incomplete_cleanup_warning() {
|
|
1483
|
+
local case_dir home id marker out
|
|
1484
|
+
id=atomic-heal-retry-warning-b10
|
|
1485
|
+
case_dir=$(make_home heal-retry-warning)
|
|
1486
|
+
home=$(home_of "$case_dir")
|
|
1487
|
+
add_item "$case_dir" "$id"
|
|
1488
|
+
start_item "$case_dir" "$id"
|
|
1489
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-warning"
|
|
1490
|
+
marker="$home/state/$id.backlog-close"
|
|
1491
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-warning\narg=--note\narg=local%%20main\n' \
|
|
1492
|
+
"$id" "$home/data" > "$marker"
|
|
1493
|
+
break_verb "$case_dir" show
|
|
1494
|
+
|
|
1495
|
+
out=$(run_bootstrap "$case_dir")
|
|
1496
|
+
assert_absent "$home/state/$id.meta" \
|
|
1497
|
+
"failed replay did not cross the task-record removal boundary"
|
|
1498
|
+
assert_present "$marker" "failed replay discarded its pending close"
|
|
1499
|
+
rm -f "$case_dir/fakebin/tasks-axi"
|
|
1500
|
+
|
|
1501
|
+
out=$(run_bootstrap "$case_dir")
|
|
1502
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1503
|
+
|| fail "retried recovery left the item In flight: $out"
|
|
1504
|
+
assert_contains "$out" "endpoint or local copy may remain" \
|
|
1505
|
+
"retry lost the incomplete-cleanup evidence after removing metadata"
|
|
1506
|
+
assert_absent "$marker" "retried recovery retained its applied marker"
|
|
1507
|
+
pass "recovery preserves incomplete-cleanup evidence across a failed replay"
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
test_recovery_finishes_a_close_for_the_same_meta_incarnation() {
|
|
1511
|
+
local case_dir id out
|
|
1512
|
+
id=atomic-heal-same-incarnation-b11
|
|
1513
|
+
case_dir=$(make_home heal-same-incarnation)
|
|
1514
|
+
add_item "$case_dir" "$id"
|
|
1515
|
+
start_item "$case_dir" "$id"
|
|
1516
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-one"
|
|
1517
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-one\narg=--note\narg=local%%20main\n' \
|
|
1518
|
+
"$id" "$(home_of "$case_dir")/data" \
|
|
1519
|
+
> "$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1520
|
+
|
|
1521
|
+
out=$(run_bootstrap "$case_dir")
|
|
1522
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1523
|
+
|| fail "session start did not close the interrupted incarnation: $out"
|
|
1524
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
1525
|
+
"session start retained the interrupted incarnation's meta"
|
|
1526
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1527
|
+
"session start retained the completed incarnation's close marker"
|
|
1528
|
+
pass "session start finishes a close for the matching meta incarnation"
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
test_recovery_preserves_a_close_for_ambiguous_incarnation_metadata() {
|
|
1532
|
+
local case_dir home id marker out
|
|
1533
|
+
id=atomic-heal-ambiguous-incarnation-b12
|
|
1534
|
+
case_dir=$(make_home heal-ambiguous-incarnation)
|
|
1535
|
+
home=$(home_of "$case_dir")
|
|
1536
|
+
add_item "$case_dir" "$id"
|
|
1537
|
+
start_item "$case_dir" "$id"
|
|
1538
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes \
|
|
1539
|
+
"spawn_gen=spawn-old" "spawn_gen=spawn-current"
|
|
1540
|
+
marker="$home/state/$id.backlog-close"
|
|
1541
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-current\narg=--note\narg=local%%20main\n' \
|
|
1542
|
+
"$id" "$home/data" > "$marker"
|
|
1543
|
+
|
|
1544
|
+
out=$(run_bootstrap "$case_dir")
|
|
1545
|
+
assert_contains "$out" "has 2 spawn generation fields" \
|
|
1546
|
+
"recovery did not report ambiguous incarnation metadata"
|
|
1547
|
+
assert_present "$marker" "ambiguous metadata caused recovery to discard the close"
|
|
1548
|
+
assert_present "$home/state/$id.meta" \
|
|
1549
|
+
"ambiguous metadata caused recovery to remove the task record"
|
|
1550
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1551
|
+
|| fail "ambiguous metadata allowed recovery to close the backlog row"
|
|
1552
|
+
pass "recovery preserves closes for ambiguous task incarnations"
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
test_recovery_preserves_both_records_when_meta_removal_fails() {
|
|
1556
|
+
local case_dir id meta out
|
|
1557
|
+
id=atomic-heal-remove-failure-b12
|
|
1558
|
+
case_dir=$(make_home heal-remove-failure)
|
|
1559
|
+
add_item "$case_dir" "$id"
|
|
1560
|
+
start_item "$case_dir" "$id"
|
|
1561
|
+
meta="$(home_of "$case_dir")/state/$id.meta"
|
|
1562
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-one"
|
|
1563
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-one\narg=--note\narg=local%%20main\n' \
|
|
1564
|
+
"$id" "$(home_of "$case_dir")/data" \
|
|
1565
|
+
> "$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1566
|
+
break_meta_removal "$case_dir" "$meta"
|
|
1567
|
+
|
|
1568
|
+
out=$(run_bootstrap "$case_dir")
|
|
1569
|
+
assert_contains "$out" "the interrupted task record could not be removed" \
|
|
1570
|
+
"session start did not surface the record-removal failure"
|
|
1571
|
+
assert_present "$meta" "failed recovery removed the task record"
|
|
1572
|
+
assert_present "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1573
|
+
"failed recovery discarded the pending close"
|
|
1574
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1575
|
+
|| fail "failed recovery closed the backlog before removing meta"
|
|
1576
|
+
|
|
1577
|
+
rm -f "$case_dir/fakebin/rm"
|
|
1578
|
+
out=$(run_bootstrap "$case_dir")
|
|
1579
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
1580
|
+
|| fail "recovery did not retry after meta removal recovered: $out"
|
|
1581
|
+
assert_absent "$meta" "successful retry retained the task record"
|
|
1582
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1583
|
+
"successful retry retained the pending close"
|
|
1584
|
+
pass "recovery preserves both records when meta removal fails"
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
test_recovery_preserves_a_close_beside_symlinked_metadata() {
|
|
1588
|
+
local case_dir home id marker target out
|
|
1589
|
+
id=atomic-heal-symlink-meta-close-b12
|
|
1590
|
+
case_dir=$(make_home heal-symlink-meta-close)
|
|
1591
|
+
home=$(home_of "$case_dir")
|
|
1592
|
+
add_item "$case_dir" "$id"
|
|
1593
|
+
start_item "$case_dir" "$id"
|
|
1594
|
+
target="$case_dir/foreign-meta-target"
|
|
1595
|
+
printf 'kind=ship\nspawn_gen=other-incarnation\n' > "$target"
|
|
1596
|
+
ln -s "$target" "$home/state/$id.meta"
|
|
1597
|
+
marker="$home/state/$id.backlog-close"
|
|
1598
|
+
printf 'id=%s\ndata=%s\nspawn_gen=closing-incarnation\narg=--note\narg=local%%20main\n' \
|
|
1599
|
+
"$id" "$home/data" > "$marker"
|
|
1600
|
+
|
|
1601
|
+
out=$(run_bootstrap "$case_dir")
|
|
1602
|
+
assert_contains "$out" "unsafe interrupted task record" \
|
|
1603
|
+
"recovery did not report unsafe metadata beside the close"
|
|
1604
|
+
assert_present "$marker" "unsafe metadata caused recovery to discard the close"
|
|
1605
|
+
[ -L "$home/state/$id.meta" ] \
|
|
1606
|
+
|| fail "recovery replaced or removed the unsafe metadata path"
|
|
1607
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1608
|
+
|| fail "unsafe metadata allowed recovery to close the backlog row"
|
|
1609
|
+
pass "recovery preserves closes beside symlinked metadata"
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
test_recovery_rejects_a_marker_for_another_task_identity() {
|
|
1613
|
+
local case_dir locked_id target_id marker out
|
|
1614
|
+
locked_id=atomic-marker-lock-owner-b12
|
|
1615
|
+
target_id=atomic-marker-target-b12
|
|
1616
|
+
case_dir=$(make_home marker-identity-mismatch)
|
|
1617
|
+
add_item "$case_dir" "$target_id"
|
|
1618
|
+
start_item "$case_dir" "$target_id"
|
|
1619
|
+
write_task_meta "$case_dir" "$target_id" ship no-mistakes "spawn_gen=spawn-marker-target"
|
|
1620
|
+
marker="$(home_of "$case_dir")/state/$locked_id.backlog-close"
|
|
1621
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-marker-target\narg=--note\narg=local%%20main\n' \
|
|
1622
|
+
"$target_id" "$(home_of "$case_dir")/data" > "$marker"
|
|
1623
|
+
|
|
1624
|
+
out=$(run_bootstrap "$case_dir")
|
|
1625
|
+
assert_present "$marker" "identity-mismatched close marker was consumed"
|
|
1626
|
+
assert_present "$(home_of "$case_dir")/state/$target_id.meta" \
|
|
1627
|
+
"identity-mismatched close marker removed another task record"
|
|
1628
|
+
[ "$(row_state "$case_dir" "$target_id")" = in_flight ] \
|
|
1629
|
+
|| fail "identity-mismatched close marker changed another task's row: $out"
|
|
1630
|
+
pass "recovery binds close-marker identity to its locked filename"
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
test_recovery_rejects_a_foreign_data_directory() {
|
|
1634
|
+
local case_dir foreign_case id marker out
|
|
1635
|
+
id=atomic-marker-foreign-data-b12
|
|
1636
|
+
case_dir=$(make_home marker-foreign-data-local)
|
|
1637
|
+
foreign_case=$(make_home marker-foreign-data-remote)
|
|
1638
|
+
add_item "$case_dir" "$id"
|
|
1639
|
+
start_item "$case_dir" "$id"
|
|
1640
|
+
add_item "$foreign_case" "$id"
|
|
1641
|
+
start_item "$foreign_case" "$id"
|
|
1642
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-foreign-data"
|
|
1643
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1644
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-foreign-data\narg=--note\narg=local%%20main\n' \
|
|
1645
|
+
"$id" "$(home_of "$foreign_case")/data" > "$marker"
|
|
1646
|
+
|
|
1647
|
+
out=$(run_bootstrap "$case_dir")
|
|
1648
|
+
assert_present "$marker" "foreign-data close marker was consumed"
|
|
1649
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1650
|
+
"foreign-data close marker removed the local task record"
|
|
1651
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1652
|
+
|| fail "foreign-data close marker changed the local backlog row: $out"
|
|
1653
|
+
[ "$(row_state "$foreign_case" "$id")" = in_flight ] \
|
|
1654
|
+
|| fail "foreign-data close marker reached into another home's backlog: $out"
|
|
1655
|
+
pass "recovery rejects close markers targeting another home's data"
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
test_recovery_rejects_an_unterminated_unknown_field() {
|
|
1659
|
+
local case_dir id marker out
|
|
1660
|
+
id=atomic-marker-unterminated-field-b12
|
|
1661
|
+
case_dir=$(make_home marker-unterminated-field)
|
|
1662
|
+
add_item "$case_dir" "$id"
|
|
1663
|
+
start_item "$case_dir" "$id"
|
|
1664
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-unterminated-field"
|
|
1665
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1666
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-unterminated-field\narg=--note\narg=local%%20main\nunknown=value' \
|
|
1667
|
+
"$id" "$(home_of "$case_dir")/data" > "$marker"
|
|
1668
|
+
|
|
1669
|
+
out=$(run_bootstrap "$case_dir")
|
|
1670
|
+
assert_present "$marker" "marker with an unterminated unknown field was consumed"
|
|
1671
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1672
|
+
"unterminated unknown marker field allowed task-record removal"
|
|
1673
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1674
|
+
|| fail "unterminated unknown marker field changed the backlog row: $out"
|
|
1675
|
+
pass "recovery validates an unterminated final marker field"
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
test_recovery_rejects_lexical_data_traversal() {
|
|
1679
|
+
local case_dir id marker data out
|
|
1680
|
+
id=atomic-marker-data-traversal-b12
|
|
1681
|
+
case_dir=$(make_home marker-data-traversal)
|
|
1682
|
+
add_item "$case_dir" "$id"
|
|
1683
|
+
start_item "$case_dir" "$id"
|
|
1684
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-data-traversal"
|
|
1685
|
+
data="$(home_of "$case_dir")/data"
|
|
1686
|
+
mkdir -p "$data/sub"
|
|
1687
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1688
|
+
printf 'id=%s\ndata=%s/sub/..\nspawn_gen=spawn-data-traversal\narg=--note\narg=local%%20main\n' \
|
|
1689
|
+
"$id" "$data" > "$marker"
|
|
1690
|
+
|
|
1691
|
+
out=$(run_bootstrap "$case_dir")
|
|
1692
|
+
assert_present "$marker" "marker with lexical data traversal was consumed"
|
|
1693
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1694
|
+
"lexical data traversal allowed task-record removal"
|
|
1695
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1696
|
+
|| fail "lexical data traversal changed the backlog row: $out"
|
|
1697
|
+
pass "recovery rejects lexical traversal before resolving marker data"
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
test_recovery_rejects_raw_control_bytes() {
|
|
1701
|
+
local case_dir id marker data out
|
|
1702
|
+
id=atomic-marker-nul-byte-b12
|
|
1703
|
+
case_dir=$(make_home marker-nul-byte)
|
|
1704
|
+
add_item "$case_dir" "$id"
|
|
1705
|
+
start_item "$case_dir" "$id"
|
|
1706
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-nul-byte"
|
|
1707
|
+
data="$(home_of "$case_dir")/data"
|
|
1708
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1709
|
+
printf 'id=%s\ndata=%s\0\nspawn_gen=spawn-nul-byte\narg=--note\narg=local%%20main\n' \
|
|
1710
|
+
"$id" "$data" > "$marker"
|
|
1711
|
+
|
|
1712
|
+
out=$(run_bootstrap "$case_dir")
|
|
1713
|
+
assert_present "$marker" "NUL-bearing close marker was consumed"
|
|
1714
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1715
|
+
"NUL-bearing close marker removed the task record"
|
|
1716
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1717
|
+
|| fail "NUL-bearing close marker changed the backlog row: $out"
|
|
1718
|
+
pass "recovery rejects marker control bytes before parsing"
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
test_recovery_rejects_malformed_pr_urls() {
|
|
1722
|
+
local case_dir first_id second_id third_id first_marker second_marker third_marker out
|
|
1723
|
+
first_id=atomic-marker-pr-port-b12
|
|
1724
|
+
second_id=atomic-marker-pr-percent-b12
|
|
1725
|
+
third_id=atomic-marker-pr-host-label-b12
|
|
1726
|
+
case_dir=$(make_home marker-malformed-pr)
|
|
1727
|
+
add_item "$case_dir" "$first_id"
|
|
1728
|
+
start_item "$case_dir" "$first_id"
|
|
1729
|
+
add_item "$case_dir" "$second_id"
|
|
1730
|
+
start_item "$case_dir" "$second_id"
|
|
1731
|
+
add_item "$case_dir" "$third_id"
|
|
1732
|
+
start_item "$case_dir" "$third_id"
|
|
1733
|
+
first_marker="$(home_of "$case_dir")/state/$first_id.backlog-close"
|
|
1734
|
+
second_marker="$(home_of "$case_dir")/state/$second_id.backlog-close"
|
|
1735
|
+
third_marker="$(home_of "$case_dir")/state/$third_id.backlog-close"
|
|
1736
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-pr-port\narg=--pr\narg=https://github.com:abc/pull/1\n' \
|
|
1737
|
+
"$first_id" "$(home_of "$case_dir")/data" > "$first_marker"
|
|
1738
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-pr-percent\narg=--pr\narg=https://github.com/pull/%%ZZ\n' \
|
|
1739
|
+
"$second_id" "$(home_of "$case_dir")/data" > "$second_marker"
|
|
1740
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-pr-host-label\narg=--pr\narg=https://foo.-bar.com/pull/1\n' \
|
|
1741
|
+
"$third_id" "$(home_of "$case_dir")/data" > "$third_marker"
|
|
1742
|
+
|
|
1743
|
+
out=$(run_bootstrap "$case_dir")
|
|
1744
|
+
assert_present "$first_marker" "PR marker with a nonnumeric port was consumed"
|
|
1745
|
+
assert_present "$second_marker" "PR marker with an invalid percent escape was consumed"
|
|
1746
|
+
assert_present "$third_marker" "PR marker with a malformed host label was consumed"
|
|
1747
|
+
[ "$(row_state "$case_dir" "$first_id")" = in_flight ] \
|
|
1748
|
+
|| fail "nonnumeric PR port changed the backlog row: $out"
|
|
1749
|
+
[ "$(row_state "$case_dir" "$second_id")" = in_flight ] \
|
|
1750
|
+
|| fail "invalid PR percent escape changed the backlog row: $out"
|
|
1751
|
+
[ "$(row_state "$case_dir" "$third_id")" = in_flight ] \
|
|
1752
|
+
|| fail "malformed PR host label changed the backlog row: $out"
|
|
1753
|
+
pass "recovery rejects malformed PR URL values"
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
test_failed_close_replay_is_not_started_as_live_work() {
|
|
1757
|
+
local case_dir id marker out
|
|
1758
|
+
id=atomic-pending-close-not-started-b12
|
|
1759
|
+
case_dir=$(make_home pending-close-not-started)
|
|
1760
|
+
add_item "$case_dir" "$id"
|
|
1761
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-pending-close"
|
|
1762
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1763
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-pending-close\narg=--pr\narg=https://\n' \
|
|
1764
|
+
"$id" "$(home_of "$case_dir")/data" > "$marker"
|
|
1765
|
+
|
|
1766
|
+
out=$(run_bootstrap "$case_dir")
|
|
1767
|
+
assert_present "$marker" "failed close replay discarded its pending marker"
|
|
1768
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1769
|
+
"failed close replay removed its task record"
|
|
1770
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1771
|
+
|| fail "retained pending close was started as live work: $out"
|
|
1772
|
+
pass "a retained pending close is never started by reconciliation"
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
test_recovery_rejects_invalid_close_arguments() {
|
|
1776
|
+
local case_dir id marker out
|
|
1777
|
+
id=atomic-marker-invalid-args-b12
|
|
1778
|
+
case_dir=$(make_home marker-invalid-args)
|
|
1779
|
+
add_item "$case_dir" "$id"
|
|
1780
|
+
start_item "$case_dir" "$id"
|
|
1781
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1782
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-invalid-args\narg=--unknown\narg=value\n' \
|
|
1783
|
+
"$id" "$(home_of "$case_dir")/data" > "$marker"
|
|
1784
|
+
|
|
1785
|
+
out=$(run_bootstrap "$case_dir")
|
|
1786
|
+
assert_present "$marker" "invalid-argument close marker was consumed"
|
|
1787
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1788
|
+
|| fail "invalid close arguments changed the backlog row: $out"
|
|
1789
|
+
pass "recovery rejects close-marker arguments outside its protocol"
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
test_recovery_rejects_a_symlinked_close_marker() {
|
|
1793
|
+
local case_dir id marker payload out rc=0
|
|
1794
|
+
id=atomic-marker-symlink-b12
|
|
1795
|
+
case_dir=$(make_home marker-symlink)
|
|
1796
|
+
add_item "$case_dir" "$id"
|
|
1797
|
+
start_item "$case_dir" "$id"
|
|
1798
|
+
payload="$(home_of "$case_dir")/state/marker-payload"
|
|
1799
|
+
marker="$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1800
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-symlink\narg=--note\narg=local%%20main\n' \
|
|
1801
|
+
"$id" "$(home_of "$case_dir")/data" > "$payload"
|
|
1802
|
+
ln -s "$payload" "$marker"
|
|
1803
|
+
rm -f "$payload"
|
|
1804
|
+
|
|
1805
|
+
out=$(run_bootstrap "$case_dir") || rc=$?
|
|
1806
|
+
[ "$rc" -ne 0 ] || fail "bootstrap accepted a symlinked close marker"
|
|
1807
|
+
[ -L "$marker" ] || fail "dangling symlink close marker was consumed: $out"
|
|
1808
|
+
assert_contains "$out" "bootstrap refused unsafe pending close" \
|
|
1809
|
+
"dangling symlink close marker was silently skipped"
|
|
1810
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1811
|
+
|| fail "symlinked close marker changed the backlog row: $out"
|
|
1812
|
+
pass "recovery reports and rejects dangling symlink close markers"
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
test_recovery_drops_a_close_for_a_newer_meta_incarnation() {
|
|
1816
|
+
local case_dir id out
|
|
1817
|
+
id=atomic-heal-new-incarnation-b12
|
|
1818
|
+
case_dir=$(make_home heal-new-incarnation)
|
|
1819
|
+
add_item "$case_dir" "$id"
|
|
1820
|
+
start_item "$case_dir" "$id"
|
|
1821
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-two"
|
|
1822
|
+
printf 'id=%s\ndata=%s\nspawn_gen=spawn-one\narg=--note\narg=local%%20main\n' \
|
|
1823
|
+
"$id" "$(home_of "$case_dir")/data" \
|
|
1824
|
+
> "$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1825
|
+
|
|
1826
|
+
out=$(run_bootstrap "$case_dir")
|
|
1827
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1828
|
+
|| fail "session start closed the newer task incarnation: $out"
|
|
1829
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1830
|
+
"session start removed the newer task incarnation's meta"
|
|
1831
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1832
|
+
"a stale recorded close was left to fire on a later restart"
|
|
1833
|
+
pass "session start drops a close recorded for an older meta incarnation"
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
test_recovery_rejects_a_legacy_close_without_an_incarnation() {
|
|
1837
|
+
local case_dir id out
|
|
1838
|
+
id=atomic-heal-legacy-close-b13
|
|
1839
|
+
case_dir=$(make_home heal-legacy-close)
|
|
1840
|
+
add_item "$case_dir" "$id"
|
|
1841
|
+
start_item "$case_dir" "$id"
|
|
1842
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-two"
|
|
1843
|
+
printf 'id=%s\ndata=%s\narg=--note\narg=local%%20main\n' \
|
|
1844
|
+
"$id" "$(home_of "$case_dir")/data" \
|
|
1845
|
+
> "$(home_of "$case_dir")/state/$id.backlog-close"
|
|
1846
|
+
|
|
1847
|
+
out=$(run_bootstrap "$case_dir")
|
|
1848
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1849
|
+
|| fail "session start guessed that a legacy close belonged to the current meta: $out"
|
|
1850
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1851
|
+
"session start removed meta for an unversioned legacy close"
|
|
1852
|
+
assert_present "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
1853
|
+
"session start consumed an unversioned close marker"
|
|
1854
|
+
pass "session start rejects an unversioned close marker"
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
test_bootstrap_rechecks_worker_record_boundary_after_locking() {
|
|
1858
|
+
local case_dir foreign_case home foreign_state id real_ln out rc=0
|
|
1859
|
+
id=atomic-bootstrap-state-swap-b13
|
|
1860
|
+
case_dir=$(make_home bootstrap-state-swap)
|
|
1861
|
+
foreign_case=$(make_home bootstrap-state-swap-foreign)
|
|
1862
|
+
home=$(home_of "$case_dir")
|
|
1863
|
+
foreign_state="$(home_of "$foreign_case")/state"
|
|
1864
|
+
add_item "$case_dir" "$id"
|
|
1865
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=local-worker"
|
|
1866
|
+
write_task_meta "$foreign_case" "$id" ship no-mistakes "spawn_gen=foreign-worker"
|
|
1867
|
+
real_ln=$(command -v ln)
|
|
1868
|
+
cat > "$case_dir/fakebin/ln" <<SH
|
|
1869
|
+
#!/usr/bin/env bash
|
|
1870
|
+
case "\$*" in
|
|
1871
|
+
*"$home/state/.meta-$id.lock"*)
|
|
1872
|
+
if [ ! -e "$case_dir/state-swapped" ]; then
|
|
1873
|
+
: > "$case_dir/state-swapped"
|
|
1874
|
+
mv "$home/state" "$home/state-original" || exit 1
|
|
1875
|
+
"$real_ln" -s "$foreign_state" "$home/state" || exit 1
|
|
1876
|
+
fi
|
|
1877
|
+
;;
|
|
1878
|
+
esac
|
|
1879
|
+
exec "$real_ln" "\$@"
|
|
1880
|
+
SH
|
|
1881
|
+
chmod +x "$case_dir/fakebin/ln"
|
|
1882
|
+
|
|
1883
|
+
out=$(run_bootstrap "$case_dir") || rc=$?
|
|
1884
|
+
[ "$rc" -ne 0 ] || fail "bootstrap trusted a worker record after its state boundary changed"
|
|
1885
|
+
assert_contains "$out" "post-lock worker record check refused" \
|
|
1886
|
+
"bootstrap did not report the post-lock state-boundary failure"
|
|
1887
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1888
|
+
|| fail "bootstrap changed the local row after reading through a swapped state path"
|
|
1889
|
+
assert_present "$foreign_state/$id.meta" "bootstrap removed the foreign worker record"
|
|
1890
|
+
pass "bootstrap rechecks worker-record containment after locking"
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
test_lifecycle_refuses_ancestor_symlinks_outside_home_roots() {
|
|
1894
|
+
local backlog_case worker_case close_case home foreign id marker out rc=0
|
|
1895
|
+
id=atomic-ancestor-symlink-b14
|
|
1896
|
+
|
|
1897
|
+
backlog_case=$(make_home ancestor-symlink-backlog "$id")
|
|
1898
|
+
home=$(home_of "$backlog_case")
|
|
1899
|
+
foreign="$backlog_case/foreign-home"
|
|
1900
|
+
mkdir -p "$foreign/data"
|
|
1901
|
+
cp "$(backlog_of "$backlog_case")" "$foreign/data/backlog.md"
|
|
1902
|
+
ln -s "$foreign" "$home/foreign-link"
|
|
1903
|
+
out=$(FM_DATA_OVERRIDE="$home/foreign-link/data" run_ship_spawn "$backlog_case" "$id") || rc=$?
|
|
1904
|
+
[ "$rc" -ne 0 ] || fail "dispatch accepted a backlog through an ancestor symlink"
|
|
1905
|
+
assert_absent "$home/state/$id.meta" "dispatch published through a foreign backlog root"
|
|
1906
|
+
|
|
1907
|
+
worker_case=$(make_home ancestor-symlink-worker)
|
|
1908
|
+
home=$(home_of "$worker_case")
|
|
1909
|
+
foreign="$worker_case/foreign-home"
|
|
1910
|
+
mkdir -p "$foreign/state"
|
|
1911
|
+
add_item "$worker_case" "$id"
|
|
1912
|
+
fm_write_meta "$foreign/state/$id.meta" "kind=ship" "spawn_gen=foreign-worker"
|
|
1913
|
+
ln -s "$foreign" "$home/foreign-link"
|
|
1914
|
+
rc=0
|
|
1915
|
+
out=$(FM_STATE_OVERRIDE="$home/foreign-link/state" run_bootstrap "$worker_case") || rc=$?
|
|
1916
|
+
[ "$rc" -ne 0 ] || fail "bootstrap accepted a worker record through an ancestor symlink"
|
|
1917
|
+
[ "$(row_state "$worker_case" "$id")" = queued ] \
|
|
1918
|
+
|| fail "bootstrap paired a foreign worker with the local backlog"
|
|
1919
|
+
|
|
1920
|
+
close_case=$(make_home ancestor-symlink-close)
|
|
1921
|
+
home=$(home_of "$close_case")
|
|
1922
|
+
foreign="$close_case/foreign-home"
|
|
1923
|
+
mkdir -p "$foreign/state"
|
|
1924
|
+
add_item "$close_case" "$id"
|
|
1925
|
+
start_item "$close_case" "$id"
|
|
1926
|
+
marker="$foreign/state/$id.backlog-close"
|
|
1927
|
+
printf 'id=%s\ndata=%s\nspawn_gen=foreign-close\narg=--note\narg=local%%20main\n' \
|
|
1928
|
+
"$id" "$home/data" > "$marker"
|
|
1929
|
+
ln -s "$foreign" "$home/foreign-link"
|
|
1930
|
+
rc=0
|
|
1931
|
+
out=$(FM_STATE_OVERRIDE="$home/foreign-link/state" run_bootstrap "$close_case") || rc=$?
|
|
1932
|
+
[ "$rc" -ne 0 ] || fail "bootstrap accepted a close record through an ancestor symlink"
|
|
1933
|
+
assert_present "$marker" "bootstrap discarded a foreign authoritative close"
|
|
1934
|
+
[ "$(row_state "$close_case" "$id")" = in_flight ] \
|
|
1935
|
+
|| fail "bootstrap applied a foreign close to the local backlog"
|
|
1936
|
+
pass "lifecycle files reject ancestor symlinks outside home roots"
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
test_same_home_state_override_remains_supported() {
|
|
1940
|
+
local case_dir home state id out
|
|
1941
|
+
id=atomic-same-home-state-override-b14
|
|
1942
|
+
case_dir=$(make_home same-home-state-override)
|
|
1943
|
+
home=$(home_of "$case_dir")
|
|
1944
|
+
state="$home/runtime-state"
|
|
1945
|
+
add_item "$case_dir" "$id"
|
|
1946
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=same-home-override"
|
|
1947
|
+
mv "$home/state" "$state"
|
|
1948
|
+
|
|
1949
|
+
out=$(FM_STATE_OVERRIDE="$state" run_bootstrap "$case_dir") \
|
|
1950
|
+
|| fail "same-home state override was refused: $out"
|
|
1951
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
1952
|
+
|| fail "same-home state override did not reconcile its worker"
|
|
1953
|
+
pass "same-home state overrides remain supported"
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
test_bootstrap_refuses_a_symlinked_state_directory_before_reconciliation() {
|
|
1957
|
+
local case_dir foreign_case home foreign_state id out rc=0
|
|
1958
|
+
id=atomic-bootstrap-symlink-state-b11
|
|
1959
|
+
case_dir=$(make_home bootstrap-symlink-state)
|
|
1960
|
+
foreign_case=$(make_home bootstrap-symlink-state-foreign)
|
|
1961
|
+
home=$(home_of "$case_dir")
|
|
1962
|
+
foreign_state="$(home_of "$foreign_case")/state"
|
|
1963
|
+
add_item "$case_dir" "$id"
|
|
1964
|
+
write_task_meta "$foreign_case" "$id" ship no-mistakes "spawn_gen=foreign-worker"
|
|
1965
|
+
rm -rf "$home/state"
|
|
1966
|
+
ln -s "$foreign_state" "$home/state"
|
|
1967
|
+
|
|
1968
|
+
out=$(run_bootstrap "$case_dir") || rc=$?
|
|
1969
|
+
[ "$rc" -ne 0 ] || fail "bootstrap accepted a symlinked state directory"
|
|
1970
|
+
assert_contains "$out" "state directory is not a real directory" \
|
|
1971
|
+
"bootstrap did not report the unsafe state boundary"
|
|
1972
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
1973
|
+
|| fail "bootstrap reconciled a foreign record into the local backlog"
|
|
1974
|
+
assert_present "$foreign_state/$id.meta" \
|
|
1975
|
+
"bootstrap removed the foreign worker record"
|
|
1976
|
+
pass "bootstrap refuses symlinked state before reconciliation"
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
test_bootstrap_stops_when_data_disappears_before_reconciliation() {
|
|
1980
|
+
local case_dir id saved out rc=0
|
|
1981
|
+
id=atomic-bootstrap-data-race-b11
|
|
1982
|
+
case_dir=$(make_home bootstrap-data-race)
|
|
1983
|
+
add_item "$case_dir" "$id"
|
|
1984
|
+
start_item "$case_dir" "$id"
|
|
1985
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes "spawn_gen=spawn-bootstrap-race"
|
|
1986
|
+
remove_data_during_startup_budget_check "$case_dir"
|
|
1987
|
+
saved="$case_dir/bootstrap-data"
|
|
1988
|
+
|
|
1989
|
+
out=$(run_bootstrap "$case_dir") || rc=$?
|
|
1990
|
+
[ "$rc" -ne 0 ] || fail "bootstrap absorbed a fatal reconciliation addressing error: $out"
|
|
1991
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
1992
|
+
"fatal bootstrap reconciliation removed the task record"
|
|
1993
|
+
[ "$(tasks-axi show "$id" --file "$saved/backlog.md" 2>/dev/null | sed -n 's/^ state: *//p' | head -1)" = in_flight ] \
|
|
1994
|
+
|| fail "fatal bootstrap reconciliation changed the backlog row"
|
|
1995
|
+
pass "bootstrap stops when backlog data disappears before reconciliation"
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
test_bootstrap_addressing_exemptions_remain_nonfatal() {
|
|
1999
|
+
local manual_case no_backlog_case secondmate_case secondmate_id out
|
|
2000
|
+
manual_case=$(make_home bootstrap-manual-exempt)
|
|
2001
|
+
printf '%s\n' manual > "$(home_of "$manual_case")/config/backlog-backend"
|
|
2002
|
+
mv "$(home_of "$manual_case")/data" "$manual_case/manual-data"
|
|
2003
|
+
out=$(run_bootstrap "$manual_case") \
|
|
2004
|
+
|| fail "manual bootstrap exemption became fatal: $out"
|
|
2005
|
+
|
|
2006
|
+
no_backlog_case=$(make_home bootstrap-no-backlog-exempt)
|
|
2007
|
+
rm -f "$(backlog_of "$no_backlog_case")"
|
|
2008
|
+
out=$(run_bootstrap "$no_backlog_case") \
|
|
2009
|
+
|| fail "no-backlog bootstrap exemption became fatal: $out"
|
|
2010
|
+
|
|
2011
|
+
secondmate_id=atomic-bootstrap-secondmate-exempt-b11
|
|
2012
|
+
secondmate_case=$(make_home bootstrap-secondmate-exempt)
|
|
2013
|
+
write_task_meta "$secondmate_case" "$secondmate_id" secondmate '' \
|
|
2014
|
+
"spawn_gen=spawn-secondmate-exempt"
|
|
2015
|
+
mv "$(home_of "$secondmate_case")/data" "$secondmate_case/secondmate-data"
|
|
2016
|
+
out=$(run_bootstrap "$secondmate_case") \
|
|
2017
|
+
|| fail "secondmate bootstrap exemption became fatal: $out"
|
|
2018
|
+
assert_present "$(home_of "$secondmate_case")/state/$secondmate_id.meta" \
|
|
2019
|
+
"secondmate bootstrap exemption removed the persistent agent record"
|
|
2020
|
+
pass "bootstrap preserves secondmate, manual, and absent-backlog exemptions"
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
test_recovery_leaves_a_captain_held_item_alone() {
|
|
2024
|
+
local case_dir id out
|
|
2025
|
+
id=atomic-heal-b11
|
|
2026
|
+
case_dir=$(make_home heal-held)
|
|
2027
|
+
add_item "$case_dir" "$id"
|
|
2028
|
+
tasks-axi hold "$id" --reason "captain decision pending" --kind captain \
|
|
2029
|
+
--file "$(backlog_of "$case_dir")" >/dev/null
|
|
2030
|
+
write_task_meta "$case_dir" "$id" ship no-mistakes
|
|
2031
|
+
|
|
2032
|
+
out=$(run_bootstrap "$case_dir")
|
|
2033
|
+
[ "$(row_state "$case_dir" "$id")" = queued ] \
|
|
2034
|
+
|| fail "session start moved a captain-held item to $(row_state "$case_dir" "$id"): $out"
|
|
2035
|
+
pass "session start leaves a captain-held item where the captain put it"
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
# --- backend selection and secondmate scope ---------------------------------
|
|
2039
|
+
|
|
2040
|
+
test_no_backlog_teardown_refuses_a_symlinked_task_record_at_entry() {
|
|
2041
|
+
local case_dir home id target target_dir foreign_worktree out rc=0
|
|
2042
|
+
id=atomic-no-backlog-symlink-meta-b12
|
|
2043
|
+
case_dir=$(make_home no-backlog-symlink-meta)
|
|
2044
|
+
home=$(home_of "$case_dir")
|
|
2045
|
+
rm -f "$(backlog_of "$case_dir")"
|
|
2046
|
+
foreign_worktree="$case_dir/foreign-worktree"
|
|
2047
|
+
mkdir -p "$foreign_worktree"
|
|
2048
|
+
target_dir="$home/state-foreign"
|
|
2049
|
+
target="$target_dir/$id.meta"
|
|
2050
|
+
mkdir -p "$target_dir"
|
|
2051
|
+
fm_write_meta "$target" \
|
|
2052
|
+
"window=firstmate:fm-$id" "endpoint_task_id=$id" \
|
|
2053
|
+
"worktree=$foreign_worktree" "project=$case_dir/foreign-project" \
|
|
2054
|
+
"harness=claude" "kind=ship" "mode=local-only" "yolo=off"
|
|
2055
|
+
ln -s "$target" "$home/state/$id.meta"
|
|
2056
|
+
track_teardown_resource_actions "$case_dir"
|
|
2057
|
+
|
|
2058
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
2059
|
+
[ "$rc" -ne 0 ] || fail "no-backlog teardown accepted a symlinked task record"
|
|
2060
|
+
assert_contains "$out" "task record resolves outside its authorized directory" \
|
|
2061
|
+
"teardown did not identify the unsafe task record"
|
|
2062
|
+
[ -L "$home/state/$id.meta" ] || fail "teardown removed the symlinked task record"
|
|
2063
|
+
assert_present "$foreign_worktree" "teardown removed a foreign local copy"
|
|
2064
|
+
assert_absent "$case_dir/backend-resource-action" \
|
|
2065
|
+
"teardown acted on the foreign endpoint"
|
|
2066
|
+
assert_absent "$case_dir/local-copy-resource-action" \
|
|
2067
|
+
"teardown acted on the foreign local copy"
|
|
2068
|
+
pass "no-backlog teardown refuses symlinked records before resource actions"
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
test_teardown_rechecks_record_parent_after_lock_acquisition() {
|
|
2072
|
+
local case_dir home id foreign_state foreign_worktree real_ln out rc=0
|
|
2073
|
+
id=atomic-state-parent-swap-b12
|
|
2074
|
+
case_dir=$(make_home state-parent-swap)
|
|
2075
|
+
home=$(home_of "$case_dir")
|
|
2076
|
+
rm -f "$(backlog_of "$case_dir")"
|
|
2077
|
+
write_task_meta "$case_dir" "$id" ship local-only
|
|
2078
|
+
foreign_state="$case_dir/foreign-state"
|
|
2079
|
+
foreign_worktree="$case_dir/foreign-worktree"
|
|
2080
|
+
mkdir -p "$foreign_state" "$foreign_worktree"
|
|
2081
|
+
fm_write_meta "$foreign_state/$id.meta" \
|
|
2082
|
+
"window=firstmate:fm-$id" "endpoint_task_id=$id" \
|
|
2083
|
+
"worktree=$foreign_worktree" "project=$case_dir/foreign-project" \
|
|
2084
|
+
"harness=claude" "kind=ship" "mode=local-only" "yolo=off"
|
|
2085
|
+
track_teardown_resource_actions "$case_dir"
|
|
2086
|
+
real_ln=$(command -v ln)
|
|
2087
|
+
cat > "$case_dir/fakebin/ln" <<SH
|
|
2088
|
+
#!/usr/bin/env bash
|
|
2089
|
+
case "\$*" in
|
|
2090
|
+
*"$home/state/.meta-$id.lock"*)
|
|
2091
|
+
if [ ! -e "$case_dir/state-swapped" ]; then
|
|
2092
|
+
: > "$case_dir/state-swapped"
|
|
2093
|
+
mv "$home/state" "$home/state-original" || exit 1
|
|
2094
|
+
"$real_ln" -s "$foreign_state" "$home/state" || exit 1
|
|
2095
|
+
fi
|
|
2096
|
+
;;
|
|
2097
|
+
esac
|
|
2098
|
+
exec "$real_ln" "\$@"
|
|
2099
|
+
SH
|
|
2100
|
+
chmod +x "$case_dir/fakebin/ln"
|
|
2101
|
+
|
|
2102
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
2103
|
+
[ "$rc" -ne 0 ] || fail "teardown trusted a record after its parent was swapped"
|
|
2104
|
+
assert_contains "$out" "task record authorized directory resolves outside this home" \
|
|
2105
|
+
"post-lock record check did not report the swapped parent"
|
|
2106
|
+
assert_present "$foreign_state/$id.meta" "teardown removed the foreign record"
|
|
2107
|
+
assert_present "$foreign_worktree" "teardown removed the foreign local copy"
|
|
2108
|
+
assert_absent "$case_dir/backend-resource-action" \
|
|
2109
|
+
"teardown acted on a foreign endpoint after the parent swap"
|
|
2110
|
+
assert_absent "$case_dir/local-copy-resource-action" \
|
|
2111
|
+
"teardown acted on a foreign local copy after the parent swap"
|
|
2112
|
+
pass "teardown rechecks record parents after locking"
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
test_teardown_refuses_a_symlinked_state_directory_at_entry() {
|
|
2116
|
+
local case_dir home id external_state out rc=0
|
|
2117
|
+
id=atomic-symlink-state-b12
|
|
2118
|
+
case_dir=$(make_home symlink-state)
|
|
2119
|
+
home=$(home_of "$case_dir")
|
|
2120
|
+
external_state="$case_dir/external-state"
|
|
2121
|
+
mv "$home/state" "$external_state"
|
|
2122
|
+
fm_write_meta "$external_state/$id.meta" \
|
|
2123
|
+
"window=firstmate:fm-$id" "endpoint_task_id=$id" \
|
|
2124
|
+
"worktree=$case_dir/foreign-worktree" "project=$case_dir/foreign-project" \
|
|
2125
|
+
"harness=claude" "kind=ship" "mode=local-only" "yolo=off"
|
|
2126
|
+
ln -s "$external_state" "$home/state"
|
|
2127
|
+
track_teardown_resource_actions "$case_dir"
|
|
2128
|
+
|
|
2129
|
+
out=$(run_teardown "$case_dir" "$id") || rc=$?
|
|
2130
|
+
[ "$rc" -ne 0 ] || fail "teardown accepted a symlinked state directory"
|
|
2131
|
+
assert_contains "$out" "state directory is not a real directory" \
|
|
2132
|
+
"teardown did not identify the unsafe state directory"
|
|
2133
|
+
assert_present "$external_state/$id.meta" \
|
|
2134
|
+
"teardown removed metadata through the symlinked state directory"
|
|
2135
|
+
assert_absent "$case_dir/backend-resource-action" \
|
|
2136
|
+
"teardown acted on an endpoint through symlinked state"
|
|
2137
|
+
assert_absent "$case_dir/local-copy-resource-action" \
|
|
2138
|
+
"teardown acted on a local copy through symlinked state"
|
|
2139
|
+
pass "teardown refuses symlinked state before resource actions"
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
test_home_without_a_backlog_dispatches_and_completes() {
|
|
2143
|
+
local case_dir id out
|
|
2144
|
+
id=atomic-no-backlog-b12
|
|
2145
|
+
case_dir=$(make_home no-backlog "$id")
|
|
2146
|
+
rm -f "$(backlog_of "$case_dir")"
|
|
2147
|
+
make_tasks_axi_incompatible "$case_dir"
|
|
2148
|
+
|
|
2149
|
+
out=$(run_ship_spawn "$case_dir" "$id") || fail "no-backlog spawn failed: $out"
|
|
2150
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" \
|
|
2151
|
+
"no-backlog spawn did not publish its task record"
|
|
2152
|
+
out=$(run_teardown "$case_dir" "$id") || fail "no-backlog teardown failed: $out"
|
|
2153
|
+
assert_absent "$(home_of "$case_dir")/state/$id.meta" \
|
|
2154
|
+
"no-backlog teardown retained its task record"
|
|
2155
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
2156
|
+
"no-backlog teardown recorded a close marker"
|
|
2157
|
+
pass "a home with no backlog remains exempt from lifecycle transitions"
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
test_manual_backend_home_dispatches_and_completes_without_touching_the_backlog() {
|
|
2161
|
+
local case_dir id data data_resolved out
|
|
2162
|
+
id=atomic-manual-b12
|
|
2163
|
+
case_dir=$(make_home manual-backend "$id")
|
|
2164
|
+
printf '%s\n' manual > "$(home_of "$case_dir")/config/backlog-backend"
|
|
2165
|
+
data="$case_dir/manual-data"
|
|
2166
|
+
mv "$(home_of "$case_dir")/data" "$data"
|
|
2167
|
+
data_resolved=$(cd "$data" && pwd -P)
|
|
2168
|
+
make_tasks_axi_incompatible "$case_dir"
|
|
2169
|
+
# Deliberately no backlog item: on a manual home the operator owns the file,
|
|
2170
|
+
# so neither half of the lifecycle may hard-fail over its contents.
|
|
2171
|
+
out=$(FM_DATA_OVERRIDE="$data" run_ship_spawn "$case_dir" "$id") \
|
|
2172
|
+
|| fail "manual-backend spawn failed: $out"
|
|
2173
|
+
assert_contains "$out" "spawned $id" "manual-backend spawn did not report success"
|
|
2174
|
+
|
|
2175
|
+
out=$(FM_DATA_OVERRIDE="$data" run_teardown "$case_dir" "$id") \
|
|
2176
|
+
|| fail "manual-backend teardown failed: $out"
|
|
2177
|
+
assert_contains "$out" "Update $data_resolved/backlog.md" \
|
|
2178
|
+
"manual-backend teardown did not name its configured backlog path"
|
|
2179
|
+
assert_absent "$(home_of "$case_dir")/state/$id.backlog-close" \
|
|
2180
|
+
"manual-backend teardown recorded a close it never owed"
|
|
2181
|
+
pass "a manual-backlog home dispatches and completes without a hard failure"
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
test_a_secondmate_home_keeps_its_own_books() {
|
|
2185
|
+
local case_dir id out
|
|
2186
|
+
id=atomic-mate-b13
|
|
2187
|
+
case_dir=$(make_home mate-own-books "$id")
|
|
2188
|
+
# The mate's home is a firstmate home in its own right; the invariant is
|
|
2189
|
+
# single-host, so its own dispatch and completion keep its own two records
|
|
2190
|
+
# paired with no parent involved.
|
|
2191
|
+
printf '%s\n' mate-h1 > "$(home_of "$case_dir")/.fm-secondmate-home"
|
|
2192
|
+
add_item "$case_dir" "$id"
|
|
2193
|
+
|
|
2194
|
+
out=$(run_ship_spawn "$case_dir" "$id") || fail "mate-home spawn failed: $out"
|
|
2195
|
+
[ "$(row_state "$case_dir" "$id")" = in_flight ] \
|
|
2196
|
+
|| fail "a mate's own dispatch left its item at $(row_state "$case_dir" "$id")"
|
|
2197
|
+
|
|
2198
|
+
rm -f "$(home_of "$case_dir")/state/$id.meta"
|
|
2199
|
+
write_task_meta "$case_dir" "$id" ship local-only "spawn_gen=spawn-mate-close"
|
|
2200
|
+
out=$(run_teardown "$case_dir" "$id") || fail "mate-home teardown failed: $out"
|
|
2201
|
+
[ "$(row_state "$case_dir" "$id")" = "done" ] \
|
|
2202
|
+
|| fail "a mate's own completion left its item at $(row_state "$case_dir" "$id")"
|
|
2203
|
+
pass "a secondmate home keeps its own books paired through dispatch and completion"
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
test_a_persistent_secondmate_is_never_a_backlog_item() {
|
|
2207
|
+
local case_dir id out mate
|
|
2208
|
+
id=atomic-mate-b14
|
|
2209
|
+
case_dir=$(make_home mate-not-an-item)
|
|
2210
|
+
mate="$case_dir/mate-home"
|
|
2211
|
+
mkdir -p "$mate/bin" "$mate/data"
|
|
2212
|
+
printf '# Firstmate\n' > "$mate/AGENTS.md"
|
|
2213
|
+
printf '%s\n' "$id" > "$mate/.fm-secondmate-home"
|
|
2214
|
+
printf 'charter for %s\n' "$id" > "$mate/data/charter.md"
|
|
2215
|
+
|
|
2216
|
+
# No backlog item exists for the mate, and none should be required: agents are
|
|
2217
|
+
# not work items. The dispatch must succeed anyway.
|
|
2218
|
+
out=$(run_spawn "$case_dir" "$id" "$mate" --secondmate) \
|
|
2219
|
+
|| fail "secondmate spawn failed: $out"
|
|
2220
|
+
assert_contains "$out" "spawned $id" "secondmate spawn did not report success"
|
|
2221
|
+
assert_present "$(home_of "$case_dir")/state/$id.meta" "secondmate spawn published no record"
|
|
2222
|
+
pass "dispatching a persistent secondmate needs no backlog item"
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
test_dispatch_moves_the_item_in_flight_in_the_same_run
|
|
2226
|
+
test_dispatch_refuses_a_pending_authoritative_close
|
|
2227
|
+
test_dispatch_refuses_a_held_row_before_creating_resources
|
|
2228
|
+
test_dispatch_refuses_a_blocked_row_before_creating_resources
|
|
2229
|
+
test_dispatch_refuses_a_held_in_flight_row_before_relaunch
|
|
2230
|
+
test_dispatch_reads_the_row_from_the_backlog_root
|
|
2231
|
+
test_recovery_uses_the_parent_of_a_trailing_slash_data_record
|
|
2232
|
+
test_completion_targets_a_nested_relative_data_directory
|
|
2233
|
+
test_immediate_child_absolute_data_dispatches_and_completes
|
|
2234
|
+
test_bare_relative_data_dispatches_and_completes
|
|
2235
|
+
test_dispatch_refuses_a_symlinked_backlog_without_crossing_homes
|
|
2236
|
+
test_automatic_backend_refuses_incompatible_tasks_axi_before_mutation
|
|
2237
|
+
test_dispatch_refuses_an_unresolvable_data_directory
|
|
2238
|
+
test_completion_refuses_an_unresolvable_data_directory
|
|
2239
|
+
test_dispatch_refuses_an_id_this_home_has_no_item_for
|
|
2240
|
+
test_dispatch_reports_a_backlog_read_failure
|
|
2241
|
+
test_dispatch_refuses_a_closed_item
|
|
2242
|
+
test_dispatch_refuses_to_commit_without_a_published_record
|
|
2243
|
+
test_dispatch_leaves_no_record_when_the_transition_fails
|
|
2244
|
+
test_dispatch_reports_an_incomplete_record_rollback
|
|
2245
|
+
test_dispatch_reports_an_incomplete_busy_rollback
|
|
2246
|
+
test_dispatch_rolls_back_before_a_failed_launch_delivery
|
|
2247
|
+
test_dispatch_defers_interruption_across_backlog_commit
|
|
2248
|
+
test_dispatch_interruption_during_kimi_readiness_fails_before_commit
|
|
2249
|
+
test_dispatch_does_not_resurrect_a_row_closed_after_preflight
|
|
2250
|
+
test_dispatch_fails_when_its_row_vanishes_after_preflight
|
|
2251
|
+
test_completion_closes_a_local_only_ship_before_reporting_success
|
|
2252
|
+
test_completion_closes_a_scout_with_its_report
|
|
2253
|
+
test_completion_refuses_a_legacy_record_without_an_incarnation
|
|
2254
|
+
test_completion_refuses_ambiguous_incarnation_metadata
|
|
2255
|
+
test_completion_records_a_relative_report_for_relocated_data
|
|
2256
|
+
test_space_containing_scout_report_marker_replays
|
|
2257
|
+
test_trailing_newline_data_path_fails_closed
|
|
2258
|
+
test_control_character_data_path_is_refused_before_cleanup
|
|
2259
|
+
test_completion_preserves_records_when_meta_removal_fails
|
|
2260
|
+
test_completion_fails_loudly_and_records_the_close_it_still_owes
|
|
2261
|
+
test_interrupted_destructive_cleanup_leaves_a_recoverable_close
|
|
2262
|
+
test_completion_refuses_a_close_target_symlinked_to_a_directory
|
|
2263
|
+
test_completion_fails_when_its_close_marker_cannot_be_removed
|
|
2264
|
+
test_recovery_retries_when_a_close_marker_cannot_be_removed
|
|
2265
|
+
test_recovery_reports_an_owned_row_read_failure
|
|
2266
|
+
test_orca_cleanup_recovery_never_transitions_the_backlog
|
|
2267
|
+
test_recovery_marks_an_owned_record_in_flight
|
|
2268
|
+
test_recovery_rejects_an_internal_worker_record_symlink
|
|
2269
|
+
test_recovery_ignores_a_symlinked_worker_record
|
|
2270
|
+
test_recovery_replays_a_close_an_interrupted_cleanup_left_open
|
|
2271
|
+
test_recovery_backfills_a_recorded_link_on_an_already_done_item
|
|
2272
|
+
test_recovery_preserves_a_close_when_the_backlog_cannot_be_read
|
|
2273
|
+
test_recovery_retry_preserves_incomplete_cleanup_warning
|
|
2274
|
+
test_recovery_finishes_a_close_for_the_same_meta_incarnation
|
|
2275
|
+
test_recovery_preserves_a_close_for_ambiguous_incarnation_metadata
|
|
2276
|
+
test_recovery_preserves_both_records_when_meta_removal_fails
|
|
2277
|
+
test_recovery_preserves_a_close_beside_symlinked_metadata
|
|
2278
|
+
test_recovery_rejects_a_marker_for_another_task_identity
|
|
2279
|
+
test_recovery_rejects_a_foreign_data_directory
|
|
2280
|
+
test_recovery_rejects_an_unterminated_unknown_field
|
|
2281
|
+
test_recovery_rejects_lexical_data_traversal
|
|
2282
|
+
test_recovery_rejects_raw_control_bytes
|
|
2283
|
+
test_recovery_rejects_malformed_pr_urls
|
|
2284
|
+
test_failed_close_replay_is_not_started_as_live_work
|
|
2285
|
+
test_recovery_rejects_invalid_close_arguments
|
|
2286
|
+
test_recovery_rejects_a_symlinked_close_marker
|
|
2287
|
+
test_recovery_drops_a_close_for_a_newer_meta_incarnation
|
|
2288
|
+
test_recovery_rejects_a_legacy_close_without_an_incarnation
|
|
2289
|
+
test_bootstrap_rechecks_worker_record_boundary_after_locking
|
|
2290
|
+
test_lifecycle_refuses_ancestor_symlinks_outside_home_roots
|
|
2291
|
+
test_same_home_state_override_remains_supported
|
|
2292
|
+
test_bootstrap_refuses_a_symlinked_state_directory_before_reconciliation
|
|
2293
|
+
test_bootstrap_stops_when_data_disappears_before_reconciliation
|
|
2294
|
+
test_bootstrap_addressing_exemptions_remain_nonfatal
|
|
2295
|
+
test_recovery_leaves_a_captain_held_item_alone
|
|
2296
|
+
test_no_backlog_teardown_refuses_a_symlinked_task_record_at_entry
|
|
2297
|
+
test_teardown_rechecks_record_parent_after_lock_acquisition
|
|
2298
|
+
test_teardown_refuses_a_symlinked_state_directory_at_entry
|
|
2299
|
+
test_home_without_a_backlog_dispatches_and_completes
|
|
2300
|
+
test_manual_backend_home_dispatches_and_completes_without_touching_the_backlog
|
|
2301
|
+
test_a_secondmate_home_keeps_its_own_books
|
|
2302
|
+
test_a_persistent_secondmate_is_never_a_backlog_item
|