@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,2515 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tests/fm-session-start.test.sh - behavior tests for bin/fm-session-start.sh,
|
|
3
|
+
# the single command that collapses AGENTS.md sections 3 (bootstrap) and 5
|
|
4
|
+
# (recovery) into one ordered digest.
|
|
5
|
+
#
|
|
6
|
+
# Coverage:
|
|
7
|
+
# - absent-file markers vs empty-but-present files in the context digest
|
|
8
|
+
# - the lock-refusal read-only path: banner leads, every mutating step is
|
|
9
|
+
# skipped (including bootstrap's seven mutating sweeps, verified by their
|
|
10
|
+
# ABSENCE), the digest still completes
|
|
11
|
+
# - output section ordering: the safety preamble leads unchanged, live fleet
|
|
12
|
+
# state precedes the curated memory a truncated tail may take, and the
|
|
13
|
+
# read-once contract precedes both
|
|
14
|
+
# - context-aware next-step guidance for read-only, AFK, X mode, and normal
|
|
15
|
+
# watcher ownership
|
|
16
|
+
# - status-tail bounding, default and FM_SESSION_START_STATUS_TAIL override
|
|
17
|
+
# - the per-line status-tail cap and its truncation marker
|
|
18
|
+
# - startup backlog composition: done rows dropped, every in-flight/held/
|
|
19
|
+
# blocked row kept whole, the dispatchable queued listing bounded with an
|
|
20
|
+
# exact disclosed remainder
|
|
21
|
+
# - orphan status logs whose task meta has already disappeared
|
|
22
|
+
# - per-task endpoint-liveness lines for a live and a dead recorded target,
|
|
23
|
+
# tmux and herdr both
|
|
24
|
+
# - composition: the script invokes the real fm-lock.sh/fm-bootstrap.sh/
|
|
25
|
+
# fm-wake-drain.sh (their real, distinctive output appears verbatim), it
|
|
26
|
+
# does not reimplement their logic
|
|
27
|
+
# - the deferred network stage: an unreachable host delays a reported check
|
|
28
|
+
# rather than the digest, the sweeps it defers still run and land, a result
|
|
29
|
+
# surfaces exactly once (inline or as a wake, never both), a read-only
|
|
30
|
+
# session declares the checks it skipped, and the tasks-axi compatibility
|
|
31
|
+
# verdict is paid for once per session start
|
|
32
|
+
set -u
|
|
33
|
+
|
|
34
|
+
# shellcheck source=tests/lib.sh
|
|
35
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
36
|
+
# shellcheck source=tests/wake-helpers.sh
|
|
37
|
+
. "$(dirname "${BASH_SOURCE[0]}")/wake-helpers.sh"
|
|
38
|
+
|
|
39
|
+
SESSION_START="$ROOT/bin/fm-session-start.sh"
|
|
40
|
+
BASE_PATH=${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}
|
|
41
|
+
TMP_ROOT=$(fm_test_tmproot fm-session-start-tests)
|
|
42
|
+
SESSION_START_TEST_HARNESS_PID=$$
|
|
43
|
+
SESSION_START_SECOND_MATE_ID="fmtest-sm-${TMP_ROOT##*.}"
|
|
44
|
+
SESSION_START_SECOND_MATE_TMP="/tmp/fm-$SESSION_START_SECOND_MATE_ID"
|
|
45
|
+
SESSION_START_HERDR_SECOND_MATE_ID="fmtest-herdr-${TMP_ROOT##*.}"
|
|
46
|
+
SESSION_START_HERDR_SECOND_MATE_TMP="/tmp/fm-$SESSION_START_HERDR_SECOND_MATE_ID"
|
|
47
|
+
FM_TEST_CLEANUP_DIRS+=("$TMP_ROOT" "$SESSION_START_SECOND_MATE_TMP" "$SESSION_START_HERDR_SECOND_MATE_TMP")
|
|
48
|
+
trap fm_test_cleanup EXIT
|
|
49
|
+
fm_git_identity fmtest fmtest@example.invalid
|
|
50
|
+
|
|
51
|
+
# --- world builders ----------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
# new_world <name>: a real, throwaway git repo on `main` (so the worktree-tangle
|
|
54
|
+
# and default-branch checks behave exactly as they do against the real
|
|
55
|
+
# firstmate repo) to use as FM_ROOT_OVERRIDE, plus an empty FM_HOME with
|
|
56
|
+
# state/, data/, config/, and a fakebin. Echoes "<root-dir>|<home-dir>|<fakebin>".
|
|
57
|
+
new_world() {
|
|
58
|
+
local name=$1 w root home fakebin
|
|
59
|
+
w="$TMP_ROOT/$name"
|
|
60
|
+
root="$w/root"
|
|
61
|
+
home="$w/home"
|
|
62
|
+
fakebin="$w/fakebin"
|
|
63
|
+
mkdir -p "$home/state" "$home/data" "$home/config" "$fakebin"
|
|
64
|
+
git init -q -b main "$root"
|
|
65
|
+
git -C "$root" commit -q --allow-empty -m init
|
|
66
|
+
printf '%s|%s|%s\n' "$root" "$home" "$fakebin"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# make_fake_toolchain <fakebin>: every tool fm-bootstrap.sh detects, present
|
|
70
|
+
# and compatible, so its own detect-only section stays quiet except where a
|
|
71
|
+
# test deliberately breaks one. Mirrors fm-bootstrap.test.sh's fixture.
|
|
72
|
+
make_fake_toolchain() {
|
|
73
|
+
local fakebin=$1
|
|
74
|
+
fm_fake_exit0 "$fakebin" tmux node chrome-devtools-axi
|
|
75
|
+
fm_fake_version_tool "$fakebin" lavish-axi FM_FAKE_LAVISH_AXI_VERSION 0.1.46
|
|
76
|
+
cat > "$fakebin/gh-axi" <<'SH'
|
|
77
|
+
#!/usr/bin/env bash
|
|
78
|
+
if [ "${1:-}" = --version ]; then
|
|
79
|
+
printf '%s\n' '0.1.29'
|
|
80
|
+
exit 0
|
|
81
|
+
fi
|
|
82
|
+
exit 0
|
|
83
|
+
SH
|
|
84
|
+
chmod +x "$fakebin/gh-axi"
|
|
85
|
+
cat > "$fakebin/gh" <<'SH'
|
|
86
|
+
#!/usr/bin/env bash
|
|
87
|
+
exit 0
|
|
88
|
+
SH
|
|
89
|
+
chmod +x "$fakebin/gh"
|
|
90
|
+
cat > "$fakebin/treehouse" <<'SH'
|
|
91
|
+
#!/usr/bin/env bash
|
|
92
|
+
if [ "${1:-}" = get ] && [ "${2:-}" = --help ]; then
|
|
93
|
+
printf '%s\n' 'Usage: treehouse get [--lease]'
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
exit 0
|
|
97
|
+
SH
|
|
98
|
+
chmod +x "$fakebin/treehouse"
|
|
99
|
+
cat > "$fakebin/no-mistakes" <<'SH'
|
|
100
|
+
#!/usr/bin/env bash
|
|
101
|
+
if [ "${1:-}" = --version ]; then
|
|
102
|
+
printf '%s\n' 'no-mistakes version v1.46.0 (fake) 2026-06-27T00:02:18Z'
|
|
103
|
+
exit 0
|
|
104
|
+
fi
|
|
105
|
+
exit 0
|
|
106
|
+
SH
|
|
107
|
+
chmod +x "$fakebin/no-mistakes"
|
|
108
|
+
printf '%s\n' manual > "${fakebin%/*}/home-placeholder" 2>/dev/null || true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# make_fake_tasks_axi_compact <fakebin>: a tasks-axi boundary that answers the
|
|
112
|
+
# four group filters the startup listing composes (in-flight, held, blocked
|
|
113
|
+
# queued, and the dispatchable ready set) and REFUSES anything the recovery
|
|
114
|
+
# listing must never ask for: a body field, an unfiltered whole-backlog listing,
|
|
115
|
+
# or done rows. FM_FAKE_TASKS_AXI_READY sizes the ready set so the queued bound
|
|
116
|
+
# can be driven past its limit.
|
|
117
|
+
make_fake_tasks_axi_compact() {
|
|
118
|
+
local fakebin=$1
|
|
119
|
+
cat > "$fakebin/tasks-axi" <<'SH'
|
|
120
|
+
#!/usr/bin/env bash
|
|
121
|
+
set -u
|
|
122
|
+
log=${FM_FAKE_TASKS_AXI_LOG:-}
|
|
123
|
+
[ -n "$log" ] && printf '%s\n' "$*" >> "$log"
|
|
124
|
+
ready_count=${FM_FAKE_TASKS_AXI_READY:-2}
|
|
125
|
+
require_file() {
|
|
126
|
+
case "$*" in *'--file '*) return 0 ;; esac
|
|
127
|
+
printf '%s\n' 'missing explicit backlog file' >&2
|
|
128
|
+
exit 9
|
|
129
|
+
}
|
|
130
|
+
task_header() {
|
|
131
|
+
printf 'count: %s\n' "$1"
|
|
132
|
+
printf 'tasks[%s]{id,state,kind,repo,title,blocked_by,hold_kind,hold_reason}:\n' "$1"
|
|
133
|
+
}
|
|
134
|
+
list_help() {
|
|
135
|
+
printf 'help[1]:\n'
|
|
136
|
+
printf '%s\n' ' - Run `tasks-axi show <id> --full` for full notes on a task'
|
|
137
|
+
}
|
|
138
|
+
case "${1:-}" in
|
|
139
|
+
--version|-v|-V)
|
|
140
|
+
printf '%s\n' '0.2.4'
|
|
141
|
+
exit 0
|
|
142
|
+
;;
|
|
143
|
+
update)
|
|
144
|
+
if [ "${2:-}" = --help ]; then
|
|
145
|
+
printf '%s\n' 'usage: tasks-axi update <id> [--archive-body]'
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
;;
|
|
149
|
+
mv)
|
|
150
|
+
if [ "${2:-}" = --help ]; then
|
|
151
|
+
printf '%s\n' 'usage: tasks-axi mv <dest> [<id>...]'
|
|
152
|
+
exit 0
|
|
153
|
+
fi
|
|
154
|
+
;;
|
|
155
|
+
ready)
|
|
156
|
+
require_file "$@"
|
|
157
|
+
printf 'count: %s\n' "$ready_count"
|
|
158
|
+
printf 'ready[%s]{id,state,kind,repo,title}:\n' "$ready_count"
|
|
159
|
+
i=1
|
|
160
|
+
while [ "$i" -le "$ready_count" ]; do
|
|
161
|
+
printf ' ready-%s,queued,ship,firstmate,Ready item %s\n' "$i" "$i"
|
|
162
|
+
i=$((i + 1))
|
|
163
|
+
done
|
|
164
|
+
printf 'ready_public_followups: 0 delivery-ready obligations\n'
|
|
165
|
+
printf 'help[1]:\n'
|
|
166
|
+
printf '%s\n' ' - Run `tasks-axi start <id>` to dispatch one of these'
|
|
167
|
+
exit 0
|
|
168
|
+
;;
|
|
169
|
+
list)
|
|
170
|
+
case "$*" in
|
|
171
|
+
*'--fields '*'body'*|*'--fields='*'body'*)
|
|
172
|
+
printf '%s\n' 'unexpected body field requested' >&2
|
|
173
|
+
exit 9
|
|
174
|
+
;;
|
|
175
|
+
esac
|
|
176
|
+
require_file "$@"
|
|
177
|
+
case "$*" in
|
|
178
|
+
*'--state done'*)
|
|
179
|
+
printf '%s\n' 'startup recovery must never list done rows' >&2
|
|
180
|
+
exit 9
|
|
181
|
+
;;
|
|
182
|
+
*'--state in_flight'*)
|
|
183
|
+
task_header 1
|
|
184
|
+
printf '%s\n' ' compact-startup,in_flight,ship,firstmate,Compact startup digest,none,captain,captain choice pending'
|
|
185
|
+
;;
|
|
186
|
+
*'--state held'*)
|
|
187
|
+
task_header 1
|
|
188
|
+
printf '%s\n' ' held-queued,queued,ship,firstmate,Held queued work,none,captain,captain choice pending'
|
|
189
|
+
;;
|
|
190
|
+
*'--state queued'*'--blocked'*)
|
|
191
|
+
task_header 1
|
|
192
|
+
printf '%s\n' ' blocked-followup,queued,scout,firstmate,Follow compact startup,compact-startup,"-","-"'
|
|
193
|
+
;;
|
|
194
|
+
*)
|
|
195
|
+
printf '%s\n' 'startup recovery must not request an unfiltered whole-backlog listing' >&2
|
|
196
|
+
exit 9
|
|
197
|
+
;;
|
|
198
|
+
esac
|
|
199
|
+
list_help
|
|
200
|
+
exit 0
|
|
201
|
+
;;
|
|
202
|
+
esac
|
|
203
|
+
exit 1
|
|
204
|
+
SH
|
|
205
|
+
chmod +x "$fakebin/tasks-axi"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# make_fake_ps_claude <fakebin>: harness_pid()/holder_alive() (fm-lock.sh) walk
|
|
209
|
+
# `ps` output looking for a harness command name; this fake reports EVERY
|
|
210
|
+
# queried pid as a live `claude` harness unless a stable harness pid is set.
|
|
211
|
+
make_fake_ps_claude() {
|
|
212
|
+
local fakebin=$1
|
|
213
|
+
make_fake_ps_harness "$fakebin" claude
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
make_fake_ps_harness() {
|
|
217
|
+
local fakebin=$1 harness=$2
|
|
218
|
+
cat > "$fakebin/ps" <<'SH'
|
|
219
|
+
#!/usr/bin/env bash
|
|
220
|
+
set -u
|
|
221
|
+
harness=${FM_FAKE_HARNESS:-claude}
|
|
222
|
+
pid=
|
|
223
|
+
previous=
|
|
224
|
+
for argument in "$@"; do
|
|
225
|
+
[ "$previous" = -p ] && pid=$argument
|
|
226
|
+
previous=$argument
|
|
227
|
+
done
|
|
228
|
+
case "$*" in
|
|
229
|
+
*"comm="*)
|
|
230
|
+
if [ -z "${FM_FAKE_HARNESS_PID:-}" ] || [ "$pid" = "$FM_FAKE_HARNESS_PID" ] \
|
|
231
|
+
|| [ "$pid" = "${FM_FAKE_LIVE_HOLDER_PID:-}" ]; then
|
|
232
|
+
printf '/usr/local/bin/%s\n' "$harness"
|
|
233
|
+
else
|
|
234
|
+
printf '/bin/bash\n'
|
|
235
|
+
fi
|
|
236
|
+
exit 0
|
|
237
|
+
;;
|
|
238
|
+
*"args="*)
|
|
239
|
+
if [ -z "${FM_FAKE_HARNESS_PID:-}" ] || [ "$pid" = "$FM_FAKE_HARNESS_PID" ] \
|
|
240
|
+
|| [ "$pid" = "${FM_FAKE_LIVE_HOLDER_PID:-}" ]; then
|
|
241
|
+
printf '%s\n' "$harness"
|
|
242
|
+
else
|
|
243
|
+
printf 'bash\n'
|
|
244
|
+
fi
|
|
245
|
+
exit 0
|
|
246
|
+
;;
|
|
247
|
+
*"ppid="*)
|
|
248
|
+
[ -n "${FM_FAKE_HARNESS_PID:-}" ] || exit 1
|
|
249
|
+
/bin/ps -o ppid= -p "$pid"
|
|
250
|
+
;;
|
|
251
|
+
esac
|
|
252
|
+
exit 1
|
|
253
|
+
SH
|
|
254
|
+
chmod +x "$fakebin/ps"
|
|
255
|
+
printf '%s\n' "$harness" > "$fakebin/.harness-name"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
make_fake_ps_pi_holder() {
|
|
259
|
+
local fakebin=$1 holder_pid=$2
|
|
260
|
+
cat > "$fakebin/ps" <<SH
|
|
261
|
+
#!/usr/bin/env bash
|
|
262
|
+
set -u
|
|
263
|
+
pid=""
|
|
264
|
+
prev=""
|
|
265
|
+
for arg in "\$@"; do
|
|
266
|
+
[ "\$prev" = "-p" ] && pid="\$arg"
|
|
267
|
+
prev="\$arg"
|
|
268
|
+
done
|
|
269
|
+
case "\$*" in
|
|
270
|
+
*"comm="*)
|
|
271
|
+
if [ "\$pid" = "$holder_pid" ]; then
|
|
272
|
+
printf '/usr/local/bin/pi\n'
|
|
273
|
+
else
|
|
274
|
+
printf '/bin/zsh\n'
|
|
275
|
+
fi
|
|
276
|
+
exit 0
|
|
277
|
+
;;
|
|
278
|
+
*"args="*)
|
|
279
|
+
if [ "\$pid" = "$holder_pid" ]; then
|
|
280
|
+
printf 'pi\n'
|
|
281
|
+
else
|
|
282
|
+
printf 'zsh\n'
|
|
283
|
+
fi
|
|
284
|
+
exit 0
|
|
285
|
+
;;
|
|
286
|
+
*"ppid="*) printf '%s\n' "$holder_pid"; exit 0 ;;
|
|
287
|
+
esac
|
|
288
|
+
exit 1
|
|
289
|
+
SH
|
|
290
|
+
chmod +x "$fakebin/ps"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# make_fake_tmux <fakebin> <live-target>: display-message succeeds only for
|
|
294
|
+
# the given "session:window" target - the exact primitive
|
|
295
|
+
# fm_backend_target_exists uses for a tmux endpoint liveness read.
|
|
296
|
+
make_fake_tmux() {
|
|
297
|
+
local fakebin=$1 live=$2
|
|
298
|
+
cat > "$fakebin/tmux" <<SH
|
|
299
|
+
#!/usr/bin/env bash
|
|
300
|
+
set -u
|
|
301
|
+
case "\${1:-}" in
|
|
302
|
+
display-message)
|
|
303
|
+
target=""
|
|
304
|
+
prev=""
|
|
305
|
+
for a in "\$@"; do
|
|
306
|
+
[ "\$prev" = "-t" ] && target="\$a"
|
|
307
|
+
prev="\$a"
|
|
308
|
+
done
|
|
309
|
+
[ "\$target" = "$live" ] && { printf '%%1\n'; exit 0; }
|
|
310
|
+
exit 1
|
|
311
|
+
;;
|
|
312
|
+
esac
|
|
313
|
+
exit 1
|
|
314
|
+
SH
|
|
315
|
+
chmod +x "$fakebin/tmux"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# make_fake_tmux_secondmate_recovery <fakebin>: a stateful tmux boundary
|
|
319
|
+
# fixture for the real session-start -> bootstrap -> spawn path.
|
|
320
|
+
# FM_FAKE_TMUX_MODE selects missing, ambiguous, unreadable, or shell; missing
|
|
321
|
+
# reproduces real tmux's active-window fallback while inventory omits the mate.
|
|
322
|
+
make_fake_tmux_secondmate_recovery() {
|
|
323
|
+
local fakebin=$1
|
|
324
|
+
cat > "$fakebin/tmux" <<'SH'
|
|
325
|
+
#!/usr/bin/env bash
|
|
326
|
+
set -u
|
|
327
|
+
mode=${FM_FAKE_TMUX_MODE:?}
|
|
328
|
+
log=${FM_FAKE_TMUX_LOG:?}
|
|
329
|
+
spawned=${FM_FAKE_TMUX_SPAWNED:?}
|
|
330
|
+
killed=${spawned}.killed
|
|
331
|
+
mate_home=${FM_FAKE_SECOND_MATE_HOME:?}
|
|
332
|
+
mate_id=${FM_FAKE_SECOND_MATE_ID:?}
|
|
333
|
+
mate_window="fm-$mate_id"
|
|
334
|
+
case "${1:-}" in
|
|
335
|
+
display-message)
|
|
336
|
+
target=
|
|
337
|
+
format=
|
|
338
|
+
prev=
|
|
339
|
+
for arg in "$@"; do
|
|
340
|
+
[ "$prev" = -t ] && target=$arg
|
|
341
|
+
prev=$arg
|
|
342
|
+
case "$arg" in '#{'*) format=$arg ;; esac
|
|
343
|
+
done
|
|
344
|
+
if [ "${target#%}" != "$target" ]; then
|
|
345
|
+
case "$format" in
|
|
346
|
+
*pane_current_path*) printf '%s\n' "$mate_home" ;;
|
|
347
|
+
*pane_current_command*) printf '%s\n' node ;;
|
|
348
|
+
*) printf '%s\n' "$target" ;;
|
|
349
|
+
esac
|
|
350
|
+
exit 0
|
|
351
|
+
fi
|
|
352
|
+
if [ -e "$spawned" ]; then
|
|
353
|
+
case "$format" in
|
|
354
|
+
*pane_current_command*) printf '%s\n' node ;;
|
|
355
|
+
*) printf '%%1\n' ;;
|
|
356
|
+
esac
|
|
357
|
+
exit 0
|
|
358
|
+
fi
|
|
359
|
+
case "$mode" in
|
|
360
|
+
ambiguous)
|
|
361
|
+
case "$format" in *pane_current_command*) printf '%s\n' node ;; *) printf '%%1\n' ;; esac
|
|
362
|
+
exit 0
|
|
363
|
+
;;
|
|
364
|
+
shell)
|
|
365
|
+
case "$format" in *pane_current_command*) printf '%s\n' zsh ;; *) printf '%%1\n' ;; esac
|
|
366
|
+
exit 0
|
|
367
|
+
;;
|
|
368
|
+
missing)
|
|
369
|
+
case "$format" in *pane_current_command*) printf '%s\n' node ;; *) printf '%%fallback\n' ;; esac
|
|
370
|
+
exit 0
|
|
371
|
+
;;
|
|
372
|
+
unreadable) exit 1 ;;
|
|
373
|
+
esac
|
|
374
|
+
;;
|
|
375
|
+
list-windows)
|
|
376
|
+
if [ "$mode" = unreadable ] && [ ! -e "$spawned" ] && [ ! -e "$killed" ]; then
|
|
377
|
+
exit 1
|
|
378
|
+
fi
|
|
379
|
+
if [ -e "$spawned" ]; then
|
|
380
|
+
printf '%s\n' "$mate_window"
|
|
381
|
+
elif [ ! -e "$killed" ] && { [ "$mode" = ambiguous ] || [ "$mode" = shell ]; }; then
|
|
382
|
+
printf '%s\n' "$mate_window"
|
|
383
|
+
else
|
|
384
|
+
printf '%s\n' main
|
|
385
|
+
fi
|
|
386
|
+
exit 0
|
|
387
|
+
;;
|
|
388
|
+
has-session) exit 0 ;;
|
|
389
|
+
kill-window)
|
|
390
|
+
printf '%s\n' "$*" >> "$log"
|
|
391
|
+
: > "$killed"
|
|
392
|
+
exit 0
|
|
393
|
+
;;
|
|
394
|
+
new-window)
|
|
395
|
+
printf '%s\n' "$*" >> "$log"
|
|
396
|
+
: > "$spawned"
|
|
397
|
+
printf '%%1\n'
|
|
398
|
+
exit 0
|
|
399
|
+
;;
|
|
400
|
+
set-window-option|send-keys) exit 0 ;;
|
|
401
|
+
esac
|
|
402
|
+
exit 0
|
|
403
|
+
SH
|
|
404
|
+
chmod +x "$fakebin/tmux"
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
make_fake_herdr_secondmate_recovery() {
|
|
408
|
+
local fakebin=$1
|
|
409
|
+
# The recovery kill now requires the shared named-session lock and an exact
|
|
410
|
+
# focus snapshot. Keep a focused sibling tab so this test's husk close is
|
|
411
|
+
# provably non-workspace-emptying and never needs to signal a fake shell pid.
|
|
412
|
+
cat > "$fakebin/herdr" <<'SH'
|
|
413
|
+
#!/usr/bin/env bash
|
|
414
|
+
set -u
|
|
415
|
+
log=${FM_FAKE_HERDR_LOG:?}
|
|
416
|
+
state=${FM_FAKE_HERDR_STATE:?}
|
|
417
|
+
mate_id=${FM_FAKE_SECOND_MATE_ID:?}
|
|
418
|
+
killed="${state}.killed"
|
|
419
|
+
spawned="${state}.spawned"
|
|
420
|
+
printf '%s\n' "$*" >> "$log"
|
|
421
|
+
case "${1:-} ${2:-}" in
|
|
422
|
+
"status --json")
|
|
423
|
+
printf '%s\n' '{"client":{"protocol":14,"version":"test"},"server":{"running":true}}'
|
|
424
|
+
;;
|
|
425
|
+
"session list")
|
|
426
|
+
printf '{"sessions":[{"name":"default","running":true,"socket_path":"%s.sock"}]}\n' "$state"
|
|
427
|
+
;;
|
|
428
|
+
"workspace list")
|
|
429
|
+
printf '{"result":{"workspaces":[{"workspace_id":"ws1","label":"2ndmate-%s","focused":true,"active_tab_id":"t-focus"}]}}\n' "$mate_id"
|
|
430
|
+
;;
|
|
431
|
+
"tab list")
|
|
432
|
+
if [ -e "$spawned" ]; then
|
|
433
|
+
printf '{"result":{"tabs":[{"tab_id":"t-focus","workspace_id":"ws1","label":"captain","focused":true},{"tab_id":"t-new","workspace_id":"ws1","label":"fm-%s","focused":false}]}}\n' "$mate_id"
|
|
434
|
+
elif [ -e "$killed" ]; then
|
|
435
|
+
printf '%s\n' '{"result":{"tabs":[{"tab_id":"t-focus","workspace_id":"ws1","label":"captain","focused":true}]}}'
|
|
436
|
+
else
|
|
437
|
+
printf '{"result":{"tabs":[{"tab_id":"t-focus","workspace_id":"ws1","label":"captain","focused":true},{"tab_id":"t-old","workspace_id":"ws1","label":"fm-%s","focused":false}]}}\n' "$mate_id"
|
|
438
|
+
fi
|
|
439
|
+
;;
|
|
440
|
+
"tab create")
|
|
441
|
+
: > "$spawned"
|
|
442
|
+
printf '%s\n' '{"result":{"tab":{"tab_id":"t-new"},"root_pane":{"pane_id":"p-new"}}}'
|
|
443
|
+
;;
|
|
444
|
+
"pane list")
|
|
445
|
+
if [ -e "$spawned" ]; then
|
|
446
|
+
printf '%s\n' '{"result":{"panes":[{"pane_id":"p-new","tab_id":"t-new"}]}}'
|
|
447
|
+
elif [ ! -e "$killed" ]; then
|
|
448
|
+
printf '%s\n' '{"result":{"panes":[{"pane_id":"p-old","tab_id":"t-old"}]}}'
|
|
449
|
+
else
|
|
450
|
+
printf '%s\n' '{"result":{"panes":[]}}'
|
|
451
|
+
fi
|
|
452
|
+
;;
|
|
453
|
+
"pane get")
|
|
454
|
+
pane=${3:-}
|
|
455
|
+
if [ "$pane" = p-new ] && [ -e "$spawned" ]; then
|
|
456
|
+
printf '%s\n' '{"result":{"pane":{"pane_id":"p-new","tab_id":"t-new","workspace_id":"ws1"}}}'
|
|
457
|
+
elif [ "$pane" = p-old ] && [ ! -e "$killed" ]; then
|
|
458
|
+
printf '%s\n' '{"result":{"pane":{"pane_id":"p-old","tab_id":"t-old","workspace_id":"ws1"}}}'
|
|
459
|
+
else
|
|
460
|
+
printf '%s\n' '{"error":{"code":"pane_not_found"}}' >&2
|
|
461
|
+
exit 1
|
|
462
|
+
fi
|
|
463
|
+
;;
|
|
464
|
+
"agent get")
|
|
465
|
+
if [ "${3:-}" = p-new ] && [ -e "$spawned" ]; then
|
|
466
|
+
printf '%s\n' '{"result":{"agent":{"agent_status":"idle"}}}'
|
|
467
|
+
else
|
|
468
|
+
printf '%s\n' '{"error":{"code":"agent_not_found"}}' >&2
|
|
469
|
+
exit 1
|
|
470
|
+
fi
|
|
471
|
+
;;
|
|
472
|
+
"pane close")
|
|
473
|
+
[ "${3:-}" = p-old ] && : > "$killed"
|
|
474
|
+
;;
|
|
475
|
+
"pane run"|"pane send-text"|"pane send-keys"|"tab close")
|
|
476
|
+
;;
|
|
477
|
+
*)
|
|
478
|
+
exit 1
|
|
479
|
+
;;
|
|
480
|
+
esac
|
|
481
|
+
exit 0
|
|
482
|
+
SH
|
|
483
|
+
chmod +x "$fakebin/herdr"
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# make_fake_herdr <fakebin> <live-pane>: `herdr pane get <pane>` succeeds only
|
|
487
|
+
# for the given pane id - the exact primitive fm_backend_target_exists uses
|
|
488
|
+
# for a herdr endpoint liveness read. No version/server-start calls: a
|
|
489
|
+
# liveness check must never auto-start a server (fm-backend.sh's contract).
|
|
490
|
+
make_fake_herdr() {
|
|
491
|
+
local fakebin=$1 live=$2
|
|
492
|
+
cat > "$fakebin/herdr" <<SH
|
|
493
|
+
#!/usr/bin/env bash
|
|
494
|
+
set -u
|
|
495
|
+
if [ "\${1:-}" = pane ] && [ "\${2:-}" = get ]; then
|
|
496
|
+
[ "\${3:-}" = "$live" ] && exit 0
|
|
497
|
+
exit 1
|
|
498
|
+
fi
|
|
499
|
+
exit 1
|
|
500
|
+
SH
|
|
501
|
+
chmod +x "$fakebin/herdr"
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
# run_session_start <home> <root> <path>
|
|
505
|
+
# Drop every harness env marker from bin/fm-harness.sh detect_own so the
|
|
506
|
+
# surrounding interactive shell cannot leak past the suite's fake ps harness.
|
|
507
|
+
# Markers today: CLAUDECODE (claude), PI_CODING_AGENT plus FM_PI_HARNESS
|
|
508
|
+
# (Pi family), GROK_AGENT (grok).
|
|
509
|
+
# codex and opencode have no env markers (ancestry only). Without this, a local
|
|
510
|
+
# claude/pi/grok session fails cases that pin a different fake harness while CI
|
|
511
|
+
# (no ambient markers) still passes.
|
|
512
|
+
run_session_start() {
|
|
513
|
+
local home=$1 root=$2 path=$3 pi_harness=${4:-}
|
|
514
|
+
if [ -n "$pi_harness" ]; then
|
|
515
|
+
env -u CLAUDECODE -u GROK_AGENT PI_CODING_AGENT=true FM_PI_HARNESS="$pi_harness" \
|
|
516
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$path" \
|
|
517
|
+
"$SESSION_START"
|
|
518
|
+
else
|
|
519
|
+
env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
520
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$path" \
|
|
521
|
+
"$SESSION_START"
|
|
522
|
+
fi
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
run_pi_session_start() { # <home> <root> <path> [fm-session-start args...]
|
|
526
|
+
local home=$1 root=$2 path=$3
|
|
527
|
+
shift 3
|
|
528
|
+
env -u CLAUDECODE -u GROK_AGENT PI_CODING_AGENT=true FM_PI_HARNESS=pi \
|
|
529
|
+
FM_FAKE_HARNESS_PID="$SESSION_START_TEST_HARNESS_PID" \
|
|
530
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$path" \
|
|
531
|
+
"$SESSION_START" "$@"
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
run_named_harness_session_start() { # <harness> <home> <root> <path> [fm-session-start args...]
|
|
535
|
+
local harness=$1 home=$2 root=$3 path=$4
|
|
536
|
+
shift 4
|
|
537
|
+
env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
538
|
+
FM_FAKE_HARNESS="$harness" FM_FAKE_HARNESS_PID="$SESSION_START_TEST_HARNESS_PID" \
|
|
539
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$path" \
|
|
540
|
+
"$SESSION_START" "$@"
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
# prepare_session_start_secondmate <name>: a throwaway main home and Pi
|
|
544
|
+
# secondmate home wired to the real spawn implementation through the fixture
|
|
545
|
+
# root. Echoes root|home|fakebin|mate|log|spawned.
|
|
546
|
+
prepare_session_start_secondmate() {
|
|
547
|
+
local name=$1 rec root home fakebin w mate log spawned id=$SESSION_START_SECOND_MATE_ID
|
|
548
|
+
rec=$(new_world "$name")
|
|
549
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
550
|
+
$rec
|
|
551
|
+
EOF
|
|
552
|
+
w=${root%/root}
|
|
553
|
+
mate="$w/secondmate-$id"
|
|
554
|
+
log="$w/tmux.log"
|
|
555
|
+
spawned="$w/tmux.spawned"
|
|
556
|
+
mkdir -p "$mate/bin" "$mate/data" "$mate/state" "$mate/config" "$mate/projects"
|
|
557
|
+
printf '%s\n' "$id" > "$mate/.fm-secondmate-home"
|
|
558
|
+
printf '# Firstmate\n' > "$mate/AGENTS.md"
|
|
559
|
+
printf 'Second mate charter.\n' > "$mate/data/charter.md"
|
|
560
|
+
printf '%s\n' pi > "$home/config/secondmate-harness"
|
|
561
|
+
printf '%s\n' manual > "$home/config/backlog-backend"
|
|
562
|
+
touch "$home/state/.last-watcher-beat"
|
|
563
|
+
{
|
|
564
|
+
printf 'window=firstmate:fm-%s\n' "$id"
|
|
565
|
+
printf 'kind=secondmate\n'
|
|
566
|
+
printf 'harness=pi\n'
|
|
567
|
+
printf 'home=%s\n' "$mate"
|
|
568
|
+
} > "$home/state/$id.meta"
|
|
569
|
+
ln -s "$ROOT/bin" "$root/bin"
|
|
570
|
+
make_fake_toolchain "$fakebin"
|
|
571
|
+
make_fake_ps_claude "$fakebin"
|
|
572
|
+
fm_fake_exit0 "$fakebin" pi
|
|
573
|
+
make_fake_tmux_secondmate_recovery "$fakebin"
|
|
574
|
+
: > "$log"
|
|
575
|
+
printf '%s|%s|%s|%s|%s|%s\n' "$root" "$home" "$fakebin" "$mate" "$log" "$spawned"
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
run_session_start_secondmate() {
|
|
579
|
+
local root=$1 home=$2 fakebin=$3 mate=$4 log=$5 spawned=$6 mode=$7
|
|
580
|
+
TMUX='' FM_BACKEND=tmux FM_FAKE_TMUX_MODE="$mode" FM_FAKE_TMUX_LOG="$log" \
|
|
581
|
+
FM_FAKE_TMUX_SPAWNED="$spawned" FM_FAKE_SECOND_MATE_HOME="$mate" \
|
|
582
|
+
FM_FAKE_SECOND_MATE_ID="$SESSION_START_SECOND_MATE_ID" \
|
|
583
|
+
FM_FAKE_HARNESS_PID=$$ \
|
|
584
|
+
run_session_start "$home" "$root" "$fakebin:$BASE_PATH"
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
prepare_session_start_herdr_secondmate() {
|
|
588
|
+
local name=$1 rec root home fakebin w mate log state id=$SESSION_START_HERDR_SECOND_MATE_ID
|
|
589
|
+
rec=$(new_world "$name")
|
|
590
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
591
|
+
$rec
|
|
592
|
+
EOF
|
|
593
|
+
w=${root%/root}
|
|
594
|
+
mate="$w/secondmate-$id"
|
|
595
|
+
log="$w/herdr.log"
|
|
596
|
+
state="$w/herdr.state"
|
|
597
|
+
mkdir -p "$mate/bin" "$mate/data" "$mate/state" "$mate/config" "$mate/projects"
|
|
598
|
+
printf '%s\n' "$id" > "$mate/.fm-secondmate-home"
|
|
599
|
+
printf '# Firstmate\n' > "$mate/AGENTS.md"
|
|
600
|
+
printf 'Second mate charter.\n' > "$mate/data/charter.md"
|
|
601
|
+
printf '%s\n' herdr > "$home/config/backend"
|
|
602
|
+
printf '%s\n' pi > "$home/config/secondmate-harness"
|
|
603
|
+
printf '%s\n' manual > "$home/config/backlog-backend"
|
|
604
|
+
touch "$home/state/.last-watcher-beat"
|
|
605
|
+
{
|
|
606
|
+
printf 'window=default:p-old\n'
|
|
607
|
+
printf 'kind=secondmate\n'
|
|
608
|
+
printf 'harness=pi\n'
|
|
609
|
+
printf 'home=%s\n' "$mate"
|
|
610
|
+
printf 'backend=herdr\n'
|
|
611
|
+
printf 'herdr_session=default\n'
|
|
612
|
+
printf 'herdr_workspace_id=ws1\n'
|
|
613
|
+
printf 'herdr_tab_id=t-old\n'
|
|
614
|
+
printf 'herdr_pane_id=p-old\n'
|
|
615
|
+
} > "$home/state/$id.meta"
|
|
616
|
+
ln -s "$ROOT/bin" "$root/bin"
|
|
617
|
+
make_fake_toolchain "$fakebin"
|
|
618
|
+
make_fake_ps_claude "$fakebin"
|
|
619
|
+
fm_fake_exit0 "$fakebin" pi
|
|
620
|
+
make_fake_herdr_secondmate_recovery "$fakebin"
|
|
621
|
+
: > "$log"
|
|
622
|
+
printf '%s|%s|%s|%s|%s|%s\n' "$root" "$home" "$fakebin" "$mate" "$log" "$state"
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
run_session_start_herdr_secondmate() {
|
|
626
|
+
local root=$1 home=$2 fakebin=$3 mate=$4 log=$5 state=$6
|
|
627
|
+
FM_BACKEND=herdr FM_FAKE_HERDR_LOG="$log" FM_FAKE_HERDR_STATE="$state" \
|
|
628
|
+
FM_FAKE_SECOND_MATE_ID="$SESSION_START_HERDR_SECOND_MATE_ID" \
|
|
629
|
+
FM_FAKE_HARNESS_PID=$$ \
|
|
630
|
+
run_session_start "$home" "$root" "$fakebin:$BASE_PATH"
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
# wait_for_network_stage <home> <root> [seconds]
|
|
634
|
+
# Block until the deferred network stage this home's session start launched has
|
|
635
|
+
# published. Only a TEST does this: the digest itself is required never to wait,
|
|
636
|
+
# which is exactly why the sweeps it used to run inline have to be re-asserted
|
|
637
|
+
# here instead of straight off the digest's own output.
|
|
638
|
+
wait_for_network_stage() {
|
|
639
|
+
local home=$1 root=$2 limit=${3:-30}
|
|
640
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" \
|
|
641
|
+
"$ROOT/bin/fm-startup-network.sh" wait "$limit"
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
wait_for_network_wake() {
|
|
645
|
+
local home=$1 limit=${2:-30} waited=0
|
|
646
|
+
while ! grep -Fq $'check\tstartup-network' "$home/state/.wake-queue" 2>/dev/null \
|
|
647
|
+
&& [ "$waited" -lt "$limit" ]; do
|
|
648
|
+
sleep 1
|
|
649
|
+
waited=$((waited + 1))
|
|
650
|
+
done
|
|
651
|
+
grep -Fq $'check\tstartup-network' "$home/state/.wake-queue" 2>/dev/null
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
network_stage_report() {
|
|
655
|
+
local home=$1 root=$2
|
|
656
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" "$ROOT/bin/fm-startup-network.sh" report
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
hash_file_for_test() {
|
|
660
|
+
local file=$1
|
|
661
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
662
|
+
shasum -a 256 "$file" | awk '{print "sha256:" $1}'
|
|
663
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
664
|
+
sha256sum "$file" | awk '{print "sha256:" $1}'
|
|
665
|
+
else
|
|
666
|
+
cksum "$file" | awk '{print "cksum:" $1 ":" $2}'
|
|
667
|
+
fi
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
install_pi_turnend_extension_fixture() {
|
|
671
|
+
local root=$1
|
|
672
|
+
mkdir -p "$root/.pi/extensions"
|
|
673
|
+
cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$root/.pi/extensions/fm-primary-turnend-guard.ts"
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
install_pi_watch_extension_fixture() {
|
|
677
|
+
local root=$1
|
|
678
|
+
mkdir -p "$root/.pi/extensions"
|
|
679
|
+
cp "$ROOT/.pi/extensions/fm-primary-pi-watch.ts" "$root/.pi/extensions/fm-primary-pi-watch.ts"
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
write_pi_watch_loaded_marker() {
|
|
683
|
+
local home=$1 root=$2 pid=$3 version
|
|
684
|
+
version=$(hash_file_for_test "$root/.pi/extensions/fm-primary-pi-watch.ts")
|
|
685
|
+
printf '%s\n%s\n' "$version" "$pid" > "$home/state/.pi-watch-extension-loaded"
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
write_pi_turnend_loaded_marker() {
|
|
689
|
+
local home=$1 root=$2 pid=$3 version
|
|
690
|
+
version=$(hash_file_for_test "$root/.pi/extensions/fm-primary-turnend-guard.ts")
|
|
691
|
+
printf '%s\n%s\n' "$version" "$pid" > "$home/state/.pi-turnend-extension-loaded"
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
write_pi_loaded_markers() {
|
|
695
|
+
local home=$1 root=$2 pid=$3
|
|
696
|
+
write_pi_watch_loaded_marker "$home" "$root" "$pid"
|
|
697
|
+
write_pi_turnend_loaded_marker "$home" "$root" "$pid"
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
# --- context digest: absent vs empty vs present -----------------------------
|
|
701
|
+
|
|
702
|
+
test_context_digest_absent_empty_present() {
|
|
703
|
+
local rec root home fakebin out
|
|
704
|
+
rec=$(new_world context-digest)
|
|
705
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
706
|
+
$rec
|
|
707
|
+
EOF
|
|
708
|
+
make_fake_toolchain "$fakebin"
|
|
709
|
+
make_fake_ps_claude "$fakebin"
|
|
710
|
+
|
|
711
|
+
printf '%s\n' '- demo [no-mistakes] - a demo project (added 2026-07-01)' > "$home/data/projects.md"
|
|
712
|
+
: > "$home/data/captain.md"
|
|
713
|
+
# secondmates.md, captain-shared.md, and learnings.md deliberately absent
|
|
714
|
+
|
|
715
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
716
|
+
|
|
717
|
+
jq -e --arg home "$home" '
|
|
718
|
+
.schema == "fm-secondmate-home-summary.v1"
|
|
719
|
+
and .home == $home
|
|
720
|
+
and (.generated_epoch | type) == "number"
|
|
721
|
+
' "$home/state/home-summary.json" >/dev/null \
|
|
722
|
+
|| fail "a locked session start did not publish the home summary ledger"
|
|
723
|
+
assert_contains "$out" "data/projects.md" "digest did not label the projects.md section"
|
|
724
|
+
assert_contains "$out" "- demo [no-mistakes] - a demo project (added 2026-07-01)" "digest did not print projects.md content"
|
|
725
|
+
|
|
726
|
+
assert_contains "$out" "data/captain.md" "digest did not label the captain.md section"
|
|
727
|
+
assert_contains "$out" "data/captain-shared.md (shared, main-authoritative, read-only in secondmate homes)" \
|
|
728
|
+
"digest did not label the shared captain section"
|
|
729
|
+
|
|
730
|
+
assert_contains "$out" "data/secondmates.md" "digest did not label the secondmates.md section"
|
|
731
|
+
assert_contains "$out" "data/learnings.md" "digest did not label the learnings.md section"
|
|
732
|
+
|
|
733
|
+
# Exactly four context ABSENT markers (secondmates.md, captain-shared.md,
|
|
734
|
+
# learnings.md; backlog.md is covered by its own test) - and the
|
|
735
|
+
# present-but-empty captain.md must NOT print ABSENT.
|
|
736
|
+
absent_count=$(printf '%s\n' "$out" | grep -c '^ABSENT$')
|
|
737
|
+
[ "$absent_count" -eq 4 ] || fail "expected 4 ABSENT markers (secondmates.md, captain-shared.md, learnings.md, backlog.md), got $absent_count: $out"
|
|
738
|
+
|
|
739
|
+
cap_section=$(printf '%s\n' "$out" | awk '/^data\/captain\.md$/{flag=1;next}/^data\//{flag=0}flag')
|
|
740
|
+
assert_contains "$cap_section" "(present, empty)" "empty-but-present captain.md was not distinguished from ABSENT"
|
|
741
|
+
|
|
742
|
+
pass "context digest distinguishes ABSENT, empty-but-present, and populated files"
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
# --- lock refusal: read-only path --------------------------------------------
|
|
746
|
+
|
|
747
|
+
test_lock_refusal_read_only_path() {
|
|
748
|
+
local rec root home fakebin holder_pid out status
|
|
749
|
+
rec=$(new_world lock-refusal)
|
|
750
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
751
|
+
$rec
|
|
752
|
+
EOF
|
|
753
|
+
make_fake_toolchain "$fakebin"
|
|
754
|
+
make_fake_ps_claude "$fakebin"
|
|
755
|
+
|
|
756
|
+
# A live secondmate meta with a window pointed at nothing real - if the
|
|
757
|
+
# bootstrap sweep's secondmate_sync ran (a MUTATING step), it would try to
|
|
758
|
+
# fast-forward this "home" and/or report a SECONDMATE_SYNC/NUDGE_SECONDMATES
|
|
759
|
+
# line. Absence of any such line is this test's proof that
|
|
760
|
+
# FM_BOOTSTRAP_DETECT_ONLY=1 actually suppressed the mutating sweep.
|
|
761
|
+
mkdir -p "$home/other-secondmate/state"
|
|
762
|
+
fm_write_secondmate_meta "$home/state/sm-x.meta" "$home/other-secondmate" "firstmate:fm-sm-x" alpha
|
|
763
|
+
append_wake "$home/state" signal sm-x "done: surfaced before refusal" || fail "seed wake failed"
|
|
764
|
+
git -C "$root" checkout -q -B fm/read-only-tangle
|
|
765
|
+
|
|
766
|
+
sleep 300 &
|
|
767
|
+
holder_pid=$!
|
|
768
|
+
printf '%s\n' "$holder_pid" > "$home/state/.lock"
|
|
769
|
+
|
|
770
|
+
status=0
|
|
771
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH") || status=$?
|
|
772
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
773
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
774
|
+
|
|
775
|
+
expect_code 0 "$status" "fm-session-start.sh must exit 0 even on a lock refusal"
|
|
776
|
+
assert_contains "$out" "READ-ONLY SESSION" "read-only banner missing on lock refusal"
|
|
777
|
+
assert_contains "$out" "another live firstmate session holds the lock" "read-only banner did not surface fm-lock.sh's own error text"
|
|
778
|
+
assert_contains "$out" "Skipping every mutating step" "read-only banner did not explain what was skipped"
|
|
779
|
+
assert_contains "$out" "skipped (read-only session)" "wake-queue section did not report itself skipped"
|
|
780
|
+
assert_contains "$out" "WATCHER DOWN - SUPERVISION IS OFF" "read-only guard did not surface watcher-liveness alarm"
|
|
781
|
+
assert_contains "$out" "queued wakes pending - left untouched because this session lacks verified fleet-lock ownership" "read-only guard did not leave queued wakes untouched without verified lock ownership"
|
|
782
|
+
assert_contains "$out" "TANGLE: primary checkout on feature branch 'fm/read-only-tangle'" "read-only bootstrap did not surface the tangle diagnostic"
|
|
783
|
+
assert_contains "$out" "read-only session must leave restore work" "read-only tangle diagnostic did not explain restore ownership"
|
|
784
|
+
assert_contains "$out" "Stay read-only: do not arm" "read-only next step did not block direct watcher repair"
|
|
785
|
+
assert_not_contains "$out" "drain them with bin/fm-wake-drain.sh" "read-only guard printed a mutating drain instruction"
|
|
786
|
+
assert_not_contains "$out" "After draining queued wakes" "read-only guard printed a drain-then-rearm instruction"
|
|
787
|
+
assert_not_contains "$out" "run bin/fm-watch-arm.sh" "read-only guard printed a mutating watcher-arm instruction"
|
|
788
|
+
assert_not_contains "$out" "git -C $root checkout main" "read-only bootstrap printed a state-changing checkout remediation"
|
|
789
|
+
|
|
790
|
+
# Detect-only bootstrap diagnostics still ran (the fakebin's PATH excludes
|
|
791
|
+
# tasks-axi, so bootstrap's own read-only tool-detection line fires
|
|
792
|
+
# deterministically regardless of what is installed on the test host).
|
|
793
|
+
assert_contains "$out" "MISSING: tasks-axi (install:" "detect-only bootstrap diagnostics did not run on the read-only path"
|
|
794
|
+
|
|
795
|
+
# The mutating secondmate sweep must NOT have run: no SECONDMATE_SYNC/
|
|
796
|
+
# NUDGE_SECONDMATES line, and the sowed secondmate meta's target dir is
|
|
797
|
+
# untouched (fm-ff-lib would have tried to fast-forward it otherwise).
|
|
798
|
+
assert_not_contains "$out" "SECONDMATE_SYNC" "mutating secondmate sweep ran during a lock refusal"
|
|
799
|
+
assert_not_contains "$out" "NUDGE_SECONDMATES" "mutating secondmate sweep ran during a lock refusal"
|
|
800
|
+
|
|
801
|
+
# The rest of the digest (read-only-safe) still completed.
|
|
802
|
+
assert_contains "$out" "FLEET STATE" "fleet-state digest section missing on the read-only path"
|
|
803
|
+
assert_contains "$out" "NEXT STEP" "closing reminder missing on the read-only path"
|
|
804
|
+
|
|
805
|
+
pass "a lock refusal prints a loud read-only banner, skips every mutating step, and still completes the digest"
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
test_lock_write_failure_read_only_path() {
|
|
809
|
+
local rec root home fakebin out status
|
|
810
|
+
rec=$(new_world lock-write-failure)
|
|
811
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
812
|
+
$rec
|
|
813
|
+
EOF
|
|
814
|
+
make_fake_toolchain "$fakebin"
|
|
815
|
+
make_fake_ps_claude "$fakebin"
|
|
816
|
+
append_wake "$home/state" signal task-a "done: must remain queued" || fail "seed wake failed"
|
|
817
|
+
chmod 0500 "$home/state"
|
|
818
|
+
|
|
819
|
+
status=0
|
|
820
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH") || status=$?
|
|
821
|
+
chmod 0700 "$home/state"
|
|
822
|
+
|
|
823
|
+
expect_code 0 "$status" "fm-session-start.sh must exit 0 when lock publication fails"
|
|
824
|
+
assert_contains "$out" "cannot write session lock" "lock publication failure was not surfaced"
|
|
825
|
+
assert_contains "$out" "READ-ONLY SESSION" "lock publication failure did not force a read-only session"
|
|
826
|
+
assert_contains "$out" "FLEET LOCK OWNERSHIP WAS NOT VERIFIED" "lock publication failure was misreported as a live holder"
|
|
827
|
+
assert_contains "$out" "lacks verified fleet-lock ownership" "lock publication failure did not explain why queued wakes remain untouched"
|
|
828
|
+
assert_not_contains "$out" "ANOTHER LIVE FIRSTMATE SESSION HOLDS THE FLEET LOCK" "lock publication failure falsely claimed a live lock holder"
|
|
829
|
+
[ -s "$home/state/.wake-queue" ] || fail "lock publication failure allowed the wake queue to mutate"
|
|
830
|
+
|
|
831
|
+
pass "session start stays read-only when lock ownership cannot be published"
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
test_trace_context_effective_state_is_frozen_after_lock() {
|
|
835
|
+
local rec root home fakebin out frozen
|
|
836
|
+
rec=$(new_world trace-context-session-state)
|
|
837
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
838
|
+
$rec
|
|
839
|
+
EOF
|
|
840
|
+
make_fake_toolchain "$fakebin"
|
|
841
|
+
make_fake_ps_claude "$fakebin"
|
|
842
|
+
: > "$home/config/trace-context"
|
|
843
|
+
|
|
844
|
+
FM_TRACE_CONTEXT=off run_session_start "$home" "$root" "$fakebin:$BASE_PATH" >/dev/null
|
|
845
|
+
[ "$(awk '{print $2}' "$home/state/.trace-context-effective")" = off ] \
|
|
846
|
+
|| fail "session start must freeze an env-off override over a present config flag"
|
|
847
|
+
|
|
848
|
+
rm "$home/config/trace-context"
|
|
849
|
+
FM_TRACE_CONTEXT=on run_session_start "$home" "$root" "$fakebin:$BASE_PATH" >/dev/null
|
|
850
|
+
[ "$(awk '{print $2}' "$home/state/.trace-context-effective")" = on ] \
|
|
851
|
+
|| fail "a new session start must freeze an env-on override over an absent config flag"
|
|
852
|
+
frozen=$(cat "$home/state/.trace-context-effective")
|
|
853
|
+
|
|
854
|
+
sleep 300 &
|
|
855
|
+
holder_pid=$!
|
|
856
|
+
printf '%s\n' "$holder_pid" > "$home/state/.lock"
|
|
857
|
+
out=$(FM_TRACE_CONTEXT=off run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
858
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
859
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
860
|
+
assert_contains "$out" "READ-ONLY SESSION" "trace-context refusal fixture did not enter read-only mode"
|
|
861
|
+
[ "$(cat "$home/state/.trace-context-effective")" = "$frozen" ] \
|
|
862
|
+
|| fail "a lock-refused session must not mutate the frozen trace-context state"
|
|
863
|
+
|
|
864
|
+
pass "locked session start freezes trace context and lock refusal leaves it unchanged"
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
test_session_lock_concurrent_single_winner() {
|
|
868
|
+
local rec root home fakebin ready completed winners pids i pid count
|
|
869
|
+
rec=$(new_world lock-concurrency)
|
|
870
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
871
|
+
$rec
|
|
872
|
+
EOF
|
|
873
|
+
ready="$home/ready"
|
|
874
|
+
completed="$home/done"
|
|
875
|
+
winners="$home/winners"
|
|
876
|
+
mkdir -p "$ready" "$completed"
|
|
877
|
+
: > "$winners"
|
|
878
|
+
cat > "$fakebin/ps" <<'SH'
|
|
879
|
+
#!/usr/bin/env bash
|
|
880
|
+
set -u
|
|
881
|
+
pid=
|
|
882
|
+
previous=
|
|
883
|
+
for argument in "$@"; do
|
|
884
|
+
[ "$previous" = -p ] && pid=$argument
|
|
885
|
+
previous=$argument
|
|
886
|
+
done
|
|
887
|
+
case "$*" in
|
|
888
|
+
*"comm="*)
|
|
889
|
+
if [ -f "$FM_FAKE_LOCK_STATE/harness-$pid" ]; then
|
|
890
|
+
printf '%s\n' /usr/local/bin/claude
|
|
891
|
+
else
|
|
892
|
+
printf '%s\n' /bin/bash
|
|
893
|
+
fi
|
|
894
|
+
;;
|
|
895
|
+
*"args="*)
|
|
896
|
+
if [ -f "$FM_FAKE_LOCK_STATE/harness-$pid" ]; then
|
|
897
|
+
printf '%s\n' claude
|
|
898
|
+
else
|
|
899
|
+
printf '%s\n' bash
|
|
900
|
+
fi
|
|
901
|
+
;;
|
|
902
|
+
*"ppid="*) printf '%s\n' "$FM_FAKE_HARNESS_PID" ;;
|
|
903
|
+
*) exit 1 ;;
|
|
904
|
+
esac
|
|
905
|
+
SH
|
|
906
|
+
chmod +x "$fakebin/ps"
|
|
907
|
+
|
|
908
|
+
pids=
|
|
909
|
+
i=1
|
|
910
|
+
while [ "$i" -le 40 ]; do
|
|
911
|
+
(
|
|
912
|
+
harness_pid=$(sh -c 'printf "%s\n" "$PPID"')
|
|
913
|
+
: > "$home/state/harness-$harness_pid"
|
|
914
|
+
: > "$ready/$i"
|
|
915
|
+
while [ "$(find "$ready" -type f | wc -l | tr -d ' ')" -lt 40 ]; do
|
|
916
|
+
sleep 0.01
|
|
917
|
+
done
|
|
918
|
+
if FM_HOME="$home" FM_FAKE_LOCK_STATE="$home/state" \
|
|
919
|
+
FM_FAKE_HARNESS_PID="$harness_pid" PATH="$fakebin:$BASE_PATH" \
|
|
920
|
+
"$ROOT/bin/fm-lock.sh" >/dev/null 2>&1; then
|
|
921
|
+
printf '%s\n' "$harness_pid" >> "$winners"
|
|
922
|
+
fi
|
|
923
|
+
: > "$completed/$i"
|
|
924
|
+
while [ "$(find "$completed" -type f | wc -l | tr -d ' ')" -lt 40 ]; do
|
|
925
|
+
sleep 0.01
|
|
926
|
+
done
|
|
927
|
+
) &
|
|
928
|
+
pids="$pids $!"
|
|
929
|
+
i=$((i + 1))
|
|
930
|
+
done
|
|
931
|
+
for pid in $pids; do
|
|
932
|
+
wait "$pid" 2>/dev/null || true
|
|
933
|
+
done
|
|
934
|
+
count=$(awk 'NF { count++ } END { print count + 0 }' "$winners")
|
|
935
|
+
[ "$count" -eq 1 ] || fail "concurrent session-lock acquisition produced $count winners"
|
|
936
|
+
|
|
937
|
+
pass "concurrent session-lock acquisition admits exactly one live harness"
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
# --- output ordering ----------------------------------------------------------
|
|
941
|
+
|
|
942
|
+
# The digest is delivered through a harness that truncates from the TAIL, so
|
|
943
|
+
# section order decides what a truncated startup loses. The safety preamble
|
|
944
|
+
# still leads, live fleet identity now outranks curated memory, and the
|
|
945
|
+
# read-once contract arrives before the payload it governs.
|
|
946
|
+
test_output_ordering_diagnostics_lead() {
|
|
947
|
+
local rec root home fakebin out lock_line boot_line wake_line read_once_line
|
|
948
|
+
local context_line fleet_line next_line inventory_line missing_line
|
|
949
|
+
rec=$(new_world ordering)
|
|
950
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
951
|
+
$rec
|
|
952
|
+
EOF
|
|
953
|
+
make_fake_toolchain "$fakebin"
|
|
954
|
+
make_fake_ps_claude "$fakebin"
|
|
955
|
+
# Force a MISSING diagnostic line so the bootstrap section is non-trivial.
|
|
956
|
+
rm -f "$fakebin/node"
|
|
957
|
+
|
|
958
|
+
printf 'window=fm-sess:w1\nkind=ship\n' > "$home/state/task-a.meta"
|
|
959
|
+
printf 'Captain memory that may be truncated away safely.\n' > "$home/data/captain.md"
|
|
960
|
+
|
|
961
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
962
|
+
|
|
963
|
+
lock_line=$(printf '%s\n' "$out" | grep -n '^LOCK$' | head -1 | cut -d: -f1)
|
|
964
|
+
boot_line=$(printf '%s\n' "$out" | grep -n '^BOOTSTRAP$' | head -1 | cut -d: -f1)
|
|
965
|
+
wake_line=$(printf '%s\n' "$out" | grep -n '^WAKE QUEUE$' | head -1 | cut -d: -f1)
|
|
966
|
+
read_once_line=$(printf '%s\n' "$out" | grep -n '^READ-ONCE CONTRACT$' | head -1 | cut -d: -f1)
|
|
967
|
+
context_line=$(printf '%s\n' "$out" | grep -n '^CONTEXT$' | head -1 | cut -d: -f1)
|
|
968
|
+
fleet_line=$(printf '%s\n' "$out" | grep -n '^FLEET STATE$' | head -1 | cut -d: -f1)
|
|
969
|
+
next_line=$(printf '%s\n' "$out" | grep -n '^NEXT STEP$' | head -1 | cut -d: -f1)
|
|
970
|
+
inventory_line=$(printf '%s\n' "$out" | grep -n '^--- task-a ---$' | head -1 | cut -d: -f1)
|
|
971
|
+
|
|
972
|
+
if [ -z "$lock_line" ] || [ -z "$boot_line" ] || [ -z "$wake_line" ] \
|
|
973
|
+
|| [ -z "$read_once_line" ] || [ -z "$context_line" ] || [ -z "$fleet_line" ] \
|
|
974
|
+
|| [ -z "$next_line" ] || [ -z "$inventory_line" ]; then
|
|
975
|
+
fail "one or more section headers missing from digest: $out"
|
|
976
|
+
fi
|
|
977
|
+
|
|
978
|
+
# The safety preamble's order is unchanged: mutation authority, then
|
|
979
|
+
# diagnostics, then this turn's work queue, before anything bulky is read.
|
|
980
|
+
[ "$lock_line" -lt "$boot_line" ] || fail "LOCK did not precede BOOTSTRAP"
|
|
981
|
+
[ "$boot_line" -lt "$wake_line" ] || fail "BOOTSTRAP did not precede WAKE QUEUE"
|
|
982
|
+
[ "$wake_line" -lt "$read_once_line" ] || fail "WAKE QUEUE did not precede the read-once contract"
|
|
983
|
+
|
|
984
|
+
[ "$read_once_line" -lt "$fleet_line" ] || fail "the read-once contract did not precede FLEET STATE"
|
|
985
|
+
[ "$fleet_line" -lt "$context_line" ] || fail "FLEET STATE did not precede CONTEXT"
|
|
986
|
+
[ "$context_line" -lt "$next_line" ] || fail "CONTEXT did not precede NEXT STEP"
|
|
987
|
+
|
|
988
|
+
# The live-task inventory - the record recovery actually depends on - must sit
|
|
989
|
+
# ahead of the curated memory a truncated tail is allowed to take.
|
|
990
|
+
[ "$inventory_line" -lt "$context_line" ] \
|
|
991
|
+
|| fail "the live-task inventory was buried behind the curated memory files"
|
|
992
|
+
assert_contains "$out" "Captain memory that may be truncated away safely." \
|
|
993
|
+
"the ordering fixture did not actually print a memory file"
|
|
994
|
+
|
|
995
|
+
missing_line=$(printf '%s\n' "$out" | grep -n 'MISSING: node' | head -1 | cut -d: -f1)
|
|
996
|
+
[ -n "$missing_line" ] || fail "MISSING diagnostic did not appear at all"
|
|
997
|
+
[ "$missing_line" -lt "$fleet_line" ] || fail "actionable MISSING diagnostic was buried after the bulk fleet-state digest"
|
|
998
|
+
|
|
999
|
+
pass "digest sections are ordered safety-preamble first, live fleet state before curated memory"
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
# The contract has to survive tail truncation and stay honest once it precedes
|
|
1003
|
+
# the sections it governs, so it carries the truncated-stage escape itself.
|
|
1004
|
+
test_read_once_contract_is_stated_once_before_its_subject() {
|
|
1005
|
+
local rec root home fakebin out contract_count
|
|
1006
|
+
rec=$(new_world read-once)
|
|
1007
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1008
|
+
$rec
|
|
1009
|
+
EOF
|
|
1010
|
+
make_fake_toolchain "$fakebin"
|
|
1011
|
+
make_fake_ps_claude "$fakebin"
|
|
1012
|
+
|
|
1013
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1014
|
+
|
|
1015
|
+
assert_contains "$out" "Do NOT re-read any of them after reading this digest" \
|
|
1016
|
+
"the read-once contract lost its core instruction"
|
|
1017
|
+
assert_contains "$out" "STARTUP TRUNCATED banner named the stage that would have printed it" \
|
|
1018
|
+
"the read-once contract does not void itself for a stage that never ran"
|
|
1019
|
+
assert_contains "$out" "The READ-ONCE CONTRACT" \
|
|
1020
|
+
"the closing reminder does not point back at the contract"
|
|
1021
|
+
|
|
1022
|
+
contract_count=$(printf '%s\n' "$out" | grep -c 'Do NOT re-read any of them')
|
|
1023
|
+
[ "$contract_count" -eq 1 ] \
|
|
1024
|
+
|| fail "the read-once contract is stated $contract_count times instead of once: $out"
|
|
1025
|
+
|
|
1026
|
+
pass "the read-once contract is stated once, ahead of the sources it governs"
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
test_herdr_backend_diagnostics_follow_real_session_start() {
|
|
1030
|
+
local mode rec root home fakebin mask out
|
|
1031
|
+
for mode in configured autodetected; do
|
|
1032
|
+
rec=$(new_world "herdr-$mode")
|
|
1033
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1034
|
+
$rec
|
|
1035
|
+
EOF
|
|
1036
|
+
make_fake_toolchain "$fakebin"
|
|
1037
|
+
make_fake_ps_claude "$fakebin"
|
|
1038
|
+
rm -f "$fakebin/tmux"
|
|
1039
|
+
fm_fake_exit0 "$fakebin" herdr jq
|
|
1040
|
+
printf '%s\n' manual > "$home/config/backlog-backend"
|
|
1041
|
+
mask="$home/mask-tmux.bash"
|
|
1042
|
+
cat > "$mask" <<'SH'
|
|
1043
|
+
command() {
|
|
1044
|
+
if [ "${1:-}" = -v ] && [ "${2:-}" = tmux ]; then
|
|
1045
|
+
return 1
|
|
1046
|
+
fi
|
|
1047
|
+
builtin command "$@"
|
|
1048
|
+
}
|
|
1049
|
+
SH
|
|
1050
|
+
if [ "$mode" = configured ]; then
|
|
1051
|
+
printf '%s\n' herdr > "$home/config/backend"
|
|
1052
|
+
out=$(TMUX='' HERDR_ENV='' BASH_ENV="$mask" run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1053
|
+
assert_not_contains "$out" "NOTICE: auto-detected herdr runtime" \
|
|
1054
|
+
"an explicit Herdr home should not be reported as auto-detected"
|
|
1055
|
+
else
|
|
1056
|
+
out=$(TMUX='' HERDR_ENV=1 BASH_ENV="$mask" run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1057
|
+
assert_contains "$out" "NOTICE: auto-detected herdr runtime (HERDR_ENV=1)" \
|
|
1058
|
+
"session start did not preserve the Herdr runtime auto-detection fallback"
|
|
1059
|
+
fi
|
|
1060
|
+
assert_contains "$out" "SESSION START - $home" "the real session-start path did not run in the throwaway home"
|
|
1061
|
+
assert_not_contains "$out" "MISSING: tmux" "Herdr session start falsely required masked tmux"
|
|
1062
|
+
assert_not_contains "$out" "MISSING: herdr" "Herdr session start missed its available session CLI"
|
|
1063
|
+
assert_not_contains "$out" "MISSING: jq" "Herdr session start missed its available JSON dependency"
|
|
1064
|
+
assert_not_contains "$out" "MISSING: treehouse" "Herdr session start missed its available worktree provider"
|
|
1065
|
+
done
|
|
1066
|
+
pass "session start: configured and auto-detected Herdr homes never require tmux"
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
# --- status tail bounding -----------------------------------------------------
|
|
1070
|
+
|
|
1071
|
+
test_status_tail_bounding() {
|
|
1072
|
+
local rec root home fakebin out
|
|
1073
|
+
rec=$(new_world status-tail)
|
|
1074
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1075
|
+
$rec
|
|
1076
|
+
EOF
|
|
1077
|
+
make_fake_toolchain "$fakebin"
|
|
1078
|
+
make_fake_ps_claude "$fakebin"
|
|
1079
|
+
make_fake_tmux "$fakebin" "fm-sess:live"
|
|
1080
|
+
|
|
1081
|
+
printf 'window=fm-sess:live\nkind=ship\n' > "$home/state/task-a.meta"
|
|
1082
|
+
printf 'working: step 1\nworking: step 2\nworking: step 3\nworking: step 4\nworking: step 5\nworking: step 6\nworking: step 7\n' \
|
|
1083
|
+
> "$home/state/task-a.status"
|
|
1084
|
+
|
|
1085
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1086
|
+
assert_contains "$out" "working: step 7" "default status tail missing the most recent line"
|
|
1087
|
+
assert_contains "$out" "working: step 3" "default status tail (5 lines) missing an expected recent line"
|
|
1088
|
+
assert_not_contains "$out" "working: step 1" "default status tail (5 lines) leaked an older line"
|
|
1089
|
+
assert_contains "$out" "$home/state/task-a.status" "digest did not print the full status log path for a deeper read"
|
|
1090
|
+
assert_contains "$out" "a bounded tail of every state/*.status" "read-once contract does not distinguish bounded status tails"
|
|
1091
|
+
|
|
1092
|
+
out=$(FM_SESSION_START_STATUS_TAIL=2 run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1093
|
+
assert_contains "$out" "working: step 7" "FM_SESSION_START_STATUS_TAIL=2 tail missing the most recent line"
|
|
1094
|
+
assert_not_contains "$out" "working: step 5" "FM_SESSION_START_STATUS_TAIL=2 did not bound the tail to 2 lines"
|
|
1095
|
+
|
|
1096
|
+
pass "status tail is bounded to the configured line count, with the full log path always printed"
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
# A crewmate writes its own status lines, so nothing upstream bounds their
|
|
1100
|
+
# length: an observed one ran 865 characters. The tail is a wake-EVENT view
|
|
1101
|
+
# whose full log path is printed beside it, so a long line is cut, marked, and
|
|
1102
|
+
# left recoverable rather than allowed to scale the digest with fleet load.
|
|
1103
|
+
test_status_tail_line_cap() {
|
|
1104
|
+
local rec root home fakebin out lede longest capped tail_section
|
|
1105
|
+
rec=$(new_world status-line-cap)
|
|
1106
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1107
|
+
$rec
|
|
1108
|
+
EOF
|
|
1109
|
+
make_fake_toolchain "$fakebin"
|
|
1110
|
+
make_fake_ps_claude "$fakebin"
|
|
1111
|
+
make_fake_tmux "$fakebin" "fm-sess:live"
|
|
1112
|
+
|
|
1113
|
+
lede='needs-decision: [key=cap] pick the rendering strategy'
|
|
1114
|
+
printf 'window=fm-sess:live\nkind=ship\n' > "$home/state/task-cap.meta"
|
|
1115
|
+
{
|
|
1116
|
+
printf '%s' "$lede"
|
|
1117
|
+
awk 'BEGIN { while (i++ < 400) printf " padding" }'
|
|
1118
|
+
printf '\n'
|
|
1119
|
+
printf 'working: short line kept whole\n'
|
|
1120
|
+
} > "$home/state/task-cap.status"
|
|
1121
|
+
|
|
1122
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1123
|
+
|
|
1124
|
+
assert_contains "$out" "$lede" "the cap discarded the lede that carries the state word and decision key"
|
|
1125
|
+
assert_contains "$out" " [truncated]" "an over-long status line was not marked as truncated"
|
|
1126
|
+
assert_contains "$out" "working: short line kept whole" "the cap mangled a status line already under it"
|
|
1127
|
+
assert_contains "$out" "each capped at 220 characters" "the status tail header does not disclose its per-line cap"
|
|
1128
|
+
assert_contains "$out" "$home/state/task-cap.status" "a capped tail dropped the full log path that recovers the rest"
|
|
1129
|
+
|
|
1130
|
+
# Nothing the tail emits may exceed the cap, and the padded line really was
|
|
1131
|
+
# long enough to exercise it.
|
|
1132
|
+
tail_section=$(printf '%s\n' "$out" | awk '/^status tail \(/ { flag = 1; next } flag && /^$/ { flag = 0 } flag')
|
|
1133
|
+
longest=$(printf '%s\n' "$tail_section" | awk '{ if (length($0) > max) max = length($0) } END { print max + 0 }')
|
|
1134
|
+
[ "$longest" -le 220 ] || fail "a status tail line ran $longest characters past the 220-character cap"
|
|
1135
|
+
capped=$(printf '%s\n' "$tail_section" | grep -c ' \[truncated\]$')
|
|
1136
|
+
[ "$capped" -eq 1 ] || fail "expected exactly one truncated tail line, got $capped: $tail_section"
|
|
1137
|
+
|
|
1138
|
+
pass "status tail lines are capped with a truncation marker while the full log stays reachable"
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
test_orphan_status_logs_are_printed() {
|
|
1142
|
+
local rec root home fakebin out matched_count orphan_count
|
|
1143
|
+
rec=$(new_world orphan-status)
|
|
1144
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1145
|
+
$rec
|
|
1146
|
+
EOF
|
|
1147
|
+
make_fake_toolchain "$fakebin"
|
|
1148
|
+
make_fake_ps_claude "$fakebin"
|
|
1149
|
+
|
|
1150
|
+
printf 'kind=ship\n' > "$home/state/task-a.meta"
|
|
1151
|
+
printf 'matched: surfaced once\n' > "$home/state/task-a.status"
|
|
1152
|
+
printf 'orphan: step 1\norphan: step 2\norphan: step 3\norphan: step 4\norphan: step 5\norphan: step 6\n' \
|
|
1153
|
+
> "$home/state/task-orphan.status"
|
|
1154
|
+
|
|
1155
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1156
|
+
|
|
1157
|
+
assert_contains "$out" "Orphan status logs (state/*.status without matching .meta)" "digest did not label orphan status logs"
|
|
1158
|
+
assert_contains "$out" "--- task-orphan ---" "digest did not print the orphan status id"
|
|
1159
|
+
assert_contains "$out" "orphan: step 6" "orphan status tail missing the newest line"
|
|
1160
|
+
assert_not_contains "$out" "orphan: step 1" "orphan status tail was not bounded"
|
|
1161
|
+
assert_contains "$out" "$home/state/task-orphan.status" "orphan status tail did not print the full log path"
|
|
1162
|
+
|
|
1163
|
+
matched_count=$(printf '%s\n' "$out" | grep -F -c 'matched: surfaced once')
|
|
1164
|
+
orphan_count=$(printf '%s\n' "$out" | grep -F -c 'orphan: step 6')
|
|
1165
|
+
[ "$matched_count" -eq 1 ] || fail "matched status log was printed $matched_count times: $out"
|
|
1166
|
+
[ "$orphan_count" -eq 1 ] || fail "orphan status log was printed $orphan_count times: $out"
|
|
1167
|
+
|
|
1168
|
+
pass "orphan status logs are printed once with bounded tails"
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
# --- session-start secondmate recovery boundary -----------------------------
|
|
1172
|
+
|
|
1173
|
+
test_session_start_relaunches_missing_pi_secondmate() {
|
|
1174
|
+
local rec root home fakebin mate log spawned out first_calls second_calls
|
|
1175
|
+
rec=$(prepare_session_start_secondmate secondmate-missing-pi)
|
|
1176
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1177
|
+
$rec
|
|
1178
|
+
EOF
|
|
1179
|
+
|
|
1180
|
+
out=$(run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" missing)
|
|
1181
|
+
|
|
1182
|
+
# The relaunch now runs off the blocking path, so the digest's own liveness
|
|
1183
|
+
# read may legitimately still show the pre-relaunch endpoint. What must NOT
|
|
1184
|
+
# happen is silence: the section names the relaunch as either done or not yet
|
|
1185
|
+
# confirmed.
|
|
1186
|
+
assert_contains "$out" "NETWORK CHECKS" "the digest lost its deferred network-check section"
|
|
1187
|
+
assert_contains "$out" "dead-secondmate relaunch" \
|
|
1188
|
+
"the digest never accounted for the dead-secondmate relaunch"
|
|
1189
|
+
|
|
1190
|
+
wait_for_network_stage "$home" "$root" \
|
|
1191
|
+
|| fail "the deferred network stage never published: $(network_stage_report "$home" "$root")"
|
|
1192
|
+
|
|
1193
|
+
assert_not_contains "$(network_stage_report "$home" "$root")" "SECONDMATE_LIVENESS:" \
|
|
1194
|
+
"successful missing-window recovery should stay non-actionable"
|
|
1195
|
+
assert_contains "$(cat "$log")" "new-window" "the deferred stage did not relaunch the missing Pi secondmate"
|
|
1196
|
+
assert_not_contains "$(cat "$log")" "kill-window" "the deferred stage tried to kill an already-absent window"
|
|
1197
|
+
assert_grep 'harness=pi' "$home/state/$SESSION_START_SECOND_MATE_ID.meta" \
|
|
1198
|
+
"the real respawn path did not preserve the Pi harness: $(cat "$home/state/$SESSION_START_SECOND_MATE_ID.meta")"
|
|
1199
|
+
|
|
1200
|
+
first_calls=$(grep -c 'new-window' "$log" || true)
|
|
1201
|
+
rm -f "$home/state/.lock"
|
|
1202
|
+
run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" missing >/dev/null
|
|
1203
|
+
wait_for_network_stage "$home" "$root" \
|
|
1204
|
+
|| fail "the second pass's deferred network stage never published"
|
|
1205
|
+
second_calls=$(grep -c 'new-window' "$log" || true)
|
|
1206
|
+
[ "$first_calls" -eq 1 ] && [ "$second_calls" -eq 1 ] \
|
|
1207
|
+
|| fail "a second session-start pass duplicated the relaunched Pi secondmate: $(cat "$log")"
|
|
1208
|
+
pass "session start: an absent recorded tmux window relaunches its Pi secondmate exactly once, off the blocking path"
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
# The relaunch is the sharpest deferral: it mutates the very endpoint record the
|
|
1212
|
+
# digest printed moments earlier. Silence would leave that stale record looking
|
|
1213
|
+
# authoritative, so the deferred pass reports it whether or not verbose facts are
|
|
1214
|
+
# on, and the report says the digest's records are now behind.
|
|
1215
|
+
test_deferred_relaunch_is_always_reported() {
|
|
1216
|
+
local rec root home fakebin mate log spawned report
|
|
1217
|
+
rec=$(prepare_session_start_secondmate secondmate-relaunch-reported)
|
|
1218
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1219
|
+
$rec
|
|
1220
|
+
EOF
|
|
1221
|
+
|
|
1222
|
+
run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" missing >/dev/null
|
|
1223
|
+
wait_for_network_stage "$home" "$root" || fail "the deferred network stage never published"
|
|
1224
|
+
|
|
1225
|
+
report=$(network_stage_report "$home" "$root")
|
|
1226
|
+
assert_contains "$report" "secondmate $SESSION_START_SECOND_MATE_ID relaunched" \
|
|
1227
|
+
"a relaunch performed after the digest was composed went unreported"
|
|
1228
|
+
assert_contains "$report" "re-read any record" \
|
|
1229
|
+
"the report did not tell the reader the digest's records are now behind"
|
|
1230
|
+
pass "session start: a deferred relaunch is always reported, so the digest's stale endpoint record cannot stand"
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
test_session_start_preserves_ambiguous_pi_process() {
|
|
1234
|
+
local rec root home fakebin mate log spawned out
|
|
1235
|
+
rec=$(prepare_session_start_secondmate secondmate-ambiguous-pi)
|
|
1236
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1237
|
+
$rec
|
|
1238
|
+
EOF
|
|
1239
|
+
|
|
1240
|
+
out=$(run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" ambiguous)
|
|
1241
|
+
wait_for_network_stage "$home" "$root" || fail "the deferred network stage never published"
|
|
1242
|
+
|
|
1243
|
+
assert_contains "$(network_stage_report "$home" "$root")" \
|
|
1244
|
+
"SECONDMATE_LIVENESS: secondmate $SESSION_START_SECOND_MATE_ID: skipped: existing endpoint has ambiguous agent process (backend=tmux)" \
|
|
1245
|
+
"session start did not distinguish an existing Pi-shaped process from a missing window"
|
|
1246
|
+
[ ! -s "$log" ] || fail "session start touched an ambiguous existing Pi process: $(cat "$log")"
|
|
1247
|
+
assert_contains "$out" "endpoint: alive (backend=tmux window=firstmate:fm-$SESSION_START_SECOND_MATE_ID)" \
|
|
1248
|
+
"the later fleet read should still see the ambiguous endpoint"
|
|
1249
|
+
pass "session start: an existing ambiguous Pi process prevents duplicate recovery"
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
test_session_start_preserves_transiently_unreadable_tmux() {
|
|
1253
|
+
local rec root home fakebin mate log spawned out
|
|
1254
|
+
rec=$(prepare_session_start_secondmate secondmate-unreadable-pi)
|
|
1255
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1256
|
+
$rec
|
|
1257
|
+
EOF
|
|
1258
|
+
|
|
1259
|
+
out=$(run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" unreadable)
|
|
1260
|
+
wait_for_network_stage "$home" "$root" || fail "the deferred network stage never published"
|
|
1261
|
+
|
|
1262
|
+
assert_contains "$(network_stage_report "$home" "$root")" \
|
|
1263
|
+
"SECONDMATE_LIVENESS: secondmate $SESSION_START_SECOND_MATE_ID: skipped: endpoint probe unreadable (backend=tmux)" \
|
|
1264
|
+
"session start did not distinguish transient unreadability from absence"
|
|
1265
|
+
[ ! -s "$log" ] || fail "session start touched a transiently unreadable target: $(cat "$log")"
|
|
1266
|
+
assert_contains "$out" "endpoint: dead (backend=tmux window=firstmate:fm-$SESSION_START_SECOND_MATE_ID)" \
|
|
1267
|
+
"the later cheap presence read should preserve the visible offline symptom"
|
|
1268
|
+
pass "session start: transient tmux unreadability never licenses a relaunch"
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
test_session_start_preserves_proven_bare_shell_recovery() {
|
|
1272
|
+
local rec root home fakebin mate log spawned out
|
|
1273
|
+
rec=$(prepare_session_start_secondmate secondmate-bare-shell)
|
|
1274
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1275
|
+
$rec
|
|
1276
|
+
EOF
|
|
1277
|
+
|
|
1278
|
+
run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" shell >/dev/null
|
|
1279
|
+
wait_for_network_stage "$home" "$root" || fail "the deferred network stage never published"
|
|
1280
|
+
|
|
1281
|
+
out=$(network_stage_report "$home" "$root")
|
|
1282
|
+
assert_not_contains "$out" "SECONDMATE_LIVENESS:" "successful bare-shell recovery should stay non-actionable"
|
|
1283
|
+
assert_contains "$(cat "$log")" "kill-window -t =firstmate:=fm-$SESSION_START_SECOND_MATE_ID" \
|
|
1284
|
+
"the proven bare-shell path did not remove its existing dead endpoint"
|
|
1285
|
+
assert_contains "$(cat "$log")" "new-window" "the proven bare-shell path did not relaunch"
|
|
1286
|
+
pass "session start: the proven bare-shell recovery path remains intact"
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
test_session_start_relaunches_herdr_husk_secondmate() {
|
|
1290
|
+
local rec root home fakebin mate log state out
|
|
1291
|
+
rec=$(prepare_session_start_herdr_secondmate secondmate-herdr-husk)
|
|
1292
|
+
IFS='|' read -r root home fakebin mate log state <<EOF
|
|
1293
|
+
$rec
|
|
1294
|
+
EOF
|
|
1295
|
+
|
|
1296
|
+
run_session_start_herdr_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$state" >/dev/null
|
|
1297
|
+
wait_for_network_stage "$home" "$root" || fail "the deferred network stage never published"
|
|
1298
|
+
|
|
1299
|
+
out=$(network_stage_report "$home" "$root")
|
|
1300
|
+
assert_not_contains "$out" "SECONDMATE_LIVENESS:" "successful Herdr husk recovery should stay non-actionable"
|
|
1301
|
+
assert_contains "$(cat "$log")" "pane close p-old" "session start did not close the confirmed Herdr husk"
|
|
1302
|
+
assert_contains "$(cat "$log")" "tab create" "session start did not relaunch the Herdr secondmate"
|
|
1303
|
+
assert_grep 'herdr_pane_id=p-new' "$home/state/$SESSION_START_HERDR_SECOND_MATE_ID.meta" \
|
|
1304
|
+
"the real respawn path did not record the replacement Herdr pane"
|
|
1305
|
+
pass "session start: a confirmed Herdr husk is closed and relaunched"
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
# --- endpoint liveness: tmux and herdr, live and dead ------------------------
|
|
1309
|
+
|
|
1310
|
+
test_endpoint_liveness_tmux() {
|
|
1311
|
+
local rec root home fakebin out
|
|
1312
|
+
rec=$(new_world liveness-tmux)
|
|
1313
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1314
|
+
$rec
|
|
1315
|
+
EOF
|
|
1316
|
+
make_fake_toolchain "$fakebin"
|
|
1317
|
+
make_fake_ps_claude "$fakebin"
|
|
1318
|
+
make_fake_tmux "$fakebin" "fm-sess:live-window"
|
|
1319
|
+
|
|
1320
|
+
printf 'window=fm-sess:live-window\nkind=ship\n' > "$home/state/task-live.meta"
|
|
1321
|
+
printf 'window=fm-sess:dead-window\nkind=ship\n' > "$home/state/task-dead.meta"
|
|
1322
|
+
|
|
1323
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1324
|
+
assert_contains "$out" "endpoint: alive (backend=tmux window=fm-sess:live-window)" "live tmux endpoint not reported alive"
|
|
1325
|
+
assert_contains "$out" "endpoint: dead (backend=tmux window=fm-sess:dead-window)" "dead tmux endpoint not reported dead"
|
|
1326
|
+
|
|
1327
|
+
pass "tmux endpoint liveness is reported per task: alive for a live window, dead for a gone one"
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
test_endpoint_liveness_herdr() {
|
|
1331
|
+
local rec root home fakebin out
|
|
1332
|
+
rec=$(new_world liveness-herdr)
|
|
1333
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1334
|
+
$rec
|
|
1335
|
+
EOF
|
|
1336
|
+
make_fake_toolchain "$fakebin"
|
|
1337
|
+
make_fake_ps_claude "$fakebin"
|
|
1338
|
+
make_fake_herdr "$fakebin" "p-live"
|
|
1339
|
+
|
|
1340
|
+
printf 'window=sess:p-live\nkind=ship\nbackend=herdr\n' > "$home/state/task-live.meta"
|
|
1341
|
+
printf 'window=sess:p-dead\nkind=ship\nbackend=herdr\n' > "$home/state/task-dead.meta"
|
|
1342
|
+
|
|
1343
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1344
|
+
assert_contains "$out" "endpoint: alive (backend=herdr window=sess:p-live)" "live herdr endpoint not reported alive"
|
|
1345
|
+
assert_contains "$out" "endpoint: dead (backend=herdr window=sess:p-dead)" "dead herdr endpoint not reported dead"
|
|
1346
|
+
|
|
1347
|
+
pass "herdr endpoint liveness is reported per task: alive for a live pane, dead for a gone one"
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
# --- composition: real scripts run, not reimplemented ------------------------
|
|
1351
|
+
|
|
1352
|
+
test_composition_invokes_real_scripts() {
|
|
1353
|
+
local rec root home fakebin out
|
|
1354
|
+
rec=$(new_world composition)
|
|
1355
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1356
|
+
$rec
|
|
1357
|
+
EOF
|
|
1358
|
+
make_fake_toolchain "$fakebin"
|
|
1359
|
+
make_fake_ps_claude "$fakebin"
|
|
1360
|
+
rm -f "$fakebin/node"
|
|
1361
|
+
|
|
1362
|
+
printf 'needs-decision: pick a library\n' > "$home/state/task-z.status"
|
|
1363
|
+
append_wake "$home/state" signal task-z.status "needs-decision: pick a library"
|
|
1364
|
+
|
|
1365
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1366
|
+
|
|
1367
|
+
# fm-lock.sh's own exact success text.
|
|
1368
|
+
assert_contains "$out" "lock acquired: harness pid" "fm-lock.sh's real output did not appear (composition, not reimplementation)"
|
|
1369
|
+
# fm-bootstrap.sh's own exact MISSING-tool line format.
|
|
1370
|
+
assert_contains "$out" "MISSING: node (install:" "fm-bootstrap.sh's real detect line did not appear verbatim"
|
|
1371
|
+
# fm-wake-drain.sh's real drained record (raw tab-separated queue line).
|
|
1372
|
+
assert_contains "$out" "$(printf 'signal\ttask-z.status\tneeds-decision: pick a library')" "fm-wake-drain.sh's real drained record did not appear"
|
|
1373
|
+
assert_contains "$out" "wake annotation: latest wake-EVENT observed at drain, not current state: task-z.status: needs-decision: pick a library" "fm-session-start.sh did not preserve the drain's separate annotation line"
|
|
1374
|
+
|
|
1375
|
+
pass "fm-session-start.sh composes the real fm-lock.sh, fm-bootstrap.sh, and fm-wake-drain.sh output verbatim"
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
test_branch_outcome_replay_and_lease_sweep() {
|
|
1379
|
+
local rec root home fakebin out
|
|
1380
|
+
rec=$(new_world branch-recovery)
|
|
1381
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1382
|
+
$rec
|
|
1383
|
+
EOF
|
|
1384
|
+
make_fake_toolchain "$fakebin"
|
|
1385
|
+
make_fake_ps_harness "$fakebin" pi
|
|
1386
|
+
|
|
1387
|
+
# A crash window the locked start must close: the supervision branch stored
|
|
1388
|
+
# an outcome durably that never reached main, plus one lease whose
|
|
1389
|
+
# supervising process died and one still held by a live process.
|
|
1390
|
+
FM_HOME="$home" "$ROOT/bin/fm-branch-outcome.sh" append \
|
|
1391
|
+
--task task-b --verdict captain --summary 'PR https://example.com/pr/b checks green' >/dev/null \
|
|
1392
|
+
|| fail "could not seed the unread branch outcome"
|
|
1393
|
+
printf 'branch\t999999\t123\n' > "$home/state/.lease-task-dead"
|
|
1394
|
+
FM_HOME="$home" FM_SUPERVISION_ACTOR=branch FM_LEASE_HOLDER_PID=$$ "$ROOT/bin/fm-lease.sh" claim task-live --actor branch \
|
|
1395
|
+
|| fail "could not seed the live lease"
|
|
1396
|
+
|
|
1397
|
+
out=$(run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1398
|
+
assert_contains "$out" "BRANCH OUTCOMES (handled by the supervision branch, not yet seen by this session):" \
|
|
1399
|
+
"locked start did not replay the unread branch outcome"
|
|
1400
|
+
assert_contains "$out" "https://example.com/pr/b" "replayed outcome lost its content"
|
|
1401
|
+
[ ! -e "$home/state/.lease-task-dead" ] || fail "locked start left a provably dead lease in place"
|
|
1402
|
+
[ -e "$home/state/.lease-task-live" ] || fail "locked start swept a live lease"
|
|
1403
|
+
|
|
1404
|
+
# Replay is one-shot: presenting the digest is the delivery, so the next
|
|
1405
|
+
# locked start stays silent about the same outcome.
|
|
1406
|
+
out=$(run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1407
|
+
case "$out" in
|
|
1408
|
+
*"BRANCH OUTCOMES"*) fail "second start re-presented already-replayed branch outcomes" ;;
|
|
1409
|
+
esac
|
|
1410
|
+
pass "locked Pi session start replays unread branch outcomes once and sweeps only dead leases"
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
test_non_pi_session_start_leaves_branch_state_untouched() {
|
|
1414
|
+
local rec root home fakebin out
|
|
1415
|
+
rec=$(new_world non-pi-branch-recovery)
|
|
1416
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1417
|
+
$rec
|
|
1418
|
+
EOF
|
|
1419
|
+
make_fake_toolchain "$fakebin"
|
|
1420
|
+
make_fake_ps_claude "$fakebin"
|
|
1421
|
+
|
|
1422
|
+
FM_HOME="$home" "$ROOT/bin/fm-branch-outcome.sh" append \
|
|
1423
|
+
--task task-b --verdict captain --summary 'unread Pi branch outcome' >/dev/null \
|
|
1424
|
+
|| fail "could not seed the non-Pi unread branch outcome"
|
|
1425
|
+
rm -f "$home/state/.branch-outcomes-cursor"
|
|
1426
|
+
printf 'branch\t999999\t123\n' > "$home/state/.lease-task-dead"
|
|
1427
|
+
|
|
1428
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1429
|
+
case "$out" in
|
|
1430
|
+
*"BRANCH OUTCOMES"*|*"unread Pi branch outcome"*) fail "non-Pi session replayed Pi branch outcomes" ;;
|
|
1431
|
+
esac
|
|
1432
|
+
[ -e "$home/state/.lease-task-dead" ] || fail "non-Pi session swept a Pi branch lease"
|
|
1433
|
+
[ ! -e "$home/state/.branch-outcomes-cursor" ] || fail "non-Pi session marked a Pi branch outcome read"
|
|
1434
|
+
pass "non-Pi session start neither sweeps nor replays Pi branch state"
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
# --- deferred network stage -------------------------------------------------
|
|
1438
|
+
|
|
1439
|
+
# install_slow_gh <fakebin> <seconds>: one external-network call the digest used
|
|
1440
|
+
# to make directly. Making it pathologically slow is how a test stands in for an
|
|
1441
|
+
# unreachable host without touching one: if any part of the blocking path still
|
|
1442
|
+
# waits on the network, the digest cannot finish before this does.
|
|
1443
|
+
install_slow_gh() {
|
|
1444
|
+
local fakebin=$1 seconds=$2 finished_marker=${3:-}
|
|
1445
|
+
cat > "$fakebin/gh" <<SH
|
|
1446
|
+
#!/usr/bin/env bash
|
|
1447
|
+
if [ "\${1:-}" = auth ]; then
|
|
1448
|
+
sleep $seconds
|
|
1449
|
+
[ -z '$finished_marker' ] || : > '$finished_marker'
|
|
1450
|
+
exit 1
|
|
1451
|
+
fi
|
|
1452
|
+
exit 0
|
|
1453
|
+
SH
|
|
1454
|
+
chmod +x "$fakebin/gh"
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
# The headline guarantee: an unreachable host delays a reported CHECK, never the
|
|
1458
|
+
# startup. The fake host hangs for 12s; the digest must be done long before that,
|
|
1459
|
+
# must say so rather than implying the checks passed, and the sweeps must still
|
|
1460
|
+
# run and land afterwards.
|
|
1461
|
+
test_unreachable_network_never_blocks_the_digest() {
|
|
1462
|
+
local rec root home fakebin mate log spawned network_finished out started elapsed
|
|
1463
|
+
rec=$(prepare_session_start_secondmate secondmate-slow-network)
|
|
1464
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1465
|
+
$rec
|
|
1466
|
+
EOF
|
|
1467
|
+
network_finished="${root%/root}/network-finished"
|
|
1468
|
+
install_slow_gh "$fakebin" 12 "$network_finished"
|
|
1469
|
+
|
|
1470
|
+
started=$(date +%s)
|
|
1471
|
+
out=$(run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" missing)
|
|
1472
|
+
elapsed=$(( $(date +%s) - started ))
|
|
1473
|
+
|
|
1474
|
+
[ ! -e "$network_finished" ] \
|
|
1475
|
+
|| fail "the digest waited for the 12s unreachable-host probe instead of returning from local state (${elapsed}s)"
|
|
1476
|
+
assert_contains "$out" "SESSION START" "the digest did not complete"
|
|
1477
|
+
assert_contains "$out" "IN PROGRESS - the deferred network checks have not finished yet." \
|
|
1478
|
+
"the digest did not disclose that its network checks were still running"
|
|
1479
|
+
assert_contains "$out" "NOT yet confirmed: GitHub authentication, dead-secondmate relaunch" \
|
|
1480
|
+
"the digest did not name the checks it has not confirmed"
|
|
1481
|
+
assert_not_contains "$out" "NEEDS_GH_AUTH" \
|
|
1482
|
+
"the digest reported a GitHub-auth verdict it could not yet have"
|
|
1483
|
+
|
|
1484
|
+
# ... and the work itself still happens, off the blocking path.
|
|
1485
|
+
wait_for_network_stage "$home" "$root" 60 \
|
|
1486
|
+
|| fail "the deferred stage never finished: $(network_stage_report "$home" "$root")"
|
|
1487
|
+
assert_contains "$(network_stage_report "$home" "$root")" "NEEDS_GH_AUTH" \
|
|
1488
|
+
"the deferred stage lost the GitHub-auth verdict it was deferring"
|
|
1489
|
+
assert_contains "$(cat "$log")" "new-window" \
|
|
1490
|
+
"the deferred stage lost the dead-secondmate relaunch"
|
|
1491
|
+
pass "session start: an unreachable host delays a reported check, not the digest"
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
# A result the digest could not print must still reach the agent by itself. The
|
|
1495
|
+
# opposite half of the handshake - a printed result never ALSO queuing a wake -
|
|
1496
|
+
# is asserted deterministically in tests/fm-startup-network.test.sh, where the
|
|
1497
|
+
# claim can be set up directly instead of raced against digest composition.
|
|
1498
|
+
test_deferred_result_reaches_the_agent_when_the_digest_cannot_print_it() {
|
|
1499
|
+
local rec root home fakebin mate log spawned queue
|
|
1500
|
+
rec=$(prepare_session_start_secondmate secondmate-wake-once)
|
|
1501
|
+
IFS='|' read -r root home fakebin mate log spawned <<EOF
|
|
1502
|
+
$rec
|
|
1503
|
+
EOF
|
|
1504
|
+
install_slow_gh "$fakebin" 8
|
|
1505
|
+
queue="$home/state/.wake-queue"
|
|
1506
|
+
|
|
1507
|
+
run_session_start_secondmate "$root" "$home" "$fakebin" "$mate" "$log" "$spawned" missing >/dev/null
|
|
1508
|
+
wait_for_network_stage "$home" "$root" 60 || fail "the deferred stage never finished"
|
|
1509
|
+
wait_for_network_wake "$home" 60 || fail "the deferred stage never settled wake delivery"
|
|
1510
|
+
assert_grep 'check startup-network' "$queue" \
|
|
1511
|
+
"a result the digest could not print never reached the agent: $(cat "$queue" 2>/dev/null)"
|
|
1512
|
+
pass "session start: a deferred result the digest outran still reaches the agent as a wake"
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
# A read-only session has no lock, so it neither owns the mutating sweeps nor has
|
|
1516
|
+
# any action a GitHub-auth verdict would gate. It must say that plainly instead of
|
|
1517
|
+
# quietly dropping the checks.
|
|
1518
|
+
test_read_only_session_declares_skipped_network_checks() {
|
|
1519
|
+
local rec root home fakebin out
|
|
1520
|
+
rec=$(new_world network-read-only)
|
|
1521
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1522
|
+
$rec
|
|
1523
|
+
EOF
|
|
1524
|
+
make_fake_toolchain "$fakebin"
|
|
1525
|
+
make_fake_ps_claude "$fakebin"
|
|
1526
|
+
printf '999999\n' > "$home/state/.lock"
|
|
1527
|
+
cat > "$fakebin/ps" <<'SH'
|
|
1528
|
+
#!/usr/bin/env bash
|
|
1529
|
+
set -u
|
|
1530
|
+
case "$*" in
|
|
1531
|
+
*"-p 999999"*) printf 'claude\n'; exit 0 ;;
|
|
1532
|
+
*"comm="*|*"args="*) printf 'bash\n'; exit 0 ;;
|
|
1533
|
+
esac
|
|
1534
|
+
exit 0
|
|
1535
|
+
SH
|
|
1536
|
+
chmod +x "$fakebin/ps"
|
|
1537
|
+
|
|
1538
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1539
|
+
|
|
1540
|
+
assert_contains "$out" "READ-ONLY SESSION" "the read-only fixture did not actually refuse the lock"
|
|
1541
|
+
assert_contains "$out" "skipped (read-only session) - GitHub authentication" \
|
|
1542
|
+
"a read-only session did not declare its skipped network checks"
|
|
1543
|
+
assert_absent "$home/state/.startup-network.status" \
|
|
1544
|
+
"a read-only session started the deferred stage it has no authority for"
|
|
1545
|
+
pass "session start: a read-only session declares its skipped network checks rather than dropping them"
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
# The compatibility verdict costs three tasks-axi subprocesses and one session
|
|
1549
|
+
# start needs it twice. The digest must pay for it once.
|
|
1550
|
+
test_tasks_axi_compatibility_is_probed_once() {
|
|
1551
|
+
local rec root home fakebin log probes
|
|
1552
|
+
rec=$(new_world tasks-axi-once)
|
|
1553
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1554
|
+
$rec
|
|
1555
|
+
EOF
|
|
1556
|
+
make_fake_toolchain "$fakebin"
|
|
1557
|
+
make_fake_ps_claude "$fakebin"
|
|
1558
|
+
make_fake_tasks_axi_compact "$fakebin"
|
|
1559
|
+
log="$home/tasks-axi.log"
|
|
1560
|
+
printf '# Backlog\n\n## In flight\n\n## Queued\n' > "$home/data/backlog.md"
|
|
1561
|
+
|
|
1562
|
+
FM_FAKE_TASKS_AXI_LOG="$log" run_session_start "$home" "$root" "$fakebin:$BASE_PATH" >/dev/null
|
|
1563
|
+
|
|
1564
|
+
probes=$(grep -c -- '--version' "$log" || true)
|
|
1565
|
+
[ "$probes" -eq 1 ] \
|
|
1566
|
+
|| fail "tasks-axi was version-probed $probes times in one session start: $(cat "$log")"
|
|
1567
|
+
probes=$(grep -c -- 'update --help' "$log" || true)
|
|
1568
|
+
[ "$probes" -eq 1 ] \
|
|
1569
|
+
|| fail "tasks-axi update --help ran $probes times in one session start: $(cat "$log")"
|
|
1570
|
+
assert_grep 'ready --file' "$log" "the backlog listing never ran, so the verdict was not actually reused"
|
|
1571
|
+
pass "session start: the tasks-axi compatibility verdict is computed once and reused"
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
# --- fleet-state digest: compact backlog rendering --------------------------
|
|
1575
|
+
|
|
1576
|
+
# A backlog whose Done section, held row, blocked row, and plain queued rows can
|
|
1577
|
+
# each be told apart in the rendered digest. DONE-ROW-LINE and the *-BODY-LINE
|
|
1578
|
+
# markers exist so a leak is unmistakable.
|
|
1579
|
+
write_long_body_backlog() {
|
|
1580
|
+
local path=$1 i=1
|
|
1581
|
+
cat > "$path" <<'EOF'
|
|
1582
|
+
# Backlog
|
|
1583
|
+
|
|
1584
|
+
## In flight
|
|
1585
|
+
- [ ] compact-startup - Compact startup digest (repo: firstmate) (kind: ship) (since 2026-07-15) (hold: captain choice pending) (hold-kind: captain)
|
|
1586
|
+
OVERSIZED-BODY-LINE current startup leaks task note bodies into the session digest.
|
|
1587
|
+
Another long body line that should not be printed after the fix.
|
|
1588
|
+
|
|
1589
|
+
## Queued
|
|
1590
|
+
- [ ] blocked-followup - Follow compact startup blocked-by: compact-startup - waits for implementation (repo: firstmate) (kind: scout) (since 2026-07-15)
|
|
1591
|
+
QUEUED-BODY-LINE this is another long multiline note.
|
|
1592
|
+
- [ ] held-queued - Held queued work (repo: firstmate) (kind: ship) (hold: captain choice pending) (hold-kind: captain)
|
|
1593
|
+
EOF
|
|
1594
|
+
while [ "$i" -le 25 ]; do
|
|
1595
|
+
printf -- '- [ ] plain-%s - Plain queued item %s (repo: firstmate) (kind: ship)\n' "$i" "$i" >> "$path"
|
|
1596
|
+
i=$((i + 1))
|
|
1597
|
+
done
|
|
1598
|
+
cat >> "$path" <<'EOF'
|
|
1599
|
+
|
|
1600
|
+
## Done
|
|
1601
|
+
- [x] landed-earlier - DONE-ROW-LINE already landed and torn down (repo: firstmate) (kind: ship)
|
|
1602
|
+
EOF
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
test_backlog_compact_tasks_axi_omits_bodies_and_keeps_metadata() {
|
|
1606
|
+
local rec root home fakebin out log
|
|
1607
|
+
rec=$(new_world backlog-compact-tasks-axi)
|
|
1608
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1609
|
+
$rec
|
|
1610
|
+
EOF
|
|
1611
|
+
make_fake_toolchain "$fakebin"
|
|
1612
|
+
make_fake_tasks_axi_compact "$fakebin"
|
|
1613
|
+
make_fake_ps_claude "$fakebin"
|
|
1614
|
+
write_long_body_backlog "$home/data/backlog.md"
|
|
1615
|
+
mkdir -p "$home/projects/firstmate"
|
|
1616
|
+
printf 'window=fm-sess:compact\nworktree=%s\nproject=firstmate\nkind=ship\n' "$home/projects/firstmate" \
|
|
1617
|
+
> "$home/state/compact-startup.meta"
|
|
1618
|
+
log="$home/tasks-axi.log"
|
|
1619
|
+
|
|
1620
|
+
out=$(FM_FAKE_TASKS_AXI_LOG="$log" FM_FAKE_TASKS_AXI_READY=3 \
|
|
1621
|
+
run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1622
|
+
|
|
1623
|
+
assert_contains "$out" "compact backlog listing (tasks-axi; done rows omitted; every in-flight, held, and blocked row shown in full; ready queued bounded to 20; task bodies omitted)" \
|
|
1624
|
+
"compatible tasks-axi backend did not render the compact backlog listing"
|
|
1625
|
+
assert_contains "$out" "tasks[1]{id,state,kind,repo,title,blocked_by,hold_kind,hold_reason}:" \
|
|
1626
|
+
"tasks-axi compact listing omitted the expected structured field header"
|
|
1627
|
+
assert_contains "$out" "compact-startup,in_flight,ship,firstmate,Compact startup digest,none,captain,captain choice pending" \
|
|
1628
|
+
"tasks-axi compact listing omitted in-flight identity, state, or hold metadata"
|
|
1629
|
+
assert_contains "$out" "held-queued,queued,ship,firstmate,Held queued work,none,captain,captain choice pending" \
|
|
1630
|
+
"tasks-axi compact listing omitted a held row or its hold metadata"
|
|
1631
|
+
assert_contains "$out" 'blocked-followup,queued,scout,firstmate,Follow compact startup,compact-startup,"-","-"' \
|
|
1632
|
+
"tasks-axi compact listing omitted blocked-by metadata"
|
|
1633
|
+
assert_contains "$out" "ready-3,queued,ship,firstmate,Ready item 3" \
|
|
1634
|
+
"tasks-axi compact listing omitted a dispatchable queued row inside the bound"
|
|
1635
|
+
assert_not_contains "$out" "OVERSIZED-BODY-LINE" "tasks-axi compact digest leaked an in-flight task body"
|
|
1636
|
+
assert_not_contains "$out" "QUEUED-BODY-LINE" "tasks-axi compact digest leaked a queued task body"
|
|
1637
|
+
assert_not_contains "$out" "DONE-ROW-LINE" "tasks-axi compact digest listed a done row at startup"
|
|
1638
|
+
assert_contains "$out" "--- compact-startup ---" "in-flight meta identity disappeared from startup recovery digest"
|
|
1639
|
+
assert_contains "$out" "worktree=$home/projects/firstmate" "in-flight recovery worktree identity disappeared from startup digest"
|
|
1640
|
+
assert_contains "$out" "Full task bodies remain available on demand: tasks-axi show <id> --full" \
|
|
1641
|
+
"compact digest omitted the full-body lookup pointer"
|
|
1642
|
+
assert_contains "$out" "ready_public_followups: 0 delivery-ready obligations" \
|
|
1643
|
+
"the composed listing dropped a real signal from the dispatchable set"
|
|
1644
|
+
# One section pointer, not one repeated help block per composed group.
|
|
1645
|
+
assert_not_contains "$out" "help[1]:" \
|
|
1646
|
+
"the composed listing repeated tasks-axi's per-group help block"
|
|
1647
|
+
|
|
1648
|
+
# The fake refuses a body field, an unfiltered listing, and a done listing, so
|
|
1649
|
+
# a clean render already proves those were never asked for; pin the group
|
|
1650
|
+
# filters the listing is built from.
|
|
1651
|
+
assert_grep "--state in_flight --fields blocked_by,hold_kind,hold_reason" "$log" \
|
|
1652
|
+
"session start did not ask tasks-axi for the in-flight group"
|
|
1653
|
+
assert_grep "--state held --fields blocked_by,hold_kind,hold_reason" "$log" \
|
|
1654
|
+
"session start did not ask tasks-axi for the held group"
|
|
1655
|
+
assert_grep "--state queued --blocked --fields blocked_by,hold_kind,hold_reason" "$log" \
|
|
1656
|
+
"session start did not ask tasks-axi for the blocked queued group"
|
|
1657
|
+
assert_grep "ready --file $home/data/backlog.md" "$log" \
|
|
1658
|
+
"session start did not ask tasks-axi for the dispatchable queued set"
|
|
1659
|
+
|
|
1660
|
+
pass "compatible tasks-axi backlog rendering drops done rows and keeps every in-flight, held, and blocked row"
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
# The bound may only ever cut the dispatchable-now listing, and whatever it cuts
|
|
1664
|
+
# must be disclosed with an exact count and the command that shows the rest.
|
|
1665
|
+
test_backlog_queued_bound_discloses_its_remainder() {
|
|
1666
|
+
local rec root home fakebin out
|
|
1667
|
+
rec=$(new_world backlog-queued-bound)
|
|
1668
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1669
|
+
$rec
|
|
1670
|
+
EOF
|
|
1671
|
+
make_fake_toolchain "$fakebin"
|
|
1672
|
+
make_fake_tasks_axi_compact "$fakebin"
|
|
1673
|
+
make_fake_ps_claude "$fakebin"
|
|
1674
|
+
write_long_body_backlog "$home/data/backlog.md"
|
|
1675
|
+
|
|
1676
|
+
out=$(FM_FAKE_TASKS_AXI_READY=7 FM_SESSION_START_QUEUED_LIMIT=3 \
|
|
1677
|
+
run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1678
|
+
|
|
1679
|
+
assert_contains "$out" "ready-3,queued,ship,firstmate,Ready item 3" \
|
|
1680
|
+
"the queued bound dropped a row inside its own limit"
|
|
1681
|
+
assert_not_contains "$out" "ready-4,queued" "the queued bound did not actually bound the ready listing"
|
|
1682
|
+
assert_contains "$out" "(shown 3 of 7 ready queued item(s))" \
|
|
1683
|
+
"the bounded queued listing did not report what it showed"
|
|
1684
|
+
assert_contains "$out" "(4 more queued - tasks-axi ready --file $home/data/backlog.md)" \
|
|
1685
|
+
"the bounded queued listing did not disclose an exact remainder and how to see it"
|
|
1686
|
+
|
|
1687
|
+
# The bound is for dispatchable work only: held and blocked rows stay whole.
|
|
1688
|
+
assert_contains "$out" "held-queued,queued,ship,firstmate,Held queued work,none,captain,captain choice pending" \
|
|
1689
|
+
"the queued bound swallowed a held row"
|
|
1690
|
+
assert_contains "$out" 'blocked-followup,queued,scout,firstmate,Follow compact startup,compact-startup,"-","-"' \
|
|
1691
|
+
"the queued bound swallowed a blocked row"
|
|
1692
|
+
assert_contains "$out" "compact-startup,in_flight,ship,firstmate,Compact startup digest,none,captain,captain choice pending" \
|
|
1693
|
+
"the queued bound swallowed an in-flight row"
|
|
1694
|
+
|
|
1695
|
+
pass "the startup backlog bound cuts only dispatchable queued rows and discloses the remainder exactly"
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
test_backlog_compact_manual_backend_skips_indented_bodies() {
|
|
1699
|
+
local rec root home fakebin out
|
|
1700
|
+
rec=$(new_world backlog-compact-manual)
|
|
1701
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1702
|
+
$rec
|
|
1703
|
+
EOF
|
|
1704
|
+
make_fake_toolchain "$fakebin"
|
|
1705
|
+
make_fake_ps_claude "$fakebin"
|
|
1706
|
+
printf '%s\n' manual > "$home/config/backlog-backend"
|
|
1707
|
+
write_long_body_backlog "$home/data/backlog.md"
|
|
1708
|
+
|
|
1709
|
+
out=$(FM_SESSION_START_QUEUED_LIMIT=4 run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1710
|
+
|
|
1711
|
+
assert_contains "$out" "compact backlog listing (manual backend; done rows omitted; every in-flight, held, and blocked title line kept; other queued bounded to 4; indented task bodies omitted)" \
|
|
1712
|
+
"manual backend did not use compact title-line rendering"
|
|
1713
|
+
assert_contains "$out" "## In flight" "manual compact rendering omitted the in-flight section heading"
|
|
1714
|
+
assert_contains "$out" "- [ ] compact-startup - Compact startup digest" \
|
|
1715
|
+
"manual compact rendering omitted the in-flight title line"
|
|
1716
|
+
assert_contains "$out" "(hold: captain choice pending) (hold-kind: captain)" \
|
|
1717
|
+
"manual compact rendering omitted hold metadata"
|
|
1718
|
+
assert_contains "$out" "blocked-by: compact-startup - waits for implementation" \
|
|
1719
|
+
"manual compact rendering omitted blocker metadata"
|
|
1720
|
+
assert_contains "$out" "- [ ] held-queued - Held queued work" \
|
|
1721
|
+
"manual compact rendering dropped a held queued title line"
|
|
1722
|
+
assert_not_contains "$out" "OVERSIZED-BODY-LINE" "manual compact digest leaked an in-flight task body"
|
|
1723
|
+
assert_not_contains "$out" "QUEUED-BODY-LINE" "manual compact digest leaked a queued task body"
|
|
1724
|
+
assert_not_contains "$out" "DONE-ROW-LINE" "manual compact digest listed a done row at startup"
|
|
1725
|
+
assert_not_contains "$out" "## Done" "manual compact digest printed the done heading it never fills"
|
|
1726
|
+
assert_contains "$out" "- [ ] plain-4 - Plain queued item 4" \
|
|
1727
|
+
"manual compact rendering dropped a queued title line inside its bound"
|
|
1728
|
+
assert_not_contains "$out" "- [ ] plain-5 - Plain queued item 5" \
|
|
1729
|
+
"manual compact rendering did not bound its plain queued listing"
|
|
1730
|
+
assert_contains "$out" "(shown 1 in-flight, 2 held or blocked queued, 4 of 25 other queued title line(s); 1 done row(s) omitted)" \
|
|
1731
|
+
"manual compact rendering did not report its bound accounting"
|
|
1732
|
+
assert_contains "$out" "(21 more queued - raise FM_SESSION_START_QUEUED_LIMIT or read data/backlog.md for the rest)" \
|
|
1733
|
+
"manual compact rendering did not disclose an exact queued remainder"
|
|
1734
|
+
assert_contains "$out" "or data/backlog.md" "manual compact digest omitted the data/backlog.md full-body pointer"
|
|
1735
|
+
|
|
1736
|
+
pass "manual backlog rendering drops done rows, keeps every held or blocked title line, and bounds the rest"
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
test_backlog_compact_tasks_axi_unavailable_uses_manual_fallback() {
|
|
1740
|
+
local rec root home fakebin out
|
|
1741
|
+
rec=$(new_world backlog-compact-unavailable)
|
|
1742
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1743
|
+
$rec
|
|
1744
|
+
EOF
|
|
1745
|
+
make_fake_toolchain "$fakebin"
|
|
1746
|
+
make_fake_ps_claude "$fakebin"
|
|
1747
|
+
write_long_body_backlog "$home/data/backlog.md"
|
|
1748
|
+
|
|
1749
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1750
|
+
|
|
1751
|
+
assert_contains "$out" "compact backlog listing (tasks-axi unavailable or incompatible; done rows omitted;" \
|
|
1752
|
+
"unavailable tasks-axi did not fall back to compact title-line rendering"
|
|
1753
|
+
assert_contains "$out" "- [ ] compact-startup - Compact startup digest" \
|
|
1754
|
+
"unavailable tasks-axi fallback omitted a backlog title line"
|
|
1755
|
+
assert_not_contains "$out" "OVERSIZED-BODY-LINE" "unavailable tasks-axi fallback leaked an in-flight task body"
|
|
1756
|
+
assert_not_contains "$out" "DONE-ROW-LINE" "unavailable tasks-axi fallback listed a done row at startup"
|
|
1757
|
+
|
|
1758
|
+
pass "unavailable or incompatible tasks-axi falls back to compact manual backlog rendering"
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
# --- runtime bound -----------------------------------------------------------
|
|
1762
|
+
#
|
|
1763
|
+
# The digest runs on a session-open hook that blocks session initialization, so
|
|
1764
|
+
# it must have a guaranteed upper bound. These cases drive REAL processes that
|
|
1765
|
+
# really hang, and assert the outcome the hook depends on: whatever the digest
|
|
1766
|
+
# already emitted survives, the agent is told exactly what it never saw, and
|
|
1767
|
+
# the command still exits 0 so the session can open.
|
|
1768
|
+
|
|
1769
|
+
# make_hanging_tool <fakebin> <name>: a real, unkillable-by-timeout-alone
|
|
1770
|
+
# subprocess of the digest. `git` is the honest choice - the bootstrap stage
|
|
1771
|
+
# shells out to it - and it also proves the bound reaches a GRANDCHILD, because
|
|
1772
|
+
# bootstrap runs it inside its own command substitution.
|
|
1773
|
+
make_hanging_tool() {
|
|
1774
|
+
local fakebin=$1 name=$2
|
|
1775
|
+
cat > "$fakebin/$name" <<'SH'
|
|
1776
|
+
#!/usr/bin/env bash
|
|
1777
|
+
trap '' TERM
|
|
1778
|
+
sleep 600
|
|
1779
|
+
SH
|
|
1780
|
+
chmod +x "$fakebin/$name"
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
make_term_escalating_timeout() {
|
|
1784
|
+
local fakebin=$1
|
|
1785
|
+
cat > "$fakebin/timeout" <<'SH'
|
|
1786
|
+
#!/usr/bin/env perl
|
|
1787
|
+
use strict;
|
|
1788
|
+
use warnings;
|
|
1789
|
+
(shift @ARGV) eq '-k' or exit 64;
|
|
1790
|
+
my $kill_after = shift @ARGV;
|
|
1791
|
+
my $seconds = shift @ARGV;
|
|
1792
|
+
my $pid = fork;
|
|
1793
|
+
defined $pid or die "fork failed";
|
|
1794
|
+
if (!$pid) {
|
|
1795
|
+
setpgrp(0, 0);
|
|
1796
|
+
exec @ARGV;
|
|
1797
|
+
}
|
|
1798
|
+
local $SIG{ALRM} = sub {
|
|
1799
|
+
kill 'TERM', -$pid;
|
|
1800
|
+
select undef, undef, undef, $kill_after;
|
|
1801
|
+
kill 'KILL', -$pid;
|
|
1802
|
+
waitpid $pid, 0;
|
|
1803
|
+
exit 137;
|
|
1804
|
+
};
|
|
1805
|
+
alarm $seconds;
|
|
1806
|
+
waitpid $pid, 0;
|
|
1807
|
+
alarm 0;
|
|
1808
|
+
exit($? >> 8);
|
|
1809
|
+
SH
|
|
1810
|
+
chmod +x "$fakebin/timeout"
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
test_runtime_bound_truncates_loudly_and_exits_zero() {
|
|
1814
|
+
local rec root home fakebin out status=0 stray mechanism
|
|
1815
|
+
rec=$(new_world runtime-bound)
|
|
1816
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1817
|
+
$rec
|
|
1818
|
+
EOF
|
|
1819
|
+
make_fake_toolchain "$fakebin"
|
|
1820
|
+
make_fake_ps_claude "$fakebin"
|
|
1821
|
+
make_hanging_tool "$fakebin" git
|
|
1822
|
+
|
|
1823
|
+
mechanism=$(FM_TIMEOUT_MECHANISM_OVERRIDE=bash bash -c '. "$1"; fm_timeout_mechanism' \
|
|
1824
|
+
_ "$ROOT/bin/fm-timeout-lib.sh")
|
|
1825
|
+
[ "$mechanism" = bash ] || fail "the forced pure-Bash timeout fixture selected '$mechanism'"
|
|
1826
|
+
|
|
1827
|
+
out=$(FM_TIMEOUT_MECHANISM_OVERRIDE=bash FM_SESSION_START_TIMEOUT=3 FM_STARTUP_NETWORK_TIMEOUT=2 \
|
|
1828
|
+
run_session_start "$home" "$root" "$fakebin:$BASE_PATH") || status=$?
|
|
1829
|
+
|
|
1830
|
+
expect_code 0 "$status" "a truncated session start must still exit 0 so the session can open"
|
|
1831
|
+
assert_contains "$out" "SESSION START - $home" "the truncated digest lost the output it had already produced"
|
|
1832
|
+
assert_contains "$out" "LOCK" "the truncated digest lost a stage that had completed"
|
|
1833
|
+
assert_contains "$out" "STARTUP TRUNCATED - SESSION START HIT ITS" "a truncated session start did not say so"
|
|
1834
|
+
assert_contains "$out" "RUNTIME BOUND" "the truncation banner did not name the bound it hit"
|
|
1835
|
+
assert_contains "$out" 'stopped during the "bootstrap" stage' "the truncation banner did not name the incomplete stage"
|
|
1836
|
+
assert_contains "$out" "RECONCILE these stages" "the truncation banner did not tell the agent what to reconcile"
|
|
1837
|
+
assert_contains "$out" "wake-queue supervision-instructions read-once fleet-state network-checks context next-step" \
|
|
1838
|
+
"the truncation banner did not list every stage that never ran"
|
|
1839
|
+
assert_not_contains "$out" "NEXT STEP" "a truncated digest claimed to have reached its closing reminder"
|
|
1840
|
+
assert_absent "$home/state/.session-start-complete" \
|
|
1841
|
+
"a truncated startup recorded itself as complete"
|
|
1842
|
+
|
|
1843
|
+
# The bound must reach the whole process group: a hung grandchild that
|
|
1844
|
+
# outlives the digest would keep holding whatever the digest was waiting on.
|
|
1845
|
+
# There are now TWO bounds, deliberately independent - the digest's, and the
|
|
1846
|
+
# deferred network stage's own - because a truncated digest must not kill work
|
|
1847
|
+
# it was never waiting for. So the guarantee asserted here is the one that
|
|
1848
|
+
# actually matters: once BOTH deadlines have passed, nothing hung is left.
|
|
1849
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" FM_STARTUP_NETWORK_TIMEOUT=2 \
|
|
1850
|
+
"$ROOT/bin/fm-startup-network.sh" wait 30 >/dev/null || true
|
|
1851
|
+
sleep 1
|
|
1852
|
+
stray=$(pgrep -f "$fakebin/git" 2>/dev/null | wc -l | tr -d ' ')
|
|
1853
|
+
[ "$stray" -eq 0 ] || fail "the runtime bound left $stray hung subprocess(es) behind"
|
|
1854
|
+
|
|
1855
|
+
status=0
|
|
1856
|
+
FM_TIMEOUT_MECHANISM_OVERRIDE=bash bash -c \
|
|
1857
|
+
'. "$1"; fm_run_timed 2 bash -c "exit 137"' _ "$ROOT/bin/fm-timeout-lib.sh" || status=$?
|
|
1858
|
+
expect_code 137 "$status" "pure-Bash natural command exit 137"
|
|
1859
|
+
|
|
1860
|
+
pass "the pure-Bash watchdog bounds session start, kills its hung grandchild, and emits the truncation contract"
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
test_portable_timeout_escalates_term_resistant_process() {
|
|
1864
|
+
local fakebin="$TMP_ROOT/portable-kill-after" driver status=0
|
|
1865
|
+
mkdir -p "$fakebin"
|
|
1866
|
+
make_term_escalating_timeout "$fakebin"
|
|
1867
|
+
driver="$TMP_ROOT/portable-kill-after-driver.sh"
|
|
1868
|
+
cat > "$driver" <<'SH'
|
|
1869
|
+
#!/usr/bin/env bash
|
|
1870
|
+
. "$1"
|
|
1871
|
+
shift
|
|
1872
|
+
fm_run_timed 1 "$@"
|
|
1873
|
+
SH
|
|
1874
|
+
chmod +x "$driver"
|
|
1875
|
+
|
|
1876
|
+
perl -e '
|
|
1877
|
+
my $pid = fork;
|
|
1878
|
+
die "fork failed" unless defined $pid;
|
|
1879
|
+
if (!$pid) { setpgrp(0, 0); exec @ARGV }
|
|
1880
|
+
local $SIG{ALRM} = sub { kill "KILL", -$pid; waitpid $pid, 0; exit 99 };
|
|
1881
|
+
alarm 5;
|
|
1882
|
+
waitpid $pid, 0;
|
|
1883
|
+
exit($? >> 8);
|
|
1884
|
+
' env PATH="$fakebin:$BASE_PATH" "$driver" "$ROOT/bin/fm-timeout-lib.sh" \
|
|
1885
|
+
perl -e '$SIG{TERM} = "IGNORE"; sleep 600' || status=$?
|
|
1886
|
+
|
|
1887
|
+
expect_code 124 "$status" "portable timeout TERM-resistant escalation"
|
|
1888
|
+
status=0
|
|
1889
|
+
env PATH="$fakebin:$BASE_PATH" "$driver" "$ROOT/bin/fm-timeout-lib.sh" \
|
|
1890
|
+
bash -c 'exit 137' || status=$?
|
|
1891
|
+
expect_code 137 "$status" "natural command exit 137"
|
|
1892
|
+
pass "the portable timeout path force-kills a command that ignores TERM"
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
test_runtime_bound_leaves_a_healthy_digest_untouched() {
|
|
1896
|
+
local rec root home fakebin out
|
|
1897
|
+
rec=$(new_world runtime-bound-healthy)
|
|
1898
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1899
|
+
$rec
|
|
1900
|
+
EOF
|
|
1901
|
+
make_fake_toolchain "$fakebin"
|
|
1902
|
+
make_fake_ps_claude "$fakebin"
|
|
1903
|
+
|
|
1904
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
1905
|
+
|
|
1906
|
+
# The banner line itself, not the phrase: the read-once contract names the
|
|
1907
|
+
# banner as the condition that voids it, and that mention is not a banner.
|
|
1908
|
+
assert_not_contains "$out" "STARTUP TRUNCATED - SESSION START HIT ITS" \
|
|
1909
|
+
"a digest that finished in time reported itself truncated"
|
|
1910
|
+
assert_contains "$out" "NEXT STEP" "a digest that finished in time lost its closing reminder"
|
|
1911
|
+
assert_absent "${TMPDIR:-/tmp}/fm-session-start-stage" "the stage breadcrumb leaked a fixed-name file"
|
|
1912
|
+
|
|
1913
|
+
pass "a session start inside its budget prints no truncation banner"
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
test_runtime_bound_leaves_harness_ancestry_headroom() {
|
|
1917
|
+
local rec root home fakebin nest out
|
|
1918
|
+
rec=$(new_world runtime-bound-ancestry)
|
|
1919
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1920
|
+
$rec
|
|
1921
|
+
EOF
|
|
1922
|
+
make_fake_toolchain "$fakebin"
|
|
1923
|
+
|
|
1924
|
+
# Only ONE pid in the whole tree is the harness, and it sits at the very top.
|
|
1925
|
+
# fm-session-lock-lib.sh walks a BOUNDED sixteen parents to find it, and the
|
|
1926
|
+
# runtime bound spends some of that budget on its own wrapper processes, so
|
|
1927
|
+
# this pins that the budget still reaches a realistically deep session.
|
|
1928
|
+
cat > "$fakebin/ps" <<'SH'
|
|
1929
|
+
#!/usr/bin/env bash
|
|
1930
|
+
set -u
|
|
1931
|
+
pid=
|
|
1932
|
+
previous=
|
|
1933
|
+
for argument in "$@"; do
|
|
1934
|
+
[ "$previous" = -p ] && pid=$argument
|
|
1935
|
+
previous=$argument
|
|
1936
|
+
done
|
|
1937
|
+
case "$*" in
|
|
1938
|
+
*"comm="*)
|
|
1939
|
+
if [ "$pid" = "${FM_FAKE_HARNESS_PID:-}" ]; then printf '%s\n' /usr/local/bin/claude
|
|
1940
|
+
else printf '%s\n' /bin/bash; fi
|
|
1941
|
+
;;
|
|
1942
|
+
*"args="*)
|
|
1943
|
+
if [ "$pid" = "${FM_FAKE_HARNESS_PID:-}" ]; then printf '%s\n' claude
|
|
1944
|
+
else printf '%s\n' bash; fi
|
|
1945
|
+
;;
|
|
1946
|
+
*"ppid="*) /bin/ps -o ppid= -p "$pid" ;;
|
|
1947
|
+
*) exit 1 ;;
|
|
1948
|
+
esac
|
|
1949
|
+
SH
|
|
1950
|
+
chmod +x "$fakebin/ps"
|
|
1951
|
+
|
|
1952
|
+
# Each level forks rather than execs, so the counter really is process depth.
|
|
1953
|
+
nest="$home/nest.sh"
|
|
1954
|
+
cat > "$nest" <<'SH'
|
|
1955
|
+
#!/usr/bin/env bash
|
|
1956
|
+
set -u
|
|
1957
|
+
levels=$1
|
|
1958
|
+
shift
|
|
1959
|
+
if [ "$levels" -gt 0 ]; then
|
|
1960
|
+
bash "$0" $((levels - 1)) "$@"
|
|
1961
|
+
exit $?
|
|
1962
|
+
fi
|
|
1963
|
+
exec "$@"
|
|
1964
|
+
SH
|
|
1965
|
+
chmod +x "$nest"
|
|
1966
|
+
|
|
1967
|
+
# shellcheck disable=SC2016 # $$ must expand in the launched shell, not here.
|
|
1968
|
+
out=$(env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
1969
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$fakebin:$BASE_PATH" \
|
|
1970
|
+
bash -c 'export FM_FAKE_HARNESS_PID=$$; exec "$1" 8 "$2"' _ "$nest" "$SESSION_START")
|
|
1971
|
+
|
|
1972
|
+
assert_contains "$out" "lock acquired: harness pid" \
|
|
1973
|
+
"the runtime bound's wrapper processes pushed the harness out of the bounded ancestry walk"
|
|
1974
|
+
assert_not_contains "$out" "READ-ONLY SESSION" \
|
|
1975
|
+
"a session start eight shells below its harness was wrongly refused the lock"
|
|
1976
|
+
|
|
1977
|
+
pass "the runtime bound leaves enough ancestry headroom for a deeply nested session to take the lock"
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
# --- context re-emit (--reemit) ----------------------------------------------
|
|
1981
|
+
|
|
1982
|
+
test_reemit_skips_startup_sweeps_but_keeps_the_wake_drain() {
|
|
1983
|
+
local rec root home fakebin network_report reemit sequence generation
|
|
1984
|
+
rec=$(new_world reemit)
|
|
1985
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
1986
|
+
$rec
|
|
1987
|
+
EOF
|
|
1988
|
+
make_fake_toolchain "$fakebin"
|
|
1989
|
+
make_fake_ps_claude "$fakebin"
|
|
1990
|
+
mkdir -p "$home/other-secondmate/state"
|
|
1991
|
+
fm_write_secondmate_meta "$home/state/sm-r.meta" "$home/other-secondmate" "firstmate:fm-sm-r" alpha
|
|
1992
|
+
append_wake "$home/state" signal task-r "done: queued after startup" || fail "seed wake failed"
|
|
1993
|
+
|
|
1994
|
+
# A full startup reconciles the secondmate sweep and reports it.
|
|
1995
|
+
FM_FAKE_HARNESS_PID=$$ run_session_start "$home" "$root" "$fakebin:$BASE_PATH" >/dev/null
|
|
1996
|
+
wait_for_network_stage "$home" "$root" \
|
|
1997
|
+
|| fail "the full startup fixture's deferred network stage never published"
|
|
1998
|
+
network_report=$(network_stage_report "$home" "$root")
|
|
1999
|
+
assert_contains "$network_report" "SECONDMATE_LIVENESS" \
|
|
2000
|
+
"the full startup fixture did not exercise a mutating sweep"
|
|
2001
|
+
|
|
2002
|
+
append_wake "$home/state" signal task-r "done: queued after the re-emit too" || fail "seed second wake failed"
|
|
2003
|
+
reemit=$(FM_HOME="$home" FM_ROOT_OVERRIDE="$root" FM_FAKE_HARNESS_PID=$$ PATH="$fakebin:$BASE_PATH" \
|
|
2004
|
+
env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
2005
|
+
"$SESSION_START" --reemit)
|
|
2006
|
+
|
|
2007
|
+
assert_contains "$reemit" "SESSION START (CONTEXT RE-EMIT) - $home" "--reemit did not label itself"
|
|
2008
|
+
assert_not_contains "$reemit" "SECONDMATE_LIVENESS" "--reemit repeated a mutating sweep startup already ran"
|
|
2009
|
+
assert_contains "$reemit" "done: queued after the re-emit too" "--reemit did not drain the wake queue"
|
|
2010
|
+
[ -s "$home/state/.wake-queue" ] || fail "--reemit removed the wake before its handling acknowledgement"
|
|
2011
|
+
sequence=$(printf '%s\n' "$reemit" | sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through \([0-9][0-9]*\) --recovery-generation [A-Za-z0-9._-][A-Za-z0-9._-]*$/\1/p' | tail -1)
|
|
2012
|
+
generation=$(printf '%s\n' "$reemit" | sed -n 's/^WAKE_ACK_REQUIRED:.*--ack-through [0-9][0-9]* --recovery-generation \([A-Za-z0-9._-][A-Za-z0-9._-]*\)$/\1/p' | tail -1)
|
|
2013
|
+
[ -n "$sequence" ] && [ -n "$generation" ] \
|
|
2014
|
+
|| fail "--reemit omitted the generation-bound wake acknowledgement"
|
|
2015
|
+
FM_STATE_OVERRIDE="$home/state" "$ROOT/bin/fm-wake-drain.sh" --ack-through "$sequence" \
|
|
2016
|
+
--recovery-generation "$generation" || fail "--reemit wake acknowledgement failed"
|
|
2017
|
+
[ ! -s "$home/state/.wake-queue" ] || fail "--reemit acknowledgement left queued wakes behind"
|
|
2018
|
+
assert_contains "$reemit" "CONTEXT" "--reemit dropped the context digest"
|
|
2019
|
+
assert_contains "$reemit" "FLEET STATE" "--reemit dropped the fleet-state digest"
|
|
2020
|
+
assert_contains "$reemit" "NEXT STEP" "--reemit dropped the closing reminder"
|
|
2021
|
+
|
|
2022
|
+
pass "--reemit reprints the digest without repeating startup's mutating sweeps and still drains queued wakes"
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
test_agents_baseline_stays_at_true_start_and_reemits_on_every_drifted_pi_compact() {
|
|
2026
|
+
local rec root home fakebin startup compact_equal compact_first compact_second clear_out resume_out reset_out baseline baseline_after expected_hash refresh_line bootstrap_line
|
|
2027
|
+
rec=$(new_world agents-refresh)
|
|
2028
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2029
|
+
$rec
|
|
2030
|
+
EOF
|
|
2031
|
+
make_fake_toolchain "$fakebin"
|
|
2032
|
+
make_fake_ps_harness "$fakebin" pi
|
|
2033
|
+
cat > "$root/AGENTS.md" <<'EOF'
|
|
2034
|
+
FIRSTMATE_TEST_INSTRUCTION=original
|
|
2035
|
+
Keep this original instruction.
|
|
2036
|
+
EOF
|
|
2037
|
+
|
|
2038
|
+
startup=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --source startup)
|
|
2039
|
+
assert_contains "$startup" "SESSION START - $home" "true startup did not run the full digest"
|
|
2040
|
+
assert_present "$home/state/.session-start-agents-baseline" "true startup did not record an AGENTS baseline"
|
|
2041
|
+
baseline=$(cat "$home/state/.session-start-agents-baseline")
|
|
2042
|
+
expected_hash=$(hash_file_for_test "$root/AGENTS.md")
|
|
2043
|
+
[ "$(printf '%s\n' "$baseline" | sed -n '2p')" = "$expected_hash" ] \
|
|
2044
|
+
|| fail "true startup baseline did not record the original AGENTS hash: $baseline"
|
|
2045
|
+
|
|
2046
|
+
compact_equal=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2047
|
+
assert_not_contains "$compact_equal" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2048
|
+
"an unchanged AGENTS file was unnecessarily re-emitted"
|
|
2049
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2050
|
+
|| fail "a no-drift compact rewrote the true-start baseline"
|
|
2051
|
+
|
|
2052
|
+
cat > "$root/AGENTS.md" <<'EOF'
|
|
2053
|
+
FIRSTMATE_TEST_INSTRUCTION=updated
|
|
2054
|
+
The complete updated instruction must survive every stale rebuild.
|
|
2055
|
+
EOF
|
|
2056
|
+
resume_out=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --source resume)
|
|
2057
|
+
assert_not_contains "$resume_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2058
|
+
"a context-preserving continuation emitted a replacement contract"
|
|
2059
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2060
|
+
|| fail "a context-preserving continuation rebased the true-start baseline"
|
|
2061
|
+
|
|
2062
|
+
compact_first=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2063
|
+
assert_contains "$compact_first" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2064
|
+
"a drifted Pi compact did not emit the replacement instructions"
|
|
2065
|
+
assert_contains "$compact_first" "FIRSTMATE_TEST_INSTRUCTION=updated" \
|
|
2066
|
+
"a drifted Pi compact did not emit the complete current AGENTS content"
|
|
2067
|
+
refresh_line=$(printf '%s\n' "$compact_first" | grep -n '^CURRENT AGENTS.md - INSTRUCTION REFRESH$' | head -1 | cut -d: -f1)
|
|
2068
|
+
bootstrap_line=$(printf '%s\n' "$compact_first" | grep -n '^BOOTSTRAP$' | head -1 | cut -d: -f1)
|
|
2069
|
+
[ -n "$refresh_line" ] && [ -n "$bootstrap_line" ] && [ "$refresh_line" -lt "$bootstrap_line" ] \
|
|
2070
|
+
|| fail "replacement instructions were not emitted before the bulky digest"
|
|
2071
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2072
|
+
|| fail "a drifted compact rebased the original-session baseline"
|
|
2073
|
+
|
|
2074
|
+
compact_second=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2075
|
+
assert_contains "$compact_second" "FIRSTMATE_TEST_INSTRUCTION=updated" \
|
|
2076
|
+
"a second drifted compact suppressed the required replacement instructions"
|
|
2077
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2078
|
+
|| fail "a repeated compact rebased the original-session baseline"
|
|
2079
|
+
|
|
2080
|
+
clear_out=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source clear)
|
|
2081
|
+
assert_not_contains "$clear_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2082
|
+
"a Pi clear, which creates a fresh runtime, unnecessarily emitted a replacement contract"
|
|
2083
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2084
|
+
|| fail "a clear rebuild rebased the original-session baseline"
|
|
2085
|
+
|
|
2086
|
+
reset_out=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --source reset)
|
|
2087
|
+
assert_not_contains "$reset_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2088
|
+
"an unrecognized reset source emitted a replacement contract"
|
|
2089
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2090
|
+
|| fail "reset rebased the original-session baseline"
|
|
2091
|
+
|
|
2092
|
+
rm -f "$home/state/.session-start-agents-baseline"
|
|
2093
|
+
compact_first=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2094
|
+
assert_contains "$compact_first" "FIRSTMATE_TEST_INSTRUCTION=updated" \
|
|
2095
|
+
"a missing baseline did not trigger first-post-fix replacement instructions"
|
|
2096
|
+
assert_absent "$home/state/.session-start-agents-baseline" \
|
|
2097
|
+
"a rebuild fabricated a baseline instead of preserving true-start-only ownership"
|
|
2098
|
+
|
|
2099
|
+
printf 'wrong-session\n%s\n' "$(hash_file_for_test "$root/AGENTS.md")" > "$home/state/.session-start-agents-baseline"
|
|
2100
|
+
compact_first=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2101
|
+
assert_contains "$compact_first" "FIRSTMATE_TEST_INSTRUCTION=updated" \
|
|
2102
|
+
"a wrong-session baseline did not trigger replacement instructions"
|
|
2103
|
+
baseline_after=$(cat "$home/state/.session-start-agents-baseline")
|
|
2104
|
+
[ "$baseline_after" = "wrong-session
|
|
2105
|
+
$(hash_file_for_test "$root/AGENTS.md")" ] \
|
|
2106
|
+
|| fail "a wrong-session baseline was rewritten during a rebuild"
|
|
2107
|
+
|
|
2108
|
+
pass "true-start AGENTS baselines stay immutable while every drifted Pi compact re-emits the current contract"
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
test_read_only_pi_compact_refreshes_against_its_own_session_identity() {
|
|
2112
|
+
local rec root home fakebin holder_pid out baseline_before completion_before
|
|
2113
|
+
rec=$(new_world agents-refresh-read-only)
|
|
2114
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2115
|
+
$rec
|
|
2116
|
+
EOF
|
|
2117
|
+
make_fake_toolchain "$fakebin"
|
|
2118
|
+
make_fake_ps_harness "$fakebin" pi
|
|
2119
|
+
printf '%s\n' 'READ_ONLY_AGENTS=current' > "$root/AGENTS.md"
|
|
2120
|
+
FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --source startup >/dev/null
|
|
2121
|
+
|
|
2122
|
+
sleep 300 &
|
|
2123
|
+
holder_pid=$!
|
|
2124
|
+
printf '%s\n%s\n' "$holder_pid" "$(hash_file_for_test "$root/AGENTS.md")" \
|
|
2125
|
+
> "$home/state/.session-start-agents-baseline"
|
|
2126
|
+
printf '%s\n' "$holder_pid" > "$home/state/.lock"
|
|
2127
|
+
baseline_before=$(cat "$home/state/.session-start-agents-baseline")
|
|
2128
|
+
completion_before=$(cat "$home/state/.session-start-complete")
|
|
2129
|
+
|
|
2130
|
+
out=$(FM_FAKE_HARNESS=pi FM_FAKE_LIVE_HOLDER_PID="$holder_pid" \
|
|
2131
|
+
run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2132
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
2133
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
2134
|
+
|
|
2135
|
+
assert_contains "$out" "READ-ONLY SESSION" "competing live lock owner did not force read-only mode"
|
|
2136
|
+
assert_contains "$out" "READ_ONLY_AGENTS=current" \
|
|
2137
|
+
"read-only compact trusted another session's equal baseline"
|
|
2138
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline_before" ] \
|
|
2139
|
+
|| fail "read-only compact mutated the competing session's baseline"
|
|
2140
|
+
[ "$(cat "$home/state/.session-start-complete")" = "$completion_before" ] \
|
|
2141
|
+
|| fail "read-only compact mutated startup completion state"
|
|
2142
|
+
|
|
2143
|
+
pass "read-only Pi compact refreshes against the rebuilding session identity without mutation"
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
test_codex_unreachable_reset_sources_do_not_claim_instruction_refresh() {
|
|
2147
|
+
local rec root home fakebin startup baseline clear_out compact_out
|
|
2148
|
+
rec=$(new_world codex-instruction-refresh)
|
|
2149
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2150
|
+
$rec
|
|
2151
|
+
EOF
|
|
2152
|
+
make_fake_toolchain "$fakebin"
|
|
2153
|
+
make_fake_ps_harness "$fakebin" codex
|
|
2154
|
+
printf '%s\n' 'CODEX_TEST_INSTRUCTION=original' > "$root/AGENTS.md"
|
|
2155
|
+
|
|
2156
|
+
startup=$(run_named_harness_session_start codex "$home" "$root" "$fakebin:$BASE_PATH" --source startup)
|
|
2157
|
+
assert_contains "$startup" "primary harness: codex" "codex fixture did not select the codex run tier"
|
|
2158
|
+
baseline=$(cat "$home/state/.session-start-agents-baseline")
|
|
2159
|
+
printf '%s\n' 'CODEX_TEST_INSTRUCTION=updated' > "$root/AGENTS.md"
|
|
2160
|
+
|
|
2161
|
+
clear_out=$(run_named_harness_session_start codex "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source clear)
|
|
2162
|
+
compact_out=$(run_named_harness_session_start codex "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2163
|
+
assert_not_contains "$clear_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2164
|
+
"Codex clear claimed an instruction-refresh channel unavailable to the tracked transport"
|
|
2165
|
+
assert_not_contains "$compact_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
2166
|
+
"Codex compact claimed an instruction-refresh channel unavailable to the tracked transport"
|
|
2167
|
+
[ "$(cat "$home/state/.session-start-agents-baseline")" = "$baseline" ] \
|
|
2168
|
+
|| fail "an unsupported Codex rebuild rewrote the true-start baseline"
|
|
2169
|
+
|
|
2170
|
+
pass "Codex reset sources do not claim an unavailable instruction-refresh channel"
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
test_agents_baseline_requires_sha256_and_successful_completion() {
|
|
2174
|
+
local rec root home fakebin compact_out
|
|
2175
|
+
rec=$(new_world agents-baseline-failures)
|
|
2176
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2177
|
+
$rec
|
|
2178
|
+
EOF
|
|
2179
|
+
make_fake_toolchain "$fakebin"
|
|
2180
|
+
make_fake_ps_harness "$fakebin" pi
|
|
2181
|
+
printf '%s\n' 'AGENTS_SHA_TEST=original' > "$root/AGENTS.md"
|
|
2182
|
+
printf '#!/usr/bin/env bash\nexit 1\n' > "$fakebin/shasum"
|
|
2183
|
+
printf '#!/usr/bin/env bash\nexit 1\n' > "$fakebin/sha256sum"
|
|
2184
|
+
chmod +x "$fakebin/shasum" "$fakebin/sha256sum"
|
|
2185
|
+
|
|
2186
|
+
FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --source startup >/dev/null
|
|
2187
|
+
assert_absent "$home/state/.session-start-agents-baseline" \
|
|
2188
|
+
"startup recorded a non-SHA-256 instruction baseline when both SHA-256 tools failed"
|
|
2189
|
+
printf '%s\n' 'AGENTS_SHA_TEST=updated' > "$root/AGENTS.md"
|
|
2190
|
+
compact_out=$(FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --reemit --source compact)
|
|
2191
|
+
assert_contains "$compact_out" "AGENTS_SHA_TEST=updated" \
|
|
2192
|
+
"a missing SHA-256 baseline did not conservatively refresh a supported rebuild"
|
|
2193
|
+
|
|
2194
|
+
rm -f "$fakebin/shasum" "$fakebin/sha256sum" "$home/state/.session-start-complete"
|
|
2195
|
+
cat > "$fakebin/mv" <<SH
|
|
2196
|
+
#!/usr/bin/env bash
|
|
2197
|
+
case "\${*: -1}" in
|
|
2198
|
+
"$home/state/.session-start-complete") exit 1 ;;
|
|
2199
|
+
esac
|
|
2200
|
+
exec /bin/mv "\$@"
|
|
2201
|
+
SH
|
|
2202
|
+
chmod +x "$fakebin/mv"
|
|
2203
|
+
FM_FAKE_HARNESS=pi run_pi_session_start "$home" "$root" "$fakebin:$BASE_PATH" --source startup >/dev/null
|
|
2204
|
+
assert_absent "$home/state/.session-start-complete" \
|
|
2205
|
+
"startup published completion despite the atomic completion write failure"
|
|
2206
|
+
assert_absent "$home/state/.session-start-agents-baseline" \
|
|
2207
|
+
"startup recorded an instruction baseline after completion publication failed"
|
|
2208
|
+
|
|
2209
|
+
pass "instruction baselines require SHA-256 and successful startup completion"
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
test_reemit_keeps_repair_ownership_with_the_lock_holder() {
|
|
2213
|
+
local rec root home fakebin reemit readonly_out holder_pid
|
|
2214
|
+
rec=$(new_world reemit-tangle)
|
|
2215
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2216
|
+
$rec
|
|
2217
|
+
EOF
|
|
2218
|
+
make_fake_toolchain "$fakebin"
|
|
2219
|
+
make_fake_ps_claude "$fakebin"
|
|
2220
|
+
git -C "$root" checkout -q -B fm/reemit-tangle
|
|
2221
|
+
|
|
2222
|
+
reemit=$(FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$fakebin:$BASE_PATH" \
|
|
2223
|
+
env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
2224
|
+
"$SESSION_START" --reemit)
|
|
2225
|
+
|
|
2226
|
+
# A re-emit skips the sweeps because it ALREADY ran them, not because it lacks
|
|
2227
|
+
# the lock, so it must still own repair rather than deferring to a lock holder.
|
|
2228
|
+
assert_contains "$reemit" "restore the primary with: git -C $root checkout main" \
|
|
2229
|
+
"--reemit disowned a repair it is entitled to perform"
|
|
2230
|
+
assert_not_contains "$reemit" "must leave restore work to the session holding the fleet lock" \
|
|
2231
|
+
"--reemit misreported itself as an unlocked read-only session"
|
|
2232
|
+
|
|
2233
|
+
rm -f "$home/state/.lock"
|
|
2234
|
+
sleep 300 &
|
|
2235
|
+
holder_pid=$!
|
|
2236
|
+
printf '%s\n' "$holder_pid" > "$home/state/.lock"
|
|
2237
|
+
readonly_out=$(FM_HOME="$home" FM_ROOT_OVERRIDE="$root" PATH="$fakebin:$BASE_PATH" \
|
|
2238
|
+
env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
2239
|
+
"$SESSION_START" --reemit)
|
|
2240
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
2241
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
2242
|
+
|
|
2243
|
+
assert_contains "$readonly_out" "READ-ONLY SESSION" \
|
|
2244
|
+
"--reemit assumed lock ownership instead of re-verifying it"
|
|
2245
|
+
assert_contains "$readonly_out" "must leave restore work to the session holding the fleet lock" \
|
|
2246
|
+
"a lock-refused --reemit still claimed repair ownership"
|
|
2247
|
+
|
|
2248
|
+
pass "--reemit re-verifies lock ownership and keeps repair ownership with whoever holds it"
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
# --- fleet-state digest: no in-flight tasks ----------------------------------
|
|
2252
|
+
|
|
2253
|
+
test_fleet_digest_empty_fleet() {
|
|
2254
|
+
local rec root home fakebin out
|
|
2255
|
+
rec=$(new_world empty-fleet)
|
|
2256
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2257
|
+
$rec
|
|
2258
|
+
EOF
|
|
2259
|
+
make_fake_toolchain "$fakebin"
|
|
2260
|
+
make_fake_ps_claude "$fakebin"
|
|
2261
|
+
|
|
2262
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2263
|
+
assert_contains "$out" "(none)" "empty fleet did not report (none) for in-flight tasks"
|
|
2264
|
+
assert_contains "$out" "absent" "empty fleet's AFK section did not report absent"
|
|
2265
|
+
|
|
2266
|
+
pass "an empty fleet reports (none) for in-flight tasks and an absent AFK flag"
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
test_next_step_sources_x_mode_cadence() {
|
|
2270
|
+
local rec root home fakebin out
|
|
2271
|
+
rec=$(new_world next-step-x)
|
|
2272
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2273
|
+
$rec
|
|
2274
|
+
EOF
|
|
2275
|
+
make_fake_toolchain "$fakebin"
|
|
2276
|
+
make_fake_ps_claude "$fakebin"
|
|
2277
|
+
fm_fake_exit0 "$fakebin" curl jq
|
|
2278
|
+
printf 'FMX_PAIRING_TOKEN=tok-next-step\n' > "$home/.env"
|
|
2279
|
+
|
|
2280
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2281
|
+
|
|
2282
|
+
assert_contains "$out" "FMX: X mode on" "bootstrap did not activate X mode"
|
|
2283
|
+
assert_contains "$out" "SUPERVISION OPERATING INSTRUCTIONS - primary harness: claude" "supervision block missing"
|
|
2284
|
+
assert_contains "$out" "- X mode: active" "supervision block did not mention X cadence"
|
|
2285
|
+
assert_contains "$out" "Follow the supervision operating instructions block above" "next step did not point back to the emitted supervision block"
|
|
2286
|
+
|
|
2287
|
+
pass "session start emits X-mode cadence guidance in the harness supervision block"
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
test_next_step_afk_delegates_to_daemon() {
|
|
2291
|
+
local rec root home fakebin out
|
|
2292
|
+
rec=$(new_world next-step-afk)
|
|
2293
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2294
|
+
$rec
|
|
2295
|
+
EOF
|
|
2296
|
+
make_fake_toolchain "$fakebin"
|
|
2297
|
+
make_fake_ps_claude "$fakebin"
|
|
2298
|
+
: > "$home/state/.afk"
|
|
2299
|
+
|
|
2300
|
+
out=$(run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2301
|
+
|
|
2302
|
+
assert_contains "$out" "away-mode supervision is active" "AFK digest did not report away mode"
|
|
2303
|
+
assert_contains "$out" "Away mode is active" "next step did not switch to AFK guidance"
|
|
2304
|
+
assert_contains "$out" "daemon owns the watcher" "next step did not delegate watcher ownership to the daemon"
|
|
2305
|
+
assert_contains "$out" "- Away mode: active" "supervision block did not include active AFK state"
|
|
2306
|
+
assert_not_contains "$out" " bin/fm-watch-arm.sh" "AFK next step still told the agent to arm the watcher directly"
|
|
2307
|
+
|
|
2308
|
+
pass "next step delegates watcher ownership to the AFK daemon"
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
test_supervision_block_exactly_one_and_pi_diagnostic() {
|
|
2312
|
+
local rec root home fakebin out block_count wake_line sup_line context_line
|
|
2313
|
+
rec=$(new_world pi-supervision-block)
|
|
2314
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2315
|
+
$rec
|
|
2316
|
+
EOF
|
|
2317
|
+
make_fake_toolchain "$fakebin"
|
|
2318
|
+
make_fake_ps_harness "$fakebin" pi
|
|
2319
|
+
|
|
2320
|
+
out=$(FM_FAKE_HARNESS=pi run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2321
|
+
|
|
2322
|
+
block_count=$(printf '%s\n' "$out" | grep -c '^SUPERVISION OPERATING INSTRUCTIONS - primary harness:')
|
|
2323
|
+
[ "$block_count" -eq 1 ] || fail "expected exactly one supervision block, got $block_count"
|
|
2324
|
+
assert_contains "$out" "SUPERVISION OPERATING INSTRUCTIONS - primary harness: pi" "pi supervision block missing"
|
|
2325
|
+
assert_contains "$out" "Mode: Pi extension background wake." "pi snippet missing from session start"
|
|
2326
|
+
assert_contains "$out" "PI_WATCH_EXTENSION: not loaded" "pi extension load diagnostic missing"
|
|
2327
|
+
assert_contains "$out" "restart plain pi so $root/.pi/extensions/fm-primary-turnend-guard.ts and $root/.pi/extensions/fm-primary-pi-watch.ts auto-load" "pi extension load diagnostic omits the turn-end guard extension"
|
|
2328
|
+
|
|
2329
|
+
wake_line=$(printf '%s\n' "$out" | grep -n '^WAKE QUEUE$' | head -1 | cut -d: -f1)
|
|
2330
|
+
sup_line=$(printf '%s\n' "$out" | grep -n '^SUPERVISION OPERATING INSTRUCTIONS' | head -1 | cut -d: -f1)
|
|
2331
|
+
context_line=$(printf '%s\n' "$out" | grep -n '^CONTEXT$' | head -1 | cut -d: -f1)
|
|
2332
|
+
[ "$wake_line" -lt "$sup_line" ] || fail "supervision block did not follow wake queue"
|
|
2333
|
+
[ "$sup_line" -lt "$context_line" ] || fail "supervision block did not precede context"
|
|
2334
|
+
|
|
2335
|
+
pass "session start emits exactly one detected harness block and reports Pi extension load state"
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
test_pi_signed_primary_uses_pi_extensions_without_identity_normalization() {
|
|
2339
|
+
local rec root home fakebin out
|
|
2340
|
+
rec=$(new_world pi-signed-supervision-block)
|
|
2341
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2342
|
+
$rec
|
|
2343
|
+
EOF
|
|
2344
|
+
make_fake_toolchain "$fakebin"
|
|
2345
|
+
make_fake_ps_harness "$fakebin" pi-signed
|
|
2346
|
+
|
|
2347
|
+
out=$(FM_FAKE_HARNESS=pi-signed run_session_start "$home" "$root" "$fakebin:$BASE_PATH" pi-signed)
|
|
2348
|
+
|
|
2349
|
+
assert_contains "$out" "SUPERVISION OPERATING INSTRUCTIONS - primary harness: pi-signed" \
|
|
2350
|
+
"session start normalized a pi-signed primary to pi"
|
|
2351
|
+
assert_contains "$out" "Mode: Pi extension background wake." \
|
|
2352
|
+
"pi-signed primary did not reuse Pi's supervision protocol"
|
|
2353
|
+
assert_contains "$out" "PI_WATCH_EXTENSION: not loaded" \
|
|
2354
|
+
"pi-signed primary skipped Pi extension validation"
|
|
2355
|
+
assert_contains "$out" "restart pi-signed so $root/.pi/extensions/fm-primary-turnend-guard.ts and $root/.pi/extensions/fm-primary-pi-watch.ts auto-load" \
|
|
2356
|
+
"pi-signed extension diagnostic did not preserve the executable identity"
|
|
2357
|
+
|
|
2358
|
+
pass "session start preserves pi-signed primary identity while applying Pi extension guarantees"
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
test_pi_diagnostic_rejects_stale_loaded_marker() {
|
|
2362
|
+
local rec root home fakebin out marker holder_pid
|
|
2363
|
+
rec=$(new_world pi-stale-loaded-marker)
|
|
2364
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2365
|
+
$rec
|
|
2366
|
+
EOF
|
|
2367
|
+
make_fake_toolchain "$fakebin"
|
|
2368
|
+
|
|
2369
|
+
sleep 300 &
|
|
2370
|
+
holder_pid=$!
|
|
2371
|
+
make_fake_ps_pi_holder "$fakebin" "$holder_pid"
|
|
2372
|
+
install_pi_turnend_extension_fixture "$root"
|
|
2373
|
+
install_pi_watch_extension_fixture "$root"
|
|
2374
|
+
marker="$home/state/.pi-watch-extension-loaded"
|
|
2375
|
+
printf 'stale-extension-version\n%s\n' "$holder_pid" > "$marker"
|
|
2376
|
+
write_pi_turnend_loaded_marker "$home" "$root" "$holder_pid"
|
|
2377
|
+
touch -t 203001010000 "$marker" 2>/dev/null || touch "$marker"
|
|
2378
|
+
|
|
2379
|
+
out=$(FM_FAKE_HARNESS=pi run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2380
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
2381
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
2382
|
+
|
|
2383
|
+
assert_contains "$out" "PI_WATCH_EXTENSION: not loaded" "pi diagnostic trusted a stale loaded marker"
|
|
2384
|
+
|
|
2385
|
+
pass "session start rejects stale Pi loaded markers"
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
test_pi_diagnostic_accepts_prelock_loaded_marker() {
|
|
2389
|
+
local rec root home fakebin out holder_pid
|
|
2390
|
+
rec=$(new_world pi-prelock-loaded-marker)
|
|
2391
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2392
|
+
$rec
|
|
2393
|
+
EOF
|
|
2394
|
+
make_fake_toolchain "$fakebin"
|
|
2395
|
+
|
|
2396
|
+
sleep 300 &
|
|
2397
|
+
holder_pid=$!
|
|
2398
|
+
make_fake_ps_pi_holder "$fakebin" "$holder_pid"
|
|
2399
|
+
install_pi_turnend_extension_fixture "$root"
|
|
2400
|
+
install_pi_watch_extension_fixture "$root"
|
|
2401
|
+
|
|
2402
|
+
write_pi_loaded_markers "$home" "$root" "$holder_pid"
|
|
2403
|
+
|
|
2404
|
+
out=$(FM_FAKE_HARNESS=pi run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2405
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
2406
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
2407
|
+
|
|
2408
|
+
assert_not_contains "$out" "PI_WATCH_EXTENSION: not loaded" "pi diagnostic rejected a current pre-lock loaded marker"
|
|
2409
|
+
|
|
2410
|
+
pass "session start accepts current Pi markers written before lock acquisition"
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
test_pi_diagnostic_rejects_missing_turnend_guard_marker() {
|
|
2414
|
+
local rec root home fakebin out holder_pid
|
|
2415
|
+
rec=$(new_world pi-missing-turnend-marker)
|
|
2416
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2417
|
+
$rec
|
|
2418
|
+
EOF
|
|
2419
|
+
make_fake_toolchain "$fakebin"
|
|
2420
|
+
|
|
2421
|
+
sleep 300 &
|
|
2422
|
+
holder_pid=$!
|
|
2423
|
+
make_fake_ps_pi_holder "$fakebin" "$holder_pid"
|
|
2424
|
+
install_pi_turnend_extension_fixture "$root"
|
|
2425
|
+
install_pi_watch_extension_fixture "$root"
|
|
2426
|
+
|
|
2427
|
+
write_pi_watch_loaded_marker "$home" "$root" "$holder_pid"
|
|
2428
|
+
|
|
2429
|
+
out=$(FM_FAKE_HARNESS=pi run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2430
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
2431
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
2432
|
+
|
|
2433
|
+
assert_contains "$out" "PI_WATCH_EXTENSION: not loaded" "pi diagnostic trusted a session without the turn-end guard extension"
|
|
2434
|
+
|
|
2435
|
+
pass "session start rejects Pi sessions missing the turn-end guard marker"
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
test_pi_diagnostic_rejects_previous_session_loaded_marker() {
|
|
2439
|
+
local rec root home fakebin out marker version holder_pid
|
|
2440
|
+
rec=$(new_world pi-previous-session-loaded-marker)
|
|
2441
|
+
IFS='|' read -r root home fakebin <<EOF
|
|
2442
|
+
$rec
|
|
2443
|
+
EOF
|
|
2444
|
+
make_fake_toolchain "$fakebin"
|
|
2445
|
+
|
|
2446
|
+
sleep 300 &
|
|
2447
|
+
holder_pid=$!
|
|
2448
|
+
make_fake_ps_pi_holder "$fakebin" "$holder_pid"
|
|
2449
|
+
install_pi_turnend_extension_fixture "$root"
|
|
2450
|
+
install_pi_watch_extension_fixture "$root"
|
|
2451
|
+
marker="$home/state/.pi-watch-extension-loaded"
|
|
2452
|
+
version=$(hash_file_for_test "$root/.pi/extensions/fm-primary-pi-watch.ts")
|
|
2453
|
+
printf '%s\n999999\n' "$version" > "$marker"
|
|
2454
|
+
write_pi_turnend_loaded_marker "$home" "$root" "$holder_pid"
|
|
2455
|
+
|
|
2456
|
+
out=$(FM_FAKE_HARNESS=pi run_session_start "$home" "$root" "$fakebin:$BASE_PATH")
|
|
2457
|
+
kill "$holder_pid" 2>/dev/null || true
|
|
2458
|
+
wait "$holder_pid" 2>/dev/null || true
|
|
2459
|
+
|
|
2460
|
+
assert_contains "$out" "PI_WATCH_EXTENSION: not loaded" "pi diagnostic trusted a marker from a previous Pi process"
|
|
2461
|
+
|
|
2462
|
+
pass "session start rejects Pi loaded markers from previous sessions"
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
test_context_digest_absent_empty_present
|
|
2466
|
+
test_lock_refusal_read_only_path
|
|
2467
|
+
test_lock_write_failure_read_only_path
|
|
2468
|
+
test_trace_context_effective_state_is_frozen_after_lock
|
|
2469
|
+
test_session_lock_concurrent_single_winner
|
|
2470
|
+
test_output_ordering_diagnostics_lead
|
|
2471
|
+
test_read_once_contract_is_stated_once_before_its_subject
|
|
2472
|
+
test_herdr_backend_diagnostics_follow_real_session_start
|
|
2473
|
+
test_session_start_relaunches_missing_pi_secondmate
|
|
2474
|
+
test_deferred_relaunch_is_always_reported
|
|
2475
|
+
test_unreachable_network_never_blocks_the_digest
|
|
2476
|
+
test_deferred_result_reaches_the_agent_when_the_digest_cannot_print_it
|
|
2477
|
+
test_read_only_session_declares_skipped_network_checks
|
|
2478
|
+
test_tasks_axi_compatibility_is_probed_once
|
|
2479
|
+
test_session_start_preserves_ambiguous_pi_process
|
|
2480
|
+
test_session_start_preserves_transiently_unreadable_tmux
|
|
2481
|
+
test_session_start_preserves_proven_bare_shell_recovery
|
|
2482
|
+
test_session_start_relaunches_herdr_husk_secondmate
|
|
2483
|
+
test_status_tail_bounding
|
|
2484
|
+
test_status_tail_line_cap
|
|
2485
|
+
test_orphan_status_logs_are_printed
|
|
2486
|
+
test_endpoint_liveness_tmux
|
|
2487
|
+
test_endpoint_liveness_herdr
|
|
2488
|
+
test_composition_invokes_real_scripts
|
|
2489
|
+
test_branch_outcome_replay_and_lease_sweep
|
|
2490
|
+
test_non_pi_session_start_leaves_branch_state_untouched
|
|
2491
|
+
test_backlog_compact_tasks_axi_omits_bodies_and_keeps_metadata
|
|
2492
|
+
test_backlog_queued_bound_discloses_its_remainder
|
|
2493
|
+
test_backlog_compact_manual_backend_skips_indented_bodies
|
|
2494
|
+
test_backlog_compact_tasks_axi_unavailable_uses_manual_fallback
|
|
2495
|
+
test_fleet_digest_empty_fleet
|
|
2496
|
+
test_next_step_sources_x_mode_cadence
|
|
2497
|
+
test_next_step_afk_delegates_to_daemon
|
|
2498
|
+
test_supervision_block_exactly_one_and_pi_diagnostic
|
|
2499
|
+
test_pi_signed_primary_uses_pi_extensions_without_identity_normalization
|
|
2500
|
+
test_pi_diagnostic_rejects_stale_loaded_marker
|
|
2501
|
+
test_pi_diagnostic_accepts_prelock_loaded_marker
|
|
2502
|
+
test_pi_diagnostic_rejects_missing_turnend_guard_marker
|
|
2503
|
+
test_pi_diagnostic_rejects_previous_session_loaded_marker
|
|
2504
|
+
test_runtime_bound_truncates_loudly_and_exits_zero
|
|
2505
|
+
test_portable_timeout_escalates_term_resistant_process
|
|
2506
|
+
test_runtime_bound_leaves_a_healthy_digest_untouched
|
|
2507
|
+
test_runtime_bound_leaves_harness_ancestry_headroom
|
|
2508
|
+
test_reemit_skips_startup_sweeps_but_keeps_the_wake_drain
|
|
2509
|
+
test_agents_baseline_stays_at_true_start_and_reemits_on_every_drifted_pi_compact
|
|
2510
|
+
test_read_only_pi_compact_refreshes_against_its_own_session_identity
|
|
2511
|
+
test_codex_unreachable_reset_sources_do_not_claim_instruction_refresh
|
|
2512
|
+
test_agents_baseline_requires_sha256_and_successful_completion
|
|
2513
|
+
test_reemit_keeps_repair_ownership_with_the_lock_holder
|
|
2514
|
+
|
|
2515
|
+
echo "# fm-session-start.test.sh: all assertions passed"
|