@zerwiz/ymir 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -39
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# End-to-end tests for captain-held tasks: the one primitive behind "a decision
|
|
3
|
+
# is simply a task waiting on the captain", its completion gate, its recorded
|
|
4
|
+
# answers, the record-divergence guard over its two records, and the legacy
|
|
5
|
+
# compatibility for pre-collapse decision identities.
|
|
6
|
+
set -u
|
|
7
|
+
|
|
8
|
+
# shellcheck source=tests/lib.sh
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
11
|
+
|
|
12
|
+
TEARDOWN="$ROOT/bin/fm-teardown.sh"
|
|
13
|
+
BEARINGS="$ROOT/bin/fm-bearings-snapshot.sh"
|
|
14
|
+
TMP_ROOT=$(fm_test_tmproot fm-captain-hold)
|
|
15
|
+
TASKS_AXI_BIN=$(command -v tasks-axi || true)
|
|
16
|
+
|
|
17
|
+
command -v jq >/dev/null 2>&1 || { echo "skip: jq not found"; exit 0; }
|
|
18
|
+
command -v tasks-axi >/dev/null 2>&1 || { echo "skip: tasks-axi not found"; exit 0; }
|
|
19
|
+
|
|
20
|
+
make_home() { # <name>
|
|
21
|
+
local home="$TMP_ROOT/$1" fakebin
|
|
22
|
+
mkdir -p "$home/data" "$home/state" "$home/config" "$home/projects"
|
|
23
|
+
cp "$ROOT/.tasks.toml" "$home/.tasks.toml"
|
|
24
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
25
|
+
## In flight
|
|
26
|
+
|
|
27
|
+
## Queued
|
|
28
|
+
|
|
29
|
+
## Done
|
|
30
|
+
EOF
|
|
31
|
+
fakebin=$(fm_fakebin "$home")
|
|
32
|
+
fm_fake_exit0 "$fakebin" tmux treehouse no-mistakes gh gh-axi
|
|
33
|
+
printf '%s\n' "$home"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# The Lavish review adapter, run against this suite's isolated home. The
|
|
37
|
+
# machine-wide process-event claim root is redirected into the fixture so arming
|
|
38
|
+
# a review here can never contend with a real one on this machine.
|
|
39
|
+
run_lavish() { # <home> <command args...>
|
|
40
|
+
local home=$1
|
|
41
|
+
shift
|
|
42
|
+
PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" \
|
|
43
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
44
|
+
FM_PROCEVENT_CLAIM_ROOT="$home/procevent-claims" \
|
|
45
|
+
"$ROOT/bin/fm-procevent-lavish.sh" "$@"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
run_bearings() { # <home>
|
|
49
|
+
local home=$1
|
|
50
|
+
PATH="$home/fakebin:$PATH" FM_HOME="$home" FM_BEARINGS_NOW=2026-07-14T12:00:00Z \
|
|
51
|
+
"$BEARINGS" --json
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
run_teardown() { # <home> <id>
|
|
55
|
+
local home=$1 id=$2
|
|
56
|
+
PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" \
|
|
57
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
58
|
+
FM_CONFIG_OVERRIDE="$home/config" "$TEARDOWN" "$id"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
tasks_in() { # <home> <tasks-axi args...>
|
|
62
|
+
local home=$1
|
|
63
|
+
shift
|
|
64
|
+
(cd "$home" && tasks-axi "$@")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
run_captain() { # <home> <command args...>
|
|
68
|
+
local home=$1
|
|
69
|
+
shift
|
|
70
|
+
PATH="$home/fakebin:$PATH" REAL_TASKS_AXI="$TASKS_AXI_BIN" \
|
|
71
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
72
|
+
FM_CONFIG_OVERRIDE="$home/config" "$ROOT/bin/fm-captain-hold.sh" "$@"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# The retired command surface, kept for one release as a shim; in-flight
|
|
76
|
+
# pre-collapse work still drives the lifecycle through these spellings.
|
|
77
|
+
run_shim() { # <home> <command args...>
|
|
78
|
+
local home=$1
|
|
79
|
+
shift
|
|
80
|
+
PATH="$home/fakebin:$PATH" REAL_TASKS_AXI="$TASKS_AXI_BIN" \
|
|
81
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
82
|
+
FM_CONFIG_OVERRIDE="$home/config" "$ROOT/bin/fm-decision-hold.sh" "$@"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
write_origin_meta() { # <home> <id> [kind]
|
|
86
|
+
local home=$1 id=$2 kind=${3:-scout}
|
|
87
|
+
fm_write_meta "$home/state/$id.meta" \
|
|
88
|
+
"window=firstmate:fm-$id" \
|
|
89
|
+
"worktree=$home/projects/missing-$id" \
|
|
90
|
+
"project=$home/projects/sample" \
|
|
91
|
+
"harness=codex" \
|
|
92
|
+
"kind=$kind" \
|
|
93
|
+
"mode=$kind" \
|
|
94
|
+
"spawn_gen=fixture-$id"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Reproduces the loss exactly with privacy-safe synthetic names: the investigation
|
|
98
|
+
# and visual review have ended, the only genuine unresolved captain call is report
|
|
99
|
+
# prose, no held backlog item or open status exists, and the authoritative
|
|
100
|
+
# Bearings view correctly omits it. Completion must now refuse before teardown can
|
|
101
|
+
# erase the source.
|
|
102
|
+
test_uninventoried_report_decision_refuses_completion() {
|
|
103
|
+
local home id json rc
|
|
104
|
+
home=$(make_home omitted-decision)
|
|
105
|
+
id=sample-route-review
|
|
106
|
+
mkdir -p "$home/data/$id"
|
|
107
|
+
cat > "$home/data/backlog.md" <<EOF
|
|
108
|
+
## In flight
|
|
109
|
+
- [ ] $id - Investigate sample routing (repo: sample) (kind: scout) (since 2026-07-14)
|
|
110
|
+
|
|
111
|
+
## Queued
|
|
112
|
+
|
|
113
|
+
## Done
|
|
114
|
+
EOF
|
|
115
|
+
write_origin_meta "$home" "$id"
|
|
116
|
+
printf 'done: report and visual review complete\n' > "$home/state/$id.status"
|
|
117
|
+
cat > "$home/data/$id/report.md" <<'EOF'
|
|
118
|
+
# Sample route review
|
|
119
|
+
|
|
120
|
+
The evidence is complete.
|
|
121
|
+
The captain still needs to choose route north or route south before follow-up work starts.
|
|
122
|
+
EOF
|
|
123
|
+
|
|
124
|
+
json=$(run_bearings "$home") || fail "Bearings failed for unresolved-call regression"
|
|
125
|
+
printf '%s' "$json" | jq -e '
|
|
126
|
+
(.decisions_open | length) == 0
|
|
127
|
+
and (.gates | length) == 0
|
|
128
|
+
and (.reports | any(.id == "sample-route-review"))
|
|
129
|
+
' >/dev/null || fail "the pre-policy omission shape was not reproduced: $json"
|
|
130
|
+
|
|
131
|
+
set +e
|
|
132
|
+
run_teardown "$home" "$id" > "$home/teardown.out" 2> "$home/teardown.err"
|
|
133
|
+
rc=$?
|
|
134
|
+
set -e
|
|
135
|
+
[ "$rc" -ne 0 ] || fail "completed investigation teardown erased a report-only unresolved captain call"
|
|
136
|
+
assert_present "$home/state/$id.meta" "refused completion must preserve investigation metadata"
|
|
137
|
+
assert_grep "REFUSED" "$home/teardown.err" "refusal must be explicit"
|
|
138
|
+
pass "report-only unresolved captain call is reproduced and completion refuses before loss"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# The completion gate on the collapsed primitive: an origin with open keyed
|
|
142
|
+
# status decisions refuses --none, refuses an inventory naming absent tasks,
|
|
143
|
+
# attests a verified inventory of captain-held task ids, and transfers every
|
|
144
|
+
# still-open status decision to that durable inventory.
|
|
145
|
+
test_completion_gate_attests_and_transfers() {
|
|
146
|
+
local home id json open before after
|
|
147
|
+
home=$(make_home completion-gate)
|
|
148
|
+
id=sample-systems-review
|
|
149
|
+
mkdir -p "$home/data/$id"
|
|
150
|
+
tasks_in "$home" add "$id" "Investigate sample systems" --kind scout --repo sample --start >/dev/null \
|
|
151
|
+
|| fail "could not create investigation backlog fixture"
|
|
152
|
+
write_origin_meta "$home" "$id"
|
|
153
|
+
cat > "$home/state/$id.status" <<'EOF'
|
|
154
|
+
working: report drafted
|
|
155
|
+
needs-decision [key=route]: choose route north or route south
|
|
156
|
+
needs-decision [key=access]: choose open or restricted sample access
|
|
157
|
+
EOF
|
|
158
|
+
cat > "$home/data/$id/report.md" <<'EOF'
|
|
159
|
+
# Sample systems review
|
|
160
|
+
|
|
161
|
+
Two choices remain unresolved: the route and the sample access level.
|
|
162
|
+
A separate recommendation is already resolved and requires no captain action.
|
|
163
|
+
EOF
|
|
164
|
+
|
|
165
|
+
if run_captain "$home" complete "$id" --none > "$home/none.out" 2> "$home/none.err"; then
|
|
166
|
+
fail "--none attested while captain calls were still open in the status stream"
|
|
167
|
+
fi
|
|
168
|
+
assert_no_grep "decisions_reviewed=1" "$home/state/$id.meta" \
|
|
169
|
+
"failed completion recorded a false completion attestation"
|
|
170
|
+
if run_captain "$home" complete "$id" sample-route-call > "$home/absent.out" 2> "$home/absent.err"; then
|
|
171
|
+
fail "completion accepted an inventory entry that names no task"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
run_captain "$home" hold sample-route-call \
|
|
175
|
+
--title "Choose route: north, south" --reason "captain route and access choices pending" \
|
|
176
|
+
--repo sample --origin "$id" >/dev/null \
|
|
177
|
+
|| fail "could not register the captain-held task"
|
|
178
|
+
run_captain "$home" hold sample-route-call \
|
|
179
|
+
--title "Choose route: north, south" --reason "captain route and access choices pending" \
|
|
180
|
+
--repo sample >/dev/null \
|
|
181
|
+
|| fail "idempotent hold retry failed"
|
|
182
|
+
[ "$(grep -cE "^- \[ \] sample-route-call -" "$home/data/backlog.md")" = 1 ] \
|
|
183
|
+
|| fail "idempotent retry duplicated the captain-held task"
|
|
184
|
+
if run_captain "$home" hold sample-route-call --title "A different title" \
|
|
185
|
+
--reason "captain route and access choices pending" > "$home/title.out" 2> "$home/title.err"; then
|
|
186
|
+
fail "hold accepted a changed title on an existing task"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
FM_STATE_OVERRIDE="$home/state" bash -c '
|
|
190
|
+
. "$1"
|
|
191
|
+
fm_wake_status_mark_current "$2" "$3"
|
|
192
|
+
' _ "$ROOT/bin/fm-wake-lib.sh" "$home/state" "$home/state/$id.status" \
|
|
193
|
+
|| fail "could not prime the announced decision baseline"
|
|
194
|
+
run_captain "$home" complete "$id" sample-route-call >/dev/null \
|
|
195
|
+
|| fail "shared investigation completion gate failed"
|
|
196
|
+
FM_STATE_OVERRIDE="$home/state" bash -c '
|
|
197
|
+
. "$1"; fm_wake_signal_seen_current "$2" "$3"
|
|
198
|
+
' _ "$ROOT/bin/fm-wake-lib.sh" "$home/state" "$home/state/$id.status" \
|
|
199
|
+
|| fail "captain-held bookkeeping closes re-woke their own home"
|
|
200
|
+
assert_grep "decisions_reviewed=1" "$home/state/$id.meta" "completion attestation missing"
|
|
201
|
+
assert_grep "decision_keys=sample-route-call" "$home/state/$id.meta" "inventory was not recorded as task ids"
|
|
202
|
+
open=$(bash -c '. "$1"; status_open_decisions "$2"' _ \
|
|
203
|
+
"$ROOT/bin/fm-classify-lib.sh" "$home/state/$id.status")
|
|
204
|
+
[ -z "$open" ] || fail "captain-held transfer did not close the live status decisions: $open"
|
|
205
|
+
grep -F 'captain-held [key=route]: tracked by sample-route-call' "$home/state/$id.status" >/dev/null \
|
|
206
|
+
|| fail "the transfer line does not name the tracking inventory"
|
|
207
|
+
|
|
208
|
+
before=$(shasum -a 256 "$home/data/backlog.md" | awk '{print $1}')
|
|
209
|
+
json=$(run_bearings "$home") || fail "Bearings failed with a captain-held task"
|
|
210
|
+
after=$(shasum -a 256 "$home/data/backlog.md" | awk '{print $1}')
|
|
211
|
+
[ "$before" = "$after" ] || fail "Bearings mutated the authoritative backlog"
|
|
212
|
+
printf '%s' "$json" | jq -e '
|
|
213
|
+
(.decisions_open | any(.id == "sample-route-call" and .verb == "captain-hold" and .owner == "(main)"))
|
|
214
|
+
and (.gates | any(.id == "sample-route-call") | not)
|
|
215
|
+
' >/dev/null || fail "Bearings did not surface the captain-held task: $json"
|
|
216
|
+
|
|
217
|
+
run_teardown "$home" "$id" >/dev/null 2> "$home/teardown.err" \
|
|
218
|
+
|| fail "reviewed investigation teardown failed: $(cat "$home/teardown.err")"
|
|
219
|
+
tasks_in "$home" "done" "$id" --report "data/$id/report.md" --keep 0 >/dev/null \
|
|
220
|
+
|| fail "could not archive completed investigation"
|
|
221
|
+
json=$(run_bearings "$home") || fail "Bearings failed after source teardown and archival"
|
|
222
|
+
printf '%s' "$json" | jq -e '
|
|
223
|
+
(.decisions_open | any(.id == "sample-route-call" and .verb == "captain-hold"))
|
|
224
|
+
and (.in_flight | any(.id == "sample-systems-review") | not)
|
|
225
|
+
' >/dev/null || fail "teardown or archival erased a captain-held task: $json"
|
|
226
|
+
pass "the completion gate attests captain-held inventory and transfers open status decisions"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# The recorded-answer rule: answering closes with the captain's exact words, an
|
|
230
|
+
# exact retry is idempotent, a drifted retry is rejected, dependent work routed
|
|
231
|
+
# behind the answered task is released by the close, and the completion gate is
|
|
232
|
+
# satisfied only by a recorded answer.
|
|
233
|
+
test_answer_records_and_closes() {
|
|
234
|
+
local home id json show
|
|
235
|
+
home=$(make_home answer-close)
|
|
236
|
+
id=sample-guard-review
|
|
237
|
+
mkdir -p "$home/data/$id"
|
|
238
|
+
tasks_in "$home" add "$id" "Guard the answer path" --kind scout --repo sample --start >/dev/null \
|
|
239
|
+
|| fail "could not create the answer-guard origin"
|
|
240
|
+
write_origin_meta "$home" "$id"
|
|
241
|
+
printf 'done: report complete\n' > "$home/state/$id.status"
|
|
242
|
+
printf '# Guard review\n\nOne captain choice remains.\n' > "$home/data/$id/report.md"
|
|
243
|
+
run_captain "$home" hold sample-guard-call \
|
|
244
|
+
--title "Choose the guard option" --reason "captain guard choice pending" --repo sample >/dev/null \
|
|
245
|
+
|| fail "could not register the captain-held task"
|
|
246
|
+
run_captain "$home" complete "$id" sample-guard-call >/dev/null \
|
|
247
|
+
|| fail "completion failed for the held inventory"
|
|
248
|
+
tasks_in "$home" add sample-guard-work "Apply the guard option" \
|
|
249
|
+
--kind ship --repo sample --blocked-by sample-guard-call >/dev/null \
|
|
250
|
+
|| fail "could not route work behind the captain-held task"
|
|
251
|
+
|
|
252
|
+
printf '' > "$home/empty.txt"
|
|
253
|
+
if run_captain "$home" answer sample-guard-call --decision-file "$home/empty.txt" \
|
|
254
|
+
> "$home/empty-answer.out" 2> "$home/empty-answer.err"; then
|
|
255
|
+
fail "answer accepted an empty captain decision"
|
|
256
|
+
fi
|
|
257
|
+
if run_captain "$home" answer sample-guard-call > "$home/bare-answer.out" 2> "$home/bare-answer.err"; then
|
|
258
|
+
fail "answer accepted a close with no captain decision file at all"
|
|
259
|
+
fi
|
|
260
|
+
printf 'An answer the captain never gave.\n' > "$home/invented.txt"
|
|
261
|
+
if run_captain "$home" answer sample-absent-call --decision-file "$home/invented.txt" \
|
|
262
|
+
> "$home/absent-answer.out" 2> "$home/absent-answer.err"; then
|
|
263
|
+
fail "answer invented a resolution for a task that does not exist"
|
|
264
|
+
fi
|
|
265
|
+
if run_captain "$home" answer sample-guard-work --decision-file "$home/invented.txt" \
|
|
266
|
+
> "$home/unheld-answer.out" 2> "$home/unheld-answer.err"; then
|
|
267
|
+
fail "answer closed a task that is not held for the captain"
|
|
268
|
+
fi
|
|
269
|
+
show=$(tasks_in "$home" show sample-guard-call --full)
|
|
270
|
+
assert_contains "$show" "state: queued" "a refused answer closed the captain-held task"
|
|
271
|
+
assert_contains "$show" "held: yes" "a refused answer released the captain-held task"
|
|
272
|
+
|
|
273
|
+
printf 'Captain chose the guard option.\n' > "$home/guard-decision.txt"
|
|
274
|
+
run_captain "$home" answer sample-guard-call --decision-file "$home/guard-decision.txt" >/dev/null \
|
|
275
|
+
|| fail "answer could not close the captain-held task"
|
|
276
|
+
show=$(tasks_in "$home" show sample-guard-call --full)
|
|
277
|
+
assert_contains "$show" "state: done" "an answered captain-held task did not close"
|
|
278
|
+
assert_contains "$show" "Resolution recorded by fm-captain-hold" "the answered task lost the decision record"
|
|
279
|
+
assert_contains "$show" "Resolution mode: answered" "the answered task did not record its close path"
|
|
280
|
+
assert_contains "$show" "Captain chose the guard option." \
|
|
281
|
+
"the answered task did not record the captain decision text"
|
|
282
|
+
run_captain "$home" answer sample-guard-call --decision-file "$home/guard-decision.txt" >/dev/null \
|
|
283
|
+
|| fail "identical answer retry was not idempotent"
|
|
284
|
+
printf 'Captain chose something else entirely.\n' > "$home/drifted.txt"
|
|
285
|
+
if run_captain "$home" answer sample-guard-call --decision-file "$home/drifted.txt" \
|
|
286
|
+
> "$home/drifted-answer.out" 2> "$home/drifted-answer.err"; then
|
|
287
|
+
fail "answer retry accepted a different captain decision"
|
|
288
|
+
fi
|
|
289
|
+
# The answered call releases the work routed behind it: a Done blocker reads
|
|
290
|
+
# as resolved everywhere.
|
|
291
|
+
show=$(tasks_in "$home" show sample-guard-work --full)
|
|
292
|
+
assert_contains "$show" "blocked: no" "the recorded answer did not release dependent work"
|
|
293
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
294
|
+
|| fail "an answered captain call did not satisfy the completion gate"
|
|
295
|
+
json=$(run_bearings "$home") || fail "Bearings failed after the answer"
|
|
296
|
+
printf '%s' "$json" | jq -e '
|
|
297
|
+
(.decisions_open | any(.id == "sample-guard-call") | not)
|
|
298
|
+
and (.gates | any(.id == "sample-guard-call") | not)
|
|
299
|
+
and (.landed | any(.id == "sample-guard-call") | not)
|
|
300
|
+
' >/dev/null || fail "an answered captain call still renders somewhere it should not: $json"
|
|
301
|
+
pass "answer records the captain's words, closes idempotently, and releases routed work"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# --release lifts the hold instead of closing, preserving the work item's own
|
|
305
|
+
# body under the record; a re-held task later accepts a new answer.
|
|
306
|
+
test_release_frees_held_work() {
|
|
307
|
+
local home show out
|
|
308
|
+
home=$(make_home release-work)
|
|
309
|
+
tasks_in "$home" add sample-widget "Ship the sample widget" --kind ship --repo sample \
|
|
310
|
+
--body 'The widget plan body. Literal escape: \n. Unicode: café.' >/dev/null \
|
|
311
|
+
|| fail "could not create the held work item"
|
|
312
|
+
run_captain "$home" hold sample-widget --reason "captain go needed before shipping" >/dev/null \
|
|
313
|
+
|| fail "could not hold the work item for the captain"
|
|
314
|
+
printf 'Go: ship it as planned.\n' > "$home/go.txt"
|
|
315
|
+
run_captain "$home" answer sample-widget --decision-file "$home/go.txt" --release >/dev/null \
|
|
316
|
+
|| fail "answer --release failed on the held work item"
|
|
317
|
+
show=$(tasks_in "$home" show sample-widget --full)
|
|
318
|
+
assert_contains "$show" "state: queued" "a released work item did not stay queued"
|
|
319
|
+
assert_contains "$show" "held: no" "a released work item kept its hold"
|
|
320
|
+
assert_contains "$show" "Resolution mode: released" "the release did not record its close path"
|
|
321
|
+
assert_contains "$show" "Go: ship it as planned." "the release lost the captain's words"
|
|
322
|
+
assert_contains "$show" "The widget plan body." "the release destroyed the work item body"
|
|
323
|
+
assert_contains "$show" 'Literal escape: \\n. Unicode: café.' \
|
|
324
|
+
"the release corrupted escaped or Unicode body text"
|
|
325
|
+
run_captain "$home" answer sample-widget --decision-file "$home/go.txt" --release >/dev/null \
|
|
326
|
+
|| fail "identical release retry was not idempotent"
|
|
327
|
+
if run_captain "$home" answer sample-widget --decision-file "$home/go.txt" \
|
|
328
|
+
> "$home/wrong-mode.out" 2> "$home/wrong-mode.err"; then
|
|
329
|
+
fail "a released answer replay without --release reported completion"
|
|
330
|
+
fi
|
|
331
|
+
assert_grep "mode released" "$home/wrong-mode.err" \
|
|
332
|
+
"the mismatched replay did not name the recorded release mode"
|
|
333
|
+
show=$(tasks_in "$home" show sample-widget --full)
|
|
334
|
+
assert_contains "$show" "state: queued" "a mismatched release replay closed the work item"
|
|
335
|
+
assert_contains "$show" "held: no" "a mismatched release replay re-held the work item"
|
|
336
|
+
|
|
337
|
+
tasks_in "$home" add sample-empty-label-widget "Ship without a display label" \
|
|
338
|
+
--kind ship --repo sample >/dev/null
|
|
339
|
+
run_captain "$home" hold sample-empty-label-widget --reason "captain go needed" >/dev/null
|
|
340
|
+
out=$(printf 'sample-empty-label-widget\tgo\t\trelease\n' \
|
|
341
|
+
| run_captain "$home" answers --source "empty-label release fixture") \
|
|
342
|
+
|| fail "an empty answer label shifted the release close mode"
|
|
343
|
+
assert_contains "$out" "closed: sample-empty-label-widget" \
|
|
344
|
+
"the empty-label release was not accepted"
|
|
345
|
+
show=$(tasks_in "$home" show sample-empty-label-widget --full)
|
|
346
|
+
assert_contains "$show" "state: queued" "an empty-label release completed its work item"
|
|
347
|
+
assert_contains "$show" "held: no" "an empty-label release did not lift the hold"
|
|
348
|
+
assert_contains "$show" "Resolution mode: released" \
|
|
349
|
+
"an empty-label release recorded the wrong close mode"
|
|
350
|
+
|
|
351
|
+
# A NEW captain gate on the same task later takes a NEW answer.
|
|
352
|
+
run_captain "$home" hold sample-widget --reason "captain pricing call needed" >/dev/null \
|
|
353
|
+
|| fail "could not re-hold the released work item"
|
|
354
|
+
printf 'Price it at nine dollars.\n' > "$home/price.txt"
|
|
355
|
+
run_captain "$home" answer sample-widget --decision-file "$home/price.txt" --release >/dev/null \
|
|
356
|
+
|| fail "a re-held task refused a new answer"
|
|
357
|
+
show=$(tasks_in "$home" show sample-widget --full)
|
|
358
|
+
assert_contains "$show" "Price it at nine dollars." "the new answer was not recorded"
|
|
359
|
+
assert_contains "$show" "Go: ship it as planned." "the new answer erased the earlier record"
|
|
360
|
+
|
|
361
|
+
tasks_in "$home" "done" sample-widget >/dev/null \
|
|
362
|
+
|| fail "could not complete the released work item normally"
|
|
363
|
+
if run_captain "$home" answer sample-widget --decision-file "$home/price.txt" \
|
|
364
|
+
> "$home/closed-wrong-mode.out" 2> "$home/closed-wrong-mode.err"; then
|
|
365
|
+
fail "a completed release replay without --release reported an answer"
|
|
366
|
+
fi
|
|
367
|
+
assert_grep "mode released" "$home/closed-wrong-mode.err" \
|
|
368
|
+
"the completed replay did not name the recorded release mode"
|
|
369
|
+
show=$(tasks_in "$home" show sample-widget --full)
|
|
370
|
+
assert_contains "$show" "state: done" "a refused completed replay changed task state"
|
|
371
|
+
pass "release frees held work with the captain's words recorded and the body preserved"
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
# Deferral is a date, not a live card: hold --until keeps the task out of
|
|
375
|
+
# captain_actionable until due, tasks-axi's own date-gate expiry keeps the task
|
|
376
|
+
# answerable, and Bearings renders the wait as a dated gate.
|
|
377
|
+
test_deferral_leaves_captains_call_until_due() {
|
|
378
|
+
local home json snap show
|
|
379
|
+
home=$(make_home deferral)
|
|
380
|
+
run_captain "$home" hold sample-later-call --title "Revisit the sample plan" \
|
|
381
|
+
--reason "captain deferred revisit later" --repo sample --until 2026-08-01 >/dev/null \
|
|
382
|
+
|| fail "could not register the deferred captain call"
|
|
383
|
+
run_captain "$home" hold sample-now-call --title "Decide the sample cut" \
|
|
384
|
+
--reason "captain cut choice pending" --repo sample >/dev/null \
|
|
385
|
+
|| fail "could not register the live captain call"
|
|
386
|
+
if run_captain "$home" hold sample-bad-date --title "Bad date" \
|
|
387
|
+
--reason "captain choice" --until 2026-8-1 > "$home/bad-date.out" 2> "$home/bad-date.err"; then
|
|
388
|
+
fail "hold accepted a malformed --until date"
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
snap=$(PATH="$home/fakebin:$PATH" FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" \
|
|
392
|
+
FM_DATA_OVERRIDE="$home/data" FM_CONFIG_OVERRIDE="$home/config" \
|
|
393
|
+
FM_PROJECTS_OVERRIDE="$home/projects" FM_SNAPSHOT_NOW=2026-07-14T12:00:00Z \
|
|
394
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json) || fail "fleet snapshot failed"
|
|
395
|
+
printf '%s' "$snap" | jq -e '
|
|
396
|
+
([.backlog.records[] | select(.id == "sample-later-call")][0]) as $later
|
|
397
|
+
| ([.backlog.records[] | select(.id == "sample-now-call")][0]) as $now
|
|
398
|
+
| $later.captain_actionable == false and $later.hold_until == "2026-08-01"
|
|
399
|
+
and $now.captain_actionable == true and $now.hold_until == null
|
|
400
|
+
and ($later.title | contains("hold-until") | not)
|
|
401
|
+
' >/dev/null || fail "the due gate or hold-until parsing is wrong: $snap"
|
|
402
|
+
|
|
403
|
+
json=$(run_bearings "$home") || fail "Bearings failed with a deferred call"
|
|
404
|
+
printf '%s' "$json" | jq -e '
|
|
405
|
+
(.decisions_open | any(.id == "sample-now-call"))
|
|
406
|
+
and (.decisions_open | any(.id == "sample-later-call") | not)
|
|
407
|
+
and (.gates | any(.id == "sample-later-call" and (.reason | startswith("until 2026-08-01"))))
|
|
408
|
+
' >/dev/null || fail "the deferred call did not render as a dated gate: $json"
|
|
409
|
+
|
|
410
|
+
# On its date the call is due again - and still answerable even though
|
|
411
|
+
# tasks-axi reports the expired hold as no longer held.
|
|
412
|
+
snap=$(PATH="$home/fakebin:$PATH" FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" \
|
|
413
|
+
FM_DATA_OVERRIDE="$home/data" FM_CONFIG_OVERRIDE="$home/config" \
|
|
414
|
+
FM_PROJECTS_OVERRIDE="$home/projects" FM_SNAPSHOT_NOW=2026-08-01T12:00:00Z \
|
|
415
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json) || fail "fleet snapshot failed at the due date"
|
|
416
|
+
printf '%s' "$snap" | jq -e '
|
|
417
|
+
[.backlog.records[] | select(.id == "sample-later-call")][0].captain_actionable == true
|
|
418
|
+
' >/dev/null || fail "a due deferral did not resurface as captain-actionable"
|
|
419
|
+
show=$(tasks_in "$home" show sample-later-call --full)
|
|
420
|
+
assert_contains "$show" "hold_kind: captain" "the expired deferral lost its captain-hold annotations"
|
|
421
|
+
printf 'Answered on the due date.\n' > "$home/due.txt"
|
|
422
|
+
run_captain "$home" answer sample-later-call --decision-file "$home/due.txt" >/dev/null \
|
|
423
|
+
|| fail "an expired deferral was not answerable"
|
|
424
|
+
pass "a deferred captain call leaves the live Captain's Call until its date and stays answerable"
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# The recorded-answer guard survives an out-of-band close: a bare tasks-axi done
|
|
428
|
+
# fails verify until answer records the captain's word, and an ordinary finished
|
|
429
|
+
# task can never be dressed up as an answered captain call.
|
|
430
|
+
test_out_of_band_close_is_recordable() {
|
|
431
|
+
local home id show
|
|
432
|
+
home=$(make_home out-of-band)
|
|
433
|
+
id=sample-fullrun-review
|
|
434
|
+
mkdir -p "$home/data/$id"
|
|
435
|
+
tasks_in "$home" add "$id" "Investigate the sample full run" --kind scout --repo sample --start >/dev/null \
|
|
436
|
+
|| fail "could not create out-of-band origin"
|
|
437
|
+
write_origin_meta "$home" "$id"
|
|
438
|
+
printf 'done: report complete\n' > "$home/state/$id.status"
|
|
439
|
+
printf '# Sample full run review\n\nOne captain choice remains.\n' > "$home/data/$id/report.md"
|
|
440
|
+
run_captain "$home" hold sample-submission-call --title "Choose the sample submission" \
|
|
441
|
+
--reason "captain submission choice pending" --repo sample --origin "$id" >/dev/null \
|
|
442
|
+
|| fail "could not register the captain-held task"
|
|
443
|
+
run_captain "$home" complete "$id" sample-submission-call >/dev/null \
|
|
444
|
+
|| fail "completion failed before the out-of-band close"
|
|
445
|
+
|
|
446
|
+
tasks_in "$home" "done" sample-submission-call >/dev/null \
|
|
447
|
+
|| fail "could not reproduce the direct out-of-band close"
|
|
448
|
+
if run_captain "$home" verify "$id" > "$home/broken-verify.out" 2> "$home/broken-verify.err"; then
|
|
449
|
+
fail "verification passed a captain call closed with no recorded answer"
|
|
450
|
+
fi
|
|
451
|
+
if run_teardown "$home" "$id" > "$home/broken-teardown.out" 2> "$home/broken-teardown.err"; then
|
|
452
|
+
fail "teardown proceeded while a captain call had no recorded answer"
|
|
453
|
+
fi
|
|
454
|
+
assert_present "$home/state/$id.meta" "refused teardown removed investigation metadata"
|
|
455
|
+
|
|
456
|
+
printf 'Declined: do not submit the sample full run upstream.\n' > "$home/submission.txt"
|
|
457
|
+
run_captain "$home" answer sample-submission-call --decision-file "$home/submission.txt" >/dev/null \
|
|
458
|
+
|| fail "answer could not record the missing captain decision on the closed task"
|
|
459
|
+
show=$(tasks_in "$home" show sample-submission-call --full)
|
|
460
|
+
assert_contains "$show" "state: done" "recording the answer reopened the closed task"
|
|
461
|
+
assert_contains "$show" "Resolution mode: repaired" "the retroactive record did not name its path"
|
|
462
|
+
assert_contains "$show" "Declined: do not submit the sample full run upstream." \
|
|
463
|
+
"the retroactive record lost the captain decision text"
|
|
464
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
465
|
+
|| fail "the recorded answer did not satisfy the completion gate"
|
|
466
|
+
run_captain "$home" answer sample-submission-call --decision-file "$home/submission.txt" >/dev/null \
|
|
467
|
+
|| fail "identical retroactive retry was not idempotent"
|
|
468
|
+
printf 'A different answer entirely.\n' > "$home/drifted.txt"
|
|
469
|
+
if run_captain "$home" answer sample-submission-call --decision-file "$home/drifted.txt" \
|
|
470
|
+
> "$home/drifted.out" 2> "$home/drifted.err"; then
|
|
471
|
+
fail "a drifted retry overwrote the recorded captain decision"
|
|
472
|
+
fi
|
|
473
|
+
run_teardown "$home" "$id" >/dev/null 2> "$home/teardown.err" \
|
|
474
|
+
|| fail "teardown still refused after the answer was recorded: $(cat "$home/teardown.err")"
|
|
475
|
+
|
|
476
|
+
# An ordinary finished task was never the captain's item; recording an
|
|
477
|
+
# invented answer on it must be refused.
|
|
478
|
+
tasks_in "$home" add sample-ordinary-work "Ordinary finished work" --kind ship --repo sample >/dev/null
|
|
479
|
+
tasks_in "$home" "done" sample-ordinary-work >/dev/null
|
|
480
|
+
printf 'An answer the captain never gave.\n' > "$home/invented.txt"
|
|
481
|
+
if run_captain "$home" answer sample-ordinary-work --decision-file "$home/invented.txt" \
|
|
482
|
+
> "$home/never-held.out" 2> "$home/never-held.err"; then
|
|
483
|
+
fail "an ordinary finished task was dressed up as an answered captain call"
|
|
484
|
+
fi
|
|
485
|
+
assert_grep "never held for the captain" "$home/never-held.err" \
|
|
486
|
+
"the refusal must say the task carries no captain-hold provenance"
|
|
487
|
+
pass "an out-of-band close is recordable with the captain's word and nothing else"
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
# A post-teardown visual review completes against the surviving report and
|
|
491
|
+
# durable tasks, with no volatile task metadata and no second decision database.
|
|
492
|
+
test_visual_review_uses_shared_completion_owner() {
|
|
493
|
+
local home id json
|
|
494
|
+
home=$(make_home visual-review)
|
|
495
|
+
id=sample-board-review
|
|
496
|
+
mkdir -p "$home/data/$id"
|
|
497
|
+
tasks_in "$home" add "$id" "Review the sample board" --kind scout --repo sample --start >/dev/null
|
|
498
|
+
write_origin_meta "$home" "$id"
|
|
499
|
+
printf 'done: investigation complete\n' > "$home/state/$id.status"
|
|
500
|
+
printf '# Sample board investigation\n\nThe initial findings need no captain choice.\n' > "$home/data/$id/report.md"
|
|
501
|
+
run_captain "$home" complete "$id" --none >/dev/null \
|
|
502
|
+
|| fail "initial investigation could not pass the shared completion owner"
|
|
503
|
+
run_teardown "$home" "$id" >/dev/null 2> "$home/visual-teardown.err" \
|
|
504
|
+
|| fail "completed investigation teardown failed: $(cat "$home/visual-teardown.err")"
|
|
505
|
+
tasks_in "$home" "done" "$id" --report "data/$id/report.md" --keep 0 >/dev/null
|
|
506
|
+
|
|
507
|
+
mkdir -p "$home/.lavish"
|
|
508
|
+
printf '<html><body>Synthetic sample board</body></html>\n' > "$home/.lavish/sample-board.html"
|
|
509
|
+
run_captain "$home" hold sample-layout-call --title "Choose the sample layout" \
|
|
510
|
+
--reason "captain layout choice pending" --repo sample --origin "$id" >/dev/null \
|
|
511
|
+
|| fail "post-teardown visual review could not use the shared hold owner"
|
|
512
|
+
run_captain "$home" complete "$id" sample-layout-call >/dev/null \
|
|
513
|
+
|| fail "post-teardown visual review could not use the shared completion owner"
|
|
514
|
+
json=$(run_bearings "$home") || fail "Bearings failed after the ended visual review"
|
|
515
|
+
printf '%s' "$json" | jq -e '
|
|
516
|
+
.decisions_open | any(.id == "sample-layout-call" and .verb == "captain-hold")
|
|
517
|
+
' >/dev/null || fail "ended visual review did not leave its durable Captain Call: $json"
|
|
518
|
+
[ ! -e "$home/data/visual-review-decisions.json" ] \
|
|
519
|
+
|| fail "visual review created a second decision database"
|
|
520
|
+
pass "ended visual review follows the same captain-hold completion owner"
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
test_none_inventory_and_resolved_prose_do_not_create_holds() {
|
|
524
|
+
local home id json
|
|
525
|
+
home=$(make_home no-false-holds)
|
|
526
|
+
id=sample-resolved-review
|
|
527
|
+
mkdir -p "$home/data/$id"
|
|
528
|
+
tasks_in "$home" add "$id" "Review a resolved sample finding" --kind scout --repo sample --start >/dev/null
|
|
529
|
+
write_origin_meta "$home" "$id"
|
|
530
|
+
printf 'resolved [key=old-choice]: the sample choice was already recorded\ndone: report complete\n' \
|
|
531
|
+
> "$home/state/$id.status"
|
|
532
|
+
cat > "$home/data/$id/report.md" <<'EOF'
|
|
533
|
+
# Resolved sample finding
|
|
534
|
+
|
|
535
|
+
Decision record: the earlier choice is resolved.
|
|
536
|
+
The recommendation is informational and needs no captain action.
|
|
537
|
+
EOF
|
|
538
|
+
run_captain "$home" complete "$id" --none >/dev/null \
|
|
539
|
+
|| fail "explicit no-call inventory failed"
|
|
540
|
+
json=$(run_bearings "$home") || fail "Bearings failed for no-call inventory"
|
|
541
|
+
printf '%s' "$json" | jq -e '
|
|
542
|
+
(.decisions_open | any(.id | startswith("sample-resolved-review")) | not)
|
|
543
|
+
' >/dev/null || fail "resolved findings or decision-like prose created a false captain call: $json"
|
|
544
|
+
pass "resolved findings and decision-like prose do not create captain-held tasks"
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
test_terminal_single_owner_status_decision_does_not_block_empty_inventory() {
|
|
548
|
+
local home id open secondmate
|
|
549
|
+
home=$(make_home stale-terminal-decision)
|
|
550
|
+
id=sample-terminal-review
|
|
551
|
+
mkdir -p "$home/data/$id"
|
|
552
|
+
tasks_in "$home" add "$id" "Review a terminal sample finding" --kind scout --repo sample --start >/dev/null
|
|
553
|
+
write_origin_meta "$home" "$id"
|
|
554
|
+
printf 'needs-decision [key=default]: choose route A or route B\ndone: report complete\n' \
|
|
555
|
+
> "$home/state/$id.status"
|
|
556
|
+
printf '# Terminal sample review\n\nNo unresolved captain choice remains.\n' > "$home/data/$id/report.md"
|
|
557
|
+
open=$(bash -c '. "$1"; status_open_decisions "$2"' _ \
|
|
558
|
+
"$ROOT/bin/fm-classify-lib.sh" "$home/state/$id.status")
|
|
559
|
+
assert_contains "$open" "default" "fixture must retain the raw stale status decision"
|
|
560
|
+
run_captain "$home" complete "$id" --none >/dev/null \
|
|
561
|
+
|| fail "terminal single-owner stale status decision blocked empty inventory completion"
|
|
562
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
563
|
+
|| fail "terminal single-owner stale status decision blocked inventory verification"
|
|
564
|
+
run_teardown "$home" "$id" >/dev/null 2> "$home/terminal-teardown.err" \
|
|
565
|
+
|| fail "terminal single-owner stale status decision blocked teardown: $(cat "$home/terminal-teardown.err")"
|
|
566
|
+
|
|
567
|
+
secondmate=sample-secondmate
|
|
568
|
+
write_origin_meta "$home" "$secondmate" secondmate
|
|
569
|
+
printf 'needs-decision [key=route]: choose route A or route B\ndone: heartbeat complete\n' \
|
|
570
|
+
> "$home/state/$secondmate.status"
|
|
571
|
+
if run_captain "$home" complete "$secondmate" --none \
|
|
572
|
+
> "$home/secondmate-terminal.out" 2> "$home/secondmate-terminal.err"; then
|
|
573
|
+
fail "secondmate terminal status decision was incorrectly cleared"
|
|
574
|
+
fi
|
|
575
|
+
pass "terminal single-owner stale status decisions do not block empty inventory"
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
test_secondmate_hold_stays_in_authoritative_home() {
|
|
579
|
+
local parent mate fakebin origin json
|
|
580
|
+
parent=$(make_home main-routing)
|
|
581
|
+
mate="$TMP_ROOT/sample-mate-home"
|
|
582
|
+
mkdir -p "$mate/data" "$mate/state" "$mate/config" "$mate/projects" "$mate/bin"
|
|
583
|
+
cp "$ROOT/.tasks.toml" "$mate/.tasks.toml"
|
|
584
|
+
printf '# Synthetic secondmate home\n' > "$mate/AGENTS.md"
|
|
585
|
+
printf 'sample-mate\n' > "$mate/.fm-secondmate-home"
|
|
586
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
587
|
+
## In flight
|
|
588
|
+
|
|
589
|
+
## Queued
|
|
590
|
+
|
|
591
|
+
## Done
|
|
592
|
+
EOF
|
|
593
|
+
fakebin=$(fm_fakebin "$mate")
|
|
594
|
+
fm_fake_exit0 "$fakebin" tmux treehouse no-mistakes gh gh-axi
|
|
595
|
+
origin=sample-mate-review
|
|
596
|
+
mkdir -p "$mate/data/$origin"
|
|
597
|
+
tasks_in "$mate" add "$origin" "Investigate secondmate sample" --kind scout --repo sample --start >/dev/null
|
|
598
|
+
write_origin_meta "$mate" "$origin"
|
|
599
|
+
printf 'done: report and visual review complete\n' > "$mate/state/$origin.status"
|
|
600
|
+
printf '# Sample secondmate review\n\nOne captain choice remains.\n' > "$mate/data/$origin/report.md"
|
|
601
|
+
run_captain "$mate" hold sample-release-call --title "Choose the sample release" \
|
|
602
|
+
--reason "captain release choice pending" --repo sample --origin "$origin" >/dev/null \
|
|
603
|
+
|| fail "secondmate-owned hold creation failed"
|
|
604
|
+
run_captain "$mate" complete "$origin" sample-release-call >/dev/null \
|
|
605
|
+
|| fail "secondmate-owned completion failed"
|
|
606
|
+
run_teardown "$mate" "$origin" >/dev/null 2> "$mate/teardown.err" \
|
|
607
|
+
|| fail "secondmate investigation teardown failed: $(cat "$mate/teardown.err")"
|
|
608
|
+
tasks_in "$mate" "done" "$origin" --report "data/$origin/report.md" --keep 0 >/dev/null
|
|
609
|
+
|
|
610
|
+
printf -- '- sample-mate - synthetic scope (home: %s; scope: sample reviews; projects: sample; added 2026-07-14)\n' \
|
|
611
|
+
"$mate" > "$parent/data/secondmates.md"
|
|
612
|
+
fm_write_secondmate_meta "$parent/state/sample-mate.meta" "$mate" \
|
|
613
|
+
"firstmate:fm-sample-mate" sample
|
|
614
|
+
json=$(run_bearings "$parent") || fail "parent Bearings could not read the secondmate captain call"
|
|
615
|
+
printf '%s' "$json" | jq -e '
|
|
616
|
+
.decisions_open | any(.owner == "sample-mate" and .verb == "captain-hold"
|
|
617
|
+
and (.id | endswith("sample-release-call")))
|
|
618
|
+
' >/dev/null || fail "secondmate captain call did not surface with authoritative owner: $json"
|
|
619
|
+
assert_no_grep "sample-release-call" "$parent/data/backlog.md" "secondmate call leaked into the main backlog"
|
|
620
|
+
assert_grep "sample-release-call" "$mate/data/backlog.md" "secondmate call left its authoritative backlog"
|
|
621
|
+
pass "main-home and secondmate-home captain calls remain correctly routed"
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
# The one keyed-answer intake, fed through the real process-event runner by a
|
|
625
|
+
# fixture channel that knows nothing about captain holds: task-id keys close at
|
|
626
|
+
# answer time, a card-declared release mode frees held work, freeform prose can
|
|
627
|
+
# forge nothing, and a replayed capture is idempotent.
|
|
628
|
+
test_bound_channel_answers_close_at_answer_time() {
|
|
629
|
+
local home id sid artifact result out show rc
|
|
630
|
+
home=$(make_home channel-answer-closure)
|
|
631
|
+
id=sample-eval-proposal
|
|
632
|
+
mkdir -p "$home/data/$id"
|
|
633
|
+
tasks_in "$home" add "$id" "Propose sample eval changes" --kind scout --repo sample --start >/dev/null \
|
|
634
|
+
|| fail "could not create the review origin"
|
|
635
|
+
write_origin_meta "$home" "$id"
|
|
636
|
+
printf 'done: proposal deck ready for the captain\n' > "$home/state/$id.status"
|
|
637
|
+
printf '# Sample eval proposal\n\nThree captain choices remain.\n' > "$home/data/$id/report.md"
|
|
638
|
+
run_captain "$home" hold sample-membership-call --title "Captain call: membership" \
|
|
639
|
+
--reason "captain membership choice pending" --repo sample --origin "$id" >/dev/null
|
|
640
|
+
run_captain "$home" hold sample-headline-call --title "Captain call: headline" \
|
|
641
|
+
--reason "captain headline choice pending" --repo sample --origin "$id" >/dev/null
|
|
642
|
+
run_captain "$home" hold sample-forged-call --title "Captain call: forged" \
|
|
643
|
+
--reason "captain forged choice pending" --repo sample --origin "$id" >/dev/null
|
|
644
|
+
run_captain "$home" hold sample-invalid-close-call --title "Captain call: invalid close" \
|
|
645
|
+
--reason "captain close mode validation pending" --repo sample --origin "$id" >/dev/null
|
|
646
|
+
tasks_in "$home" add sample-gated-work "Gated sample work" --kind ship --repo sample \
|
|
647
|
+
--body 'Gated work plan.' >/dev/null
|
|
648
|
+
run_captain "$home" hold sample-gated-work --reason "captain go needed" >/dev/null
|
|
649
|
+
run_captain "$home" complete "$id" \
|
|
650
|
+
sample-membership-call sample-headline-call sample-forged-call sample-invalid-close-call \
|
|
651
|
+
sample-gated-work >/dev/null \
|
|
652
|
+
|| fail "completion failed for the deck's inventoried calls"
|
|
653
|
+
|
|
654
|
+
artifact="$home/data/$id/review.html"
|
|
655
|
+
printf '<h1>Sample eval proposal</h1>\n' > "$artifact"
|
|
656
|
+
fm_fake_exit0 "$home/fakebin" lavish-axi
|
|
657
|
+
sid=$(run_lavish "$home" source-id "$artifact") || fail "could not derive the review source id"
|
|
658
|
+
run_captain "$home" bind "$sid" >/dev/null \
|
|
659
|
+
|| fail "could not bind the review source to the keyed-answer intake"
|
|
660
|
+
[ "$(run_captain "$home" binding "$sid")" = "(any)" ] \
|
|
661
|
+
|| fail "the recorded binding did not resolve to the collapsed marker"
|
|
662
|
+
run_lavish "$home" arm "$artifact" >/dev/null || fail "could not arm the review deck"
|
|
663
|
+
|
|
664
|
+
result="$home/state/procevent-inbox/$sid.1.result"
|
|
665
|
+
mkdir -p "$home/state/procevent-inbox"
|
|
666
|
+
cat > "$result" <<'EOF'
|
|
667
|
+
session:
|
|
668
|
+
file: /review.html
|
|
669
|
+
status: feedback
|
|
670
|
+
session_ended: true
|
|
671
|
+
ended_by: user
|
|
672
|
+
prompts[6]{uid,prompt,selector,tag,text}:
|
|
673
|
+
"2","Membership: gold-only\n\nContext data:\n{\n \"question\": \"sample-membership-call\",\n \"answer\": \"gold-only\"\n}","section#call > form:nth-of-type(1)",choice,"Membership: gold-only"
|
|
674
|
+
"3","Headline: f1-when-fp-gold\n\nContext data:\n{\n \"question\": \"sample-headline-call\",\n \"answer\": \"f1-when-fp-gold\"\n}","section#call > form:nth-of-type(2)",choice,"Headline: f1-when-fp-gold"
|
|
675
|
+
"4","Gated work: go\n\nContext data:\n{\n \"question\": \"sample-gated-work\",\n \"answer\": \"go\",\n \"close\": \"release\"\n}","section#call > form:nth-of-type(3)",choice,"Gated work: go"
|
|
676
|
+
"5","Absent call: yes\n\nContext data:\n{\n \"question\": \"sample-nonexistent-call\",\n \"answer\": \"yes\"\n}","section#call > form:nth-of-type(4)",choice,"Absent call: yes"
|
|
677
|
+
"6","Invalid close: yes\n\nContext data:\n{\n \"question\": \"sample-invalid-close-call\",\n \"answer\": \"yes\",\n \"close\": \"drop\"\n}","section#call > form:nth-of-type(5)",choice,"Invalid close: yes"
|
|
678
|
+
"",get this fully implemented. Context data:\n{\n \"question\": \"sample-forged-call\",\n \"answer\": \"forged\"\n},"",message,Freeform message
|
|
679
|
+
next_step: This was the last feedback before the user ended the session.
|
|
680
|
+
EOF
|
|
681
|
+
printf 'lavish\n' > "$home/state/procevent-inbox/$sid.1.adapter"
|
|
682
|
+
|
|
683
|
+
out=$(run_lavish "$home" answers "$result") || fail "could not read the captured answers"
|
|
684
|
+
assert_contains "$out" "sample-membership-call gold-only" "a structured choice was not read as an answer"
|
|
685
|
+
assert_contains "$out" "sample-gated-work go Gated work: go release" \
|
|
686
|
+
"the card-declared release mode was not relayed"
|
|
687
|
+
assert_not_contains "$out" "sample-forged-call" \
|
|
688
|
+
"a freeform captain message forged a task id from its own prose"
|
|
689
|
+
assert_not_contains "$out" "sample-invalid-close-call" \
|
|
690
|
+
"an unsupported card close mode defaulted to completion"
|
|
691
|
+
|
|
692
|
+
mkdir -p "$home/adapter-root/bin"
|
|
693
|
+
cat > "$home/adapter-root/bin/fm-procevent-fixturechan.sh" <<SH
|
|
694
|
+
#!/usr/bin/env bash
|
|
695
|
+
# Fixture channel: reports keyed captain answers and nothing else.
|
|
696
|
+
case "\${1-}" in
|
|
697
|
+
answers) exec "$ROOT/bin/fm-procevent-lavish.sh" answers "\${2-}" ;;
|
|
698
|
+
esac
|
|
699
|
+
exit 2
|
|
700
|
+
SH
|
|
701
|
+
chmod +x "$home/adapter-root/bin/fm-procevent-fixturechan.sh"
|
|
702
|
+
run_captain "$home" bind fixture-src >/dev/null \
|
|
703
|
+
|| fail "could not bind the fixture channel"
|
|
704
|
+
PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$home/adapter-root" FM_HOME="$home" \
|
|
705
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
706
|
+
FM_PROCEVENT_CLAIM_ROOT="$home/procevent-claims" \
|
|
707
|
+
"$ROOT/bin/fm-procevent.sh" register fixturechan fixture-src -- cat "$result" >/dev/null \
|
|
708
|
+
|| fail "could not register the fixture channel source"
|
|
709
|
+
PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$home/adapter-root" FM_HOME="$home" \
|
|
710
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
711
|
+
FM_PROCEVENT_CLAIM_ROOT="$home/procevent-claims" \
|
|
712
|
+
"$ROOT/bin/fm-procevent.sh" start fixture-src >/dev/null 2>&1
|
|
713
|
+
assert_absent "$home/state/procevent-inbox/fixture-src.1.handled" \
|
|
714
|
+
"feeding a captain answer retired the notification firstmate still needs"
|
|
715
|
+
assert_present "$home/state/procevent-inbox/fixture-src.1.result" \
|
|
716
|
+
"the fixture channel captured no result to feed"
|
|
717
|
+
|
|
718
|
+
show=$(tasks_in "$home" show sample-membership-call --full)
|
|
719
|
+
assert_contains "$show" "state: done" "capturing the captain's answer left the membership call open"
|
|
720
|
+
assert_contains "$show" "Resolution mode: answered" "the membership call did not record its close path"
|
|
721
|
+
assert_contains "$show" "Answer: gold-only" "the closed call did not record the captain's actual answer"
|
|
722
|
+
show=$(tasks_in "$home" show sample-gated-work --full)
|
|
723
|
+
assert_contains "$show" "state: queued" "the released work item did not stay queued"
|
|
724
|
+
assert_contains "$show" "held: no" "the card-declared release did not lift the hold"
|
|
725
|
+
assert_contains "$show" "Resolution mode: released" "the released work did not record its close path"
|
|
726
|
+
assert_contains "$show" "Gated work plan." "the released work item lost its body"
|
|
727
|
+
show=$(tasks_in "$home" show sample-forged-call --full)
|
|
728
|
+
assert_contains "$show" "state: queued" "a forged key from freeform prose closed a captain call"
|
|
729
|
+
show=$(tasks_in "$home" show sample-invalid-close-call --full)
|
|
730
|
+
assert_contains "$show" "state: queued" "an unsupported card close mode closed a captain call"
|
|
731
|
+
assert_contains "$show" "held: yes" "an unsupported card close mode released a captain call"
|
|
732
|
+
|
|
733
|
+
# Replaying the same capture is a no-op, not a rejected different decision. A
|
|
734
|
+
# run that could not close every answered key still reports nonzero.
|
|
735
|
+
set +e
|
|
736
|
+
out=$(run_lavish "$home" answers "$result" \
|
|
737
|
+
| run_captain "$home" answers --source "the captured result fixture-src sequence 1" 2>&1)
|
|
738
|
+
rc=$?
|
|
739
|
+
set -e
|
|
740
|
+
[ "$rc" -ne 0 ] || fail "a run that skipped a key reported success"
|
|
741
|
+
assert_contains "$out" "closed: sample-membership-call" \
|
|
742
|
+
"replaying an identical capture was not idempotent: $out"
|
|
743
|
+
assert_contains "$out" "closed: sample-gated-work" \
|
|
744
|
+
"replaying an identical released answer was not idempotent: $out"
|
|
745
|
+
assert_contains "$out" "skipped: sample-nonexistent-call" \
|
|
746
|
+
"a key naming no task was not reported as skipped: $out"
|
|
747
|
+
|
|
748
|
+
printf 'Captain answered the forged call directly.\n' > "$home/forged.txt"
|
|
749
|
+
run_captain "$home" answer sample-forged-call --decision-file "$home/forged.txt" >/dev/null \
|
|
750
|
+
|| fail "could not close the untouched call through the answer path"
|
|
751
|
+
printf 'Captain answered the invalid-close call directly.\n' > "$home/invalid-close.txt"
|
|
752
|
+
run_captain "$home" answer sample-invalid-close-call --decision-file "$home/invalid-close.txt" >/dev/null \
|
|
753
|
+
|| fail "could not close the invalid-close call through the answer path"
|
|
754
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
755
|
+
|| fail "answered calls did not satisfy the completion gate"
|
|
756
|
+
pass "a bound channel's captured answers close their captain-held tasks at answer time"
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
# Answer-time closure is opt-in per source. A channel with no binding must behave
|
|
760
|
+
# exactly as it always did: capture, announce, close nothing.
|
|
761
|
+
test_unbound_source_closes_no_hold() {
|
|
762
|
+
local home id sid artifact result out show rc
|
|
763
|
+
home=$(make_home lavish-unbound)
|
|
764
|
+
id=sample-unbound-review
|
|
765
|
+
mkdir -p "$home/data/$id"
|
|
766
|
+
tasks_in "$home" add "$id" "Review sample without binding" --kind scout --repo sample --start >/dev/null \
|
|
767
|
+
|| fail "could not create the unbound origin"
|
|
768
|
+
write_origin_meta "$home" "$id"
|
|
769
|
+
printf 'done: deck ready\n' > "$home/state/$id.status"
|
|
770
|
+
printf '# Unbound review\n\nOne captain choice remains.\n' > "$home/data/$id/report.md"
|
|
771
|
+
run_captain "$home" hold sample-only-call --title "Captain call: only choice" \
|
|
772
|
+
--reason "captain only choice pending" --repo sample --origin "$id" >/dev/null \
|
|
773
|
+
|| fail "could not register the unbound call"
|
|
774
|
+
|
|
775
|
+
artifact="$home/data/$id/review.html"
|
|
776
|
+
printf '<h1>Unbound</h1>\n' > "$artifact"
|
|
777
|
+
fm_fake_exit0 "$home/fakebin" lavish-axi
|
|
778
|
+
sid=$(run_lavish "$home" source-id "$artifact") || fail "could not derive the unbound source id"
|
|
779
|
+
run_lavish "$home" arm "$artifact" >/dev/null || fail "could not arm the unbound review"
|
|
780
|
+
|
|
781
|
+
result="$home/state/procevent-inbox/$sid.1.result"
|
|
782
|
+
mkdir -p "$home/state/procevent-inbox"
|
|
783
|
+
cat > "$result" <<'EOF'
|
|
784
|
+
session:
|
|
785
|
+
file: /review.html
|
|
786
|
+
status: feedback
|
|
787
|
+
prompts[1]{uid,prompt,selector,tag,text}:
|
|
788
|
+
"2","Only choice: yes\n\nContext data:\n{\n \"question\": \"sample-only-call\",\n \"answer\": \"yes\"\n}","form",choice,"Only choice: yes"
|
|
789
|
+
EOF
|
|
790
|
+
set +e
|
|
791
|
+
out=$(run_captain "$home" binding "$sid" 2>&1)
|
|
792
|
+
rc=$?
|
|
793
|
+
set -e
|
|
794
|
+
[ "$rc" -ne 0 ] || fail "an unbound source reported a binding"
|
|
795
|
+
[ -z "$out" ] || fail "an unbound source printed a binding: $out"
|
|
796
|
+
show=$(tasks_in "$home" show sample-only-call --full)
|
|
797
|
+
assert_contains "$show" "state: queued" "an unbound review closed a captain call"
|
|
798
|
+
assert_contains "$show" "held: yes" "an unbound review released a captain call"
|
|
799
|
+
pass "a channel source with no decision binding closes nothing"
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
# Everything a pre-collapse install already has keeps working: composed
|
|
803
|
+
# identities through the shim, short decision keys in recorded metadata, a
|
|
804
|
+
# concrete-origin binding, and the chat fallback for old rows.
|
|
805
|
+
test_legacy_identities_keep_working() {
|
|
806
|
+
local home id hold out show legacy_text legacy_digest old_hold
|
|
807
|
+
home=$(make_home legacy-compat)
|
|
808
|
+
id=sample-legacy-review
|
|
809
|
+
mkdir -p "$home/data/$id"
|
|
810
|
+
tasks_in "$home" add "$id" "Legacy-shaped review" --kind scout --repo sample --start >/dev/null
|
|
811
|
+
write_origin_meta "$home" "$id"
|
|
812
|
+
printf 'done: report complete\n' > "$home/state/$id.status"
|
|
813
|
+
printf '# Legacy review\n\nTwo captain choices remain.\n' > "$home/data/$id/report.md"
|
|
814
|
+
|
|
815
|
+
hold=$(run_shim "$home" id "$id" pick-one)
|
|
816
|
+
[ "$hold" = "$id-decision-pick-one" ] || fail "the shim identity was not deterministic: $hold"
|
|
817
|
+
out=$(run_shim "$home" hold "$id" pick-one \
|
|
818
|
+
--title "Pick one" --reason "captain choice pending" --repo sample) \
|
|
819
|
+
|| fail "the shim hold path failed"
|
|
820
|
+
[ "$out" = "$hold" ] || fail "the shim hold did not print the composed identity: $out"
|
|
821
|
+
run_shim "$home" hold "$id" keep-two \
|
|
822
|
+
--title "Keep two" --reason "captain second choice pending" --repo sample >/dev/null \
|
|
823
|
+
|| fail "the shim second hold failed"
|
|
824
|
+
show=$(tasks_in "$home" show "$hold" --full)
|
|
825
|
+
assert_contains "$show" "hold_kind: captain" "the shim-created row is not a plain captain-held task"
|
|
826
|
+
|
|
827
|
+
# A pre-collapse metadata attestation records SHORT keys; verify must resolve
|
|
828
|
+
# them through the legacy composed identity.
|
|
829
|
+
printf 'decisions_reviewed=1\ndecision_keys=keep-two,pick-one\n' >> "$home/state/$id.meta"
|
|
830
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
831
|
+
|| fail "legacy short-key metadata did not verify against composed identities"
|
|
832
|
+
|
|
833
|
+
# The shim's routed close records the routed work inside the captain decision
|
|
834
|
+
# and clears the recorded edge.
|
|
835
|
+
tasks_in "$home" add sample-legacy-work "Apply the legacy choice" \
|
|
836
|
+
--kind ship --repo sample --blocked-by "$hold" >/dev/null
|
|
837
|
+
tasks_in "$home" add sample-unrouted-work "Unrouted legacy work" \
|
|
838
|
+
--kind ship --repo sample >/dev/null
|
|
839
|
+
printf 'Use route north.\n' > "$home/route.txt"
|
|
840
|
+
if run_shim "$home" resolve "$id" pick-one --decision-file "$home/route.txt" \
|
|
841
|
+
--routed-to sample-missing-work > "$home/missing-route.out" 2> "$home/missing-route.err"; then
|
|
842
|
+
fail "the shim resolve accepted a missing routed task"
|
|
843
|
+
fi
|
|
844
|
+
if run_shim "$home" resolve "$id" pick-one --decision-file "$home/route.txt" \
|
|
845
|
+
--routed-to sample-unrouted-work > "$home/unrouted.out" 2> "$home/unrouted.err"; then
|
|
846
|
+
fail "the shim resolve accepted work not blocked by the legacy decision"
|
|
847
|
+
fi
|
|
848
|
+
show=$(tasks_in "$home" show "$hold" --full)
|
|
849
|
+
assert_contains "$show" "state: queued" "invalid shim routing closed the legacy decision"
|
|
850
|
+
assert_not_contains "$show" "Resolution recorded" "invalid shim routing recorded an answer"
|
|
851
|
+
run_shim "$home" resolve "$id" pick-one --decision-file "$home/route.txt" \
|
|
852
|
+
--routed-to sample-legacy-work >/dev/null \
|
|
853
|
+
|| fail "the shim resolve path failed"
|
|
854
|
+
show=$(tasks_in "$home" show "$hold" --full)
|
|
855
|
+
assert_contains "$show" "state: done" "the shim resolve did not close the row"
|
|
856
|
+
assert_contains "$show" "Use route north." "the shim resolve lost the captain decision"
|
|
857
|
+
assert_contains "$show" "- sample-legacy-work" "the shim resolve lost the routed identities"
|
|
858
|
+
show=$(tasks_in "$home" show sample-legacy-work --full)
|
|
859
|
+
assert_contains "$show" "blocked: no" "the shim resolve did not release the routed work"
|
|
860
|
+
|
|
861
|
+
old_hold=$(run_shim "$home" hold "$id" old-route \
|
|
862
|
+
--title "Old routed choice" --reason "captain old route pending" --repo sample)
|
|
863
|
+
tasks_in "$home" add sample-old-routed-work "Apply the old routed choice" \
|
|
864
|
+
--kind ship --repo sample --blocked-by "$old_hold" >/dev/null
|
|
865
|
+
printf 'Use the historical route.\n' > "$home/old-route.txt"
|
|
866
|
+
legacy_text=$(cat "$home/old-route.txt")
|
|
867
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
868
|
+
legacy_digest=$(printf '%s' "$legacy_text" | shasum -a 256 | awk '{print $1}')
|
|
869
|
+
else
|
|
870
|
+
legacy_digest=$(printf '%s' "$legacy_text" | sha256sum | awk '{print $1}')
|
|
871
|
+
fi
|
|
872
|
+
printf 'Resolution recorded by fm-decision-hold.\nDecision digest: %s\nRouted identities: sample-old-routed-work\nResolution mode: routed\n\nCaptain decision:\n%s\n\nRouted work:\n- sample-old-routed-work\n' \
|
|
873
|
+
"$legacy_digest" "$legacy_text" > "$home/old-route-body.txt"
|
|
874
|
+
tasks_in "$home" update "$old_hold" --body-file "$home/old-route-body.txt" --archive-body >/dev/null
|
|
875
|
+
run_shim "$home" resolve "$id" old-route --decision-file "$home/old-route.txt" \
|
|
876
|
+
--routed-to sample-old-routed-work >/dev/null \
|
|
877
|
+
|| fail "the shim did not replay a matching pre-collapse routed record"
|
|
878
|
+
show=$(tasks_in "$home" show "$old_hold" --full)
|
|
879
|
+
assert_contains "$show" "state: done" "the replayed legacy resolve did not close its hold"
|
|
880
|
+
show=$(tasks_in "$home" show sample-old-routed-work --full)
|
|
881
|
+
assert_contains "$show" "blocked_by: none" "the replayed legacy resolve did not clear its recorded edge"
|
|
882
|
+
|
|
883
|
+
# The shim decline path maps onto the same recorded answer.
|
|
884
|
+
printf 'Declined: keep the current shape.\n' > "$home/decline.txt"
|
|
885
|
+
run_shim "$home" decline "$id" keep-two --decision-file "$home/decline.txt" >/dev/null \
|
|
886
|
+
|| fail "the shim decline path failed"
|
|
887
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
888
|
+
|| fail "shim-closed rows did not satisfy the completion gate"
|
|
889
|
+
|
|
890
|
+
# A concrete-origin binding (a pre-collapse record) makes short channel keys
|
|
891
|
+
# resolve through the composed identity.
|
|
892
|
+
run_shim "$home" hold "$id" third-choice \
|
|
893
|
+
--title "Third choice" --reason "captain third choice pending" --repo sample >/dev/null
|
|
894
|
+
run_shim "$home" bind legacy-src "$id" >/dev/null || fail "the shim bind path failed"
|
|
895
|
+
[ "$(run_captain "$home" binding legacy-src)" = "$id" ] \
|
|
896
|
+
|| fail "the concrete-origin binding was not preserved"
|
|
897
|
+
printf 'third-choice\toption b\t\n' \
|
|
898
|
+
| run_captain "$home" answers "$(run_captain "$home" binding legacy-src)" \
|
|
899
|
+
--source "legacy channel" >/dev/null \
|
|
900
|
+
|| fail "a short key did not resolve through the concrete-origin binding"
|
|
901
|
+
show=$(tasks_in "$home" show "$id-decision-third-choice" --full)
|
|
902
|
+
assert_contains "$show" "state: done" "the legacy-keyed answer did not close its row"
|
|
903
|
+
|
|
904
|
+
run_shim "$home" hold "$id" fourth-choice \
|
|
905
|
+
--title "Fourth choice" --reason "captain fourth choice pending" --repo sample >/dev/null
|
|
906
|
+
legacy_text=$(printf 'Captain answered this decision through legacy replay.\nDecision key: fourth-choice\nAnswer: option c\n')
|
|
907
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
908
|
+
legacy_digest=$(printf '%s' "$legacy_text" | shasum -a 256 | awk '{print $1}')
|
|
909
|
+
else
|
|
910
|
+
legacy_digest=$(printf '%s' "$legacy_text" | sha256sum | awk '{print $1}')
|
|
911
|
+
fi
|
|
912
|
+
printf 'Resolution recorded by fm-decision-hold.\nDecision digest: %s\nRouted identities: none\nResolution mode: answered\n\nCaptain decision:\n%s\n' \
|
|
913
|
+
"$legacy_digest" "$legacy_text" > "$home/legacy-body.txt"
|
|
914
|
+
tasks_in "$home" update "$id-decision-fourth-choice" --body-file "$home/legacy-body.txt" --archive-body >/dev/null
|
|
915
|
+
tasks_in "$home" "done" "$id-decision-fourth-choice" >/dev/null
|
|
916
|
+
out=$(printf 'fourth-choice\toption c\t\n' \
|
|
917
|
+
| run_captain "$home" answers "$id" --source "legacy replay") \
|
|
918
|
+
|| fail "an identical pre-collapse keyed answer was not idempotent"
|
|
919
|
+
assert_contains "$out" "closed: $id-decision-fourth-choice" \
|
|
920
|
+
"the pre-collapse keyed answer digest was treated as drift"
|
|
921
|
+
out=$(printf '%s-decision-fourth-choice\toption c\t\n' "$id" \
|
|
922
|
+
| run_captain "$home" answers --source "legacy replay") \
|
|
923
|
+
|| fail "a full legacy task-id replay without an origin was not idempotent"
|
|
924
|
+
assert_contains "$out" "closed: $id-decision-fourth-choice" \
|
|
925
|
+
"the origin-free legacy replay digest was treated as drift"
|
|
926
|
+
pass "legacy identities, metadata, bindings, and the shim keep working"
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
# The intake is channel-agnostic, so chat must reach it the same way a captured
|
|
930
|
+
# review does - for a task-id key, and for a legacy composed identity.
|
|
931
|
+
test_chat_channel_feeds_the_same_keyed_answer_intake() {
|
|
932
|
+
local home id fb show
|
|
933
|
+
home=$(make_home chat-channel)
|
|
934
|
+
id=sample-chat-review
|
|
935
|
+
mkdir -p "$home/data/$id"
|
|
936
|
+
tasks_in "$home" add "$id" "Review sample chat routing" --kind scout --repo sample --start >/dev/null \
|
|
937
|
+
|| fail "could not create the chat-channel origin"
|
|
938
|
+
write_origin_meta "$home" "$id" ship
|
|
939
|
+
printf 'needs-decision [key=chat-choice]: pick option A or option B\n' > "$home/state/$id.status"
|
|
940
|
+
printf '# Chat review\n\nTwo captain choices remain.\n' > "$home/data/$id/report.md"
|
|
941
|
+
run_shim "$home" hold "$id" chat-choice \
|
|
942
|
+
--title "Choose the sample chat option" --reason "captain chat choice pending" --repo sample >/dev/null \
|
|
943
|
+
|| fail "could not register the legacy chat row"
|
|
944
|
+
run_captain "$home" hold sample-chat-followup --title "Choose the chat follow-up" \
|
|
945
|
+
--reason "captain follow-up choice pending" --repo sample >/dev/null \
|
|
946
|
+
|| fail "could not register the task-id chat call"
|
|
947
|
+
run_captain "$home" complete "$id" "$id-decision-chat-choice" sample-chat-followup >/dev/null \
|
|
948
|
+
|| fail "completion failed for the chat calls"
|
|
949
|
+
grep -F 'captain-held [key=chat-choice]' "$home/state/$id.status" >/dev/null \
|
|
950
|
+
|| fail "precondition: completion did not transfer the decision to its durable owner"
|
|
951
|
+
|
|
952
|
+
fb="$home/fakebin"
|
|
953
|
+
cat > "$fb/tmux" <<'SH'
|
|
954
|
+
#!/usr/bin/env bash
|
|
955
|
+
set -u
|
|
956
|
+
case "${1:-}" in
|
|
957
|
+
send-keys)
|
|
958
|
+
shift
|
|
959
|
+
literal=0
|
|
960
|
+
while [ $# -gt 0 ]; do
|
|
961
|
+
case "$1" in
|
|
962
|
+
-t) shift 2 ;;
|
|
963
|
+
-l) literal=1; shift ;;
|
|
964
|
+
*) break ;;
|
|
965
|
+
esac
|
|
966
|
+
done
|
|
967
|
+
if [ "$literal" = 1 ]; then
|
|
968
|
+
printf '%s' "${1:-}" >> "$FM_SEND_LOG"
|
|
969
|
+
fi
|
|
970
|
+
exit 0 ;;
|
|
971
|
+
display-message)
|
|
972
|
+
for a in "$@"; do case "$a" in *cursor_y*) printf '1\n'; exit 0 ;; esac; done
|
|
973
|
+
printf 'fakepane\n'; exit 0 ;;
|
|
974
|
+
capture-pane) printf '╭────╮\n│ │\n╰────╯\n'; exit 0 ;;
|
|
975
|
+
list-windows) exit 0 ;;
|
|
976
|
+
esac
|
|
977
|
+
exit 0
|
|
978
|
+
SH
|
|
979
|
+
chmod +x "$fb/tmux"
|
|
980
|
+
|
|
981
|
+
: > "$home/send.log"
|
|
982
|
+
env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$home" FM_HOME="$home" \
|
|
983
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
984
|
+
FM_SEND_LOG="$home/send.log" FM_SEND_SETTLE=0 \
|
|
985
|
+
"$ROOT/bin/fm-send.sh" "$id" --resolve-key chat-choice "go with option A" >/dev/null 2>&1 \
|
|
986
|
+
|| fail "an answer to a transferred legacy decision was refused by the chat channel"
|
|
987
|
+
# The answer rides fm-send's durable inbox plane: the record carries the
|
|
988
|
+
# text while the typed channel carries only the doorbell.
|
|
989
|
+
grep -qF "go with option A" "$home/state/$id.inbox/001.msg" \
|
|
990
|
+
|| fail "the answer text never reached the worker's durable inbox record"
|
|
991
|
+
show=$(tasks_in "$home" show "$id-decision-chat-choice" --full)
|
|
992
|
+
assert_contains "$show" "state: done" "a chat answer left the legacy row open"
|
|
993
|
+
assert_contains "$show" "Answer: go with option A" "the chat-answered row lost the captain answer"
|
|
994
|
+
|
|
995
|
+
: > "$home/send.log"
|
|
996
|
+
env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$home" FM_HOME="$home" \
|
|
997
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
998
|
+
FM_SEND_LOG="$home/send.log" FM_SEND_SETTLE=0 \
|
|
999
|
+
"$ROOT/bin/fm-send.sh" "$id" --resolve-key sample-chat-followup "take the second option" >/dev/null 2>&1 \
|
|
1000
|
+
|| fail "an answer keyed by a task id was refused by the chat channel"
|
|
1001
|
+
show=$(tasks_in "$home" show sample-chat-followup --full)
|
|
1002
|
+
assert_contains "$show" "state: done" "a chat answer left the task-id call open"
|
|
1003
|
+
assert_contains "$show" "Resolution mode: answered" "the chat-answered call did not record its close path"
|
|
1004
|
+
assert_contains "$show" "Answer: take the second option" "the chat-answered call lost the captain answer"
|
|
1005
|
+
assert_contains "$show" "answer sent to $id" "the chat-answered call lost its channel provenance"
|
|
1006
|
+
|
|
1007
|
+
if env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$home" FM_HOME="$home" \
|
|
1008
|
+
FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
|
|
1009
|
+
FM_SEND_LOG="$home/send.log" FM_SEND_SETTLE=0 \
|
|
1010
|
+
"$ROOT/bin/fm-send.sh" "$id" --resolve-key sample-chat-followup "again" \
|
|
1011
|
+
> "$home/closed-key.out" 2> "$home/closed-key.err"; then
|
|
1012
|
+
fail "a key already closed in both ledgers was accepted"
|
|
1013
|
+
fi
|
|
1014
|
+
run_captain "$home" verify "$id" >/dev/null \
|
|
1015
|
+
|| fail "chat-answered calls did not satisfy the completion gate"
|
|
1016
|
+
pass "the chat channel feeds the same keyed-answer intake a captured review does"
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
test_origin_slug_validation_precedes_path_construction() {
|
|
1020
|
+
local home
|
|
1021
|
+
home=$(make_home slug-validation)
|
|
1022
|
+
if run_captain "$home" complete "../escape" --none > "$home/escape.out" 2> "$home/escape.err"; then
|
|
1023
|
+
fail "complete accepted a path-escaping origin id"
|
|
1024
|
+
fi
|
|
1025
|
+
assert_grep "privacy-safe slug" "$home/escape.err" "the refusal must name the slug contract"
|
|
1026
|
+
if run_captain "$home" verify "../escape" > "$home/escape-verify.out" 2> "$home/escape-verify.err"; then
|
|
1027
|
+
fail "verify accepted a path-escaping origin id"
|
|
1028
|
+
fi
|
|
1029
|
+
if run_captain "$home" hold "bad id" --title "x" --reason "y" > "$home/bad-hold.out" 2> "$home/bad-hold.err"; then
|
|
1030
|
+
fail "hold accepted an invalid task id"
|
|
1031
|
+
fi
|
|
1032
|
+
pass "completion and verification validate origins before constructing paths"
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
# --- record divergence ------------------------------------------------------
|
|
1036
|
+
|
|
1037
|
+
run_drain() { # <home>
|
|
1038
|
+
local home=$1
|
|
1039
|
+
PATH="$home/fakebin:$PATH" REAL_TASKS_AXI="$TASKS_AXI_BIN" \
|
|
1040
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" \
|
|
1041
|
+
FM_DATA_OVERRIDE="$home/data" FM_CONFIG_OVERRIDE="$home/config" \
|
|
1042
|
+
"$ROOT/bin/fm-wake-drain.sh" 2>/dev/null
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
# Reconstructs the 2026-08-06 loss with synthetic names: the answer was posted
|
|
1046
|
+
# as a `resolved [key=...]` line and nothing else, so the status fold went quiet
|
|
1047
|
+
# while the durable captain-held task stayed open and kept reading as if the
|
|
1048
|
+
# captain had never spoken. Both identities that can carry a captain call must
|
|
1049
|
+
# be caught - the collapsed one (the key IS the task id) and the legacy derived
|
|
1050
|
+
# one a pre-collapse origin minted - and the report must reach the drain, which
|
|
1051
|
+
# is where firstmate actually looks.
|
|
1052
|
+
test_status_resolution_over_an_open_hold_is_signalled() {
|
|
1053
|
+
local home id out drain
|
|
1054
|
+
home=$(make_home divergence-signalled)
|
|
1055
|
+
id=sample-route-review
|
|
1056
|
+
tasks_in "$home" add "$id" "Investigate sample routing" --kind scout --repo sample --start >/dev/null \
|
|
1057
|
+
|| fail "could not create the investigation fixture"
|
|
1058
|
+
write_origin_meta "$home" "$id"
|
|
1059
|
+
run_captain "$home" hold sample-route-call \
|
|
1060
|
+
--title "Choose route: north or south" --reason "captain route choice pending" \
|
|
1061
|
+
--repo sample --origin "$id" >/dev/null \
|
|
1062
|
+
|| fail "could not register the collapsed-identity captain call"
|
|
1063
|
+
run_captain "$home" hold "$id-decision-access" \
|
|
1064
|
+
--title "Open or restricted sample access" --reason "captain access choice pending" \
|
|
1065
|
+
--repo sample --origin "$id" >/dev/null \
|
|
1066
|
+
|| fail "could not register the legacy-identity captain call"
|
|
1067
|
+
cat > "$home/state/$id.status" <<'EOF'
|
|
1068
|
+
working: report drafted
|
|
1069
|
+
needs-decision [key=sample-route-call]: north or south
|
|
1070
|
+
resolved [key=sample-route-call]: answered: north
|
|
1071
|
+
needs-decision [key=access]: open or restricted sample access
|
|
1072
|
+
resolved [key=access]: answered: restricted
|
|
1073
|
+
done: report complete
|
|
1074
|
+
EOF
|
|
1075
|
+
|
|
1076
|
+
out=$(run_captain "$home" diverged) || fail "diverged failed on the reconstructed loss"
|
|
1077
|
+
printf '%s\n' "$out" | grep -F "sample-route-call $id sample-route-call" >/dev/null \
|
|
1078
|
+
|| fail "the collapsed-identity divergence was not signalled: $out"
|
|
1079
|
+
printf '%s\n' "$out" | grep -F "$id-decision-access $id access" >/dev/null \
|
|
1080
|
+
|| fail "the legacy-identity divergence was not signalled: $out"
|
|
1081
|
+
|
|
1082
|
+
drain=$(run_drain "$home") || fail "the drain failed while reporting divergence"
|
|
1083
|
+
printf '%s\n' "$drain" | grep -F 'RECORD DIVERGENCE' >/dev/null \
|
|
1084
|
+
|| fail "the divergence never reached the drain: $drain"
|
|
1085
|
+
printf '%s\n' "$drain" | grep -F 'sample-route-call [key=sample-route-call]' >/dev/null \
|
|
1086
|
+
|| fail "the drain section omitted the collapsed-identity divergence: $drain"
|
|
1087
|
+
printf '%s\n' "$drain" | grep -F "$id-decision-access [key=access]" >/dev/null \
|
|
1088
|
+
|| fail "the drain section omitted the legacy-identity divergence: $drain"
|
|
1089
|
+
|
|
1090
|
+
# It signals; it never closes. Both records must survive the report unchanged,
|
|
1091
|
+
# because closing a captain call wrongly removes it from review entirely.
|
|
1092
|
+
assert_grep "sample-route-call" "$home/data/backlog.md" "the report must not remove the captain-held task"
|
|
1093
|
+
tasks_in "$home" show sample-route-call --full | grep -E '^ held: yes' >/dev/null \
|
|
1094
|
+
|| fail "the report released or closed the captain-held task"
|
|
1095
|
+
[ "$(grep -c '^resolved \[key=sample-route-call\]' "$home/state/$id.status")" = 1 ] \
|
|
1096
|
+
|| fail "the report rewrote the status log"
|
|
1097
|
+
|
|
1098
|
+
# And it names BOTH reconciliation directions. A status resolution is not proof
|
|
1099
|
+
# the captain ruled: one of the real cases dissolved because its premise was
|
|
1100
|
+
# false and another was a question of fact whose first reading was wrong, so
|
|
1101
|
+
# the only safe instruction is "reconcile with what actually happened".
|
|
1102
|
+
printf '%s\n' "$drain" | grep -F 'fm-captain-hold.sh answer' >/dev/null \
|
|
1103
|
+
|| fail "the drain section does not say how to record the captain's answer: $drain"
|
|
1104
|
+
printf '%s\n' "$drain" | grep -F 're-open the status decision' >/dev/null \
|
|
1105
|
+
|| fail "the drain section does not offer the re-open direction: $drain"
|
|
1106
|
+
pass "a status resolution over a still-open captain-held task is signalled, not closed"
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
# The false-signal boundary, driven by the shapes that are genuinely fine. A
|
|
1110
|
+
# captain call whose deliverable IS the decision has no routed work item at all,
|
|
1111
|
+
# and that is legitimate: routed work must never be part of the test. Nor may a
|
|
1112
|
+
# verified `captain-held` transfer, a still-open status decision, an already
|
|
1113
|
+
# answered call, or an ordinary task that merely had a keyed question answered.
|
|
1114
|
+
test_legitimate_holds_produce_no_divergence_signal() {
|
|
1115
|
+
local home id out drain answer
|
|
1116
|
+
home=$(make_home divergence-no-false-signal)
|
|
1117
|
+
id=sample-systems-review
|
|
1118
|
+
tasks_in "$home" add "$id" "Investigate sample systems" --kind scout --repo sample --start >/dev/null \
|
|
1119
|
+
|| fail "could not create the investigation fixture"
|
|
1120
|
+
write_origin_meta "$home" "$id"
|
|
1121
|
+
|
|
1122
|
+
# (1) The decision IS the deliverable: held for the captain, nothing routed,
|
|
1123
|
+
# no status line anywhere naming it.
|
|
1124
|
+
run_captain "$home" hold sample-standalone-call \
|
|
1125
|
+
--title "Adopt the sample naming convention" --reason "captain call with no routed work" \
|
|
1126
|
+
--repo sample >/dev/null || fail "could not register the deliverable-is-the-decision call"
|
|
1127
|
+
# (2) The verified transfer: still open structurally, closed on the status side
|
|
1128
|
+
# by the captain-held verb command_complete writes.
|
|
1129
|
+
run_captain "$home" hold sample-transfer-call \
|
|
1130
|
+
--title "Choose the sample retention window" --reason "captain retention choice pending" \
|
|
1131
|
+
--repo sample >/dev/null || fail "could not register the transferred call"
|
|
1132
|
+
# (4) An already answered call whose status line reads resolved.
|
|
1133
|
+
run_captain "$home" hold sample-answered-call \
|
|
1134
|
+
--title "Choose the sample export format" --reason "captain export choice pending" \
|
|
1135
|
+
--repo sample >/dev/null || fail "could not register the answered call"
|
|
1136
|
+
answer="$home/answer.txt"
|
|
1137
|
+
printf 'Export as CSV.\n' > "$answer"
|
|
1138
|
+
run_captain "$home" answer sample-answered-call --decision-file "$answer" >/dev/null \
|
|
1139
|
+
|| fail "could not record the captain answer fixture"
|
|
1140
|
+
# (5) An ordinary in-flight work item that is not held for the captain.
|
|
1141
|
+
tasks_in "$home" add sample-plain-work "Ordinary sample work" --kind ship --repo sample --start >/dev/null \
|
|
1142
|
+
|| fail "could not create the ordinary work fixture"
|
|
1143
|
+
|
|
1144
|
+
cat > "$home/state/$id.status" <<'EOF'
|
|
1145
|
+
working: report drafted
|
|
1146
|
+
needs-decision [key=sample-transfer-call]: choose the retention window
|
|
1147
|
+
captain-held [key=sample-transfer-call]: tracked by sample-transfer-call
|
|
1148
|
+
needs-decision [key=sample-open-call]: still open on both sides
|
|
1149
|
+
needs-decision [key=sample-answered-call]: choose the export format
|
|
1150
|
+
resolved [key=sample-answered-call]: answered: CSV
|
|
1151
|
+
needs-decision [key=sample-plain-work]: worker question about the sample fixture
|
|
1152
|
+
resolved [key=sample-plain-work]: answered: go ahead
|
|
1153
|
+
EOF
|
|
1154
|
+
# (3) A still-open status decision whose structured twin is also still open.
|
|
1155
|
+
run_captain "$home" hold sample-open-call \
|
|
1156
|
+
--title "Choose the sample refresh cadence" --reason "captain cadence choice pending" \
|
|
1157
|
+
--repo sample >/dev/null || fail "could not register the still-open call"
|
|
1158
|
+
|
|
1159
|
+
out=$(run_captain "$home" diverged) || fail "diverged failed on the legitimate shapes"
|
|
1160
|
+
[ -z "$out" ] || fail "legitimate captain holds produced a false divergence signal: $out"
|
|
1161
|
+
|
|
1162
|
+
drain=$(run_drain "$home") || fail "the drain failed on the legitimate shapes"
|
|
1163
|
+
if printf '%s\n' "$drain" | grep -F 'RECORD DIVERGENCE' >/dev/null; then
|
|
1164
|
+
fail "the drain printed a divergence section with nothing diverging: $drain"
|
|
1165
|
+
fi
|
|
1166
|
+
printf '%s\n' "$drain" | grep -F 'sample-open-call' >/dev/null \
|
|
1167
|
+
|| fail "setup error: the still-open decision should still reach OPEN DECISIONS: $drain"
|
|
1168
|
+
pass "a captain call with no routed work, a verified transfer, an open decision, and an answered call all stay silent"
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
test_uninventoried_report_decision_refuses_completion
|
|
1172
|
+
test_completion_gate_attests_and_transfers
|
|
1173
|
+
test_answer_records_and_closes
|
|
1174
|
+
test_release_frees_held_work
|
|
1175
|
+
test_deferral_leaves_captains_call_until_due
|
|
1176
|
+
test_out_of_band_close_is_recordable
|
|
1177
|
+
test_visual_review_uses_shared_completion_owner
|
|
1178
|
+
test_none_inventory_and_resolved_prose_do_not_create_holds
|
|
1179
|
+
test_terminal_single_owner_status_decision_does_not_block_empty_inventory
|
|
1180
|
+
test_secondmate_hold_stays_in_authoritative_home
|
|
1181
|
+
test_bound_channel_answers_close_at_answer_time
|
|
1182
|
+
test_unbound_source_closes_no_hold
|
|
1183
|
+
test_legacy_identities_keep_working
|
|
1184
|
+
test_chat_channel_feeds_the_same_keyed_answer_intake
|
|
1185
|
+
test_origin_slug_validation_precedes_path_construction
|
|
1186
|
+
test_status_resolution_over_an_open_hold_is_signalled
|
|
1187
|
+
test_legitimate_holds_produce_no_divergence_signal
|