@zerwiz/ymir 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -59
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Isolated real-Herdr E2E coverage for the default-on disposable single-task
|
|
3
|
+
# presentation projection, its explicit opt-out, and its best-effort
|
|
4
|
+
# owning-parent ordering across primary and secondmate homes.
|
|
5
|
+
# The test drives the real spawn and teardown scripts, a real Treehouse pool,
|
|
6
|
+
# and the guarded named-session lab helper.
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
10
|
+
HERDR_LAB_HELPER=${HERDR_LAB_HELPER:-$ROOT/bin/fm-herdr-lab.sh}
|
|
11
|
+
|
|
12
|
+
fail() { printf 'not ok - %s\n' "$1" >&2; cleanup_all; exit 1; }
|
|
13
|
+
pass() { printf 'ok - %s\n' "$1"; }
|
|
14
|
+
|
|
15
|
+
command -v herdr >/dev/null 2>&1 || { echo "skip: herdr not found"; exit 0; }
|
|
16
|
+
command -v jq >/dev/null 2>&1 || { echo "skip: jq not found"; exit 0; }
|
|
17
|
+
command -v treehouse >/dev/null 2>&1 || { echo "skip: treehouse not found"; exit 0; }
|
|
18
|
+
[ -x "$HERDR_LAB_HELPER" ] || { echo "skip: Herdr lab helper not executable at $HERDR_LAB_HELPER"; exit 0; }
|
|
19
|
+
|
|
20
|
+
REAL_HERDR=$(command -v herdr)
|
|
21
|
+
REAL_TREEHOUSE=$(command -v treehouse)
|
|
22
|
+
HERDR_ORIGINAL_PATH=$PATH
|
|
23
|
+
TMP_ROOT=$(mktemp -d "$(cd "${TMPDIR:-/tmp}" && pwd -P)/fm-herdr-presentation.XXXXXX")
|
|
24
|
+
FAKEBIN="$TMP_ROOT/fakebin"
|
|
25
|
+
HERDR_CALL_LOG="$TMP_ROOT/herdr-calls.log"
|
|
26
|
+
TREEHOUSE_CALL_LOG="$TMP_ROOT/treehouse-calls.log"
|
|
27
|
+
MOVE_CALL_LOG="$TMP_ROOT/workspace-move-calls.log"
|
|
28
|
+
FOCUS_AUDIT_LOG="$TMP_ROOT/focus-audit.log"
|
|
29
|
+
ACTIVE_SEEDED_CONTROL="$TMP_ROOT/active-seeded-control"
|
|
30
|
+
POST_CREATE_ABORT_CONTROL="$TMP_ROOT/post-create-abort-control"
|
|
31
|
+
mkdir -p "$FAKEBIN"
|
|
32
|
+
: > "$HERDR_CALL_LOG"
|
|
33
|
+
: > "$TREEHOUSE_CALL_LOG"
|
|
34
|
+
: > "$MOVE_CALL_LOG"
|
|
35
|
+
: > "$FOCUS_AUDIT_LOG"
|
|
36
|
+
REAL_MOVER="$ROOT/bin/backends/herdr-workspace-move.py"
|
|
37
|
+
export REAL_HERDR REAL_TREEHOUSE REAL_MOVER HERDR_CALL_LOG TREEHOUSE_CALL_LOG MOVE_CALL_LOG FOCUS_AUDIT_LOG HERDR_ORIGINAL_PATH HERDR_LAB_HELPER
|
|
38
|
+
export ACTIVE_SEEDED_CONTROL POST_CREATE_ABORT_CONTROL TMP_ROOT
|
|
39
|
+
|
|
40
|
+
# Log every production-adapter call, remove its already-validated trailing
|
|
41
|
+
# session flag, and send the operation through the lab helper so that helper
|
|
42
|
+
# remains the sole process which appends the real trailing session flag.
|
|
43
|
+
# The adapter's deliberately session-independent version read cannot pass the
|
|
44
|
+
# helper's leading-option guard, so the wrapper sends only that read straight
|
|
45
|
+
# to the absolute real binary with the same explicit trailing lab session.
|
|
46
|
+
cat > "$FAKEBIN/herdr" <<'SH'
|
|
47
|
+
#!/usr/bin/env bash
|
|
48
|
+
set -u
|
|
49
|
+
{
|
|
50
|
+
first=1
|
|
51
|
+
for arg in "$@"; do
|
|
52
|
+
[ "$first" -eq 0 ] && printf '\t'
|
|
53
|
+
printf '%s' "$arg"
|
|
54
|
+
first=0
|
|
55
|
+
done
|
|
56
|
+
printf '\n'
|
|
57
|
+
} >> "$HERDR_CALL_LOG"
|
|
58
|
+
args=("$@")
|
|
59
|
+
last_index=$((${#args[@]} - 1))
|
|
60
|
+
flag_index=$((last_index - 1))
|
|
61
|
+
if [ "${#args[@]}" -ge 2 ] \
|
|
62
|
+
&& [ "${args[$flag_index]}" = --session ] \
|
|
63
|
+
&& [ "${args[$last_index]}" = "${HERDR_LAB_SESSION:?}" ]; then
|
|
64
|
+
unset "args[$last_index]" "args[$flag_index]"
|
|
65
|
+
fi
|
|
66
|
+
set -- "${args[@]}"
|
|
67
|
+
for arg in "$@"; do
|
|
68
|
+
case "$arg" in
|
|
69
|
+
--session|--session=*)
|
|
70
|
+
echo "test wrapper: unexpected caller-supplied session flag" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
done
|
|
75
|
+
if [ "${1:-}" = --version ]; then
|
|
76
|
+
exec env PATH="$HERDR_ORIGINAL_PATH" "$REAL_HERDR" "$@" --session "$HERDR_LAB_SESSION"
|
|
77
|
+
fi
|
|
78
|
+
focus_snapshot() {
|
|
79
|
+
local list row workspace tab tabs
|
|
80
|
+
list=$(env PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" workspace list) || return 1
|
|
81
|
+
row=$(printf '%s' "$list" | jq -r '
|
|
82
|
+
[.result.workspaces[]? | select(.focused == true)]
|
|
83
|
+
| select(length == 1)
|
|
84
|
+
| .[0]
|
|
85
|
+
| select((.workspace_id | type) == "string" and (.active_tab_id | type) == "string")
|
|
86
|
+
| [.workspace_id, .active_tab_id]
|
|
87
|
+
| @tsv
|
|
88
|
+
') || return 1
|
|
89
|
+
[ -n "$row" ] || return 1
|
|
90
|
+
workspace=${row%%$'\t'*}
|
|
91
|
+
tab=${row#*$'\t'}
|
|
92
|
+
tabs=$(env PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" tab list --workspace "$workspace") || return 1
|
|
93
|
+
printf '%s' "$tabs" | jq -e --arg tab "$tab" '
|
|
94
|
+
([.result.tabs[]? | select(.focused == true)] | length) == 1
|
|
95
|
+
and ([.result.tabs[]? | select(.focused == true)][0].tab_id == $tab)
|
|
96
|
+
' >/dev/null 2>&1 || return 1
|
|
97
|
+
printf '%s/%s' "$workspace" "$tab"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
arg_value() {
|
|
101
|
+
local want=$1 previous= arg
|
|
102
|
+
shift
|
|
103
|
+
for arg in "$@"; do
|
|
104
|
+
if [ "$previous" = "$want" ]; then
|
|
105
|
+
printf '%s' "$arg"
|
|
106
|
+
return 0
|
|
107
|
+
fi
|
|
108
|
+
previous=$arg
|
|
109
|
+
done
|
|
110
|
+
return 1
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
label=$(arg_value --label "$@" || true)
|
|
114
|
+
if [ "${1:-} ${2:-}" = "workspace list" ] && [ -d "$ACTIVE_SEEDED_CONTROL" ]; then
|
|
115
|
+
stage=$(cat "$ACTIVE_SEEDED_CONTROL/stage" 2>/dev/null || true)
|
|
116
|
+
if [ "$stage" = task-created ]; then
|
|
117
|
+
printf '%s\n' post-task-snapshot > "$ACTIVE_SEEDED_CONTROL/stage"
|
|
118
|
+
elif [ "$stage" = post-task-snapshot ]; then
|
|
119
|
+
seeded_tab=$(cat "$ACTIVE_SEEDED_CONTROL/seeded-tab")
|
|
120
|
+
inject_before=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
121
|
+
env PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" tab focus "$seeded_tab" >/dev/null
|
|
122
|
+
inject_after=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
123
|
+
printf 'active-seeded-inject\t%s\t%s\t%s\n' "$inject_before" "$inject_after" "$seeded_tab" >> "$FOCUS_AUDIT_LOG"
|
|
124
|
+
printf '%s\n' injected > "$ACTIVE_SEEDED_CONTROL/stage"
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
mutation=
|
|
129
|
+
mutation_target=${3:-}
|
|
130
|
+
case "${1:-} ${2:-}" in
|
|
131
|
+
"workspace create") mutation=workspace-create; mutation_target=$label ;;
|
|
132
|
+
"tab create") mutation=tab-create; mutation_target=$label ;;
|
|
133
|
+
"pane close") mutation=pane-close ;;
|
|
134
|
+
"tab focus") mutation=tab-focus ;;
|
|
135
|
+
esac
|
|
136
|
+
refusal_probe=0
|
|
137
|
+
if [ "${1:-} ${2:-}" = "pane get" ] && [ -d "$ACTIVE_SEEDED_CONTROL" ] \
|
|
138
|
+
&& [ "$(cat "$ACTIVE_SEEDED_CONTROL/stage" 2>/dev/null || true)" = injected ] \
|
|
139
|
+
&& [ "${3:-}" = "$(cat "$ACTIVE_SEEDED_CONTROL/seeded-pane" 2>/dev/null || true)" ]; then
|
|
140
|
+
refusal_probe=1
|
|
141
|
+
refusal_before=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
142
|
+
fi
|
|
143
|
+
before=
|
|
144
|
+
[ -z "$mutation" ] || before=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
145
|
+
if out=$(env PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" "$@"); then
|
|
146
|
+
status=0
|
|
147
|
+
else
|
|
148
|
+
status=$?
|
|
149
|
+
fi
|
|
150
|
+
if [ "$status" -eq 0 ] && [ "$mutation" = workspace-create ]; then
|
|
151
|
+
case "$label" in
|
|
152
|
+
$'└ active-seeded · p:'*)
|
|
153
|
+
mkdir -p "$ACTIVE_SEEDED_CONTROL"
|
|
154
|
+
printf '%s\n' "$(printf '%s' "$out" | jq -r '.result.workspace.workspace_id')" > "$ACTIVE_SEEDED_CONTROL/workspace"
|
|
155
|
+
printf '%s\n' "$(printf '%s' "$out" | jq -r '.result.tab.tab_id')" > "$ACTIVE_SEEDED_CONTROL/seeded-tab"
|
|
156
|
+
printf '%s\n' "$(printf '%s' "$out" | jq -r '.result.root_pane.pane_id')" > "$ACTIVE_SEEDED_CONTROL/seeded-pane"
|
|
157
|
+
;;
|
|
158
|
+
$'└ abort-a · p:'*|$'└ abort-b · p:'*)
|
|
159
|
+
task=${label#$'└ '}; task=${task%% *}
|
|
160
|
+
mkdir -p "$POST_CREATE_ABORT_CONTROL/$task"
|
|
161
|
+
printf '%s\n' "$(printf '%s' "$out" | jq -r '.result.workspace.workspace_id')" > "$POST_CREATE_ABORT_CONTROL/$task/workspace"
|
|
162
|
+
;;
|
|
163
|
+
esac
|
|
164
|
+
fi
|
|
165
|
+
if [ "$status" -eq 0 ] && [ "$mutation" = tab-create ]; then
|
|
166
|
+
case "$label" in
|
|
167
|
+
fm-active-seeded)
|
|
168
|
+
printf '%s\n' "$(printf '%s' "$out" | jq -r '.result.root_pane.pane_id')" > "$ACTIVE_SEEDED_CONTROL/task-pane"
|
|
169
|
+
printf '%s\n' task-created > "$ACTIVE_SEEDED_CONTROL/stage"
|
|
170
|
+
;;
|
|
171
|
+
fm-abort-a|fm-abort-b)
|
|
172
|
+
task=${label#fm-}
|
|
173
|
+
mkdir -p "$POST_CREATE_ABORT_CONTROL/$task"
|
|
174
|
+
printf '%s\n' "$(printf '%s' "$out" | jq -r '.result.root_pane.pane_id')" > "$POST_CREATE_ABORT_CONTROL/$task/task-pane"
|
|
175
|
+
;;
|
|
176
|
+
esac
|
|
177
|
+
fi
|
|
178
|
+
if [ "$status" -eq 0 ] && [ "${1:-} ${2:-}" = "pane get" ] && [ -d "$POST_CREATE_ABORT_CONTROL" ]; then
|
|
179
|
+
for task_dir in "$POST_CREATE_ABORT_CONTROL"/abort-*; do
|
|
180
|
+
[ -d "$task_dir" ] || continue
|
|
181
|
+
[ "${3:-}" = "$(cat "$task_dir/task-pane" 2>/dev/null || true)" ] || continue
|
|
182
|
+
out=$(printf '%s' "$out" | jq --arg cwd "$POST_CREATE_ABORT_CONTROL/not-a-worktree" '.result.pane.foreground_cwd = $cwd')
|
|
183
|
+
break
|
|
184
|
+
done
|
|
185
|
+
fi
|
|
186
|
+
if [ -n "$mutation" ]; then
|
|
187
|
+
after=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
188
|
+
printf '%s\t%s\t%s\t%s\n' "$mutation" "$before" "$after" "$mutation_target" >> "$FOCUS_AUDIT_LOG"
|
|
189
|
+
fi
|
|
190
|
+
if [ "$refusal_probe" -eq 1 ]; then
|
|
191
|
+
refusal_after=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
192
|
+
printf 'seeded-prune-refusal\t%s\t%s\t%s\n' "$refusal_before" "$refusal_after" "${3:-}" >> "$FOCUS_AUDIT_LOG"
|
|
193
|
+
fi
|
|
194
|
+
[ -z "$out" ] || printf '%s\n' "$out"
|
|
195
|
+
exit "$status"
|
|
196
|
+
SH
|
|
197
|
+
|
|
198
|
+
cat > "$FAKEBIN/treehouse" <<'SH'
|
|
199
|
+
#!/usr/bin/env bash
|
|
200
|
+
set -u
|
|
201
|
+
{
|
|
202
|
+
first=1
|
|
203
|
+
for arg in "$@"; do
|
|
204
|
+
[ "$first" -eq 0 ] && printf '\t'
|
|
205
|
+
printf '%s' "$arg"
|
|
206
|
+
first=0
|
|
207
|
+
done
|
|
208
|
+
printf '\n'
|
|
209
|
+
} >> "$TREEHOUSE_CALL_LOG"
|
|
210
|
+
if [ -d "$POST_CREATE_ABORT_CONTROL" ] && [ "${1:-}" = get ]; then
|
|
211
|
+
exit 0
|
|
212
|
+
fi
|
|
213
|
+
exec "$REAL_TREEHOUSE" "$@"
|
|
214
|
+
SH
|
|
215
|
+
|
|
216
|
+
cat > "$FAKEBIN/herdr-workspace-mover" <<'SH'
|
|
217
|
+
#!/usr/bin/env bash
|
|
218
|
+
set -u
|
|
219
|
+
focus_snapshot() {
|
|
220
|
+
local list row workspace tab tabs
|
|
221
|
+
list=$(env PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" workspace list) || return 1
|
|
222
|
+
row=$(printf '%s' "$list" | jq -r '
|
|
223
|
+
[.result.workspaces[]? | select(.focused == true)]
|
|
224
|
+
| select(length == 1)
|
|
225
|
+
| .[0]
|
|
226
|
+
| [.workspace_id, .active_tab_id]
|
|
227
|
+
| @tsv
|
|
228
|
+
') || return 1
|
|
229
|
+
[ -n "$row" ] || return 1
|
|
230
|
+
workspace=${row%%$'\t'*}
|
|
231
|
+
tab=${row#*$'\t'}
|
|
232
|
+
tabs=$(env PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" tab list --workspace "$workspace") || return 1
|
|
233
|
+
printf '%s' "$tabs" | jq -e --arg tab "$tab" '
|
|
234
|
+
([.result.tabs[]? | select(.focused == true)] | length) == 1
|
|
235
|
+
and ([.result.tabs[]? | select(.focused == true)][0].tab_id == $tab)
|
|
236
|
+
' >/dev/null 2>&1 || return 1
|
|
237
|
+
printf '%s/%s' "$workspace" "$tab"
|
|
238
|
+
}
|
|
239
|
+
printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$MOVE_CALL_LOG"
|
|
240
|
+
before=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
241
|
+
if out=$("$REAL_MOVER" "$@"); then
|
|
242
|
+
status=0
|
|
243
|
+
else
|
|
244
|
+
status=$?
|
|
245
|
+
fi
|
|
246
|
+
after=$(focus_snapshot || printf ambiguous/ambiguous)
|
|
247
|
+
printf 'workspace-move\t%s\t%s\t%s\n' "$before" "$after" "$2" >> "$FOCUS_AUDIT_LOG"
|
|
248
|
+
[ -z "$out" ] || printf '%s\n' "$out"
|
|
249
|
+
exit "$status"
|
|
250
|
+
SH
|
|
251
|
+
chmod +x "$FAKEBIN/herdr" "$FAKEBIN/treehouse"
|
|
252
|
+
chmod +x "$FAKEBIN/herdr-workspace-mover"
|
|
253
|
+
export PATH="$FAKEBIN:$PATH"
|
|
254
|
+
export FM_BACKEND_HERDR_WORKSPACE_MOVER="$FAKEBIN/herdr-workspace-mover"
|
|
255
|
+
|
|
256
|
+
# shellcheck source=tests/herdr-test-safety.sh
|
|
257
|
+
. "$ROOT/tests/herdr-test-safety.sh"
|
|
258
|
+
# This suite runs against its own isolated lab session, so a Herdr pane
|
|
259
|
+
# inherited from the terminal it was launched in must not follow spawn into it
|
|
260
|
+
# as a cross-session parent identity. Every projection below is anchored on the
|
|
261
|
+
# parent this suite sets up, not on the developer's own workspace.
|
|
262
|
+
herdr_forget_inherited_pane
|
|
263
|
+
|
|
264
|
+
HERDR_LAB_SESSION=$(PATH="$HERDR_ORIGINAL_PATH" \
|
|
265
|
+
"$HERDR_LAB_HELPER" name fm-herdr-presentation-projection)
|
|
266
|
+
export HERDR_SESSION="$HERDR_LAB_SESSION" HERDR_LAB_SESSION
|
|
267
|
+
LAB_READY=0
|
|
268
|
+
RECORDED_WORKTREES=""
|
|
269
|
+
LOCK_CONTENTION_OWNER_PID=
|
|
270
|
+
cleanup_all() {
|
|
271
|
+
local wt
|
|
272
|
+
if [ -n "$LOCK_CONTENTION_OWNER_PID" ]; then
|
|
273
|
+
kill "$LOCK_CONTENTION_OWNER_PID" 2>/dev/null || true
|
|
274
|
+
wait "$LOCK_CONTENTION_OWNER_PID" 2>/dev/null || true
|
|
275
|
+
LOCK_CONTENTION_OWNER_PID=
|
|
276
|
+
fi
|
|
277
|
+
while IFS= read -r wt; do
|
|
278
|
+
[ -n "$wt" ] || continue
|
|
279
|
+
[ -d "$wt" ] || continue
|
|
280
|
+
"$REAL_TREEHOUSE" return --force "$wt" >/dev/null 2>&1 || true
|
|
281
|
+
done <<EOF
|
|
282
|
+
$RECORDED_WORKTREES
|
|
283
|
+
EOF
|
|
284
|
+
if [ "$LAB_READY" -eq 1 ]; then
|
|
285
|
+
PATH="$HERDR_ORIGINAL_PATH" \
|
|
286
|
+
"$HERDR_LAB_HELPER" teardown "$HERDR_LAB_SESSION" >/dev/null 2>&1 || true
|
|
287
|
+
LAB_READY=0
|
|
288
|
+
fi
|
|
289
|
+
rm -rf "$TMP_ROOT"
|
|
290
|
+
}
|
|
291
|
+
trap cleanup_all EXIT
|
|
292
|
+
|
|
293
|
+
PATH="$HERDR_ORIGINAL_PATH" \
|
|
294
|
+
"$HERDR_LAB_HELPER" provision "$HERDR_LAB_SESSION" \
|
|
295
|
+
|| fail "could not provision the isolated Herdr lab"
|
|
296
|
+
LAB_READY=1
|
|
297
|
+
|
|
298
|
+
lab() {
|
|
299
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" run "$HERDR_LAB_SESSION" "$@"
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
focus_snapshot() {
|
|
303
|
+
local list row workspace tab tabs
|
|
304
|
+
list=$(lab workspace list) || fail "could not read the active workspace for focus instrumentation"
|
|
305
|
+
row=$(printf '%s' "$list" | jq -r '
|
|
306
|
+
[.result.workspaces[]? | select(.focused == true)]
|
|
307
|
+
| select(length == 1)
|
|
308
|
+
| .[0]
|
|
309
|
+
| select((.workspace_id | type) == "string" and (.active_tab_id | type) == "string")
|
|
310
|
+
| [.workspace_id, .active_tab_id]
|
|
311
|
+
| @tsv
|
|
312
|
+
') || fail "could not parse the active workspace and tab"
|
|
313
|
+
[ -n "$row" ] || fail "focus instrumentation found an ambiguous active workspace"
|
|
314
|
+
workspace=${row%%$'\t'*}
|
|
315
|
+
tab=${row#*$'\t'}
|
|
316
|
+
tabs=$(lab tab list --workspace "$workspace") || fail "could not verify the active tab"
|
|
317
|
+
printf '%s' "$tabs" | jq -e --arg tab "$tab" '
|
|
318
|
+
([.result.tabs[]? | select(.focused == true)] | length) == 1
|
|
319
|
+
and ([.result.tabs[]? | select(.focused == true)][0].tab_id == $tab)
|
|
320
|
+
' >/dev/null 2>&1 || fail "workspace active_tab_id disagreed with the focused tab"
|
|
321
|
+
printf '%s/%s' "$workspace" "$tab"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
assert_focus_is() { # <expected> <case-name>
|
|
325
|
+
local expected=$1 case_name=$2 actual
|
|
326
|
+
actual=$(focus_snapshot)
|
|
327
|
+
[ "$actual" = "$expected" ] || fail "$case_name changed active workspace/tab from $expected to $actual"
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
focus_audit_line_count() { wc -l < "$FOCUS_AUDIT_LOG" | tr -d '[:space:]'; }
|
|
331
|
+
|
|
332
|
+
assert_raw_presentation_mutations_preserved_since() { # <line-count> <case-name>
|
|
333
|
+
local start=$1 case_name=$2 changed
|
|
334
|
+
changed=$(sed -n "$((start + 1)),\$p" "$FOCUS_AUDIT_LOG" | awk -F '\t' '
|
|
335
|
+
($1 == "workspace-create" || $1 == "tab-create" || $1 == "workspace-move" || $1 == "pane-close") && $2 != $3 {
|
|
336
|
+
print $0
|
|
337
|
+
}
|
|
338
|
+
')
|
|
339
|
+
[ -z "$changed" ] || fail "$case_name changed active workspace/tab inside a create, move, or seeded cleanup: $changed"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# The focus-safe emptying-close plan removes a last pane through Herdr's
|
|
343
|
+
# pane-death path with no pane.close mutation at all (the raw explicit-close
|
|
344
|
+
# defect is demonstrated by tests/fm-backend-herdr-focus-flash-e2e.test.sh);
|
|
345
|
+
# a fallback plain close must preserve or immediately restore exact focus.
|
|
346
|
+
assert_cleanup_focus_preserved() { # <line-count> <pane-id> <expected-focus>
|
|
347
|
+
local start=$1 pane_id=$2 expected=$3
|
|
348
|
+
sed -n "$((start + 1)),\$p" "$FOCUS_AUDIT_LOG" | awk -F '\t' -v pane="$pane_id" -v expected="$expected" '
|
|
349
|
+
$1 == "pane-close" && $4 == pane {
|
|
350
|
+
saw_close = 1
|
|
351
|
+
if ($2 != expected) { bad = 1 }
|
|
352
|
+
else if ($3 == expected) { preserved = 1 }
|
|
353
|
+
else { drift = $3 }
|
|
354
|
+
next
|
|
355
|
+
}
|
|
356
|
+
saw_close && drift != "" && $1 == "tab-focus" && $2 == drift && $3 == expected {
|
|
357
|
+
preserved = 1
|
|
358
|
+
}
|
|
359
|
+
END { exit(bad || (saw_close && !preserved) ? 1 : 0) }
|
|
360
|
+
' || fail "projected pane close did not preserve or restore the exact active workspace and tab"
|
|
361
|
+
if lab pane get "$pane_id" >/dev/null 2>&1; then
|
|
362
|
+
fail "projected cleanup left exact pane $pane_id alive"
|
|
363
|
+
fi
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
remember_meta_worktree() { # <meta>
|
|
367
|
+
local wt
|
|
368
|
+
wt=$(grep '^worktree=' "$1" | cut -d= -f2-)
|
|
369
|
+
[ -n "$wt" ] || fail "metadata did not record a worktree"
|
|
370
|
+
RECORDED_WORKTREES="${RECORDED_WORKTREES}${wt}"$'\n'
|
|
371
|
+
printf '%s' "$wt"
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
make_project() { # <dir>
|
|
375
|
+
local dir=$1
|
|
376
|
+
mkdir -p "$dir"
|
|
377
|
+
git -C "$dir" init -q
|
|
378
|
+
printf '# Herdr projection E2E fixture\n' > "$dir/README.md"
|
|
379
|
+
git -C "$dir" add README.md
|
|
380
|
+
git -C "$dir" -c user.name='Firstmate Tests' -c user.email='tests@example.invalid' commit -qm initial
|
|
381
|
+
git clone --quiet --bare "$dir" "$dir.origin.git"
|
|
382
|
+
git -C "$dir" remote add origin "file://$dir.origin.git"
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
spawn_task() { # <id> <home> <project>
|
|
386
|
+
local id=$1 home=$2 project=$3
|
|
387
|
+
FM_GATE_REFUSE_BYPASS=1 FM_SPAWN_NO_GUARD=1 FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
388
|
+
"$ROOT/bin/fm-spawn.sh" "$id" "$project" "sh -c 'sleep 120'" --mode no-mistakes --yolo off --backend herdr
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
finish_concurrent_spawn() { # <id> <status> <stdout> <stderr>
|
|
392
|
+
local id=$1 status=$2 out=$3 err=$4
|
|
393
|
+
[ "$status" -ne 0 ] || return 0
|
|
394
|
+
grep -F "task set is locked" "$err" >/dev/null 2>&1 \
|
|
395
|
+
|| fail "concurrent projected spawn $id failed unexpectedly: $(cat "$err")"
|
|
396
|
+
spawn_task "$id" "$HOME_DIR" "$PROJECT_DIR" > "$out" 2> "$err" \
|
|
397
|
+
|| fail "projected spawn $id retry failed after task-set publication completed: $(cat "$err")"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
finish_concurrent_expected_abort() { # <id> <status> <stdout> <stderr>
|
|
401
|
+
local id=$1 status=$2 out=$3 err=$4
|
|
402
|
+
[ "$status" -ne 0 ] || fail "post-create abort fixture $id unexpectedly succeeded"
|
|
403
|
+
if grep -F "task set is locked" "$err" >/dev/null 2>&1; then
|
|
404
|
+
if spawn_task "$id" "$HOME_DIR" "$PROJECT_DIR" > "$out" 2> "$err"; then
|
|
405
|
+
fail "post-create abort fixture $id unexpectedly succeeded after task-set publication completed"
|
|
406
|
+
fi
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
spawn_secondmate_task() {
|
|
411
|
+
local id=$1 home=$2
|
|
412
|
+
FM_GATE_REFUSE_BYPASS=1 FM_SPAWN_NO_GUARD=1 FM_HOME="$HOME_DIR" FM_ROOT_OVERRIDE="$ROOT" \
|
|
413
|
+
"$ROOT/bin/fm-spawn.sh" "$id" "$home" "sh -c 'sleep 120'" --secondmate --backend herdr
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
teardown_task() { # <id> <home>
|
|
417
|
+
local id=$1 home=$2
|
|
418
|
+
FM_GATE_REFUSE_BYPASS=1 FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
419
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
420
|
+
FM_CONFIG_OVERRIDE="$home/config" \
|
|
421
|
+
"$ROOT/bin/fm-teardown.sh" "$id" --force
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
finish_concurrent_teardown() { # <id> <status> <stdout> <stderr>
|
|
425
|
+
local id=$1 status=$2 out=$3 err=$4
|
|
426
|
+
[ "$status" -ne 0 ] || return 0
|
|
427
|
+
grep -F "session presentation lock is contended" "$err" >/dev/null 2>&1 \
|
|
428
|
+
|| fail "projected teardown $id failed unexpectedly: $(cat "$err")"
|
|
429
|
+
teardown_task "$id" "$HOME_DIR" > "$out" 2> "$err" \
|
|
430
|
+
|| fail "projected teardown $id retry failed after presentation cleanup completed: $(cat "$err")"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
normalize_meta() { # <meta>
|
|
434
|
+
sed -E \
|
|
435
|
+
-e 's|^window=.*$|window=<herdr-container-id>|' \
|
|
436
|
+
-e 's|^herdr_workspace_id=.*$|herdr_workspace_id=<herdr-container-id>|' \
|
|
437
|
+
-e 's|^herdr_tab_id=.*$|herdr_tab_id=<herdr-container-id>|' \
|
|
438
|
+
-e 's|^herdr_pane_id=.*$|herdr_pane_id=<herdr-container-id>|' \
|
|
439
|
+
-e 's|^spawn_gen=.*$|spawn_gen=<spawn-incarnation>|' \
|
|
440
|
+
"$1"
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
log_line_count() { wc -l < "$HERDR_CALL_LOG" | tr -d '[:space:]'; }
|
|
444
|
+
|
|
445
|
+
projection_labels_from_log() { # <start-line>
|
|
446
|
+
local start=$1
|
|
447
|
+
sed -n "$((start + 1)),\$p" "$HERDR_CALL_LOG" | awk -F '\t' '
|
|
448
|
+
$1 == "workspace" && $2 == "create" {
|
|
449
|
+
for (i = 1; i < NF; i += 1) {
|
|
450
|
+
if ($i == "--label" && $(i + 1) ~ /^└ /) {
|
|
451
|
+
print $(i + 1)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
'
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
session_presentation_lock_path() {
|
|
459
|
+
PATH="$FAKEBIN:$PATH" HERDR_SESSION="$HERDR_LAB_SESSION" bash -c '
|
|
460
|
+
. "$0/bin/backends/herdr.sh"
|
|
461
|
+
fm_backend_herdr_presentation_session_lock_path "$1"
|
|
462
|
+
' "$ROOT" "$HERDR_LAB_SESSION"
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
assert_no_ordering_lifecycle_calls_since() { # <line-count> <case-name>
|
|
466
|
+
local start=$1 name=$2 calls
|
|
467
|
+
calls=$(sed -n "$((start + 1)),\$p" "$HERDR_CALL_LOG")
|
|
468
|
+
if printf '%s\n' "$calls" | grep -E $'^(workspace\t(close|rename)|tab\tclose|session\t(stop|delete)|server)' >/dev/null 2>&1; then
|
|
469
|
+
fail "$name introduced a workspace/tab/session lifecycle or label mutation call"
|
|
470
|
+
fi
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
assert_no_projection_mutation_since() { # <line-count> <case-name>
|
|
474
|
+
local start=$1 name=$2 calls
|
|
475
|
+
calls=$(sed -n "$((start + 1)),\$p" "$HERDR_CALL_LOG")
|
|
476
|
+
if printf '%s\n' "$calls" | grep -E $'^(workspace\t(create|close|rename)|tab\t(create|close)|pane\tclose|session\t(stop|delete)|server)' >/dev/null 2>&1; then
|
|
477
|
+
fail "$name performed a create, close, delete, rename, or lifecycle call during recovery inspection"
|
|
478
|
+
fi
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
HOME_DIR="$TMP_ROOT/home"
|
|
482
|
+
PROJECT_DIR="$TMP_ROOT/project"
|
|
483
|
+
mkdir -p "$HOME_DIR/state" "$HOME_DIR/config" \
|
|
484
|
+
"$HOME_DIR/data/anchor" "$HOME_DIR/data/shape" \
|
|
485
|
+
"$HOME_DIR/data/order-a" "$HOME_DIR/data/order-b" \
|
|
486
|
+
"$HOME_DIR/data/order-fail" "$HOME_DIR/data/fm-hibit-resume-r1" \
|
|
487
|
+
"$HOME_DIR/data/wheelhouse-healing-r1"
|
|
488
|
+
mkdir -p "$HOME_DIR/data/active-seeded" "$HOME_DIR/data/abort-a" "$HOME_DIR/data/abort-b" \
|
|
489
|
+
"$HOME_DIR/data/lock-contended" "$HOME_DIR/data/default-on"
|
|
490
|
+
touch "$HOME_DIR/state/.last-watcher-beat"
|
|
491
|
+
# Presentation spaces are on by default, so the flat baseline below opts out
|
|
492
|
+
# explicitly; the projected cases each restate the setting they exercise.
|
|
493
|
+
printf 'off\n' > "$HOME_DIR/config/herdr-presentation-spaces"
|
|
494
|
+
printf 'Projection anchor fixture.\n' > "$HOME_DIR/data/anchor/brief.md"
|
|
495
|
+
printf 'Projection E2E fixture.\n' > "$HOME_DIR/data/shape/brief.md"
|
|
496
|
+
printf 'Projection ordering fixture A.\n' > "$HOME_DIR/data/order-a/brief.md"
|
|
497
|
+
printf 'Projection ordering fixture B.\n' > "$HOME_DIR/data/order-b/brief.md"
|
|
498
|
+
printf 'Projection ordering failure fixture.\n' > "$HOME_DIR/data/order-fail/brief.md"
|
|
499
|
+
printf 'Hi Bit-style projection restart fixture.\n' > "$HOME_DIR/data/fm-hibit-resume-r1/brief.md"
|
|
500
|
+
printf 'Wheelhouse-style projection restart fixture.\n' > "$HOME_DIR/data/wheelhouse-healing-r1/brief.md"
|
|
501
|
+
printf 'Projection active seeded fixture.\n' > "$HOME_DIR/data/active-seeded/brief.md"
|
|
502
|
+
printf 'Projection abort fixture A.\n' > "$HOME_DIR/data/abort-a/brief.md"
|
|
503
|
+
printf 'Projection abort fixture B.\n' > "$HOME_DIR/data/abort-b/brief.md"
|
|
504
|
+
printf 'Projection lock contention fixture.\n' > "$HOME_DIR/data/lock-contended/brief.md"
|
|
505
|
+
printf 'Projection default-on fixture.\n' > "$HOME_DIR/data/default-on/brief.md"
|
|
506
|
+
make_project "$PROJECT_DIR"
|
|
507
|
+
|
|
508
|
+
# Keep one ordinary primary task live so the durable firstmate workspace is
|
|
509
|
+
# first and remains present while disposable workers are projected around it.
|
|
510
|
+
spawn_task anchor "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/anchor.out" 2> "$TMP_ROOT/anchor.err" \
|
|
511
|
+
|| fail "opted-out anchor spawn failed: $(cat "$TMP_ROOT/anchor.err")"
|
|
512
|
+
ANCHOR_META="$HOME_DIR/state/anchor.meta"
|
|
513
|
+
remember_meta_worktree "$ANCHOR_META" >/dev/null
|
|
514
|
+
FIRSTMATE_WSID=$(grep '^herdr_workspace_id=' "$ANCHOR_META" | cut -d= -f2-)
|
|
515
|
+
[ -n "$FIRSTMATE_WSID" ] || fail "anchor metadata did not record the firstmate workspace"
|
|
516
|
+
|
|
517
|
+
# The same task id and project run once opted out and once projected, so
|
|
518
|
+
# Treehouse commands and metadata can be compared after normalizing endpoint
|
|
519
|
+
# IDs and the deliberately fresh per-spawn incarnation.
|
|
520
|
+
: > "$TREEHOUSE_CALL_LOG"
|
|
521
|
+
OFF_HERDR_START=$(log_line_count)
|
|
522
|
+
OFF_MOVE_START=$(wc -l < "$MOVE_CALL_LOG" | tr -d '[:space:]')
|
|
523
|
+
spawn_task shape "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/off.out" 2> "$TMP_ROOT/off.err" \
|
|
524
|
+
|| fail "opted-out spawn failed: $(cat "$TMP_ROOT/off.err")"
|
|
525
|
+
OFF_HERDR_END=$(log_line_count)
|
|
526
|
+
OFF_META="$TMP_ROOT/off.meta"
|
|
527
|
+
cp "$HOME_DIR/state/shape.meta" "$OFF_META"
|
|
528
|
+
OFF_WT=$(remember_meta_worktree "$OFF_META")
|
|
529
|
+
cp "$TREEHOUSE_CALL_LOG" "$TMP_ROOT/off-treehouse.log"
|
|
530
|
+
[ "$(wc -l < "$MOVE_CALL_LOG" | tr -d '[:space:]')" = "$OFF_MOVE_START" ] \
|
|
531
|
+
|| fail "opted-out spawn invoked the presentation-only workspace mover"
|
|
532
|
+
OFF_HERDR_CALLS=$(sed -n "$((OFF_HERDR_START + 1)),${OFF_HERDR_END}p" "$HERDR_CALL_LOG")
|
|
533
|
+
if printf '%s\n' "$OFF_HERDR_CALLS" | grep -E $'^(api\tschema|session\tlist)' >/dev/null 2>&1; then
|
|
534
|
+
fail "opted-out spawn added presentation-ordering capability or socket calls"
|
|
535
|
+
fi
|
|
536
|
+
pass "real Herdr lab: an opted-out spawn retains the Stage 1 Herdr command sequence with zero ordering calls"
|
|
537
|
+
teardown_task shape "$HOME_DIR" > "$TMP_ROOT/off-teardown.out" 2> "$TMP_ROOT/off-teardown.err" \
|
|
538
|
+
|| fail "opted-out teardown failed: $(cat "$TMP_ROOT/off-teardown.err")"
|
|
539
|
+
|
|
540
|
+
# A home that configured nothing at all follows the version floor: it is
|
|
541
|
+
# projected on a release at or above it, and takes the ordinary flat layout with
|
|
542
|
+
# one naming warning below it. The only difference from the opted-out spawn
|
|
543
|
+
# above is the removed file, so this case is the floor's live end-user proof on
|
|
544
|
+
# whichever Herdr this lab is running.
|
|
545
|
+
rm -f "$HOME_DIR/config/herdr-presentation-spaces"
|
|
546
|
+
FLOOR_STATUS=$(lab status --json) || fail 'could not read the lab release for the presentation floor'
|
|
547
|
+
FLOOR_VERSION=$(printf '%s' "$FLOOR_STATUS" | jq -r 'if .server.running then .server.version else .client.version end')
|
|
548
|
+
FLOOR_PROTOCOL=$(printf '%s' "$FLOOR_STATUS" | jq -r 'if .server.running then .server.protocol else .client.protocol end')
|
|
549
|
+
FLOOR_VERDICT=$(bash -c '
|
|
550
|
+
. "$0/bin/backends/herdr.sh"
|
|
551
|
+
status=0
|
|
552
|
+
fm_backend_herdr_release_floor_verdict "$1" "$2" || status=$?
|
|
553
|
+
printf "%s\n" "$status"
|
|
554
|
+
' "$ROOT" "$FLOOR_PROTOCOL" "$FLOOR_VERSION")
|
|
555
|
+
[ "$FLOOR_VERDICT" = 0 ] || [ "$FLOOR_VERDICT" = 1 ] \
|
|
556
|
+
|| fail "herdr $FLOOR_VERSION protocol $FLOOR_PROTOCOL could not be classified against the presentation floor"
|
|
557
|
+
spawn_task default-on "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/default-on.out" 2> "$TMP_ROOT/default-on.err" \
|
|
558
|
+
|| fail "default-on spawn failed: $(cat "$TMP_ROOT/default-on.err")"
|
|
559
|
+
DEFAULT_ON_META="$HOME_DIR/state/default-on.meta"
|
|
560
|
+
remember_meta_worktree "$DEFAULT_ON_META" >/dev/null
|
|
561
|
+
DEFAULT_ON_JOURNAL="$HOME_DIR/state/default-on.herdr-presentation"
|
|
562
|
+
DEFAULT_ON_WSID=$(grep '^herdr_workspace_id=' "$DEFAULT_ON_META" | cut -d= -f2-)
|
|
563
|
+
if [ "$FLOOR_VERDICT" = 0 ]; then
|
|
564
|
+
[ -f "$DEFAULT_ON_JOURNAL" ] \
|
|
565
|
+
|| fail "an unconfigured home did not publish a presentation journal on supported herdr $FLOOR_VERSION"
|
|
566
|
+
DEFAULT_ON_TOKEN=$(grep '^projection_id=' "$DEFAULT_ON_JOURNAL" | cut -d= -f2-)
|
|
567
|
+
[ -n "$DEFAULT_ON_WSID" ] && [ "$DEFAULT_ON_WSID" != "$FIRSTMATE_WSID" ] \
|
|
568
|
+
|| fail "an unconfigured home reused the flat firstmate workspace instead of projecting"
|
|
569
|
+
DEFAULT_ON_LABEL=$(lab workspace get "$DEFAULT_ON_WSID" | jq -r '.result.workspace.label // empty')
|
|
570
|
+
[ "$DEFAULT_ON_LABEL" = "└ default-on · p:$DEFAULT_ON_TOKEN" ] \
|
|
571
|
+
|| fail "default-on projection used an unexpected workspace label: $DEFAULT_ON_LABEL"
|
|
572
|
+
pass "real Herdr lab: a home that configured nothing is projected by default on herdr $FLOOR_VERSION"
|
|
573
|
+
else
|
|
574
|
+
[ ! -e "$DEFAULT_ON_JOURNAL" ] \
|
|
575
|
+
|| fail "an unconfigured home published a presentation journal on below-floor herdr $FLOOR_VERSION"
|
|
576
|
+
[ "$DEFAULT_ON_WSID" = "$FIRSTMATE_WSID" ] \
|
|
577
|
+
|| fail "an unconfigured home did not land in the flat firstmate workspace on below-floor herdr $FLOOR_VERSION (got '${DEFAULT_ON_WSID:-<empty>}')"
|
|
578
|
+
grep -q "$FLOOR_VERSION" "$TMP_ROOT/default-on.err" \
|
|
579
|
+
|| fail "the below-floor fallback did not name herdr $FLOOR_VERSION: $(cat "$TMP_ROOT/default-on.err")"
|
|
580
|
+
pass "real Herdr lab: a home that configured nothing falls back flat on below-floor herdr $FLOOR_VERSION with one naming warning"
|
|
581
|
+
fi
|
|
582
|
+
teardown_task default-on "$HOME_DIR" > "$TMP_ROOT/default-on-teardown.out" 2> "$TMP_ROOT/default-on-teardown.err" \
|
|
583
|
+
|| fail "default-on teardown failed: $(cat "$TMP_ROOT/default-on-teardown.err")"
|
|
584
|
+
if [ "$FLOOR_VERDICT" = 0 ] && lab workspace get "$DEFAULT_ON_WSID" >/dev/null 2>&1; then
|
|
585
|
+
fail "default-on teardown left its disposable workspace behind"
|
|
586
|
+
fi
|
|
587
|
+
# The ordering scenarios below read the whole move log cumulatively against the
|
|
588
|
+
# projected workspaces that are still live, so this retired one starts them clean.
|
|
589
|
+
: > "$MOVE_CALL_LOG"
|
|
590
|
+
|
|
591
|
+
SECOND_ONE_OUT=$(lab workspace create --cwd "$PROJECT_DIR" --label 2ndmate-alpha --no-focus) \
|
|
592
|
+
|| fail "could not create the first secondmate presentation fixture"
|
|
593
|
+
SECOND_TWO_OUT=$(lab workspace create --cwd "$PROJECT_DIR" --label 2ndmate-bravo --focus) \
|
|
594
|
+
|| fail "could not create the focused secondmate presentation fixture"
|
|
595
|
+
SECOND_ONE_WSID=$(printf '%s' "$SECOND_ONE_OUT" | jq -r '.result.workspace.workspace_id // empty')
|
|
596
|
+
SECOND_TWO_WSID=$(printf '%s' "$SECOND_TWO_OUT" | jq -r '.result.workspace.workspace_id // empty')
|
|
597
|
+
SECOND_TWO_TAB=$(printf '%s' "$SECOND_TWO_OUT" | jq -r '.result.tab.tab_id // empty')
|
|
598
|
+
SECOND_TWO_PANE=$(printf '%s' "$SECOND_TWO_OUT" | jq -r '.result.root_pane.pane_id // empty')
|
|
599
|
+
[ -n "$SECOND_ONE_WSID" ] && [ -n "$SECOND_TWO_WSID" ] && [ -n "$SECOND_TWO_TAB" ] && [ -n "$SECOND_TWO_PANE" ] \
|
|
600
|
+
|| fail "secondmate presentation fixtures returned incomplete IDs"
|
|
601
|
+
SECOND_ORDER_BEFORE=$(printf '%s\n%s\n' "$SECOND_ONE_WSID" "$SECOND_TWO_WSID")
|
|
602
|
+
CAPTAIN_FOCUS="$SECOND_TWO_WSID/$SECOND_TWO_TAB"
|
|
603
|
+
assert_focus_is "$CAPTAIN_FOCUS" "focused secondmate fixture"
|
|
604
|
+
|
|
605
|
+
: > "$TREEHOUSE_CALL_LOG"
|
|
606
|
+
# The historical presence-based opt-in was an empty file; it must still project,
|
|
607
|
+
# so no home that had already enabled the projection is turned off by the default.
|
|
608
|
+
: > "$HOME_DIR/config/herdr-presentation-spaces"
|
|
609
|
+
SHAPE_FOCUS_AUDIT_START=$(focus_audit_line_count)
|
|
610
|
+
spawn_task shape "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/on.out" 2> "$TMP_ROOT/on.err" \
|
|
611
|
+
|| fail "projected spawn failed: $(cat "$TMP_ROOT/on.err")"
|
|
612
|
+
assert_focus_is "$CAPTAIN_FOCUS" "projected spawn"
|
|
613
|
+
assert_raw_presentation_mutations_preserved_since "$SHAPE_FOCUS_AUDIT_START" "projected spawn"
|
|
614
|
+
ON_META="$TMP_ROOT/on.meta"
|
|
615
|
+
cp "$HOME_DIR/state/shape.meta" "$ON_META"
|
|
616
|
+
ON_WT=$(remember_meta_worktree "$ON_META")
|
|
617
|
+
cmp -s "$TMP_ROOT/off-treehouse.log" "$TREEHOUSE_CALL_LOG" \
|
|
618
|
+
|| fail "Treehouse command sequence changed between opted-out and projected spawns"
|
|
619
|
+
JOURNAL="$HOME_DIR/state/shape.herdr-presentation"
|
|
620
|
+
[ -f "$JOURNAL" ] || fail "projected spawn did not publish its presentation journal"
|
|
621
|
+
TOKEN=$(grep '^projection_id=' "$JOURNAL" | cut -d= -f2-)
|
|
622
|
+
[ "${#TOKEN}" -eq 22 ] || fail "projection id is not the compact 22-character encoding of 128 bits"
|
|
623
|
+
PROJECTED_WSID=$(grep '^herdr_workspace_id=' "$ON_META" | cut -d= -f2-)
|
|
624
|
+
PROJECTED_TAB=$(grep '^herdr_tab_id=' "$ON_META" | cut -d= -f2-)
|
|
625
|
+
PROJECTED_PANE=$(grep '^herdr_pane_id=' "$ON_META" | cut -d= -f2-)
|
|
626
|
+
PROJECTED_INFO=$(lab workspace get "$PROJECTED_WSID") || fail "could not inspect the projected workspace"
|
|
627
|
+
PROJECTED_LABEL=$(printf '%s' "$PROJECTED_INFO" | jq -r '.result.workspace.label // empty')
|
|
628
|
+
[ "$PROJECTED_LABEL" = "└ shape · p:$TOKEN" ] \
|
|
629
|
+
|| fail "projected workspace label did not use the corner format with full token: $PROJECTED_LABEL"
|
|
630
|
+
PROJECTED_TABS=$(lab tab list --workspace "$PROJECTED_WSID")
|
|
631
|
+
PROJECTED_PANES=$(lab pane list --workspace "$PROJECTED_WSID")
|
|
632
|
+
[ "$(printf '%s' "$PROJECTED_TABS" | jq -r '.result.tabs | length')" = 1 ] \
|
|
633
|
+
|| fail "projected workspace retained a seeded or placeholder tab"
|
|
634
|
+
[ "$(printf '%s' "$PROJECTED_PANES" | jq -r '.result.panes | length')" = 1 ] \
|
|
635
|
+
|| fail "projected workspace did not contain exactly one task pane"
|
|
636
|
+
printf '%s' "$PROJECTED_TABS" | jq -e --arg tab "$PROJECTED_TAB" \
|
|
637
|
+
'.result.tabs[0].tab_id == $tab and .result.tabs[0].label == "fm-shape"' >/dev/null 2>&1 \
|
|
638
|
+
|| fail "projected workspace's only tab was not the normal fm-shape task tab"
|
|
639
|
+
printf '%s' "$PROJECTED_PANES" | jq -e --arg pane "$PROJECTED_PANE" \
|
|
640
|
+
'.result.panes[0].pane_id == $pane' >/dev/null 2>&1 \
|
|
641
|
+
|| fail "projected workspace's only pane was not the exact recorded task pane"
|
|
642
|
+
SECOND_TWO_INFO=$(lab workspace get "$SECOND_TWO_WSID") || fail "focused secondmate disappeared during projected create"
|
|
643
|
+
[ "$(printf '%s' "$SECOND_TWO_INFO" | jq -r '.result.workspace.focused')" = true ] \
|
|
644
|
+
|| fail "projected create or workspace.move stole focus from the captain's current space"
|
|
645
|
+
pass "real Herdr lab: every projected create, task-tab create, seeded prune, and move preserves active workspace and tab"
|
|
646
|
+
|
|
647
|
+
mkdir -p "$ACTIVE_SEEDED_CONTROL"
|
|
648
|
+
printf '%s\n' requested > "$ACTIVE_SEEDED_CONTROL/stage"
|
|
649
|
+
ACTIVE_SEEDED_START=$(log_line_count)
|
|
650
|
+
ACTIVE_SEEDED_FOCUS_START=$(focus_audit_line_count)
|
|
651
|
+
if spawn_task active-seeded "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/active-seeded.out" 2> "$TMP_ROOT/active-seeded.err"; then
|
|
652
|
+
fail "active seeded-tab projection should refuse the prune"
|
|
653
|
+
fi
|
|
654
|
+
grep -F "target is the captain's active tab" "$TMP_ROOT/active-seeded.err" >/dev/null 2>&1 \
|
|
655
|
+
|| fail "active seeded-tab projection did not report its exact refusal"
|
|
656
|
+
ACTIVE_SEEDED_WSID=$(cat "$ACTIVE_SEEDED_CONTROL/workspace")
|
|
657
|
+
ACTIVE_SEEDED_TAB=$(cat "$ACTIVE_SEEDED_CONTROL/seeded-tab")
|
|
658
|
+
ACTIVE_SEEDED_PANE=$(cat "$ACTIVE_SEEDED_CONTROL/seeded-pane")
|
|
659
|
+
ACTIVE_SEEDED_TASK_PANE=$(cat "$ACTIVE_SEEDED_CONTROL/task-pane")
|
|
660
|
+
ACTIVE_SEEDED_FOCUS="$ACTIVE_SEEDED_WSID/$ACTIVE_SEEDED_TAB"
|
|
661
|
+
assert_focus_is "$ACTIVE_SEEDED_FOCUS" "active seeded-tab prune refusal"
|
|
662
|
+
assert_raw_presentation_mutations_preserved_since "$ACTIVE_SEEDED_FOCUS_START" "active seeded-tab prune refusal"
|
|
663
|
+
lab pane get "$ACTIVE_SEEDED_PANE" >/dev/null 2>&1 \
|
|
664
|
+
|| fail "active seeded-tab refusal removed the exact seeded pane"
|
|
665
|
+
if lab pane get "$ACTIVE_SEEDED_TASK_PANE" >/dev/null 2>&1; then
|
|
666
|
+
fail "active seeded-tab failure did not abort-clean the non-active task pane"
|
|
667
|
+
fi
|
|
668
|
+
sed -n "$((ACTIVE_SEEDED_FOCUS_START + 1)),\$p" "$FOCUS_AUDIT_LOG" | awk -F '\t' -v focus="$ACTIVE_SEEDED_FOCUS" -v pane="$ACTIVE_SEEDED_PANE" '
|
|
669
|
+
$1 == "seeded-prune-refusal" && $2 == focus && $3 == focus && $4 == pane { found = 1 }
|
|
670
|
+
END { exit(found ? 0 : 1) }
|
|
671
|
+
' || fail "guarded lab did not observe exact focus across the active seeded-tab refusal"
|
|
672
|
+
if sed -n "$((ACTIVE_SEEDED_START + 1)),\$p" "$HERDR_CALL_LOG" | grep -F $'pane\tclose\t'"$ACTIVE_SEEDED_PANE" >/dev/null 2>&1; then
|
|
673
|
+
fail "active seeded-tab refusal closed the exact active pane"
|
|
674
|
+
fi
|
|
675
|
+
lab tab focus "$SECOND_TWO_TAB" >/dev/null || fail "could not restore the captured captain tab after the active seeded-tab fixture"
|
|
676
|
+
assert_focus_is "$CAPTAIN_FOCUS" "active seeded-tab fixture restoration"
|
|
677
|
+
rm -rf "$ACTIVE_SEEDED_CONTROL"
|
|
678
|
+
ACTIVE_SEEDED_CLEANUP_FOCUS_START=$(focus_audit_line_count)
|
|
679
|
+
ACTIVE_SEEDED_LOCK=$(session_presentation_lock_path) \
|
|
680
|
+
|| fail "could not resolve the session presentation lock for active-seeded cleanup"
|
|
681
|
+
PATH="$FAKEBIN:$PATH" FM_HOME="$HOME_DIR" bash -c '
|
|
682
|
+
. "$0/bin/fm-wake-lib.sh"
|
|
683
|
+
. "$0/bin/backends/herdr.sh"
|
|
684
|
+
lock=$1
|
|
685
|
+
fm_lock_acquire_wait "$lock"
|
|
686
|
+
fm_backend_herdr_projection_cleanup_exact "$2" "$3" "$4"
|
|
687
|
+
fm_lock_release "$lock"
|
|
688
|
+
' "$ROOT" "$ACTIVE_SEEDED_LOCK" "$HERDR_LAB_SESSION" "$ACTIVE_SEEDED_TASK_PANE" "$ACTIVE_SEEDED_PANE"
|
|
689
|
+
assert_focus_is "$CAPTAIN_FOCUS" "active seeded-tab fixture cleanup"
|
|
690
|
+
assert_cleanup_focus_preserved "$ACTIVE_SEEDED_CLEANUP_FOCUS_START" "$ACTIVE_SEEDED_PANE" "$CAPTAIN_FOCUS"
|
|
691
|
+
rm -f "$HOME_DIR/state/active-seeded.herdr-presentation"
|
|
692
|
+
pass "real Herdr lab: active seeded-tab pruning refuses the exact pane and preserves exact focus"
|
|
693
|
+
|
|
694
|
+
LOCK_CONTENTION_READY="$TMP_ROOT/lock-contention-ready"
|
|
695
|
+
LOCK_CONTENTION_RELEASE="$TMP_ROOT/lock-contention-release"
|
|
696
|
+
LOCK_CONTENTION_PATH=$(session_presentation_lock_path) \
|
|
697
|
+
|| fail "could not resolve the session presentation lock for contention"
|
|
698
|
+
ROOT="$ROOT" READY="$LOCK_CONTENTION_READY" RELEASE="$LOCK_CONTENTION_RELEASE" \
|
|
699
|
+
LOCK="$LOCK_CONTENTION_PATH" bash -c '
|
|
700
|
+
. "$ROOT/bin/fm-wake-lib.sh"
|
|
701
|
+
fm_lock_try_acquire "$LOCK" || exit 1
|
|
702
|
+
: > "$READY"
|
|
703
|
+
while [ ! -e "$RELEASE" ]; do sleep 0.05; done
|
|
704
|
+
fm_lock_release "$LOCK"
|
|
705
|
+
' &
|
|
706
|
+
LOCK_CONTENTION_OWNER_PID=$!
|
|
707
|
+
while [ ! -e "$LOCK_CONTENTION_READY" ] && kill -0 "$LOCK_CONTENTION_OWNER_PID" 2>/dev/null; do sleep 0.01; done
|
|
708
|
+
[ -e "$LOCK_CONTENTION_READY" ] || fail "could not hold the guarded lab presentation lock"
|
|
709
|
+
LOCK_CONTENTION_START=$(log_line_count)
|
|
710
|
+
LOCK_CONTENTION_FOCUS_START=$(focus_audit_line_count)
|
|
711
|
+
LOCK_CONTENTION_MOVE_START=$(wc -l < "$MOVE_CALL_LOG" | tr -d '[:space:]')
|
|
712
|
+
if spawn_task lock-contended "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/lock-contended.out" 2> "$TMP_ROOT/lock-contended.err"; then
|
|
713
|
+
LOCK_CONTENTION_STATUS=0
|
|
714
|
+
else
|
|
715
|
+
LOCK_CONTENTION_STATUS=$?
|
|
716
|
+
fi
|
|
717
|
+
: > "$LOCK_CONTENTION_RELEASE"
|
|
718
|
+
wait "$LOCK_CONTENTION_OWNER_PID" || fail "guarded lab presentation lock owner failed"
|
|
719
|
+
LOCK_CONTENTION_OWNER_PID=
|
|
720
|
+
[ "$LOCK_CONTENTION_STATUS" -eq 0 ] \
|
|
721
|
+
|| fail "bounded presentation lock contention did not fall back to a successful flat spawn: $(cat "$TMP_ROOT/lock-contended.err")"
|
|
722
|
+
grep -F "presentation focus lock unavailable; using the ordinary flat layout without projection" "$TMP_ROOT/lock-contended.err" >/dev/null 2>&1 \
|
|
723
|
+
|| fail "bounded presentation lock contention did not warn about flat fallback"
|
|
724
|
+
LOCK_CONTENTION_META="$HOME_DIR/state/lock-contended.meta"
|
|
725
|
+
remember_meta_worktree "$LOCK_CONTENTION_META" >/dev/null
|
|
726
|
+
LOCK_CONTENTION_WSID=$(grep '^herdr_workspace_id=' "$LOCK_CONTENTION_META" | cut -d= -f2-)
|
|
727
|
+
[ "$LOCK_CONTENTION_WSID" = "$FIRSTMATE_WSID" ] \
|
|
728
|
+
|| fail "bounded lock contention did not use the ordinary flat firstmate workspace"
|
|
729
|
+
[ ! -e "$HOME_DIR/state/lock-contended.herdr-presentation" ] \
|
|
730
|
+
|| fail "bounded lock contention published a projection journal"
|
|
731
|
+
LOCK_CONTENTION_CALLS=$(sed -n "$((LOCK_CONTENTION_START + 1)),\$p" "$HERDR_CALL_LOG")
|
|
732
|
+
# session list is required to resolve the shared session lock path before the
|
|
733
|
+
# bounded acquire attempt; it must not unlock projection create or move.
|
|
734
|
+
if printf '%s\n' "$LOCK_CONTENTION_CALLS" | grep -E $'^(workspace\tcreate|pane\tclose|api\tschema)' >/dev/null 2>&1; then
|
|
735
|
+
fail "bounded lock contention performed an unlocked projection mutation or ordering capability call"
|
|
736
|
+
fi
|
|
737
|
+
[ "$(wc -l < "$MOVE_CALL_LOG" | tr -d '[:space:]')" = "$LOCK_CONTENTION_MOVE_START" ] \
|
|
738
|
+
|| fail "bounded lock contention invoked workspace.move"
|
|
739
|
+
assert_focus_is "$CAPTAIN_FOCUS" "bounded presentation lock flat fallback"
|
|
740
|
+
assert_raw_presentation_mutations_preserved_since "$LOCK_CONTENTION_FOCUS_START" "bounded presentation lock flat fallback"
|
|
741
|
+
teardown_task lock-contended "$HOME_DIR" > "$TMP_ROOT/lock-contended-teardown.out" 2> "$TMP_ROOT/lock-contended-teardown.err" \
|
|
742
|
+
|| fail "flat lock-contention fixture teardown failed"
|
|
743
|
+
assert_focus_is "$CAPTAIN_FOCUS" "bounded presentation lock flat fallback teardown"
|
|
744
|
+
pass "real Herdr lab: bounded lock contention warns and falls back flat without projection or focus drift"
|
|
745
|
+
PROJECTION_ORDER_START=$(log_line_count)
|
|
746
|
+
|
|
747
|
+
[ "$OFF_WT" = "$ON_WT" ] || fail "Treehouse did not reuse the same fixture worktree, so byte comparison is inconclusive"
|
|
748
|
+
normalize_meta "$OFF_META" > "$TMP_ROOT/off.meta.normalized"
|
|
749
|
+
normalize_meta "$ON_META" > "$TMP_ROOT/on.meta.normalized"
|
|
750
|
+
cmp -s "$TMP_ROOT/off.meta.normalized" "$TMP_ROOT/on.meta.normalized" \
|
|
751
|
+
|| fail "metadata changed beyond Herdr container IDs between opted-out and projected paths"
|
|
752
|
+
|
|
753
|
+
# Two real primary spawns begin concurrently.
|
|
754
|
+
# The fresh-spawn task-set lock may fail closed for one while the other
|
|
755
|
+
# publishes, in which case retry it only after the lock owner has completed.
|
|
756
|
+
# Their final relative order must match Herdr's actual serialized create order,
|
|
757
|
+
# rather than a task-name or priority guess.
|
|
758
|
+
CONCURRENT_FOCUS_AUDIT_START=$(focus_audit_line_count)
|
|
759
|
+
spawn_task order-a "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/order-a.out" 2> "$TMP_ROOT/order-a.err" &
|
|
760
|
+
ORDER_A_PID=$!
|
|
761
|
+
spawn_task order-b "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/order-b.out" 2> "$TMP_ROOT/order-b.err" &
|
|
762
|
+
ORDER_B_PID=$!
|
|
763
|
+
if wait "$ORDER_A_PID"; then ORDER_A_STATUS=0; else ORDER_A_STATUS=$?; fi
|
|
764
|
+
if wait "$ORDER_B_PID"; then ORDER_B_STATUS=0; else ORDER_B_STATUS=$?; fi
|
|
765
|
+
finish_concurrent_spawn order-a "$ORDER_A_STATUS" "$TMP_ROOT/order-a.out" "$TMP_ROOT/order-a.err"
|
|
766
|
+
finish_concurrent_spawn order-b "$ORDER_B_STATUS" "$TMP_ROOT/order-b.out" "$TMP_ROOT/order-b.err"
|
|
767
|
+
assert_focus_is "$CAPTAIN_FOCUS" "concurrent projected spawns"
|
|
768
|
+
assert_raw_presentation_mutations_preserved_since "$CONCURRENT_FOCUS_AUDIT_START" "concurrent projected spawns"
|
|
769
|
+
ORDER_A_META="$HOME_DIR/state/order-a.meta"
|
|
770
|
+
ORDER_B_META="$HOME_DIR/state/order-b.meta"
|
|
771
|
+
remember_meta_worktree "$ORDER_A_META" >/dev/null
|
|
772
|
+
remember_meta_worktree "$ORDER_B_META" >/dev/null
|
|
773
|
+
|
|
774
|
+
ORDER_LIST=$(lab workspace list) || fail "could not inspect concurrent presentation ordering"
|
|
775
|
+
CREATED_LABELS=$(projection_labels_from_log "$PROJECTION_ORDER_START")
|
|
776
|
+
EXPECTED_LABELS=$(printf 'firstmate\n%s\n%s\n2ndmate-alpha\n2ndmate-bravo' "$PROJECTED_LABEL" "$CREATED_LABELS")
|
|
777
|
+
ACTUAL_LABELS=$(printf '%s' "$ORDER_LIST" | jq -r '.result.workspaces[].label')
|
|
778
|
+
[ "$ACTUAL_LABELS" = "$EXPECTED_LABELS" ] || fail "workspace order was not firstmate, stable primary block, secondmates: $ACTUAL_LABELS"
|
|
779
|
+
PRIMARY_IDS=$(printf '%s' "$ORDER_LIST" | jq -r '
|
|
780
|
+
.result.workspaces[]
|
|
781
|
+
| select((.label | startswith("└ ")) or (.label | startswith("firstmate/")))
|
|
782
|
+
| .workspace_id
|
|
783
|
+
')
|
|
784
|
+
MOVE_TARGETS=$(cut -f2 "$MOVE_CALL_LOG")
|
|
785
|
+
[ "$MOVE_TARGETS" = "$PRIMARY_IDS" ] \
|
|
786
|
+
|| fail "workspace.move targeted something other than each exact current projected-create id"
|
|
787
|
+
MOVE_INDEXES=$(cut -f3 "$MOVE_CALL_LOG")
|
|
788
|
+
[ "$MOVE_INDEXES" = $'1\n2\n3' ] \
|
|
789
|
+
|| fail "concurrent primary workers did not append stably to the contiguous block: $MOVE_INDEXES"
|
|
790
|
+
SECOND_ORDER_AFTER=$(printf '%s' "$ORDER_LIST" | jq -r '.result.workspaces[] | select(.label | startswith("2ndmate-")) | .workspace_id')
|
|
791
|
+
[ "$SECOND_ORDER_AFTER" = "$SECOND_ORDER_BEFORE" ] \
|
|
792
|
+
|| fail "primary workspace ordering changed secondmate relative order"
|
|
793
|
+
[ "$(lab workspace get "$SECOND_TWO_WSID" | jq -r '.result.workspace.focused')" = true ] \
|
|
794
|
+
|| fail "concurrent primary workspace ordering stole focus"
|
|
795
|
+
assert_no_ordering_lifecycle_calls_since "$PROJECTION_ORDER_START" "successful presentation ordering"
|
|
796
|
+
pass "real Herdr lab: concurrent primary workers form one stable contiguous block without active workspace/tab drift"
|
|
797
|
+
|
|
798
|
+
# Force only the raw move transport to fail after a safe projected create.
|
|
799
|
+
# The spawn must remain successful in Herdr's default appended order, with its
|
|
800
|
+
# exact task pane alive and no ordering-triggered cleanup.
|
|
801
|
+
FAIL_MOVER="$TMP_ROOT/fail-workspace-mover"
|
|
802
|
+
cat > "$FAIL_MOVER" <<'SH'
|
|
803
|
+
#!/usr/bin/env bash
|
|
804
|
+
exit 9
|
|
805
|
+
SH
|
|
806
|
+
chmod +x "$FAIL_MOVER"
|
|
807
|
+
FAIL_START=$(log_line_count)
|
|
808
|
+
FAIL_FOCUS_AUDIT_START=$(focus_audit_line_count)
|
|
809
|
+
FM_BACKEND_HERDR_WORKSPACE_MOVER="$FAIL_MOVER" \
|
|
810
|
+
spawn_task order-fail "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/order-fail.out" 2> "$TMP_ROOT/order-fail.err" \
|
|
811
|
+
|| fail "move-failure projected spawn should still succeed: $(cat "$TMP_ROOT/order-fail.err")"
|
|
812
|
+
assert_focus_is "$CAPTAIN_FOCUS" "failed presentation ordering"
|
|
813
|
+
assert_raw_presentation_mutations_preserved_since "$FAIL_FOCUS_AUDIT_START" "failed presentation ordering"
|
|
814
|
+
grep -F "workspace move failed or had an ambiguous response" "$TMP_ROOT/order-fail.err" >/dev/null 2>&1 \
|
|
815
|
+
|| fail "forced workspace.move failure did not report only the best-effort warning"
|
|
816
|
+
ORDER_FAIL_META="$HOME_DIR/state/order-fail.meta"
|
|
817
|
+
remember_meta_worktree "$ORDER_FAIL_META" >/dev/null
|
|
818
|
+
ORDER_FAIL_WSID=$(grep '^herdr_workspace_id=' "$ORDER_FAIL_META" | cut -d= -f2-)
|
|
819
|
+
ORDER_FAIL_PANE=$(grep '^herdr_pane_id=' "$ORDER_FAIL_META" | cut -d= -f2-)
|
|
820
|
+
FAIL_LIST=$(lab workspace list) || fail "could not inspect the move-failure fallback"
|
|
821
|
+
[ "$(printf '%s' "$FAIL_LIST" | jq -r '.result.workspaces[-1].workspace_id')" = "$ORDER_FAIL_WSID" ] \
|
|
822
|
+
|| fail "workspace.move failure did not leave the safe worker in Herdr's default appended order"
|
|
823
|
+
lab pane get "$ORDER_FAIL_PANE" >/dev/null 2>&1 \
|
|
824
|
+
|| fail "workspace.move failure cleaned up the safely-created task pane"
|
|
825
|
+
FAIL_CLOSED_PANES=$(sed -n "$((FAIL_START + 1)),\$p" "$HERDR_CALL_LOG" | awk -F '\t' '$1 == "pane" && $2 == "close" { print $3 }')
|
|
826
|
+
[ "$(printf '%s\n' "$FAIL_CLOSED_PANES" | awk 'NF { n += 1 } END { print n + 0 }')" = 1 ] \
|
|
827
|
+
|| fail "move-failure spawn performed a pane close beyond the normal seeded-pane prune"
|
|
828
|
+
[ "$FAIL_CLOSED_PANES" != "$ORDER_FAIL_PANE" ] \
|
|
829
|
+
|| fail "move-failure spawn closed its exact task pane"
|
|
830
|
+
assert_no_ordering_lifecycle_calls_since "$FAIL_START" "failed presentation ordering"
|
|
831
|
+
pass "real Herdr lab: forced workspace.move failure leaves a successful worker in default order with a warning and no cleanup"
|
|
832
|
+
|
|
833
|
+
mkdir -p "$POST_CREATE_ABORT_CONTROL"
|
|
834
|
+
ABORT_START=$(log_line_count)
|
|
835
|
+
ABORT_FOCUS_START=$(focus_audit_line_count)
|
|
836
|
+
spawn_task abort-a "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/abort-a.out" 2> "$TMP_ROOT/abort-a.err" &
|
|
837
|
+
ABORT_A_PID=$!
|
|
838
|
+
spawn_task abort-b "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/abort-b.out" 2> "$TMP_ROOT/abort-b.err" &
|
|
839
|
+
ABORT_B_PID=$!
|
|
840
|
+
if wait "$ABORT_A_PID"; then ABORT_A_STATUS=0; else ABORT_A_STATUS=$?; fi
|
|
841
|
+
if wait "$ABORT_B_PID"; then ABORT_B_STATUS=0; else ABORT_B_STATUS=$?; fi
|
|
842
|
+
finish_concurrent_expected_abort abort-a "$ABORT_A_STATUS" "$TMP_ROOT/abort-a.out" "$TMP_ROOT/abort-a.err"
|
|
843
|
+
finish_concurrent_expected_abort abort-b "$ABORT_B_STATUS" "$TMP_ROOT/abort-b.out" "$TMP_ROOT/abort-b.err"
|
|
844
|
+
grep -F "did not yield an isolated worktree" "$TMP_ROOT/abort-a.err" >/dev/null 2>&1 \
|
|
845
|
+
|| fail "post-create abort fixture A did not reach the armed validation failure"
|
|
846
|
+
grep -F "did not yield an isolated worktree" "$TMP_ROOT/abort-b.err" >/dev/null 2>&1 \
|
|
847
|
+
|| fail "post-create abort fixture B did not reach the armed validation failure"
|
|
848
|
+
ABORT_A_PANE=$(cat "$POST_CREATE_ABORT_CONTROL/abort-a/task-pane")
|
|
849
|
+
ABORT_B_PANE=$(cat "$POST_CREATE_ABORT_CONTROL/abort-b/task-pane")
|
|
850
|
+
ABORT_SEQUENCE=$(sed -n "$((ABORT_FOCUS_START + 1)),\$p" "$FOCUS_AUDIT_LOG" | awk -F '\t' -v a="$ABORT_A_PANE" -v b="$ABORT_B_PANE" '
|
|
851
|
+
$1 == "workspace-create" && $4 ~ /^└ abort-a · p:/ { print "create-a" }
|
|
852
|
+
$1 == "workspace-create" && $4 ~ /^└ abort-b · p:/ { print "create-b" }
|
|
853
|
+
$1 == "pane-close" && $4 == a { print "close-a" }
|
|
854
|
+
$1 == "pane-close" && $4 == b { print "close-b" }
|
|
855
|
+
')
|
|
856
|
+
case "$ABORT_SEQUENCE" in
|
|
857
|
+
$'create-a\nclose-a\ncreate-b\nclose-b'|$'create-b\nclose-b\ncreate-a\nclose-a') ;;
|
|
858
|
+
*) fail "concurrent post-create abort cleanup interleaved outside the presentation lock: $ABORT_SEQUENCE" ;;
|
|
859
|
+
esac
|
|
860
|
+
ABORT_UNRESTORED=$(sed -n "$((ABORT_FOCUS_START + 1)),\$p" "$FOCUS_AUDIT_LOG" | awk -F '\t' -v a="$ABORT_A_PANE" -v b="$ABORT_B_PANE" '
|
|
861
|
+
($1 == "workspace-create" || $1 == "tab-create" || $1 == "workspace-move" || ($1 == "pane-close" && $4 != a && $4 != b)) && $2 != $3 { print }
|
|
862
|
+
')
|
|
863
|
+
[ -z "$ABORT_UNRESTORED" ] \
|
|
864
|
+
|| fail "post-create abort create, prune, or move changed exact focus: $ABORT_UNRESTORED"
|
|
865
|
+
assert_focus_is "$CAPTAIN_FOCUS" "concurrent post-create abort cleanup"
|
|
866
|
+
assert_cleanup_focus_preserved "$ABORT_FOCUS_START" "$ABORT_A_PANE" "$CAPTAIN_FOCUS"
|
|
867
|
+
assert_cleanup_focus_preserved "$ABORT_FOCUS_START" "$ABORT_B_PANE" "$CAPTAIN_FOCUS"
|
|
868
|
+
assert_no_ordering_lifecycle_calls_since "$ABORT_START" "concurrent post-create abort cleanup"
|
|
869
|
+
for ABORT_PANE in "$ABORT_A_PANE" "$ABORT_B_PANE"; do
|
|
870
|
+
if lab pane get "$ABORT_PANE" >/dev/null 2>&1; then
|
|
871
|
+
fail "serialized post-create abort cleanup left exact task pane $ABORT_PANE alive"
|
|
872
|
+
fi
|
|
873
|
+
done
|
|
874
|
+
[ ! -e "$HOME_DIR/state/abort-a.meta" ] && [ ! -e "$HOME_DIR/state/abort-b.meta" ] \
|
|
875
|
+
|| fail "post-create abort fixtures published task metadata before launch"
|
|
876
|
+
rm -rf "$POST_CREATE_ABORT_CONTROL"
|
|
877
|
+
rm -f "$HOME_DIR/state/abort-a.herdr-presentation" "$HOME_DIR/state/abort-b.herdr-presentation"
|
|
878
|
+
pass "real Herdr lab: concurrent post-create abort cleanup stays serialized with exact focus restoration"
|
|
879
|
+
|
|
880
|
+
SHAPE_CLEANUP_AUDIT_START=$(focus_audit_line_count)
|
|
881
|
+
teardown_task shape "$HOME_DIR" > "$TMP_ROOT/on-teardown.out" 2> "$TMP_ROOT/on-teardown.err" \
|
|
882
|
+
|| fail "projected teardown failed: $(cat "$TMP_ROOT/on-teardown.err")"
|
|
883
|
+
assert_focus_is "$CAPTAIN_FOCUS" "projected teardown"
|
|
884
|
+
assert_cleanup_focus_preserved "$SHAPE_CLEANUP_AUDIT_START" "$PROJECTED_PANE" "$CAPTAIN_FOCUS"
|
|
885
|
+
pass "real Herdr lab: Treehouse commands and metadata shape are byte-identical except for endpoint IDs and spawn incarnation"
|
|
886
|
+
if lab workspace get "$PROJECTED_WSID" >/dev/null 2>&1; then
|
|
887
|
+
fail "closing the exact projected task pane did not remove its last-tab workspace"
|
|
888
|
+
fi
|
|
889
|
+
lab pane get "$SECOND_TWO_PANE" >/dev/null 2>&1 \
|
|
890
|
+
|| fail "projected teardown affected the focused secondmate workspace"
|
|
891
|
+
[ ! -e "$JOURNAL" ] || fail "confirmed projected teardown did not retire its presentation journal"
|
|
892
|
+
pass "real Herdr lab: exact task-pane close removes the projected workspace with no unrestored wrong-focus interval"
|
|
893
|
+
|
|
894
|
+
teardown_task order-a "$HOME_DIR" > "$TMP_ROOT/order-a-teardown.out" 2> "$TMP_ROOT/order-a-teardown.err" &
|
|
895
|
+
ORDER_A_TEARDOWN_PID=$!
|
|
896
|
+
teardown_task order-b "$HOME_DIR" > "$TMP_ROOT/order-b-teardown.out" 2> "$TMP_ROOT/order-b-teardown.err" &
|
|
897
|
+
ORDER_B_TEARDOWN_PID=$!
|
|
898
|
+
if wait "$ORDER_A_TEARDOWN_PID"; then ORDER_A_TEARDOWN_STATUS=0; else ORDER_A_TEARDOWN_STATUS=$?; fi
|
|
899
|
+
if wait "$ORDER_B_TEARDOWN_PID"; then ORDER_B_TEARDOWN_STATUS=0; else ORDER_B_TEARDOWN_STATUS=$?; fi
|
|
900
|
+
finish_concurrent_teardown order-a "$ORDER_A_TEARDOWN_STATUS" "$TMP_ROOT/order-a-teardown.out" "$TMP_ROOT/order-a-teardown.err"
|
|
901
|
+
finish_concurrent_teardown order-b "$ORDER_B_TEARDOWN_STATUS" "$TMP_ROOT/order-b-teardown.out" "$TMP_ROOT/order-b-teardown.err"
|
|
902
|
+
assert_focus_is "$CAPTAIN_FOCUS" "concurrent projected teardowns"
|
|
903
|
+
teardown_task order-fail "$HOME_DIR" > "$TMP_ROOT/order-fail-teardown.out" 2> "$TMP_ROOT/order-fail-teardown.err" \
|
|
904
|
+
|| fail "projected ordering failure fixture teardown failed"
|
|
905
|
+
assert_focus_is "$CAPTAIN_FOCUS" "failed-order projection teardown"
|
|
906
|
+
pass "real Herdr lab: concurrent projected cleanup is serialized and leaves active workspace/tab unchanged"
|
|
907
|
+
|
|
908
|
+
# Repeat full two-worker create, order, and cleanup waves.
|
|
909
|
+
# This exercises the focus guard after the original regression sequence and
|
|
910
|
+
# proves the shared presentation lock keeps concurrent operations composable.
|
|
911
|
+
for ROUND in 1 2 3; do
|
|
912
|
+
mkdir -p "$HOME_DIR/data/focus-$ROUND-a" "$HOME_DIR/data/focus-$ROUND-b"
|
|
913
|
+
printf 'Projection focus wave %s fixture A.\n' "$ROUND" > "$HOME_DIR/data/focus-$ROUND-a/brief.md"
|
|
914
|
+
printf 'Projection focus wave %s fixture B.\n' "$ROUND" > "$HOME_DIR/data/focus-$ROUND-b/brief.md"
|
|
915
|
+
WAVE_LOG_START=$(log_line_count)
|
|
916
|
+
WAVE_FOCUS_START=$(focus_audit_line_count)
|
|
917
|
+
spawn_task "focus-$ROUND-a" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/focus-$ROUND-a.out" 2> "$TMP_ROOT/focus-$ROUND-a.err" &
|
|
918
|
+
WAVE_A_PID=$!
|
|
919
|
+
spawn_task "focus-$ROUND-b" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/focus-$ROUND-b.out" 2> "$TMP_ROOT/focus-$ROUND-b.err" &
|
|
920
|
+
WAVE_B_PID=$!
|
|
921
|
+
if wait "$WAVE_A_PID"; then WAVE_A_STATUS=0; else WAVE_A_STATUS=$?; fi
|
|
922
|
+
if wait "$WAVE_B_PID"; then WAVE_B_STATUS=0; else WAVE_B_STATUS=$?; fi
|
|
923
|
+
finish_concurrent_spawn "focus-$ROUND-a" "$WAVE_A_STATUS" "$TMP_ROOT/focus-$ROUND-a.out" "$TMP_ROOT/focus-$ROUND-a.err"
|
|
924
|
+
finish_concurrent_spawn "focus-$ROUND-b" "$WAVE_B_STATUS" "$TMP_ROOT/focus-$ROUND-b.out" "$TMP_ROOT/focus-$ROUND-b.err"
|
|
925
|
+
remember_meta_worktree "$HOME_DIR/state/focus-$ROUND-a.meta" >/dev/null
|
|
926
|
+
remember_meta_worktree "$HOME_DIR/state/focus-$ROUND-b.meta" >/dev/null
|
|
927
|
+
assert_focus_is "$CAPTAIN_FOCUS" "focus wave $ROUND concurrent spawns"
|
|
928
|
+
assert_raw_presentation_mutations_preserved_since "$WAVE_FOCUS_START" "focus wave $ROUND concurrent spawns"
|
|
929
|
+
WAVE_LABELS=$(projection_labels_from_log "$WAVE_LOG_START")
|
|
930
|
+
WAVE_EXPECTED=$(printf 'firstmate\n%s\n2ndmate-alpha\n2ndmate-bravo' "$WAVE_LABELS")
|
|
931
|
+
WAVE_ACTUAL=$(lab workspace list | jq -r '.result.workspaces[] | select(.label == "firstmate" or (.label | startswith("└ ")) or (.label | startswith("2ndmate-"))) | .label')
|
|
932
|
+
[ "$WAVE_ACTUAL" = "$WAVE_EXPECTED" ] \
|
|
933
|
+
|| fail "focus wave $ROUND lost stable contiguous ordering: $WAVE_ACTUAL"
|
|
934
|
+
WAVE_SECOND_ORDER=$(lab workspace list | jq -r '.result.workspaces[] | select(.label | startswith("2ndmate-")) | .workspace_id')
|
|
935
|
+
[ "$WAVE_SECOND_ORDER" = "$SECOND_ORDER_BEFORE" ] \
|
|
936
|
+
|| fail "focus wave $ROUND changed secondmate relative order"
|
|
937
|
+
|
|
938
|
+
teardown_task "focus-$ROUND-a" "$HOME_DIR" > "$TMP_ROOT/focus-$ROUND-a-teardown.out" 2> "$TMP_ROOT/focus-$ROUND-a-teardown.err" &
|
|
939
|
+
WAVE_A_TEARDOWN_PID=$!
|
|
940
|
+
teardown_task "focus-$ROUND-b" "$HOME_DIR" > "$TMP_ROOT/focus-$ROUND-b-teardown.out" 2> "$TMP_ROOT/focus-$ROUND-b-teardown.err" &
|
|
941
|
+
WAVE_B_TEARDOWN_PID=$!
|
|
942
|
+
if wait "$WAVE_A_TEARDOWN_PID"; then WAVE_A_TEARDOWN_STATUS=0; else WAVE_A_TEARDOWN_STATUS=$?; fi
|
|
943
|
+
if wait "$WAVE_B_TEARDOWN_PID"; then WAVE_B_TEARDOWN_STATUS=0; else WAVE_B_TEARDOWN_STATUS=$?; fi
|
|
944
|
+
finish_concurrent_teardown "focus-$ROUND-a" "$WAVE_A_TEARDOWN_STATUS" "$TMP_ROOT/focus-$ROUND-a-teardown.out" "$TMP_ROOT/focus-$ROUND-a-teardown.err"
|
|
945
|
+
finish_concurrent_teardown "focus-$ROUND-b" "$WAVE_B_TEARDOWN_STATUS" "$TMP_ROOT/focus-$ROUND-b-teardown.out" "$TMP_ROOT/focus-$ROUND-b-teardown.err"
|
|
946
|
+
assert_focus_is "$CAPTAIN_FOCUS" "focus wave $ROUND concurrent teardowns"
|
|
947
|
+
WAVE_REMAINING=$(lab workspace list | jq -r '.result.workspaces[].label')
|
|
948
|
+
[ "$WAVE_REMAINING" = $'firstmate\n2ndmate-alpha\n2ndmate-bravo' ] \
|
|
949
|
+
|| fail "focus wave $ROUND cleanup left a projected workspace behind: $WAVE_REMAINING"
|
|
950
|
+
done
|
|
951
|
+
pass "real Herdr lab: three repeated concurrent create/order/cleanup waves have zero active workspace or tab drift"
|
|
952
|
+
|
|
953
|
+
# ------------------------------------------------------------------
|
|
954
|
+
# Multi-home topology: real secondmate FM_HOME spawn paths, inheritance,
|
|
955
|
+
# concurrent cross-home waves, and session-scoped lock contention.
|
|
956
|
+
# ------------------------------------------------------------------
|
|
957
|
+
SECOND_HOME_A="$TMP_ROOT/home-2ndmate-alpha"
|
|
958
|
+
SECOND_HOME_B="$TMP_ROOT/home-2ndmate-bravo"
|
|
959
|
+
mkdir -p "$SECOND_HOME_A/state" "$SECOND_HOME_A/config" "$SECOND_HOME_A/data" \
|
|
960
|
+
"$SECOND_HOME_B/state" "$SECOND_HOME_B/config" "$SECOND_HOME_B/data"
|
|
961
|
+
printf 'alpha\n' > "$SECOND_HOME_A/.fm-secondmate-home"
|
|
962
|
+
printf 'bravo\n' > "$SECOND_HOME_B/.fm-secondmate-home"
|
|
963
|
+
touch "$SECOND_HOME_A/state/.last-watcher-beat" "$SECOND_HOME_B/state/.last-watcher-beat"
|
|
964
|
+
# Ensure the secondmate homes look like gitignored firstmate homes so inheritance
|
|
965
|
+
# may write config/herdr-presentation-spaces.
|
|
966
|
+
git -C "$SECOND_HOME_A" init -q
|
|
967
|
+
git -C "$SECOND_HOME_B" init -q
|
|
968
|
+
printf 'config/herdr-presentation-spaces\nconfig/crew-harness\nconfig/crew-dispatch.json\nconfig/backlog-backend\nconfig/backend\nconfig/startup-memory-budget\n' \
|
|
969
|
+
> "$SECOND_HOME_A/.gitignore"
|
|
970
|
+
cp "$SECOND_HOME_A/.gitignore" "$SECOND_HOME_B/.gitignore"
|
|
971
|
+
git -C "$SECOND_HOME_A" add .gitignore
|
|
972
|
+
git -C "$SECOND_HOME_B" add .gitignore
|
|
973
|
+
git -C "$SECOND_HOME_A" -c user.name='Firstmate Tests' -c user.email='tests@example.invalid' commit -qm init
|
|
974
|
+
git -C "$SECOND_HOME_B" -c user.name='Firstmate Tests' -c user.email='tests@example.invalid' commit -qm init
|
|
975
|
+
mkdir -p "$SECOND_HOME_A/bin"
|
|
976
|
+
printf '# Firstmate secondmate fixture\n' > "$SECOND_HOME_A/AGENTS.md"
|
|
977
|
+
printf 'Secondmate alpha charter.\n' > "$SECOND_HOME_A/data/charter.md"
|
|
978
|
+
|
|
979
|
+
# Primary setting only; real inheritance must push it into both secondmate homes.
|
|
980
|
+
[ -f "$HOME_DIR/config/herdr-presentation-spaces" ] \
|
|
981
|
+
|| fail "primary presentation setting disappeared before multi-home inheritance"
|
|
982
|
+
[ ! -e "$SECOND_HOME_A/config/herdr-presentation-spaces" ] \
|
|
983
|
+
|| fail "secondmate A unexpectedly had a local presentation setting before inheritance"
|
|
984
|
+
[ ! -e "$SECOND_HOME_B/config/herdr-presentation-spaces" ] \
|
|
985
|
+
|| fail "secondmate B unexpectedly had a local presentation setting before inheritance"
|
|
986
|
+
SECOND_SPAWN_LOG_START=$(log_line_count)
|
|
987
|
+
spawn_secondmate_task alpha "$SECOND_HOME_A" > "$TMP_ROOT/alpha.out" 2> "$TMP_ROOT/alpha.err" \
|
|
988
|
+
|| fail "secondmate alpha spawn failed: $(cat "$TMP_ROOT/alpha.err")"
|
|
989
|
+
[ -f "$SECOND_HOME_A/config/herdr-presentation-spaces" ] \
|
|
990
|
+
|| fail "secondmate spawn did not inherit the presentation setting"
|
|
991
|
+
[ ! -e "$HOME_DIR/state/alpha.herdr-presentation" ] \
|
|
992
|
+
|| fail "secondmate spawn published a presentation journal"
|
|
993
|
+
SECOND_META="$HOME_DIR/state/alpha.meta"
|
|
994
|
+
[ "$(grep '^kind=' "$SECOND_META" | cut -d= -f2-)" = secondmate ] \
|
|
995
|
+
|| fail "secondmate spawn did not record kind=secondmate"
|
|
996
|
+
SECOND_WSID=$(grep '^herdr_workspace_id=' "$SECOND_META" | cut -d= -f2-)
|
|
997
|
+
SECOND_LABEL=$(lab workspace get "$SECOND_WSID" | jq -r '.result.workspace.label')
|
|
998
|
+
[ "$SECOND_LABEL" = 2ndmate-alpha ] \
|
|
999
|
+
|| fail "secondmate spawn did not use its flat parent workspace: $SECOND_LABEL"
|
|
1000
|
+
[ -z "$(projection_labels_from_log "$SECOND_SPAWN_LOG_START")" ] \
|
|
1001
|
+
|| fail "secondmate spawn created a corner projection workspace"
|
|
1002
|
+
if sed -n "$((SECOND_SPAWN_LOG_START + 1)),\$p" "$HERDR_CALL_LOG" \
|
|
1003
|
+
| grep -E $'^(workspace\tmove|session\tlist)' >/dev/null 2>&1; then
|
|
1004
|
+
fail "secondmate spawn attempted presentation ordering"
|
|
1005
|
+
fi
|
|
1006
|
+
# shellcheck source=/dev/null
|
|
1007
|
+
. "$ROOT/bin/fm-config-inherit-lib.sh"
|
|
1008
|
+
propagate_inheritable_config "$HOME_DIR/config" "$SECOND_HOME_A/config" \
|
|
1009
|
+
|| fail "inheritance into secondmate A failed"
|
|
1010
|
+
propagate_inheritable_config "$HOME_DIR/config" "$SECOND_HOME_B/config" \
|
|
1011
|
+
|| fail "inheritance into secondmate B failed"
|
|
1012
|
+
[ -f "$SECOND_HOME_A/config/herdr-presentation-spaces" ] \
|
|
1013
|
+
|| fail "primary presentation setting did not reach secondmate A"
|
|
1014
|
+
[ -f "$SECOND_HOME_B/config/herdr-presentation-spaces" ] \
|
|
1015
|
+
|| fail "primary presentation setting did not reach secondmate B"
|
|
1016
|
+
pass "real Herdr lab: the primary presentation setting inherits into real secondmate homes"
|
|
1017
|
+
|
|
1018
|
+
# Keep the pre-existing 2ndmate-alpha/bravo workspaces as owning parents and captain focus.
|
|
1019
|
+
assert_focus_is "$CAPTAIN_FOCUS" "multi-home captain focus"
|
|
1020
|
+
|
|
1021
|
+
mkdir -p "$SECOND_HOME_A/data/a1" "$SECOND_HOME_A/data/a2" \
|
|
1022
|
+
"$SECOND_HOME_B/data/b1" "$SECOND_HOME_B/data/b2" \
|
|
1023
|
+
"$HOME_DIR/data/p1" "$HOME_DIR/data/p2"
|
|
1024
|
+
printf 'Primary multi-home fixture 1.\n' > "$HOME_DIR/data/p1/brief.md"
|
|
1025
|
+
printf 'Primary multi-home fixture 2.\n' > "$HOME_DIR/data/p2/brief.md"
|
|
1026
|
+
printf 'Secondmate A fixture 1.\n' > "$SECOND_HOME_A/data/a1/brief.md"
|
|
1027
|
+
printf 'Secondmate A fixture 2.\n' > "$SECOND_HOME_A/data/a2/brief.md"
|
|
1028
|
+
printf 'Secondmate B fixture 1.\n' > "$SECOND_HOME_B/data/b1/brief.md"
|
|
1029
|
+
printf 'Secondmate B fixture 2.\n' > "$SECOND_HOME_B/data/b2/brief.md"
|
|
1030
|
+
|
|
1031
|
+
MULTI_FOCUS_START=$(focus_audit_line_count)
|
|
1032
|
+
spawn_task p1 "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/p1.out" 2> "$TMP_ROOT/p1.err" \
|
|
1033
|
+
|| fail "multi-home primary p1 failed: $(cat "$TMP_ROOT/p1.err")"
|
|
1034
|
+
spawn_task p2 "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/p2.out" 2> "$TMP_ROOT/p2.err" \
|
|
1035
|
+
|| fail "multi-home primary p2 failed: $(cat "$TMP_ROOT/p2.err")"
|
|
1036
|
+
spawn_task a1 "$SECOND_HOME_A" "$PROJECT_DIR" > "$TMP_ROOT/a1.out" 2> "$TMP_ROOT/a1.err" \
|
|
1037
|
+
|| fail "multi-home secondmate A a1 failed: $(cat "$TMP_ROOT/a1.err")"
|
|
1038
|
+
spawn_task a2 "$SECOND_HOME_A" "$PROJECT_DIR" > "$TMP_ROOT/a2.out" 2> "$TMP_ROOT/a2.err" \
|
|
1039
|
+
|| fail "multi-home secondmate A a2 failed: $(cat "$TMP_ROOT/a2.err")"
|
|
1040
|
+
spawn_task b1 "$SECOND_HOME_B" "$PROJECT_DIR" > "$TMP_ROOT/b1.out" 2> "$TMP_ROOT/b1.err" \
|
|
1041
|
+
|| fail "multi-home secondmate B b1 failed: $(cat "$TMP_ROOT/b1.err")"
|
|
1042
|
+
spawn_task b2 "$SECOND_HOME_B" "$PROJECT_DIR" > "$TMP_ROOT/b2.out" 2> "$TMP_ROOT/b2.err" \
|
|
1043
|
+
|| fail "multi-home secondmate B b2 failed: $(cat "$TMP_ROOT/b2.err")"
|
|
1044
|
+
for META_X in p1 p2 a1 a2 b1 b2; do
|
|
1045
|
+
case "$META_X" in
|
|
1046
|
+
p*) remember_meta_worktree "$HOME_DIR/state/$META_X.meta" >/dev/null ;;
|
|
1047
|
+
a*) remember_meta_worktree "$SECOND_HOME_A/state/$META_X.meta" >/dev/null ;;
|
|
1048
|
+
b*) remember_meta_worktree "$SECOND_HOME_B/state/$META_X.meta" >/dev/null ;;
|
|
1049
|
+
esac
|
|
1050
|
+
done
|
|
1051
|
+
assert_focus_is "$CAPTAIN_FOCUS" "multi-home sequential spawns"
|
|
1052
|
+
assert_raw_presentation_mutations_preserved_since "$MULTI_FOCUS_START" "multi-home sequential spawns"
|
|
1053
|
+
|
|
1054
|
+
P1_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$HOME_DIR/state/p1.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1055
|
+
P2_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$HOME_DIR/state/p2.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1056
|
+
A1_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$SECOND_HOME_A/state/a1.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1057
|
+
A2_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$SECOND_HOME_A/state/a2.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1058
|
+
B1_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$SECOND_HOME_B/state/b1.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1059
|
+
B2_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$SECOND_HOME_B/state/b2.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1060
|
+
case "$P1_LABEL" in $'└ p1 · p:'*) ;; *) fail "primary p1 label wrong: $P1_LABEL" ;; esac
|
|
1061
|
+
case "$P2_LABEL" in $'└ p2 · p:'*) ;; *) fail "primary p2 label wrong: $P2_LABEL" ;; esac
|
|
1062
|
+
case "$A1_LABEL" in $'└ a1 · p:'*) ;; *) fail "secondmate A a1 label wrong: $A1_LABEL" ;; esac
|
|
1063
|
+
case "$A2_LABEL" in $'└ a2 · p:'*) ;; *) fail "secondmate A a2 label wrong: $A2_LABEL" ;; esac
|
|
1064
|
+
case "$B1_LABEL" in $'└ b1 · p:'*) ;; *) fail "secondmate B b1 label wrong: $B1_LABEL" ;; esac
|
|
1065
|
+
case "$B2_LABEL" in $'└ b2 · p:'*) ;; *) fail "secondmate B b2 label wrong: $B2_LABEL" ;; esac
|
|
1066
|
+
|
|
1067
|
+
MULTI_LIST=$(lab workspace list) || fail "could not list multi-home topology"
|
|
1068
|
+
MULTI_LABELS=$(printf '%s' "$MULTI_LIST" | jq -r '
|
|
1069
|
+
.result.workspaces[]
|
|
1070
|
+
| select(
|
|
1071
|
+
.label == "firstmate"
|
|
1072
|
+
or .label == "2ndmate-alpha"
|
|
1073
|
+
or .label == "2ndmate-bravo"
|
|
1074
|
+
or (.label | startswith("└ "))
|
|
1075
|
+
)
|
|
1076
|
+
| .label
|
|
1077
|
+
')
|
|
1078
|
+
MULTI_EXPECTED=$(printf '%s\n' \
|
|
1079
|
+
firstmate "$P1_LABEL" "$P2_LABEL" \
|
|
1080
|
+
2ndmate-alpha "$A1_LABEL" "$A2_LABEL" \
|
|
1081
|
+
2ndmate-bravo "$B1_LABEL" "$B2_LABEL")
|
|
1082
|
+
[ "$MULTI_LABELS" = "$MULTI_EXPECTED" ] \
|
|
1083
|
+
|| fail "multi-home topology was not owning-parent grouped: $MULTI_LABELS"
|
|
1084
|
+
pass "real Herdr lab: primary and two secondmate homes each own a top-level contiguous child block"
|
|
1085
|
+
|
|
1086
|
+
# Concurrent cross-home wave under the one session lock.
|
|
1087
|
+
mkdir -p "$HOME_DIR/data/pcw" "$SECOND_HOME_A/data/acw" "$SECOND_HOME_B/data/bcw"
|
|
1088
|
+
printf 'Cross-home concurrent primary.\n' > "$HOME_DIR/data/pcw/brief.md"
|
|
1089
|
+
printf 'Cross-home concurrent A.\n' > "$SECOND_HOME_A/data/acw/brief.md"
|
|
1090
|
+
printf 'Cross-home concurrent B.\n' > "$SECOND_HOME_B/data/bcw/brief.md"
|
|
1091
|
+
WAVE_CROSS_FOCUS=$(focus_audit_line_count)
|
|
1092
|
+
spawn_task pcw "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/pcw.out" 2> "$TMP_ROOT/pcw.err" &
|
|
1093
|
+
PCW_PID=$!
|
|
1094
|
+
spawn_task acw "$SECOND_HOME_A" "$PROJECT_DIR" > "$TMP_ROOT/acw.out" 2> "$TMP_ROOT/acw.err" &
|
|
1095
|
+
ACW_PID=$!
|
|
1096
|
+
spawn_task bcw "$SECOND_HOME_B" "$PROJECT_DIR" > "$TMP_ROOT/bcw.out" 2> "$TMP_ROOT/bcw.err" &
|
|
1097
|
+
BCW_PID=$!
|
|
1098
|
+
wait "$PCW_PID" || fail "cross-home concurrent primary failed: $(cat "$TMP_ROOT/pcw.err")"
|
|
1099
|
+
wait "$ACW_PID" || fail "cross-home concurrent A failed: $(cat "$TMP_ROOT/acw.err")"
|
|
1100
|
+
wait "$BCW_PID" || fail "cross-home concurrent B failed: $(cat "$TMP_ROOT/bcw.err")"
|
|
1101
|
+
remember_meta_worktree "$HOME_DIR/state/pcw.meta" >/dev/null
|
|
1102
|
+
remember_meta_worktree "$SECOND_HOME_A/state/acw.meta" >/dev/null
|
|
1103
|
+
remember_meta_worktree "$SECOND_HOME_B/state/bcw.meta" >/dev/null
|
|
1104
|
+
assert_focus_is "$CAPTAIN_FOCUS" "cross-home concurrent wave"
|
|
1105
|
+
assert_raw_presentation_mutations_preserved_since "$WAVE_CROSS_FOCUS" "cross-home concurrent wave"
|
|
1106
|
+
CROSS_LIST=$(lab workspace list)
|
|
1107
|
+
printf '%s' "$CROSS_LIST" | jq -e '
|
|
1108
|
+
([.result.workspaces[].label] | index("firstmate")) as $fm
|
|
1109
|
+
| ([.result.workspaces[].label] | index("2ndmate-alpha")) as $a
|
|
1110
|
+
| ([.result.workspaces[].label] | index("2ndmate-bravo")) as $b
|
|
1111
|
+
| $fm != null and $a != null and $b != null
|
|
1112
|
+
and $fm < $a and $a < $b
|
|
1113
|
+
' >/dev/null 2>&1 || fail "cross-home concurrent wave reordered parents"
|
|
1114
|
+
PCW_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$HOME_DIR/state/pcw.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1115
|
+
ACW_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$SECOND_HOME_A/state/acw.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1116
|
+
BCW_LABEL=$(lab workspace get "$(grep '^herdr_workspace_id=' "$SECOND_HOME_B/state/bcw.meta" | cut -d= -f2-)" | jq -r '.result.workspace.label')
|
|
1117
|
+
case "$PCW_LABEL" in $'└ pcw · p:'*|firstmate) ;; *) fail "cross-home primary label wrong: $PCW_LABEL" ;; esac
|
|
1118
|
+
case "$ACW_LABEL" in $'└ acw · p:'*|2ndmate-alpha) ;; *) fail "cross-home A label wrong: $ACW_LABEL" ;; esac
|
|
1119
|
+
case "$BCW_LABEL" in $'└ bcw · p:'*|2ndmate-bravo) ;; *) fail "cross-home B label wrong: $BCW_LABEL" ;; esac
|
|
1120
|
+
pass "real Herdr lab: concurrent primary/A/B spawns preserve parent order and exact focus"
|
|
1121
|
+
|
|
1122
|
+
# Hold the shared session lock from a different home and force flat fallback.
|
|
1123
|
+
CROSS_LOCK_READY="$TMP_ROOT/cross-lock-ready"
|
|
1124
|
+
CROSS_LOCK_RELEASE="$TMP_ROOT/cross-lock-release"
|
|
1125
|
+
CROSS_LOCK_PATH=$(session_presentation_lock_path) \
|
|
1126
|
+
|| fail "could not resolve session lock for cross-home contention"
|
|
1127
|
+
ROOT="$ROOT" READY="$CROSS_LOCK_READY" RELEASE="$CROSS_LOCK_RELEASE" LOCK="$CROSS_LOCK_PATH" bash -c '
|
|
1128
|
+
. "$ROOT/bin/fm-wake-lib.sh"
|
|
1129
|
+
fm_lock_try_acquire "$LOCK" || exit 1
|
|
1130
|
+
: > "$READY"
|
|
1131
|
+
while [ ! -e "$RELEASE" ]; do sleep 0.05; done
|
|
1132
|
+
fm_lock_release "$LOCK"
|
|
1133
|
+
' &
|
|
1134
|
+
CROSS_LOCK_PID=$!
|
|
1135
|
+
while [ ! -e "$CROSS_LOCK_READY" ] && kill -0 "$CROSS_LOCK_PID" 2>/dev/null; do sleep 0.01; done
|
|
1136
|
+
[ -e "$CROSS_LOCK_READY" ] || fail "could not hold the cross-home session presentation lock"
|
|
1137
|
+
mkdir -p "$SECOND_HOME_A/data/aflat"
|
|
1138
|
+
printf 'Flat fallback under session lock contention.\n' > "$SECOND_HOME_A/data/aflat/brief.md"
|
|
1139
|
+
if spawn_task aflat "$SECOND_HOME_A" "$PROJECT_DIR" > "$TMP_ROOT/aflat.out" 2> "$TMP_ROOT/aflat.err"; then
|
|
1140
|
+
AFLAT_STATUS=0
|
|
1141
|
+
else
|
|
1142
|
+
AFLAT_STATUS=$?
|
|
1143
|
+
fi
|
|
1144
|
+
: > "$CROSS_LOCK_RELEASE"
|
|
1145
|
+
wait "$CROSS_LOCK_PID" || fail "cross-home session lock owner failed"
|
|
1146
|
+
[ "$AFLAT_STATUS" -eq 0 ] \
|
|
1147
|
+
|| fail "cross-home lock contention did not fall back flat: $(cat "$TMP_ROOT/aflat.err")"
|
|
1148
|
+
grep -F "presentation focus lock unavailable; using the ordinary flat layout without projection" "$TMP_ROOT/aflat.err" >/dev/null 2>&1 \
|
|
1149
|
+
|| fail "cross-home lock contention did not warn about flat fallback"
|
|
1150
|
+
remember_meta_worktree "$SECOND_HOME_A/state/aflat.meta" >/dev/null
|
|
1151
|
+
AFLAT_WSID=$(grep '^herdr_workspace_id=' "$SECOND_HOME_A/state/aflat.meta" | cut -d= -f2-)
|
|
1152
|
+
AFLAT_LABEL=$(lab workspace get "$AFLAT_WSID" | jq -r '.result.workspace.label')
|
|
1153
|
+
[ "$AFLAT_LABEL" = 2ndmate-alpha ] \
|
|
1154
|
+
|| fail "cross-home lock contention did not use the ordinary secondmate home workspace: $AFLAT_LABEL"
|
|
1155
|
+
[ ! -e "$SECOND_HOME_A/state/aflat.herdr-presentation" ] \
|
|
1156
|
+
|| fail "cross-home lock contention published a projection journal"
|
|
1157
|
+
assert_focus_is "$CAPTAIN_FOCUS" "cross-home lock contention flat fallback"
|
|
1158
|
+
teardown_task aflat "$SECOND_HOME_A" > "$TMP_ROOT/aflat-teardown.out" 2> "$TMP_ROOT/aflat-teardown.err" \
|
|
1159
|
+
|| fail "flat cross-home contention fixture teardown failed"
|
|
1160
|
+
pass "real Herdr lab: session lock contention from a secondmate home falls back flat with no journal"
|
|
1161
|
+
|
|
1162
|
+
# Same-identity recovery replaces only one exact agent-free husk in its
|
|
1163
|
+
# original projected workspace.
|
|
1164
|
+
# Exercise both the leading fm- identity style seen in Hi Bit work and the
|
|
1165
|
+
# project-name identity style used by Wheelhouse work.
|
|
1166
|
+
for RESTART_ID in fm-hibit-resume-r1 wheelhouse-healing-r1; do
|
|
1167
|
+
spawn_task "$RESTART_ID" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/$RESTART_ID-first.out" 2> "$TMP_ROOT/$RESTART_ID-first.err" \
|
|
1168
|
+
|| fail "$RESTART_ID fixture's projected spawn failed: $(cat "$TMP_ROOT/$RESTART_ID-first.err")"
|
|
1169
|
+
RESTART_META="$HOME_DIR/state/$RESTART_ID.meta"
|
|
1170
|
+
OLD_RESTART_WT=$(remember_meta_worktree "$RESTART_META")
|
|
1171
|
+
OLD_RESTART_WSID=$(grep '^herdr_workspace_id=' "$RESTART_META" | cut -d= -f2-)
|
|
1172
|
+
OLD_RESTART_PANE=$(grep '^herdr_pane_id=' "$RESTART_META" | cut -d= -f2-)
|
|
1173
|
+
OLD_RESTART_LABEL=$(lab workspace get "$OLD_RESTART_WSID" | jq -r '.result.workspace.label')
|
|
1174
|
+
[ "$(grep '^version=' "$HOME_DIR/state/$RESTART_ID.herdr-presentation")" = version=2 ] \
|
|
1175
|
+
|| fail "$RESTART_ID fresh projection did not publish an exact restart binding"
|
|
1176
|
+
EXPECTED_CONCISE=${RESTART_ID#fm-}
|
|
1177
|
+
case "$OLD_RESTART_LABEL" in
|
|
1178
|
+
"└ $EXPECTED_CONCISE · p:"*) ;;
|
|
1179
|
+
*) fail "$RESTART_ID fresh projection label did not apply concise prefix handling: $OLD_RESTART_LABEL" ;;
|
|
1180
|
+
esac
|
|
1181
|
+
PATH="$HERDR_ORIGINAL_PATH" \
|
|
1182
|
+
"$HERDR_LAB_HELPER" stop "$HERDR_LAB_SESSION" >/dev/null \
|
|
1183
|
+
|| fail "could not stop the isolated session for $RESTART_ID validation"
|
|
1184
|
+
PATH="$HERDR_ORIGINAL_PATH" \
|
|
1185
|
+
"$HERDR_LAB_HELPER" provision "$HERDR_LAB_SESSION" \
|
|
1186
|
+
|| fail "could not reprovision the isolated session for $RESTART_ID validation"
|
|
1187
|
+
lab pane get "$OLD_RESTART_PANE" >/dev/null 2>&1 \
|
|
1188
|
+
|| fail "$RESTART_ID restart did not preserve the projected pane structurally"
|
|
1189
|
+
if lab agent get "$OLD_RESTART_PANE" >/dev/null 2>&1; then
|
|
1190
|
+
fail "$RESTART_ID restart fixture unexpectedly retained a registered agent"
|
|
1191
|
+
fi
|
|
1192
|
+
RECLAIM_FOCUS=$(focus_snapshot)
|
|
1193
|
+
spawn_task "$RESTART_ID" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/$RESTART_ID-reclaim.out" 2> "$TMP_ROOT/$RESTART_ID-reclaim.err" \
|
|
1194
|
+
|| fail "$RESTART_ID same-identity reclaim failed: $(cat "$TMP_ROOT/$RESTART_ID-reclaim.err")"
|
|
1195
|
+
NEW_RESTART_WT=$(remember_meta_worktree "$RESTART_META")
|
|
1196
|
+
NEW_RESTART_WSID=$(grep '^herdr_workspace_id=' "$RESTART_META" | cut -d= -f2-)
|
|
1197
|
+
NEW_RESTART_PANE=$(grep '^herdr_pane_id=' "$RESTART_META" | cut -d= -f2-)
|
|
1198
|
+
[ "$NEW_RESTART_WSID" = "$OLD_RESTART_WSID" ] \
|
|
1199
|
+
|| fail "$RESTART_ID reclaim flattened into a different workspace"
|
|
1200
|
+
[ "$NEW_RESTART_PANE" != "$OLD_RESTART_PANE" ] \
|
|
1201
|
+
|| fail "$RESTART_ID reclaim reused the old husk pane"
|
|
1202
|
+
[ "$(lab workspace get "$NEW_RESTART_WSID" | jq -r '.result.workspace.label')" = "$OLD_RESTART_LABEL" ] \
|
|
1203
|
+
|| fail "$RESTART_ID reclaim renamed or replaced the projected workspace"
|
|
1204
|
+
if lab pane get "$OLD_RESTART_PANE" >/dev/null 2>&1; then
|
|
1205
|
+
fail "$RESTART_ID reclaim did not close the exact old husk pane"
|
|
1206
|
+
fi
|
|
1207
|
+
[ "$(grep '^pane_id=' "$HOME_DIR/state/$RESTART_ID.herdr-presentation" | cut -d= -f2-)" = "$NEW_RESTART_PANE" ] \
|
|
1208
|
+
|| fail "$RESTART_ID reclaim did not advance the exact journal binding"
|
|
1209
|
+
assert_focus_is "$RECLAIM_FOCUS" "$RESTART_ID same-identity reclaim"
|
|
1210
|
+
|
|
1211
|
+
if [ "$RESTART_ID" = fm-hibit-resume-r1 ]; then
|
|
1212
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" stop "$HERDR_LAB_SESSION" >/dev/null \
|
|
1213
|
+
|| fail "could not stop the isolated session for idempotent reclaim"
|
|
1214
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" provision "$HERDR_LAB_SESSION" \
|
|
1215
|
+
|| fail "could not reprovision the isolated session for idempotent reclaim"
|
|
1216
|
+
PRIOR_RESTART_WT=$NEW_RESTART_WT
|
|
1217
|
+
PRIOR_RESTART_PANE=$NEW_RESTART_PANE
|
|
1218
|
+
spawn_task "$RESTART_ID" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/$RESTART_ID-idempotent.out" 2> "$TMP_ROOT/$RESTART_ID-idempotent.err" \
|
|
1219
|
+
|| fail "$RESTART_ID repeated reclaim failed: $(cat "$TMP_ROOT/$RESTART_ID-idempotent.err")"
|
|
1220
|
+
NEW_RESTART_WT=$(remember_meta_worktree "$RESTART_META")
|
|
1221
|
+
NEW_RESTART_WSID=$(grep '^herdr_workspace_id=' "$RESTART_META" | cut -d= -f2-)
|
|
1222
|
+
NEW_RESTART_PANE=$(grep '^herdr_pane_id=' "$RESTART_META" | cut -d= -f2-)
|
|
1223
|
+
[ "$NEW_RESTART_WSID" = "$OLD_RESTART_WSID" ] \
|
|
1224
|
+
|| fail "$RESTART_ID repeated reclaim changed workspace identity"
|
|
1225
|
+
[ "$NEW_RESTART_PANE" != "$PRIOR_RESTART_PANE" ] \
|
|
1226
|
+
|| fail "$RESTART_ID repeated reclaim reused the prior husk pane"
|
|
1227
|
+
"$REAL_TREEHOUSE" return --force "$PRIOR_RESTART_WT" >/dev/null 2>&1 || true
|
|
1228
|
+
fi
|
|
1229
|
+
|
|
1230
|
+
teardown_task "$RESTART_ID" "$HOME_DIR" > "$TMP_ROOT/$RESTART_ID-teardown.out" 2> "$TMP_ROOT/$RESTART_ID-teardown.err" \
|
|
1231
|
+
|| fail "$RESTART_ID teardown after reclaim failed: $(cat "$TMP_ROOT/$RESTART_ID-teardown.err")"
|
|
1232
|
+
[ ! -e "$HOME_DIR/state/$RESTART_ID.herdr-presentation" ] \
|
|
1233
|
+
|| fail "$RESTART_ID exact reclaimed teardown did not retire its journal"
|
|
1234
|
+
"$REAL_TREEHOUSE" return --force "$OLD_RESTART_WT" >/dev/null 2>&1 || true
|
|
1235
|
+
"$REAL_TREEHOUSE" return --force "$NEW_RESTART_WT" >/dev/null 2>&1 || true
|
|
1236
|
+
done
|
|
1237
|
+
pass "real Herdr lab: Hi Bit and Wheelhouse-style same-identity restarts reclaim one nested space with exact focus and idempotence"
|
|
1238
|
+
|
|
1239
|
+
# A secondmate child binds and reclaims only inside its own home and parent.
|
|
1240
|
+
CROSS_RESTART_ID=wheel-child-resume
|
|
1241
|
+
mkdir -p "$SECOND_HOME_A/data/$CROSS_RESTART_ID"
|
|
1242
|
+
printf 'Cross-home restart fixture.\n' > "$SECOND_HOME_A/data/$CROSS_RESTART_ID/brief.md"
|
|
1243
|
+
spawn_task "$CROSS_RESTART_ID" "$SECOND_HOME_A" "$PROJECT_DIR" > "$TMP_ROOT/cross-restart-first.out" 2> "$TMP_ROOT/cross-restart-first.err" \
|
|
1244
|
+
|| fail "cross-home restart fixture failed: $(cat "$TMP_ROOT/cross-restart-first.err")"
|
|
1245
|
+
CROSS_RESTART_META="$SECOND_HOME_A/state/$CROSS_RESTART_ID.meta"
|
|
1246
|
+
CROSS_OLD_WT=$(remember_meta_worktree "$CROSS_RESTART_META")
|
|
1247
|
+
CROSS_OLD_WSID=$(grep '^herdr_workspace_id=' "$CROSS_RESTART_META" | cut -d= -f2-)
|
|
1248
|
+
CROSS_OLD_PANE=$(grep '^herdr_pane_id=' "$CROSS_RESTART_META" | cut -d= -f2-)
|
|
1249
|
+
CROSS_OLD_LABEL=$(lab workspace get "$CROSS_OLD_WSID" | jq -r '.result.workspace.label')
|
|
1250
|
+
CROSS_BOUND_HOME=$(grep '^home=' "$SECOND_HOME_A/state/$CROSS_RESTART_ID.herdr-presentation" | cut -d= -f2-)
|
|
1251
|
+
[ "$CROSS_BOUND_HOME" = "$(cd "$SECOND_HOME_A" && pwd -P)" ] \
|
|
1252
|
+
|| fail "cross-home restart journal did not bind the secondmate's exact home"
|
|
1253
|
+
[ ! -e "$HOME_DIR/state/$CROSS_RESTART_ID.herdr-presentation" ] \
|
|
1254
|
+
|| fail "cross-home restart published a journal in the primary home"
|
|
1255
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" stop "$HERDR_LAB_SESSION" >/dev/null \
|
|
1256
|
+
|| fail "could not stop the isolated session for cross-home restart"
|
|
1257
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" provision "$HERDR_LAB_SESSION" \
|
|
1258
|
+
|| fail "could not reprovision the isolated session for cross-home restart"
|
|
1259
|
+
spawn_task "$CROSS_RESTART_ID" "$SECOND_HOME_A" "$PROJECT_DIR" > "$TMP_ROOT/cross-restart-resume.out" 2> "$TMP_ROOT/cross-restart-resume.err" \
|
|
1260
|
+
|| fail "cross-home same-identity reclaim failed: $(cat "$TMP_ROOT/cross-restart-resume.err")"
|
|
1261
|
+
CROSS_NEW_WT=$(remember_meta_worktree "$CROSS_RESTART_META")
|
|
1262
|
+
CROSS_NEW_WSID=$(grep '^herdr_workspace_id=' "$CROSS_RESTART_META" | cut -d= -f2-)
|
|
1263
|
+
CROSS_NEW_PANE=$(grep '^herdr_pane_id=' "$CROSS_RESTART_META" | cut -d= -f2-)
|
|
1264
|
+
[ "$CROSS_NEW_WSID" = "$CROSS_OLD_WSID" ] && [ "$CROSS_NEW_PANE" != "$CROSS_OLD_PANE" ] \
|
|
1265
|
+
|| fail "cross-home reclaim did not replace one pane inside the same secondmate child workspace"
|
|
1266
|
+
[ "$(lab workspace get "$CROSS_NEW_WSID" | jq -r '.result.workspace.label')" = "$CROSS_OLD_LABEL" ] \
|
|
1267
|
+
|| fail "cross-home reclaim changed the secondmate child's presentation label"
|
|
1268
|
+
teardown_task "$CROSS_RESTART_ID" "$SECOND_HOME_A" > "$TMP_ROOT/cross-restart-teardown.out" 2> "$TMP_ROOT/cross-restart-teardown.err" \
|
|
1269
|
+
|| fail "cross-home reclaimed teardown failed: $(cat "$TMP_ROOT/cross-restart-teardown.err")"
|
|
1270
|
+
"$REAL_TREEHOUSE" return --force "$CROSS_OLD_WT" >/dev/null 2>&1 || true
|
|
1271
|
+
"$REAL_TREEHOUSE" return --force "$CROSS_NEW_WT" >/dev/null 2>&1 || true
|
|
1272
|
+
pass "real Herdr lab: secondmate restart binding and reclaim stay isolated to the exact child home and parent"
|
|
1273
|
+
|
|
1274
|
+
# Two homes recovering concurrently serialize on the named session lock and
|
|
1275
|
+
# each replace only their own exact husk.
|
|
1276
|
+
PRIMARY_WAVE_ID=resume-wave-primary
|
|
1277
|
+
BRAVO_WAVE_ID=resume-wave-bravo
|
|
1278
|
+
mkdir -p "$HOME_DIR/data/$PRIMARY_WAVE_ID" "$SECOND_HOME_B/data/$BRAVO_WAVE_ID"
|
|
1279
|
+
printf 'Concurrent primary recovery fixture.\n' > "$HOME_DIR/data/$PRIMARY_WAVE_ID/brief.md"
|
|
1280
|
+
printf 'Concurrent secondmate recovery fixture.\n' > "$SECOND_HOME_B/data/$BRAVO_WAVE_ID/brief.md"
|
|
1281
|
+
spawn_task "$PRIMARY_WAVE_ID" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/primary-wave-first.out" 2> "$TMP_ROOT/primary-wave-first.err" \
|
|
1282
|
+
|| fail "primary recovery-wave fixture failed: $(cat "$TMP_ROOT/primary-wave-first.err")"
|
|
1283
|
+
spawn_task "$BRAVO_WAVE_ID" "$SECOND_HOME_B" "$PROJECT_DIR" > "$TMP_ROOT/bravo-wave-first.out" 2> "$TMP_ROOT/bravo-wave-first.err" \
|
|
1284
|
+
|| fail "secondmate recovery-wave fixture failed: $(cat "$TMP_ROOT/bravo-wave-first.err")"
|
|
1285
|
+
PRIMARY_WAVE_META="$HOME_DIR/state/$PRIMARY_WAVE_ID.meta"
|
|
1286
|
+
BRAVO_WAVE_META="$SECOND_HOME_B/state/$BRAVO_WAVE_ID.meta"
|
|
1287
|
+
PRIMARY_WAVE_OLD_WT=$(remember_meta_worktree "$PRIMARY_WAVE_META")
|
|
1288
|
+
BRAVO_WAVE_OLD_WT=$(remember_meta_worktree "$BRAVO_WAVE_META")
|
|
1289
|
+
PRIMARY_WAVE_WSID=$(grep '^herdr_workspace_id=' "$PRIMARY_WAVE_META" | cut -d= -f2-)
|
|
1290
|
+
BRAVO_WAVE_WSID=$(grep '^herdr_workspace_id=' "$BRAVO_WAVE_META" | cut -d= -f2-)
|
|
1291
|
+
PRIMARY_WAVE_OLD_PANE=$(grep '^herdr_pane_id=' "$PRIMARY_WAVE_META" | cut -d= -f2-)
|
|
1292
|
+
BRAVO_WAVE_OLD_PANE=$(grep '^herdr_pane_id=' "$BRAVO_WAVE_META" | cut -d= -f2-)
|
|
1293
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" stop "$HERDR_LAB_SESSION" >/dev/null \
|
|
1294
|
+
|| fail "could not stop the isolated session for concurrent recovery"
|
|
1295
|
+
PATH="$HERDR_ORIGINAL_PATH" "$HERDR_LAB_HELPER" provision "$HERDR_LAB_SESSION" \
|
|
1296
|
+
|| fail "could not reprovision the isolated session for concurrent recovery"
|
|
1297
|
+
CONCURRENT_RECOVERY_FOCUS=$(focus_snapshot)
|
|
1298
|
+
spawn_task "$PRIMARY_WAVE_ID" "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/primary-wave-resume.out" 2> "$TMP_ROOT/primary-wave-resume.err" &
|
|
1299
|
+
PRIMARY_WAVE_PID=$!
|
|
1300
|
+
spawn_task "$BRAVO_WAVE_ID" "$SECOND_HOME_B" "$PROJECT_DIR" > "$TMP_ROOT/bravo-wave-resume.out" 2> "$TMP_ROOT/bravo-wave-resume.err" &
|
|
1301
|
+
BRAVO_WAVE_PID=$!
|
|
1302
|
+
wait "$PRIMARY_WAVE_PID" || fail "concurrent primary recovery failed: $(cat "$TMP_ROOT/primary-wave-resume.err")"
|
|
1303
|
+
wait "$BRAVO_WAVE_PID" || fail "concurrent secondmate recovery failed: $(cat "$TMP_ROOT/bravo-wave-resume.err")"
|
|
1304
|
+
PRIMARY_WAVE_NEW_WT=$(remember_meta_worktree "$PRIMARY_WAVE_META")
|
|
1305
|
+
BRAVO_WAVE_NEW_WT=$(remember_meta_worktree "$BRAVO_WAVE_META")
|
|
1306
|
+
PRIMARY_WAVE_NEW_PANE=$(grep '^herdr_pane_id=' "$PRIMARY_WAVE_META" | cut -d= -f2-)
|
|
1307
|
+
BRAVO_WAVE_NEW_PANE=$(grep '^herdr_pane_id=' "$BRAVO_WAVE_META" | cut -d= -f2-)
|
|
1308
|
+
[ "$(grep '^herdr_workspace_id=' "$PRIMARY_WAVE_META" | cut -d= -f2-)" = "$PRIMARY_WAVE_WSID" ] \
|
|
1309
|
+
&& [ "$(grep '^herdr_workspace_id=' "$BRAVO_WAVE_META" | cut -d= -f2-)" = "$BRAVO_WAVE_WSID" ] \
|
|
1310
|
+
|| fail "concurrent recovery flattened one task into a different workspace"
|
|
1311
|
+
[ "$PRIMARY_WAVE_NEW_PANE" != "$PRIMARY_WAVE_OLD_PANE" ] \
|
|
1312
|
+
&& [ "$BRAVO_WAVE_NEW_PANE" != "$BRAVO_WAVE_OLD_PANE" ] \
|
|
1313
|
+
|| fail "concurrent recovery reused an old husk pane"
|
|
1314
|
+
if lab pane get "$PRIMARY_WAVE_OLD_PANE" >/dev/null 2>&1 \
|
|
1315
|
+
|| lab pane get "$BRAVO_WAVE_OLD_PANE" >/dev/null 2>&1; then
|
|
1316
|
+
fail "concurrent recovery left an old husk pane behind"
|
|
1317
|
+
fi
|
|
1318
|
+
assert_focus_is "$CONCURRENT_RECOVERY_FOCUS" "concurrent cross-home recovery"
|
|
1319
|
+
teardown_task "$PRIMARY_WAVE_ID" "$HOME_DIR" > "$TMP_ROOT/primary-wave-teardown.out" 2> "$TMP_ROOT/primary-wave-teardown.err" \
|
|
1320
|
+
|| fail "concurrent primary recovery teardown failed"
|
|
1321
|
+
teardown_task "$BRAVO_WAVE_ID" "$SECOND_HOME_B" > "$TMP_ROOT/bravo-wave-teardown.out" 2> "$TMP_ROOT/bravo-wave-teardown.err" \
|
|
1322
|
+
|| fail "concurrent secondmate recovery teardown failed"
|
|
1323
|
+
"$REAL_TREEHOUSE" return --force "$PRIMARY_WAVE_OLD_WT" >/dev/null 2>&1 || true
|
|
1324
|
+
"$REAL_TREEHOUSE" return --force "$BRAVO_WAVE_OLD_WT" >/dev/null 2>&1 || true
|
|
1325
|
+
"$REAL_TREEHOUSE" return --force "$PRIMARY_WAVE_NEW_WT" >/dev/null 2>&1 || true
|
|
1326
|
+
"$REAL_TREEHOUSE" return --force "$BRAVO_WAVE_NEW_WT" >/dev/null 2>&1 || true
|
|
1327
|
+
pass "real Herdr lab: concurrent cross-home recoveries replace exact husks under one session lock with no focus drift"
|
|
1328
|
+
|
|
1329
|
+
# Seed a legacy old-format primary projection and a flat secondmate tab; correction must not migrate them.
|
|
1330
|
+
LEGACY_OUT=$(lab workspace create --cwd "$PROJECT_DIR" --label "firstmate/legacy-seed · p:AbCdEfGhIjKlMnOpQrStUv" --no-focus) \
|
|
1331
|
+
|| fail "could not seed a legacy old-format presentation space"
|
|
1332
|
+
LEGACY_WSID=$(printf '%s' "$LEGACY_OUT" | jq -r '.result.workspace.workspace_id // empty')
|
|
1333
|
+
[ -n "$LEGACY_WSID" ] || fail "legacy seed returned no workspace id"
|
|
1334
|
+
FLAT_TAB_OUT=$(lab tab create --workspace "$(lab workspace list | jq -r '.result.workspaces[] | select(.label == "2ndmate-alpha") | .workspace_id' | head -1)" --cwd "$PROJECT_DIR" --label fm-flat-legacy-tab --no-focus) \
|
|
1335
|
+
|| fail "could not seed a flat secondmate child tab"
|
|
1336
|
+
FLAT_TAB_ID=$(printf '%s' "$FLAT_TAB_OUT" | jq -r '.result.tab.tab_id // empty')
|
|
1337
|
+
mkdir -p "$HOME_DIR/data/post-legacy"
|
|
1338
|
+
printf 'Post-legacy primary child.\n' > "$HOME_DIR/data/post-legacy/brief.md"
|
|
1339
|
+
spawn_task post-legacy "$HOME_DIR" "$PROJECT_DIR" > "$TMP_ROOT/post-legacy.out" 2> "$TMP_ROOT/post-legacy.err" \
|
|
1340
|
+
|| fail "post-legacy projected spawn failed: $(cat "$TMP_ROOT/post-legacy.err")"
|
|
1341
|
+
remember_meta_worktree "$HOME_DIR/state/post-legacy.meta" >/dev/null
|
|
1342
|
+
[ "$(lab workspace get "$LEGACY_WSID" | jq -r '.result.workspace.label')" = "firstmate/legacy-seed · p:AbCdEfGhIjKlMnOpQrStUv" ] \
|
|
1343
|
+
|| fail "correction renamed or moved the seeded legacy projection"
|
|
1344
|
+
lab tab get "$FLAT_TAB_ID" >/dev/null 2>&1 \
|
|
1345
|
+
|| fail "correction removed the seeded flat secondmate child tab"
|
|
1346
|
+
pass "real Herdr lab: legacy projection labels and flat secondmate tabs are left unmigrated"
|
|
1347
|
+
|
|
1348
|
+
# Teardown multi-home projected tasks by exact pane only.
|
|
1349
|
+
for META_HOME_PAIR in \
|
|
1350
|
+
"p1:$HOME_DIR" "p2:$HOME_DIR" "pcw:$HOME_DIR" "post-legacy:$HOME_DIR" \
|
|
1351
|
+
"a1:$SECOND_HOME_A" "a2:$SECOND_HOME_A" "acw:$SECOND_HOME_A" \
|
|
1352
|
+
"alpha:$HOME_DIR" \
|
|
1353
|
+
"b1:$SECOND_HOME_B" "b2:$SECOND_HOME_B" "bcw:$SECOND_HOME_B"
|
|
1354
|
+
do
|
|
1355
|
+
TASK_ID=${META_HOME_PAIR%%:*}
|
|
1356
|
+
TASK_HOME=${META_HOME_PAIR#*:}
|
|
1357
|
+
teardown_task "$TASK_ID" "$TASK_HOME" > "$TMP_ROOT/td-$TASK_ID.out" 2> "$TMP_ROOT/td-$TASK_ID.err" \
|
|
1358
|
+
|| fail "multi-home teardown of $TASK_ID failed: $(cat "$TMP_ROOT/td-$TASK_ID.err")"
|
|
1359
|
+
done
|
|
1360
|
+
assert_focus_is "$CAPTAIN_FOCUS" "multi-home teardown"
|
|
1361
|
+
pass "real Herdr lab: multi-home exact-pane teardowns restore captain focus without workspace close authority"
|
|
1362
|
+
|
|
1363
|
+
# Missing, renamed, and duplicate tokens are read-only recovery diagnostics.
|
|
1364
|
+
# The duplicate case allows flat fallback only when every matching pane is
|
|
1365
|
+
# positively agent-free.
|
|
1366
|
+
# shellcheck source=/dev/null
|
|
1367
|
+
. "$ROOT/bin/backends/herdr.sh"
|
|
1368
|
+
|
|
1369
|
+
MISSING_STATE="$TMP_ROOT/missing-state"; mkdir -p "$MISSING_STATE"
|
|
1370
|
+
fm_backend_herdr_projection_journal_create "$MISSING_STATE" missing1 >/dev/null
|
|
1371
|
+
MISSING_JOURNAL=$(fm_backend_herdr_projection_journal_path "$MISSING_STATE" missing1)
|
|
1372
|
+
START=$(log_line_count)
|
|
1373
|
+
fm_backend_herdr_projection_recovery_allows_flat "$HERDR_LAB_SESSION" "$MISSING_JOURNAL" missing1 \
|
|
1374
|
+
|| fail "missing token match should degrade to flat"
|
|
1375
|
+
assert_no_projection_mutation_since "$START" "missing-token recovery"
|
|
1376
|
+
|
|
1377
|
+
RENAMED_STATE="$TMP_ROOT/renamed-state"; mkdir -p "$RENAMED_STATE"
|
|
1378
|
+
RENAMED_TOKEN=$(fm_backend_herdr_projection_journal_create "$RENAMED_STATE" renamed1)
|
|
1379
|
+
RENAMED_JOURNAL=$(fm_backend_herdr_projection_journal_path "$RENAMED_STATE" renamed1)
|
|
1380
|
+
RENAMED_OUT=$(lab workspace create --cwd "$PROJECT_DIR" --label "firstmate/renamed1 · p:$RENAMED_TOKEN" --no-focus)
|
|
1381
|
+
RENAMED_WSID=$(printf '%s' "$RENAMED_OUT" | jq -r '.result.workspace.workspace_id')
|
|
1382
|
+
lab workspace rename "$RENAMED_WSID" renamed-without-token >/dev/null
|
|
1383
|
+
START=$(log_line_count)
|
|
1384
|
+
fm_backend_herdr_projection_recovery_allows_flat "$HERDR_LAB_SESSION" "$RENAMED_JOURNAL" renamed1 \
|
|
1385
|
+
|| fail "renamed token match should degrade to flat"
|
|
1386
|
+
assert_no_projection_mutation_since "$START" "renamed-token recovery"
|
|
1387
|
+
lab workspace get "$RENAMED_WSID" >/dev/null 2>&1 || fail "renamed-token recovery removed or adopted the old workspace"
|
|
1388
|
+
|
|
1389
|
+
DUP_STATE="$TMP_ROOT/duplicate-state"; mkdir -p "$DUP_STATE"
|
|
1390
|
+
DUP_TOKEN=$(fm_backend_herdr_projection_journal_create "$DUP_STATE" duplicate1)
|
|
1391
|
+
DUP_JOURNAL=$(fm_backend_herdr_projection_journal_path "$DUP_STATE" duplicate1)
|
|
1392
|
+
DUP1=$(lab workspace create --cwd "$PROJECT_DIR" --label "firstmate/duplicate1 · p:$DUP_TOKEN" --no-focus)
|
|
1393
|
+
DUP2=$(lab workspace create --cwd "$PROJECT_DIR" --label "copy/duplicate1 · p:$DUP_TOKEN" --no-focus)
|
|
1394
|
+
DUP1_WSID=$(printf '%s' "$DUP1" | jq -r '.result.workspace.workspace_id')
|
|
1395
|
+
DUP2_WSID=$(printf '%s' "$DUP2" | jq -r '.result.workspace.workspace_id')
|
|
1396
|
+
DUP1_PANE=$(printf '%s' "$DUP1" | jq -r '.result.root_pane.pane_id')
|
|
1397
|
+
START=$(log_line_count)
|
|
1398
|
+
fm_backend_herdr_projection_recovery_allows_flat "$HERDR_LAB_SESSION" "$DUP_JOURNAL" duplicate1 \
|
|
1399
|
+
|| fail "agent-free duplicate token matches should permit flat fallback"
|
|
1400
|
+
assert_no_projection_mutation_since "$START" "agent-free duplicate-token recovery"
|
|
1401
|
+
lab workspace get "$DUP1_WSID" >/dev/null 2>&1 || fail "duplicate-token recovery removed the first quarantined workspace"
|
|
1402
|
+
lab workspace get "$DUP2_WSID" >/dev/null 2>&1 || fail "duplicate-token recovery removed the second quarantined workspace"
|
|
1403
|
+
|
|
1404
|
+
lab pane report-agent "$DUP1_PANE" --source fm-projection-e2e --agent test-agent --state idle >/dev/null \
|
|
1405
|
+
|| fail "could not register the duplicate-live-agent risk fixture"
|
|
1406
|
+
START=$(log_line_count)
|
|
1407
|
+
if fm_backend_herdr_projection_recovery_allows_flat "$HERDR_LAB_SESSION" "$DUP_JOURNAL" duplicate1; then
|
|
1408
|
+
fail "a duplicate token match with a registered agent should refuse fallback"
|
|
1409
|
+
fi
|
|
1410
|
+
assert_no_projection_mutation_since "$START" "live duplicate-token recovery"
|
|
1411
|
+
lab workspace get "$DUP1_WSID" >/dev/null 2>&1 || fail "live duplicate refusal removed the first workspace"
|
|
1412
|
+
lab workspace get "$DUP2_WSID" >/dev/null 2>&1 || fail "live duplicate refusal removed the second workspace"
|
|
1413
|
+
pass "real Herdr lab: missing, renamed, and duplicate tokens trigger zero destructive or adoptive calls, and live duplicate risk refuses launch"
|
|
1414
|
+
|
|
1415
|
+
STATUS_JSON=$(lab status --json)
|
|
1416
|
+
HERDR_VERSION=$(printf '%s' "$STATUS_JSON" | jq -r '.client.version // "unknown"')
|
|
1417
|
+
PATH="$HERDR_ORIGINAL_PATH" \
|
|
1418
|
+
"$HERDR_LAB_HELPER" teardown "$HERDR_LAB_SESSION" \
|
|
1419
|
+
|| fail "guarded Herdr lab teardown or default-session tripwire verification failed"
|
|
1420
|
+
LAB_READY=0
|
|
1421
|
+
pass "real Herdr lab validation completed on Herdr $HERDR_VERSION with the default-session tripwire intact"
|
|
1422
|
+
|
|
1423
|
+
cleanup_all
|
|
1424
|
+
trap - EXIT
|