@zerwiz/ymir 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -39
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1466 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generic process-to-event runner: supervise a registered long-polling child
|
|
3
|
+
# outside the agent's foreground turn and turn completed results into normalized
|
|
4
|
+
# durable wakes.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# fm-procevent.sh register <adapter> <source-id> -- <argv>...
|
|
8
|
+
# fm-procevent.sh register-extension <adapter> <source-id> --config-ref <reference>
|
|
9
|
+
# fm-procevent.sh start <source-id>
|
|
10
|
+
# fm-procevent.sh reconcile
|
|
11
|
+
# fm-procevent.sh classify <result-file>
|
|
12
|
+
# fm-procevent.sh handled <source-id> <sequence>
|
|
13
|
+
# fm-procevent.sh retire <source-id> [--if-absent|--if-matches <adapter> -- <argv>...|--if-owner <registration-token>]
|
|
14
|
+
# fm-procevent.sh sweep-home [--preflight]
|
|
15
|
+
# fm-procevent.sh binding-retirement-preflight <binding-digest>
|
|
16
|
+
# fm-procevent.sh extension-retirement <binding|transfer> <retirement-arguments...>
|
|
17
|
+
# fm-procevent.sh extension-bind <bind|receive-transfer-bind> <binding-arguments...>
|
|
18
|
+
# fm-procevent.sh extension-process-event <process-event-arguments...>
|
|
19
|
+
# fm-procevent.sh list
|
|
20
|
+
#
|
|
21
|
+
# register Record a built-in source: its adapter, its canonical id, and the
|
|
22
|
+
# exact argv to execute. argv is stored one argument per line and
|
|
23
|
+
# executed directly, so there is no shell surface and no argument
|
|
24
|
+
# splitting. Built-in adapters register sources; nothing here parses
|
|
25
|
+
# user text.
|
|
26
|
+
# register-extension
|
|
27
|
+
# Resolve an explicitly enabled home-local process-event-adapter/1
|
|
28
|
+
# binding, verify its package and handshake, and record the source
|
|
29
|
+
# configuration reference with the exact extension id/version,
|
|
30
|
+
# capability version, package digest, binding digest, and a fresh
|
|
31
|
+
# registration token. The tracked extension host constructs every
|
|
32
|
+
# invocation; no package argv or shell command is stored.
|
|
33
|
+
# classify Ask the immutable adapter owner captured beside <result-file> for a
|
|
34
|
+
# bounded classification. Built-in results keep their existing
|
|
35
|
+
# script command; extension results must still match the exact bound
|
|
36
|
+
# package identity captured with them.
|
|
37
|
+
# start Claim the source, run its child to completion, durably capture the
|
|
38
|
+
# output, publish normalized wakes for pending results, then release
|
|
39
|
+
# the claim. It blocks for as long as the source blocks and is meant
|
|
40
|
+
# to run as a supervised background process, never in a conversational
|
|
41
|
+
# turn. After publishing, it asks the source's own adapter whether the
|
|
42
|
+
# captured result ends the source and retires the registration when it
|
|
43
|
+
# says so, so a source that has ended stops being restarted.
|
|
44
|
+
# reconcile Idempotent liveness entry the watcher calls on its ordinary cycle:
|
|
45
|
+
# republish every durably captured result with no handled
|
|
46
|
+
# acknowledgement yet - regardless of any earlier publication - and
|
|
47
|
+
# start a runner for any registered source that has no live owner.
|
|
48
|
+
# This is liveness repair only - it never discovers results by
|
|
49
|
+
# polling the source, because the child blocks on the source itself.
|
|
50
|
+
# handled Durably and idempotently record that a captured result has been
|
|
51
|
+
# fully handled: <source-id> <sequence>. Prints "handled: id seq"
|
|
52
|
+
# the first time for that exact source-and-sequence generation and
|
|
53
|
+
# "already-handled: id seq" on every repeat call, atomically
|
|
54
|
+
# deduplicated so a paired external effect is never authorized
|
|
55
|
+
# twice. Until this is called, the result stays eligible for
|
|
56
|
+
# bounded re-announcement on every reconcile. Marking a result
|
|
57
|
+
# handled does not retire its source registration or claim.
|
|
58
|
+
# retire Drop a registration, stop a runner this home owns, release the claim.
|
|
59
|
+
# Idempotent, and still the supported explicit path after a source has
|
|
60
|
+
# already retired itself on its adapter's terminal verdict. Existing
|
|
61
|
+
# unconditional built-in retirement remains compatible. An external
|
|
62
|
+
# registration requires --if-owner. --if-matches compares a complete
|
|
63
|
+
# built-in registration, --if-absent refuses while any registration
|
|
64
|
+
# exists, and --if-owner removes only the exact extension registration
|
|
65
|
+
# token printed by register-extension, so a stale owner cannot retire
|
|
66
|
+
# a replacement generation.
|
|
67
|
+
# sweep-home Retire a bounded snapshot of this home's registrations and owned
|
|
68
|
+
# claims, then refuse unless no registration, runner record, or owned
|
|
69
|
+
# claim remains. Used by supported Firstmate home retirement.
|
|
70
|
+
# binding-retirement-preflight
|
|
71
|
+
# Refuse while an extension registration or unhandled captured result
|
|
72
|
+
# still owns the exact enabled binding digest. Called by the tracked
|
|
73
|
+
# extension host before identity-conditional binding retirement.
|
|
74
|
+
# extension-retirement
|
|
75
|
+
# Serialize one tracked binding or transfer retirement against
|
|
76
|
+
# extension resolution and registration publication in this home.
|
|
77
|
+
# extension-bind
|
|
78
|
+
# Serialize tracked binding publication against extension resolution,
|
|
79
|
+
# registration publication, and retirement in this home.
|
|
80
|
+
# list Show registered sources, owners, and pending captured results.
|
|
81
|
+
#
|
|
82
|
+
# Terminal knowledge is adapter-owned. This runner never inspects a result and
|
|
83
|
+
# never names an adapter-specific status: built-ins keep the existing
|
|
84
|
+
# `bin/fm-procevent-<adapter>.sh terminal <result-file>` path, while an external
|
|
85
|
+
# result uses the exact process-event-adapter/1 package identity captured beside
|
|
86
|
+
# it. Exit 0 is the only terminal verdict. A missing command, an error, or any
|
|
87
|
+
# other exit keeps the registration armed, so an adapter that has no notion of
|
|
88
|
+
# ending needs no change.
|
|
89
|
+
#
|
|
90
|
+
# Routine no-op knowledge is adapter-owned through the same kind of seam. Some
|
|
91
|
+
# sources produce a result that carries no news at all - a review surface that
|
|
92
|
+
# simply closed with nothing said - and announcing it makes the handler read a
|
|
93
|
+
# wake to learn that nothing happened. So before publishing, this runner asks
|
|
94
|
+
# the immutable captured adapter owner - the built-in `silent` command or the
|
|
95
|
+
# bound extension operation - and treats exit 0 as the only silence verdict: the
|
|
96
|
+
# result is recorded handled and never announced, so it neither wakes a handler
|
|
97
|
+
# now nor returns on a later reconcile. A missing command, an error, or any other
|
|
98
|
+
# exit publishes the wake exactly as before, so an adapter with no notion of a
|
|
99
|
+
# no-op needs no change and an unknown or degraded result always reaches its
|
|
100
|
+
# handler. This runner still inspects nothing and still names no adapter-specific
|
|
101
|
+
# condition. For built-ins, silence remains independent of the keyed-answer feed
|
|
102
|
+
# below: suppressing an announcement never suppresses the captain's own answer.
|
|
103
|
+
#
|
|
104
|
+
# Applying a built-in result is adapter-owned through the same kind of seam. Some results
|
|
105
|
+
# carry no judgement at all - they must simply be applied idempotently to the
|
|
106
|
+
# home's own durable state - and leaving that to an agent that has to remember
|
|
107
|
+
# means it silently does not happen. So after publishing, `start` calls
|
|
108
|
+
# `bin/fm-procevent-<adapter>.sh autohandle <source-id> <sequence> <result-file>`
|
|
109
|
+
# and lets the adapter apply and acknowledge its own result. Exit 0 means the
|
|
110
|
+
# adapter fully handled it. A missing command, an error, or any other exit is not
|
|
111
|
+
# a failure of capture: the result stays unacknowledged and therefore eligible
|
|
112
|
+
# for re-announcement, so the handler still receives it exactly as before. This
|
|
113
|
+
# runner still inspects nothing and still names no adapter-specific condition.
|
|
114
|
+
# External bindings deliberately receive no autohandle operation.
|
|
115
|
+
#
|
|
116
|
+
# Built-in announcement is adapter-owned through one more seam of the same kind. An
|
|
117
|
+
# adapter that answers exit 0 to `bin/fm-procevent-<adapter>.sh self-announcing`
|
|
118
|
+
# declares that every result its autohandle fully applies is announced through a
|
|
119
|
+
# durable downstream channel of its own (for remote-reply, the mirrored parent
|
|
120
|
+
# status append the watcher's signal scan detects). For such an adapter, `start`
|
|
121
|
+
# runs autohandle FIRST and publishes a check wake only for what remains
|
|
122
|
+
# unhandled afterwards, so a fully autohandled capture never produces a second
|
|
123
|
+
# announcement and a byte-identical replay produces none at all. Every other
|
|
124
|
+
# adapter keeps the strict publish-before-apply order, because without a
|
|
125
|
+
# declared downstream channel an applied-and-acknowledged result would otherwise
|
|
126
|
+
# go silent. An unhandled result stays eligible for bounded re-announcement on
|
|
127
|
+
# every reconcile in both modes, exactly as before.
|
|
128
|
+
#
|
|
129
|
+
# Keyed captain answers from built-in adapters use one more seam of the same kind,
|
|
130
|
+
# and this runner still decides nothing about them. Some sources carry the
|
|
131
|
+
# captain's answer to a captain-held task. What such an answer MEANS is owned
|
|
132
|
+
# once, by bin/fm-captain-hold.sh's keyed-answer intake, and reaching it must not
|
|
133
|
+
# depend on an agent remembering. So after capture, a bound source
|
|
134
|
+
# has its result passed to
|
|
135
|
+
# `bin/fm-procevent-<adapter>.sh answers <result-file>`, and whatever that prints
|
|
136
|
+
# is piped straight into that one intake. The adapter reports only what the
|
|
137
|
+
# captain chose; the intake owns every rule about what happens next. This runner
|
|
138
|
+
# names no adapter, parses no result, and knows no decision rule, so a future
|
|
139
|
+
# built-in source needs nothing here beyond an `answers` command and a binding.
|
|
140
|
+
# External binding responses never enter this authority-bearing intake.
|
|
141
|
+
#
|
|
142
|
+
# Feeding is deliberately independent of handling: it never acknowledges a result
|
|
143
|
+
# and never suppresses a wake. Recording the captain's answer is transcription,
|
|
144
|
+
# while ACTING on it is firstmate's judgement, so the capture stays unacknowledged
|
|
145
|
+
# and its `check` wake reaches the handler exactly as it would have anyway.
|
|
146
|
+
#
|
|
147
|
+
# Ownership is machine-wide per canonical source, because separate Firstmate
|
|
148
|
+
# homes can share one underlying source store. A live owner is never displaced;
|
|
149
|
+
# only a claim whose whole generation is gone is reclaimed. A runner leads its
|
|
150
|
+
# own process group, so a crashed leader whose group still has members is not
|
|
151
|
+
# stale: reconcile stops that surviving group and releases its generation before
|
|
152
|
+
# any replacement starts, and keeps the claim for a later retry when it cannot.
|
|
153
|
+
#
|
|
154
|
+
# Durability boundary: see bin/fm-procevent-lib.sh. This runner proves capture
|
|
155
|
+
# before publication and bounded re-announcement until handled, and nothing
|
|
156
|
+
# about the source side of the handoff.
|
|
157
|
+
set -u
|
|
158
|
+
|
|
159
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
160
|
+
FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
161
|
+
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
|
|
162
|
+
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
|
|
163
|
+
|
|
164
|
+
# shellcheck source=bin/fm-pr-lib.sh
|
|
165
|
+
. "$SCRIPT_DIR/fm-pr-lib.sh"
|
|
166
|
+
# shellcheck source=bin/fm-wake-lib.sh
|
|
167
|
+
. "$SCRIPT_DIR/fm-wake-lib.sh"
|
|
168
|
+
# shellcheck source=bin/fm-procevent-lib.sh
|
|
169
|
+
. "$SCRIPT_DIR/fm-procevent-lib.sh"
|
|
170
|
+
|
|
171
|
+
REG=$(fm_procevent_registry_dir "$STATE")
|
|
172
|
+
MAX_OUTPUT_BYTES=${FM_PROCEVENT_MAX_OUTPUT_BYTES:-1048576}
|
|
173
|
+
EXTENSION_HOST="$SCRIPT_DIR/fm-extension.mjs"
|
|
174
|
+
EXTENSION_LIFECYCLE_LOCK="$REG/.extension-binding-lifecycle.lock"
|
|
175
|
+
|
|
176
|
+
die() { printf 'error: %s\n' "$1" >&2; exit 1; }
|
|
177
|
+
usage() { sed -n '2,/^set -u$/p' "${BASH_SOURCE[0]}" | sed '$d; s/^# \{0,1\}//'; exit 2; }
|
|
178
|
+
|
|
179
|
+
adapter_script() { printf '%s/bin/fm-procevent-%s.sh\n' "$FM_ROOT" "$1"; }
|
|
180
|
+
|
|
181
|
+
extension_lifecycle_lock_acquire() {
|
|
182
|
+
(umask 077; mkdir -p "$REG") || return 1
|
|
183
|
+
[ -d "$REG" ] && [ ! -L "$REG" ] || return 1
|
|
184
|
+
fm_lock_acquire_wait "$EXTENSION_LIFECYCLE_LOCK"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
extension_lifecycle_lock_release() {
|
|
188
|
+
fm_lock_release "$EXTENSION_LIFECYCLE_LOCK"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
run_extension_invocation_cleanup() { # [cleanup selector...]
|
|
192
|
+
[ -x "$EXTENSION_HOST" ] && [ ! -L "$EXTENSION_HOST" ] || return 1
|
|
193
|
+
if [ -n "${FM_STATE_OVERRIDE:-}" ]; then
|
|
194
|
+
FM_HOME="$FM_HOME" FM_STATE_OVERRIDE="$STATE" \
|
|
195
|
+
"$EXTENSION_HOST" cleanup-invocations "$@" >/dev/null 2>&1
|
|
196
|
+
else
|
|
197
|
+
FM_HOME="$FM_HOME" "$EXTENSION_HOST" cleanup-invocations "$@" >/dev/null 2>&1
|
|
198
|
+
fi
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
cleanup_extension_binding_invocations() { # <binding-digest>
|
|
202
|
+
run_extension_invocation_cleanup --binding-digest "$1"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
cleanup_extension_registration_invocations_locked() { # <source-id>
|
|
206
|
+
local owner_state
|
|
207
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$1"
|
|
208
|
+
owner_state=$?
|
|
209
|
+
case "$owner_state" in
|
|
210
|
+
0) cleanup_extension_binding_invocations "$FM_PROCEVENT_EXTENSION_BINDING_DIGEST" ;;
|
|
211
|
+
1) return 0 ;;
|
|
212
|
+
*) return 1 ;;
|
|
213
|
+
esac
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Invoke one captured result through its exact extension owner. The immutable
|
|
217
|
+
# sidecar, not the current adapter name alone, supplies every expected binding
|
|
218
|
+
# field, so replacing a binding cannot reinterpret old evidence.
|
|
219
|
+
extension_result_command() { # <adapter> <operation> <result-file>
|
|
220
|
+
local adapter=$1 operation=$2 result=$3 owner_state reservation='' owner claim_path handoff_status
|
|
221
|
+
fm_procevent_result_extension_load "$result"
|
|
222
|
+
owner_state=$?
|
|
223
|
+
[ "$owner_state" -eq 0 ] || return 1
|
|
224
|
+
[ -x "$EXTENSION_HOST" ] && [ ! -L "$EXTENSION_HOST" ] || return 1
|
|
225
|
+
case "$operation" in
|
|
226
|
+
result.terminal) reservation=${FM_PROCEVENT_CAPTURE_RESERVATION_TERMINAL:-} ;;
|
|
227
|
+
result.silent) reservation=${FM_PROCEVENT_CAPTURE_RESERVATION_SILENT:-} ;;
|
|
228
|
+
esac
|
|
229
|
+
local -a command=("$EXTENSION_HOST" process-event "$adapter" "$operation"
|
|
230
|
+
--result-file "$result"
|
|
231
|
+
--expect-extension "$FM_PROCEVENT_RESULT_EXTENSION_ID"
|
|
232
|
+
--expect-version "$FM_PROCEVENT_RESULT_EXTENSION_VERSION"
|
|
233
|
+
--expect-capability-version "$FM_PROCEVENT_RESULT_EXTENSION_CAPABILITY_VERSION"
|
|
234
|
+
--expect-package-digest "$FM_PROCEVENT_RESULT_EXTENSION_PACKAGE_DIGEST"
|
|
235
|
+
--expect-binding-digest "$FM_PROCEVENT_RESULT_EXTENSION_BINDING_DIGEST")
|
|
236
|
+
if [ -n "$reservation" ]; then
|
|
237
|
+
extension_lifecycle_lock_acquire || return 1
|
|
238
|
+
owner=${FM_LOCK_OWNER_DIR:-}
|
|
239
|
+
[ -n "$owner" ] || { extension_lifecycle_lock_release; return 1; }
|
|
240
|
+
claim_path=$(fm_procevent_claim_path "$CLAIM_ID") || { extension_lifecycle_lock_release; return 1; }
|
|
241
|
+
FM_EXTENSION_RETIREMENT_MODE=process-event \
|
|
242
|
+
FM_EXTENSION_LIFECYCLE_LOCK="$EXTENSION_LIFECYCLE_LOCK" \
|
|
243
|
+
FM_EXTENSION_LIFECYCLE_OWNER="$owner" \
|
|
244
|
+
perl "$SCRIPT_DIR/fm-procevent-extension-capture.pl" handoff \
|
|
245
|
+
8 6 "$claim_path" "$CLAIM_HOME" "$CLAIM_ID" "$CLAIM_TOKEN" "$CLAIM_PID" \
|
|
246
|
+
"$(fm_pid_identity "$CLAIM_PID")" "$FM_PROCEVENT_RESULT_EXTENSION_BINDING_DIGEST" "$reservation" \
|
|
247
|
+
"$operation" "$result" "$EXTENSION_HOST" -- "${command[@]:1}"
|
|
248
|
+
handoff_status=$?
|
|
249
|
+
extension_lifecycle_lock_release
|
|
250
|
+
return "$handoff_status"
|
|
251
|
+
fi
|
|
252
|
+
"${command[@]}"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# Ask the source's own adapter whether a captured result ends the source. Exit 0
|
|
256
|
+
# is the only terminal verdict; everything else - including a missing adapter
|
|
257
|
+
# command - keeps the registration armed. See the terminal-knowledge note in the
|
|
258
|
+
# header: no adapter-specific condition may appear in this runner.
|
|
259
|
+
adapter_result_is_terminal() { # <adapter> <result-file>
|
|
260
|
+
local script owner_state
|
|
261
|
+
fm_procevent_result_extension_load "$2"
|
|
262
|
+
owner_state=$?
|
|
263
|
+
case "$owner_state" in
|
|
264
|
+
0) extension_result_command "$1" result.terminal "$2" >/dev/null 2>&1; return $? ;;
|
|
265
|
+
2) return 1 ;;
|
|
266
|
+
esac
|
|
267
|
+
script=$(adapter_script "$1")
|
|
268
|
+
[ -f "$script" ] && [ ! -L "$script" ] || return 1
|
|
269
|
+
"$script" terminal "$2" >/dev/null 2>&1
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Ask the source's own adapter whether a captured result is a routine no-op that
|
|
273
|
+
# needs no wake at all. This mirrors the terminal seam above exactly: exit 0 is
|
|
274
|
+
# the only silence verdict, and everything else - including a missing adapter
|
|
275
|
+
# command - publishes the wake. See the routine-no-op note in the header: no
|
|
276
|
+
# adapter-specific condition may appear in this runner.
|
|
277
|
+
adapter_result_is_silent() { # <adapter> <result-file>
|
|
278
|
+
local script owner_state
|
|
279
|
+
fm_procevent_result_extension_load "$2"
|
|
280
|
+
owner_state=$?
|
|
281
|
+
case "$owner_state" in
|
|
282
|
+
0) extension_result_command "$1" result.silent "$2" >/dev/null 2>&1; return $? ;;
|
|
283
|
+
2) return 1 ;;
|
|
284
|
+
esac
|
|
285
|
+
script=$(adapter_script "$1")
|
|
286
|
+
[ -f "$script" ] && [ ! -L "$script" ] || return 1
|
|
287
|
+
"$script" silent "$2" >/dev/null 2>&1
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# Ask the adapter whether its autohandled results announce themselves through a
|
|
291
|
+
# durable downstream channel of their own (see the announcement-ownership note
|
|
292
|
+
# in the header). Exit 0 is the only declaration; everything else - including a
|
|
293
|
+
# missing adapter or an adapter without the command - keeps the strict
|
|
294
|
+
# publish-before-apply order.
|
|
295
|
+
adapter_self_announcing() { # <adapter>
|
|
296
|
+
local script
|
|
297
|
+
script=$(adapter_script "$1")
|
|
298
|
+
[ -f "$script" ] && [ ! -L "$script" ] || return 1
|
|
299
|
+
"$script" self-announcing >/dev/null 2>&1
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
source_file() { printf '%s/%s.source\n' "$REG" "$1"; }
|
|
303
|
+
runner_file() { printf '%s/%s.runner\n' "$REG" "$1"; }
|
|
304
|
+
staging_file() { printf '%s/.%s.%s.output\n' "$REG" "$1" "$2"; }
|
|
305
|
+
|
|
306
|
+
# Let the source's own adapter apply and acknowledge one captured result. See
|
|
307
|
+
# the header for why this exists and what each exit means. An already
|
|
308
|
+
# acknowledged result is skipped, so this is safe to call more than once for the
|
|
309
|
+
# same generation. The adapter runs OUTSIDE any source-lock hold here, because a
|
|
310
|
+
# handling adapter is expected to re-arm its own next source, which takes that
|
|
311
|
+
# same lock.
|
|
312
|
+
adapter_autohandle() { # <adapter> <source-id> <result-file>
|
|
313
|
+
local adapter=$1 id=$2 result=$3 script seq
|
|
314
|
+
script=$(adapter_script "$adapter")
|
|
315
|
+
[ -f "$script" ] && [ ! -L "$script" ] || return 1
|
|
316
|
+
seq=$(fm_procevent_result_sequence "$result") || return 1
|
|
317
|
+
case "$seq" in ''|*[!0-9]*) return 1 ;; esac
|
|
318
|
+
fm_procevent_is_handled "$STATE" "$id" "$seq" && return 0
|
|
319
|
+
# Silenced exactly like the terminal seam above, so an adapter that has no
|
|
320
|
+
# such command is a quiet no-op rather than runner noise. This runner's own
|
|
321
|
+
# one-line outcome is the interface; an adapter that failed keeps its result
|
|
322
|
+
# announced, and the handler's own call reproduces the diagnostics in full.
|
|
323
|
+
"$script" autohandle "$id" "$seq" "$result" >/dev/null 2>&1
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# Pass a bound source's captured result to the one keyed-answer intake. The
|
|
327
|
+
# adapter turns its own format into keyed lines; the intake owns everything those
|
|
328
|
+
# lines mean. Silenced and best-effort exactly like the seams above: an unbound
|
|
329
|
+
# source, an adapter with no `answers` command, and a failure on either side all
|
|
330
|
+
# leave the capture untouched and still announced, because this never
|
|
331
|
+
# acknowledges anything (see the keyed-answer note in the header).
|
|
332
|
+
feed_keyed_answers() { # <adapter> <source-id> <result-file>
|
|
333
|
+
local adapter=$1 id=$2 result=$3 script origin seq
|
|
334
|
+
script=$(adapter_script "$adapter")
|
|
335
|
+
[ -f "$script" ] && [ ! -L "$script" ] || return 1
|
|
336
|
+
origin=$("$SCRIPT_DIR/fm-captain-hold.sh" binding "$id" 2>/dev/null) || return 1
|
|
337
|
+
[ -n "$origin" ] || return 1
|
|
338
|
+
seq=$(fm_procevent_result_sequence "$result") || return 1
|
|
339
|
+
"$script" answers "$result" 2>/dev/null \
|
|
340
|
+
| "$SCRIPT_DIR/fm-captain-hold.sh" answers "$origin" \
|
|
341
|
+
--source "the captured result $id sequence $seq" >/dev/null 2>&1
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
read_adapter() { # <source-id>
|
|
345
|
+
local f; f=$(source_file "$1")
|
|
346
|
+
[ -f "$f" ] && [ ! -L "$f" ] || return 1
|
|
347
|
+
sed -n 's/^adapter=//p' "$f" | head -1
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# Read the stored argv into the ARGV array. One argument per line after the
|
|
351
|
+
# argv= count, so an argument containing spaces is not re-split.
|
|
352
|
+
read_argv() { # <source-id>
|
|
353
|
+
local f n; f=$(source_file "$1")
|
|
354
|
+
ARGV=()
|
|
355
|
+
[ -f "$f" ] && [ ! -L "$f" ] || return 1
|
|
356
|
+
n=$(sed -n 's/^argc=//p' "$f" | head -1)
|
|
357
|
+
case "$n" in ''|*[!0-9]*) return 1 ;; esac
|
|
358
|
+
local i=0 line
|
|
359
|
+
while IFS= read -r line; do
|
|
360
|
+
i=$((i + 1))
|
|
361
|
+
[ "$i" -le "$n" ] && ARGV+=("$line")
|
|
362
|
+
done < <(sed -n '/^argv:$/,$p' "$f" | tail -n +2)
|
|
363
|
+
[ "${#ARGV[@]}" -eq "$n" ]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
extension_registration_replacement_safe_locked() { # <source-id>
|
|
367
|
+
local id=$1 owner_state claim_state
|
|
368
|
+
if [ ! -e "$(source_file "$id")" ] && [ ! -L "$(source_file "$id")" ]; then
|
|
369
|
+
return 0
|
|
370
|
+
fi
|
|
371
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$id"
|
|
372
|
+
owner_state=$?
|
|
373
|
+
[ "$owner_state" -eq 0 ] || return 0
|
|
374
|
+
fm_procevent_claim_state_locked "$id"
|
|
375
|
+
claim_state=$?
|
|
376
|
+
case "$claim_state" in
|
|
377
|
+
0|2|3|4) return 1 ;;
|
|
378
|
+
*) return 0 ;;
|
|
379
|
+
esac
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
cmd_register() {
|
|
383
|
+
local adapter=${1-} id=${2-} sep=${3-}
|
|
384
|
+
shift 3 2>/dev/null || usage
|
|
385
|
+
fm_procevent_adapter_valid "$adapter" || die "adapter name must be lowercase alphanumeric or dash: $adapter"
|
|
386
|
+
fm_procevent_source_id_valid "$id" || die "source id must be path-safe and at most 64 characters: $id"
|
|
387
|
+
[ "$sep" = -- ] || usage
|
|
388
|
+
[ "$#" -ge 1 ] || die "register needs at least one argv element after --"
|
|
389
|
+
local arg
|
|
390
|
+
for arg in "$@"; do
|
|
391
|
+
case "$arg" in *$'\n'*) die "argv elements cannot contain newlines" ;; esac
|
|
392
|
+
done
|
|
393
|
+
[ -f "$(adapter_script "$adapter")" ] || die "no installed adapter for: $adapter"
|
|
394
|
+
fm_procevent_source_lock_acquire "$id" || die "cannot lock the source"
|
|
395
|
+
if ! extension_registration_replacement_safe_locked "$id"; then
|
|
396
|
+
fm_procevent_source_lock_release "$id"
|
|
397
|
+
die "cannot replace extension registration while its prior runner remains active: $id"
|
|
398
|
+
fi
|
|
399
|
+
if ! fm_procevent_registration_publish_locked "$STATE" "$adapter" "$id" "$@"; then
|
|
400
|
+
fm_procevent_source_lock_release "$id"
|
|
401
|
+
die "cannot publish the registration"
|
|
402
|
+
fi
|
|
403
|
+
fm_procevent_source_lock_release "$id"
|
|
404
|
+
printf 'registered: %s (%s)\n' "$id" "$adapter"
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
new_extension_registration_token() {
|
|
408
|
+
local hex
|
|
409
|
+
hex=$(LC_ALL=C od -An -v -tx1 -N 32 /dev/urandom 2>/dev/null | tr -d ' \n') || return 1
|
|
410
|
+
[ "${#hex}" -eq 64 ] || return 1
|
|
411
|
+
printf 'sha256:%s\n' "$hex"
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
extension_source_request_id() { # <adapter> <source-id> <next-sequence> <registration-token> <package-digest>
|
|
415
|
+
local digest
|
|
416
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
417
|
+
digest=$(printf 'firstmate-process-event-request-v1\n%s\n%s\n%s\n%s\n%s\n' "$@" \
|
|
418
|
+
| shasum -a 256 | awk '{print $1}') || return 1
|
|
419
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
420
|
+
digest=$(printf 'firstmate-process-event-request-v1\n%s\n%s\n%s\n%s\n%s\n' "$@" \
|
|
421
|
+
| sha256sum | awk '{print $1}') || return 1
|
|
422
|
+
else
|
|
423
|
+
return 1
|
|
424
|
+
fi
|
|
425
|
+
[ "${#digest}" -eq 64 ] || return 1
|
|
426
|
+
printf 'sha256:%s\n' "$digest"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
next_result_sequence() { # <source-id>
|
|
430
|
+
local id=$1 inbox seq=1
|
|
431
|
+
inbox=$(fm_procevent_inbox_dir "$STATE")
|
|
432
|
+
while [ -e "$inbox/$id.$seq.result" ]; do seq=$((seq + 1)); done
|
|
433
|
+
printf '%s\n' "$seq"
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
cmd_register_extension() {
|
|
437
|
+
local adapter=${1-} id=${2-} option=${3-} config_ref=${4-} resolution schema extension_id
|
|
438
|
+
local extension_version capability_version package_digest binding_digest extra registration_token
|
|
439
|
+
[ "$#" -eq 4 ] || usage
|
|
440
|
+
fm_procevent_adapter_valid "$adapter" || die "adapter name must be lowercase alphanumeric or dash: $adapter"
|
|
441
|
+
fm_procevent_source_id_valid "$id" || die "source id must be path-safe and at most 64 characters: $id"
|
|
442
|
+
[ "$option" = --config-ref ] || usage
|
|
443
|
+
fm_procevent_extension_config_ref_valid "$config_ref" \
|
|
444
|
+
|| die "source configuration reference must be one bounded line"
|
|
445
|
+
if [ ! -x "$EXTENSION_HOST" ] || [ -L "$EXTENSION_HOST" ]; then
|
|
446
|
+
die "the tracked extension host is unavailable"
|
|
447
|
+
fi
|
|
448
|
+
extension_lifecycle_lock_acquire || die "cannot lock the extension lifecycle"
|
|
449
|
+
if ! resolution=$("$EXTENSION_HOST" resolve-process-event "$adapter"); then
|
|
450
|
+
extension_lifecycle_lock_release
|
|
451
|
+
die "extension adapter verification failed: $adapter"
|
|
452
|
+
fi
|
|
453
|
+
if [ "$(printf '%s\n' "$resolution" | wc -l | tr -d ' ')" != 1 ]; then
|
|
454
|
+
extension_lifecycle_lock_release
|
|
455
|
+
die "extension adapter resolution was malformed: $adapter"
|
|
456
|
+
fi
|
|
457
|
+
IFS=$'\t' read -r schema extension_id extension_version capability_version \
|
|
458
|
+
package_digest binding_digest extra <<< "$resolution"
|
|
459
|
+
if [ "$schema" != fm-extension-process-event-resolution.v1 ] || [ -n "$extra" ]; then
|
|
460
|
+
extension_lifecycle_lock_release
|
|
461
|
+
die "extension adapter resolution was malformed: $adapter"
|
|
462
|
+
fi
|
|
463
|
+
if ! fm_procevent_extension_id_valid "$extension_id" \
|
|
464
|
+
|| ! fm_procevent_extension_version_valid "$extension_version" \
|
|
465
|
+
|| [ "$capability_version" != 1 ] \
|
|
466
|
+
|| ! fm_procevent_digest_valid "$package_digest" \
|
|
467
|
+
|| ! fm_procevent_digest_valid "$binding_digest"; then
|
|
468
|
+
extension_lifecycle_lock_release
|
|
469
|
+
die "extension adapter identity was malformed: $adapter"
|
|
470
|
+
fi
|
|
471
|
+
if ! registration_token=$(new_extension_registration_token); then
|
|
472
|
+
extension_lifecycle_lock_release
|
|
473
|
+
die "cannot create an extension registration identity"
|
|
474
|
+
fi
|
|
475
|
+
if ! fm_procevent_source_lock_acquire "$id"; then
|
|
476
|
+
extension_lifecycle_lock_release
|
|
477
|
+
die "cannot lock the source"
|
|
478
|
+
fi
|
|
479
|
+
if ! extension_registration_replacement_safe_locked "$id"; then
|
|
480
|
+
fm_procevent_source_lock_release "$id"
|
|
481
|
+
extension_lifecycle_lock_release
|
|
482
|
+
die "cannot replace extension registration while its prior runner remains active: $id"
|
|
483
|
+
fi
|
|
484
|
+
if ! fm_procevent_extension_registration_publish_locked "$STATE" "$adapter" "$id" \
|
|
485
|
+
"$extension_id" "$extension_version" "$capability_version" "$package_digest" \
|
|
486
|
+
"$binding_digest" "$config_ref" "$registration_token"; then
|
|
487
|
+
fm_procevent_source_lock_release "$id"
|
|
488
|
+
extension_lifecycle_lock_release
|
|
489
|
+
die "cannot publish the extension registration"
|
|
490
|
+
fi
|
|
491
|
+
fm_procevent_source_lock_release "$id"
|
|
492
|
+
extension_lifecycle_lock_release
|
|
493
|
+
printf 'registered: %s (%s from %s@%s)\n' "$id" "$adapter" "$extension_id" "$extension_version"
|
|
494
|
+
printf 'owner-token: %s\n' "$registration_token"
|
|
495
|
+
printf 'retire: bin/fm-procevent.sh retire %s --if-owner %s\n' "$id" "$registration_token"
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
# Publish every durably captured result with no handled acknowledgement yet.
|
|
499
|
+
# Capture already happened, so this only turns durable state into durable
|
|
500
|
+
# events - and it republishes on every call regardless of any earlier
|
|
501
|
+
# publication, so a result stays eligible for re-announcement across restarts
|
|
502
|
+
# and drains until `fm_procevent_mark_handled` records it.
|
|
503
|
+
publish_result() { # <result-file>
|
|
504
|
+
local result=$1 id seq adapter line status=1
|
|
505
|
+
id=$(fm_procevent_result_source_id "$result")
|
|
506
|
+
seq=$(fm_procevent_result_sequence "$result")
|
|
507
|
+
fm_procevent_source_id_valid "$id" || return 1
|
|
508
|
+
adapter=$(fm_procevent_result_adapter "$result" 2>/dev/null || true)
|
|
509
|
+
[ -n "$adapter" ] || return 1
|
|
510
|
+
line=$(fm_procevent_event_line "$adapter" "$id" "$seq") || return 1
|
|
511
|
+
fm_procevent_source_lock_acquire "$id" || return 1
|
|
512
|
+
if ! fm_procevent_is_handled "$STATE" "$id" "$seq"; then
|
|
513
|
+
# A result its own adapter declares a routine no-op is recorded as handled
|
|
514
|
+
# and never announced, so it neither wakes a handler now nor comes back on
|
|
515
|
+
# a later reconcile's re-announcement. Recording it is what makes that
|
|
516
|
+
# silence durable, so both a newly written marker (0) and one a concurrent
|
|
517
|
+
# caller already wrote (1) settle it; only an unrecordable silence (2)
|
|
518
|
+
# falls through and announces, because a silence nothing remembers would
|
|
519
|
+
# otherwise be re-evaluated on every reconcile forever.
|
|
520
|
+
export FM_PROCEVENT_CAPTURE_SOURCE_LOCK_HELD=1
|
|
521
|
+
if adapter_result_is_silent "$adapter" "$result"; then
|
|
522
|
+
unset FM_PROCEVENT_CAPTURE_SOURCE_LOCK_HELD
|
|
523
|
+
fm_procevent_mark_handled "$STATE" "$id" "$seq"
|
|
524
|
+
case "$?" in
|
|
525
|
+
0|1)
|
|
526
|
+
fm_procevent_source_lock_release "$id"
|
|
527
|
+
return 1
|
|
528
|
+
;;
|
|
529
|
+
esac
|
|
530
|
+
fi
|
|
531
|
+
unset FM_PROCEVENT_CAPTURE_SOURCE_LOCK_HELD
|
|
532
|
+
if fm_wake_append check "procevent:$id:$seq" "check: $line"; then
|
|
533
|
+
status=0
|
|
534
|
+
fi
|
|
535
|
+
fi
|
|
536
|
+
fm_procevent_source_lock_release "$id"
|
|
537
|
+
return "$status"
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
publish_pending() { # [result-file-to-skip]
|
|
541
|
+
local skip=${1-} result published=0
|
|
542
|
+
while IFS= read -r result; do
|
|
543
|
+
[ -n "$result" ] || continue
|
|
544
|
+
[ "$result" = "$skip" ] && continue
|
|
545
|
+
if publish_result "$result"; then
|
|
546
|
+
published=$((published + 1))
|
|
547
|
+
fi
|
|
548
|
+
done < <(fm_procevent_pending "$STATE")
|
|
549
|
+
printf '%s\n' "$published"
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
isolate_runner() { # <wait|detach> <source-id>
|
|
553
|
+
local mode=$1 id=$2 program
|
|
554
|
+
# shellcheck disable=SC2016 # Perl owns every $ expression in this literal program.
|
|
555
|
+
program='my $mode = shift @ARGV;
|
|
556
|
+
defined(my $pid = fork) or exit 125;
|
|
557
|
+
if ($pid == 0) {
|
|
558
|
+
setpgrp(0, 0) or exit 125;
|
|
559
|
+
$ENV{FM_PROCEVENT_RUNNER_GROUP} = $$;
|
|
560
|
+
exec @ARGV;
|
|
561
|
+
exit 125;
|
|
562
|
+
}
|
|
563
|
+
exit 0 if $mode eq "detach";
|
|
564
|
+
waitpid($pid, 0) == $pid or exit 125;
|
|
565
|
+
my $status = $?;
|
|
566
|
+
exit(128 + ($status & 127)) if $status & 127;
|
|
567
|
+
exit($status >> 8);'
|
|
568
|
+
if [ "$mode" = wait ]; then
|
|
569
|
+
exec perl -e "$program" "$mode" "$SCRIPT_DIR/fm-procevent.sh" _start "$id"
|
|
570
|
+
fi
|
|
571
|
+
perl -e "$program" "$mode" "$SCRIPT_DIR/fm-procevent.sh" _start "$id" >/dev/null 2>&1 &
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
require_runner_group() {
|
|
575
|
+
local pgid
|
|
576
|
+
[ "${FM_PROCEVENT_RUNNER_GROUP:-}" = "$$" ] \
|
|
577
|
+
|| die "runner process group was not isolated"
|
|
578
|
+
pgid=$(ps -o pgid= -p "$$" 2>/dev/null | tr -d '[:space:]') \
|
|
579
|
+
|| die "cannot inspect runner process group"
|
|
580
|
+
[ -n "$pgid" ] || die "cannot inspect runner process group"
|
|
581
|
+
[ "$pgid" = "$$" ] || die "runner does not lead its process group"
|
|
582
|
+
unset FM_PROCEVENT_RUNNER_GROUP
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
cmd_start_public() {
|
|
586
|
+
local id=${1-}
|
|
587
|
+
[ "$#" -eq 1 ] || usage
|
|
588
|
+
fm_procevent_source_id_valid "$id" || die "source id must be path-safe: $id"
|
|
589
|
+
isolate_runner wait "$id"
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
cmd_start() {
|
|
593
|
+
local id=${1-} adapter out rc claimed bound_rc published_capture=0 handled_capture=0 self_announcing=0
|
|
594
|
+
local extension_owner=0 extension_load_state extension_sequence='' extension_request_id=''
|
|
595
|
+
fm_procevent_source_id_valid "$id" || die "source id must be path-safe: $id"
|
|
596
|
+
require_runner_group
|
|
597
|
+
fm_procevent_source_lock_acquire "$id" || die "cannot lock source: $id"
|
|
598
|
+
if [ ! -f "$(source_file "$id")" ] || [ -L "$(source_file "$id")" ]; then
|
|
599
|
+
fm_procevent_source_lock_release "$id"
|
|
600
|
+
die "source is not registered: $id"
|
|
601
|
+
fi
|
|
602
|
+
if ! adapter=$(read_adapter "$id"); then
|
|
603
|
+
fm_procevent_source_lock_release "$id"
|
|
604
|
+
die "registration is unreadable: $id"
|
|
605
|
+
fi
|
|
606
|
+
if ! fm_procevent_adapter_valid "$adapter"; then
|
|
607
|
+
fm_procevent_source_lock_release "$id"
|
|
608
|
+
die "registration names an invalid adapter"
|
|
609
|
+
fi
|
|
610
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$id"
|
|
611
|
+
extension_load_state=$?
|
|
612
|
+
case "$extension_load_state" in
|
|
613
|
+
0)
|
|
614
|
+
extension_owner=1
|
|
615
|
+
[ "$FM_PROCEVENT_EXTENSION_ADAPTER" = "$adapter" ] || {
|
|
616
|
+
fm_procevent_source_lock_release "$id"
|
|
617
|
+
die "extension registration adapter identity is inconsistent: $id"
|
|
618
|
+
}
|
|
619
|
+
[ -x "$EXTENSION_HOST" ] && [ ! -L "$EXTENSION_HOST" ] || {
|
|
620
|
+
fm_procevent_source_lock_release "$id"
|
|
621
|
+
die "the tracked extension host is unavailable"
|
|
622
|
+
}
|
|
623
|
+
extension_sequence=$(next_result_sequence "$id") \
|
|
624
|
+
|| { fm_procevent_source_lock_release "$id"; die "cannot derive extension request sequence: $id"; }
|
|
625
|
+
extension_request_id=$(extension_source_request_id "$adapter" "$id" "$extension_sequence" \
|
|
626
|
+
"$FM_PROCEVENT_EXTENSION_REGISTRATION_TOKEN" "$FM_PROCEVENT_EXTENSION_PACKAGE_DIGEST") \
|
|
627
|
+
|| { fm_procevent_source_lock_release "$id"; die "cannot derive extension request identity: $id"; }
|
|
628
|
+
ARGV=("$EXTENSION_HOST" process-event "$adapter" source.poll \
|
|
629
|
+
--source-id "$id" --config-ref "$FM_PROCEVENT_EXTENSION_CONFIG_REF" \
|
|
630
|
+
--request-id "$extension_request_id" \
|
|
631
|
+
--expect-extension "$FM_PROCEVENT_EXTENSION_ID" \
|
|
632
|
+
--expect-version "$FM_PROCEVENT_EXTENSION_VERSION" \
|
|
633
|
+
--expect-capability-version "$FM_PROCEVENT_EXTENSION_CAPABILITY_VERSION" \
|
|
634
|
+
--expect-package-digest "$FM_PROCEVENT_EXTENSION_PACKAGE_DIGEST" \
|
|
635
|
+
--expect-binding-digest "$FM_PROCEVENT_EXTENSION_BINDING_DIGEST")
|
|
636
|
+
;;
|
|
637
|
+
1)
|
|
638
|
+
if ! read_argv "$id"; then
|
|
639
|
+
fm_procevent_source_lock_release "$id"
|
|
640
|
+
die "registration argv is unreadable: $id"
|
|
641
|
+
fi
|
|
642
|
+
;;
|
|
643
|
+
*)
|
|
644
|
+
fm_procevent_source_lock_release "$id"
|
|
645
|
+
die "extension registration owner is unreadable: $id"
|
|
646
|
+
;;
|
|
647
|
+
esac
|
|
648
|
+
fm_procevent_claim_acquire_locked "$id" "$FM_HOME" "$$" "$(source_file "$id")"
|
|
649
|
+
claimed=$?
|
|
650
|
+
fm_procevent_source_lock_release "$id"
|
|
651
|
+
case "$claimed" in
|
|
652
|
+
0) ;;
|
|
653
|
+
2) printf 'already owned: %s\n' "$id"; exit 0 ;;
|
|
654
|
+
*) die "cannot claim source: $id" ;;
|
|
655
|
+
esac
|
|
656
|
+
CLAIM_ID=$id
|
|
657
|
+
CLAIM_HOME=$FM_HOME
|
|
658
|
+
CLAIM_PID=$$
|
|
659
|
+
CLAIM_TOKEN=$FM_PROCEVENT_CLAIM_TOKEN
|
|
660
|
+
CLAIM_REG_IDENTITY=$FM_PROCEVENT_CLAIM_REG_IDENTITY
|
|
661
|
+
STAGED_OUTPUT=
|
|
662
|
+
release_start_claim() {
|
|
663
|
+
extension_lifecycle_lock_release 2>/dev/null || true
|
|
664
|
+
[ -z "$STAGED_OUTPUT" ] || rm -f -- "$STAGED_OUTPUT"
|
|
665
|
+
fm_procevent_source_lock_acquire "$CLAIM_ID" 2>/dev/null || return 0
|
|
666
|
+
if fm_procevent_claim_load_locked "$CLAIM_ID" 2>/dev/null \
|
|
667
|
+
&& [ "$FM_PROCEVENT_CLAIM_HOME" = "$CLAIM_HOME" ] \
|
|
668
|
+
&& [ "$FM_PROCEVENT_CLAIM_PID" = "$CLAIM_PID" ] \
|
|
669
|
+
&& [ "$FM_PROCEVENT_CLAIM_TOKEN" = "$CLAIM_TOKEN" ] \
|
|
670
|
+
&& [ "$FM_PROCEVENT_CLAIM_TERMINAL" = terminal ]; then
|
|
671
|
+
fm_procevent_source_lock_release "$CLAIM_ID" 2>/dev/null || true
|
|
672
|
+
return 0
|
|
673
|
+
fi
|
|
674
|
+
fm_procevent_claim_release_locked "$CLAIM_ID" "$CLAIM_HOME" "$CLAIM_PID" "$CLAIM_TOKEN" 2>/dev/null || true
|
|
675
|
+
fm_procevent_source_lock_release "$CLAIM_ID" 2>/dev/null || true
|
|
676
|
+
}
|
|
677
|
+
trap release_start_claim EXIT
|
|
678
|
+
local runner inbox reservation_dir
|
|
679
|
+
if [ "$extension_owner" -eq 1 ]; then
|
|
680
|
+
fm_procevent_extension_staging_prepare "$STATE" \
|
|
681
|
+
|| die "cannot safely prepare the external registry staging boundary"
|
|
682
|
+
inbox=$(fm_procevent_capture_inbox_prepare "$STATE") \
|
|
683
|
+
|| die "cannot durably capture the extension result"
|
|
684
|
+
CDPATH='' cd -- "$REG" 2>/dev/null \
|
|
685
|
+
|| die "cannot safely prepare the external registry staging boundary"
|
|
686
|
+
[ "$(pwd -P)" = "$REG" ] \
|
|
687
|
+
|| die "cannot safely prepare the external registry staging boundary"
|
|
688
|
+
exec 9<. || die "cannot retain the external registry staging boundary"
|
|
689
|
+
CDPATH='' cd -- "$inbox" 2>/dev/null \
|
|
690
|
+
|| die "cannot durably capture the extension result"
|
|
691
|
+
[ "$(pwd -P)" = "$inbox" ] \
|
|
692
|
+
|| die "cannot durably capture the extension result"
|
|
693
|
+
exec 8<. || die "cannot retain the external capture boundary"
|
|
694
|
+
reservation_dir=$(fm_procevent_capture_reservation_prepare "$STATE") \
|
|
695
|
+
|| die "cannot retain the external capture reservation boundary"
|
|
696
|
+
exec 6<"$reservation_dir" || die "cannot retain the external capture reservation boundary"
|
|
697
|
+
FM_PROCEVENT_CAPTURE_PINNED_INBOX=1
|
|
698
|
+
export FM_PROCEVENT_CAPTURE_INBOX_FD=8
|
|
699
|
+
runner="$id.runner"
|
|
700
|
+
else
|
|
701
|
+
runner=$(runner_file "$id")
|
|
702
|
+
fi
|
|
703
|
+
|
|
704
|
+
case "$MAX_OUTPUT_BYTES" in ''|*[!0-9]*) die "FM_PROCEVENT_MAX_OUTPUT_BYTES must be a nonnegative integer" ;; esac
|
|
705
|
+
if [ "$extension_owner" -eq 1 ]; then
|
|
706
|
+
out=".$id.$CLAIM_TOKEN.output"
|
|
707
|
+
else
|
|
708
|
+
out=$(staging_file "$id" "$CLAIM_TOKEN")
|
|
709
|
+
printf '%s\n' "$$" > "$runner" 2>/dev/null || true
|
|
710
|
+
chmod 0600 "$runner" 2>/dev/null || true
|
|
711
|
+
fi
|
|
712
|
+
# Built-in adapters do not run the extension capture helper, so keep this
|
|
713
|
+
# sentinel defined while sharing the no-result branch below under `set -u`.
|
|
714
|
+
local truncated=0 capture_state='' durable='' reservation_terminal='' reservation_silent=''
|
|
715
|
+
if [ "$extension_owner" -eq 1 ]; then
|
|
716
|
+
capture_state=$(perl "$SCRIPT_DIR/fm-procevent-extension-capture.pl" \
|
|
717
|
+
9 8 6 "$id" "$adapter" "$FM_PROCEVENT_EXTENSION_ID" \
|
|
718
|
+
"$FM_PROCEVENT_EXTENSION_VERSION" "$FM_PROCEVENT_EXTENSION_CAPABILITY_VERSION" \
|
|
719
|
+
"$FM_PROCEVENT_EXTENSION_PACKAGE_DIGEST" "$FM_PROCEVENT_EXTENSION_BINDING_DIGEST" \
|
|
720
|
+
"$CLAIM_TOKEN" "$runner" "$out" "$$" "$(fm_pid_identity "$$")" "$MAX_OUTPUT_BYTES" -- "${ARGV[@]}") \
|
|
721
|
+
|| die "cannot safely stage the extension result"
|
|
722
|
+
IFS=$'\t' read -r capture_state durable rc truncated reservation_terminal reservation_silent <<EOF
|
|
723
|
+
$capture_state
|
|
724
|
+
EOF
|
|
725
|
+
exec 9<&-
|
|
726
|
+
case "$capture_state" in
|
|
727
|
+
captured|no-result) ;;
|
|
728
|
+
failure) die "external source invocation failed: $id" ;;
|
|
729
|
+
*) die "cannot safely stage the extension result" ;;
|
|
730
|
+
esac
|
|
731
|
+
if [ "$capture_state" = captured ]; then
|
|
732
|
+
FM_PROCEVENT_CAPTURE_RESERVATION_TERMINAL=$reservation_terminal
|
|
733
|
+
FM_PROCEVENT_CAPTURE_RESERVATION_SILENT=$reservation_silent
|
|
734
|
+
fi
|
|
735
|
+
else
|
|
736
|
+
[ ! -e "$out" ] && [ ! -L "$out" ] || die "cannot safely stage output"
|
|
737
|
+
(umask 077; : > "$out") || die "cannot stage output"
|
|
738
|
+
STAGED_OUTPUT=$out
|
|
739
|
+
"${ARGV[@]}" 2>/dev/null | perl -e '
|
|
740
|
+
use strict;
|
|
741
|
+
use warnings;
|
|
742
|
+
my $limit = shift;
|
|
743
|
+
my ($written, $truncated) = (0, 0);
|
|
744
|
+
while (1) {
|
|
745
|
+
my $count = sysread(STDIN, my $buffer, 65536);
|
|
746
|
+
exit 2 unless defined $count;
|
|
747
|
+
last if $count == 0;
|
|
748
|
+
my $take = $written < $limit ? $limit - $written : 0;
|
|
749
|
+
$take = $count if $take > $count;
|
|
750
|
+
if ($take > 0) {
|
|
751
|
+
my $offset = 0;
|
|
752
|
+
while ($offset < $take) {
|
|
753
|
+
my $count_written = syswrite(STDOUT, $buffer, $take - $offset, $offset);
|
|
754
|
+
exit 2 unless defined $count_written;
|
|
755
|
+
$offset += $count_written;
|
|
756
|
+
}
|
|
757
|
+
$written += $take;
|
|
758
|
+
}
|
|
759
|
+
$truncated = 1 if $take < $count;
|
|
760
|
+
}
|
|
761
|
+
exit($truncated ? 3 : 0);
|
|
762
|
+
' "$MAX_OUTPUT_BYTES" > "$out"
|
|
763
|
+
local pipe_status=("${PIPESTATUS[@]}")
|
|
764
|
+
rc=${pipe_status[0]}
|
|
765
|
+
bound_rc=${pipe_status[1]}
|
|
766
|
+
case "$bound_rc" in
|
|
767
|
+
0) ;;
|
|
768
|
+
3) truncated=1 ;;
|
|
769
|
+
*) die "cannot bound source output" ;;
|
|
770
|
+
esac
|
|
771
|
+
fi
|
|
772
|
+
|
|
773
|
+
if [ "$capture_state" = no-result ] || { [ "$extension_owner" -eq 0 ] && [ "$rc" -ne 0 ] && [ ! -s "$out" ]; }; then
|
|
774
|
+
# No usable result. Leave the registration armed; the adapter decides
|
|
775
|
+
# whether a nonzero exit is terminal when it handles the next result.
|
|
776
|
+
if [ "$extension_owner" -eq 0 ]; then
|
|
777
|
+
rm -f -- "$out" "$runner"
|
|
778
|
+
fi
|
|
779
|
+
printf 'no-result: %s (exit %s)\n' "$id" "$rc"
|
|
780
|
+
exit 0
|
|
781
|
+
fi
|
|
782
|
+
|
|
783
|
+
if [ "$extension_owner" -eq 1 ]; then
|
|
784
|
+
durable="./$durable"
|
|
785
|
+
fi
|
|
786
|
+
|
|
787
|
+
if [ "$extension_owner" -eq 1 ]; then
|
|
788
|
+
:
|
|
789
|
+
else
|
|
790
|
+
durable=$(fm_procevent_capture "$STATE" "$id" "$adapter" "$out") \
|
|
791
|
+
|| { rm -f -- "$out"; die "cannot durably capture the result"; }
|
|
792
|
+
fi
|
|
793
|
+
[ "$extension_owner" -eq 1 ] || rm -f -- "$out"
|
|
794
|
+
STAGED_OUTPUT=
|
|
795
|
+
[ "$truncated" -eq 1 ] && printf 'truncated: %s at %s bytes\n' "$id" "$MAX_OUTPUT_BYTES" >&2
|
|
796
|
+
|
|
797
|
+
# Independent of publication and acknowledgement, so it runs once per capture
|
|
798
|
+
# for every adapter and cannot change what the handler receives.
|
|
799
|
+
if [ "$extension_owner" -eq 0 ] \
|
|
800
|
+
&& feed_keyed_answers "$adapter" "$id" "$durable"; then
|
|
801
|
+
printf 'answers-fed: %s\n' "$id"
|
|
802
|
+
fi
|
|
803
|
+
|
|
804
|
+
# A self-announcing adapter's autohandle announces through its own durable
|
|
805
|
+
# downstream channel, so publication waits until after application and covers
|
|
806
|
+
# only what remains unhandled; every other adapter keeps the strict
|
|
807
|
+
# publish-before-apply order (announcement-ownership note in the header).
|
|
808
|
+
if [ "$extension_owner" -eq 0 ] && adapter_self_announcing "$adapter"; then
|
|
809
|
+
self_announcing=1
|
|
810
|
+
else
|
|
811
|
+
if publish_result "$durable"; then
|
|
812
|
+
published_capture=1
|
|
813
|
+
elif fm_procevent_is_handled "$STATE" "$id" "$(fm_procevent_result_sequence "$durable")"; then
|
|
814
|
+
handled_capture=1
|
|
815
|
+
fi
|
|
816
|
+
publish_pending "$durable" >/dev/null
|
|
817
|
+
fi
|
|
818
|
+
[ "$extension_owner" -eq 1 ] || rm -f -- "$runner"
|
|
819
|
+
if [ "$self_announcing" -eq 1 ]; then
|
|
820
|
+
if adapter_autohandle "$adapter" "$id" "$durable"; then
|
|
821
|
+
printf 'autohandled: %s\n' "$id"
|
|
822
|
+
else
|
|
823
|
+
printf 'not-autohandled: %s (left for the handler; still unacknowledged)\n' "$id" >&2
|
|
824
|
+
fi
|
|
825
|
+
# publish_result's own handled guard keeps a fully autohandled capture
|
|
826
|
+
# quiet here; anything the adapter left unhandled is announced exactly as
|
|
827
|
+
# before, and a crash above leaves it to reconcile's re-announcement.
|
|
828
|
+
if publish_result "$durable"; then
|
|
829
|
+
published_capture=1
|
|
830
|
+
fi
|
|
831
|
+
publish_pending "$durable" >/dev/null
|
|
832
|
+
elif [ "$handled_capture" -eq 1 ]; then
|
|
833
|
+
:
|
|
834
|
+
elif [ "$extension_owner" -eq 0 ] \
|
|
835
|
+
&& [ "$published_capture" -eq 1 ] \
|
|
836
|
+
&& adapter_autohandle "$adapter" "$id" "$durable"; then
|
|
837
|
+
printf 'autohandled: %s\n' "$id"
|
|
838
|
+
else
|
|
839
|
+
printf 'not-autohandled: %s (left for the handler; still unacknowledged)\n' "$id" >&2
|
|
840
|
+
fi
|
|
841
|
+
if adapter_result_is_terminal "$adapter" "$durable"; then
|
|
842
|
+
if retire_owned_terminal_source "$id"; then
|
|
843
|
+
printf 'retired: %s (adapter classified the captured result terminal)\n' "$id"
|
|
844
|
+
else
|
|
845
|
+
printf 'cannot retire terminal source; it remains registered: %s\n' "$id" >&2
|
|
846
|
+
fi
|
|
847
|
+
fi
|
|
848
|
+
printf 'captured: %s\n' "$durable"
|
|
849
|
+
if [ "$extension_owner" -eq 1 ]; then
|
|
850
|
+
fm_procevent_claim_capture_reservation_remove_locked || true
|
|
851
|
+
exec 6<&-
|
|
852
|
+
fi
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
# Retire a source this runner owns because its adapter classified the captured
|
|
856
|
+
# result terminal. Ownership is re-proved, the registration is dropped, and this
|
|
857
|
+
# runner's own claim is released under ONE source-lock hold, so no concurrent
|
|
858
|
+
# reconcile can observe a registered source with no owner (and start a
|
|
859
|
+
# replacement) or an owned claim with no registration (and signal this runner
|
|
860
|
+
# mid-exit), and a generation this runner no longer owns is never unregistered.
|
|
861
|
+
# The EXIT trap's own release then no-ops, because the generation is already gone.
|
|
862
|
+
retire_owned_terminal_source() { # <source-id>
|
|
863
|
+
local id=$1 status=0 registration current_identity
|
|
864
|
+
registration=$(source_file "$id")
|
|
865
|
+
fm_procevent_source_lock_acquire "$id" || return 1
|
|
866
|
+
if fm_procevent_claim_load_locked "$id" 2>/dev/null \
|
|
867
|
+
&& [ "$FM_PROCEVENT_CLAIM_HOME" = "$CLAIM_HOME" ] \
|
|
868
|
+
&& [ "$FM_PROCEVENT_CLAIM_PID" = "$CLAIM_PID" ] \
|
|
869
|
+
&& [ "$FM_PROCEVENT_CLAIM_TOKEN" = "$CLAIM_TOKEN" ] \
|
|
870
|
+
&& [ "$FM_PROCEVENT_CLAIM_REG_IDENTITY" = "$CLAIM_REG_IDENTITY" ] \
|
|
871
|
+
&& current_identity=$(fm_pr_file_identity "$registration" 2>/dev/null) \
|
|
872
|
+
&& [ "$current_identity" = "$CLAIM_REG_IDENTITY" ] \
|
|
873
|
+
&& fm_procevent_claim_mark_terminal_locked "$id" "$CLAIM_HOME" "$CLAIM_PID" "$CLAIM_TOKEN"; then
|
|
874
|
+
if rm -f -- "$registration" && [ ! -e "$registration" ] && [ ! -L "$registration" ]; then
|
|
875
|
+
fm_procevent_claim_release_locked "$id" "$CLAIM_HOME" "$CLAIM_PID" "$CLAIM_TOKEN" || status=1
|
|
876
|
+
else
|
|
877
|
+
status=1
|
|
878
|
+
fi
|
|
879
|
+
else
|
|
880
|
+
status=1
|
|
881
|
+
fi
|
|
882
|
+
fm_procevent_source_lock_release "$id"
|
|
883
|
+
return "$status"
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
# Start a runner outside the watcher cycle that noticed it was missing. The
|
|
887
|
+
# public start boundary establishes its own process group before claiming.
|
|
888
|
+
detach_runner() { # <source-id>
|
|
889
|
+
isolate_runner detach "$1"
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
cmd_reconcile() {
|
|
893
|
+
local rec id published started=0 stopped=0 uncertain=0 claim owner pid token identity claim_state stop_state
|
|
894
|
+
published=$(publish_pending)
|
|
895
|
+
|
|
896
|
+
# Stop a runner this home owns whose source is no longer registered. Without
|
|
897
|
+
# this, unregistering a source that never completes leaves its child blocked
|
|
898
|
+
# forever with nothing left to reap it.
|
|
899
|
+
for claim in "$(fm_procevent_claim_root)"/*.claim; do
|
|
900
|
+
[ -e "$claim" ] || continue
|
|
901
|
+
id=${claim##*/}; id=${id%.claim}
|
|
902
|
+
fm_procevent_source_id_valid "$id" || continue
|
|
903
|
+
fm_procevent_source_lock_acquire "$id" || continue
|
|
904
|
+
if [ -f "$(source_file "$id")" ] && [ ! -L "$(source_file "$id")" ]; then
|
|
905
|
+
fm_procevent_source_lock_release "$id"
|
|
906
|
+
continue
|
|
907
|
+
fi
|
|
908
|
+
if ! fm_procevent_claim_load_locked "$id" 2>/dev/null; then
|
|
909
|
+
uncertain=$((uncertain + 1))
|
|
910
|
+
fm_procevent_source_lock_release "$id"
|
|
911
|
+
continue
|
|
912
|
+
fi
|
|
913
|
+
owner=$FM_PROCEVENT_CLAIM_HOME
|
|
914
|
+
pid=$FM_PROCEVENT_CLAIM_PID
|
|
915
|
+
token=$FM_PROCEVENT_CLAIM_TOKEN
|
|
916
|
+
identity=$FM_PROCEVENT_CLAIM_IDENTITY
|
|
917
|
+
if [ "$owner" != "$FM_HOME" ]; then
|
|
918
|
+
fm_procevent_source_lock_release "$id"
|
|
919
|
+
continue
|
|
920
|
+
fi
|
|
921
|
+
stop_runner_pid "$pid" "$identity"
|
|
922
|
+
stop_state=$?
|
|
923
|
+
case "$stop_state" in
|
|
924
|
+
0|1)
|
|
925
|
+
if fm_procevent_claim_release_locked "$id" "$owner" "$pid" "$token" 2>/dev/null; then
|
|
926
|
+
rm -f -- "$(staging_file "$id" "$token")"
|
|
927
|
+
rm -f -- "$(runner_file "$id")"
|
|
928
|
+
stopped=$((stopped + 1))
|
|
929
|
+
else
|
|
930
|
+
uncertain=$((uncertain + 1))
|
|
931
|
+
fi
|
|
932
|
+
;;
|
|
933
|
+
*) uncertain=$((uncertain + 1)) ;;
|
|
934
|
+
esac
|
|
935
|
+
fm_procevent_source_lock_release "$id"
|
|
936
|
+
done
|
|
937
|
+
|
|
938
|
+
if [ -d "$REG" ]; then
|
|
939
|
+
for rec in "$REG"/*.source; do
|
|
940
|
+
[ -e "$rec" ] || continue
|
|
941
|
+
id=${rec##*/}; id=${id%.source}
|
|
942
|
+
fm_procevent_source_id_valid "$id" || continue
|
|
943
|
+
fm_procevent_source_lock_acquire "$id" || continue
|
|
944
|
+
if [ -f "$(source_file "$id")" ] && [ ! -L "$(source_file "$id")" ]; then
|
|
945
|
+
fm_procevent_claim_state_locked "$id"
|
|
946
|
+
claim_state=$?
|
|
947
|
+
if [ "$claim_state" -eq 1 ]; then
|
|
948
|
+
if ! cleanup_extension_registration_invocations_locked "$id"; then
|
|
949
|
+
uncertain=$((uncertain + 1))
|
|
950
|
+
fm_procevent_source_lock_release "$id"
|
|
951
|
+
continue
|
|
952
|
+
fi
|
|
953
|
+
fm_procevent_source_lock_release "$id"
|
|
954
|
+
detach_runner "$id"
|
|
955
|
+
started=$((started + 1))
|
|
956
|
+
continue
|
|
957
|
+
elif [ "$claim_state" -eq 4 ]; then
|
|
958
|
+
owner=$FM_PROCEVENT_CLAIM_HOME
|
|
959
|
+
pid=$FM_PROCEVENT_CLAIM_PID
|
|
960
|
+
token=$FM_PROCEVENT_CLAIM_TOKEN
|
|
961
|
+
if [ "$owner" = "$FM_HOME" ] \
|
|
962
|
+
&& rm -f -- "$(source_file "$id")" \
|
|
963
|
+
&& [ ! -e "$(source_file "$id")" ] \
|
|
964
|
+
&& [ ! -L "$(source_file "$id")" ] \
|
|
965
|
+
&& fm_procevent_claim_release_locked "$id" "$owner" "$pid" "$token" 2>/dev/null; then
|
|
966
|
+
stopped=$((stopped + 1))
|
|
967
|
+
else
|
|
968
|
+
uncertain=$((uncertain + 1))
|
|
969
|
+
fi
|
|
970
|
+
elif [ "$claim_state" -eq 3 ]; then
|
|
971
|
+
# The leader crashed but its owned group is still consuming the
|
|
972
|
+
# source. Never start a replacement alongside it: stop that group and
|
|
973
|
+
# release its generation first, and if either cannot be proved, keep
|
|
974
|
+
# the claim and retry on a later cycle rather than adding a second
|
|
975
|
+
# poller. Only the owning home may signal its own group.
|
|
976
|
+
owner=$FM_PROCEVENT_CLAIM_HOME
|
|
977
|
+
pid=$FM_PROCEVENT_CLAIM_PID
|
|
978
|
+
token=$FM_PROCEVENT_CLAIM_TOKEN
|
|
979
|
+
identity=$FM_PROCEVENT_CLAIM_IDENTITY
|
|
980
|
+
stop_state=2
|
|
981
|
+
if [ "$owner" = "$FM_HOME" ]; then
|
|
982
|
+
stop_runner_pid "$pid" "$identity"
|
|
983
|
+
stop_state=$?
|
|
984
|
+
fi
|
|
985
|
+
if [ "$stop_state" -eq 0 ] \
|
|
986
|
+
&& cleanup_extension_registration_invocations_locked "$id" \
|
|
987
|
+
&& fm_procevent_claim_release_locked "$id" "$owner" "$pid" "$token" 2>/dev/null; then
|
|
988
|
+
rm -f -- "$(staging_file "$id" "$token")"
|
|
989
|
+
rm -f -- "$(runner_file "$id")"
|
|
990
|
+
fm_procevent_source_lock_release "$id"
|
|
991
|
+
detach_runner "$id"
|
|
992
|
+
started=$((started + 1))
|
|
993
|
+
continue
|
|
994
|
+
fi
|
|
995
|
+
uncertain=$((uncertain + 1))
|
|
996
|
+
elif [ "$claim_state" -eq 2 ]; then
|
|
997
|
+
uncertain=$((uncertain + 1))
|
|
998
|
+
fi
|
|
999
|
+
fi
|
|
1000
|
+
fm_procevent_source_lock_release "$id"
|
|
1001
|
+
done
|
|
1002
|
+
fi
|
|
1003
|
+
printf 'reconciled: published=%s started=%s stopped=%s uncertain=%s\n' "$published" "$started" "$stopped" "$uncertain"
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
# Stop a runner and the child it is blocked on. A runner started by reconcile is
|
|
1007
|
+
# its own process group leader, so the group signal is what actually reaches the
|
|
1008
|
+
# blocking child - signalling only the runner would leave that child alive and
|
|
1009
|
+
# reparented, which is exactly how a source that never completes leaks.
|
|
1010
|
+
stop_runner_pid() { # <pid> <identity>
|
|
1011
|
+
local pid=${1-} identity=${2-} state pgid i=0
|
|
1012
|
+
case "$pid" in ''|*[!0-9]*) return 2 ;; esac
|
|
1013
|
+
[ -n "$identity" ] || return 2
|
|
1014
|
+
fm_procevent_pid_state "$pid" "$identity"
|
|
1015
|
+
state=$?
|
|
1016
|
+
case "$state" in
|
|
1017
|
+
0)
|
|
1018
|
+
# A live identity-matched leader still owns its group, so prove the group
|
|
1019
|
+
# really is the one this pid leads before signalling it.
|
|
1020
|
+
pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d '[:space:]') || return 2
|
|
1021
|
+
[ "$pgid" = "$pid" ] || return 2
|
|
1022
|
+
;;
|
|
1023
|
+
3)
|
|
1024
|
+
# The leader crashed but its owned group is still running. Its pgid cannot
|
|
1025
|
+
# be read from the dead leader, and it does not need to be: only an absent
|
|
1026
|
+
# leader reaches this state, so the group cannot belong to a reused pid.
|
|
1027
|
+
;;
|
|
1028
|
+
*) return "$state" ;;
|
|
1029
|
+
esac
|
|
1030
|
+
kill -TERM -"$pid" 2>/dev/null || return 2
|
|
1031
|
+
while [ "$i" -lt 20 ]; do
|
|
1032
|
+
kill -0 -"$pid" 2>/dev/null || return 0
|
|
1033
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
1034
|
+
fm_procevent_pid_state "$pid" "$identity"
|
|
1035
|
+
state=$?
|
|
1036
|
+
[ "$state" -eq 2 ] && return 2
|
|
1037
|
+
fi
|
|
1038
|
+
sleep 0.1
|
|
1039
|
+
i=$((i + 1))
|
|
1040
|
+
done
|
|
1041
|
+
kill -KILL -"$pid" 2>/dev/null || return 2
|
|
1042
|
+
i=0
|
|
1043
|
+
while [ "$i" -lt 20 ]; do
|
|
1044
|
+
kill -0 -"$pid" 2>/dev/null || return 0
|
|
1045
|
+
sleep 0.1
|
|
1046
|
+
i=$((i + 1))
|
|
1047
|
+
done
|
|
1048
|
+
return 2
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
# The owned handling interface: durably and idempotently record that a
|
|
1052
|
+
# captured result has been fully handled, keyed by the exact source id and
|
|
1053
|
+
# sequence generation. Serialized under the same per-source boundary as every
|
|
1054
|
+
# other mutation here, on top of the marker's own atomic O_EXCL create, so a
|
|
1055
|
+
# caller can trust the reported first-time/repeat distinction to authorize a
|
|
1056
|
+
# paired external effect at most once.
|
|
1057
|
+
cmd_classify() {
|
|
1058
|
+
local result=${1-} adapter script owner_state
|
|
1059
|
+
[ "$#" -eq 1 ] || usage
|
|
1060
|
+
adapter=$(fm_procevent_result_adapter "$result" 2>/dev/null) \
|
|
1061
|
+
|| die "captured result has no readable adapter identity: $result"
|
|
1062
|
+
fm_procevent_result_extension_load "$result"
|
|
1063
|
+
owner_state=$?
|
|
1064
|
+
case "$owner_state" in
|
|
1065
|
+
0) extension_result_command "$adapter" result.classify "$result"; return $? ;;
|
|
1066
|
+
2) die "captured extension result has an unreadable owner identity: $result" ;;
|
|
1067
|
+
esac
|
|
1068
|
+
script=$(adapter_script "$adapter")
|
|
1069
|
+
[ -f "$script" ] && [ ! -L "$script" ] \
|
|
1070
|
+
|| die "captured result adapter is unavailable: $adapter"
|
|
1071
|
+
"$script" classify "$result"
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
cmd_handled() {
|
|
1075
|
+
local id=${1-} seq=${2-} status
|
|
1076
|
+
fm_procevent_source_id_valid "$id" || die "source id must be path-safe: $id"
|
|
1077
|
+
case "$seq" in ''|*[!0-9]*) die "sequence must be a nonnegative integer: $seq" ;; esac
|
|
1078
|
+
fm_procevent_source_lock_acquire "$id" || die "cannot lock source: $id"
|
|
1079
|
+
fm_procevent_mark_handled "$STATE" "$id" "$seq"
|
|
1080
|
+
status=$?
|
|
1081
|
+
fm_procevent_source_lock_release "$id"
|
|
1082
|
+
case "$status" in
|
|
1083
|
+
0) printf 'handled: %s %s\n' "$id" "$seq" ;;
|
|
1084
|
+
1) printf 'already-handled: %s %s\n' "$id" "$seq" ;;
|
|
1085
|
+
*) die "cannot durably record handling: $id $seq" ;;
|
|
1086
|
+
esac
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
cmd_retire() {
|
|
1090
|
+
local id=${1-} condition=${2-} adapter='' sep='' expected_owner='' owner='' pid='' token='' identity='' stop_state owner_state
|
|
1091
|
+
local extension_binding_digest=''
|
|
1092
|
+
fm_procevent_source_id_valid "$id" || die "source id must be path-safe: $id"
|
|
1093
|
+
case "$condition" in
|
|
1094
|
+
'') [ "$#" -eq 1 ] || usage ;;
|
|
1095
|
+
--if-absent) [ "$#" -eq 2 ] || usage ;;
|
|
1096
|
+
--if-owner)
|
|
1097
|
+
[ "$#" -eq 3 ] || usage
|
|
1098
|
+
expected_owner=${3-}
|
|
1099
|
+
fm_procevent_extension_registration_token_valid "$expected_owner" \
|
|
1100
|
+
|| die "extension registration owner token is invalid"
|
|
1101
|
+
;;
|
|
1102
|
+
--if-matches)
|
|
1103
|
+
adapter=${3-}
|
|
1104
|
+
sep=${4-}
|
|
1105
|
+
shift 4 2>/dev/null || usage
|
|
1106
|
+
fm_procevent_adapter_valid "$adapter" \
|
|
1107
|
+
|| die "adapter name must be lowercase alphanumeric or dash: $adapter"
|
|
1108
|
+
[ "$sep" = -- ] && [ "$#" -ge 1 ] || usage
|
|
1109
|
+
;;
|
|
1110
|
+
*) usage ;;
|
|
1111
|
+
esac
|
|
1112
|
+
fm_procevent_source_lock_acquire "$id" || die "cannot lock source: $id"
|
|
1113
|
+
if [ -e "$(source_file "$id")" ] || [ -L "$(source_file "$id")" ]; then
|
|
1114
|
+
if [ -z "$condition" ]; then
|
|
1115
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$id"
|
|
1116
|
+
owner_state=$?
|
|
1117
|
+
case "$owner_state" in
|
|
1118
|
+
0)
|
|
1119
|
+
fm_procevent_source_lock_release "$id"
|
|
1120
|
+
die "extension registration requires its exact --if-owner token: $id"
|
|
1121
|
+
;;
|
|
1122
|
+
2)
|
|
1123
|
+
fm_procevent_source_lock_release "$id"
|
|
1124
|
+
die "cannot safely read extension registration ownership: $id"
|
|
1125
|
+
;;
|
|
1126
|
+
esac
|
|
1127
|
+
fi
|
|
1128
|
+
case "$condition" in
|
|
1129
|
+
--if-absent)
|
|
1130
|
+
fm_procevent_source_lock_release "$id"
|
|
1131
|
+
die "source registration does not match the expected owner: $id"
|
|
1132
|
+
;;
|
|
1133
|
+
--if-matches)
|
|
1134
|
+
if ! fm_procevent_registration_matches_locked "$STATE" "$adapter" "$id" "$@"; then
|
|
1135
|
+
fm_procevent_source_lock_release "$id"
|
|
1136
|
+
die "source registration does not match the expected owner: $id"
|
|
1137
|
+
fi
|
|
1138
|
+
;;
|
|
1139
|
+
--if-owner)
|
|
1140
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$id"
|
|
1141
|
+
owner_state=$?
|
|
1142
|
+
if [ "$owner_state" -ne 0 ] \
|
|
1143
|
+
|| [ "$FM_PROCEVENT_EXTENSION_REGISTRATION_TOKEN" != "$expected_owner" ]; then
|
|
1144
|
+
fm_procevent_source_lock_release "$id"
|
|
1145
|
+
die "source registration does not match the expected owner: $id"
|
|
1146
|
+
fi
|
|
1147
|
+
extension_binding_digest=$FM_PROCEVENT_EXTENSION_BINDING_DIGEST
|
|
1148
|
+
;;
|
|
1149
|
+
esac
|
|
1150
|
+
elif [ "$condition" = --if-owner ] \
|
|
1151
|
+
&& { [ -e "$(fm_procevent_claim_path "$id")" ] || [ -L "$(fm_procevent_claim_path "$id")" ]; }; then
|
|
1152
|
+
fm_procevent_source_lock_release "$id"
|
|
1153
|
+
die "source owner cannot be proved after its registration disappeared: $id"
|
|
1154
|
+
fi
|
|
1155
|
+
if [ -e "$(fm_procevent_claim_path "$id")" ]; then
|
|
1156
|
+
if ! fm_procevent_claim_load_locked "$id" 2>/dev/null; then
|
|
1157
|
+
fm_procevent_source_lock_release "$id"
|
|
1158
|
+
die "cannot safely read source ownership: $id"
|
|
1159
|
+
fi
|
|
1160
|
+
if [ "$FM_PROCEVENT_CLAIM_HOME" = "$FM_HOME" ]; then
|
|
1161
|
+
owner=$FM_PROCEVENT_CLAIM_HOME
|
|
1162
|
+
pid=$FM_PROCEVENT_CLAIM_PID
|
|
1163
|
+
token=$FM_PROCEVENT_CLAIM_TOKEN
|
|
1164
|
+
identity=$FM_PROCEVENT_CLAIM_IDENTITY
|
|
1165
|
+
stop_runner_pid "$pid" "$identity"
|
|
1166
|
+
stop_state=$?
|
|
1167
|
+
if [ "$stop_state" -eq 2 ]; then
|
|
1168
|
+
fm_procevent_source_lock_release "$id"
|
|
1169
|
+
die "cannot confirm runner identity; source remains registered: $id"
|
|
1170
|
+
fi
|
|
1171
|
+
if [ -n "$extension_binding_digest" ] \
|
|
1172
|
+
&& ! cleanup_extension_binding_invocations "$extension_binding_digest"; then
|
|
1173
|
+
fm_procevent_source_lock_release "$id"
|
|
1174
|
+
die "cannot prove external adapter cleanup; source remains registered: $id"
|
|
1175
|
+
fi
|
|
1176
|
+
if ! fm_procevent_claim_release_locked "$id" "$owner" "$pid" "$token"; then
|
|
1177
|
+
fm_procevent_source_lock_release "$id"
|
|
1178
|
+
die "cannot release source ownership: $id"
|
|
1179
|
+
fi
|
|
1180
|
+
rm -f -- "$(staging_file "$id" "$token")"
|
|
1181
|
+
fi
|
|
1182
|
+
elif [ -n "$extension_binding_digest" ] \
|
|
1183
|
+
&& ! cleanup_extension_binding_invocations "$extension_binding_digest"; then
|
|
1184
|
+
fm_procevent_source_lock_release "$id"
|
|
1185
|
+
die "cannot prove external adapter cleanup; source remains registered: $id"
|
|
1186
|
+
fi
|
|
1187
|
+
rm -f -- "$(source_file "$id")"
|
|
1188
|
+
rm -f -- "$(runner_file "$id")"
|
|
1189
|
+
fm_procevent_source_lock_release "$id"
|
|
1190
|
+
# A retired source produces no further answer, so drop any decision binding it
|
|
1191
|
+
# carried. Generic and idempotent: the binding owner is asked to forget this
|
|
1192
|
+
# source id, and an unbound source is unaffected.
|
|
1193
|
+
"$SCRIPT_DIR/fm-captain-hold.sh" unbind "$id" >/dev/null 2>&1 || true
|
|
1194
|
+
printf 'retired: %s\n' "$id"
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
sweep_add_id() {
|
|
1198
|
+
local id=$1
|
|
1199
|
+
case "$SWEEP_IDS" in
|
|
1200
|
+
*$'\n'"$id"$'\n'*) ;;
|
|
1201
|
+
*) SWEEP_IDS+="$id"$'\n' ;;
|
|
1202
|
+
esac
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
sweep_relevant_state() {
|
|
1206
|
+
local path owner
|
|
1207
|
+
for path in "$STATE/extension-invocations"/*.owner.json; do
|
|
1208
|
+
[ -e "$path" ] && return 0
|
|
1209
|
+
done
|
|
1210
|
+
for path in "$REG"/*.source "$REG"/*.runner; do
|
|
1211
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
1212
|
+
return 0
|
|
1213
|
+
fi
|
|
1214
|
+
done
|
|
1215
|
+
for path in "$(fm_procevent_claim_root)"/*.claim; do
|
|
1216
|
+
[ -f "$path" ] && [ ! -L "$path" ] || continue
|
|
1217
|
+
IFS= read -r owner < "$path" 2>/dev/null || continue
|
|
1218
|
+
[ "$owner" = "$FM_HOME" ] && return 0
|
|
1219
|
+
done
|
|
1220
|
+
return 1
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
sweep_source_preflight() {
|
|
1224
|
+
local id=$1 state
|
|
1225
|
+
fm_procevent_source_lock_acquire "$id" || return 1
|
|
1226
|
+
if [ -e "$(fm_procevent_claim_path "$id")" ] || [ -L "$(fm_procevent_claim_path "$id")" ]; then
|
|
1227
|
+
if ! fm_procevent_claim_load_locked "$id" 2>/dev/null; then
|
|
1228
|
+
fm_procevent_source_lock_release "$id"
|
|
1229
|
+
return 1
|
|
1230
|
+
fi
|
|
1231
|
+
if [ "$FM_PROCEVENT_CLAIM_HOME" = "$FM_HOME" ]; then
|
|
1232
|
+
fm_procevent_pid_state "$FM_PROCEVENT_CLAIM_PID" "$FM_PROCEVENT_CLAIM_IDENTITY"
|
|
1233
|
+
state=$?
|
|
1234
|
+
if [ "$state" -eq 2 ]; then
|
|
1235
|
+
fm_procevent_source_lock_release "$id"
|
|
1236
|
+
return 1
|
|
1237
|
+
fi
|
|
1238
|
+
fi
|
|
1239
|
+
fi
|
|
1240
|
+
fm_procevent_source_lock_release "$id"
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
sweep_retire_source() { # <source-id>
|
|
1244
|
+
local id=$1 owner_state expected_owner=''
|
|
1245
|
+
if [ -e "$(source_file "$id")" ] || [ -L "$(source_file "$id")" ]; then
|
|
1246
|
+
fm_procevent_source_lock_acquire "$id" || return 1
|
|
1247
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$id"
|
|
1248
|
+
owner_state=$?
|
|
1249
|
+
case "$owner_state" in
|
|
1250
|
+
0) expected_owner=$FM_PROCEVENT_EXTENSION_REGISTRATION_TOKEN ;;
|
|
1251
|
+
1) ;;
|
|
1252
|
+
*) fm_procevent_source_lock_release "$id"; return 1 ;;
|
|
1253
|
+
esac
|
|
1254
|
+
fm_procevent_source_lock_release "$id"
|
|
1255
|
+
fi
|
|
1256
|
+
if [ -n "$expected_owner" ]; then
|
|
1257
|
+
FM_HOME="$FM_HOME" FM_STATE_OVERRIDE="$STATE" \
|
|
1258
|
+
"$SCRIPT_DIR/fm-procevent.sh" retire "$id" --if-owner "$expected_owner"
|
|
1259
|
+
else
|
|
1260
|
+
FM_HOME="$FM_HOME" FM_STATE_OVERRIDE="$STATE" \
|
|
1261
|
+
"$SCRIPT_DIR/fm-procevent.sh" retire "$id"
|
|
1262
|
+
fi
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
cmd_sweep_home() {
|
|
1266
|
+
local preflight_only=${1-} path id owner attempted=0 failed=0
|
|
1267
|
+
[ -z "$preflight_only" ] || [ "$preflight_only" = --preflight ] || usage
|
|
1268
|
+
SWEEP_IDS=$'\n'
|
|
1269
|
+
for path in "$REG"/*.source; do
|
|
1270
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
1271
|
+
id=${path##*/}; id=${id%.source}
|
|
1272
|
+
if fm_procevent_source_id_valid "$id"; then
|
|
1273
|
+
sweep_add_id "$id"
|
|
1274
|
+
else
|
|
1275
|
+
failed=$((failed + 1))
|
|
1276
|
+
fi
|
|
1277
|
+
fi
|
|
1278
|
+
done
|
|
1279
|
+
for path in "$(fm_procevent_claim_root)"/*.claim; do
|
|
1280
|
+
[ -f "$path" ] && [ ! -L "$path" ] || continue
|
|
1281
|
+
IFS= read -r owner < "$path" 2>/dev/null || continue
|
|
1282
|
+
[ "$owner" = "$FM_HOME" ] || continue
|
|
1283
|
+
id=${path##*/}; id=${id%.claim}
|
|
1284
|
+
if fm_procevent_source_id_valid "$id"; then
|
|
1285
|
+
sweep_add_id "$id"
|
|
1286
|
+
else
|
|
1287
|
+
failed=$((failed + 1))
|
|
1288
|
+
fi
|
|
1289
|
+
done
|
|
1290
|
+
for path in "$REG"/*.runner; do
|
|
1291
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
1292
|
+
id=${path##*/}; id=${id%.runner}
|
|
1293
|
+
if ! fm_procevent_source_id_valid "$id"; then
|
|
1294
|
+
failed=$((failed + 1))
|
|
1295
|
+
else
|
|
1296
|
+
case "$SWEEP_IDS" in
|
|
1297
|
+
*$'\n'"$id"$'\n'*) ;;
|
|
1298
|
+
*) failed=$((failed + 1)) ;;
|
|
1299
|
+
esac
|
|
1300
|
+
fi
|
|
1301
|
+
fi
|
|
1302
|
+
done
|
|
1303
|
+
while IFS= read -r id; do
|
|
1304
|
+
[ -n "$id" ] || continue
|
|
1305
|
+
sweep_source_preflight "$id" || failed=$((failed + 1))
|
|
1306
|
+
done <<< "$SWEEP_IDS"
|
|
1307
|
+
if [ "$failed" -ne 0 ]; then
|
|
1308
|
+
printf 'error: process-event home sweep preflight failed: attempted=0 failed=%s\n' "$failed" >&2
|
|
1309
|
+
return 1
|
|
1310
|
+
fi
|
|
1311
|
+
if [ "$preflight_only" = --preflight ]; then
|
|
1312
|
+
printf 'sweep preflight: ready\n'
|
|
1313
|
+
return 0
|
|
1314
|
+
fi
|
|
1315
|
+
while IFS= read -r id; do
|
|
1316
|
+
[ -n "$id" ] || continue
|
|
1317
|
+
attempted=$((attempted + 1))
|
|
1318
|
+
if ! sweep_retire_source "$id"; then
|
|
1319
|
+
failed=$((failed + 1))
|
|
1320
|
+
fi
|
|
1321
|
+
done <<< "$SWEEP_IDS"
|
|
1322
|
+
if ! run_extension_invocation_cleanup; then
|
|
1323
|
+
failed=$((failed + 1))
|
|
1324
|
+
fi
|
|
1325
|
+
if [ "$failed" -ne 0 ] || sweep_relevant_state; then
|
|
1326
|
+
printf 'error: process-event home sweep incomplete: attempted=%s failed=%s\n' "$attempted" "$failed" >&2
|
|
1327
|
+
return 1
|
|
1328
|
+
fi
|
|
1329
|
+
printf 'swept: attempted=%s\n' "$attempted"
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
cmd_list() {
|
|
1333
|
+
local rec id adapter owner pending
|
|
1334
|
+
if ! fm_procevent_any_registered "$STATE"; then
|
|
1335
|
+
printf 'no sources registered\n'
|
|
1336
|
+
return 0
|
|
1337
|
+
fi
|
|
1338
|
+
printf '%-28s %-12s %-10s %s\n' SOURCE ADAPTER OWNER PENDING
|
|
1339
|
+
for rec in "$REG"/*.source; do
|
|
1340
|
+
[ -e "$rec" ] || continue
|
|
1341
|
+
id=${rec##*/}; id=${id%.source}
|
|
1342
|
+
adapter=$(read_adapter "$id" 2>/dev/null || echo '?')
|
|
1343
|
+
fm_procevent_source_lock_acquire "$id" || continue
|
|
1344
|
+
fm_procevent_claim_state_locked "$id"
|
|
1345
|
+
case "$?" in 0) owner=live ;; 1) owner=none ;; 3) owner=orphaned ;; *) owner=uncertain ;; esac
|
|
1346
|
+
fm_procevent_source_lock_release "$id"
|
|
1347
|
+
pending=$(fm_procevent_pending "$STATE" | grep -c "/$id\." || true)
|
|
1348
|
+
printf '%-28s %-12s %-10s %s\n' "$id" "$adapter" "$owner" "$pending"
|
|
1349
|
+
done
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
cmd_binding_retirement_preflight() {
|
|
1353
|
+
local digest=${1-} rec id owner_state result
|
|
1354
|
+
if [ "$#" -ne 1 ] || ! fm_procevent_digest_valid "$digest"; then
|
|
1355
|
+
die "binding-retirement-preflight requires one binding digest"
|
|
1356
|
+
fi
|
|
1357
|
+
for rec in "$REG"/*.source; do
|
|
1358
|
+
[ -e "$rec" ] || continue
|
|
1359
|
+
[ -f "$rec" ] && [ ! -L "$rec" ] || die "binding retirement found unsafe registration state"
|
|
1360
|
+
id=${rec##*/}; id=${id%.source}
|
|
1361
|
+
fm_procevent_source_id_valid "$id" || die "binding retirement found malformed registration state"
|
|
1362
|
+
fm_lock_try_acquire "$(fm_procevent_source_lock_path "$id")" \
|
|
1363
|
+
|| die "binding still owns process-event registration: $id"
|
|
1364
|
+
fm_procevent_extension_registration_load_locked "$STATE" "$id"
|
|
1365
|
+
owner_state=$?
|
|
1366
|
+
fm_lock_release "$(fm_procevent_source_lock_path "$id")"
|
|
1367
|
+
case "$owner_state" in
|
|
1368
|
+
0) [ "$FM_PROCEVENT_EXTENSION_BINDING_DIGEST" != "$digest" ] \
|
|
1369
|
+
|| die "binding still owns process-event registration: $id" ;;
|
|
1370
|
+
1) ;;
|
|
1371
|
+
*) die "binding retirement found malformed extension registration: $id" ;;
|
|
1372
|
+
esac
|
|
1373
|
+
done
|
|
1374
|
+
for result in "$(fm_procevent_inbox_dir "$STATE")"/*.result; do
|
|
1375
|
+
[ -e "$result" ] || continue
|
|
1376
|
+
if [ -e "${result%.result}.handled" ] || [ -L "${result%.result}.handled" ]; then
|
|
1377
|
+
[ -f "${result%.result}.handled" ] && [ ! -L "${result%.result}.handled" ] \
|
|
1378
|
+
|| die "binding retirement found unsafe handled-result state: ${result##*/}"
|
|
1379
|
+
continue
|
|
1380
|
+
fi
|
|
1381
|
+
fm_procevent_result_extension_load "$result"
|
|
1382
|
+
owner_state=$?
|
|
1383
|
+
case "$owner_state" in
|
|
1384
|
+
0) [ "$FM_PROCEVENT_RESULT_EXTENSION_BINDING_DIGEST" != "$digest" ] \
|
|
1385
|
+
|| die "binding still owns unhandled process-event result: ${result##*/}" ;;
|
|
1386
|
+
1) ;;
|
|
1387
|
+
*) die "binding retirement found malformed extension result: ${result##*/}" ;;
|
|
1388
|
+
esac
|
|
1389
|
+
done
|
|
1390
|
+
printf 'binding retirement preflight: ready\n'
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
cmd_extension_retirement() {
|
|
1394
|
+
local mode=${1-} owner
|
|
1395
|
+
[ "$#" -ge 1 ] || die "extension-retirement requires a retirement mode"
|
|
1396
|
+
shift
|
|
1397
|
+
case "$mode" in binding|transfer) ;; *) die "unsupported extension retirement mode: $mode" ;; esac
|
|
1398
|
+
extension_lifecycle_lock_acquire || die "cannot lock the extension lifecycle"
|
|
1399
|
+
owner=${FM_LOCK_OWNER_DIR:-}
|
|
1400
|
+
[ -n "$owner" ] || die "extension lifecycle lock has no owner identity"
|
|
1401
|
+
export FM_EXTENSION_RETIREMENT_MODE="$mode"
|
|
1402
|
+
export FM_EXTENSION_LIFECYCLE_LOCK="$EXTENSION_LIFECYCLE_LOCK"
|
|
1403
|
+
export FM_EXTENSION_LIFECYCLE_OWNER="$owner"
|
|
1404
|
+
exec "$EXTENSION_HOST" "$@"
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
cmd_extension_bind() {
|
|
1408
|
+
local binding_command=${1-} owner
|
|
1409
|
+
case "$binding_command" in bind|receive-transfer-bind) ;; *) die "unsupported extension binding command: $binding_command" ;; esac
|
|
1410
|
+
extension_lifecycle_lock_acquire || die "cannot lock the extension lifecycle"
|
|
1411
|
+
owner=${FM_LOCK_OWNER_DIR:-}
|
|
1412
|
+
[ -n "$owner" ] || die "extension lifecycle lock has no owner identity"
|
|
1413
|
+
export FM_EXTENSION_RETIREMENT_MODE=bind
|
|
1414
|
+
export FM_EXTENSION_LIFECYCLE_LOCK="$EXTENSION_LIFECYCLE_LOCK"
|
|
1415
|
+
export FM_EXTENSION_LIFECYCLE_OWNER="$owner"
|
|
1416
|
+
exec "$EXTENSION_HOST" "$@"
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
cmd_extension_process_event() {
|
|
1420
|
+
local owner arg
|
|
1421
|
+
[ "$#" -ge 2 ] || die "extension-process-event requires process-event arguments"
|
|
1422
|
+
for arg in "$@"; do
|
|
1423
|
+
[ "$arg" != --capture-reservation ] || die "capture reservation is internal"
|
|
1424
|
+
done
|
|
1425
|
+
extension_lifecycle_lock_acquire || die "cannot lock the extension lifecycle"
|
|
1426
|
+
owner=${FM_LOCK_OWNER_DIR:-}
|
|
1427
|
+
[ -n "$owner" ] || die "extension lifecycle lock has no owner identity"
|
|
1428
|
+
export FM_EXTENSION_RETIREMENT_MODE=process-event
|
|
1429
|
+
export FM_EXTENSION_LIFECYCLE_LOCK="$EXTENSION_LIFECYCLE_LOCK"
|
|
1430
|
+
export FM_EXTENSION_LIFECYCLE_OWNER="$owner"
|
|
1431
|
+
# These descriptors are reserved for the direct, internal capture handoff.
|
|
1432
|
+
# The public lifecycle path must not let unrelated descriptors acquired while
|
|
1433
|
+
# obtaining its lock look like a malformed handoff to the host.
|
|
1434
|
+
{ exec 6<&-; } 2>/dev/null || true
|
|
1435
|
+
{ exec 7<&-; } 2>/dev/null || true
|
|
1436
|
+
{ exec 8<&-; } 2>/dev/null || true
|
|
1437
|
+
{ exec 9<&-; } 2>/dev/null || true
|
|
1438
|
+
exec "$EXTENSION_HOST" process-event "$@"
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
unset FM_PROCEVENT_CAPTURE_PINNED_INBOX FM_PROCEVENT_CAPTURE_ABSOLUTE_INBOX \
|
|
1442
|
+
FM_PROCEVENT_CAPTURE_RESERVATION_TERMINAL \
|
|
1443
|
+
FM_PROCEVENT_CAPTURE_RESERVATION_SILENT
|
|
1444
|
+
{ exec 7<&-; } 2>/dev/null || true
|
|
1445
|
+
{ exec 6<&-; } 2>/dev/null || true
|
|
1446
|
+
{ exec 8<&-; } 2>/dev/null || true
|
|
1447
|
+
{ exec 9<&-; } 2>/dev/null || true
|
|
1448
|
+
|
|
1449
|
+
case "${1-}" in
|
|
1450
|
+
register) shift; cmd_register "$@" ;;
|
|
1451
|
+
register-extension) shift; cmd_register_extension "$@" ;;
|
|
1452
|
+
start) shift; cmd_start_public "$@" ;;
|
|
1453
|
+
_start) shift; cmd_start "$@" ;;
|
|
1454
|
+
reconcile) shift; cmd_reconcile "$@" ;;
|
|
1455
|
+
classify) shift; cmd_classify "$@" ;;
|
|
1456
|
+
handled) shift; cmd_handled "$@" ;;
|
|
1457
|
+
retire) shift; cmd_retire "$@" ;;
|
|
1458
|
+
sweep-home) shift; cmd_sweep_home "$@" ;;
|
|
1459
|
+
binding-retirement-preflight) shift; cmd_binding_retirement_preflight "$@" ;;
|
|
1460
|
+
extension-retirement) shift; cmd_extension_retirement "$@" ;;
|
|
1461
|
+
extension-bind) shift; cmd_extension_bind "$@" ;;
|
|
1462
|
+
extension-process-event) shift; cmd_extension_process_event "$@" ;;
|
|
1463
|
+
list) shift; cmd_list "$@" ;;
|
|
1464
|
+
''|-h|--help|help) usage ;;
|
|
1465
|
+
*) die "unknown command: $1" ;;
|
|
1466
|
+
esac
|