@zerwiz/ymir 0.1.4 → 0.1.7
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/README.md +74 -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 +360 -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.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +11 -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/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/runes_audit.md +5 -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 +121 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -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 +159 -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 +736 -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 +539 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -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 +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -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 +101 -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 +180 -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 +503 -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 +90 -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 +57 -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 +35 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/install.md +69 -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 +498 -0
- package/CHANGELOG.md +1913 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +114 -60
- 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 +97 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +108 -0
- package/RULES/07-config.md +101 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +22 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +133 -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 +256 -0
- package/bin/apodex-smoke-test.sh +105 -0
- package/bin/bifrost-bridge.sh +131 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +112 -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 +51 -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-assemble.sh +154 -0
- package/bin/changelog-guard.sh +197 -0
- package/bin/crash-sense.sh +173 -0
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +153 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +225 -0
- package/bin/eindri-acclaim.sh +47 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +54 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +98 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +158 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +81 -0
- package/bin/github-deploy.sh +94 -0
- package/bin/gjallarhorn-expose.sh +136 -0
- package/bin/gjallarhorn-notify.sh +71 -0
- package/bin/gjallarhorn-purge.sh +67 -0
- package/bin/gjallarhorn-tunnel.sh +85 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +130 -0
- package/bin/hall-snapshot.sh +111 -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 +452 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +105 -0
- package/bin/hodd.sh +102 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +121 -0
- package/bin/local-model-lock.sh +70 -0
- package/bin/mimir-bridge.py +259 -0
- package/bin/mimir-bridge.sh +100 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +72 -0
- package/bin/mjollnir.sh +104 -0
- package/bin/model-hardware.sh +96 -0
- package/bin/model-resolve.sh +160 -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-hall-snapshot.sh +28 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +161 -0
- package/bin/omarchy-plugins.sh +187 -0
- package/bin/omarchy-sense.sh +162 -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-model-agnostic.sh +71 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +58 -0
- package/bin/project-git.sh +69 -0
- package/bin/public-guard.sh +71 -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 +104 -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 +94 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +89 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +203 -0
- package/bin/telegram-bot.sh +82 -0
- package/bin/toolchain.sh +74 -0
- package/bin/ui-truth-check.sh +75 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +298 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +60 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +163 -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 +806 -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-plan.sh +356 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +110 -0
- package/bin/ymir-setup-auth.sh +119 -0
- package/bin/ymir-validate.sh +193 -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 +1060 -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 +54 -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 +269 -0
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +217 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1745 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Behavior tests for the generic process-to-event runner and its Lavish adapter.
|
|
3
|
+
#
|
|
4
|
+
# The source under test is a fake blocking process that returns only when its
|
|
5
|
+
# trigger file appears, so completion is a real process event and no test here
|
|
6
|
+
# depends on a discovery timer. The Lavish adapter is exercised through its own
|
|
7
|
+
# public commands against the currently published poll shape; no live Lavish
|
|
8
|
+
# server is started.
|
|
9
|
+
#
|
|
10
|
+
# Delivery is deliberately NOT asserted as at-least-once or lossless: the
|
|
11
|
+
# published Lavish poll clears feedback destructively before returning it, so
|
|
12
|
+
# the only durability under test is the runner's own - output that reached the
|
|
13
|
+
# runner is stored before it is announced.
|
|
14
|
+
set -u
|
|
15
|
+
|
|
16
|
+
# shellcheck source=tests/lib.sh
|
|
17
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
18
|
+
|
|
19
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
20
|
+
TMP_ROOT=$(fm_test_tmproot fm-procevent-tests)
|
|
21
|
+
export FM_PROCEVENT_CLAIM_ROOT="$TMP_ROOT/claims"
|
|
22
|
+
|
|
23
|
+
BLOCKER="$TMP_ROOT/blocker.sh"
|
|
24
|
+
cat > "$BLOCKER" <<'SH'
|
|
25
|
+
#!/usr/bin/env bash
|
|
26
|
+
# Blocks until the trigger exists, then emits its payload. Completion is the
|
|
27
|
+
# event; nothing here polls on a schedule.
|
|
28
|
+
trigger=$1; shift
|
|
29
|
+
while [ ! -e "$trigger" ]; do sleep 0.05; done
|
|
30
|
+
[ -n "${BLOCKER_STDERR:-}" ] && printf 'noise on stderr\n' >&2
|
|
31
|
+
[ -n "${BLOCKER_EXIT:-}" ] && exit "$BLOCKER_EXIT"
|
|
32
|
+
printf '%s\n' "$@"
|
|
33
|
+
SH
|
|
34
|
+
chmod +x "$BLOCKER"
|
|
35
|
+
|
|
36
|
+
pe() { FM_HOME="$1" "$ROOT/bin/fm-procevent.sh" "${@:2}"; }
|
|
37
|
+
|
|
38
|
+
# Every source this suite registers is tracked so teardown can stop its runner.
|
|
39
|
+
# A runner started by reconcile is detached and reparented, so a source that
|
|
40
|
+
# never completes outlives the suite unless it is retired explicitly - removing
|
|
41
|
+
# the fixture directory does not stop an already-running child.
|
|
42
|
+
PE_TRACKED=()
|
|
43
|
+
pe_register() { # <home> <adapter> <source-id> -- <argv>...
|
|
44
|
+
local home=$1 adapter=$2 id=$3
|
|
45
|
+
shift 3
|
|
46
|
+
PE_TRACKED+=("$home|$id")
|
|
47
|
+
pe "$home" register "$adapter" "$id" "$@"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
procevent_teardown() {
|
|
51
|
+
local entry home seen=$'\n'
|
|
52
|
+
for entry in ${PE_TRACKED[@]+"${PE_TRACKED[@]}"}; do
|
|
53
|
+
home=${entry%%|*}
|
|
54
|
+
case "$seen" in
|
|
55
|
+
*$'\n'"$home"$'\n'*) continue ;;
|
|
56
|
+
esac
|
|
57
|
+
seen+="$home"$'\n'
|
|
58
|
+
FM_HOME="$home" "$ROOT/bin/fm-procevent.sh" sweep-home >/dev/null 2>&1 || true
|
|
59
|
+
done
|
|
60
|
+
fm_test_cleanup
|
|
61
|
+
}
|
|
62
|
+
trap procevent_teardown EXIT
|
|
63
|
+
new_home() { mkdir -p "$1/state"; }
|
|
64
|
+
wake_payloads() { awk -F '\t' '{print $5}' "$1/state/.wake-queue" 2>/dev/null; }
|
|
65
|
+
|
|
66
|
+
first_result() { # <home> <source-id>: print the first captured result, if any
|
|
67
|
+
local g
|
|
68
|
+
for g in "$1/state/procevent-inbox/$2".*.result; do
|
|
69
|
+
[ -e "$g" ] || continue
|
|
70
|
+
printf '%s\n' "$g"
|
|
71
|
+
return 0
|
|
72
|
+
done
|
|
73
|
+
return 1
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
count_results() { # <home> <source-id>
|
|
77
|
+
local g n=0
|
|
78
|
+
for g in "$1/state/procevent-inbox/$2".*.result; do
|
|
79
|
+
[ -e "$g" ] && n=$((n + 1))
|
|
80
|
+
done
|
|
81
|
+
printf '%s\n' "$n"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
wait_for() { # <file> [tries]
|
|
85
|
+
local f=$1 n=${2:-100}
|
|
86
|
+
for _ in $(seq 1 "$n"); do [ -s "$f" ] && return 0; sleep 0.1; done
|
|
87
|
+
return 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# <file> <count> [tries]: wait until <file> holds at least <count> lines. A
|
|
91
|
+
# detached runner appends its execution marker after the command that started it
|
|
92
|
+
# has already returned, so a caller that needs that append must wait for it
|
|
93
|
+
# rather than assume a fixed settle window covered it on a loaded machine.
|
|
94
|
+
wait_for_lines() {
|
|
95
|
+
local f=$1 want=$2 n=${3:-100} have
|
|
96
|
+
for _ in $(seq 1 "$n"); do
|
|
97
|
+
have=$(wc -l < "$f" 2>/dev/null | tr -d ' ')
|
|
98
|
+
case "$have" in ''|*[!0-9]*) have=0 ;; esac
|
|
99
|
+
[ "$have" -ge "$want" ] && return 0
|
|
100
|
+
sleep 0.1
|
|
101
|
+
done
|
|
102
|
+
return 1
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hold_source_lock() { # <source-id> <ready-file> <release-file>
|
|
106
|
+
local id=$1 ready=$2 release=$3 parent=$$
|
|
107
|
+
FM_HOME="$TMP_ROOT/lock-helper-home" bash -c '
|
|
108
|
+
. "$1/bin/fm-pr-lib.sh"
|
|
109
|
+
. "$1/bin/fm-wake-lib.sh"
|
|
110
|
+
. "$1/bin/fm-procevent-lib.sh"
|
|
111
|
+
fm_procevent_source_lock_acquire "$2" || exit 1
|
|
112
|
+
trap "fm_procevent_source_lock_release \"$2\"" EXIT
|
|
113
|
+
printf "ready\n" > "$3"
|
|
114
|
+
while [ ! -e "$4" ]; do
|
|
115
|
+
kill -0 "$5" 2>/dev/null || exit 0
|
|
116
|
+
sleep 0.02
|
|
117
|
+
done
|
|
118
|
+
' _ "$ROOT" "$id" "$ready" "$release" "$parent" &
|
|
119
|
+
HOLDER_PID=$!
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
hold_source_lock_then_handle() { # <home> <source-id> <sequence> <ready-file> <release-file>
|
|
123
|
+
local home=$1 id=$2 seq=$3 ready=$4 release=$5 parent=$$
|
|
124
|
+
FM_HOME="$home" bash -c '
|
|
125
|
+
. "$1/bin/fm-pr-lib.sh"
|
|
126
|
+
. "$1/bin/fm-wake-lib.sh"
|
|
127
|
+
. "$1/bin/fm-procevent-lib.sh"
|
|
128
|
+
fm_procevent_source_lock_acquire "$2" || exit 1
|
|
129
|
+
trap "fm_procevent_source_lock_release \"$2\"" EXIT
|
|
130
|
+
printf "ready\n" > "$4"
|
|
131
|
+
while [ ! -e "$5" ]; do
|
|
132
|
+
kill -0 "$6" 2>/dev/null || exit 1
|
|
133
|
+
sleep 0.02
|
|
134
|
+
done
|
|
135
|
+
fm_procevent_mark_handled "$3/state" "$2" "$7"
|
|
136
|
+
' _ "$ROOT" "$id" "$home" "$ready" "$release" "$parent" "$seq" &
|
|
137
|
+
HOLDER_PID=$!
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# --- inert with nothing configured ------------------------------------------
|
|
141
|
+
IDLE="$TMP_ROOT/idle"; new_home "$IDLE"
|
|
142
|
+
out=$(pe "$IDLE" list)
|
|
143
|
+
assert_contains "$out" "no sources registered" "an unconfigured home reports no sources"
|
|
144
|
+
out=$(pe "$IDLE" reconcile)
|
|
145
|
+
assert_contains "$out" "published=0 started=0" "reconcile is a no-op with nothing registered"
|
|
146
|
+
[ -z "$(ls -A "$IDLE/state" 2>/dev/null)" ] || fail "an unconfigured home generated state: $(ls -A "$IDLE/state")"
|
|
147
|
+
pass "no configured source means no generated state and no process"
|
|
148
|
+
|
|
149
|
+
sup=$(PATH="${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}" bash -c \
|
|
150
|
+
'. "$1/bin/fm-supervision-lib.sh"; fm_supervision_needed "$2" && echo yes || echo no' _ "$ROOT" "$IDLE/state")
|
|
151
|
+
assert_contains "$sup" no "an unconfigured home does not need supervision"
|
|
152
|
+
|
|
153
|
+
# --- a blocking source completes into exactly one normalized event ----------
|
|
154
|
+
H1="$TMP_ROOT/h1"; new_home "$H1"
|
|
155
|
+
TRIG="$TMP_ROOT/trigger-one"
|
|
156
|
+
out=$(pe_register "$H1" lavish src-one -- "$BLOCKER" "$TRIG" "payload one")
|
|
157
|
+
assert_contains "$out" "registered: src-one" "register records a source"
|
|
158
|
+
|
|
159
|
+
sup=$(PATH="${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}" bash -c \
|
|
160
|
+
'. "$1/bin/fm-supervision-lib.sh"; fm_supervision_needed "$2" && echo yes || echo no' _ "$ROOT" "$H1/state")
|
|
161
|
+
assert_contains "$sup" yes "a registered source needs supervision with no task metadata"
|
|
162
|
+
|
|
163
|
+
pe "$H1" reconcile >/dev/null
|
|
164
|
+
# Reconcile's replacement runner is detached, so ownership is recorded after
|
|
165
|
+
# reconcile has already returned. Wait for the claim itself: a duplicate start
|
|
166
|
+
# only has an owner to lose to once that claim exists.
|
|
167
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/src-one.claim" || fail "reconcile never claimed the registered source"
|
|
168
|
+
out=$(pe "$H1" start src-one)
|
|
169
|
+
assert_contains "$out" "already owned" "a duplicate start loses instead of running a second child"
|
|
170
|
+
|
|
171
|
+
: > "$TRIG"
|
|
172
|
+
wait_for "$H1/state/.wake-queue" || fail "no event was published after the source completed"
|
|
173
|
+
payload=$(wake_payloads "$H1")
|
|
174
|
+
assert_contains "$payload" "procevent lavish src-one 1" "completion publishes the committed result sequence"
|
|
175
|
+
assert_not_contains "$payload" "payload one" "source output never reaches the event line"
|
|
176
|
+
[ "$(printf '%s\n' "$payload" | grep -c .)" = 1 ] || fail "expected exactly one event, got: $payload"
|
|
177
|
+
pass "one blocking completion yields exactly one bounded normalized event"
|
|
178
|
+
|
|
179
|
+
RESULT=$(first_result "$H1" src-one || true)
|
|
180
|
+
[ -n "$RESULT" ] || fail "no durable result was captured"
|
|
181
|
+
mode=$(PATH="${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}" bash -c \
|
|
182
|
+
'. "$1/bin/fm-pr-lib.sh"; fm_pr_file_mode "$2"' _ "$ROOT" "$RESULT")
|
|
183
|
+
assert_contains "$mode" 600 "the captured result is private"
|
|
184
|
+
assert_grep 'payload one' "$RESULT" "the captured result holds the source output verbatim"
|
|
185
|
+
assert_grep 'lavish' "${RESULT%.result}.adapter" "the captured result retains its immutable adapter"
|
|
186
|
+
assert_absent "${RESULT%.result}.handled" "publication alone never marks a result handled"
|
|
187
|
+
|
|
188
|
+
# --- the public start boundary establishes generation group ownership -------
|
|
189
|
+
HPG="$TMP_ROOT/hpg"; new_home "$HPG"
|
|
190
|
+
DIRECT_TRIGGER="$TMP_ROOT/direct-trigger"
|
|
191
|
+
pe_register "$HPG" lavish direct-src -- "$BLOCKER" "$DIRECT_TRIGGER" "direct result" >/dev/null
|
|
192
|
+
pe "$HPG" start direct-src > "$TMP_ROOT/direct-start.out" &
|
|
193
|
+
direct_runner=$!
|
|
194
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/direct-src.claim" || fail "direct start never claimed its source"
|
|
195
|
+
direct_leader=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/direct-src.claim")
|
|
196
|
+
direct_group=$(ps -o pgid= -p "$direct_leader" 2>/dev/null | tr -d '[:space:]')
|
|
197
|
+
[ "$direct_group" = "$direct_leader" ] \
|
|
198
|
+
|| fail "direct start claimed before leading its process group: pid=$direct_leader pgid=$direct_group"
|
|
199
|
+
: > "$DIRECT_TRIGGER"
|
|
200
|
+
wait "$direct_runner" || fail "direct start failed after its source completed"
|
|
201
|
+
assert_contains "$(cat "$TMP_ROOT/direct-start.out")" "captured:" "direct start captures its result"
|
|
202
|
+
pass "public start owns the process group recorded by its claim"
|
|
203
|
+
|
|
204
|
+
SHARED_TRIGGER="$TMP_ROOT/shared-trigger"
|
|
205
|
+
SHARED_SIBLING="$TMP_ROOT/shared-sibling"
|
|
206
|
+
SHARED_LAUNCHER="$TMP_ROOT/shared-launcher.pl"
|
|
207
|
+
cat > "$SHARED_LAUNCHER" <<'PL'
|
|
208
|
+
use strict;
|
|
209
|
+
use warnings;
|
|
210
|
+
my ($sibling_file, @command) = @ARGV;
|
|
211
|
+
pipe(my $reader, my $writer) or exit 125;
|
|
212
|
+
defined(my $runner = fork) or exit 125;
|
|
213
|
+
if ($runner == 0) {
|
|
214
|
+
close $reader;
|
|
215
|
+
setpgrp(0, 0) or exit 125;
|
|
216
|
+
print {$writer} "ready\n";
|
|
217
|
+
close $writer;
|
|
218
|
+
exec @command;
|
|
219
|
+
exit 125;
|
|
220
|
+
}
|
|
221
|
+
close $writer;
|
|
222
|
+
<$reader>;
|
|
223
|
+
close $reader;
|
|
224
|
+
defined(my $sibling = fork) or exit 125;
|
|
225
|
+
if ($sibling == 0) {
|
|
226
|
+
setpgrp(0, $runner) or exit 125;
|
|
227
|
+
open(my $out, '>', $sibling_file) or exit 125;
|
|
228
|
+
print {$out} "$$\n";
|
|
229
|
+
close $out;
|
|
230
|
+
sleep 30;
|
|
231
|
+
exit 0;
|
|
232
|
+
}
|
|
233
|
+
waitpid($runner, 0);
|
|
234
|
+
waitpid($sibling, 0);
|
|
235
|
+
exit 0;
|
|
236
|
+
PL
|
|
237
|
+
pe_register "$HPG" lavish shared-src -- "$BLOCKER" "$SHARED_TRIGGER" "shared result" >/dev/null
|
|
238
|
+
FM_HOME="$HPG" perl "$SHARED_LAUNCHER" "$SHARED_SIBLING" \
|
|
239
|
+
"$ROOT/bin/fm-procevent.sh" start shared-src > "$TMP_ROOT/shared-start.out" &
|
|
240
|
+
shared_launcher=$!
|
|
241
|
+
wait_for "$SHARED_SIBLING" || fail "shared caller group never started its unrelated sibling"
|
|
242
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/shared-src.claim" || fail "shared-group start never claimed its source"
|
|
243
|
+
shared_sibling=$(cat "$SHARED_SIBLING")
|
|
244
|
+
pe "$HPG" retire shared-src >/dev/null
|
|
245
|
+
kill -0 "$shared_sibling" 2>/dev/null || fail "retirement signaled an unrelated caller-group process"
|
|
246
|
+
kill "$shared_sibling" 2>/dev/null || true
|
|
247
|
+
wait "$shared_launcher" || fail "shared caller-group fixture did not exit cleanly"
|
|
248
|
+
pass "public start never claims an inherited caller process group"
|
|
249
|
+
|
|
250
|
+
# --- an unhandled result remains eligible for re-announcement on restart ----
|
|
251
|
+
# A result is durable but nothing has ever acknowledged handling it. Every
|
|
252
|
+
# reconcile call - not just the first restart after a crash - must keep
|
|
253
|
+
# re-announcing it, because the only thing that stops re-announcement is an
|
|
254
|
+
# explicit handled acknowledgement, never a prior publication.
|
|
255
|
+
H2="$TMP_ROOT/h2"; new_home "$H2"
|
|
256
|
+
future_status=0
|
|
257
|
+
future_out=$(pe "$H2" handled src-cut 7 2>&1) || future_status=$?
|
|
258
|
+
[ "$future_status" -ne 0 ] || fail "handled accepted a generation that has not been captured"
|
|
259
|
+
assert_contains "$future_out" "cannot durably record handling" "premature acknowledgement is rejected through the public interface"
|
|
260
|
+
assert_absent "$H2/state/procevent-inbox/src-cut.7.handled" "premature acknowledgement creates no marker for the future generation"
|
|
261
|
+
mkdir -p "$H2/state/procevent-inbox"
|
|
262
|
+
printf 'stranded result\n' > "$H2/state/procevent-inbox/src-cut.7.result"
|
|
263
|
+
printf 'lavish\n' > "$H2/state/procevent-inbox/src-cut.7.adapter"
|
|
264
|
+
chmod 0600 "$H2/state/procevent-inbox/src-cut.7.result" "$H2/state/procevent-inbox/src-cut.7.adapter"
|
|
265
|
+
out=$(pe "$H2" reconcile)
|
|
266
|
+
assert_contains "$out" "published=1" "a durably captured but unhandled result is announced after restart"
|
|
267
|
+
assert_contains "$(wake_payloads "$H2")" "procevent lavish src-cut 7" "durable adapter identity survives without a registration"
|
|
268
|
+
assert_absent "$H2/state/procevent-inbox/src-cut.7.handled" "recovery alone never marks the recovered result handled"
|
|
269
|
+
mv "$H2/state/.wake-queue" "$H2/state/.wake-queue.drained-1"
|
|
270
|
+
out=$(pe "$H2" reconcile)
|
|
271
|
+
assert_contains "$out" "published=1" "an unhandled result is re-announced on every reconcile, not only the first"
|
|
272
|
+
assert_contains "$(wake_payloads "$H2")" "procevent lavish src-cut 7" "the repeat wake preserves its deduplication identity"
|
|
273
|
+
[ "$(count_results "$H2" src-cut)" = 1 ] || fail "repeat re-announcement created a second durable copy"
|
|
274
|
+
mv "$H2/state/.wake-queue" "$H2/state/.wake-queue.drained-2"
|
|
275
|
+
|
|
276
|
+
ack_out=$(pe "$H2" handled src-cut 7)
|
|
277
|
+
assert_contains "$ack_out" "handled: src-cut 7" "the owned handling interface newly authorizes the first acknowledgement"
|
|
278
|
+
assert_present "$H2/state/procevent-inbox/src-cut.7.handled" "acknowledgement durably records handling"
|
|
279
|
+
before=$(wake_payloads "$H2" | wc -l | tr -d ' ')
|
|
280
|
+
out=$(pe "$H2" reconcile)
|
|
281
|
+
assert_contains "$out" "published=0" "reconcile stops re-announcing once a result is durably handled"
|
|
282
|
+
[ "$(wake_payloads "$H2" | wc -l | tr -d ' ')" = "$before" ] || fail "a handled result was announced again"
|
|
283
|
+
|
|
284
|
+
repeat_out=$(pe "$H2" handled src-cut 7)
|
|
285
|
+
assert_contains "$repeat_out" "already-handled: src-cut 7" "repeated acknowledgement is safe and reports the repeat distinctly"
|
|
286
|
+
case "$repeat_out" in
|
|
287
|
+
handled:*) fail "a repeat acknowledgement re-authorized a second handled effect: $repeat_out" ;;
|
|
288
|
+
esac
|
|
289
|
+
pass "an unhandled result survives restart and repeat drains, and only explicit acknowledgement stops its re-announcement"
|
|
290
|
+
|
|
291
|
+
HRACE="$TMP_ROOT/hrace"; new_home "$HRACE"
|
|
292
|
+
mkdir -p "$HRACE/state/procevent-inbox"
|
|
293
|
+
printf 'racing result\n' > "$HRACE/state/procevent-inbox/racing-src.1.result"
|
|
294
|
+
printf 'lavish\n' > "$HRACE/state/procevent-inbox/racing-src.1.adapter"
|
|
295
|
+
chmod 0600 "$HRACE/state/procevent-inbox/racing-src.1.result" "$HRACE/state/procevent-inbox/racing-src.1.adapter"
|
|
296
|
+
RACE_PUBLISH_READY="$TMP_ROOT/race-publish-ready"
|
|
297
|
+
RACE_PUBLISH_RELEASE="$TMP_ROOT/race-publish-release"
|
|
298
|
+
RACE_RECONCILE_OUT="$TMP_ROOT/race-reconcile.out"
|
|
299
|
+
hold_source_lock_then_handle "$HRACE" racing-src 1 "$RACE_PUBLISH_READY" "$RACE_PUBLISH_RELEASE"
|
|
300
|
+
RACE_HANDLE_PID=$HOLDER_PID
|
|
301
|
+
wait_for "$RACE_PUBLISH_READY" || fail "publication race barrier did not acquire the source lock"
|
|
302
|
+
pe "$HRACE" reconcile > "$RACE_RECONCILE_OUT" &
|
|
303
|
+
RACE_RECONCILE_PID=$!
|
|
304
|
+
sleep 0.3
|
|
305
|
+
assert_absent "$HRACE/state/.wake-queue" "publication bypassed the source serialization boundary"
|
|
306
|
+
: > "$RACE_PUBLISH_RELEASE"
|
|
307
|
+
wait "$RACE_HANDLE_PID" || fail "publication race barrier could not record handling"
|
|
308
|
+
wait "$RACE_RECONCILE_PID" || fail "reconcile failed after the concurrent acknowledgement"
|
|
309
|
+
assert_contains "$(cat "$RACE_RECONCILE_OUT")" "published=0" "reconcile rechecks handling at the serialized publication boundary"
|
|
310
|
+
assert_present "$HRACE/state/procevent-inbox/racing-src.1.handled" "the concurrent acknowledgement remains durable"
|
|
311
|
+
assert_absent "$HRACE/state/.wake-queue" "an acknowledged result was appended after handling completed"
|
|
312
|
+
pass "publication cannot race a handled acknowledgement"
|
|
313
|
+
|
|
314
|
+
HPRIVATE="$TMP_ROOT/hprivate"; new_home "$HPRIVATE"
|
|
315
|
+
mkdir -p "$HPRIVATE/state/procevent-inbox"
|
|
316
|
+
printf 'private result\n' > "$HPRIVATE/state/procevent-inbox/private-src.1.result"
|
|
317
|
+
printf 'lavish\n' > "$HPRIVATE/state/procevent-inbox/private-src.1.adapter"
|
|
318
|
+
chmod 0600 "$HPRIVATE/state/procevent-inbox/private-src.1.result" "$HPRIVATE/state/procevent-inbox/private-src.1.adapter"
|
|
319
|
+
FAIL_CHMOD_BIN="$TMP_ROOT/fail-chmod-bin"
|
|
320
|
+
mkdir -p "$FAIL_CHMOD_BIN"
|
|
321
|
+
cat > "$FAIL_CHMOD_BIN/chmod" <<'SH'
|
|
322
|
+
#!/usr/bin/env bash
|
|
323
|
+
exit 1
|
|
324
|
+
SH
|
|
325
|
+
chmod +x "$FAIL_CHMOD_BIN/chmod"
|
|
326
|
+
private_status=0
|
|
327
|
+
private_out=$(PATH="$FAIL_CHMOD_BIN:$PATH" pe "$HPRIVATE" handled private-src 1 2>&1) || private_status=$?
|
|
328
|
+
[ "$private_status" -ne 0 ] || fail "handled succeeded when private mode enforcement failed"
|
|
329
|
+
assert_contains "$private_out" "cannot durably record handling" "mode enforcement failure is reported through the owned interface"
|
|
330
|
+
assert_absent "$HPRIVATE/state/procevent-inbox/private-src.1.handled" "failed mode enforcement left an authoritative marker"
|
|
331
|
+
private_out=$(umask 000; pe "$HPRIVATE" handled private-src 1)
|
|
332
|
+
assert_contains "$private_out" "handled: private-src 1" "handling succeeds after private mode enforcement recovers"
|
|
333
|
+
private_mode=$(PATH="${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}" bash -c \
|
|
334
|
+
'. "$1/bin/fm-pr-lib.sh"; fm_pr_file_mode "$2"' _ "$ROOT" "$HPRIVATE/state/procevent-inbox/private-src.1.handled")
|
|
335
|
+
assert_contains "$private_mode" 600 "the handled marker is private under a permissive caller umask"
|
|
336
|
+
pass "handled acknowledgement creation is private and fails safely"
|
|
337
|
+
|
|
338
|
+
# --- a terminal result retires its source, on the adapter's verdict alone ----
|
|
339
|
+
# The runner must carry no notion of its own about what "done" means for a
|
|
340
|
+
# source. It asks that source's adapter whether the captured result ends the
|
|
341
|
+
# source, and retires the registration only on that adapter's verdict. Two
|
|
342
|
+
# fixture adapters isolate exactly that decision - one that ends on any result,
|
|
343
|
+
# one with no terminal knowledge at all - so the observed behavior is proven to
|
|
344
|
+
# follow the adapter rather than any condition built into the runner.
|
|
345
|
+
ADAPTER_ROOT="$TMP_ROOT/adapter-root"
|
|
346
|
+
mkdir -p "$ADAPTER_ROOT/bin"
|
|
347
|
+
cat > "$ADAPTER_ROOT/bin/fm-procevent-endnow.sh" <<'SH'
|
|
348
|
+
#!/usr/bin/env bash
|
|
349
|
+
# Fixture adapter: every captured result ends this source.
|
|
350
|
+
case "${1-}" in
|
|
351
|
+
terminal) [ -f "${2-}" ] && exit 0 || exit 1 ;;
|
|
352
|
+
esac
|
|
353
|
+
exit 2
|
|
354
|
+
SH
|
|
355
|
+
cat > "$ADAPTER_ROOT/bin/fm-procevent-openended.sh" <<'SH'
|
|
356
|
+
#!/usr/bin/env bash
|
|
357
|
+
# Fixture adapter with no terminal knowledge at all: nothing ever ends it.
|
|
358
|
+
exit 2
|
|
359
|
+
SH
|
|
360
|
+
cat > "$ADAPTER_ROOT/bin/fm-procevent-applying.sh" <<'SH'
|
|
361
|
+
#!/usr/bin/env bash
|
|
362
|
+
case "${1-}" in
|
|
363
|
+
autohandle)
|
|
364
|
+
printf '%s %s\n' "$2" "$3" >> "$FM_HOME/state/applied"
|
|
365
|
+
"$FM_PROCEVENT_UNDER_TEST" handled "$2" "$3" >/dev/null
|
|
366
|
+
;;
|
|
367
|
+
*) exit 2 ;;
|
|
368
|
+
esac
|
|
369
|
+
SH
|
|
370
|
+
cat > "$ADAPTER_ROOT/bin/fm-procevent-selfann.sh" <<'SH'
|
|
371
|
+
#!/usr/bin/env bash
|
|
372
|
+
# Fixture adapter that declares a durable downstream announcement of its own.
|
|
373
|
+
# FM_HOME/state/selfann-fail makes its application fail so the fallback
|
|
374
|
+
# publication path stays provable.
|
|
375
|
+
case "${1-}" in
|
|
376
|
+
self-announcing) exit 0 ;;
|
|
377
|
+
autohandle)
|
|
378
|
+
[ ! -e "$FM_HOME/state/selfann-fail" ] || exit 1
|
|
379
|
+
printf '%s %s\n' "$2" "$3" >> "$FM_HOME/state/applied"
|
|
380
|
+
"$FM_PROCEVENT_UNDER_TEST" handled "$2" "$3" >/dev/null
|
|
381
|
+
;;
|
|
382
|
+
*) exit 2 ;;
|
|
383
|
+
esac
|
|
384
|
+
SH
|
|
385
|
+
chmod +x "$ADAPTER_ROOT/bin/fm-procevent-endnow.sh" "$ADAPTER_ROOT/bin/fm-procevent-openended.sh" \
|
|
386
|
+
"$ADAPTER_ROOT/bin/fm-procevent-applying.sh" "$ADAPTER_ROOT/bin/fm-procevent-selfann.sh"
|
|
387
|
+
|
|
388
|
+
pe_adapter() { # <home> <command>...: run the runner against the fixture adapters
|
|
389
|
+
local home=$1
|
|
390
|
+
shift
|
|
391
|
+
FM_ROOT_OVERRIDE="$ADAPTER_ROOT" FM_PROCEVENT_UNDER_TEST="$ROOT/bin/fm-procevent.sh" \
|
|
392
|
+
FM_HOME="$home" "$ROOT/bin/fm-procevent.sh" "$@"
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
HPUBLISH="$TMP_ROOT/hpublish"; new_home "$HPUBLISH"
|
|
396
|
+
PE_TRACKED+=("$HPUBLISH|publish-src")
|
|
397
|
+
pe_adapter "$HPUBLISH" register applying publish-src -- /bin/echo "apply after publish" >/dev/null
|
|
398
|
+
mkdir "$HPUBLISH/state/.wake-queue"
|
|
399
|
+
out=$(pe_adapter "$HPUBLISH" start publish-src 2>&1)
|
|
400
|
+
assert_contains "$out" "not-autohandled: publish-src" "failed publication did not suppress automatic application"
|
|
401
|
+
assert_absent "$HPUBLISH/state/applied" "a result was applied before its wake was durably published"
|
|
402
|
+
assert_absent "$HPUBLISH/state/procevent-inbox/publish-src.1.handled" "a result was acknowledged before its wake was durably published"
|
|
403
|
+
rmdir "$HPUBLISH/state/.wake-queue"
|
|
404
|
+
# This source's child returns instantly, so leaving it registered would have the
|
|
405
|
+
# recovery reconcile below start a detached poll that races every assertion after
|
|
406
|
+
# it for the source claim, the next sequence, and this home's applied record.
|
|
407
|
+
# Re-announcement is proven from the durable inbox alone and needs no
|
|
408
|
+
# registration, so retire it first - the same retire-before-reconcile discipline
|
|
409
|
+
# the blocker-backed sources rely on - and prove no competing poll was started.
|
|
410
|
+
pe_adapter "$HPUBLISH" retire publish-src >/dev/null
|
|
411
|
+
out=$(pe_adapter "$HPUBLISH" reconcile)
|
|
412
|
+
assert_contains "$out" "published=1" "the unpublished capture was not announced on later reconciliation"
|
|
413
|
+
assert_contains "$out" "started=0" "reconcile started an always-ready poll that races the recovery assertions"
|
|
414
|
+
assert_contains "$(wake_payloads "$HPUBLISH")" "procevent applying publish-src 1" "later reconciliation did not deliver the capture to a handler"
|
|
415
|
+
FM_HOME="$HPUBLISH" FM_PROCEVENT_UNDER_TEST="$ROOT/bin/fm-procevent.sh" \
|
|
416
|
+
"$ADAPTER_ROOT/bin/fm-procevent-applying.sh" autohandle publish-src 1 \
|
|
417
|
+
"$HPUBLISH/state/procevent-inbox/publish-src.1.result"
|
|
418
|
+
assert_grep 'publish-src 1' "$HPUBLISH/state/applied" "the handler could not apply the later announcement"
|
|
419
|
+
assert_present "$HPUBLISH/state/procevent-inbox/publish-src.1.handled" "the later handler application was not acknowledged"
|
|
420
|
+
pass "automatic application waits for durable publication and failed publication remains recoverable"
|
|
421
|
+
|
|
422
|
+
# A self-announcing adapter inverts that order on its own declaration: the
|
|
423
|
+
# runner applies first and publishes nothing for a capture the adapter fully
|
|
424
|
+
# applied and acknowledged, because the adapter's own durable downstream
|
|
425
|
+
# channel is the announcement. The declaration never silences a capture the
|
|
426
|
+
# adapter could NOT apply - that one still publishes for the handler.
|
|
427
|
+
HSELF="$TMP_ROOT/hself"; new_home "$HSELF"
|
|
428
|
+
PE_TRACKED+=("$HSELF|self-src")
|
|
429
|
+
pe_adapter "$HSELF" register selfann self-src -- /bin/echo "self announced" >/dev/null
|
|
430
|
+
out=$(pe_adapter "$HSELF" start self-src 2>&1)
|
|
431
|
+
assert_contains "$out" "autohandled: self-src" "the self-announcing adapter did not apply its own capture"
|
|
432
|
+
assert_not_contains "$out" "not-autohandled" "the applied capture was still reported as left for the handler"
|
|
433
|
+
assert_grep 'self-src 1' "$HSELF/state/applied" "the self-announcing capture was not applied"
|
|
434
|
+
assert_present "$HSELF/state/procevent-inbox/self-src.1.handled" "the self-announcing application was not acknowledged"
|
|
435
|
+
if [ -e "$HSELF/state/.wake-queue" ] && grep -q 'procevent selfann self-src 1' "$HSELF/state/.wake-queue"; then
|
|
436
|
+
fail "a fully autohandled self-announcing capture still published a duplicate check wake"
|
|
437
|
+
fi
|
|
438
|
+
# This self-announcing source's child returns instantly, so reconcile would
|
|
439
|
+
# restart it and that detached poll would race the failing-path start below for
|
|
440
|
+
# the source claim - non-deterministically stealing its sequence or the claim
|
|
441
|
+
# itself. Retire it before the re-announcement check so reconcile starts no
|
|
442
|
+
# competing poll, then re-register for the failing-path capture, the same
|
|
443
|
+
# retire-before-reconcile discipline the blocker-backed sources rely on.
|
|
444
|
+
pe_adapter "$HSELF" retire self-src >/dev/null
|
|
445
|
+
out=$(pe_adapter "$HSELF" reconcile)
|
|
446
|
+
assert_contains "$out" "published=0" "reconcile re-announced a capture its adapter already acknowledged"
|
|
447
|
+
assert_contains "$out" "started=0" "reconcile restarted an always-ready acknowledged source and raced the next start"
|
|
448
|
+
pe_adapter "$HSELF" register selfann self-src -- /bin/echo "self announced" >/dev/null
|
|
449
|
+
: > "$HSELF/state/selfann-fail"
|
|
450
|
+
out=$(pe_adapter "$HSELF" start self-src 2>&1)
|
|
451
|
+
assert_contains "$out" "not-autohandled: self-src" "a failed self-announcing application was reported as applied"
|
|
452
|
+
assert_absent "$HSELF/state/procevent-inbox/self-src.2.handled" "a failed self-announcing application was acknowledged anyway"
|
|
453
|
+
assert_contains "$(wake_payloads "$HSELF")" "procevent selfann self-src 2" \
|
|
454
|
+
"a capture the self-announcing adapter could not apply lost its check-wake announcement"
|
|
455
|
+
rm -f "$HSELF/state/selfann-fail"
|
|
456
|
+
pass "a self-announcing adapter applies quietly and still publishes what it could not apply"
|
|
457
|
+
|
|
458
|
+
HTERM="$TMP_ROOT/hterm"; new_home "$HTERM"
|
|
459
|
+
PE_TRACKED+=("$HTERM|ends-src")
|
|
460
|
+
pe_adapter "$HTERM" register endnow ends-src -- /bin/echo "terminal payload" >/dev/null
|
|
461
|
+
out=$(pe_adapter "$HTERM" start ends-src)
|
|
462
|
+
assert_contains "$out" "captured:" "a terminal result is still captured durably"
|
|
463
|
+
assert_contains "$out" "retired: ends-src" "the runner reports the adapter-driven retirement"
|
|
464
|
+
assert_absent "$HTERM/state/procevent/ends-src.source" "an adapter-classified terminal result retires its registration"
|
|
465
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/ends-src.claim" "terminal retirement releases this runner's own claim"
|
|
466
|
+
assert_contains "$(wake_payloads "$HTERM")" "procevent endnow ends-src 1" "the terminal result is still announced"
|
|
467
|
+
[ "$(count_results "$HTERM" ends-src)" = 1 ] || fail "terminal retirement lost or duplicated the captured result"
|
|
468
|
+
TERMINAL_RESULT=$(first_result "$HTERM" ends-src || true)
|
|
469
|
+
assert_grep 'terminal payload' "$TERMINAL_RESULT" "automatic retirement retains the captured output verbatim"
|
|
470
|
+
out=$(pe_adapter "$HTERM" reconcile)
|
|
471
|
+
assert_contains "$out" "started=0" "a retired terminal source is never restarted"
|
|
472
|
+
assert_contains "$out" "published=1" "an unhandled terminal result is still re-announced until acknowledged"
|
|
473
|
+
[ "$(count_results "$HTERM" ends-src)" = 1 ] || fail "a retired terminal source ran its poll again"
|
|
474
|
+
out=$(pe_adapter "$HTERM" retire ends-src)
|
|
475
|
+
assert_contains "$out" "retired: ends-src" "explicit retirement stays supported and idempotent after automatic retirement"
|
|
476
|
+
ack_out=$(pe_adapter "$HTERM" handled ends-src 1)
|
|
477
|
+
assert_contains "$ack_out" "handled: ends-src 1" "a terminal result is acknowledged through the owned interface"
|
|
478
|
+
out=$(pe_adapter "$HTERM" reconcile)
|
|
479
|
+
assert_contains "$out" "published=0" "an acknowledged terminal result stops being re-announced"
|
|
480
|
+
pass "an adapter-classified terminal result is captured once, announced, and retires its source automatically"
|
|
481
|
+
|
|
482
|
+
HOPEN="$TMP_ROOT/hopen"; new_home "$HOPEN"
|
|
483
|
+
PE_TRACKED+=("$HOPEN|open-src")
|
|
484
|
+
pe_adapter "$HOPEN" register openended open-src -- /bin/echo "open payload" >/dev/null
|
|
485
|
+
out=$(pe_adapter "$HOPEN" start open-src)
|
|
486
|
+
assert_contains "$out" "captured:" "a result from an adapter with no terminal verdict is captured"
|
|
487
|
+
assert_not_contains "$out" "retired:" "an adapter with no terminal verdict never retires its source"
|
|
488
|
+
assert_present "$HOPEN/state/procevent/open-src.source" "a source with no terminal verdict stays armed"
|
|
489
|
+
pe_adapter "$HOPEN" retire open-src >/dev/null
|
|
490
|
+
pass "a source stays armed unless its own adapter classifies the result terminal"
|
|
491
|
+
|
|
492
|
+
HREPLACE="$TMP_ROOT/hreplace"; new_home "$HREPLACE"
|
|
493
|
+
PE_TRACKED+=("$HREPLACE|replace-src")
|
|
494
|
+
OLD_TRIGGER="$TMP_ROOT/replace-old-trigger"
|
|
495
|
+
pe_adapter "$HREPLACE" register endnow replace-src -- "$BLOCKER" "$OLD_TRIGGER" "old terminal payload" >/dev/null
|
|
496
|
+
pe_adapter "$HREPLACE" start replace-src > "$TMP_ROOT/replace-old.out" 2>&1 &
|
|
497
|
+
replace_old_pid=$!
|
|
498
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/replace-src.claim" || fail "the old registration was never claimed"
|
|
499
|
+
pe_adapter "$HREPLACE" register openended replace-src -- /bin/echo "replacement payload" >/dev/null
|
|
500
|
+
touch "$OLD_TRIGGER"
|
|
501
|
+
wait "$replace_old_pid" || fail "the old terminal runner failed"
|
|
502
|
+
assert_contains "$(cat "$TMP_ROOT/replace-old.out")" "cannot retire terminal source" \
|
|
503
|
+
"an old runner refuses to retire a replacement registration"
|
|
504
|
+
assert_present "$HREPLACE/state/procevent/replace-src.source" \
|
|
505
|
+
"a replacement registration survives the old runner's terminal result"
|
|
506
|
+
assert_contains "$(cat "$HREPLACE/state/procevent/replace-src.source")" "adapter=openended" \
|
|
507
|
+
"the surviving registration is the replacement generation"
|
|
508
|
+
out=$(pe_adapter "$HREPLACE" start replace-src)
|
|
509
|
+
assert_contains "$out" "captured:" "the replacement registration remains independently runnable"
|
|
510
|
+
[ "$(count_results "$HREPLACE" replace-src)" = 2 ] \
|
|
511
|
+
|| fail "the replacement generation did not capture its own result"
|
|
512
|
+
pe_adapter "$HREPLACE" retire replace-src >/dev/null
|
|
513
|
+
pass "terminal retirement preserves and releases a concurrently replaced registration"
|
|
514
|
+
|
|
515
|
+
HRETFAIL="$TMP_ROOT/hretfail"; new_home "$HRETFAIL"
|
|
516
|
+
PE_TRACKED+=("$HRETFAIL|retire-fail-src")
|
|
517
|
+
FAIL_RM_BIN=$(fm_fakebin "$TMP_ROOT/retire-fail-bin")
|
|
518
|
+
REAL_RM=$(command -v rm)
|
|
519
|
+
export REAL_RM
|
|
520
|
+
cat > "$FAIL_RM_BIN/rm" <<'SH'
|
|
521
|
+
#!/usr/bin/env bash
|
|
522
|
+
for arg in "$@"; do
|
|
523
|
+
case "$arg" in */retire-fail-src.source) exit 1 ;; esac
|
|
524
|
+
done
|
|
525
|
+
exec "$REAL_RM" "$@"
|
|
526
|
+
SH
|
|
527
|
+
chmod +x "$FAIL_RM_BIN/rm"
|
|
528
|
+
pe_adapter "$HRETFAIL" register endnow retire-fail-src -- /bin/echo "one terminal payload" >/dev/null
|
|
529
|
+
out=$(PATH="$FAIL_RM_BIN:$PATH" pe_adapter "$HRETFAIL" start retire-fail-src 2>&1)
|
|
530
|
+
assert_contains "$out" "cannot retire terminal source" "a failed registration removal is reported"
|
|
531
|
+
assert_present "$HRETFAIL/state/procevent/retire-fail-src.source" \
|
|
532
|
+
"failed retirement preserves the exact registration"
|
|
533
|
+
assert_present "$FM_PROCEVENT_CLAIM_ROOT/retire-fail-src.claim" \
|
|
534
|
+
"failed retirement preserves its terminal ownership claim"
|
|
535
|
+
out=$(PATH="$FAIL_RM_BIN:$PATH" pe_adapter "$HRETFAIL" reconcile)
|
|
536
|
+
assert_contains "$out" "started=0" "failed terminal retirement never restarts the poll"
|
|
537
|
+
[ "$(count_results "$HRETFAIL" retire-fail-src)" = 1 ] \
|
|
538
|
+
|| fail "failed retirement allowed recurring terminal capture"
|
|
539
|
+
pe_adapter "$HRETFAIL" reconcile >/dev/null
|
|
540
|
+
assert_absent "$HRETFAIL/state/procevent/retire-fail-src.source" \
|
|
541
|
+
"repeated retirement removes the same registration once removal recovers"
|
|
542
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/retire-fail-src.claim" \
|
|
543
|
+
"the claim releases only after that registration is removed"
|
|
544
|
+
[ "$(count_results "$HRETFAIL" retire-fail-src)" = 1 ] \
|
|
545
|
+
|| fail "retirement recovery reran the terminal source"
|
|
546
|
+
pass "failed terminal retirement is fail-closed and idempotently recoverable"
|
|
547
|
+
|
|
548
|
+
# --- end-user-aligned regression: one Send & End, one captured result -------
|
|
549
|
+
# The dogfood defect: a real armed Lavish source received one human `Send & End`
|
|
550
|
+
# action, and the runner captured four results - the human's real feedback, then
|
|
551
|
+
# recurring empty ended sessions - because it kept restarting a source whose own
|
|
552
|
+
# adapter already knew the session had ended. Driven through the adapter's own
|
|
553
|
+
# arm command against a stand-in for the published poll shape, so registration,
|
|
554
|
+
# the runner, capture, publication, and retirement all run for real.
|
|
555
|
+
HLT="$TMP_ROOT/hlt"; new_home "$HLT"
|
|
556
|
+
LAVISH_BIN=$(fm_fakebin "$TMP_ROOT/lavish-stub")
|
|
557
|
+
LAVISH_POLL_COUNT="$TMP_ROOT/lavish-poll-count"
|
|
558
|
+
export LAVISH_POLL_COUNT
|
|
559
|
+
cat > "$LAVISH_BIN/lavish-axi" <<'SH'
|
|
560
|
+
#!/usr/bin/env bash
|
|
561
|
+
# Stand-in for `lavish-axi poll <file>` around a human `Send & End`: the final
|
|
562
|
+
# feedback is delivered exactly once carrying session_ended, and every later
|
|
563
|
+
# poll returns an empty ended session immediately.
|
|
564
|
+
n=$(cat "$LAVISH_POLL_COUNT" 2>/dev/null || echo 0)
|
|
565
|
+
n=$((n + 1))
|
|
566
|
+
printf '%s\n' "$n" > "$LAVISH_POLL_COUNT"
|
|
567
|
+
if [ "$n" = 1 ]; then
|
|
568
|
+
printf 'session:\n file: /review.html\n status: feedback\n session_ended: true\n ended_by: user\nfeedback[1]{text}:\n ship it\n'
|
|
569
|
+
else
|
|
570
|
+
printf 'session:\n file: /review.html\n status: ended\n ended_by: user\n'
|
|
571
|
+
fi
|
|
572
|
+
SH
|
|
573
|
+
chmod +x "$LAVISH_BIN/lavish-axi"
|
|
574
|
+
REVIEW_ART="$TMP_ROOT/review.html"
|
|
575
|
+
printf '<h1>review</h1>\n' > "$REVIEW_ART"
|
|
576
|
+
lavish_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$REVIEW_ART")
|
|
577
|
+
PE_TRACKED+=("$HLT|$lavish_id")
|
|
578
|
+
PATH="$LAVISH_BIN:$PATH" FM_HOME="$HLT" "$ROOT/bin/fm-procevent-lavish.sh" arm "$REVIEW_ART" >/dev/null
|
|
579
|
+
for _ in $(seq 1 6); do
|
|
580
|
+
PATH="$LAVISH_BIN:$PATH" pe "$HLT" reconcile >/dev/null
|
|
581
|
+
sleep 0.3
|
|
582
|
+
done
|
|
583
|
+
[ "$(cat "$LAVISH_POLL_COUNT")" = 1 ] \
|
|
584
|
+
|| fail "an ended review kept being polled: $(cat "$LAVISH_POLL_COUNT") polls for one Send & End"
|
|
585
|
+
[ "$(count_results "$HLT" "$lavish_id")" = 1 ] \
|
|
586
|
+
|| fail "one Send & End produced $(count_results "$HLT" "$lavish_id") captured results"
|
|
587
|
+
[ "$(wake_payloads "$HLT" | sort -u | grep -c .)" = 1 ] \
|
|
588
|
+
|| fail "one Send & End produced more than one distinct event: $(wake_payloads "$HLT" | sort -u)"
|
|
589
|
+
assert_contains "$(wake_payloads "$HLT")" "procevent lavish $lavish_id 1" "the human's final feedback is announced"
|
|
590
|
+
assert_absent "$HLT/state/procevent/$lavish_id.source" "the ended review source retires automatically"
|
|
591
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/$lavish_id.claim" "the ended review releases its owned claim"
|
|
592
|
+
LAVISH_RESULT=$(first_result "$HLT" "$lavish_id" || true)
|
|
593
|
+
assert_grep 'ship it' "$LAVISH_RESULT" "automatic retirement retains the human's final feedback"
|
|
594
|
+
out=$(PATH="$LAVISH_BIN:$PATH" FM_HOME="$HLT" "$ROOT/bin/fm-procevent-lavish.sh" retire "$REVIEW_ART")
|
|
595
|
+
assert_contains "$out" "retired: $lavish_id" "explicit adapter retirement stays supported after automatic retirement"
|
|
596
|
+
pass "one Send & End yields exactly one captured result, automatic retirement, and no recurring poll"
|
|
597
|
+
|
|
598
|
+
# --- end-user-aligned regression: an empty board close is not news ------------
|
|
599
|
+
# The captain's report: closing a review surface he had said nothing on still
|
|
600
|
+
# put a wake in his chat whose entire content was that nothing happened. The
|
|
601
|
+
# adapter now answers the runner's silence seam for exactly that shape, so the
|
|
602
|
+
# result is captured and recorded handled without ever being announced. Driven
|
|
603
|
+
# through the adapter's own arm command and the real runner, so registration,
|
|
604
|
+
# capture, the silence verdict, and retirement all run for real.
|
|
605
|
+
HEMPTY="$TMP_ROOT/hempty"; new_home "$HEMPTY"
|
|
606
|
+
EMPTY_BIN=$(fm_fakebin "$TMP_ROOT/lavish-empty-stub")
|
|
607
|
+
cat > "$EMPTY_BIN/lavish-axi" <<'SH'
|
|
608
|
+
#!/usr/bin/env bash
|
|
609
|
+
# Stand-in for `lavish-axi poll <file>` when the captain closes a board he said
|
|
610
|
+
# nothing on: an ended session carrying no queued content at all.
|
|
611
|
+
printf 'session:\n file: /quiet.html\n status: ended\n ended_by: user\n'
|
|
612
|
+
SH
|
|
613
|
+
chmod +x "$EMPTY_BIN/lavish-axi"
|
|
614
|
+
QUIET_ART="$TMP_ROOT/quiet-board.html"
|
|
615
|
+
printf '<h1>quiet</h1>\n' > "$QUIET_ART"
|
|
616
|
+
quiet_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$QUIET_ART")
|
|
617
|
+
PE_TRACKED+=("$HEMPTY|$quiet_id")
|
|
618
|
+
PATH="$EMPTY_BIN:$PATH" FM_HOME="$HEMPTY" \
|
|
619
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$QUIET_ART" >/dev/null
|
|
620
|
+
quiet_out=$(PATH="$EMPTY_BIN:$PATH" pe "$HEMPTY" start "$quiet_id" 2>&1)
|
|
621
|
+
assert_not_contains "$quiet_out" "not-autohandled" \
|
|
622
|
+
"a durably silenced result was reported as still unacknowledged"
|
|
623
|
+
# The handled marker is written at exactly the point the wake would otherwise
|
|
624
|
+
# have been appended, so waiting on it - rather than on a fixed sleep - is what
|
|
625
|
+
# makes "no wake" a real observation instead of a race the test won by being
|
|
626
|
+
# early.
|
|
627
|
+
QUIET_HANDLED="$HEMPTY/state/procevent-inbox/$quiet_id.1.handled"
|
|
628
|
+
for _ in $(seq 1 100); do
|
|
629
|
+
[ -f "$QUIET_HANDLED" ] && break
|
|
630
|
+
sleep 0.1
|
|
631
|
+
done
|
|
632
|
+
[ -f "$QUIET_HANDLED" ] \
|
|
633
|
+
|| fail "a silenced result was not durably recorded handled, so a later reconcile would announce it"
|
|
634
|
+
[ "$(count_results "$HEMPTY" "$quiet_id")" = 1 ] \
|
|
635
|
+
|| fail "an empty board close captured $(count_results "$HEMPTY" "$quiet_id") results instead of one"
|
|
636
|
+
[ -z "$(wake_payloads "$HEMPTY")" ] \
|
|
637
|
+
|| fail "an empty board close woke the captain: $(wake_payloads "$HEMPTY")"
|
|
638
|
+
# Re-announcement is exactly what the handled marker exists to stop, so the
|
|
639
|
+
# silence has to survive the reconcile that would otherwise republish it.
|
|
640
|
+
PATH="$EMPTY_BIN:$PATH" pe "$HEMPTY" reconcile >/dev/null
|
|
641
|
+
sleep 0.3
|
|
642
|
+
[ -z "$(wake_payloads "$HEMPTY")" ] \
|
|
643
|
+
|| fail "a later reconcile re-announced a silenced empty board close: $(wake_payloads "$HEMPTY")"
|
|
644
|
+
assert_absent "$HEMPTY/state/procevent/$quiet_id.source" \
|
|
645
|
+
"an empty board close still retires its ended source"
|
|
646
|
+
pass "an empty board close is captured and recorded handled without ever waking the captain"
|
|
647
|
+
|
|
648
|
+
# The other half of the same contract, on the same real path: a close that
|
|
649
|
+
# carries what the captain actually said must still reach him. Same runner, same
|
|
650
|
+
# adapter, one different response shape.
|
|
651
|
+
HANSWER="$TMP_ROOT/hanswer"; new_home "$HANSWER"
|
|
652
|
+
ANSWER_BIN=$(fm_fakebin "$TMP_ROOT/lavish-answer-stub")
|
|
653
|
+
cat > "$ANSWER_BIN/lavish-axi" <<'SH'
|
|
654
|
+
#!/usr/bin/env bash
|
|
655
|
+
# Stand-in for `lavish-axi poll <file>` on a real `Send & End`: the captain's
|
|
656
|
+
# own choice, delivered with session_ended.
|
|
657
|
+
printf 'session:\n file: /answered.html\n status: feedback\n session_ended: true\n ended_by: user\nprompts[1]{tag,text,prompt}:\n "choice","Option B","Context data: {\\"question\\":\\"noop-check-routing\\",\\"answer\\":\\"b\\"}"\n'
|
|
658
|
+
SH
|
|
659
|
+
chmod +x "$ANSWER_BIN/lavish-axi"
|
|
660
|
+
ANSWER_ART="$TMP_ROOT/answered-board.html"
|
|
661
|
+
printf '<h1>answered</h1>\n' > "$ANSWER_ART"
|
|
662
|
+
answer_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$ANSWER_ART")
|
|
663
|
+
PE_TRACKED+=("$HANSWER|$answer_id")
|
|
664
|
+
PATH="$ANSWER_BIN:$PATH" FM_HOME="$HANSWER" \
|
|
665
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$ANSWER_ART" >/dev/null
|
|
666
|
+
PATH="$ANSWER_BIN:$PATH" pe "$HANSWER" reconcile >/dev/null
|
|
667
|
+
wait_for "$HANSWER/state/.wake-queue" \
|
|
668
|
+
|| fail "a board close carrying the captain's real answer produced no wake"
|
|
669
|
+
assert_contains "$(wake_payloads "$HANSWER")" "procevent lavish $answer_id 1" \
|
|
670
|
+
"a real board answer still reaches the captain"
|
|
671
|
+
[ ! -f "$HANSWER/state/procevent-inbox/$answer_id.1.handled" ] \
|
|
672
|
+
|| fail "a real board answer was recorded handled without ever being handled"
|
|
673
|
+
pass "a board close carrying the captain's real answer is still announced"
|
|
674
|
+
|
|
675
|
+
# --- end-user-aligned regression: a transient poll interruption is not news ---
|
|
676
|
+
# The dogfood defect: a live board listener can answer with exactly
|
|
677
|
+
# error: Lavish Editor poll response was interrupted
|
|
678
|
+
# code: SERVER_ERROR
|
|
679
|
+
# while the board's marks remain available. Firstmate registered raw poll output,
|
|
680
|
+
# so the generic runner captured that transient response and woke the whole fleet
|
|
681
|
+
# over what is really an internal retry. Every scenario below runs through the
|
|
682
|
+
# adapter's own arm command and the real runner, so registration, capture, and
|
|
683
|
+
# publication are exercised for real.
|
|
684
|
+
LAVISH_SCRIPTED_BIN=$(fm_fakebin "$TMP_ROOT/lavish-scripted-stub")
|
|
685
|
+
cat > "$LAVISH_SCRIPTED_BIN/lavish-axi" <<'SH'
|
|
686
|
+
#!/usr/bin/env bash
|
|
687
|
+
# Stand-in for `lavish-axi poll <file>`, scripted per scenario: LAVISH_SCRIPT
|
|
688
|
+
# names the response for each successive poll, one word per poll, and its last
|
|
689
|
+
# word repeats forever. `interrupt` is the exact transient response the server
|
|
690
|
+
# returns while the board's marks stay available.
|
|
691
|
+
n=$(cat "$LAVISH_COUNT" 2>/dev/null || echo 0)
|
|
692
|
+
n=$((n + 1))
|
|
693
|
+
printf '%s\n' "$n" > "$LAVISH_COUNT"
|
|
694
|
+
read -r -a plan <<< "$LAVISH_SCRIPT"
|
|
695
|
+
i=$((n - 1))
|
|
696
|
+
[ "$i" -ge "${#plan[@]}" ] && i=$((${#plan[@]} - 1))
|
|
697
|
+
case "${plan[$i]}" in
|
|
698
|
+
interrupt)
|
|
699
|
+
printf 'error: Lavish Editor poll response was interrupted\ncode: SERVER_ERROR\n'; exit 1 ;;
|
|
700
|
+
near-interrupt)
|
|
701
|
+
printf 'error: Lavish Editor poll response was interrupted \ncode: SERVER_ERROR\n'; exit 1 ;;
|
|
702
|
+
other-server-error)
|
|
703
|
+
printf 'error: Lavish Editor session store is unavailable\ncode: SERVER_ERROR\n'; exit 1 ;;
|
|
704
|
+
feedback)
|
|
705
|
+
printf 'session:\n file: /board.html\n status: feedback\n session_ended: true\n ended_by: user\nfeedback[1]{text}:\n ship it\n' ;;
|
|
706
|
+
stream)
|
|
707
|
+
printf 'x%.0s' {1..4096}
|
|
708
|
+
printf 'ready\n' > "$LAVISH_STREAM_READY"
|
|
709
|
+
while [ ! -e "$LAVISH_STREAM_RELEASE" ]; do sleep 0.05; done
|
|
710
|
+
printf '\n' ;;
|
|
711
|
+
esac
|
|
712
|
+
SH
|
|
713
|
+
chmod +x "$LAVISH_SCRIPTED_BIN/lavish-axi"
|
|
714
|
+
export LAVISH_COUNT LAVISH_SCRIPT
|
|
715
|
+
# A bounded test override keeps the retry policy's real bound under test without
|
|
716
|
+
# making the suite wait out the production delay.
|
|
717
|
+
export FM_LAVISH_POLL_RETRY_DELAY=0
|
|
718
|
+
|
|
719
|
+
# Two interruptions, then the captain's real feedback: the retries are silent and
|
|
720
|
+
# only the feedback becomes a captured result and a check wake.
|
|
721
|
+
HRETRY="$TMP_ROOT/hretry"; new_home "$HRETRY"
|
|
722
|
+
RETRY_ART="$TMP_ROOT/retry-board.html"
|
|
723
|
+
printf '<h1>retry</h1>\n' > "$RETRY_ART"
|
|
724
|
+
retry_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$RETRY_ART")
|
|
725
|
+
PE_TRACKED+=("$HRETRY|$retry_id")
|
|
726
|
+
LAVISH_COUNT="$TMP_ROOT/retry-count"; LAVISH_SCRIPT="interrupt interrupt feedback"
|
|
727
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HRETRY" \
|
|
728
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$RETRY_ART" >/dev/null
|
|
729
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" pe "$HRETRY" reconcile >/dev/null
|
|
730
|
+
wait_for "$HRETRY/state/.wake-queue" || fail "feedback after interrupted polls produced no wake"
|
|
731
|
+
[ "$(cat "$LAVISH_COUNT")" = 3 ] \
|
|
732
|
+
|| fail "the interrupted listener was polled $(cat "$LAVISH_COUNT") times, not the two quiet retries plus the delivering poll"
|
|
733
|
+
[ "$(count_results "$HRETRY" "$retry_id")" = 1 ] \
|
|
734
|
+
|| fail "a retried interruption produced $(count_results "$HRETRY" "$retry_id") captured results instead of one"
|
|
735
|
+
[ "$(wake_payloads "$HRETRY" | sort -u | grep -c .)" = 1 ] \
|
|
736
|
+
|| fail "a retried interruption woke the fleet: $(wake_payloads "$HRETRY" | sort -u)"
|
|
737
|
+
assert_contains "$(wake_payloads "$HRETRY")" "procevent lavish $retry_id 1" \
|
|
738
|
+
"feedback arriving after quiet retries is captured and announced"
|
|
739
|
+
assert_grep 'ship it' "$(first_result "$HRETRY" "$retry_id")" \
|
|
740
|
+
"the announced result is the captain's feedback, not the interruption"
|
|
741
|
+
pass "a transient Lavish poll interruption is retried quietly and never announced"
|
|
742
|
+
|
|
743
|
+
# Exhaustion is news: after the bounded retries the same exact response is
|
|
744
|
+
# captured and announced normally rather than being swallowed forever.
|
|
745
|
+
HEXH="$TMP_ROOT/hexh"; new_home "$HEXH"
|
|
746
|
+
EXH_ART="$TMP_ROOT/exhaust-board.html"
|
|
747
|
+
printf '<h1>exhaust</h1>\n' > "$EXH_ART"
|
|
748
|
+
exh_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$EXH_ART")
|
|
749
|
+
PE_TRACKED+=("$HEXH|$exh_id")
|
|
750
|
+
LAVISH_COUNT="$TMP_ROOT/exhaust-count"; LAVISH_SCRIPT="interrupt"
|
|
751
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HEXH" \
|
|
752
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$EXH_ART" >/dev/null
|
|
753
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" pe "$HEXH" start "$exh_id" >/dev/null
|
|
754
|
+
[ "$(cat "$LAVISH_COUNT")" = 13 ] \
|
|
755
|
+
|| fail "the retry bound polled $(cat "$LAVISH_COUNT") times, not the first poll plus 12 bounded retries"
|
|
756
|
+
[ "$(count_results "$HEXH" "$exh_id")" = 1 ] \
|
|
757
|
+
|| fail "exhaustion produced $(count_results "$HEXH" "$exh_id") captured results instead of one"
|
|
758
|
+
assert_contains "$(wake_payloads "$HEXH")" "procevent lavish $exh_id 1" \
|
|
759
|
+
"the interruption that survives the bound is announced normally"
|
|
760
|
+
assert_grep 'poll response was interrupted' "$(first_result "$HEXH" "$exh_id")" \
|
|
761
|
+
"the announced result is the exact interruption the server returned"
|
|
762
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HEXH" \
|
|
763
|
+
"$ROOT/bin/fm-procevent-lavish.sh" retire "$EXH_ART" >/dev/null
|
|
764
|
+
pass "an interruption that outlives the bounded retries is captured and announced"
|
|
765
|
+
|
|
766
|
+
# A different SERVER_ERROR is a genuine error, never a retry: no fail-open drift
|
|
767
|
+
# from the one exact transient response this adapter owns.
|
|
768
|
+
HOTHER="$TMP_ROOT/hother"; new_home "$HOTHER"
|
|
769
|
+
OTHER_ART="$TMP_ROOT/other-board.html"
|
|
770
|
+
printf '<h1>other</h1>\n' > "$OTHER_ART"
|
|
771
|
+
other_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$OTHER_ART")
|
|
772
|
+
PE_TRACKED+=("$HOTHER|$other_id")
|
|
773
|
+
LAVISH_COUNT="$TMP_ROOT/other-count"; LAVISH_SCRIPT="other-server-error"
|
|
774
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HOTHER" \
|
|
775
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$OTHER_ART" >/dev/null
|
|
776
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" pe "$HOTHER" start "$other_id" >/dev/null
|
|
777
|
+
[ "$(cat "$LAVISH_COUNT")" = 1 ] \
|
|
778
|
+
|| fail "an unrelated SERVER_ERROR was retried $(cat "$LAVISH_COUNT") times instead of surfacing at once"
|
|
779
|
+
assert_contains "$(wake_payloads "$HOTHER")" "procevent lavish $other_id 1" \
|
|
780
|
+
"an unrelated SERVER_ERROR is captured and announced immediately"
|
|
781
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HOTHER" \
|
|
782
|
+
"$ROOT/bin/fm-procevent-lavish.sh" retire "$OTHER_ART" >/dev/null
|
|
783
|
+
pass "only the exact interruption is retried; an unrelated SERVER_ERROR still surfaces"
|
|
784
|
+
unset FM_LAVISH_POLL_RETRY_DELAY
|
|
785
|
+
|
|
786
|
+
# A whitespace variant is not the exact transient response and must surface on
|
|
787
|
+
# the first poll instead of drifting into the quiet retry policy.
|
|
788
|
+
HNEAR="$TMP_ROOT/hnear"; new_home "$HNEAR"
|
|
789
|
+
NEAR_ART="$TMP_ROOT/near-board.html"
|
|
790
|
+
printf '<h1>near</h1>\n' > "$NEAR_ART"
|
|
791
|
+
near_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$NEAR_ART")
|
|
792
|
+
PE_TRACKED+=("$HNEAR|$near_id")
|
|
793
|
+
LAVISH_COUNT="$TMP_ROOT/near-count"; LAVISH_SCRIPT="near-interrupt feedback"
|
|
794
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HNEAR" FM_LAVISH_POLL_RETRY_DELAY=0 \
|
|
795
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$NEAR_ART" >/dev/null
|
|
796
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HNEAR" pe "$HNEAR" start "$near_id" >/dev/null
|
|
797
|
+
[ "$(cat "$LAVISH_COUNT")" = 1 ] \
|
|
798
|
+
|| fail "a near-match interruption was retried instead of surfacing on its first poll"
|
|
799
|
+
assert_contains "$(wake_payloads "$HNEAR")" "procevent lavish $near_id 1" \
|
|
800
|
+
"a whitespace variant of the interruption is captured and announced immediately"
|
|
801
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HNEAR" \
|
|
802
|
+
"$ROOT/bin/fm-procevent-lavish.sh" retire "$NEAR_ART" >/dev/null
|
|
803
|
+
pass "only the literal two-line interruption enters the quiet retry policy"
|
|
804
|
+
|
|
805
|
+
# The public arm boundary refuses invalid retry intervals before it publishes a
|
|
806
|
+
# source registration, rather than arming a listener that can only fail later.
|
|
807
|
+
HINVALID="$TMP_ROOT/hinvalid"; new_home "$HINVALID"
|
|
808
|
+
INVALID_ART="$TMP_ROOT/invalid-delay-board.html"
|
|
809
|
+
printf '<h1>invalid delay</h1>\n' > "$INVALID_ART"
|
|
810
|
+
invalid_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$INVALID_ART")
|
|
811
|
+
for invalid_delay in 61 invalid; do
|
|
812
|
+
invalid_status=0
|
|
813
|
+
invalid_out=$(PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HINVALID" \
|
|
814
|
+
FM_LAVISH_POLL_RETRY_DELAY="$invalid_delay" \
|
|
815
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$INVALID_ART" 2>&1) || invalid_status=$?
|
|
816
|
+
[ "$invalid_status" -ne 0 ] \
|
|
817
|
+
|| fail "arm accepted invalid retry delay: $invalid_delay"
|
|
818
|
+
assert_contains "$invalid_out" "must be whole seconds from 0 to 60" \
|
|
819
|
+
"arm explains the rejected retry delay"
|
|
820
|
+
assert_absent "$HINVALID/state/procevent/$invalid_id.source" \
|
|
821
|
+
"arm publishes no source registration for an invalid retry delay"
|
|
822
|
+
done
|
|
823
|
+
pass "arm rejects malformed and out-of-range retry delays before registration"
|
|
824
|
+
|
|
825
|
+
# Shell-safe cleanup must preserve a valid TMPDIR containing an apostrophe.
|
|
826
|
+
QUOTED_TMPDIR="$TMP_ROOT/poll's-stage"
|
|
827
|
+
mkdir -p "$QUOTED_TMPDIR"
|
|
828
|
+
LAVISH_COUNT="$TMP_ROOT/quoted-count"; LAVISH_SCRIPT="feedback"
|
|
829
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" TMPDIR="$QUOTED_TMPDIR" \
|
|
830
|
+
"$ROOT/bin/fm-procevent-lavish.sh" poll "$NEAR_ART" >/dev/null
|
|
831
|
+
quoted_staged=("$QUOTED_TMPDIR"/fm-lavish-poll.*)
|
|
832
|
+
[ ! -e "${quoted_staged[0]}" ] \
|
|
833
|
+
|| fail "poll left its staged response behind in an apostrophe-containing TMPDIR"
|
|
834
|
+
pass "poll cleanup safely handles an apostrophe-containing TMPDIR"
|
|
835
|
+
|
|
836
|
+
HSTREAM="$TMP_ROOT/hstream"; new_home "$HSTREAM"
|
|
837
|
+
STREAM_ART="$TMP_ROOT/stream-board.html"
|
|
838
|
+
STREAM_TMPDIR="$TMP_ROOT/stream-stage"
|
|
839
|
+
LAVISH_STREAM_READY="$TMP_ROOT/stream-ready"
|
|
840
|
+
LAVISH_STREAM_RELEASE="$TMP_ROOT/stream-release"
|
|
841
|
+
mkdir -p "$STREAM_TMPDIR"
|
|
842
|
+
printf '<h1>stream</h1>\n' > "$STREAM_ART"
|
|
843
|
+
stream_id=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$STREAM_ART")
|
|
844
|
+
PE_TRACKED+=("$HSTREAM|$stream_id")
|
|
845
|
+
LAVISH_COUNT="$TMP_ROOT/stream-count"; LAVISH_SCRIPT="stream"
|
|
846
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HSTREAM" \
|
|
847
|
+
"$ROOT/bin/fm-procevent-lavish.sh" arm "$STREAM_ART" >/dev/null
|
|
848
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" TMPDIR="$STREAM_TMPDIR" \
|
|
849
|
+
LAVISH_STREAM_READY="$LAVISH_STREAM_READY" LAVISH_STREAM_RELEASE="$LAVISH_STREAM_RELEASE" \
|
|
850
|
+
FM_PROCEVENT_MAX_OUTPUT_BYTES=100 pe "$HSTREAM" reconcile >/dev/null
|
|
851
|
+
wait_for "$LAVISH_STREAM_READY" || fail "streaming poll did not start"
|
|
852
|
+
stream_staged=("$STREAM_TMPDIR"/fm-lavish-poll.*)
|
|
853
|
+
[ -e "${stream_staged[0]}" ] || fail "streaming poll created no classifier staging file"
|
|
854
|
+
[ "$(wc -c < "${stream_staged[0]}" | tr -d ' ')" -le 100 ] \
|
|
855
|
+
|| fail "streaming poll exceeded its bounded classifier staging"
|
|
856
|
+
: > "$LAVISH_STREAM_RELEASE"
|
|
857
|
+
wait_for "$HSTREAM/state/.wake-queue" || fail "streaming poll produced no wake"
|
|
858
|
+
stream_result=$(first_result "$HSTREAM" "$stream_id" || true)
|
|
859
|
+
[ "$(wc -c < "$stream_result" | tr -d ' ')" -le 100 ] \
|
|
860
|
+
|| fail "streaming poll bypassed the runner output bound"
|
|
861
|
+
PATH="$LAVISH_SCRIPTED_BIN:$PATH" FM_HOME="$HSTREAM" \
|
|
862
|
+
"$ROOT/bin/fm-procevent-lavish.sh" retire "$STREAM_ART" >/dev/null
|
|
863
|
+
pass "Lavish classification staging stays bounded while nonmatches stream"
|
|
864
|
+
|
|
865
|
+
# --- end-user-aligned regression: the exact drain-before-handling restart cut
|
|
866
|
+
# Reproduces the confirmed defect through the public interface end to end: a
|
|
867
|
+
# real blocking source completes, its result is captured and published, the
|
|
868
|
+
# wake is drained without any handling, a replacement session's reconcile must
|
|
869
|
+
# resurface the exact same source and sequence, and only the owned handling
|
|
870
|
+
# interface may retire it - safely and without ever authorizing a paired
|
|
871
|
+
# effect a second time.
|
|
872
|
+
HW="$TMP_ROOT/hw"; new_home "$HW"
|
|
873
|
+
TRIGW="$TMP_ROOT/trigger-restart-cut"
|
|
874
|
+
pe_register "$HW" lavish restart-cut-src -- "$BLOCKER" "$TRIGW" "restart cut payload" >/dev/null
|
|
875
|
+
pe "$HW" reconcile >/dev/null
|
|
876
|
+
sleep 0.5
|
|
877
|
+
: > "$TRIGW"
|
|
878
|
+
wait_for "$HW/state/.wake-queue" || fail "the restart-cut source published no event"
|
|
879
|
+
assert_contains "$(wake_payloads "$HW")" "procevent lavish restart-cut-src 1" \
|
|
880
|
+
"capture and publish reaches the wake queue before any handling"
|
|
881
|
+
|
|
882
|
+
# Retire the registration now that the source has completed and captured its
|
|
883
|
+
# one result. The fixture's trigger file persists on disk, so a still-armed
|
|
884
|
+
# registration would let every further reconcile call restart the blocker and
|
|
885
|
+
# capture a fresh generation; retiring leaves only the durable inbox and wake
|
|
886
|
+
# state under test, matching the exact restart cut - the source side is done,
|
|
887
|
+
# only the handling side is still open.
|
|
888
|
+
pe "$HW" retire restart-cut-src >/dev/null
|
|
889
|
+
|
|
890
|
+
# Drain the wake without handling it: the end-user experience of a session
|
|
891
|
+
# reading the wake queue at turn end without yet acting on this specific line.
|
|
892
|
+
mv "$HW/state/.wake-queue" "$HW/state/.wake-queue.drained-unhandled"
|
|
893
|
+
[ -z "$(wake_payloads "$HW")" ] || fail "the wake queue was not actually drained"
|
|
894
|
+
|
|
895
|
+
# Simulate a replacement Firstmate session: reconcile runs cold, as it would on
|
|
896
|
+
# a fresh process with no memory of the prior turn.
|
|
897
|
+
out=$(pe "$HW" reconcile)
|
|
898
|
+
assert_contains "$out" "published=1" \
|
|
899
|
+
"a replacement session's reconcile resurfaces a drained-but-unhandled result"
|
|
900
|
+
assert_contains "$(wake_payloads "$HW")" "procevent lavish restart-cut-src 1" \
|
|
901
|
+
"the exact same captured source and sequence resurfaces, never a substitute"
|
|
902
|
+
|
|
903
|
+
# Acknowledge handling through the owned interface.
|
|
904
|
+
ack_out=$(pe "$HW" handled restart-cut-src 1)
|
|
905
|
+
assert_contains "$ack_out" "handled: restart-cut-src 1" \
|
|
906
|
+
"the first acknowledgement newly authorizes the paired effect"
|
|
907
|
+
|
|
908
|
+
mv "$HW/state/.wake-queue" "$HW/state/.wake-queue.post-handle"
|
|
909
|
+
out=$(pe "$HW" reconcile)
|
|
910
|
+
assert_contains "$out" "published=0" \
|
|
911
|
+
"a later reconcile does not resurface a result once it is durably handled"
|
|
912
|
+
[ -z "$(wake_payloads "$HW")" ] || fail "a handled result was announced again: $(wake_payloads "$HW")"
|
|
913
|
+
|
|
914
|
+
auth_count=0
|
|
915
|
+
for _ in 1 2 3; do
|
|
916
|
+
repeat_ack=$(pe "$HW" handled restart-cut-src 1)
|
|
917
|
+
assert_contains "$repeat_ack" "already-handled: restart-cut-src 1" "repeated acknowledgement stays safe and idempotent"
|
|
918
|
+
case "$repeat_ack" in handled:*) auth_count=$((auth_count + 1)) ;; esac
|
|
919
|
+
done
|
|
920
|
+
[ "$auth_count" -eq 0 ] || fail "a result already durably handled was authorized again: count=$auth_count"
|
|
921
|
+
pass "a drained-but-unhandled result survives a replacement session and is retired only by explicit handling, never twice"
|
|
922
|
+
|
|
923
|
+
HP="$TMP_ROOT/hp"; new_home "$HP"
|
|
924
|
+
mkdir -p "$HP/state/procevent-inbox"
|
|
925
|
+
for seq in 10 2 1; do
|
|
926
|
+
printf '%s\n' "$seq" > "$HP/state/procevent-inbox/ordered-src.$seq.result"
|
|
927
|
+
printf 'lavish\n' > "$HP/state/procevent-inbox/ordered-src.$seq.adapter"
|
|
928
|
+
chmod 0600 "$HP/state/procevent-inbox/ordered-src.$seq.result" "$HP/state/procevent-inbox/ordered-src.$seq.adapter"
|
|
929
|
+
done
|
|
930
|
+
pending=$(bash -c '. "$1/bin/fm-procevent-lib.sh"; fm_procevent_pending "$2"' _ "$ROOT" "$HP/state")
|
|
931
|
+
expected=$(printf '%s\n' \
|
|
932
|
+
"$HP/state/procevent-inbox/ordered-src.1.result" \
|
|
933
|
+
"$HP/state/procevent-inbox/ordered-src.2.result" \
|
|
934
|
+
"$HP/state/procevent-inbox/ordered-src.10.result")
|
|
935
|
+
[ "$pending" = "$expected" ] || fail "pending results were not emitted in numeric sequence order: $pending"
|
|
936
|
+
pe "$HP" reconcile >/dev/null
|
|
937
|
+
deduped=$(FM_HOME="$HP" bash -c '
|
|
938
|
+
. "$1/bin/fm-wake-lib.sh"
|
|
939
|
+
fm_wake_print_deduped "$2/state/.wake-queue" | awk -F "\t" "{print \$5}"
|
|
940
|
+
' _ "$ROOT" "$HP")
|
|
941
|
+
expected=$(printf '%s\n' \
|
|
942
|
+
'check: procevent lavish ordered-src 1' \
|
|
943
|
+
'check: procevent lavish ordered-src 2' \
|
|
944
|
+
'check: procevent lavish ordered-src 10')
|
|
945
|
+
[ "$deduped" = "$expected" ] || fail "distinct result generations were coalesced or reordered: $deduped"
|
|
946
|
+
pass "pending results preserve numeric order and distinct wake identity"
|
|
947
|
+
|
|
948
|
+
# --- two homes cannot both own one canonical source -------------------------
|
|
949
|
+
HA="$TMP_ROOT/ha"; HB="$TMP_ROOT/hb"; new_home "$HA"; new_home "$HB"
|
|
950
|
+
TRIG2="$TMP_ROOT/trigger-two"
|
|
951
|
+
pe_register "$HA" lavish shared-src -- "$BLOCKER" "$TRIG2" "shared" >/dev/null
|
|
952
|
+
pe_register "$HB" lavish shared-src -- "$BLOCKER" "$TRIG2" "shared" >/dev/null
|
|
953
|
+
pe "$HA" reconcile >/dev/null
|
|
954
|
+
sleep 0.5
|
|
955
|
+
out=$(pe "$HB" start shared-src)
|
|
956
|
+
assert_contains "$out" "already owned" "a second home cannot own a source another home already owns"
|
|
957
|
+
[ -z "$(wake_payloads "$HB")" ] || fail "the losing home published an event"
|
|
958
|
+
pass "one owner per canonical source across homes"
|
|
959
|
+
|
|
960
|
+
# A source whose child never completes must not survive retirement. This is the
|
|
961
|
+
# leak that reparented four orphaned runners: the fixture directory was removed
|
|
962
|
+
# while the detached child kept blocking, with nothing left to reap it.
|
|
963
|
+
runner_pid=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/shared-src.claim" 2>/dev/null)
|
|
964
|
+
[ -n "$runner_pid" ] || fail "no runner pid recorded for the blocked source"
|
|
965
|
+
kill -0 "$runner_pid" 2>/dev/null || fail "the blocked runner is not live before retirement"
|
|
966
|
+
pe "$HA" retire shared-src >/dev/null
|
|
967
|
+
for _ in $(seq 1 40); do kill -0 "$runner_pid" 2>/dev/null || break; sleep 0.1; done
|
|
968
|
+
kill -0 "$runner_pid" 2>/dev/null && fail "retire left the blocked runner alive"
|
|
969
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/shared-src.claim" "retire releases the claim"
|
|
970
|
+
pass "retiring a never-completing source stops its runner and its blocked child"
|
|
971
|
+
|
|
972
|
+
# reconcile must also stop a runner whose registration was removed out from under it.
|
|
973
|
+
TRIG4="$TMP_ROOT/trigger-four"
|
|
974
|
+
HZ="$TMP_ROOT/hz"; new_home "$HZ"
|
|
975
|
+
pe_register "$HZ" lavish orphan-src -- "$BLOCKER" "$TRIG4" "orphan" >/dev/null
|
|
976
|
+
pe "$HZ" reconcile >/dev/null
|
|
977
|
+
sleep 0.5
|
|
978
|
+
orphan_pid=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/orphan-src.claim" 2>/dev/null)
|
|
979
|
+
if [ -z "$orphan_pid" ] || ! kill -0 "$orphan_pid" 2>/dev/null; then
|
|
980
|
+
fail "orphan fixture runner did not start"
|
|
981
|
+
fi
|
|
982
|
+
rm -f "$HZ/state/procevent/orphan-src.source"
|
|
983
|
+
out=$(pe "$HZ" reconcile)
|
|
984
|
+
assert_contains "$out" "stopped=1" "reconcile stops a runner whose registration was removed"
|
|
985
|
+
for _ in $(seq 1 40); do kill -0 "$orphan_pid" 2>/dev/null || break; sleep 0.1; done
|
|
986
|
+
kill -0 "$orphan_pid" 2>/dev/null && fail "reconcile left an orphaned runner alive"
|
|
987
|
+
pass "reconcile reaps a runner whose source registration is gone"
|
|
988
|
+
|
|
989
|
+
# --- a stale claim is reclaimable, a live one is not ------------------------
|
|
990
|
+
CLAIM="$FM_PROCEVENT_CLAIM_ROOT/stale-src.claim"
|
|
991
|
+
mkdir -p "$FM_PROCEVENT_CLAIM_ROOT"
|
|
992
|
+
HC="$TMP_ROOT/hc"; new_home "$HC"
|
|
993
|
+
printf '%s\n%s\nstale-token\nstale-identity\n' "$HC" "999999" > "$CLAIM"
|
|
994
|
+
chmod 0600 "$CLAIM"
|
|
995
|
+
pe_register "$HC" lavish stale-src -- /bin/echo recovered >/dev/null
|
|
996
|
+
printf 'partial sensitive output\n' > "$HC/state/procevent/.stale-src.stale-token.output"
|
|
997
|
+
chmod 0600 "$HC/state/procevent/.stale-src.stale-token.output"
|
|
998
|
+
out=$(pe "$HC" start stale-src)
|
|
999
|
+
assert_contains "$out" "captured:" "a claim whose runner is gone is reclaimable"
|
|
1000
|
+
assert_absent "$CLAIM" "the replacement claim generation is released after completion"
|
|
1001
|
+
assert_absent "$HC/state/procevent/.stale-src.stale-token.output" "stale claim recovery removes its abandoned staging generation"
|
|
1002
|
+
pass "stale-owner recovery removes abandoned output without displacing a live owner"
|
|
1003
|
+
|
|
1004
|
+
HC_OLD="$TMP_ROOT/hc-old"; new_home "$HC_OLD"
|
|
1005
|
+
HC_NEW="$TMP_ROOT/hc-new"; new_home "$HC_NEW"
|
|
1006
|
+
HC_OLD_STATE="$TMP_ROOT/hc-old-state"
|
|
1007
|
+
mkdir -p "$HC_OLD_STATE/procevent"
|
|
1008
|
+
printf '%s\n%s\ncross-home-token\ncross-home-identity\n%s\n' \
|
|
1009
|
+
"$HC_OLD" "999999" "$HC_OLD_STATE/procevent" > "$FM_PROCEVENT_CLAIM_ROOT/cross-home-src.claim"
|
|
1010
|
+
chmod 0600 "$FM_PROCEVENT_CLAIM_ROOT/cross-home-src.claim"
|
|
1011
|
+
printf 'partial cross-home output\n' > "$HC_OLD_STATE/procevent/.cross-home-src.cross-home-token.output"
|
|
1012
|
+
chmod 0600 "$HC_OLD_STATE/procevent/.cross-home-src.cross-home-token.output"
|
|
1013
|
+
pe_register "$HC_NEW" lavish cross-home-src -- /bin/echo recovered >/dev/null
|
|
1014
|
+
out=$(pe "$HC_NEW" start cross-home-src)
|
|
1015
|
+
assert_contains "$out" "captured:" "a second home can replace a stale source owner"
|
|
1016
|
+
assert_absent "$HC_OLD_STATE/procevent/.cross-home-src.cross-home-token.output" "cross-home reclaim removes the old generation's recorded staging file"
|
|
1017
|
+
pass "cross-home stale recovery removes abandoned output from the old state directory"
|
|
1018
|
+
|
|
1019
|
+
HR="$TMP_ROOT/hr"; new_home "$HR"
|
|
1020
|
+
RACE_TRIGGER="$TMP_ROOT/race-trigger"
|
|
1021
|
+
RACE_LOG="$TMP_ROOT/race-executions"
|
|
1022
|
+
RACE_BLOCKER="$TMP_ROOT/race-blocker.sh"
|
|
1023
|
+
cat > "$RACE_BLOCKER" <<'SH'
|
|
1024
|
+
#!/usr/bin/env bash
|
|
1025
|
+
printf 'started\n' >> "$1"
|
|
1026
|
+
while [ ! -e "$2" ]; do sleep 0.05; done
|
|
1027
|
+
printf 'race result\n'
|
|
1028
|
+
SH
|
|
1029
|
+
chmod +x "$RACE_BLOCKER"
|
|
1030
|
+
pe_register "$HR" lavish race-src -- "$RACE_BLOCKER" "$RACE_LOG" "$RACE_TRIGGER" >/dev/null
|
|
1031
|
+
printf '%s\n%s\nold-token\nold-identity\n' "$TMP_ROOT/gone-home" 999999 > "$FM_PROCEVENT_CLAIM_ROOT/race-src.claim"
|
|
1032
|
+
chmod 0600 "$FM_PROCEVENT_CLAIM_ROOT/race-src.claim"
|
|
1033
|
+
race_pids=()
|
|
1034
|
+
for _ in $(seq 1 24); do
|
|
1035
|
+
pe "$HR" start race-src >/dev/null &
|
|
1036
|
+
race_pids+=("$!")
|
|
1037
|
+
done
|
|
1038
|
+
wait_for "$RACE_LOG" || fail "no contender acquired the stale claim"
|
|
1039
|
+
sleep 0.5
|
|
1040
|
+
[ "$(wc -l < "$RACE_LOG" | tr -d ' ')" = 1 ] || fail "stale-claim race started more than one runner"
|
|
1041
|
+
: > "$RACE_TRIGGER"
|
|
1042
|
+
for race_pid in "${race_pids[@]}"; do wait "$race_pid" 2>/dev/null || true; done
|
|
1043
|
+
pass "concurrent stale-claim replacement starts exactly one runner"
|
|
1044
|
+
|
|
1045
|
+
# --- a crashed runner leader must not make its live child group look stale ---
|
|
1046
|
+
# The runner is its own process group leader, so SIGKILL on the leader alone
|
|
1047
|
+
# leaves the blocking source child running in that group. Classifying the
|
|
1048
|
+
# missing leader as stale would release ownership and start a second poller
|
|
1049
|
+
# against one canonical source, which for a destructive source means two
|
|
1050
|
+
# concurrent long polls racing on the same session. The surviving group must be
|
|
1051
|
+
# stopped before ownership can move.
|
|
1052
|
+
HG="$TMP_ROOT/hg"; new_home "$HG"
|
|
1053
|
+
ORPHAN_TRIGGER="$TMP_ROOT/orphan-trigger"
|
|
1054
|
+
ORPHAN_LOG="$TMP_ROOT/orphan-executions"
|
|
1055
|
+
ORPHAN_GROUP="$TMP_ROOT/orphan-group"
|
|
1056
|
+
ORPHAN_OVERLAP="$TMP_ROOT/orphan-overlap"
|
|
1057
|
+
ORPHAN_BLOCKER="$TMP_ROOT/orphan-blocker.sh"
|
|
1058
|
+
cat > "$ORPHAN_BLOCKER" <<'SH'
|
|
1059
|
+
#!/usr/bin/env bash
|
|
1060
|
+
printf 'started\n' >> "$1"
|
|
1061
|
+
if [ -s "$3" ]; then
|
|
1062
|
+
IFS= read -r old_group < "$3"
|
|
1063
|
+
if kill -0 "-$old_group" 2>/dev/null; then
|
|
1064
|
+
printf 'overlap\n' > "$4"
|
|
1065
|
+
fi
|
|
1066
|
+
fi
|
|
1067
|
+
while [ ! -e "$2" ]; do sleep 0.05; done
|
|
1068
|
+
printf 'orphan result\n'
|
|
1069
|
+
SH
|
|
1070
|
+
chmod +x "$ORPHAN_BLOCKER"
|
|
1071
|
+
pe_register "$HG" lavish orphan-src -- \
|
|
1072
|
+
"$ORPHAN_BLOCKER" "$ORPHAN_LOG" "$ORPHAN_TRIGGER" "$ORPHAN_GROUP" "$ORPHAN_OVERLAP" >/dev/null
|
|
1073
|
+
pe "$HG" reconcile >/dev/null
|
|
1074
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/orphan-src.claim" || fail "leader-crash fixture never claimed its source"
|
|
1075
|
+
wait_for "$ORPHAN_LOG" || fail "leader-crash fixture source never started"
|
|
1076
|
+
orphan_leader=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/orphan-src.claim")
|
|
1077
|
+
case "$orphan_leader" in ''|*[!0-9]*) fail "could not read the runner leader pid: $orphan_leader" ;; esac
|
|
1078
|
+
printf '%s\n' "$orphan_leader" > "$ORPHAN_GROUP"
|
|
1079
|
+
|
|
1080
|
+
kill -KILL "$orphan_leader" 2>/dev/null || fail "could not kill the runner leader"
|
|
1081
|
+
for _ in $(seq 1 50); do kill -0 "$orphan_leader" 2>/dev/null || break; sleep 0.1; done
|
|
1082
|
+
kill -0 "$orphan_leader" 2>/dev/null && fail "the runner leader survived SIGKILL"
|
|
1083
|
+
kill -0 -"$orphan_leader" 2>/dev/null || fail "fixture invalid: the owned child group did not survive the leader"
|
|
1084
|
+
|
|
1085
|
+
orphan_out=$(pe "$HG" reconcile)
|
|
1086
|
+
kill -0 -"$orphan_leader" 2>/dev/null \
|
|
1087
|
+
&& fail "reconcile left the crashed generation's process group alive: $orphan_out"
|
|
1088
|
+
sleep 0.5
|
|
1089
|
+
assert_absent "$ORPHAN_OVERLAP" "no replacement source starts while the crashed generation remains alive"
|
|
1090
|
+
case "$orphan_out" in
|
|
1091
|
+
*"started=1"*)
|
|
1092
|
+
# The replacement is detached: it records its own claim and execs its source
|
|
1093
|
+
# after reconcile has already returned, so both effects must be waited for
|
|
1094
|
+
# rather than snapshotted behind the settle window above.
|
|
1095
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/orphan-src.claim" \
|
|
1096
|
+
|| fail "a replacement runner started without recording its own claim"
|
|
1097
|
+
wait_for_lines "$ORPHAN_LOG" 2 \
|
|
1098
|
+
|| fail "the replacement runner never started its source: $(cat "$ORPHAN_LOG")"
|
|
1099
|
+
[ "$(wc -l < "$ORPHAN_LOG" | tr -d ' ')" = 2 ] \
|
|
1100
|
+
|| fail "reconcile did not start exactly one replacement source: $(cat "$ORPHAN_LOG")"
|
|
1101
|
+
;;
|
|
1102
|
+
*"started=0"*)
|
|
1103
|
+
[ -e "$FM_PROCEVENT_CLAIM_ROOT/orphan-src.claim" ] \
|
|
1104
|
+
|| fail "refusing to replace must preserve the claim for retry: $orphan_out"
|
|
1105
|
+
[ "$(wc -l < "$ORPHAN_LOG" | tr -d ' ')" = 1 ] \
|
|
1106
|
+
|| fail "reconcile started a source while refusing replacement: $(cat "$ORPHAN_LOG")"
|
|
1107
|
+
;;
|
|
1108
|
+
*) fail "unexpected reconcile result for a crashed leader: $orphan_out" ;;
|
|
1109
|
+
esac
|
|
1110
|
+
: > "$ORPHAN_TRIGGER"
|
|
1111
|
+
pe "$HG" retire orphan-src >/dev/null
|
|
1112
|
+
pass "a crashed runner leader never lets a live owned group be reclaimed as stale"
|
|
1113
|
+
|
|
1114
|
+
# Counterexample: a genuinely dead generation - no leader and no surviving
|
|
1115
|
+
# group - must still be reclaimable, or crash recovery would deadlock.
|
|
1116
|
+
HG2="$TMP_ROOT/hg2"; new_home "$HG2"
|
|
1117
|
+
DEAD_TRIGGER="$TMP_ROOT/dead-gen-trigger"
|
|
1118
|
+
DEAD_LOG="$TMP_ROOT/dead-gen-executions"
|
|
1119
|
+
pe_register "$HG2" lavish dead-gen-src -- "$RACE_BLOCKER" "$DEAD_LOG" "$DEAD_TRIGGER" >/dev/null
|
|
1120
|
+
printf '%s\n%s\ndead-token\ndead-identity\n%s\n' "$HG2" 999999 "$HG2/state/procevent" \
|
|
1121
|
+
> "$FM_PROCEVENT_CLAIM_ROOT/dead-gen-src.claim"
|
|
1122
|
+
chmod 0600 "$FM_PROCEVENT_CLAIM_ROOT/dead-gen-src.claim"
|
|
1123
|
+
dead_out=$(pe "$HG2" reconcile)
|
|
1124
|
+
assert_contains "$dead_out" "started=1" "a generation with no leader and no group is still reclaimable"
|
|
1125
|
+
wait_for "$DEAD_LOG" || fail "the replacement source never started for a truly dead generation"
|
|
1126
|
+
: > "$DEAD_TRIGGER"
|
|
1127
|
+
pe "$HG2" retire dead-gen-src >/dev/null
|
|
1128
|
+
pass "a truly dead generation with no surviving group is still safely reclaimed"
|
|
1129
|
+
|
|
1130
|
+
HJ="$TMP_ROOT/hj"; new_home "$HJ"
|
|
1131
|
+
TORN_TRIGGER="$TMP_ROOT/torn-trigger"
|
|
1132
|
+
pe_register "$HJ" lavish torn-src -- "$BLOCKER" "$TORN_TRIGGER" "torn" >/dev/null
|
|
1133
|
+
pe "$HJ" reconcile >/dev/null
|
|
1134
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/torn-src.claim" || fail "torn-read fixture runner did not claim its source"
|
|
1135
|
+
awk 'NR == 3 { print "replacement-token"; next } { print }' \
|
|
1136
|
+
"$FM_PROCEVENT_CLAIM_ROOT/torn-src.claim" > "$TMP_ROOT/torn-next.claim"
|
|
1137
|
+
chmod 0600 "$TMP_ROOT/torn-next.claim"
|
|
1138
|
+
TORN_READY="$TMP_ROOT/torn-lock-ready"
|
|
1139
|
+
TORN_RELEASE="$TMP_ROOT/torn-lock-release"
|
|
1140
|
+
hold_source_lock torn-src "$TORN_READY" "$TORN_RELEASE"
|
|
1141
|
+
torn_holder_pid=$HOLDER_PID
|
|
1142
|
+
wait_for "$TORN_READY" || fail "could not hold the torn-read source boundary"
|
|
1143
|
+
pe "$HJ" list > "$TMP_ROOT/torn-list.out" &
|
|
1144
|
+
torn_list_pid=$!
|
|
1145
|
+
sleep 0.2
|
|
1146
|
+
kill -0 "$torn_list_pid" 2>/dev/null || fail "claim reader escaped the source boundary during replacement"
|
|
1147
|
+
mv "$TMP_ROOT/torn-next.claim" "$FM_PROCEVENT_CLAIM_ROOT/torn-src.claim"
|
|
1148
|
+
: > "$TORN_RELEASE"
|
|
1149
|
+
wait "$torn_list_pid" || fail "claim reader failed after serialized replacement"
|
|
1150
|
+
wait "$torn_holder_pid" || fail "torn-read source boundary holder failed"
|
|
1151
|
+
assert_contains "$(cat "$TMP_ROOT/torn-list.out")" "live" "claim reader observes one coherent replacement generation"
|
|
1152
|
+
pe "$HJ" retire torn-src >/dev/null
|
|
1153
|
+
pass "claim replacement cannot produce a torn ownership snapshot"
|
|
1154
|
+
|
|
1155
|
+
HK="$TMP_ROOT/hk"; new_home "$HK"
|
|
1156
|
+
START_LOG="$TMP_ROOT/retire-start-executions"
|
|
1157
|
+
START_BLOCKER="$TMP_ROOT/retire-start-blocker.sh"
|
|
1158
|
+
cat > "$START_BLOCKER" <<'SH'
|
|
1159
|
+
#!/usr/bin/env bash
|
|
1160
|
+
printf 'started\n' >> "$1"
|
|
1161
|
+
sleep 30
|
|
1162
|
+
SH
|
|
1163
|
+
chmod +x "$START_BLOCKER"
|
|
1164
|
+
pe_register "$HK" lavish retire-start-src -- "$START_BLOCKER" "$START_LOG" >/dev/null
|
|
1165
|
+
START_READY="$TMP_ROOT/retire-start-lock-ready"
|
|
1166
|
+
START_RELEASE="$TMP_ROOT/retire-start-lock-release"
|
|
1167
|
+
hold_source_lock retire-start-src "$START_READY" "$START_RELEASE"
|
|
1168
|
+
retire_start_holder_pid=$HOLDER_PID
|
|
1169
|
+
wait_for "$START_READY" || fail "could not hold the retire-start source boundary"
|
|
1170
|
+
pe "$HK" start retire-start-src > "$TMP_ROOT/retire-start.out" 2>&1 &
|
|
1171
|
+
retire_start_pid=$!
|
|
1172
|
+
sleep 0.2
|
|
1173
|
+
kill -0 "$retire_start_pid" 2>/dev/null || fail "start did not wait for the source lifecycle boundary"
|
|
1174
|
+
rm -f "$HK/state/procevent/retire-start-src.source"
|
|
1175
|
+
: > "$START_RELEASE"
|
|
1176
|
+
wait "$retire_start_pid" 2>/dev/null || true
|
|
1177
|
+
wait "$retire_start_holder_pid" || fail "retire-start source boundary holder failed"
|
|
1178
|
+
assert_absent "$START_LOG" "a start queued before retirement must revalidate the registration"
|
|
1179
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/retire-start-src.claim" "retirement cannot leave a late claim"
|
|
1180
|
+
pass "retirement and start share one serialized lifecycle boundary"
|
|
1181
|
+
|
|
1182
|
+
HI="$TMP_ROOT/hi"; new_home "$HI"
|
|
1183
|
+
pe_register "$HI" lavish reused-src -- /bin/true >/dev/null
|
|
1184
|
+
sleep 60 &
|
|
1185
|
+
innocent_pid=$!
|
|
1186
|
+
printf '%s\n%s\nreused-token\nnot-the-live-process-identity\n' \
|
|
1187
|
+
"$HI" "$innocent_pid" > "$FM_PROCEVENT_CLAIM_ROOT/reused-src.claim"
|
|
1188
|
+
chmod 0600 "$FM_PROCEVENT_CLAIM_ROOT/reused-src.claim"
|
|
1189
|
+
pe "$HI" retire reused-src >/dev/null
|
|
1190
|
+
kill -0 "$innocent_pid" 2>/dev/null || fail "retirement signaled a PID whose identity did not match the claim"
|
|
1191
|
+
kill "$innocent_pid" 2>/dev/null || true
|
|
1192
|
+
wait "$innocent_pid" 2>/dev/null || true
|
|
1193
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/reused-src.claim" "retirement releases the exact reused-pid claim"
|
|
1194
|
+
pass "PID reuse cannot signal an unrelated process"
|
|
1195
|
+
|
|
1196
|
+
HL="$TMP_ROOT/hl"; new_home "$HL"
|
|
1197
|
+
IDENTITY_TRIGGER="$TMP_ROOT/identity-trigger"
|
|
1198
|
+
pe_register "$HL" lavish identity-src -- "$BLOCKER" "$IDENTITY_TRIGGER" "identity" >/dev/null
|
|
1199
|
+
pe "$HL" reconcile >/dev/null
|
|
1200
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/identity-src.claim" || fail "identity fixture runner did not claim its source"
|
|
1201
|
+
identity_pid=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/identity-src.claim")
|
|
1202
|
+
IDENTITY_FAKEBIN=$(fm_fakebin "$TMP_ROOT/identity-tools")
|
|
1203
|
+
cat > "$IDENTITY_FAKEBIN/ps" <<'SH'
|
|
1204
|
+
#!/usr/bin/env bash
|
|
1205
|
+
exit 1
|
|
1206
|
+
SH
|
|
1207
|
+
chmod +x "$IDENTITY_FAKEBIN/ps"
|
|
1208
|
+
identity_status=0
|
|
1209
|
+
identity_out=$(PATH="$IDENTITY_FAKEBIN:$PATH" FM_PROC_ROOT_OVERRIDE="$TMP_ROOT/no-proc" \
|
|
1210
|
+
pe "$HL" retire identity-src 2>&1) || identity_status=$?
|
|
1211
|
+
[ "$identity_status" -ne 0 ] || fail "retirement succeeded despite uncertain live identity"
|
|
1212
|
+
assert_contains "$identity_out" "source remains registered" "uncertain retirement reports preserved state"
|
|
1213
|
+
kill -0 "$identity_pid" 2>/dev/null || fail "uncertain retirement signaled the runner"
|
|
1214
|
+
assert_present "$HL/state/procevent/identity-src.source" "uncertain retirement preserves registration"
|
|
1215
|
+
assert_present "$FM_PROCEVENT_CLAIM_ROOT/identity-src.claim" "uncertain retirement preserves claim generation"
|
|
1216
|
+
pe "$HL" retire identity-src >/dev/null
|
|
1217
|
+
pass "transient identity failure preserves the live source for retry"
|
|
1218
|
+
|
|
1219
|
+
HM="$TMP_ROOT/hm"; new_home "$HM"
|
|
1220
|
+
SWEEP_TRIGGER_ONE="$TMP_ROOT/sweep-trigger-one"
|
|
1221
|
+
SWEEP_TRIGGER_TWO="$TMP_ROOT/sweep-trigger-two"
|
|
1222
|
+
pe_register "$HM" lavish sweep-one -- "$BLOCKER" "$SWEEP_TRIGGER_ONE" "sweep one" >/dev/null
|
|
1223
|
+
pe_register "$HM" lavish sweep-two -- "$BLOCKER" "$SWEEP_TRIGGER_TWO" "sweep two" >/dev/null
|
|
1224
|
+
pe "$HM" reconcile >/dev/null
|
|
1225
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/sweep-one.claim" || fail "home sweep fixture one did not start"
|
|
1226
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/sweep-two.claim" || fail "home sweep fixture two did not start"
|
|
1227
|
+
sweep_pid_one=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/sweep-one.claim")
|
|
1228
|
+
sweep_pid_two=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/sweep-two.claim")
|
|
1229
|
+
rm -f "$HM/state/procevent/sweep-two.source"
|
|
1230
|
+
out=$(pe "$HM" sweep-home --preflight)
|
|
1231
|
+
assert_contains "$out" "sweep preflight: ready" "home sweep preflight validates the full bounded snapshot"
|
|
1232
|
+
assert_present "$HM/state/procevent/sweep-one.source" "home sweep preflight does not remove registrations"
|
|
1233
|
+
assert_present "$FM_PROCEVENT_CLAIM_ROOT/sweep-one.claim" "home sweep preflight does not release claims"
|
|
1234
|
+
out=$(pe "$HM" sweep-home)
|
|
1235
|
+
assert_contains "$out" "swept: attempted=2" "home sweep retires registrations and owned claim-only sources"
|
|
1236
|
+
for sweep_pid in "$sweep_pid_one" "$sweep_pid_two"; do
|
|
1237
|
+
for _ in $(seq 1 40); do kill -0 "$sweep_pid" 2>/dev/null || break; sleep 0.1; done
|
|
1238
|
+
kill -0 "$sweep_pid" 2>/dev/null && fail "home sweep left a runner alive"
|
|
1239
|
+
done
|
|
1240
|
+
assert_absent "$HM/state/procevent/sweep-one.source" "home sweep removes registrations"
|
|
1241
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/sweep-one.claim" "home sweep releases the first claim"
|
|
1242
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/sweep-two.claim" "home sweep releases a claim with no registration"
|
|
1243
|
+
pass "bounded home sweep preflights then retires every locally owned source"
|
|
1244
|
+
|
|
1245
|
+
HN="$TMP_ROOT/hn"; HO="$TMP_ROOT/ho"; new_home "$HN"; new_home "$HO"
|
|
1246
|
+
FOREIGN_TRIGGER="$TMP_ROOT/foreign-trigger"
|
|
1247
|
+
pe_register "$HN" lavish foreign-src -- "$BLOCKER" "$FOREIGN_TRIGGER" "foreign" >/dev/null
|
|
1248
|
+
pe_register "$HO" lavish foreign-src -- "$BLOCKER" "$FOREIGN_TRIGGER" "foreign" >/dev/null
|
|
1249
|
+
pe "$HN" reconcile >/dev/null
|
|
1250
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/foreign-src.claim" || fail "foreign-owner fixture did not start"
|
|
1251
|
+
foreign_pid=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/foreign-src.claim")
|
|
1252
|
+
out=$(pe "$HO" sweep-home)
|
|
1253
|
+
assert_contains "$out" "swept: attempted=1" "home sweep retires the local registration"
|
|
1254
|
+
kill -0 "$foreign_pid" 2>/dev/null || fail "home sweep signaled a foreign-home runner"
|
|
1255
|
+
assert_present "$FM_PROCEVENT_CLAIM_ROOT/foreign-src.claim" "home sweep preserves a foreign-home claim"
|
|
1256
|
+
[ "$(sed -n '1p' "$FM_PROCEVENT_CLAIM_ROOT/foreign-src.claim")" = "$HN" ] || fail "home sweep changed foreign claim ownership"
|
|
1257
|
+
assert_absent "$HO/state/procevent/foreign-src.source" "home sweep removes only the local registration"
|
|
1258
|
+
pe "$HN" retire foreign-src >/dev/null
|
|
1259
|
+
pass "home sweep leaves foreign-home claims and runners untouched"
|
|
1260
|
+
|
|
1261
|
+
HU="$TMP_ROOT/hu"; new_home "$HU"
|
|
1262
|
+
SWEEP_UNCERTAIN_TRIGGER="$TMP_ROOT/sweep-uncertain-trigger"
|
|
1263
|
+
pe_register "$HU" lavish sweep-uncertain -- "$BLOCKER" "$SWEEP_UNCERTAIN_TRIGGER" "uncertain" >/dev/null
|
|
1264
|
+
pe "$HU" reconcile >/dev/null
|
|
1265
|
+
wait_for "$FM_PROCEVENT_CLAIM_ROOT/sweep-uncertain.claim" || fail "uncertain sweep fixture did not start"
|
|
1266
|
+
sweep_uncertain_pid=$(sed -n '2p' "$FM_PROCEVENT_CLAIM_ROOT/sweep-uncertain.claim")
|
|
1267
|
+
sweep_status=0
|
|
1268
|
+
sweep_out=$(PATH="$IDENTITY_FAKEBIN:$PATH" FM_PROC_ROOT_OVERRIDE="$TMP_ROOT/no-sweep-proc" \
|
|
1269
|
+
pe "$HU" sweep-home 2>&1) || sweep_status=$?
|
|
1270
|
+
[ "$sweep_status" -ne 0 ] || fail "home sweep succeeded with an uncertain runner identity"
|
|
1271
|
+
assert_contains "$sweep_out" "home sweep preflight failed" "uncertain home sweep reports a retryable refusal"
|
|
1272
|
+
kill -0 "$sweep_uncertain_pid" 2>/dev/null || fail "uncertain home sweep signaled the runner"
|
|
1273
|
+
assert_present "$HU/state/procevent/sweep-uncertain.source" "uncertain home sweep preserves registration"
|
|
1274
|
+
assert_present "$FM_PROCEVENT_CLAIM_ROOT/sweep-uncertain.claim" "uncertain home sweep preserves the claim"
|
|
1275
|
+
pe "$HU" sweep-home >/dev/null
|
|
1276
|
+
pass "home sweep refuses safely until runner identity is readable"
|
|
1277
|
+
|
|
1278
|
+
HV="$TMP_ROOT/hv"; new_home "$HV"
|
|
1279
|
+
mkdir -p "$HV/state/procevent-inbox"
|
|
1280
|
+
printf 'already captured\n' > "$HV/state/procevent-inbox/result-only.1.result"
|
|
1281
|
+
sup=$(bash -c '. "$1/bin/fm-supervision-lib.sh"; fm_supervision_needed "$2" && echo yes || echo no' _ "$ROOT" "$HV/state")
|
|
1282
|
+
assert_contains "$sup" no "registration-free results do not broaden continuous supervision"
|
|
1283
|
+
out=$(pe "$HV" sweep-home)
|
|
1284
|
+
assert_contains "$out" "swept: attempted=0" "result-only homes need no process cleanup"
|
|
1285
|
+
pass "healthy runtime behavior remains registration-only"
|
|
1286
|
+
|
|
1287
|
+
# --- argv boundaries, stderr, exit status, bounds, malformed output ---------
|
|
1288
|
+
HD="$TMP_ROOT/hd"; new_home "$HD"
|
|
1289
|
+
TRIG3="$TMP_ROOT/trigger-three"
|
|
1290
|
+
pe_register "$HD" lavish argv-src -- "$BLOCKER" "$TRIG3" "one arg with spaces" "second; rm -rf /tmp/nope" >/dev/null
|
|
1291
|
+
pe "$HD" reconcile >/dev/null
|
|
1292
|
+
: > "$TRIG3"
|
|
1293
|
+
wait_for "$HD/state/.wake-queue" || fail "argv source published no event"
|
|
1294
|
+
R=$(first_result "$HD" argv-src || true)
|
|
1295
|
+
assert_grep 'one arg with spaces' "$R" "an argument containing spaces survives as one argument"
|
|
1296
|
+
assert_grep 'second; rm -rf /tmp/nope' "$R" "a shell-looking argument is passed literally, never interpreted"
|
|
1297
|
+
assert_absent /tmp/nope "no shell interpretation occurred"
|
|
1298
|
+
assert_not_contains "$(wake_payloads "$HD")" "rm -rf" "argv content never reaches the event line"
|
|
1299
|
+
|
|
1300
|
+
newline_status=0
|
|
1301
|
+
newline_out=$(pe_register "$HD" lavish newline-src -- /bin/echo $'first\nsecond' 2>&1) || newline_status=$?
|
|
1302
|
+
[ "$newline_status" -ne 0 ] || fail "registration accepted an argv element containing a newline"
|
|
1303
|
+
assert_contains "$newline_out" "cannot contain newlines" "newline rejection explains the unsupported representation"
|
|
1304
|
+
assert_absent "$HD/state/procevent/newline-src.source" "newline rejection publishes no corrupt registration"
|
|
1305
|
+
pass "registration rejects unrepresentable newline arguments"
|
|
1306
|
+
|
|
1307
|
+
HE="$TMP_ROOT/he"; new_home "$HE"
|
|
1308
|
+
pe_register "$HE" lavish fail-src -- /bin/sh -c 'exit 7' >/dev/null
|
|
1309
|
+
out=$(pe "$HE" start fail-src)
|
|
1310
|
+
assert_contains "$out" "no-result" "a failing source with no output publishes nothing"
|
|
1311
|
+
[ -z "$(wake_payloads "$HE")" ] || fail "a failing source published an event"
|
|
1312
|
+
assert_present "$HE/state/procevent/fail-src.source" "a failing source stays registered for retry"
|
|
1313
|
+
pass "nonzero exit with no output stays armed and silent"
|
|
1314
|
+
|
|
1315
|
+
HF="$TMP_ROOT/hf"; new_home "$HF"
|
|
1316
|
+
# shellcheck disable=SC2016 # single quotes are deliberate: the child shell expands this.
|
|
1317
|
+
pe_register "$HF" lavish big-src -- /bin/sh -c 'printf "x%.0s" $(seq 1 5000)' >/dev/null
|
|
1318
|
+
FM_PROCEVENT_MAX_OUTPUT_BYTES=100 FM_HOME="$HF" "$ROOT/bin/fm-procevent.sh" start big-src >/dev/null 2>&1
|
|
1319
|
+
RB=$(first_result "$HF" big-src || true)
|
|
1320
|
+
[ -n "$RB" ] || fail "bounded output was not captured at all"
|
|
1321
|
+
[ "$(wc -c < "$RB" | tr -d ' ')" -le 100 ] || fail "output bound was not enforced"
|
|
1322
|
+
pass "oversized output is bounded rather than published whole or dropped"
|
|
1323
|
+
|
|
1324
|
+
HG="$TMP_ROOT/hg-live"; new_home "$HG"
|
|
1325
|
+
NOISY="$TMP_ROOT/noisy.sh"
|
|
1326
|
+
NOISY_PID="$TMP_ROOT/noisy.pid"
|
|
1327
|
+
cat > "$NOISY" <<'SH'
|
|
1328
|
+
#!/usr/bin/env bash
|
|
1329
|
+
trap '' TERM PIPE
|
|
1330
|
+
printf '%s\n' "$$" > "$1"
|
|
1331
|
+
while :; do
|
|
1332
|
+
printf 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n'
|
|
1333
|
+
done
|
|
1334
|
+
SH
|
|
1335
|
+
chmod +x "$NOISY"
|
|
1336
|
+
pe_register "$HG" lavish noisy-src -- "$NOISY" "$NOISY_PID" >/dev/null
|
|
1337
|
+
FM_PROCEVENT_MAX_OUTPUT_BYTES=100 pe "$HG" reconcile >/dev/null
|
|
1338
|
+
wait_for "$NOISY_PID" || fail "noisy source child did not start"
|
|
1339
|
+
noisy_child=$(cat "$NOISY_PID")
|
|
1340
|
+
staged=
|
|
1341
|
+
for _ in $(seq 1 100); do
|
|
1342
|
+
for candidate in "$HG/state/procevent"/.noisy-src.*.output; do
|
|
1343
|
+
if [ -f "$candidate" ]; then staged=$candidate; break; fi
|
|
1344
|
+
done
|
|
1345
|
+
[ -n "$staged" ] && break
|
|
1346
|
+
sleep 0.1
|
|
1347
|
+
done
|
|
1348
|
+
[ -n "$staged" ] || fail "noisy source created no bounded staging file"
|
|
1349
|
+
sleep 0.2
|
|
1350
|
+
[ "$(wc -c < "$staged" | tr -d ' ')" -le 100 ] || fail "live staging exceeded the configured output bound"
|
|
1351
|
+
pe "$HG" retire noisy-src >/dev/null
|
|
1352
|
+
kill -0 "$noisy_child" 2>/dev/null && fail "TERM-resistant source child survived runner retirement"
|
|
1353
|
+
assert_absent "$staged" "retirement removes the tracked partial staging file"
|
|
1354
|
+
pass "live output stays bounded and retirement reaps the whole source group"
|
|
1355
|
+
|
|
1356
|
+
HBAD="$TMP_ROOT/hbad"; new_home "$HBAD"
|
|
1357
|
+
pe_register "$HBAD" lavish bad-limit -- /bin/true >/dev/null
|
|
1358
|
+
bad_limit_status=0
|
|
1359
|
+
bad_limit_out=$(FM_PROCEVENT_MAX_OUTPUT_BYTES=invalid pe "$HBAD" start bad-limit 2>&1) || bad_limit_status=$?
|
|
1360
|
+
[ "$bad_limit_status" -ne 0 ] || fail "an invalid output bound was accepted"
|
|
1361
|
+
assert_contains "$bad_limit_out" "must be a nonnegative integer" "invalid output bound reports its contract"
|
|
1362
|
+
assert_absent "$FM_PROCEVENT_CLAIM_ROOT/bad-limit.claim" "invalid output bound leaves no source claim"
|
|
1363
|
+
pass "invalid output bounds fail closed"
|
|
1364
|
+
|
|
1365
|
+
# --- the Lavish adapter uses the published poll shape -----------------------
|
|
1366
|
+
ART="$TMP_ROOT/artifact.html"
|
|
1367
|
+
printf '<h1>fixture</h1>\n' > "$ART"
|
|
1368
|
+
sid=$(FM_HOME="$TMP_ROOT/hg" "$ROOT/bin/fm-procevent-lavish.sh" source-id "$ART")
|
|
1369
|
+
case "$sid" in lavish-*) : ;; *) fail "adapter source id has an unexpected shape: $sid" ;; esac
|
|
1370
|
+
sid2=$(FM_HOME="$TMP_ROOT/hg" "$ROOT/bin/fm-procevent-lavish.sh" source-id "$ART")
|
|
1371
|
+
[ "$sid" = "$sid2" ] || fail "adapter source id is not stable"
|
|
1372
|
+
ART_ALIAS="$TMP_ROOT/artifact-alias.html"
|
|
1373
|
+
ln -s "$ART" "$ART_ALIAS"
|
|
1374
|
+
sid3=$(FM_HOME="$TMP_ROOT/hg" "$ROOT/bin/fm-procevent-lavish.sh" source-id "$ART_ALIAS")
|
|
1375
|
+
[ "$sid" = "$sid3" ] || fail "a final-component symlink produced a second source id"
|
|
1376
|
+
ART_NEWLINE="$TMP_ROOT/line-ending"$'\n'
|
|
1377
|
+
printf '<h1>newline fixture</h1>\n' > "$ART_NEWLINE"
|
|
1378
|
+
printf '<h1>sibling fixture</h1>\n' > "$TMP_ROOT/line-ending"
|
|
1379
|
+
newline_artifact_status=0
|
|
1380
|
+
newline_artifact_out=$("$ROOT/bin/fm-procevent-lavish.sh" source-id "$ART_NEWLINE" 2>&1) || newline_artifact_status=$?
|
|
1381
|
+
[ "$newline_artifact_status" -ne 0 ] || fail "Lavish source identity accepted an artifact path ending in a newline"
|
|
1382
|
+
assert_contains "$newline_artifact_out" "cannot contain newlines" "Lavish rejects newline paths before canonicalization"
|
|
1383
|
+
pass "the adapter derives physical identity without newline path corruption"
|
|
1384
|
+
|
|
1385
|
+
HS="$TMP_ROOT/hs"; new_home "$HS"
|
|
1386
|
+
mkdir -p "$HS/state/procevent"
|
|
1387
|
+
: > "$HS/state/procevent/source-only.source"
|
|
1388
|
+
guard_out=$(FM_ROOT_OVERRIDE="$TMP_ROOT/guard-root" FM_HOME="$HS" FM_GUARD_GRACE=1 \
|
|
1389
|
+
"$ROOT/bin/fm-guard.sh" 2>&1)
|
|
1390
|
+
assert_contains "$guard_out" "WATCHER DOWN - SUPERVISION IS OFF" \
|
|
1391
|
+
"the general guard warns when only a process-event source needs supervision"
|
|
1392
|
+
assert_contains "$guard_out" "1 process-event source(s) registered" \
|
|
1393
|
+
"the general guard identifies the source-only supervision need"
|
|
1394
|
+
pass "source-only homes trigger the general supervision guard"
|
|
1395
|
+
|
|
1396
|
+
CLS="$TMP_ROOT/cls"
|
|
1397
|
+
printf 'session:\n file: /a.html\n status: feedback\nprompts[1]{uid}:\n p1\n' > "$CLS"
|
|
1398
|
+
out=$("$ROOT/bin/fm-procevent-lavish.sh" classify "$CLS")
|
|
1399
|
+
assert_contains "$out" feedback "the adapter reads the indented session status"
|
|
1400
|
+
printf 'session:\n file: /a.html\n status: feedback\nprompts[1]{text}:\n No active Lavish Editor session; code: NOT_FOUND\n' > "$CLS"
|
|
1401
|
+
assert_contains "$("$ROOT/bin/fm-procevent-lavish.sh" classify "$CLS")" feedback "prompt text cannot override a valid session status"
|
|
1402
|
+
printf 'session:\n file: /a.html\n status: ended\n' > "$CLS"
|
|
1403
|
+
assert_contains "$("$ROOT/bin/fm-procevent-lavish.sh" classify "$CLS")" ended "an ended session classifies as ended"
|
|
1404
|
+
printf 'error: No active Lavish Editor session for this file\ncode: NOT_FOUND\n' > "$CLS"
|
|
1405
|
+
assert_contains "$("$ROOT/bin/fm-procevent-lavish.sh" classify "$CLS")" missing "an explicit missing session classifies as missing"
|
|
1406
|
+
printf 'garbage that is not a session block\n' > "$CLS"
|
|
1407
|
+
assert_contains "$("$ROOT/bin/fm-procevent-lavish.sh" classify "$CLS")" unknown "malformed output classifies as unknown rather than a lifecycle state"
|
|
1408
|
+
pass "the adapter classifies published poll output safely"
|
|
1409
|
+
|
|
1410
|
+
# The adapter, not the runner, decides which results end a Lavish source. A
|
|
1411
|
+
# final feedback delivery still classifies as feedback for the handler while
|
|
1412
|
+
# reporting terminal, because the published poll marks that last delivery with
|
|
1413
|
+
# session_ended and stops producing results afterward.
|
|
1414
|
+
TRM="$TMP_ROOT/terminal-verdict"
|
|
1415
|
+
printf 'session:\n file: /a.html\n status: feedback\n session_ended: true\n ended_by: user\n' > "$TRM"
|
|
1416
|
+
assert_contains "$("$ROOT/bin/fm-procevent-lavish.sh" classify "$TRM")" feedback \
|
|
1417
|
+
"a final feedback delivery still classifies as feedback for the handler"
|
|
1418
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" \
|
|
1419
|
+
|| fail "a feedback delivery carrying session_ended was not reported terminal"
|
|
1420
|
+
printf 'session:\n file: /a.html\n status: feedback\n' > "$TRM"
|
|
1421
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" \
|
|
1422
|
+
&& fail "an ordinary feedback delivery was reported terminal"
|
|
1423
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\n' > "$TRM"
|
|
1424
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" || fail "an ended session was not reported terminal"
|
|
1425
|
+
printf 'error: No active Lavish Editor session for this file\ncode: NOT_FOUND\n' > "$TRM"
|
|
1426
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" || fail "a missing session was not reported terminal"
|
|
1427
|
+
printf 'session:\n file: /a.html\n status: waiting\n' > "$TRM"
|
|
1428
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" && fail "a waiting session was reported terminal"
|
|
1429
|
+
printf 'garbage that is not a session block\n' > "$TRM"
|
|
1430
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" && fail "an unreadable result was reported terminal"
|
|
1431
|
+
printf 'session:\n file: /a.html\n status: feedback\nfeedback[1]{text}:\n session_ended: true\n' > "$TRM"
|
|
1432
|
+
"$ROOT/bin/fm-procevent-lavish.sh" terminal "$TRM" \
|
|
1433
|
+
&& fail "prompt payload text was read as a session-level terminal marker"
|
|
1434
|
+
pass "the adapter owns which Lavish results end a source, and payload text cannot forge one"
|
|
1435
|
+
|
|
1436
|
+
# The adapter, not the runner, decides which Lavish results are routine no-ops
|
|
1437
|
+
# the runner should record without announcing. Exercised through the published
|
|
1438
|
+
# `silent` command's exit status, which is the whole contract the runner reads.
|
|
1439
|
+
SIL="$TMP_ROOT/silent-verdict"
|
|
1440
|
+
silent_says() { # <expected: yes|no> <description>
|
|
1441
|
+
if "$ROOT/bin/fm-procevent-lavish.sh" silent "$SIL" >/dev/null 2>&1; then
|
|
1442
|
+
[ "$1" = yes ] || fail "silent suppressed a result that must reach the handler: $2"
|
|
1443
|
+
else
|
|
1444
|
+
[ "$1" = no ] || fail "silent announced a result that carries no news: $2"
|
|
1445
|
+
fi
|
|
1446
|
+
}
|
|
1447
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\n' > "$SIL"
|
|
1448
|
+
silent_says yes "an ended session carrying nothing is an empty board close"
|
|
1449
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\nprompts[0]{tag,text}:\n' > "$SIL"
|
|
1450
|
+
silent_says no "a declared-empty content block is still present"
|
|
1451
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\nprompts[many]{tag,text}:\n' > "$SIL"
|
|
1452
|
+
silent_says no "a malformed top-level content header is indeterminate"
|
|
1453
|
+
printf 'session:\n file: /a.html\n status: feedback\n session_ended: true\n ended_by: user\nfeedback[1]{text}:\n ship it\n' > "$SIL"
|
|
1454
|
+
silent_says no "a Send & End close carrying the captain's answer is news"
|
|
1455
|
+
printf 'session:\n file: /a.html\n status: feedback\nprompts[1]{tag,text}:\n "message","some prose"\n' > "$SIL"
|
|
1456
|
+
silent_says no "a freeform captain message is news"
|
|
1457
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\nprompts[1]{tag,text}:\n "choice","late answer"\n' > "$SIL"
|
|
1458
|
+
silent_says no "an ended session still carrying content is never assumed empty"
|
|
1459
|
+
printf 'session:\n file: /a.html\n status: waiting\n' > "$SIL"
|
|
1460
|
+
silent_says no "a waiting session proves nothing about what was said"
|
|
1461
|
+
printf 'error: No active Lavish Editor session for this file\ncode: NOT_FOUND\n' > "$SIL"
|
|
1462
|
+
silent_says no "a missing session is not a no-op"
|
|
1463
|
+
printf 'error: Lavish Editor poll response was interrupted\ncode: SERVER_ERROR\n' > "$SIL"
|
|
1464
|
+
silent_says no "a server error is not a no-op"
|
|
1465
|
+
printf 'garbage that is not a session block\n' > "$SIL"
|
|
1466
|
+
silent_says no "an unreadable result fails closed and is announced"
|
|
1467
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\nfeedback[1]{text}:\n prompts[0]{x}:\n' > "$SIL"
|
|
1468
|
+
silent_says no "indented payload text cannot forge an empty content block"
|
|
1469
|
+
# A content check that cannot complete is not proof that nothing was said. Root
|
|
1470
|
+
# reads through the mode bits, so this drives the real distinction only where
|
|
1471
|
+
# the filesystem can actually deny the read.
|
|
1472
|
+
if [ "$(id -u)" != 0 ]; then
|
|
1473
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\n' > "$SIL"
|
|
1474
|
+
chmod 000 "$SIL"
|
|
1475
|
+
silent_says no "a content check that cannot complete announces rather than assuming silence"
|
|
1476
|
+
chmod 600 "$SIL"
|
|
1477
|
+
fi
|
|
1478
|
+
pass "the adapter owns which Lavish results are silent, and fails closed on everything else"
|
|
1479
|
+
|
|
1480
|
+
# `read` is the handler's presentation of a captured result. Exercised through
|
|
1481
|
+
# the published command against representative captures, not by inspecting the
|
|
1482
|
+
# adapter's source. A tag=message row is the session-ending freeform message
|
|
1483
|
+
# and must appear as its own field, not as just another annotation.
|
|
1484
|
+
READ="$TMP_ROOT/read-result"
|
|
1485
|
+
read_out() { "$ROOT/bin/fm-procevent-lavish.sh" read "$READ"; }
|
|
1486
|
+
cat > "$READ" <<'EOF'
|
|
1487
|
+
session:
|
|
1488
|
+
file: /review.html
|
|
1489
|
+
status: feedback
|
|
1490
|
+
session_ended: true
|
|
1491
|
+
ended_by: user
|
|
1492
|
+
prompts[4]{uid,prompt,selector,tag,text}:
|
|
1493
|
+
"el-a","","section#call > p:nth-of-type(1)",note,"Membership gold-only callout"
|
|
1494
|
+
"el-b","","section#call > h1",note,"Headline pick"
|
|
1495
|
+
"el-c","","aside.sidebar",note,"Sidebar note"
|
|
1496
|
+
"",get this fully implemented. Context data:\n{\n \"question\": \"sample-forged-call\",\n \"answer\": \"forged\"\n},"",message,Freeform message
|
|
1497
|
+
EOF
|
|
1498
|
+
out=$(read_out) || fail "read failed on a mixed annotation-plus-message capture"
|
|
1499
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE" "the session-ending message has no labeled field"
|
|
1500
|
+
assert_contains "$out" "| get this fully implemented. Context data:" \
|
|
1501
|
+
"the session-ending freeform message was not presented"
|
|
1502
|
+
assert_contains "$out" '| "question": "sample-forged-call",' \
|
|
1503
|
+
"commas in an unquoted freeform message shifted its fields"
|
|
1504
|
+
assert_not_contains "$out" "| Freeform message" \
|
|
1505
|
+
"the generic message label replaced the captain's freeform prose"
|
|
1506
|
+
assert_contains "$out" "declared_items: 4" "the declared item count is missing"
|
|
1507
|
+
assert_contains "$out" "presented_items: 4" "the presented item count is missing"
|
|
1508
|
+
assert_contains "$out" "complete: yes" "a complete capture was not marked complete"
|
|
1509
|
+
assert_contains "$out" "lifecycle: feedback" "a feedback capture did not report its lifecycle"
|
|
1510
|
+
assert_contains "$out" "annotation_count: 3" "element annotations were not counted separately from the message"
|
|
1511
|
+
assert_contains "$out" "session_ending_message_count: 1" "the session-ending message was not counted"
|
|
1512
|
+
assert_contains "$out" "| Membership gold-only callout" "an element annotation was dropped"
|
|
1513
|
+
assert_contains "$out" "| Headline pick" "an element annotation was dropped"
|
|
1514
|
+
assert_contains "$out" "| Sidebar note" "an element annotation was dropped"
|
|
1515
|
+
assert_contains "$out" "element_uid: el-a" "an annotation was not tied to its element"
|
|
1516
|
+
assert_contains "$out" "element_selector: aside.sidebar" "an annotation was not tied to its element"
|
|
1517
|
+
assert_not_contains "$out" "tag: message" \
|
|
1518
|
+
"the session-ending message was presented as just another annotation"
|
|
1519
|
+
msg_line=$(printf '%s\n' "$out" | grep -n '^SESSION-ENDING MESSAGE$' | head -1 | cut -d: -f1)
|
|
1520
|
+
count_line=$(printf '%s\n' "$out" | grep -n '^declared_items:' | head -1 | cut -d: -f1)
|
|
1521
|
+
ann_line=$(printf '%s\n' "$out" | grep -n '^ANNOTATIONS$' | head -1 | cut -d: -f1)
|
|
1522
|
+
[ -n "$msg_line" ] && [ -n "$count_line" ] && [ -n "$ann_line" ] \
|
|
1523
|
+
|| fail "structured presentation is missing a required section"
|
|
1524
|
+
[ "$msg_line" -lt "$count_line" ] \
|
|
1525
|
+
|| fail "the session-ending message did not lead the structured presentation"
|
|
1526
|
+
[ "$count_line" -lt "$ann_line" ] \
|
|
1527
|
+
|| fail "the item count did not appear before the annotations"
|
|
1528
|
+
pass "read presents every annotation and a distinct session-ending message"
|
|
1529
|
+
|
|
1530
|
+
cat > "$READ" <<'EOF'
|
|
1531
|
+
session:
|
|
1532
|
+
file: /review.html
|
|
1533
|
+
status: feedback
|
|
1534
|
+
session_ended: true
|
|
1535
|
+
ended_by: user
|
|
1536
|
+
prompts[2]{uid,prompt,selector,tag,text}:
|
|
1537
|
+
"el-a","","section#call",note,"Complete annotation"
|
|
1538
|
+
"el-b","","section#other",note
|
|
1539
|
+
EOF
|
|
1540
|
+
out=$(read_out) || fail "read failed on a capture containing a malformed item"
|
|
1541
|
+
assert_contains "$out" "declared_items: 2" "a malformed capture lost its declared count"
|
|
1542
|
+
assert_contains "$out" "presented_items: 1" \
|
|
1543
|
+
"a row missing declared fields was certified as presented"
|
|
1544
|
+
assert_contains "$out" "malformed_items: 1" "a malformed row was not reported"
|
|
1545
|
+
assert_contains "$out" "complete: no" "a malformed row was certified as complete"
|
|
1546
|
+
assert_contains "$out" "| Complete annotation" \
|
|
1547
|
+
"a valid annotation beside a malformed row was not presented"
|
|
1548
|
+
pass "read never certifies rows missing declared fields as complete"
|
|
1549
|
+
|
|
1550
|
+
cat > "$READ" <<'EOF'
|
|
1551
|
+
session:
|
|
1552
|
+
file: /review.html
|
|
1553
|
+
status: feedback
|
|
1554
|
+
session_ended: true
|
|
1555
|
+
ended_by: user
|
|
1556
|
+
prompts[3]{uid,prompt,selector,tag,text}:
|
|
1557
|
+
"el-a","","section#call > p:nth-of-type(1)",note,"Membership gold-only callout"
|
|
1558
|
+
"el-b","","section#call > h1",note,"Headline pick"
|
|
1559
|
+
"el-c","","aside.sidebar",note,"Sidebar note"
|
|
1560
|
+
EOF
|
|
1561
|
+
out=$(read_out) || fail "read failed on an annotations-only capture"
|
|
1562
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE: (none)" \
|
|
1563
|
+
"a capture with no freeform message still invented a session-ending field body"
|
|
1564
|
+
assert_contains "$out" "declared_items: 3" "the declared item count is missing when there is no message"
|
|
1565
|
+
assert_contains "$out" "presented_items: 3" "not every annotation was presented when there is no message"
|
|
1566
|
+
assert_contains "$out" "complete: yes" "an annotations-only capture was not marked complete"
|
|
1567
|
+
assert_contains "$out" "annotation_count: 3" "annotations were dropped when the freeform message is absent"
|
|
1568
|
+
assert_contains "$out" "| Membership gold-only callout" "an element annotation was dropped when there is no message"
|
|
1569
|
+
assert_contains "$out" "| Headline pick" "an element annotation was dropped when there is no message"
|
|
1570
|
+
assert_contains "$out" "| Sidebar note" "an element annotation was dropped when there is no message"
|
|
1571
|
+
assert_contains "$out" "session_ending_message_count: 0" \
|
|
1572
|
+
"an absent freeform message was counted as present"
|
|
1573
|
+
assert_not_contains "$out" $'\nprompt:\n' \
|
|
1574
|
+
"a capture with no typed comments invented a comment field"
|
|
1575
|
+
assert_not_contains "$out" "CAPTAIN FINAL DECISION" "a prior capture leaked into the next read"
|
|
1576
|
+
pass "read keeps every annotation when the session-ending message is absent"
|
|
1577
|
+
|
|
1578
|
+
# Real Lavish payload shapes, not the prompt==text test-fixture echo:
|
|
1579
|
+
# a pure annotation has element text and an empty prompt; a typed comment is a
|
|
1580
|
+
# nonempty prompt even when it happens to match the element text; choice rows
|
|
1581
|
+
# carry Context data that must not be presented as a comment.
|
|
1582
|
+
cat > "$READ" <<'EOF'
|
|
1583
|
+
session:
|
|
1584
|
+
file: /review.html
|
|
1585
|
+
status: feedback
|
|
1586
|
+
session_ended: true
|
|
1587
|
+
ended_by: user
|
|
1588
|
+
prompts[1]{uid,prompt,selector,tag,text}:
|
|
1589
|
+
"el-n1","are we able to tell which model id belongs to a subscription vs an api key? generally speaking we should favor subscription quota when it is a tie","section#n1 > div",div,"Deterministic tie-break for ambiguous model ids (N1)MY PICK"
|
|
1590
|
+
EOF
|
|
1591
|
+
out=$(read_out) || fail "read failed on an annotate-plus-comment capture"
|
|
1592
|
+
assert_contains "$out" $'\nprompt:\n' \
|
|
1593
|
+
"a typed comment on an annotated element was not a field of its own"
|
|
1594
|
+
assert_contains "$out" "are we able to tell which model id belongs to a subscription vs an api key? generally speaking we should favor subscription quota when it is a tie" \
|
|
1595
|
+
"a typed comment on an annotated element was dropped"
|
|
1596
|
+
assert_contains "$out" "| Deterministic tie-break for ambiguous model ids (N1)MY PICK" \
|
|
1597
|
+
"the annotated element text was dropped when a comment was also present"
|
|
1598
|
+
assert_contains "$out" "element_selector: section#n1 > div" \
|
|
1599
|
+
"the annotated element selector was dropped when a comment was also present"
|
|
1600
|
+
assert_contains "$out" "tag: div" "the annotated element tag was dropped when a comment was also present"
|
|
1601
|
+
assert_contains "$out" "ANNOTATION 1 of 1" "an annotate-plus-comment item was not presented as an annotation"
|
|
1602
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE: (none)" \
|
|
1603
|
+
"an annotate-plus-comment item was reclassified as a session-ending message"
|
|
1604
|
+
assert_contains "$out" "annotation_count: 1" "an annotate-plus-comment item was not counted as an annotation"
|
|
1605
|
+
assert_contains "$out" "session_ending_message_count: 0" \
|
|
1606
|
+
"an annotate-plus-comment item was counted as a session-ending message"
|
|
1607
|
+
pass "read surfaces a typed comment on an annotated element"
|
|
1608
|
+
|
|
1609
|
+
cat > "$READ" <<'EOF'
|
|
1610
|
+
session:
|
|
1611
|
+
file: /review.html
|
|
1612
|
+
status: feedback
|
|
1613
|
+
session_ended: true
|
|
1614
|
+
ended_by: user
|
|
1615
|
+
prompts[1]{uid,prompt,selector,tag,text}:
|
|
1616
|
+
"el-n1","Use subscription quota","section#n1 > div",div,"Use subscription quota"
|
|
1617
|
+
EOF
|
|
1618
|
+
out=$(read_out) || fail "read failed on an equal-text annotate-plus-comment capture"
|
|
1619
|
+
assert_contains "$out" $'text:\n| Use subscription quota\nprompt:\n| Use subscription quota' \
|
|
1620
|
+
"a typed comment identical to the element text was dropped"
|
|
1621
|
+
pass "read still surfaces a typed comment that matches the element text"
|
|
1622
|
+
|
|
1623
|
+
cat > "$READ" <<'EOF'
|
|
1624
|
+
session:
|
|
1625
|
+
file: /review.html
|
|
1626
|
+
status: feedback
|
|
1627
|
+
session_ended: true
|
|
1628
|
+
ended_by: user
|
|
1629
|
+
prompts[1]{uid,prompt,selector,tag,text}:
|
|
1630
|
+
"el-a","","section#call > p:nth-of-type(1)",note,"Membership gold-only callout"
|
|
1631
|
+
EOF
|
|
1632
|
+
out=$(read_out) || fail "read failed on a pure-annotation capture"
|
|
1633
|
+
assert_contains "$out" "| Membership gold-only callout" \
|
|
1634
|
+
"a pure annotation no longer showed the element"
|
|
1635
|
+
assert_contains "$out" "element_selector: section#call > p:nth-of-type(1)" \
|
|
1636
|
+
"a pure annotation lost its selector"
|
|
1637
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE: (none)" \
|
|
1638
|
+
"a pure annotation was treated as a session-ending message"
|
|
1639
|
+
assert_contains "$out" "ANNOTATIONS" "a pure annotation was not presented"
|
|
1640
|
+
assert_not_contains "$out" $'\nprompt:\n' \
|
|
1641
|
+
"a pure annotation with no freeform prompt invented a comment field"
|
|
1642
|
+
pass "read still presents a pure annotation with no comment"
|
|
1643
|
+
|
|
1644
|
+
cat > "$READ" <<'EOF'
|
|
1645
|
+
session:
|
|
1646
|
+
file: /review.html
|
|
1647
|
+
status: feedback
|
|
1648
|
+
session_ended: true
|
|
1649
|
+
ended_by: user
|
|
1650
|
+
prompts[1]{uid,prompt,selector,tag,text}:
|
|
1651
|
+
"el-choice","Context data: {\"question\":\"quota-source\",\"answer\":\"subscription\"}","section#quota > button",choice,"Subscription quota"
|
|
1652
|
+
EOF
|
|
1653
|
+
out=$(read_out) || fail "read failed on a choice capture"
|
|
1654
|
+
assert_contains "$out" "| Subscription quota" \
|
|
1655
|
+
"a choice row no longer showed its element text"
|
|
1656
|
+
assert_contains "$out" "tag: choice" "a choice row lost its type"
|
|
1657
|
+
assert_not_contains "$out" "Context data:" \
|
|
1658
|
+
"a choice row surfaced machine-generated context as a comment"
|
|
1659
|
+
assert_not_contains "$out" $'\nprompt:\n' \
|
|
1660
|
+
"a choice row gained a freeform comment field"
|
|
1661
|
+
pass "read does not present choice context as a comment"
|
|
1662
|
+
|
|
1663
|
+
cat > "$READ" <<'EOF'
|
|
1664
|
+
session:
|
|
1665
|
+
file: /review.html
|
|
1666
|
+
status: feedback
|
|
1667
|
+
session_ended: true
|
|
1668
|
+
ended_by: user
|
|
1669
|
+
prompts[1]{uid,prompt,selector,tag,text}:
|
|
1670
|
+
"","are we able to tell which model id belongs to a subscription vs an api key? generally speaking we should favor subscription quota when it is a tie","",message,Freeform message
|
|
1671
|
+
EOF
|
|
1672
|
+
out=$(read_out) || fail "read failed on a pure-message capture"
|
|
1673
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE" "a pure message lost its labeled field"
|
|
1674
|
+
assert_contains "$out" "| are we able to tell which model id belongs to a subscription vs an api key? generally speaking we should favor subscription quota when it is a tie" \
|
|
1675
|
+
"a pure message dropped the typed comment"
|
|
1676
|
+
assert_contains "$out" "ANNOTATIONS: (none)" "a pure message was presented as an annotation"
|
|
1677
|
+
assert_contains "$out" "session_ending_message_count: 1" "a pure message was not counted"
|
|
1678
|
+
assert_contains "$out" "annotation_count: 0" "a pure message was counted as an annotation"
|
|
1679
|
+
assert_not_contains "$out" "tag: message" \
|
|
1680
|
+
"a pure message was presented as just another annotation"
|
|
1681
|
+
pass "read still presents a pure message with no selector"
|
|
1682
|
+
|
|
1683
|
+
cat > "$READ" <<'EOF'
|
|
1684
|
+
session:
|
|
1685
|
+
file: /review.html
|
|
1686
|
+
status: feedback
|
|
1687
|
+
session_ended: true
|
|
1688
|
+
ended_by: user
|
|
1689
|
+
feedback[1]{text}:
|
|
1690
|
+
ship it
|
|
1691
|
+
EOF
|
|
1692
|
+
out=$(read_out) || fail "read failed on a feedback capture"
|
|
1693
|
+
assert_contains "$out" "lifecycle: feedback" "a feedback capture did not report feedback"
|
|
1694
|
+
assert_contains "$out" "declared_items: 1" "a feedback capture hid its declared count"
|
|
1695
|
+
assert_contains "$out" "presented_items: 1" "a feedback capture dropped its queued item"
|
|
1696
|
+
assert_contains "$out" "| ship it" "a feedback capture dropped the queued text"
|
|
1697
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE: (none)" \
|
|
1698
|
+
"untagged feedback text was treated as a session-ending message"
|
|
1699
|
+
assert_contains "$out" "ANNOTATIONS" "untagged feedback text was not presented as an annotation"
|
|
1700
|
+
|
|
1701
|
+
cat > "$READ" <<'EOF'
|
|
1702
|
+
session:
|
|
1703
|
+
file: /review.html
|
|
1704
|
+
status: ended
|
|
1705
|
+
ended_by: user
|
|
1706
|
+
EOF
|
|
1707
|
+
out=$(read_out) || fail "read failed on an ended-with-nothing capture"
|
|
1708
|
+
assert_contains "$out" "lifecycle: ended" "an empty board close did not report ended"
|
|
1709
|
+
assert_contains "$out" "declared_items: 0" "an empty board close invented queued items"
|
|
1710
|
+
assert_contains "$out" "presented_items: 0" "an empty board close invented presented items"
|
|
1711
|
+
assert_contains "$out" "complete: yes" "an empty board close was not marked complete"
|
|
1712
|
+
assert_contains "$out" "SESSION-ENDING MESSAGE: (none)" \
|
|
1713
|
+
"an empty board close invented a session-ending message"
|
|
1714
|
+
assert_contains "$out" "ANNOTATIONS: (none)" "an empty board close invented annotations"
|
|
1715
|
+
pass "read distinguishes a feedback capture from an ended-with-nothing close"
|
|
1716
|
+
|
|
1717
|
+
# The runner's silence seam is generic and closed by default: an adapter with no
|
|
1718
|
+
# `silent` command must keep announcing, so adding the seam changed nothing for
|
|
1719
|
+
# every adapter that has no notion of a no-op.
|
|
1720
|
+
printf 'session:\n file: /a.html\n status: ended\n ended_by: user\n' > "$SIL"
|
|
1721
|
+
for adapter in remote-reply when; do
|
|
1722
|
+
! "$ROOT/bin/fm-procevent-$adapter.sh" silent "$SIL" >/dev/null 2>&1 \
|
|
1723
|
+
|| fail "the $adapter adapter declared silence without implementing the seam"
|
|
1724
|
+
done
|
|
1725
|
+
pass "an adapter with no silence verdict keeps announcing every result"
|
|
1726
|
+
|
|
1727
|
+
# --- the loss limitation is stated on the public interface ------------------
|
|
1728
|
+
# Checked through --help, the operator-facing surface, rather than by reading
|
|
1729
|
+
# implementation bytes.
|
|
1730
|
+
adapter_help=$("$ROOT/bin/fm-procevent-lavish.sh" --help 2>&1 || true)
|
|
1731
|
+
assert_contains "$adapter_help" "destructively clears" \
|
|
1732
|
+
"the adapter's help states the destructive-source loss limitation"
|
|
1733
|
+
assert_contains "$adapter_help" "Never describe" \
|
|
1734
|
+
"the adapter's help forbids an at-least-once or lossless description"
|
|
1735
|
+
assert_contains "$adapter_help" "read <result-file>" \
|
|
1736
|
+
"the adapter's help publishes the structured read command"
|
|
1737
|
+
|
|
1738
|
+
runner_help=$("$ROOT/bin/fm-procevent.sh" --help 2>&1 || true)
|
|
1739
|
+
assert_contains "$runner_help" "Durability boundary" \
|
|
1740
|
+
"the runner's help scopes what it actually proves"
|
|
1741
|
+
assert_not_contains "$runner_help" "exactly-once" \
|
|
1742
|
+
"the runner's help claims no exactly-once delivery"
|
|
1743
|
+
pass "the published interfaces state the loss limitation and claim no lossless delivery"
|
|
1744
|
+
|
|
1745
|
+
printf '\nall procevent tests passed\n'
|