@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,1196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Contract tests for bin/fm-test-run.sh - the single owner of behavior suite
|
|
3
|
+
# selection, portable lane composition, bounded concurrency, budgets, timing
|
|
4
|
+
# markers, JSON artifacts, coverage guard, and aggregate exit status.
|
|
5
|
+
#
|
|
6
|
+
# These tests intentionally exercise the runner with fixtures, --list, and
|
|
7
|
+
# focused scheduler checks, not the complete Firstmate suite.
|
|
8
|
+
set -u
|
|
9
|
+
|
|
10
|
+
# shellcheck source=tests/lib.sh
|
|
11
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
12
|
+
|
|
13
|
+
RUNNER="$ROOT/bin/fm-test-run.sh"
|
|
14
|
+
|
|
15
|
+
assert_present "$RUNNER" "bin/fm-test-run.sh is missing"
|
|
16
|
+
[ -x "$RUNNER" ] || fail "bin/fm-test-run.sh must be executable"
|
|
17
|
+
|
|
18
|
+
test_list_all_exact_suite_coverage() {
|
|
19
|
+
local listed expected missing extra f
|
|
20
|
+
listed=$("$RUNNER" --list --all | LC_ALL=C sort)
|
|
21
|
+
expected=$(
|
|
22
|
+
for f in "$ROOT"/tests/*.test.sh; do
|
|
23
|
+
[ -f "$f" ] || continue
|
|
24
|
+
printf 'tests/%s\n' "$(basename "$f")"
|
|
25
|
+
done | LC_ALL=C sort
|
|
26
|
+
)
|
|
27
|
+
[ -n "$listed" ] || fail "--list --all printed nothing"
|
|
28
|
+
missing=$(comm -23 <(printf '%s\n' "$expected") <(printf '%s\n' "$listed") || true)
|
|
29
|
+
extra=$(comm -13 <(printf '%s\n' "$expected") <(printf '%s\n' "$listed") || true)
|
|
30
|
+
[ -z "$missing" ] || fail "--list --all missing scripts: $missing"
|
|
31
|
+
[ -z "$extra" ] || fail "--list --all unexpected scripts: $extra"
|
|
32
|
+
# No duplicates.
|
|
33
|
+
[ "$(printf '%s\n' "$listed" | uniq | wc -l | tr -d ' ')" = \
|
|
34
|
+
"$(printf '%s\n' "$listed" | wc -l | tr -d ' ')" ] \
|
|
35
|
+
|| fail "--list --all must not duplicate scripts"
|
|
36
|
+
pass "exact suite coverage: --all lists every tests/*.test.sh once"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test_family_selection() {
|
|
40
|
+
local listed line
|
|
41
|
+
listed=$("$RUNNER" --list --family pure-contract-unit)
|
|
42
|
+
[ -n "$listed" ] || fail "--family pure-contract-unit selected nothing"
|
|
43
|
+
printf '%s\n' "$listed" | grep -Fq 'tests/fm-test-run.test.sh' \
|
|
44
|
+
|| fail "pure-contract-unit must include fm-test-run.test.sh"
|
|
45
|
+
while IFS= read -r line; do
|
|
46
|
+
[ -n "$line" ] || continue
|
|
47
|
+
case "$line" in
|
|
48
|
+
tests/*.test.sh) ;;
|
|
49
|
+
*) fail "family selection produced non-test path: $line" ;;
|
|
50
|
+
esac
|
|
51
|
+
done <<<"$listed"
|
|
52
|
+
# Family mode must not equal the complete suite for a narrow family.
|
|
53
|
+
local all_count fam_count
|
|
54
|
+
all_count=$("$RUNNER" --list --all | wc -l | tr -d ' ')
|
|
55
|
+
fam_count=$(printf '%s\n' "$listed" | wc -l | tr -d ' ')
|
|
56
|
+
[ "$fam_count" -lt "$all_count" ] \
|
|
57
|
+
|| fail "pure-contract-unit must be a proper subset of --all"
|
|
58
|
+
pass "family selection returns a proper subset of the suite"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test_single_script_selection() {
|
|
62
|
+
local listed
|
|
63
|
+
listed=$("$RUNNER" --list tests/fm-lint.test.sh)
|
|
64
|
+
[ "$listed" = "tests/fm-lint.test.sh" ] \
|
|
65
|
+
|| fail "single-script list expected tests/fm-lint.test.sh, got: $listed"
|
|
66
|
+
pass "single-script selection lists exactly that path"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
test_changed_file_selection_is_conservative() {
|
|
70
|
+
local listed all_count fam_count listed_count
|
|
71
|
+
# A path-mapped pure unit should not expand to --all.
|
|
72
|
+
listed=$("$RUNNER" --list --family pure-contract-unit)
|
|
73
|
+
all_count=$("$RUNNER" --list --all | wc -l | tr -d ' ')
|
|
74
|
+
fam_count=$(printf '%s\n' "$listed" | wc -l | tr -d ' ')
|
|
75
|
+
[ "$fam_count" -lt "$all_count" ] || fail "changed-informed pure family still full suite"
|
|
76
|
+
# Directly exercise --changed: empty or partial selection is ok; must not
|
|
77
|
+
# exceed the suite and must never silently become --all by accident.
|
|
78
|
+
listed=$("$RUNNER" --list --changed --base HEAD 2>/dev/null || true)
|
|
79
|
+
if [ -n "$listed" ]; then
|
|
80
|
+
listed_count=$(printf '%s\n' "$listed" | wc -l | tr -d ' ')
|
|
81
|
+
[ "$listed_count" -le "$all_count" ] || fail "changed selection larger than suite"
|
|
82
|
+
fi
|
|
83
|
+
# A single test path selects only that script (same contract as a
|
|
84
|
+
# tests/*.test.sh change entry in the map).
|
|
85
|
+
listed=$("$RUNNER" --list tests/fm-brief.test.sh)
|
|
86
|
+
[ "$listed" = "tests/fm-brief.test.sh" ] \
|
|
87
|
+
|| fail "test-file-only change contract should select one script"
|
|
88
|
+
pass "changed-file selection stays conservative (never silent full suite)"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
init_changed_fixture_repo() {
|
|
92
|
+
local repo=$1 script
|
|
93
|
+
mkdir -p "$repo/bin" "$repo/tests"
|
|
94
|
+
cp "$RUNNER" "$repo/bin/fm-test-run.sh"
|
|
95
|
+
chmod +x "$repo/bin/fm-test-run.sh"
|
|
96
|
+
for script in \
|
|
97
|
+
fm-brief.test.sh \
|
|
98
|
+
fm-ask-user-authority.test.sh \
|
|
99
|
+
fm-documentation-audiences.test.sh \
|
|
100
|
+
fm-test-isolation-proof.test.sh \
|
|
101
|
+
fm-test-run.test.sh \
|
|
102
|
+
fm-cd-pretool-check.test.sh \
|
|
103
|
+
fm-daemon.test.sh \
|
|
104
|
+
fm-harness-adapter-instructions-live-e2e.test.sh \
|
|
105
|
+
fm-harness-adapter-references.test.sh \
|
|
106
|
+
fm-backend-herdr-smoke.test.sh \
|
|
107
|
+
fm-secondmate-safety.test.sh \
|
|
108
|
+
fm-session-start.test.sh \
|
|
109
|
+
fm-afk-pi-herdr-return-e2e.test.sh \
|
|
110
|
+
fm-backend.test.sh \
|
|
111
|
+
fm-pr-merge.test.sh \
|
|
112
|
+
fm-procevent-quota.test.sh \
|
|
113
|
+
fm-quota-choose.test.sh \
|
|
114
|
+
fm-pi-watch-extension.test.sh \
|
|
115
|
+
fm-afk-return.test.sh \
|
|
116
|
+
fm-bearings-snapshot.test.sh \
|
|
117
|
+
fm-backend-cmux.test.sh \
|
|
118
|
+
fm-backend-zellij.test.sh \
|
|
119
|
+
fm-control-herdr-smoke.test.sh \
|
|
120
|
+
fm-backend-orca.test.sh; do
|
|
121
|
+
printf '#!/usr/bin/env bash\n# tests/lib.sh\n' >"$repo/tests/$script"
|
|
122
|
+
chmod +x "$repo/tests/$script"
|
|
123
|
+
done
|
|
124
|
+
: >"$repo/tests/lib.sh"
|
|
125
|
+
: >"$repo/tests/fm-backend-herdr-eventwait.test.py"
|
|
126
|
+
: >"$repo/bin/fm-supervisor-target-lib.sh"
|
|
127
|
+
: >"$repo/bin/fm-control-lib.sh"
|
|
128
|
+
: >"$repo/bin/fm-timeout-lib.sh"
|
|
129
|
+
: >"$repo/bin/fm-procevent-quota.sh"
|
|
130
|
+
: >"$repo/bin/fm-quota-axi-lib.sh"
|
|
131
|
+
: >"$repo/bin/fm-quota-choose.sh"
|
|
132
|
+
: >"$repo/bin/unmapped-source.sh"
|
|
133
|
+
# A shared helper with no curated family of its own, named by exactly ONE
|
|
134
|
+
# script of the expensive real-Herdr family and consumed by one curated
|
|
135
|
+
# watcher script. This is the shape that made a one-line helper change select
|
|
136
|
+
# every real-Herdr E2E.
|
|
137
|
+
: >"$repo/bin/shared-probe-lib.sh"
|
|
138
|
+
printf '# shared-probe-lib.sh\n' >>"$repo/tests/fm-backend-herdr-smoke.test.sh"
|
|
139
|
+
# shellcheck disable=SC2016 # literal fixture text: the reference must reach
|
|
140
|
+
# the file verbatim so the changed-file scan can find it, not expand here.
|
|
141
|
+
printf '. "$ROOT/bin/shared-probe-lib.sh"\n' >"$repo/bin/fm-watch-probe.sh"
|
|
142
|
+
printf '# .claude/settings.json\n# .pi/extensions/fm-primary-turnend-guard.ts\n' \
|
|
143
|
+
>>"$repo/tests/fm-cd-pretool-check.test.sh"
|
|
144
|
+
printf '# .pi/extensions/fm-primary-pi-watch.ts\n' >>"$repo/tests/fm-pi-watch-extension.test.sh"
|
|
145
|
+
mkdir -p \
|
|
146
|
+
"$repo/.agents/skills/example" \
|
|
147
|
+
"$repo/.agents/skills/harness-adapters/references/common" \
|
|
148
|
+
"$repo/.claude" "$repo/.pi/extensions" "$repo/docs" "$repo/src"
|
|
149
|
+
: >"$repo/.agents/skills/example/SKILL.md"
|
|
150
|
+
: >"$repo/.agents/skills/harness-adapters/SKILL.md"
|
|
151
|
+
: >"$repo/.agents/skills/harness-adapters/references/common/dispatch.md"
|
|
152
|
+
: >"$repo/.claude/settings.json"
|
|
153
|
+
: >"$repo/.pi/extensions/fm-primary-pi-watch.ts"
|
|
154
|
+
: >"$repo/.pi/extensions/fm-primary-turnend-guard.ts"
|
|
155
|
+
: >"$repo/docs/fm-test-isolation-proof.md"
|
|
156
|
+
: >"$repo/CONTRIBUTING.md"
|
|
157
|
+
: >"$repo/src/unmapped.ts"
|
|
158
|
+
git -C "$repo" init -q
|
|
159
|
+
git -C "$repo" add .
|
|
160
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm baseline
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
test_changed_runner_surfaces_select_their_family() {
|
|
164
|
+
local tmp repo listed
|
|
165
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-owner-scope.XXXXXX")
|
|
166
|
+
repo="$tmp/repo"
|
|
167
|
+
init_changed_fixture_repo "$repo"
|
|
168
|
+
|
|
169
|
+
# A change to the runner selects the WHOLE pure-contract-unit family, not
|
|
170
|
+
# just its own contract test. The runner executes every script in that
|
|
171
|
+
# family, so its own test passing proves its logic is right, not that the
|
|
172
|
+
# suite it drives still runs. Narrowing this to the contract owners would
|
|
173
|
+
# also make any wall-clock claim about the changed suite trivially true by
|
|
174
|
+
# not running the work.
|
|
175
|
+
printf '\n' >>"$repo/bin/fm-test-run.sh"
|
|
176
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD | LC_ALL=C sort)
|
|
177
|
+
case "$listed" in
|
|
178
|
+
*tests/fm-test-run.test.sh*) ;;
|
|
179
|
+
*) fail "runner change did not select its own contract test: $listed" ;;
|
|
180
|
+
esac
|
|
181
|
+
case "$listed" in
|
|
182
|
+
*tests/fm-brief.test.sh*) ;;
|
|
183
|
+
*) fail "runner change did not select its pure-contract-unit family: $listed" ;;
|
|
184
|
+
esac
|
|
185
|
+
case "$listed" in
|
|
186
|
+
*tests/fm-ask-user-authority.test.sh*) ;;
|
|
187
|
+
*) fail "runner change did not select its pure-contract-unit family: $listed" ;;
|
|
188
|
+
esac
|
|
189
|
+
git -C "$repo" add bin/fm-test-run.sh
|
|
190
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm runner-change
|
|
191
|
+
|
|
192
|
+
# The same holds for the surfaces that document that contract.
|
|
193
|
+
printf '\n' >>"$repo/docs/fm-test-isolation-proof.md"
|
|
194
|
+
printf '\n' >>"$repo/CONTRIBUTING.md"
|
|
195
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD | LC_ALL=C sort)
|
|
196
|
+
case "$listed" in
|
|
197
|
+
*tests/fm-documentation-audiences.test.sh*) ;;
|
|
198
|
+
*) fail "documentation surface change did not select audience coverage: $listed" ;;
|
|
199
|
+
esac
|
|
200
|
+
case "$listed" in
|
|
201
|
+
*tests/fm-brief.test.sh*) ;;
|
|
202
|
+
*) fail "documentation surface change did not select its curated family: $listed" ;;
|
|
203
|
+
esac
|
|
204
|
+
|
|
205
|
+
rm -rf "$tmp"
|
|
206
|
+
pass "runner and its documentation surfaces select their curated family, not just their contract owners"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
test_changed_dependency_selection_and_unmapped_failure() {
|
|
210
|
+
local tmp repo listed rc
|
|
211
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-changed.XXXXXX")
|
|
212
|
+
repo="$tmp/repo"
|
|
213
|
+
init_changed_fixture_repo "$repo"
|
|
214
|
+
|
|
215
|
+
printf '\n' >>"$repo/tests/lib.sh"
|
|
216
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
217
|
+
assert_contains "$listed" "tests/fm-pr-merge.test.sh" "shared helper selects pr-forge dependents"
|
|
218
|
+
assert_contains "$listed" "tests/fm-secondmate-safety.test.sh" "shared helper selects secondmate dependents"
|
|
219
|
+
assert_contains "$listed" "tests/fm-bearings-snapshot.test.sh" "shared helper selects snapshot dependents"
|
|
220
|
+
git -C "$repo" add tests/lib.sh
|
|
221
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm helper-change
|
|
222
|
+
|
|
223
|
+
printf '\n' >>"$repo/tests/fm-backend-herdr-eventwait.test.py"
|
|
224
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
225
|
+
assert_contains "$listed" "tests/fm-backend-herdr-smoke.test.sh" "eventwait test selects Herdr coverage"
|
|
226
|
+
assert_contains "$listed" "tests/fm-backend.test.sh" "eventwait test selects backend coverage"
|
|
227
|
+
git -C "$repo" add tests/fm-backend-herdr-eventwait.test.py
|
|
228
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm eventwait-change
|
|
229
|
+
|
|
230
|
+
printf '\n' >>"$repo/bin/fm-supervisor-target-lib.sh"
|
|
231
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
232
|
+
assert_contains "$listed" "tests/fm-daemon.test.sh" "supervisor target selects daemon coverage"
|
|
233
|
+
assert_contains "$listed" "tests/fm-afk-return.test.sh" "supervisor target selects afk coverage"
|
|
234
|
+
git -C "$repo" add bin/fm-supervisor-target-lib.sh
|
|
235
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm supervisor-change
|
|
236
|
+
|
|
237
|
+
printf '\n' >>"$repo/.agents/skills/example/SKILL.md"
|
|
238
|
+
printf '\n' >>"$repo/.claude/settings.json"
|
|
239
|
+
printf '\n' >>"$repo/.pi/extensions/fm-primary-pi-watch.ts"
|
|
240
|
+
printf '\n' >>"$repo/.pi/extensions/fm-primary-turnend-guard.ts"
|
|
241
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
242
|
+
assert_contains "$listed" "tests/fm-ask-user-authority.test.sh" "skill source selects pure contract coverage"
|
|
243
|
+
assert_contains "$listed" "tests/fm-cd-pretool-check.test.sh" "Claude and Pi source selects hook coverage"
|
|
244
|
+
assert_contains "$listed" "tests/fm-pi-watch-extension.test.sh" "Pi source selects watcher coverage"
|
|
245
|
+
git -C "$repo" add .agents .claude .pi
|
|
246
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm non-bin-source-change
|
|
247
|
+
|
|
248
|
+
printf '\n' >>"$repo/.agents/skills/harness-adapters/references/common/dispatch.md"
|
|
249
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
250
|
+
assert_contains "$listed" "tests/fm-harness-adapter-references.test.sh" "harness adapter reference selects portable structural coverage"
|
|
251
|
+
assert_contains "$listed" "tests/fm-harness-adapter-instructions-live-e2e.test.sh" "harness adapter reference selects opt-in instruction coverage"
|
|
252
|
+
git -C "$repo" add .agents/skills/harness-adapters
|
|
253
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm harness-adapter-reference-change
|
|
254
|
+
|
|
255
|
+
printf '\n' >>"$repo/.agents/skills/harness-adapters/SKILL.md"
|
|
256
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
257
|
+
assert_contains "$listed" "tests/fm-harness-adapter-references.test.sh" "harness adapter router selects portable structural coverage"
|
|
258
|
+
assert_contains "$listed" "tests/fm-harness-adapter-instructions-live-e2e.test.sh" "harness adapter router selects opt-in instruction coverage"
|
|
259
|
+
git -C "$repo" add .agents/skills/harness-adapters/SKILL.md
|
|
260
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm harness-adapter-router-change
|
|
261
|
+
|
|
262
|
+
printf '\n' >>"$repo/bin/fm-procevent-quota.sh"
|
|
263
|
+
printf '\n' >>"$repo/bin/fm-quota-choose.sh"
|
|
264
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
265
|
+
assert_contains "$listed" "tests/fm-procevent-quota.test.sh" \
|
|
266
|
+
"quota process-event source selects its focused test"
|
|
267
|
+
assert_contains "$listed" "tests/fm-quota-choose.test.sh" \
|
|
268
|
+
"quota chooser source selects its focused test"
|
|
269
|
+
git -C "$repo" add bin/fm-procevent-quota.sh bin/fm-quota-choose.sh
|
|
270
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm quota-source-change
|
|
271
|
+
|
|
272
|
+
printf '\n' >>"$repo/bin/fm-quota-axi-lib.sh"
|
|
273
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
274
|
+
assert_contains "$listed" "tests/fm-procevent-quota.test.sh" \
|
|
275
|
+
"shared quota validator selects process-event coverage"
|
|
276
|
+
assert_contains "$listed" "tests/fm-quota-choose.test.sh" \
|
|
277
|
+
"shared quota validator selects chooser coverage"
|
|
278
|
+
git -C "$repo" add bin/fm-quota-axi-lib.sh
|
|
279
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm quota-validator-change
|
|
280
|
+
|
|
281
|
+
printf '\n' >>"$repo/bin/fm-control-lib.sh"
|
|
282
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
283
|
+
assert_contains "$listed" "tests/fm-backend.test.sh" \
|
|
284
|
+
"control library keeps backend coverage"
|
|
285
|
+
assert_contains "$listed" "tests/fm-session-start.test.sh" \
|
|
286
|
+
"control library keeps session coverage"
|
|
287
|
+
assert_contains "$listed" "tests/fm-quota-choose.test.sh" \
|
|
288
|
+
"control library selects chooser coverage"
|
|
289
|
+
git -C "$repo" add bin/fm-control-lib.sh
|
|
290
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm control-lib-change
|
|
291
|
+
|
|
292
|
+
printf '\n' >>"$repo/bin/fm-timeout-lib.sh"
|
|
293
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
294
|
+
assert_contains "$listed" "tests/fm-procevent-quota.test.sh" \
|
|
295
|
+
"timeout library selects quota polling coverage"
|
|
296
|
+
git -C "$repo" add bin/fm-timeout-lib.sh
|
|
297
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm timeout-lib-change
|
|
298
|
+
|
|
299
|
+
printf '\n' >>"$repo/src/unmapped.ts"
|
|
300
|
+
set +e
|
|
301
|
+
(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD) >"$tmp/out" 2>"$tmp/err"
|
|
302
|
+
rc=$?
|
|
303
|
+
set -e
|
|
304
|
+
[ "$rc" -eq 2 ] || fail "unmapped changed source must fail with exit 2, got $rc"
|
|
305
|
+
grep -Fq 'no changed-test mapping for source path: src/unmapped.ts' "$tmp/err" \
|
|
306
|
+
|| fail "unmapped changed source failure is not actionable: $(cat "$tmp/err")"
|
|
307
|
+
rm -rf "$tmp"
|
|
308
|
+
pass "changed selection covers dependents and fails closed for unmapped source"
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
# A direct test reference is per-script evidence. Widening it to the referencing
|
|
312
|
+
# test's whole family is what turned a one-line change to a shared helper into
|
|
313
|
+
# every real-Herdr E2E, including scripts with no dependency on it at all.
|
|
314
|
+
# Consumer bin/ scripts must still resolve through the curated map, so recorded
|
|
315
|
+
# family-level coupling is not lost along the way.
|
|
316
|
+
test_changed_bin_reference_selects_per_script_not_per_family() {
|
|
317
|
+
local tmp repo listed
|
|
318
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-changed-scope.XXXXXX")
|
|
319
|
+
repo="$tmp/repo"
|
|
320
|
+
init_changed_fixture_repo "$repo"
|
|
321
|
+
|
|
322
|
+
printf '\n' >>"$repo/bin/shared-probe-lib.sh"
|
|
323
|
+
listed=$(cd "$repo" && bin/fm-test-run.sh --list --changed --base HEAD)
|
|
324
|
+
|
|
325
|
+
assert_contains "$listed" "tests/fm-backend-herdr-smoke.test.sh" \
|
|
326
|
+
"the one gated script that names the helper must still be selected"
|
|
327
|
+
case "$listed" in
|
|
328
|
+
*tests/fm-control-herdr-smoke.test.sh*)
|
|
329
|
+
fail "a single gated script's reference dragged in its whole family: $listed"
|
|
330
|
+
;;
|
|
331
|
+
esac
|
|
332
|
+
# The curated consumer keeps its family-level coupling.
|
|
333
|
+
assert_contains "$listed" "tests/fm-daemon.test.sh" \
|
|
334
|
+
"a curated consumer of the helper must still select its whole family"
|
|
335
|
+
assert_contains "$listed" "tests/fm-pi-watch-extension.test.sh" \
|
|
336
|
+
"a curated consumer of the helper must still select its whole family"
|
|
337
|
+
|
|
338
|
+
rm -rf "$tmp"
|
|
339
|
+
pass "a bin reference selects the referencing scripts, and consumers still select their curated families"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Exercise begin/end markers from real fixture processes to prove the automatic
|
|
343
|
+
# changed-suite default and its explicit serial override.
|
|
344
|
+
test_changed_uses_bounded_automatic_concurrency() {
|
|
345
|
+
local tmp repo script serial_shape parallel_shape timeout_repo timeout_script expected_jobs rc
|
|
346
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-changed-consent.XXXXXX")
|
|
347
|
+
repo="$tmp/repo"
|
|
348
|
+
init_changed_fixture_repo "$repo"
|
|
349
|
+
cp "$ROOT/bin/fm-timeout-lib.sh" "$repo/bin/fm-timeout-lib.sh"
|
|
350
|
+
for script in fm-backend-herdr-smoke.test.sh fm-daemon.test.sh fm-pi-watch-extension.test.sh; do
|
|
351
|
+
cat >"$repo/tests/$script" <<'SH'
|
|
352
|
+
#!/usr/bin/env bash
|
|
353
|
+
sleep 1
|
|
354
|
+
echo "ok - concurrency consent fixture"
|
|
355
|
+
SH
|
|
356
|
+
chmod +x "$repo/tests/$script"
|
|
357
|
+
done
|
|
358
|
+
git -C "$repo" add .
|
|
359
|
+
git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm fixtures
|
|
360
|
+
printf '\n' >>"$repo/bin/shared-probe-lib.sh"
|
|
361
|
+
|
|
362
|
+
(cd "$repo" && bin/fm-test-run.sh --changed --base HEAD --json "$tmp/parallel.json") \
|
|
363
|
+
>"$tmp/parallel.out" 2>"$tmp/parallel.err" \
|
|
364
|
+
|| fail "default changed fixture run failed: $(cat "$tmp/parallel.err")"
|
|
365
|
+
parallel_shape=$(grep -E '^FM_TEST_(BEGIN|END)' "$tmp/parallel.out" | head -n 2 | awk '{print $1}' | paste -sd, -)
|
|
366
|
+
[ "$parallel_shape" = FM_TEST_BEGIN,FM_TEST_BEGIN ] \
|
|
367
|
+
|| fail "plain --changed did not use bounded concurrent scheduling: $parallel_shape"
|
|
368
|
+
|
|
369
|
+
(cd "$repo" && bin/fm-test-run.sh --changed --base HEAD --jobs 1 --json "$tmp/serial.json") \
|
|
370
|
+
>"$tmp/serial.out" 2>"$tmp/serial.err" \
|
|
371
|
+
|| fail "explicit serial changed fixture run failed: $(cat "$tmp/serial.err")"
|
|
372
|
+
serial_shape=$(grep -E '^FM_TEST_(BEGIN|END)' "$tmp/serial.out" | head -n 2 | awk '{print $1}' | paste -sd, -)
|
|
373
|
+
[ "$serial_shape" = FM_TEST_BEGIN,FM_TEST_END ] \
|
|
374
|
+
|| fail "explicit --jobs 1 did not force serial execution: $serial_shape"
|
|
375
|
+
expected_jobs=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)
|
|
376
|
+
case "$expected_jobs" in
|
|
377
|
+
''|*[!0-9]*) expected_jobs=1 ;;
|
|
378
|
+
esac
|
|
379
|
+
[ "$expected_jobs" -le 4 ] || expected_jobs=4
|
|
380
|
+
[ "$expected_jobs" -ge 1 ] || expected_jobs=1
|
|
381
|
+
python3 - "$tmp/parallel.json" "$tmp/serial.json" "$expected_jobs" <<'PY' \
|
|
382
|
+
|| fail "changed timing artifacts did not record their resolved worker counts"
|
|
383
|
+
import json, sys
|
|
384
|
+
automatic = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
385
|
+
serial = json.load(open(sys.argv[2], encoding="utf-8"))
|
|
386
|
+
expected = int(sys.argv[3])
|
|
387
|
+
assert automatic["selection"].split(";")[-1] == f"jobs={expected}"
|
|
388
|
+
assert serial["selection"].split(";")[-1] == "jobs=1"
|
|
389
|
+
PY
|
|
390
|
+
|
|
391
|
+
timeout_repo="$tmp/timeout-repo"
|
|
392
|
+
timeout_script=tests/fm-calm-pi-extension.test.sh
|
|
393
|
+
mkdir -p "$timeout_repo/bin" "$timeout_repo/tests"
|
|
394
|
+
cp "$RUNNER" "$timeout_repo/bin/fm-test-run.sh"
|
|
395
|
+
cat >"$timeout_repo/bin/fm-timeout-lib.sh" <<'SH'
|
|
396
|
+
fm_run_timed() {
|
|
397
|
+
[ "$1" -eq 900 ] || return 99
|
|
398
|
+
return 124
|
|
399
|
+
}
|
|
400
|
+
SH
|
|
401
|
+
cat >"$timeout_repo/$timeout_script" <<'SH'
|
|
402
|
+
#!/usr/bin/env bash
|
|
403
|
+
touch should-not-run
|
|
404
|
+
echo "not ok - automatic timeout helper was bypassed"
|
|
405
|
+
SH
|
|
406
|
+
chmod +x "$timeout_repo/bin/fm-test-run.sh" "$timeout_repo/$timeout_script"
|
|
407
|
+
git -C "$timeout_repo" init -q
|
|
408
|
+
git -C "$timeout_repo" add .
|
|
409
|
+
git -C "$timeout_repo" -c user.name=test -c user.email=test@example.invalid commit -qm baseline
|
|
410
|
+
printf '\n' >>"$timeout_repo/$timeout_script"
|
|
411
|
+
set +e
|
|
412
|
+
(cd "$timeout_repo" && bin/fm-test-run.sh --changed --base HEAD) \
|
|
413
|
+
>"$tmp/timeout.out" 2>"$tmp/timeout.err"
|
|
414
|
+
rc=$?
|
|
415
|
+
set -e
|
|
416
|
+
[ "$rc" -eq 1 ] || fail "single-script automatic timeout must fail the run, got $rc"
|
|
417
|
+
grep -Eq '^FM_TEST_END .+ tests/fm-calm-pi-extension\.test\.sh exit=124 ' "$tmp/timeout.out" \
|
|
418
|
+
|| fail "single unproven changed script did not receive the automatic timeout: $(cat "$tmp/timeout.out")"
|
|
419
|
+
[ ! -e "$timeout_repo/should-not-run" ] || fail "automatic timeout helper did not own the single changed script"
|
|
420
|
+
|
|
421
|
+
rm -rf "$tmp"
|
|
422
|
+
pass "changed defaults to bounded automatic scheduling with serial override"
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
test_empty_selection_emits_summary() {
|
|
426
|
+
local tmp repo out json rc fake_bin real_git
|
|
427
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-empty.XXXXXX")
|
|
428
|
+
repo="$tmp/repo"
|
|
429
|
+
init_changed_fixture_repo "$repo"
|
|
430
|
+
printf 'documentation only\n' >"$repo/README.md"
|
|
431
|
+
out=$(cd "$repo" && bin/fm-test-run.sh --changed --base HEAD --json "$tmp/artifacts/timing.json" 2>"$tmp/err") \
|
|
432
|
+
|| fail "empty valid changed selection must pass"
|
|
433
|
+
printf '%s\n' "$out" | grep -Eq \
|
|
434
|
+
'^FM_TEST_SUMMARY total=0 failed=0 skipped_gate=0 duration_ms=[0-9]+$' \
|
|
435
|
+
|| fail "empty selection summary is missing or malformed: $out"
|
|
436
|
+
json="$tmp/artifacts/timing.json"
|
|
437
|
+
python3 -c '
|
|
438
|
+
import json, sys
|
|
439
|
+
doc = json.load(open(sys.argv[1]))
|
|
440
|
+
assert doc["summary"]["total"] == 0
|
|
441
|
+
assert doc["summary"]["failed"] == 0
|
|
442
|
+
assert doc["summary"]["skipped_gate"] == 0
|
|
443
|
+
assert doc["summary"]["duration_ms"] >= 0
|
|
444
|
+
assert doc["scripts"] == []
|
|
445
|
+
assert doc["families"] == []
|
|
446
|
+
' "$json" || { rm -rf "$tmp"; fail "empty selection JSON summary is wrong"; }
|
|
447
|
+
fake_bin="$tmp/fake-bin"
|
|
448
|
+
real_git=$(command -v git)
|
|
449
|
+
mkdir -p "$fake_bin"
|
|
450
|
+
cat >"$fake_bin/git" <<'SH'
|
|
451
|
+
#!/usr/bin/env bash
|
|
452
|
+
if [ ! -e "$SLOW_GIT_MARKER" ]; then
|
|
453
|
+
: >"$SLOW_GIT_MARKER"
|
|
454
|
+
sleep 1
|
|
455
|
+
fi
|
|
456
|
+
exec "$REAL_GIT" "$@"
|
|
457
|
+
SH
|
|
458
|
+
chmod +x "$fake_bin/git"
|
|
459
|
+
set +e
|
|
460
|
+
(cd "$repo" && PATH="$fake_bin:$PATH" REAL_GIT="$real_git" SLOW_GIT_MARKER="$tmp/slow-git" \
|
|
461
|
+
bin/fm-test-run.sh --changed --base HEAD --max-wall-ms 100) \
|
|
462
|
+
>"$tmp/slow-selection.out" 2>"$tmp/slow-selection.err"
|
|
463
|
+
rc=$?
|
|
464
|
+
set -e
|
|
465
|
+
[ "$rc" -eq 1 ] || fail "an empty run past its budget must fail normally, got $rc"
|
|
466
|
+
grep -Eq '^FM_TEST_SUMMARY total=0 failed=0 skipped_gate=0 duration_ms=[0-9]+$' "$tmp/slow-selection.out" \
|
|
467
|
+
|| fail "over-budget empty selection omitted its summary"
|
|
468
|
+
grep -Eq '^FM_TEST_BUDGET max_wall_ms=100 duration_ms=[0-9]+$' "$tmp/slow-selection.out" \
|
|
469
|
+
|| fail "over-budget empty selection omitted its budget result"
|
|
470
|
+
[ -e "$tmp/slow-git" ] || fail "the slow selection fixture did not run"
|
|
471
|
+
set +e
|
|
472
|
+
(cd "$repo" && bin/fm-test-run.sh --changed --base HEAD --max-wall-ms nope) \
|
|
473
|
+
>"$tmp/bad-budget.out" 2>"$tmp/bad-budget.err"
|
|
474
|
+
rc=$?
|
|
475
|
+
set -e
|
|
476
|
+
[ "$rc" -eq 2 ] || fail "malformed budget on an empty selection must be refused, got $rc"
|
|
477
|
+
set +e
|
|
478
|
+
(cd "$repo" && bin/fm-test-run.sh --changed --base HEAD --per-script-timeout-secs nope) \
|
|
479
|
+
>"$tmp/bad-timeout.out" 2>"$tmp/bad-timeout.err"
|
|
480
|
+
rc=$?
|
|
481
|
+
set -e
|
|
482
|
+
[ "$rc" -eq 2 ] || fail "malformed timeout on an empty selection must be refused, got $rc"
|
|
483
|
+
rm -rf "$tmp"
|
|
484
|
+
pass "empty changed selection emits deterministic text and JSON summaries"
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
test_timing_markers_and_json() {
|
|
488
|
+
local tmp fixture out json begin_n end_n summary
|
|
489
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-timing.XXXXXX")
|
|
490
|
+
fixture="$tmp/ok.test.sh"
|
|
491
|
+
out="$tmp/out.txt"
|
|
492
|
+
json="$tmp/timing.json"
|
|
493
|
+
cat >"$fixture" <<'SH'
|
|
494
|
+
#!/usr/bin/env bash
|
|
495
|
+
echo "ok - fixture"
|
|
496
|
+
exit 0
|
|
497
|
+
SH
|
|
498
|
+
chmod +x "$fixture"
|
|
499
|
+
"$RUNNER" --json "$json" "$fixture" >"$out" 2>"$tmp/err.txt" \
|
|
500
|
+
|| { rm -rf "$tmp"; fail "runner should pass on a green fixture"; }
|
|
501
|
+
begin_n=$(grep -c '^FM_TEST_BEGIN ' "$out" || true)
|
|
502
|
+
end_n=$(grep -c '^FM_TEST_END ' "$out" || true)
|
|
503
|
+
[ "$begin_n" -eq 1 ] || fail "expected one FM_TEST_BEGIN, got $begin_n"
|
|
504
|
+
[ "$end_n" -eq 1 ] || fail "expected one FM_TEST_END, got $end_n"
|
|
505
|
+
grep -Eq '^FM_TEST_BEGIN .+ family=unclassified expected_gate_skip=none$' "$out" \
|
|
506
|
+
|| fail "BEGIN line missing family/expected_gate_skip: $(grep '^FM_TEST_BEGIN' "$out")"
|
|
507
|
+
grep -Eq '^FM_TEST_END .+ exit=0 duration_ms=[0-9]+ gate_skip=false$' "$out" \
|
|
508
|
+
|| fail "END line missing exit/duration/gate_skip: $(grep '^FM_TEST_END' "$out")"
|
|
509
|
+
summary=$(grep '^FM_TEST_SUMMARY ' "$out" || true)
|
|
510
|
+
assert_contains "$summary" "total=1" "summary total"
|
|
511
|
+
assert_contains "$summary" "failed=0" "summary failed"
|
|
512
|
+
assert_contains "$summary" "skipped_gate=0" "summary skipped_gate"
|
|
513
|
+
grep -q '^FM_TEST_SLOWEST rank=1 ' "$out" \
|
|
514
|
+
|| fail "expected FM_TEST_SLOWEST rank=1"
|
|
515
|
+
[ -f "$json" ] || fail "JSON timing artifact was not written"
|
|
516
|
+
python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$json" \
|
|
517
|
+
|| fail "JSON timing artifact is not valid JSON"
|
|
518
|
+
python3 -c '
|
|
519
|
+
import json, sys
|
|
520
|
+
doc = json.load(open(sys.argv[1]))
|
|
521
|
+
assert "scripts" in doc and len(doc["scripts"]) == 1, doc
|
|
522
|
+
assert doc["scripts"][0]["exit"] == 0
|
|
523
|
+
assert doc["scripts"][0]["gate_skip"] is False
|
|
524
|
+
assert doc["summary"]["total"] == 1
|
|
525
|
+
assert doc["summary"]["failed"] == 0
|
|
526
|
+
assert "duration_ms" in doc["scripts"][0]
|
|
527
|
+
assert "family" in doc["scripts"][0]
|
|
528
|
+
' "$json" || { rm -rf "$tmp"; fail "JSON timing artifact missing required fields"; }
|
|
529
|
+
rm -rf "$tmp"
|
|
530
|
+
pass "timing markers and JSON artifact are valid"
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
test_aggregate_exit_behavior() {
|
|
534
|
+
local tmp pass_f fail_f rc
|
|
535
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-agg.XXXXXX")
|
|
536
|
+
pass_f="$tmp/pass.test.sh"
|
|
537
|
+
fail_f="$tmp/fail.test.sh"
|
|
538
|
+
cat >"$pass_f" <<'SH'
|
|
539
|
+
#!/usr/bin/env bash
|
|
540
|
+
echo "ok - pass"
|
|
541
|
+
exit 0
|
|
542
|
+
SH
|
|
543
|
+
cat >"$fail_f" <<'SH'
|
|
544
|
+
#!/usr/bin/env bash
|
|
545
|
+
echo "not ok - fail"
|
|
546
|
+
exit 1
|
|
547
|
+
SH
|
|
548
|
+
chmod +x "$pass_f" "$fail_f"
|
|
549
|
+
set +e
|
|
550
|
+
"$RUNNER" "$pass_f" "$fail_f" >"$tmp/out.txt" 2>"$tmp/err.txt"
|
|
551
|
+
rc=$?
|
|
552
|
+
set -e
|
|
553
|
+
[ "$rc" -ne 0 ] || fail "aggregate exit must be non-zero when any script fails"
|
|
554
|
+
grep -q 'FM_TEST_SUMMARY total=2 failed=1' "$tmp/out.txt" \
|
|
555
|
+
|| fail "summary should report total=2 failed=1: $(grep FM_TEST_SUMMARY "$tmp/out.txt")"
|
|
556
|
+
# All-green stays 0.
|
|
557
|
+
set +e
|
|
558
|
+
"$RUNNER" "$pass_f" >"$tmp/out2.txt" 2>"$tmp/err2.txt"
|
|
559
|
+
rc=$?
|
|
560
|
+
set -e
|
|
561
|
+
[ "$rc" -eq 0 ] || { rm -rf "$tmp"; fail "aggregate exit must be 0 when every script passes"; }
|
|
562
|
+
rm -rf "$tmp"
|
|
563
|
+
pass "aggregate exit reflects any script failure"
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
test_gate_skip_accounting() {
|
|
567
|
+
local tmp skip_f out json
|
|
568
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-skip.XXXXXX")
|
|
569
|
+
skip_f="$tmp/skip.test.sh"
|
|
570
|
+
out="$tmp/out.txt"
|
|
571
|
+
json="$tmp/timing.json"
|
|
572
|
+
cat >"$skip_f" <<'SH'
|
|
573
|
+
#!/usr/bin/env bash
|
|
574
|
+
echo "skip: herdr not found"
|
|
575
|
+
exit 0
|
|
576
|
+
SH
|
|
577
|
+
chmod +x "$skip_f"
|
|
578
|
+
"$RUNNER" --json "$json" "$skip_f" >"$out" 2>"$tmp/err.txt" \
|
|
579
|
+
|| fail "gate-skip fixture must exit 0 from the runner"
|
|
580
|
+
grep -Eq '^FM_TEST_END .+ exit=0 duration_ms=[0-9]+ gate_skip=true$' "$out" \
|
|
581
|
+
|| fail "END must mark gate_skip=true: $(grep '^FM_TEST_END' "$out")"
|
|
582
|
+
grep -q 'FM_TEST_SUMMARY total=1 failed=0 skipped_gate=1' "$out" \
|
|
583
|
+
|| fail "summary must count skipped_gate=1: $(grep FM_TEST_SUMMARY "$out")"
|
|
584
|
+
python3 -c '
|
|
585
|
+
import json, sys
|
|
586
|
+
doc = json.load(open(sys.argv[1]))
|
|
587
|
+
assert doc["scripts"][0]["gate_skip"] is True
|
|
588
|
+
assert doc["summary"]["skipped_gate"] == 1
|
|
589
|
+
assert doc["summary"]["failed"] == 0
|
|
590
|
+
' "$json" || { rm -rf "$tmp"; fail "JSON gate_skip accounting is wrong"; }
|
|
591
|
+
rm -rf "$tmp"
|
|
592
|
+
pass "gate-skip accounting is honest and non-failing"
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
test_fail_on_gate_skip_token() {
|
|
596
|
+
local tmp skip_f out rc
|
|
597
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-fail-skip.XXXXXX")
|
|
598
|
+
skip_f="$tmp/skip.test.sh"
|
|
599
|
+
out="$tmp/out.txt"
|
|
600
|
+
cat >"$skip_f" <<'SH'
|
|
601
|
+
#!/usr/bin/env bash
|
|
602
|
+
echo "skip: herdr not found"
|
|
603
|
+
exit 0
|
|
604
|
+
SH
|
|
605
|
+
chmod +x "$skip_f"
|
|
606
|
+
set +e
|
|
607
|
+
"$RUNNER" --fail-on-gate-skip 'herdr not found' "$skip_f" >"$out" 2>"$tmp/err.txt"
|
|
608
|
+
rc=$?
|
|
609
|
+
set -e
|
|
610
|
+
[ "$rc" -ne 0 ] || fail "fail-on-gate-skip must make herdr-not-found a hard failure"
|
|
611
|
+
grep -q 'FM_TEST_SUMMARY total=1 failed=1' "$out" \
|
|
612
|
+
|| fail "summary must report failed=1 under fail-on-gate-skip: $(grep FM_TEST_SUMMARY "$out")"
|
|
613
|
+
grep -q 'required gate skip token' "$tmp/err.txt" \
|
|
614
|
+
|| fail "runner must log the required gate skip token"
|
|
615
|
+
rm -rf "$tmp"
|
|
616
|
+
pass "fail-on-gate-skip converts herdr-not-found into a hard failure"
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
test_exclude_family() {
|
|
620
|
+
local listed
|
|
621
|
+
listed=$("$RUNNER" --list --all --exclude-family real-herdr-gated)
|
|
622
|
+
printf '%s\n' "$listed" | grep -Fq 'tests/fm-backend-herdr-smoke.test.sh' \
|
|
623
|
+
&& fail "exclude-family real-herdr-gated left a real-herdr script"
|
|
624
|
+
printf '%s\n' "$listed" | grep -Fq 'tests/fm-lint.test.sh' \
|
|
625
|
+
|| fail "exclude-family must retain pure-contract-unit scripts"
|
|
626
|
+
# Explicit family mode still works; exclude of a different family is a no-op.
|
|
627
|
+
listed=$("$RUNNER" --list --family real-herdr-gated)
|
|
628
|
+
printf '%s\n' "$listed" | grep -Fq 'tests/fm-backend-herdr-smoke.test.sh' \
|
|
629
|
+
|| fail "family real-herdr-gated must list smoke test"
|
|
630
|
+
pass "exclude-family drops the named primary family after selection"
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
test_portable_shard_union_and_coverage_guard() {
|
|
634
|
+
local s1 s2 proven serial herdr all_count union_count overlap out first
|
|
635
|
+
s1=$("$RUNNER" --list --lane portable-parallel-1)
|
|
636
|
+
s2=$("$RUNNER" --list --lane portable-parallel-2)
|
|
637
|
+
proven=$("$RUNNER" --list --proven-isolated)
|
|
638
|
+
serial=$("$RUNNER" --list --lane portable-serial)
|
|
639
|
+
herdr=$("$RUNNER" --list --family real-herdr-gated)
|
|
640
|
+
[ -n "$s1" ] && [ -n "$s2" ] || fail "portable parallel shards must be non-empty"
|
|
641
|
+
# Shards disjoint.
|
|
642
|
+
overlap=$(comm -12 <(printf '%s\n' "$s1" | LC_ALL=C sort) <(printf '%s\n' "$s2" | LC_ALL=C sort) || true)
|
|
643
|
+
[ -z "$overlap" ] || fail "portable parallel shards overlap: $overlap"
|
|
644
|
+
# Union of shards equals proven-isolated.
|
|
645
|
+
[ "$(printf '%s\n' "$s1" "$s2" | LC_ALL=C sort -u)" = \
|
|
646
|
+
"$(printf '%s\n' "$proven" | LC_ALL=C sort -u)" ] \
|
|
647
|
+
|| fail "shard union must equal proven-isolated set"
|
|
648
|
+
# No herdr in portable lanes.
|
|
649
|
+
printf '%s\n' "$s1" "$s2" "$serial" | grep -Fq 'tests/fm-backend-herdr-smoke.test.sh' \
|
|
650
|
+
&& fail "portable lanes must not include real-herdr-gated smoke"
|
|
651
|
+
printf '%s\n' "$herdr" | grep -Fq 'tests/fm-backend-herdr-smoke.test.sh' \
|
|
652
|
+
|| fail "herdr family must include smoke"
|
|
653
|
+
out=$("$RUNNER" --check-coverage)
|
|
654
|
+
assert_contains "$out" "FM_TEST_COVERAGE ok" "coverage guard success marker"
|
|
655
|
+
all_count=$("$RUNNER" --list --all | wc -l | tr -d ' ')
|
|
656
|
+
union_count=$(printf '%s\n' "$s1" "$s2" "$serial" "$herdr" | LC_ALL=C sort -u | wc -l | tr -d ' ')
|
|
657
|
+
[ "$union_count" = "$all_count" ] \
|
|
658
|
+
|| fail "union of lanes ($union_count) must equal --all ($all_count)"
|
|
659
|
+
# No duplicates across the four partitions.
|
|
660
|
+
[ "$(printf '%s\n' "$s1" "$s2" "$serial" "$herdr" | LC_ALL=C sort | uniq -d | wc -l | tr -d ' ')" = "0" ] \
|
|
661
|
+
|| fail "lanes must not duplicate scripts"
|
|
662
|
+
# LPT order: first script of shard 1 is the longest proven script.
|
|
663
|
+
first=$(printf '%s\n' "$s1" | head -n 1)
|
|
664
|
+
[ "$first" = "tests/fm-x-mode.test.sh" ] \
|
|
665
|
+
|| fail "shard 1 must start with the longest proven script, got $first"
|
|
666
|
+
pass "portable shard union, disjointness, and coverage guard hold"
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
test_portable_serial_shards_partition_the_serial_lane() {
|
|
670
|
+
local lanes count serial shard listed union dups shard_lane total cap
|
|
671
|
+
lanes=$("$RUNNER" --list-lanes)
|
|
672
|
+
count=$(printf '%s\n' "$lanes" | grep -c '^portable-serial-[0-9]*of[0-9]*$')
|
|
673
|
+
[ "$count" -ge 2 ] || fail "expected at least two portable serial shard lanes, got $count"
|
|
674
|
+
printf '%s\n' "$lanes" | grep -q "^portable-serial-1of${count}\$" \
|
|
675
|
+
|| fail "shard lane names must carry the shard count ${count}: $lanes"
|
|
676
|
+
|
|
677
|
+
serial=$("$RUNNER" --list --lane portable-serial | LC_ALL=C sort)
|
|
678
|
+
union=""
|
|
679
|
+
shard=1
|
|
680
|
+
while [ "$shard" -le "$count" ]; do
|
|
681
|
+
shard_lane="portable-serial-${shard}of${count}"
|
|
682
|
+
listed=$("$RUNNER" --list --lane "$shard_lane")
|
|
683
|
+
[ -n "$listed" ] || fail "$shard_lane selected no tests"
|
|
684
|
+
union=$(printf '%s\n%s' "$union" "$listed")
|
|
685
|
+
shard=$((shard + 1))
|
|
686
|
+
done
|
|
687
|
+
union=$(printf '%s\n' "$union" | grep -v '^$' || true)
|
|
688
|
+
|
|
689
|
+
dups=$(printf '%s\n' "$union" | LC_ALL=C sort | uniq -d || true)
|
|
690
|
+
[ -z "$dups" ] || fail "portable serial shards run the same script twice: $dups"
|
|
691
|
+
[ "$(printf '%s\n' "$union" | LC_ALL=C sort)" = "$serial" ] \
|
|
692
|
+
|| fail "portable serial shards must exactly cover the portable serial lane"
|
|
693
|
+
|
|
694
|
+
# Every shard carries a real share of the lane, so no degenerate partition
|
|
695
|
+
# leaves one runner doing nearly all of the work the split exists to spread.
|
|
696
|
+
total=$(printf '%s\n' "$serial" | wc -l | tr -d ' ')
|
|
697
|
+
cap=$((total * 6 / 10))
|
|
698
|
+
shard=1
|
|
699
|
+
while [ "$shard" -le "$count" ]; do
|
|
700
|
+
listed=$("$RUNNER" --list --lane "portable-serial-${shard}of${count}" | wc -l | tr -d ' ')
|
|
701
|
+
[ "$listed" -ge 2 ] \
|
|
702
|
+
|| fail "portable-serial-${shard}of${count} holds only $listed script(s)"
|
|
703
|
+
[ "$listed" -le "$cap" ] \
|
|
704
|
+
|| fail "portable-serial-${shard}of${count} holds $listed of $total scripts"
|
|
705
|
+
shard=$((shard + 1))
|
|
706
|
+
done
|
|
707
|
+
|
|
708
|
+
# Assignment is deterministic across invocations.
|
|
709
|
+
[ "$("$RUNNER" --list --lane "portable-serial-1of${count}")" = \
|
|
710
|
+
"$("$RUNNER" --list --lane "portable-serial-1of${count}")" ] \
|
|
711
|
+
|| fail "portable serial shard membership must be deterministic"
|
|
712
|
+
pass "portable serial shards are a deterministic disjoint cover of the serial lane"
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
test_portable_serial_shard_lane_refusals() {
|
|
716
|
+
local tmp count rc other
|
|
717
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-shard-lane.XXXXXX")
|
|
718
|
+
count=$("$RUNNER" --list-lanes | grep -c '^portable-serial-[0-9]*of[0-9]*$')
|
|
719
|
+
other=$((count + 1))
|
|
720
|
+
|
|
721
|
+
# A lane built for a different shard count must refuse rather than run a
|
|
722
|
+
# partial suite: this is what keeps a CI matrix from silently dropping tests.
|
|
723
|
+
set +e
|
|
724
|
+
"$RUNNER" --list --lane "portable-serial-1of${other}" >"$tmp/out" 2>"$tmp/err"
|
|
725
|
+
rc=$?
|
|
726
|
+
set -e
|
|
727
|
+
[ "$rc" -eq 2 ] || fail "mismatched shard count must refuse (exit 2), got $rc"
|
|
728
|
+
[ ! -s "$tmp/out" ] || fail "mismatched shard count must not list tests"
|
|
729
|
+
grep -Fq "configured for $count" "$tmp/err" \
|
|
730
|
+
|| fail "mismatch refusal must name the configured count: $(cat "$tmp/err")"
|
|
731
|
+
|
|
732
|
+
set +e
|
|
733
|
+
"$RUNNER" --list --lane "portable-serial-$((count + 1))of${count}" >"$tmp/out2" 2>"$tmp/err2"
|
|
734
|
+
rc=$?
|
|
735
|
+
set -e
|
|
736
|
+
[ "$rc" -eq 2 ] || fail "out-of-range shard index must refuse (exit 2), got $rc"
|
|
737
|
+
grep -Fq "outside 1..$count" "$tmp/err2" \
|
|
738
|
+
|| fail "range refusal message missing: $(cat "$tmp/err2")"
|
|
739
|
+
|
|
740
|
+
set +e
|
|
741
|
+
"$RUNNER" --list --lane portable-serial-1 >"$tmp/out3" 2>"$tmp/err3"
|
|
742
|
+
rc=$?
|
|
743
|
+
set -e
|
|
744
|
+
[ "$rc" -eq 2 ] || fail "shard lane without a count must refuse (exit 2), got $rc"
|
|
745
|
+
rm -rf "$tmp"
|
|
746
|
+
pass "portable serial shard lanes refuse mismatched, out-of-range, and countless names"
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
test_jobs_requires_proven_isolated() {
|
|
750
|
+
local tmp rc shard_lane
|
|
751
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-jobs.XXXXXX")
|
|
752
|
+
set +e
|
|
753
|
+
"$RUNNER" --jobs 2 --lane portable-serial >"$tmp/out" 2>"$tmp/err"
|
|
754
|
+
rc=$?
|
|
755
|
+
set -e
|
|
756
|
+
[ "$rc" -eq 2 ] || fail "--jobs with portable-serial must refuse (exit 2), got $rc"
|
|
757
|
+
grep -Fq 'not in the proven-isolated set' "$tmp/err" \
|
|
758
|
+
|| fail "--jobs refusal message missing: $(cat "$tmp/err")"
|
|
759
|
+
set +e
|
|
760
|
+
"$RUNNER" --jobs 2 tests/fm-afk-inject-e2e.test.sh >"$tmp/out2" 2>"$tmp/err2"
|
|
761
|
+
rc=$?
|
|
762
|
+
set -e
|
|
763
|
+
[ "$rc" -eq 2 ] || fail "--jobs on a family with no recorded proof must refuse, got $rc"
|
|
764
|
+
# Sharding across runners never relaxes the serial rule inside one shard.
|
|
765
|
+
shard_lane=$("$RUNNER" --list-lanes | grep -m1 '^portable-serial-[0-9]*of[0-9]*$')
|
|
766
|
+
set +e
|
|
767
|
+
"$RUNNER" --jobs 2 --lane "$shard_lane" >"$tmp/out3" 2>"$tmp/err3"
|
|
768
|
+
rc=$?
|
|
769
|
+
set -e
|
|
770
|
+
[ "$rc" -eq 2 ] || fail "--jobs with a portable serial shard must refuse, got $rc"
|
|
771
|
+
grep -Fq 'not in the proven-isolated set' "$tmp/err3" \
|
|
772
|
+
|| fail "shard --jobs refusal message missing: $(cat "$tmp/err3")"
|
|
773
|
+
rm -rf "$tmp"
|
|
774
|
+
pass "--jobs refuses non-proven / stateful selections"
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
# The complement of the refusal above: a family carrying a recorded concurrent
|
|
778
|
+
# proof is admitted and actually scheduled, so the admission rule is two-sided
|
|
779
|
+
# rather than a blanket refusal that happens to pass its negative cases.
|
|
780
|
+
test_jobs_admits_a_concurrent_safe_family() {
|
|
781
|
+
local tmp rc external
|
|
782
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-jobs-admit.XXXXXX")
|
|
783
|
+
# --list exits before the admission guard, so this has to be a real run for
|
|
784
|
+
# the assertion to mean anything. Two cheap watcher-wake-lock scripts exercise
|
|
785
|
+
# admission and the concurrent scheduler for real.
|
|
786
|
+
set +e
|
|
787
|
+
"$RUNNER" --jobs 2 \
|
|
788
|
+
tests/fm-supervision-events.test.sh tests/fm-session-lock-ancestry.test.sh \
|
|
789
|
+
>"$tmp/out" 2>"$tmp/err"
|
|
790
|
+
rc=$?
|
|
791
|
+
set -e
|
|
792
|
+
[ "$rc" -eq 0 ] || fail "--jobs on a proven family must be admitted, got $rc: $(cat "$tmp/err") $(cat "$tmp/out")"
|
|
793
|
+
grep -Fq 'FM_TEST_SUMMARY total=2 failed=0' "$tmp/out" \
|
|
794
|
+
|| fail "the admitted concurrent run did not report both scripts green: $(cat "$tmp/out")"
|
|
795
|
+
|
|
796
|
+
set +e
|
|
797
|
+
"$RUNNER" --jobs 5 tests/fm-session-lock-ancestry.test.sh \
|
|
798
|
+
>"$tmp/over-cap.out" 2>"$tmp/over-cap.err"
|
|
799
|
+
rc=$?
|
|
800
|
+
set -e
|
|
801
|
+
[ "$rc" -eq 2 ] || fail "a family run above its proven four-worker cap must be refused, got $rc"
|
|
802
|
+
|
|
803
|
+
external="$tmp/fm-session-lock-ancestry.test.sh"
|
|
804
|
+
printf '#!/usr/bin/env bash\necho "ok - colliding external fixture"\n' >"$external"
|
|
805
|
+
chmod +x "$external"
|
|
806
|
+
set +e
|
|
807
|
+
"$RUNNER" --jobs 2 "$external" >"$tmp/external.out" 2>"$tmp/external.err"
|
|
808
|
+
rc=$?
|
|
809
|
+
set -e
|
|
810
|
+
[ "$rc" -eq 2 ] \
|
|
811
|
+
|| fail "an external script colliding with a proven family member must be refused, got $rc"
|
|
812
|
+
rm -rf "$tmp"
|
|
813
|
+
pass "--jobs admits and schedules a family with a recorded concurrent proof"
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
# Workers are handed scripts in order, so the slowest script must start first or
|
|
817
|
+
# it runs alone at the tail and throws away most of the concurrency.
|
|
818
|
+
test_concurrent_runs_are_ordered_longest_first() {
|
|
819
|
+
local tmp listed first
|
|
820
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-order.XXXXXX")
|
|
821
|
+
set +e
|
|
822
|
+
"$RUNNER" --jobs 2 --family watcher-wake-lock --list >"$tmp/serial" 2>&1
|
|
823
|
+
set -e
|
|
824
|
+
# The scheduler reorders the real run, so assert on the begin-marker order of
|
|
825
|
+
# a real concurrent run over scripts whose hints differ by a wide margin.
|
|
826
|
+
set +e
|
|
827
|
+
"$RUNNER" --jobs 2 \
|
|
828
|
+
tests/fm-session-lock-ancestry.test.sh tests/fm-task-inbox.test.sh \
|
|
829
|
+
>"$tmp/out" 2>"$tmp/err"
|
|
830
|
+
set -e
|
|
831
|
+
first=$(grep -m1 '^FM_TEST_BEGIN' "$tmp/out" | awk '{print $3}')
|
|
832
|
+
[ "$first" = tests/fm-task-inbox.test.sh ] \
|
|
833
|
+
|| fail "concurrent run did not start the longest script first, started: $first"
|
|
834
|
+
rm -rf "$tmp"
|
|
835
|
+
pass "a concurrent run starts the longest-hint script first"
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
# --max-wall-ms is checked after the run, so it cannot end a run that never
|
|
839
|
+
# finishes. A hung script has to become a bounded failure, because an unbounded
|
|
840
|
+
# suite is exactly what silently outruns its caller's invocation budget.
|
|
841
|
+
test_per_script_timeout_bounds_a_hang() {
|
|
842
|
+
local tmp repo runner hang rc began ended grandchild_pid grandchild waited
|
|
843
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-hang.XXXXXX")
|
|
844
|
+
repo="$tmp/repo"
|
|
845
|
+
runner="$repo/bin/fm-test-run.sh"
|
|
846
|
+
hang=tests/fm-hang-fixture.test.sh
|
|
847
|
+
mkdir -p "$repo/bin" "$repo/tests"
|
|
848
|
+
cp "$RUNNER" "$runner"
|
|
849
|
+
cp "$ROOT/bin/fm-timeout-lib.sh" "$repo/bin/fm-timeout-lib.sh"
|
|
850
|
+
grandchild_pid="$tmp/grandchild.pid"
|
|
851
|
+
cat >"$repo/$hang" <<'SH'
|
|
852
|
+
#!/usr/bin/env bash
|
|
853
|
+
echo "ok - fixture is about to hang"
|
|
854
|
+
sh -c 'trap "" TERM; echo $$ >"$1"; sleep 600' _ "$GRANDCHILD_PID" &
|
|
855
|
+
sleep 600
|
|
856
|
+
SH
|
|
857
|
+
chmod +x "$runner" "$repo/$hang"
|
|
858
|
+
|
|
859
|
+
began=$(date +%s)
|
|
860
|
+
set +e
|
|
861
|
+
GRANDCHILD_PID="$grandchild_pid" \
|
|
862
|
+
"$runner" --per-script-timeout-secs 3 "$hang" >"$tmp/out" 2>"$tmp/err"
|
|
863
|
+
rc=$?
|
|
864
|
+
set -e
|
|
865
|
+
ended=$(date +%s)
|
|
866
|
+
|
|
867
|
+
[ "$rc" -ne 0 ] || fail "a terminated script must fail the run: $(cat "$tmp/out")"
|
|
868
|
+
[ "$((ended - began))" -lt 120 ] \
|
|
869
|
+
|| fail "the per-script bound did not stop a 600s hang (took $((ended - began))s)"
|
|
870
|
+
grep -Fq 'exceeded the per-script bound' "$tmp/out" \
|
|
871
|
+
|| fail "the terminated script was not named: $(cat "$tmp/out")"
|
|
872
|
+
grep -Eq 'FM_TEST_END .* exit=124 ' "$tmp/out" \
|
|
873
|
+
|| fail "a terminated script must be recorded as exit 124: $(cat "$tmp/out")"
|
|
874
|
+
# The run still completes and accounts for the script, rather than dying.
|
|
875
|
+
grep -Fq 'FM_TEST_SUMMARY total=1 failed=1' "$tmp/out" \
|
|
876
|
+
|| fail "the bounded run did not report a complete summary: $(cat "$tmp/out")"
|
|
877
|
+
[ -s "$grandchild_pid" ] || fail "the hanging fixture did not record its grandchild"
|
|
878
|
+
grandchild=$(cat "$grandchild_pid")
|
|
879
|
+
waited=0
|
|
880
|
+
while kill -0 "$grandchild" 2>/dev/null && [ "$waited" -lt 50 ]; do
|
|
881
|
+
sleep 0.1
|
|
882
|
+
waited=$((waited + 1))
|
|
883
|
+
done
|
|
884
|
+
if kill -0 "$grandchild" 2>/dev/null; then
|
|
885
|
+
kill -KILL "$grandchild" 2>/dev/null || true
|
|
886
|
+
fail "the timed-out script left grandchild $grandchild running"
|
|
887
|
+
fi
|
|
888
|
+
|
|
889
|
+
# 0 keeps the historical unbounded behavior, so no existing caller changes.
|
|
890
|
+
set +e
|
|
891
|
+
"$runner" --per-script-timeout-secs nope "$hang" >"$tmp/o2" 2>"$tmp/e2"
|
|
892
|
+
rc=$?
|
|
893
|
+
set -e
|
|
894
|
+
[ "$rc" -eq 2 ] || fail "--per-script-timeout-secs with a non-number must be refused, got $rc"
|
|
895
|
+
|
|
896
|
+
rm -rf "$tmp"
|
|
897
|
+
pass "--per-script-timeout-secs turns a hung script into a bounded failure"
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
# The duration regression this guard exists for: a suite whose scripts are all
|
|
901
|
+
# green but whose wall clock outgrew its caller's invocation budget. The caller
|
|
902
|
+
# gets killed mid-run and retries invisibly, so an over-budget run has to be a
|
|
903
|
+
# failure, not a note in the log.
|
|
904
|
+
test_max_wall_ms_is_a_result_not_advice() {
|
|
905
|
+
local tmp repo runner fast rc summary_duration budget_duration
|
|
906
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-budget.XXXXXX")
|
|
907
|
+
repo="$tmp/repo"
|
|
908
|
+
runner="$repo/bin/fm-test-run.sh"
|
|
909
|
+
fast=tests/fm-budget-fixture.test.sh
|
|
910
|
+
mkdir -p "$repo/bin" "$repo/tests"
|
|
911
|
+
cp "$RUNNER" "$runner"
|
|
912
|
+
cat >"$repo/$fast" <<'SH'
|
|
913
|
+
#!/usr/bin/env bash
|
|
914
|
+
sleep 1
|
|
915
|
+
echo "ok - budget fixture"
|
|
916
|
+
SH
|
|
917
|
+
chmod +x "$runner" "$repo/$fast"
|
|
918
|
+
|
|
919
|
+
# Comfortably inside budget: the run passes and states the budget it met.
|
|
920
|
+
set +e
|
|
921
|
+
"$runner" --max-wall-ms 60000 "$fast" >"$tmp/under" 2>"$tmp/under.err"
|
|
922
|
+
rc=$?
|
|
923
|
+
set -e
|
|
924
|
+
[ "$rc" -eq 0 ] || fail "a run inside its budget must pass, got $rc: $(cat "$tmp/under.err")"
|
|
925
|
+
grep -Eq '^FM_TEST_BUDGET max_wall_ms=60000 duration_ms=[0-9]+$' "$tmp/under" \
|
|
926
|
+
|| fail "an inside-budget run did not report the budget: $(cat "$tmp/under")"
|
|
927
|
+
|
|
928
|
+
# Same green script, budget it cannot meet: the run must FAIL.
|
|
929
|
+
set +e
|
|
930
|
+
"$runner" --max-wall-ms 500 "$fast" >"$tmp/over" 2>"$tmp/over.err"
|
|
931
|
+
rc=$?
|
|
932
|
+
set -e
|
|
933
|
+
[ "$rc" -eq 1 ] || fail "an over-budget run must fail through the result path, got $rc"
|
|
934
|
+
grep -Eq '^FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=[0-9]+$' "$tmp/over" \
|
|
935
|
+
|| fail "an over-budget run omitted its summary: $(cat "$tmp/over")"
|
|
936
|
+
grep -Eq '^FM_TEST_SUMMARY_FAMILY .+$' "$tmp/over" \
|
|
937
|
+
|| fail "an over-budget run omitted its family summary: $(cat "$tmp/over")"
|
|
938
|
+
grep -Eq '^FM_TEST_SLOWEST rank=1 .+$' "$tmp/over" \
|
|
939
|
+
|| fail "an over-budget run omitted its slowest result: $(cat "$tmp/over")"
|
|
940
|
+
grep -Eq '^FM_TEST_BUDGET max_wall_ms=500 duration_ms=[0-9]+$' "$tmp/over" \
|
|
941
|
+
|| fail "an over-budget run omitted its budget result: $(cat "$tmp/over")"
|
|
942
|
+
summary_duration=$(awk '/^FM_TEST_SUMMARY / { for (i=1;i<=NF;i++) if ($i ~ /^duration_ms=/) { sub(/^duration_ms=/, "", $i); print $i } }' "$tmp/over")
|
|
943
|
+
budget_duration=$(awk '/^FM_TEST_BUDGET / { for (i=1;i<=NF;i++) if ($i ~ /^duration_ms=/) { sub(/^duration_ms=/, "", $i); print $i } }' "$tmp/over")
|
|
944
|
+
[ "$budget_duration" = "$summary_duration" ] \
|
|
945
|
+
|| fail "budget verdict used a different duration than the summary: $(cat "$tmp/over")"
|
|
946
|
+
|
|
947
|
+
# A malformed budget is refused rather than silently ignored.
|
|
948
|
+
set +e
|
|
949
|
+
"$runner" --max-wall-ms 0 "$fast" >"$tmp/bad" 2>"$tmp/bad.err"
|
|
950
|
+
rc=$?
|
|
951
|
+
set -e
|
|
952
|
+
[ "$rc" -eq 2 ] || fail "--max-wall-ms 0 must be refused (exit 2), got $rc"
|
|
953
|
+
set +e
|
|
954
|
+
"$runner" --max-wall-ms nope "$fast" >"$tmp/bad2" 2>"$tmp/bad2.err"
|
|
955
|
+
rc=$?
|
|
956
|
+
set -e
|
|
957
|
+
[ "$rc" -eq 2 ] || fail "--max-wall-ms with a non-number must be refused (exit 2), got $rc"
|
|
958
|
+
|
|
959
|
+
rm -rf "$tmp"
|
|
960
|
+
pass "--max-wall-ms fails an over-budget run and refuses a malformed budget"
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
test_jobs_parallel_scheduler_and_failure_propagation() {
|
|
964
|
+
local tmp repo runner evidence fake_bin a b c d rc begin_n end_n
|
|
965
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-jobs-sched.XXXXXX")
|
|
966
|
+
repo="$tmp/repo"
|
|
967
|
+
runner="$repo/bin/fm-test-run.sh"
|
|
968
|
+
evidence="$tmp/evidence"
|
|
969
|
+
fake_bin="$tmp/fake-bin"
|
|
970
|
+
a=tests/fm-brief.test.sh
|
|
971
|
+
b=tests/fm-composer-lib.test.sh
|
|
972
|
+
c=tests/fm-lint.test.sh
|
|
973
|
+
d=tests/fm-supervision-instructions.test.sh
|
|
974
|
+
mkdir -p "$repo/bin" "$repo/tests" "$evidence" "$fake_bin"
|
|
975
|
+
cp "$RUNNER" "$runner"
|
|
976
|
+
cat >"$fake_bin/stat" <<'SH'
|
|
977
|
+
#!/usr/bin/env bash
|
|
978
|
+
if [ "$1" = "-c" ] && [ "$2" = "%a" ]; then
|
|
979
|
+
printf '700\n'
|
|
980
|
+
exit 0
|
|
981
|
+
fi
|
|
982
|
+
if [ "$1" = "-f" ] && [ "$2" = "%Lp" ]; then
|
|
983
|
+
printf ' File: "%s"\n ID: fake Namelen: 255 Type: ext2/ext3\n700\n' "$3"
|
|
984
|
+
exit 0
|
|
985
|
+
fi
|
|
986
|
+
exit 1
|
|
987
|
+
SH
|
|
988
|
+
# The slow fixture blocks on the replacement fixture's own signal rather than
|
|
989
|
+
# a wall-clock sleep, so a loaded machine cannot let it finish first and turn
|
|
990
|
+
# a correct scheduler into a failure. The bounded deadline is only there so a
|
|
991
|
+
# scheduler that really does wait for the oldest worker still reports instead
|
|
992
|
+
# of hanging.
|
|
993
|
+
cat >"$repo/$a" <<'SH'
|
|
994
|
+
#!/usr/bin/env bash
|
|
995
|
+
if [ -n "${SCHED_WAIT_FOR_REPLACEMENT:-}" ]; then
|
|
996
|
+
waited=0
|
|
997
|
+
while [ ! -e "$SCHED_EVIDENCE/replacement-started" ] && [ "$waited" -lt 600 ]; do
|
|
998
|
+
sleep 0.05
|
|
999
|
+
waited=$((waited + 1))
|
|
1000
|
+
done
|
|
1001
|
+
fi
|
|
1002
|
+
touch "$SCHED_EVIDENCE/slow-done"
|
|
1003
|
+
echo "ok - slow fixture"
|
|
1004
|
+
SH
|
|
1005
|
+
cat >"$repo/$b" <<'SH'
|
|
1006
|
+
#!/usr/bin/env bash
|
|
1007
|
+
echo "ok - fast fixture"
|
|
1008
|
+
SH
|
|
1009
|
+
cat >"$repo/$c" <<'SH'
|
|
1010
|
+
#!/usr/bin/env bash
|
|
1011
|
+
# Read the evidence before releasing the slow fixture, so the release can never
|
|
1012
|
+
# race ahead of the check it is being used to make.
|
|
1013
|
+
if [ -e "$SCHED_EVIDENCE/slow-done" ]; then
|
|
1014
|
+
touch "$SCHED_EVIDENCE/replacement-started"
|
|
1015
|
+
echo "not ok - scheduler waited for oldest worker"
|
|
1016
|
+
exit 1
|
|
1017
|
+
fi
|
|
1018
|
+
touch "$SCHED_EVIDENCE/replacement-started"
|
|
1019
|
+
echo "ok - replacement fixture started before slow fixture finished"
|
|
1020
|
+
SH
|
|
1021
|
+
chmod +x "$runner" "$repo/$a" "$repo/$b" "$repo/$c" "$fake_bin/stat"
|
|
1022
|
+
set +e
|
|
1023
|
+
PATH="$fake_bin:$PATH" SCHED_EVIDENCE="$evidence" SCHED_WAIT_FOR_REPLACEMENT=1 \
|
|
1024
|
+
"$runner" --jobs 2 --json "$tmp/timing.json" \
|
|
1025
|
+
"$a" "$b" "$c" >"$tmp/out" 2>"$tmp/err"
|
|
1026
|
+
rc=$?
|
|
1027
|
+
set -e
|
|
1028
|
+
[ "$rc" -eq 0 ] || { cat "$tmp/out" "$tmp/err"; rm -rf "$tmp"; fail "jobs=2 must refill the first completed slot"; }
|
|
1029
|
+
begin_n=$(grep -c '^FM_TEST_BEGIN ' "$tmp/out" || true)
|
|
1030
|
+
end_n=$(grep -c '^FM_TEST_END ' "$tmp/out" || true)
|
|
1031
|
+
[ "$begin_n" -eq 3 ] || fail "expected 3 BEGIN markers, got $begin_n"
|
|
1032
|
+
[ "$end_n" -eq 3 ] || fail "expected 3 END markers, got $end_n"
|
|
1033
|
+
grep -q 'FM_TEST_SUMMARY total=3 failed=0' "$tmp/out" \
|
|
1034
|
+
|| fail "summary missing for jobs run: $(grep FM_TEST_SUMMARY "$tmp/out")"
|
|
1035
|
+
python3 -c '
|
|
1036
|
+
import json,sys
|
|
1037
|
+
doc=json.load(open(sys.argv[1]))
|
|
1038
|
+
assert doc["summary"]["total"]==3
|
|
1039
|
+
assert doc["summary"]["failed"]==0
|
|
1040
|
+
assert "jobs=2" in doc["selection"]
|
|
1041
|
+
' "$tmp/timing.json" || { rm -rf "$tmp"; fail "jobs JSON artifact wrong"; }
|
|
1042
|
+
|
|
1043
|
+
# Non-proven path is refused before any worker starts (no race masking).
|
|
1044
|
+
cat >"$tmp/fail.test.sh" <<'SH'
|
|
1045
|
+
#!/usr/bin/env bash
|
|
1046
|
+
echo "not ok - deliberate fail"
|
|
1047
|
+
exit 1
|
|
1048
|
+
SH
|
|
1049
|
+
chmod +x "$tmp/fail.test.sh"
|
|
1050
|
+
set +e
|
|
1051
|
+
"$runner" --jobs 2 "$a" "$tmp/fail.test.sh" >"$tmp/out3" 2>"$tmp/err3"
|
|
1052
|
+
rc=$?
|
|
1053
|
+
set -e
|
|
1054
|
+
[ "$rc" -eq 2 ] || fail "jobs with non-proven fail fixture must refuse before run, got $rc"
|
|
1055
|
+
|
|
1056
|
+
# Parallel failure propagation stays inside the private runner fixture.
|
|
1057
|
+
cat >"$repo/$b" <<'SH'
|
|
1058
|
+
#!/usr/bin/env bash
|
|
1059
|
+
echo "not ok - deliberate proven-set fail"
|
|
1060
|
+
exit 1
|
|
1061
|
+
SH
|
|
1062
|
+
chmod +x "$repo/$b"
|
|
1063
|
+
set +e
|
|
1064
|
+
SCHED_EVIDENCE="$evidence" "$runner" --jobs 2 "$a" "$b" >"$tmp/out4" 2>"$tmp/err4"
|
|
1065
|
+
rc=$?
|
|
1066
|
+
set -e
|
|
1067
|
+
[ "$rc" -ne 0 ] || { rm -rf "$tmp"; fail "jobs aggregate must be non-zero when a proven worker fails"; }
|
|
1068
|
+
grep -q 'FM_TEST_SUMMARY total=2 failed=1' "$tmp/out4" \
|
|
1069
|
+
|| { rm -rf "$tmp"; fail "jobs failure summary wrong: $(grep FM_TEST_SUMMARY "$tmp/out4")"; }
|
|
1070
|
+
|
|
1071
|
+
cat >"$repo/$d" <<'SH'
|
|
1072
|
+
#!/usr/bin/env bash
|
|
1073
|
+
echo "skip: herdr not found" >&2
|
|
1074
|
+
exit 0
|
|
1075
|
+
SH
|
|
1076
|
+
chmod +x "$repo/$d"
|
|
1077
|
+
set +e
|
|
1078
|
+
"$runner" --jobs 2 --fail-on-gate-skip 'herdr not found' "$d" >"$tmp/out5" 2>"$tmp/err5"
|
|
1079
|
+
rc=$?
|
|
1080
|
+
set -e
|
|
1081
|
+
[ "$rc" -ne 0 ] || { rm -rf "$tmp"; fail "parallel stderr gate skip must hard-fail"; }
|
|
1082
|
+
grep -q 'FM_TEST_SUMMARY total=1 failed=1' "$tmp/out5" \
|
|
1083
|
+
|| { rm -rf "$tmp"; fail "parallel stderr hard-fail summary wrong: $(grep FM_TEST_SUMMARY "$tmp/out5")"; }
|
|
1084
|
+
|
|
1085
|
+
"$runner" --jobs 2 "$d" >"$tmp/out6" 2>"$tmp/err6" \
|
|
1086
|
+
|| { rm -rf "$tmp"; fail "ordinary parallel stderr gate skip must remain successful"; }
|
|
1087
|
+
grep -Eq '^FM_TEST_END .+ exit=0 duration_ms=[0-9]+ gate_skip=true$' "$tmp/out6" \
|
|
1088
|
+
|| { rm -rf "$tmp"; fail "parallel stderr gate skip was not recorded"; }
|
|
1089
|
+
grep -q 'FM_TEST_SUMMARY total=1 failed=0 skipped_gate=1' "$tmp/out6" \
|
|
1090
|
+
|| { rm -rf "$tmp"; fail "parallel stderr skip summary wrong: $(grep FM_TEST_SUMMARY "$tmp/out6")"; }
|
|
1091
|
+
|
|
1092
|
+
rm -rf "$tmp"
|
|
1093
|
+
pass "jobs scheduler runs proven scripts; failure propagates; non-proven refused"
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
test_herdr_ci_family_run_has_a_step_timeout() {
|
|
1097
|
+
# The required Herdr lane's hang tripwire is the family-run *step* bound, not
|
|
1098
|
+
# the 75-minute job cap. Parse the workflow as YAML so nested `with.name`
|
|
1099
|
+
# artifact keys cannot masquerade as the step contract.
|
|
1100
|
+
command -v ruby >/dev/null 2>&1 \
|
|
1101
|
+
|| fail "ruby is required to parse .github/workflows/ci.yml as YAML"
|
|
1102
|
+
local json job_timeout step_timeout
|
|
1103
|
+
json=$(ruby -ryaml -rjson -e '
|
|
1104
|
+
doc = YAML.load_file(ARGV[0])
|
|
1105
|
+
job = doc.fetch("jobs").fetch("tests-herdr")
|
|
1106
|
+
step = job.fetch("steps").find { |s|
|
|
1107
|
+
s.is_a?(Hash) && s["name"] == "Run real-Herdr family (serial, required)"
|
|
1108
|
+
}
|
|
1109
|
+
raise "missing family-run step" if step.nil?
|
|
1110
|
+
raise "family-run step has no timeout-minutes" unless step.key?("timeout-minutes")
|
|
1111
|
+
puts JSON.generate(
|
|
1112
|
+
"job_timeout" => job.fetch("timeout-minutes"),
|
|
1113
|
+
"step_timeout" => step.fetch("timeout-minutes")
|
|
1114
|
+
)
|
|
1115
|
+
' "$ROOT/.github/workflows/ci.yml") \
|
|
1116
|
+
|| fail "could not parse tests-herdr timeouts from ci.yml"
|
|
1117
|
+
job_timeout=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["job_timeout"])' <<<"$json") \
|
|
1118
|
+
|| fail "could not read job timeout from parsed workflow"
|
|
1119
|
+
step_timeout=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["step_timeout"])' <<<"$json") \
|
|
1120
|
+
|| fail "could not read step timeout from parsed workflow"
|
|
1121
|
+
[ "$job_timeout" = 75 ] \
|
|
1122
|
+
|| fail "tests-herdr job backstop must stay 75 minutes, got $job_timeout"
|
|
1123
|
+
[ "$step_timeout" = 20 ] \
|
|
1124
|
+
|| fail "family-run step timeout must be 20 minutes, got $step_timeout"
|
|
1125
|
+
[ "$step_timeout" -lt "$job_timeout" ] \
|
|
1126
|
+
|| fail "family-run step timeout must be below the job backstop"
|
|
1127
|
+
pass "Herdr CI family-run step times out at 20 min under a 75 min job backstop"
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
test_aggregate_json() {
|
|
1131
|
+
local tmp a b
|
|
1132
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-aggjson.XXXXXX")
|
|
1133
|
+
cat >"$tmp/a.json" <<'JSON'
|
|
1134
|
+
{
|
|
1135
|
+
"run_id": "a",
|
|
1136
|
+
"selection": "lane=portable-parallel-1",
|
|
1137
|
+
"started_at": "2026-07-22T00:00:00Z",
|
|
1138
|
+
"finished_at": "2026-07-22T00:01:00Z",
|
|
1139
|
+
"summary": {"total": 1, "failed": 0, "skipped_gate": 0, "duration_ms": 1000},
|
|
1140
|
+
"scripts": [{"path": "tests/a.test.sh", "family": "pure-contract-unit", "duration_ms": 1000, "exit": 0, "gate_skip": false}]
|
|
1141
|
+
}
|
|
1142
|
+
JSON
|
|
1143
|
+
cat >"$tmp/b.json" <<'JSON'
|
|
1144
|
+
{
|
|
1145
|
+
"run_id": "b",
|
|
1146
|
+
"selection": "lane=portable-serial",
|
|
1147
|
+
"started_at": "2026-07-22T00:00:00Z",
|
|
1148
|
+
"finished_at": "2026-07-22T00:02:00Z",
|
|
1149
|
+
"summary": {"total": 2, "failed": 1, "skipped_gate": 0, "duration_ms": 2000},
|
|
1150
|
+
"scripts": [
|
|
1151
|
+
{"path": "tests/b.test.sh", "family": "afk", "duration_ms": 1500, "exit": 1, "gate_skip": false},
|
|
1152
|
+
{"path": "tests/c.test.sh", "family": "afk", "duration_ms": 500, "exit": 0, "gate_skip": false}
|
|
1153
|
+
]
|
|
1154
|
+
}
|
|
1155
|
+
JSON
|
|
1156
|
+
out=$("$RUNNER" --aggregate-json "$tmp/out.json" "$tmp/a.json" "$tmp/b.json")
|
|
1157
|
+
assert_contains "$out" "FM_TEST_AGGREGATE lanes=2 total=3 failed=1" "aggregate summary line"
|
|
1158
|
+
python3 -c '
|
|
1159
|
+
import json,sys
|
|
1160
|
+
doc=json.load(open(sys.argv[1]))
|
|
1161
|
+
assert doc["kind"]=="aggregate"
|
|
1162
|
+
assert doc["summary"]["lanes"]==2
|
|
1163
|
+
assert doc["summary"]["total"]==3
|
|
1164
|
+
assert doc["summary"]["failed"]==1
|
|
1165
|
+
assert doc["summary"]["critical_path_duration_ms"]==2000
|
|
1166
|
+
assert len(doc["scripts"])==3
|
|
1167
|
+
' "$tmp/out.json" || { rm -rf "$tmp"; fail "aggregate JSON shape wrong"; }
|
|
1168
|
+
rm -rf "$tmp"
|
|
1169
|
+
pass "aggregate-json merges lane timing artifacts"
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
test_list_all_exact_suite_coverage
|
|
1173
|
+
test_family_selection
|
|
1174
|
+
test_single_script_selection
|
|
1175
|
+
test_changed_file_selection_is_conservative
|
|
1176
|
+
test_changed_runner_surfaces_select_their_family
|
|
1177
|
+
test_changed_dependency_selection_and_unmapped_failure
|
|
1178
|
+
test_changed_bin_reference_selects_per_script_not_per_family
|
|
1179
|
+
test_changed_uses_bounded_automatic_concurrency
|
|
1180
|
+
test_empty_selection_emits_summary
|
|
1181
|
+
test_timing_markers_and_json
|
|
1182
|
+
test_aggregate_exit_behavior
|
|
1183
|
+
test_gate_skip_accounting
|
|
1184
|
+
test_fail_on_gate_skip_token
|
|
1185
|
+
test_exclude_family
|
|
1186
|
+
test_portable_shard_union_and_coverage_guard
|
|
1187
|
+
test_portable_serial_shards_partition_the_serial_lane
|
|
1188
|
+
test_portable_serial_shard_lane_refusals
|
|
1189
|
+
test_jobs_requires_proven_isolated
|
|
1190
|
+
test_jobs_admits_a_concurrent_safe_family
|
|
1191
|
+
test_concurrent_runs_are_ordered_longest_first
|
|
1192
|
+
test_per_script_timeout_bounds_a_hang
|
|
1193
|
+
test_max_wall_ms_is_a_result_not_advice
|
|
1194
|
+
test_jobs_parallel_scheduler_and_failure_propagation
|
|
1195
|
+
test_herdr_ci_family_run_has_a_step_timeout
|
|
1196
|
+
test_aggregate_json
|