@zerwiz/ymir 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/README.md +74 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +360 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +11 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +121 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +90 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +57 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +35 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/install.md +69 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +498 -0
- package/CHANGELOG.md +1913 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +114 -60
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +97 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +108 -0
- package/RULES/07-config.md +101 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +22 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +133 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +256 -0
- package/bin/apodex-smoke-test.sh +105 -0
- package/bin/bifrost-bridge.sh +131 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +112 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +51 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-assemble.sh +154 -0
- package/bin/changelog-guard.sh +197 -0
- package/bin/crash-sense.sh +173 -0
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +153 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +225 -0
- package/bin/eindri-acclaim.sh +47 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +54 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +98 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +158 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +81 -0
- package/bin/github-deploy.sh +94 -0
- package/bin/gjallarhorn-expose.sh +136 -0
- package/bin/gjallarhorn-notify.sh +71 -0
- package/bin/gjallarhorn-purge.sh +67 -0
- package/bin/gjallarhorn-tunnel.sh +85 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +130 -0
- package/bin/hall-snapshot.sh +111 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +452 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +105 -0
- package/bin/hodd.sh +102 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +121 -0
- package/bin/local-model-lock.sh +70 -0
- package/bin/mimir-bridge.py +259 -0
- package/bin/mimir-bridge.sh +100 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +72 -0
- package/bin/mjollnir.sh +104 -0
- package/bin/model-hardware.sh +96 -0
- package/bin/model-resolve.sh +160 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-hall-snapshot.sh +28 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +161 -0
- package/bin/omarchy-plugins.sh +187 -0
- package/bin/omarchy-sense.sh +162 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-model-agnostic.sh +71 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +58 -0
- package/bin/project-git.sh +69 -0
- package/bin/public-guard.sh +71 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +104 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +94 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +89 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +203 -0
- package/bin/telegram-bot.sh +82 -0
- package/bin/toolchain.sh +74 -0
- package/bin/ui-truth-check.sh +75 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +298 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +60 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +163 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +806 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-plan.sh +356 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +110 -0
- package/bin/ymir-setup-auth.sh +119 -0
- package/bin/ymir-validate.sh +193 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1060 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +54 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +269 -0
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +217 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fm-public-followup.sh - the deterministic consumer and delivery owner for
|
|
3
|
+
# public commitments made through the myfirstmate relay (X and Discord).
|
|
4
|
+
#
|
|
5
|
+
# THE PROBLEM THIS SOLVES: firstmate promises a public final reply, routes the
|
|
6
|
+
# work out, and then the conversation compacts or the session restarts. Nothing
|
|
7
|
+
# in memory survives, so the promise is only kept if reconciling it is a disk
|
|
8
|
+
# operation. Every command here reads durable state and nothing else.
|
|
9
|
+
#
|
|
10
|
+
# OWNERSHIP BOUNDARIES (do not re-implement any of these here):
|
|
11
|
+
# tasks-axi public-followup the typed obligation and its state machine.
|
|
12
|
+
# state/x-context/ the private full request context (fm-x-lib.sh).
|
|
13
|
+
# bin/fm-x-reply.sh posting to the relay, thread splitting, dry run.
|
|
14
|
+
# bin/fm-public-followup-lib.sh the activation gate and private transport.
|
|
15
|
+
# This script composes them; it never restates their contracts or schemas.
|
|
16
|
+
#
|
|
17
|
+
# ZERO OVERHEAD FOR HOMES THAT DO NOT USE THE RELAY: every subcommand gates
|
|
18
|
+
# first on the authoritative activation contract (a non-empty FMX_PAIRING_TOKEN
|
|
19
|
+
# in $FM_HOME/.env). Read-side and cleanup paths then use an O(1) presence check
|
|
20
|
+
# for registrations this home actually created. A relay-disabled home therefore
|
|
21
|
+
# runs one [ -f ] test before any backlog work: no tasks-axi call, no backlog scan,
|
|
22
|
+
# and no file created. Silent read-side commands return without output; commands
|
|
23
|
+
# that require an active relay report their configuration error after the same
|
|
24
|
+
# gate. A relay-enabled home with no live commitments stops at the second gate
|
|
25
|
+
# for the same cost.
|
|
26
|
+
#
|
|
27
|
+
# Usage:
|
|
28
|
+
# fm-public-followup.sh active
|
|
29
|
+
# Silent gate probe. Exit 0 when this home has live public-followup work
|
|
30
|
+
# worth looking at, including a delivered open loop, 1 otherwise. Safe to
|
|
31
|
+
# call unconditionally.
|
|
32
|
+
#
|
|
33
|
+
# fm-public-followup.sh register <obligation-id> --relation <relation-id>
|
|
34
|
+
# --work-home <main|secondmate:<id>> --work-id <task-id> --generation <n>
|
|
35
|
+
# [--platform <x|discord>] [--request <request-id>]
|
|
36
|
+
# Record the binding the relay path just created with `tasks-axi
|
|
37
|
+
# public-followup add` + `bind-work`. This is the event-driven
|
|
38
|
+
# registration: it creates this home's private public-followup directories
|
|
39
|
+
# (0700) and the bounded public-safe registration record, which is what
|
|
40
|
+
# later makes the presence checks O(1) and lets bound work report a typed
|
|
41
|
+
# terminal result. Refuses when the relay is not active for this home.
|
|
42
|
+
#
|
|
43
|
+
# fm-public-followup.sh brief <obligation-id>
|
|
44
|
+
# Print the exact fm-public-followup-emit.sh command line the bound worker
|
|
45
|
+
# must run when its work reaches the promised terminal outcome, so the
|
|
46
|
+
# binding is copied into a brief instead of hand-assembled. The
|
|
47
|
+
# --deliverable flags name the obligation's actual required keys.
|
|
48
|
+
#
|
|
49
|
+
# fm-public-followup.sh consume
|
|
50
|
+
# Drain every pending typed terminal event: validate its derived identity,
|
|
51
|
+
# skip anything already accepted, apply `tasks-axi public-followup
|
|
52
|
+
# work-event`, and quarantine what tasks-axi refuses. Prints one
|
|
53
|
+
# "ready <obligation-id> <request-id> <platform>" line per obligation that
|
|
54
|
+
# became delivery-ready, and one "rejected <event-id>: <reason>" line per
|
|
55
|
+
# refusal. Silent when there is nothing to do. Duplicate events and restart
|
|
56
|
+
# replay are no-ops.
|
|
57
|
+
#
|
|
58
|
+
# fm-public-followup.sh pending
|
|
59
|
+
# One bounded public-safe line per open public loop, for the session
|
|
60
|
+
# start digest. Unresolved commitments print as "unresolved" (a reply is
|
|
61
|
+
# still owed). Delivered or settled registrations print as "open-loop"
|
|
62
|
+
# (the thread is still open with nothing owed). Registrations are never
|
|
63
|
+
# pruned here; only `retire` removes one. Silent when nothing is open.
|
|
64
|
+
#
|
|
65
|
+
# fm-public-followup.sh deliver <obligation-id> [--text-file <path>]
|
|
66
|
+
# Post the final public reply into the ORIGINAL thread. Uses the stored
|
|
67
|
+
# platform and opaque context binding, so the destination is never guessed.
|
|
68
|
+
# Without --text-file the accepted terminal event's bounded public-safe
|
|
69
|
+
# outcome is reused exactly, which keeps the common path deterministic.
|
|
70
|
+
# The sequence is begin-delivery with the payload hash, post, then record
|
|
71
|
+
# the posted receipt or a typed error. A validated receipt also clears any
|
|
72
|
+
# bound legacy X link, then stamps the registration state=delivered. Delivery
|
|
73
|
+
# does not close the public loop; `retire` is the only close. Prints a
|
|
74
|
+
# disposition line so the loop is handed on with `rechain` or closed
|
|
75
|
+
# explicitly. An already-posted obligation is an idempotent success
|
|
76
|
+
# without another post; an obligation left in delivery-posting by a crash
|
|
77
|
+
# is REFUSED rather than posted again.
|
|
78
|
+
#
|
|
79
|
+
# fm-public-followup.sh record-posted <obligation-id> --attempt <n> --chunks <n>
|
|
80
|
+
# Record an obligation whose post is known to have landed on exactly
|
|
81
|
+
# attempt <n> with exactly <n> messages, without posting anything. This is
|
|
82
|
+
# the late-receipt path: use it when a post succeeded but its receipt was
|
|
83
|
+
# lost, never to paper over an unknown outcome. Stamps the registration
|
|
84
|
+
# delivered; does not remove it.
|
|
85
|
+
#
|
|
86
|
+
# fm-public-followup.sh guard-work <work-home-id> <work-id>
|
|
87
|
+
# Exit 3 when this home has an unresolved public commitment bound to that
|
|
88
|
+
# exact work, printing one line per blocking obligation. Exit 0 otherwise.
|
|
89
|
+
# Cleanup paths call this so bound work is never treated as finished while
|
|
90
|
+
# its public promise is still open. A delivered registration is not a
|
|
91
|
+
# block: that work's reply already landed.
|
|
92
|
+
#
|
|
93
|
+
# fm-public-followup.sh rechain <new-obligation-id> --from <delivered-id>
|
|
94
|
+
# --work-home <main|secondmate:<id>> --work-id <task-id>
|
|
95
|
+
# --expected <pr-merged|report-ready|local-main>
|
|
96
|
+
# [--deliverable-key <k>]...
|
|
97
|
+
# Hand a delivered public loop on to follow-on work against the same
|
|
98
|
+
# thread. Decodes the retained request context, creates and binds a fresh
|
|
99
|
+
# promised-final obligation, registers it, retires the source with reason
|
|
100
|
+
# "handed on to <new-id>", and prints `brief` for the new obligation.
|
|
101
|
+
# Refuses unless the source is state=delivered, the follow-up window is
|
|
102
|
+
# still open, and the relay is active. A pre-change record without
|
|
103
|
+
# request_context_b64 is un-rechainable.
|
|
104
|
+
#
|
|
105
|
+
# fm-public-followup.sh retire <obligation-id> --reason "<why the loop is done>" [--force]
|
|
106
|
+
# The only close. Drops the registration after recording --reason.
|
|
107
|
+
# --force is the explicit discard-approved escape hatch for an unresolved
|
|
108
|
+
# or missing obligation. --reason is required.
|
|
109
|
+
#
|
|
110
|
+
# Requires jq and a compatible tasks-axi for registration, briefs,
|
|
111
|
+
# reconciliation, delivery, cleanup guards, and retirement; only `active`
|
|
112
|
+
# inspects local state alone.
|
|
113
|
+
# FM_PF_RETRY_BACKOFF_SECS (default 900) sets the next-attempt time recorded with
|
|
114
|
+
# a retryable delivery error.
|
|
115
|
+
set -u
|
|
116
|
+
|
|
117
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
118
|
+
FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
119
|
+
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
|
|
120
|
+
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
|
|
121
|
+
DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
|
|
122
|
+
|
|
123
|
+
# shellcheck source=bin/fm-public-followup-lib.sh
|
|
124
|
+
. "$SCRIPT_DIR/fm-public-followup-lib.sh"
|
|
125
|
+
# shellcheck source=bin/fm-secondmate-registry-lib.sh
|
|
126
|
+
. "$SCRIPT_DIR/fm-secondmate-registry-lib.sh"
|
|
127
|
+
|
|
128
|
+
RETRY_BACKOFF=${FM_PF_RETRY_BACKOFF_SECS:-900}
|
|
129
|
+
case "$RETRY_BACKOFF" in ''|*[!0-9]*) RETRY_BACKOFF=900 ;; esac
|
|
130
|
+
|
|
131
|
+
usage() {
|
|
132
|
+
echo "usage: fm-public-followup.sh <active|register|brief|consume|pending|deliver|record-posted|guard-work|rechain|retire> [args]" >&2
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# The header comment IS the help text, so the two can never drift apart.
|
|
136
|
+
help() { sed -n '2,/^set -u$/p' "$0" | sed '$d; s/^# \{0,1\}//'; }
|
|
137
|
+
|
|
138
|
+
die() { printf 'fm-public-followup: %s\n' "$1" >&2; exit "${2:-2}"; }
|
|
139
|
+
|
|
140
|
+
PF_TEMP_FILES=()
|
|
141
|
+
PF_REGISTRY_LOCK_IDS=()
|
|
142
|
+
pf_registry_lock_held() {
|
|
143
|
+
local wanted=$1 held
|
|
144
|
+
for held in "${PF_REGISTRY_LOCK_IDS[@]}"; do
|
|
145
|
+
[ "$held" = "$wanted" ] && return 0
|
|
146
|
+
done
|
|
147
|
+
return 1
|
|
148
|
+
}
|
|
149
|
+
pf_registry_lock_acquire() {
|
|
150
|
+
local id=$1
|
|
151
|
+
pf_registry_lock_held "$id" && return 0
|
|
152
|
+
fm_pf_registry_lock_acquire "$STATE" "$id" || return 1
|
|
153
|
+
PF_REGISTRY_LOCK_IDS+=("$id")
|
|
154
|
+
}
|
|
155
|
+
pf_registry_lock_release() {
|
|
156
|
+
local id=$1 held
|
|
157
|
+
local -a remaining=()
|
|
158
|
+
pf_registry_lock_held "$id" || return 0
|
|
159
|
+
fm_pf_registry_lock_release "$STATE" "$id"
|
|
160
|
+
for held in "${PF_REGISTRY_LOCK_IDS[@]}"; do
|
|
161
|
+
[ "$held" = "$id" ] || remaining+=("$held")
|
|
162
|
+
done
|
|
163
|
+
PF_REGISTRY_LOCK_IDS=("${remaining[@]}")
|
|
164
|
+
}
|
|
165
|
+
pf_cleanup() {
|
|
166
|
+
local i
|
|
167
|
+
for ((i=${#PF_REGISTRY_LOCK_IDS[@]}-1; i>=0; i--)); do
|
|
168
|
+
fm_pf_registry_lock_release "$STATE" "${PF_REGISTRY_LOCK_IDS[$i]}" 2>/dev/null || true
|
|
169
|
+
done
|
|
170
|
+
[ "${#PF_TEMP_FILES[@]}" -eq 0 ] || rm -f -- "${PF_TEMP_FILES[@]}"
|
|
171
|
+
}
|
|
172
|
+
trap pf_cleanup EXIT
|
|
173
|
+
|
|
174
|
+
now_rfc3339() { fm_pf_now_rfc3339; }
|
|
175
|
+
|
|
176
|
+
# next_attempt_rfc3339: the retry time recorded with a retryable delivery error.
|
|
177
|
+
# BSD and GNU date disagree on the flag, so try both and print nothing when
|
|
178
|
+
# neither works - the error is still recorded, just without a retry time.
|
|
179
|
+
next_attempt_rfc3339() {
|
|
180
|
+
local at
|
|
181
|
+
at=$(( $(date +%s) + RETRY_BACKOFF ))
|
|
182
|
+
date -u -r "$at" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
183
|
+
|| date -u -d "@$at" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
184
|
+
|| true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
require_tools() {
|
|
188
|
+
command -v jq >/dev/null 2>&1 || die "jq is required" 1
|
|
189
|
+
command -v tasks-axi >/dev/null 2>&1 || die "tasks-axi is required" 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Every tasks-axi call runs from the home whose backlog owns the obligation, the
|
|
193
|
+
# same convention bin/fm-captain-hold.sh uses for typed backlog state.
|
|
194
|
+
tx() { (cd "$FM_HOME" && tasks-axi "$@"); }
|
|
195
|
+
|
|
196
|
+
# obligation_json <id>: the complete typed obligation payload on stdout, empty
|
|
197
|
+
# when the backlog simply has no such public-followup item, and a non-zero exit
|
|
198
|
+
# ONLY when the backlog could not be read at all. Callers depend on that
|
|
199
|
+
# distinction to report the right thing, so jq runs without -e here. tasks-axi
|
|
200
|
+
# stays the single source of truth; the registration record is never consulted
|
|
201
|
+
# for state.
|
|
202
|
+
obligation_json() {
|
|
203
|
+
local id=$1 out
|
|
204
|
+
out=$(tx public-followup list --json 2>/dev/null) || return 1
|
|
205
|
+
[ -n "$out" ] || return 1
|
|
206
|
+
printf '%s' "$out" | jq -c --arg id "$id" \
|
|
207
|
+
'(.public_followups // []) | map(select(.id == $id)) | .[0] // empty' 2>/dev/null \
|
|
208
|
+
|| return 1
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pf_field() { printf '%s' "$1" | jq -r "$2 // empty" 2>/dev/null; }
|
|
212
|
+
|
|
213
|
+
# --- gates ------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
# gate_or_exit: the shared silent gate for every read-side subcommand. Exits 0
|
|
216
|
+
# with no output when this home has no public-followup work, so callers can
|
|
217
|
+
# invoke unconditionally without a relay-disabled home paying anything.
|
|
218
|
+
gate_or_exit() {
|
|
219
|
+
fm_pf_relay_active "$FM_HOME" || exit 0
|
|
220
|
+
fm_pf_has_registrations "$STATE" || fm_pf_has_events "$STATE" || exit 0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# --- subcommand: active -----------------------------------------------------
|
|
224
|
+
|
|
225
|
+
cmd_active() {
|
|
226
|
+
fm_pf_relay_active "$FM_HOME" || exit 1
|
|
227
|
+
fm_pf_has_registrations "$STATE" || fm_pf_has_events "$STATE" || exit 1
|
|
228
|
+
exit 0
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# --- subcommand: register ---------------------------------------------------
|
|
232
|
+
|
|
233
|
+
cmd_register() {
|
|
234
|
+
local id=${1:-}
|
|
235
|
+
local relation='' work_home='' work_id='' generation='' platform='' request=''
|
|
236
|
+
[ -n "$id" ] || { usage; exit 2; }
|
|
237
|
+
shift
|
|
238
|
+
while [ "$#" -gt 0 ]; do
|
|
239
|
+
case "$1" in
|
|
240
|
+
--relation) shift; relation=${1:-} ;;
|
|
241
|
+
--work-home) shift; work_home=${1:-} ;;
|
|
242
|
+
--work-id) shift; work_id=${1:-} ;;
|
|
243
|
+
--generation) shift; generation=${1:-} ;;
|
|
244
|
+
--platform) shift; platform=${1:-} ;;
|
|
245
|
+
--request) shift; request=${1:-} ;;
|
|
246
|
+
*) die "unknown argument '$1'" ;;
|
|
247
|
+
esac
|
|
248
|
+
shift || true
|
|
249
|
+
done
|
|
250
|
+
|
|
251
|
+
fm_pf_relay_active "$FM_HOME" \
|
|
252
|
+
|| die "this home has not opted into the myfirstmate relay, so it cannot own a public commitment" 1
|
|
253
|
+
require_tools
|
|
254
|
+
|
|
255
|
+
fm_pf_slug_valid "$id" || die "unsafe obligation id: $id"
|
|
256
|
+
fm_pf_slug_valid "$relation" || die "unsafe relation id: $relation"
|
|
257
|
+
fm_pf_slug_valid "$work_id" || die "unsafe work id: $work_id"
|
|
258
|
+
fm_pf_home_id_valid "$work_home" \
|
|
259
|
+
|| die "work home must be 'main' or 'secondmate:<stable-id>', got '$work_home'"
|
|
260
|
+
case "$generation" in
|
|
261
|
+
''|*[!0-9]*) die "generation must be a positive integer, got '$generation'" ;;
|
|
262
|
+
esac
|
|
263
|
+
[ "$generation" -ge 1 ] || die "generation must be >= 1"
|
|
264
|
+
|
|
265
|
+
local payload
|
|
266
|
+
payload=$(obligation_json "$id") \
|
|
267
|
+
|| die "could not read the backlog through tasks-axi" 1
|
|
268
|
+
[ -n "$payload" ] \
|
|
269
|
+
|| die "no public-followup obligation '$id' in this home's backlog; create it with tasks-axi public-followup add before registering" 1
|
|
270
|
+
|
|
271
|
+
# The relation must already be bound, so a registration can never describe a
|
|
272
|
+
# binding tasks-axi does not have.
|
|
273
|
+
printf '%s' "$payload" | jq -e --arg r "$relation" --arg h "$work_home" --arg w "$work_id" \
|
|
274
|
+
'(.public_followup.work_relations // [])
|
|
275
|
+
| map(select(.relation_id == $r and .work_ref.home_id == $h and .work_ref.task_id == $w))
|
|
276
|
+
| length > 0' >/dev/null 2>&1 \
|
|
277
|
+
|| die "obligation '$id' has no bound relation '$relation' for $work_home/$work_id; run tasks-axi public-followup bind-work first" 1
|
|
278
|
+
|
|
279
|
+
[ -n "$platform" ] || platform=$(pf_field "$payload" '.public_followup.request.platform')
|
|
280
|
+
[ -n "$request" ] || request=$(pf_field "$payload" '.public_followup.request.request_id')
|
|
281
|
+
[ -z "$request" ] || fm_pf_slug_valid "$request" || die "unsafe request id: $request"
|
|
282
|
+
|
|
283
|
+
local followup_expires_at request_json request_context_b64 work_home_path
|
|
284
|
+
followup_expires_at=$(pf_field "$payload" '.public_followup.request.followup_expires_at')
|
|
285
|
+
request_json=$(printf '%s' "$payload" | jq -c '.public_followup.request // empty' 2>/dev/null || true)
|
|
286
|
+
request_context_b64=
|
|
287
|
+
if [ -n "$request_json" ]; then
|
|
288
|
+
request_context_b64=$(printf '%s' "$request_json" | fm_pf_b64_encode)
|
|
289
|
+
fi
|
|
290
|
+
work_home_path=
|
|
291
|
+
case "$work_home" in
|
|
292
|
+
secondmate:*)
|
|
293
|
+
work_home_path=$(public_followup_secondmate_home "${work_home#secondmate:}" 2>/dev/null || true)
|
|
294
|
+
case "$work_home_path" in
|
|
295
|
+
*$'\n'*|*$'\r'*) work_home_path= ;;
|
|
296
|
+
esac
|
|
297
|
+
;;
|
|
298
|
+
esac
|
|
299
|
+
|
|
300
|
+
local mkdir_target registry_state retired_file
|
|
301
|
+
for mkdir_target in "$(fm_pf_registry_dir "$STATE")" "$(fm_pf_events_dir "$STATE")" \
|
|
302
|
+
"$(fm_pf_consumed_dir "$STATE")" "$(fm_pf_rejected_dir "$STATE")"; do
|
|
303
|
+
fmx_private_artifact_dir_prepare "$mkdir_target" >/dev/null \
|
|
304
|
+
|| die "could not prepare $mkdir_target" 1
|
|
305
|
+
done
|
|
306
|
+
|
|
307
|
+
pf_registry_lock_acquire "$id" \
|
|
308
|
+
|| die "could not lock registration '$id'" 1
|
|
309
|
+
retired_file="$(fm_pf_retired_dir "$STATE")/$id"
|
|
310
|
+
if [ -e "$retired_file" ] || [ -L "$retired_file" ]; then
|
|
311
|
+
die "public loop '$id' has already been retired and cannot be registered again" 1
|
|
312
|
+
fi
|
|
313
|
+
registry_state=$(fm_pf_registry_loop_state "$STATE" "$id")
|
|
314
|
+
if [ "$registry_state" = delivered ]; then
|
|
315
|
+
pf_registry_lock_release "$id"
|
|
316
|
+
printf 'already registered %s state=delivered\n' "$id"
|
|
317
|
+
return 0
|
|
318
|
+
fi
|
|
319
|
+
printf 'obligation_id=%s\nrelation_id=%s\nwork_home=%s\nwork_home_path=%s\nwork_id=%s\ngeneration=%s\nplatform=%s\nrequest_id=%s\nstate=open\nfollowup_expires_at=%s\nrequest_context_b64=%s\n' \
|
|
320
|
+
"$id" "$relation" "$work_home" "$work_home_path" "$work_id" "$generation" "$platform" "$request" \
|
|
321
|
+
"$followup_expires_at" "$request_context_b64" \
|
|
322
|
+
| fmx_private_artifact_publish_stdin "$(fm_pf_registry_dir "$STATE")" "$id" 600 \
|
|
323
|
+
|| die "could not write the registration record" 1
|
|
324
|
+
pf_registry_lock_release "$id"
|
|
325
|
+
|
|
326
|
+
printf 'registered %s %s/%s generation=%s platform=%s\n' \
|
|
327
|
+
"$id" "$work_home" "$work_id" "$generation" "${platform:-unknown}"
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
# --- subcommand: brief ------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
cmd_brief() {
|
|
333
|
+
local id=${1:-} relation work_home work_id generation payload outcome keys key deliverable_flags
|
|
334
|
+
[ -n "$id" ] || { usage; exit 2; }
|
|
335
|
+
fm_pf_slug_valid "$id" || die "unsafe obligation id: $id"
|
|
336
|
+
fm_pf_relay_active "$FM_HOME" || die "the relay is not active for this home" 1
|
|
337
|
+
[ -f "$(fm_pf_registry_dir "$STATE")/$id" ] \
|
|
338
|
+
|| die "no registration for '$id' in this home" 1
|
|
339
|
+
|
|
340
|
+
relation=$(fm_pf_registry_get "$STATE" "$id" relation_id)
|
|
341
|
+
work_home=$(fm_pf_registry_get "$STATE" "$id" work_home)
|
|
342
|
+
work_id=$(fm_pf_registry_get "$STATE" "$id" work_id)
|
|
343
|
+
generation=$(fm_pf_registry_get "$STATE" "$id" generation)
|
|
344
|
+
|
|
345
|
+
require_tools
|
|
346
|
+
payload=$(obligation_json "$id") \
|
|
347
|
+
|| die "could not read public-followup obligation '$id' through tasks-axi" 1
|
|
348
|
+
[ -n "$payload" ] \
|
|
349
|
+
|| die "public-followup obligation '$id' is missing from tasks-axi" 1
|
|
350
|
+
outcome=$(pf_field "$payload" '.public_followup.expected_final.type')
|
|
351
|
+
[ -n "$outcome" ] \
|
|
352
|
+
|| die "public-followup obligation '$id' has no expected final type" 1
|
|
353
|
+
keys=$(printf '%s' "$payload" \
|
|
354
|
+
| jq -er '.public_followup.expected_final.required_deliverables
|
|
355
|
+
| select(type == "array" and length > 0
|
|
356
|
+
and (map(type == "string" and test("^[a-z0-9_]+$")) | all))
|
|
357
|
+
| .[]' 2>/dev/null) \
|
|
358
|
+
|| die "public-followup obligation '$id' has no readable required deliverable keys" 1
|
|
359
|
+
deliverable_flags=
|
|
360
|
+
while IFS= read -r key; do
|
|
361
|
+
[ -n "$key" ] || continue
|
|
362
|
+
deliverable_flags="${deliverable_flags} --deliverable ${key}=<value> \\
|
|
363
|
+
"
|
|
364
|
+
done <<EOF
|
|
365
|
+
$keys
|
|
366
|
+
EOF
|
|
367
|
+
|
|
368
|
+
cat <<EOF
|
|
369
|
+
When this work reaches its promised terminal outcome, report it as typed data
|
|
370
|
+
(never as a sentence for someone to parse) by running exactly:
|
|
371
|
+
|
|
372
|
+
$FM_ROOT/bin/fm-public-followup-emit.sh \\
|
|
373
|
+
--home $FM_HOME \\
|
|
374
|
+
--obligation $id \\
|
|
375
|
+
--relation $relation \\
|
|
376
|
+
--source-home $work_home \\
|
|
377
|
+
--work-id $work_id \\
|
|
378
|
+
--generation $generation \\
|
|
379
|
+
--outcome $outcome \\
|
|
380
|
+
${deliverable_flags} --outcome-text '<one bounded public-safe sentence>'
|
|
381
|
+
|
|
382
|
+
Do not post anything publicly yourself and do not look for the public thread:
|
|
383
|
+
the home above owns the reply.
|
|
384
|
+
EOF
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# --- subcommand: consume ----------------------------------------------------
|
|
388
|
+
|
|
389
|
+
# reject_event <file> <event-id> <reason>: quarantine one refused event with an
|
|
390
|
+
# inspectable reason so it is never retried in a loop.
|
|
391
|
+
reject_event() {
|
|
392
|
+
local file=$1 event_id=$2 reason=$3 rejected event_payload
|
|
393
|
+
rejected=$(fm_pf_rejected_dir "$STATE")
|
|
394
|
+
fmx_private_artifact_dir_prepare "$rejected" >/dev/null \
|
|
395
|
+
|| { printf 'rejected %s: %s (quarantine failed; event retained)\n' "$event_id" "$reason"; return 1; }
|
|
396
|
+
if ! printf '%s\n' "$reason" \
|
|
397
|
+
| fmx_private_artifact_publish_stdin "$rejected" "$event_id.reason" 600 2>/dev/null; then
|
|
398
|
+
printf 'rejected %s: %s (quarantine failed; event retained)\n' "$event_id" "$reason"
|
|
399
|
+
return 1
|
|
400
|
+
fi
|
|
401
|
+
if ! event_payload=$(cat "$file" 2>/dev/null); then
|
|
402
|
+
printf 'rejected %s: %s (quarantine failed; event retained)\n' "$event_id" "$reason"
|
|
403
|
+
return 1
|
|
404
|
+
fi
|
|
405
|
+
if ! printf '%s' "$event_payload" \
|
|
406
|
+
| fmx_private_artifact_publish_stdin "$rejected" "$event_id.json" 600 2>/dev/null; then
|
|
407
|
+
printf 'rejected %s: %s (quarantine failed; event retained)\n' "$event_id" "$reason"
|
|
408
|
+
return 1
|
|
409
|
+
fi
|
|
410
|
+
if ! rm -f -- "$file" 2>/dev/null; then
|
|
411
|
+
printf 'rejected %s: %s (quarantine cleanup failed; event retained)\n' "$event_id" "$reason"
|
|
412
|
+
return 1
|
|
413
|
+
fi
|
|
414
|
+
printf 'rejected %s: %s\n' "$event_id" "$reason"
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
cmd_consume() {
|
|
418
|
+
gate_or_exit
|
|
419
|
+
fm_pf_has_events "$STATE" || exit 0
|
|
420
|
+
require_tools
|
|
421
|
+
|
|
422
|
+
local events_dir consumed_dir stderr_file file event_id payload derived out rc reason consume_rc=0
|
|
423
|
+
local obligation delivery request platform
|
|
424
|
+
events_dir=$(fm_pf_events_dir "$STATE")
|
|
425
|
+
consumed_dir=$(fm_pf_consumed_dir "$STATE")
|
|
426
|
+
fmx_private_artifact_dir_prepare "$consumed_dir" >/dev/null \
|
|
427
|
+
|| die "could not prepare the consumed-event ledger" 1
|
|
428
|
+
stderr_file=$(mktemp "${TMPDIR:-/tmp}/fm-pf-consume.XXXXXX") \
|
|
429
|
+
|| die "could not stage the reconciliation log" 1
|
|
430
|
+
PF_TEMP_FILES+=("$stderr_file")
|
|
431
|
+
|
|
432
|
+
for file in "$events_dir"/*.json; do
|
|
433
|
+
[ -f "$file" ] && [ ! -L "$file" ] || continue
|
|
434
|
+
event_id=$(basename "$file" .json)
|
|
435
|
+
|
|
436
|
+
if ! fm_pf_slug_valid "$event_id"; then
|
|
437
|
+
printf 'rejected %s: unsafe event filename (event retained)\n' "$event_id"
|
|
438
|
+
consume_rc=1
|
|
439
|
+
continue
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
# Already accepted on an earlier pass (duplicate emit, or a replay after
|
|
443
|
+
# restart): drop the copy without touching the state machine.
|
|
444
|
+
if [ -f "$consumed_dir/$event_id" ]; then
|
|
445
|
+
rm -f -- "$file" 2>/dev/null || true
|
|
446
|
+
continue
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
if [ "$(wc -c < "$file" 2>/dev/null || echo 0)" -gt "$FM_PF_EVENT_BYTES_MAX" ]; then
|
|
450
|
+
reject_event "$file" "$event_id" "event exceeds $FM_PF_EVENT_BYTES_MAX bytes" || consume_rc=1
|
|
451
|
+
continue
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
if ! payload=$(jq -ce . "$file" 2>/dev/null) || [ -z "$payload" ]; then
|
|
455
|
+
reject_event "$file" "$event_id" "event is not valid JSON" || consume_rc=1
|
|
456
|
+
continue
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
# The filename, the declared event_id, and the identity tuple must all agree.
|
|
460
|
+
# A mismatch means the file was hand-edited or built by something other than
|
|
461
|
+
# fm-public-followup-emit.sh, so it is refused before tasks-axi sees it.
|
|
462
|
+
if [ "$(pf_field "$payload" '.event_id')" != "$event_id" ]; then
|
|
463
|
+
reject_event "$file" "$event_id" "declared event_id does not match the filename" || consume_rc=1
|
|
464
|
+
continue
|
|
465
|
+
fi
|
|
466
|
+
derived=$(fm_pf_event_id \
|
|
467
|
+
"$(pf_field "$payload" '.obligation_id')" \
|
|
468
|
+
"$(pf_field "$payload" '.relation_id')" \
|
|
469
|
+
"$(pf_field "$payload" '.source_home_id')" \
|
|
470
|
+
"$(pf_field "$payload" '.work_id')" \
|
|
471
|
+
"$(pf_field "$payload" '.generation')" \
|
|
472
|
+
"$(pf_field "$payload" '.outcome_type')" \
|
|
473
|
+
"$(printf '%s' "$payload" | jq -Sc '.deliverables // {}' 2>/dev/null)")
|
|
474
|
+
if [ -z "$derived" ] || [ "$derived" != "$event_id" ]; then
|
|
475
|
+
reject_event "$file" "$event_id" "event id does not match its own identity fields" || consume_rc=1
|
|
476
|
+
continue
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
obligation=$(pf_field "$payload" '.obligation_id')
|
|
480
|
+
if ! fm_pf_slug_valid "$obligation"; then
|
|
481
|
+
reject_event "$file" "$event_id" "unsafe obligation id in event" || consume_rc=1
|
|
482
|
+
continue
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
# tasks-axi is the authority on source home, work id, generation, schema,
|
|
486
|
+
# outcome, and deliverables. Anything it refuses is quarantined verbatim.
|
|
487
|
+
# stderr is captured separately so a warning can never corrupt the JSON that
|
|
488
|
+
# the accepted path parses.
|
|
489
|
+
if out=$(tx public-followup work-event "$obligation" --event-file "$file" --json 2>"$stderr_file"); then
|
|
490
|
+
rc=0
|
|
491
|
+
else
|
|
492
|
+
rc=$?
|
|
493
|
+
fi
|
|
494
|
+
if [ "$rc" -ne 0 ]; then
|
|
495
|
+
reason=$( { cat "$stderr_file" 2>/dev/null; printf '%s\n' "$out"; } \
|
|
496
|
+
| grep -v '^[[:space:]]*$' | head -1 | fm_pf_clean_outcome_text | fm_pf_bound_bytes 400)
|
|
497
|
+
reject_event "$file" "$event_id" "${reason:-tasks-axi refused the event}" || consume_rc=1
|
|
498
|
+
continue
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
if ! printf 'accepted %s\n' "$(now_rfc3339)" \
|
|
502
|
+
| fmx_private_artifact_publish_stdin "$consumed_dir" "$event_id" 600 2>/dev/null; then
|
|
503
|
+
printf 'accepted %s: consumed ledger could not be recorded; event retained for reconciliation\n' "$event_id"
|
|
504
|
+
consume_rc=1
|
|
505
|
+
continue
|
|
506
|
+
fi
|
|
507
|
+
if ! rm -f -- "$file" 2>/dev/null; then
|
|
508
|
+
printf 'accepted %s: consumed ledger recorded but event could not be removed; event retained for reconciliation\n' "$event_id"
|
|
509
|
+
consume_rc=1
|
|
510
|
+
continue
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
delivery=$(printf '%s' "$out" | jq -r '.task.public_followup.delivery.state // empty' 2>/dev/null)
|
|
514
|
+
if [ "$delivery" = ready ]; then
|
|
515
|
+
request=$(printf '%s' "$out" | jq -r '.task.public_followup.request.request_id // empty' 2>/dev/null)
|
|
516
|
+
platform=$(printf '%s' "$out" | jq -r '.task.public_followup.request.platform // empty' 2>/dev/null)
|
|
517
|
+
printf 'ready %s %s %s\n' "$obligation" "${request:-unknown}" "${platform:-unknown}"
|
|
518
|
+
fi
|
|
519
|
+
done
|
|
520
|
+
|
|
521
|
+
# A fresh event must be able to wake firstmate again, so drop the surfaced
|
|
522
|
+
# signature once the inbox has been worked.
|
|
523
|
+
rm -f -- "$(fm_pf_root "$STATE")/$FM_PF_SURFACED_BASENAME" 2>/dev/null || true
|
|
524
|
+
return "$consume_rc"
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
# --- subcommand: pending ----------------------------------------------------
|
|
528
|
+
|
|
529
|
+
# print_open_loop <id> <payload>: the session-start line for a public loop that
|
|
530
|
+
# is still open after delivery (or whose obligation has left the backlog).
|
|
531
|
+
print_window_escalation() {
|
|
532
|
+
local expires=$1 window
|
|
533
|
+
window=$(fm_pf_followup_window_class "$expires")
|
|
534
|
+
case "$window" in
|
|
535
|
+
expired)
|
|
536
|
+
printf ' DEADLINE: thread can no longer be reached (window closed %s); this needs a captain decision\n' \
|
|
537
|
+
"${expires:-unknown}"
|
|
538
|
+
;;
|
|
539
|
+
closing)
|
|
540
|
+
printf ' DEADLINE: window closes %s (under 48 hours)\n' "${expires:-unknown}"
|
|
541
|
+
;;
|
|
542
|
+
esac
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
print_open_loop() {
|
|
546
|
+
local id=$1 payload=$2 request platform summary delivered expires ctx
|
|
547
|
+
request=$(fm_pf_registry_get "$STATE" "$id" request_id)
|
|
548
|
+
[ -n "$request" ] || request=$(pf_field "$payload" '.public_followup.request.request_id')
|
|
549
|
+
platform=$(fm_pf_registry_get "$STATE" "$id" platform)
|
|
550
|
+
[ -n "$platform" ] || platform=$(pf_field "$payload" '.public_followup.request.platform')
|
|
551
|
+
delivered=$(fm_pf_registry_get "$STATE" "$id" delivered_at)
|
|
552
|
+
expires=$(fm_pf_registry_get "$STATE" "$id" followup_expires_at)
|
|
553
|
+
[ -n "$expires" ] || expires=$(pf_field "$payload" '.public_followup.request.followup_expires_at')
|
|
554
|
+
summary=$(pf_field "$payload" '.public_followup.request.public_safe_summary' | fm_pf_clean_outcome_text)
|
|
555
|
+
if [ -z "$summary" ]; then
|
|
556
|
+
ctx=$(fm_pf_registry_get "$STATE" "$id" request_context_b64)
|
|
557
|
+
if [ -n "$ctx" ]; then
|
|
558
|
+
summary=$(printf '%s' "$ctx" | fm_pf_b64_decode | jq -r '.public_safe_summary // empty' 2>/dev/null | fm_pf_clean_outcome_text)
|
|
559
|
+
fi
|
|
560
|
+
fi
|
|
561
|
+
printf 'open-loop %s request=%s platform=%s\n' "$id" "${request:-unknown}" "${platform:-unknown}"
|
|
562
|
+
printf ' delivered=%s window-closes=%s\n' "${delivered:-unknown}" "${expires:-unknown}"
|
|
563
|
+
printf ' summary=%s\n' "$summary"
|
|
564
|
+
if ! fm_pf_registry_rechainable "$STATE" "$id"; then
|
|
565
|
+
printf ' unrechainable: pre-change registration lacks request_context_b64\n'
|
|
566
|
+
fi
|
|
567
|
+
print_window_escalation "$expires"
|
|
568
|
+
printf ' -> bind the follow-on with rechain, or close the loop with retire %s --reason ...\n' "$id"
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
cmd_pending() {
|
|
572
|
+
gate_or_exit
|
|
573
|
+
|
|
574
|
+
local listing id payload delivery task_state summary platform request expires printed=0 loop_state settled stamp_rc
|
|
575
|
+
# An unreadable backlog with registrations present is exactly the silence this
|
|
576
|
+
# whole path exists to prevent, so say so rather than printing nothing.
|
|
577
|
+
if ! command -v jq >/dev/null 2>&1 || ! command -v tasks-axi >/dev/null 2>&1 \
|
|
578
|
+
|| ! listing=$(tx public-followup list --json 2>/dev/null) || [ -z "$listing" ] \
|
|
579
|
+
|| ! printf '%s' "$listing" | jq -e '
|
|
580
|
+
type == "object"
|
|
581
|
+
and (.public_followups | type == "array")
|
|
582
|
+
and all(.public_followups[];
|
|
583
|
+
type == "object"
|
|
584
|
+
and (.id | type == "string")
|
|
585
|
+
and (.public_followup | type == "object")
|
|
586
|
+
and (.state | type == "string"))
|
|
587
|
+
' >/dev/null 2>&1; then
|
|
588
|
+
if fm_pf_has_registrations "$STATE"; then
|
|
589
|
+
printf 'cannot read this home'\''s public commitments through tasks-axi; %s registration(s) are still recorded under state/%s/registry\n' \
|
|
590
|
+
"$(fm_pf_registry_ids "$STATE" | grep -c . || true)" "$FM_PF_DIRNAME"
|
|
591
|
+
printed=1
|
|
592
|
+
fi
|
|
593
|
+
if fm_pf_has_events "$STATE"; then
|
|
594
|
+
printf 'unconsumed terminal results are waiting; run %s/bin/fm-public-followup.sh consume\n' "$FM_ROOT"
|
|
595
|
+
printed=1
|
|
596
|
+
fi
|
|
597
|
+
[ "$printed" -eq 1 ] || exit 0
|
|
598
|
+
return 0
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
while IFS= read -r id; do
|
|
602
|
+
[ -n "$id" ] || continue
|
|
603
|
+
payload=$(printf '%s' "$listing" | jq -ce --arg id "$id" \
|
|
604
|
+
'(.public_followups // []) | map(select(.id == $id)) | .[0] // empty' 2>/dev/null)
|
|
605
|
+
loop_state=$(fm_pf_registry_loop_state "$STATE" "$id")
|
|
606
|
+
delivery=$(pf_field "$payload" '.public_followup.delivery.state')
|
|
607
|
+
task_state=$(pf_field "$payload" '.state')
|
|
608
|
+
settled=0
|
|
609
|
+
if [ -z "$payload" ] || [ "$task_state" = 'done' ] \
|
|
610
|
+
|| [ "$delivery" = 'posted' ] || [ "$delivery" = 'waived' ] \
|
|
611
|
+
|| [ "$loop_state" = delivered ]; then
|
|
612
|
+
settled=1
|
|
613
|
+
fi
|
|
614
|
+
if [ "$settled" -eq 1 ]; then
|
|
615
|
+
if [ "$loop_state" != delivered ]; then
|
|
616
|
+
stamp_rc=0
|
|
617
|
+
fm_pf_registry_stamp_delivered "$STATE" "$id" "$(now_rfc3339)" || stamp_rc=$?
|
|
618
|
+
if [ "$stamp_rc" -eq 3 ] && fm_pf_retirement_receipt_exists "$STATE" "$id"; then
|
|
619
|
+
continue
|
|
620
|
+
fi
|
|
621
|
+
[ "$stamp_rc" -eq 0 ] \
|
|
622
|
+
|| die "could not stamp settled registration '$id' as delivered" 1
|
|
623
|
+
fi
|
|
624
|
+
# Keep the registration. Clearing a leftover legacy link is best-effort
|
|
625
|
+
# and never the close; only retire removes the record.
|
|
626
|
+
if public_followup_registration_valid "$id"; then
|
|
627
|
+
clear_public_followup_link "$id" >/dev/null 2>&1 || true
|
|
628
|
+
fi
|
|
629
|
+
print_open_loop "$id" "$payload"
|
|
630
|
+
printed=1
|
|
631
|
+
continue
|
|
632
|
+
fi
|
|
633
|
+
summary=$(pf_field "$payload" '.public_followup.request.public_safe_summary' | fm_pf_clean_outcome_text)
|
|
634
|
+
platform=$(pf_field "$payload" '.public_followup.request.platform')
|
|
635
|
+
request=$(pf_field "$payload" '.public_followup.request.request_id')
|
|
636
|
+
printf 'unresolved %s state=%s platform=%s request=%s summary=%s\n' \
|
|
637
|
+
"$id" "${delivery:-unknown}" "${platform:-unknown}" "${request:-unknown}" "$summary"
|
|
638
|
+
expires=$(fm_pf_registry_get "$STATE" "$id" followup_expires_at)
|
|
639
|
+
[ -n "$expires" ] || expires=$(pf_field "$payload" '.public_followup.request.followup_expires_at')
|
|
640
|
+
print_window_escalation "$expires"
|
|
641
|
+
if ! fm_pf_registry_rechainable "$STATE" "$id"; then
|
|
642
|
+
printf ' unrechainable: pre-change registration lacks request_context_b64\n'
|
|
643
|
+
fi
|
|
644
|
+
printed=1
|
|
645
|
+
done <<EOF
|
|
646
|
+
$(fm_pf_registry_ids "$STATE")
|
|
647
|
+
EOF
|
|
648
|
+
|
|
649
|
+
# Events that arrived while no agent was present are actionable on their own,
|
|
650
|
+
# so surface them even when every registration currently looks settled.
|
|
651
|
+
if fm_pf_has_events "$STATE"; then
|
|
652
|
+
printf 'unconsumed terminal results are waiting; run %s/bin/fm-public-followup.sh consume\n' "$FM_ROOT"
|
|
653
|
+
printed=1
|
|
654
|
+
fi
|
|
655
|
+
[ "$printed" -eq 1 ] || exit 0
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
# --- subcommand: deliver ----------------------------------------------------
|
|
659
|
+
|
|
660
|
+
public_followup_registration_valid() {
|
|
661
|
+
local id=$1 file relation work_home work_id generation
|
|
662
|
+
file="$(fm_pf_registry_dir "$STATE")/$id"
|
|
663
|
+
[ -f "$file" ] && [ ! -L "$file" ] || return 1
|
|
664
|
+
relation=$(fm_pf_registry_get "$STATE" "$id" relation_id)
|
|
665
|
+
work_home=$(fm_pf_registry_get "$STATE" "$id" work_home)
|
|
666
|
+
work_id=$(fm_pf_registry_get "$STATE" "$id" work_id)
|
|
667
|
+
generation=$(fm_pf_registry_get "$STATE" "$id" generation)
|
|
668
|
+
[ -n "$relation" ] && [ -n "$work_id" ] || return 1
|
|
669
|
+
fm_pf_home_id_valid "$work_home" || return 1
|
|
670
|
+
fm_pf_slug_valid "$work_id" || return 1
|
|
671
|
+
case "$generation" in ''|*[!0-9]*) return 1 ;; esac
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
public_followup_secondmate_home() {
|
|
675
|
+
local id=$1 include_absent=${2:-} meta_home registry_home home marker
|
|
676
|
+
fm_pf_home_id_valid "secondmate:$id" || return 1
|
|
677
|
+
meta_home=$(fmx_meta_get "$STATE/$id.meta" home)
|
|
678
|
+
registry_home=
|
|
679
|
+
if [ -f "$DATA/secondmates.md" ] && [ ! -L "$DATA/secondmates.md" ]; then
|
|
680
|
+
registry_home=$(secondmate_registry_field "$DATA/secondmates.md" "$id" home || true)
|
|
681
|
+
fi
|
|
682
|
+
if [ -n "$meta_home" ] && [ -n "$registry_home" ] && [ "$meta_home" != "$registry_home" ]; then
|
|
683
|
+
return 2
|
|
684
|
+
fi
|
|
685
|
+
home=${meta_home:-$registry_home}
|
|
686
|
+
[ -n "$home" ] || return 4
|
|
687
|
+
case "$home" in /*) ;; *) return 2 ;; esac
|
|
688
|
+
if [ ! -e "$home" ]; then
|
|
689
|
+
[ ! -L "$home" ] || return 2
|
|
690
|
+
[ "$include_absent" = include-absent ] && printf '%s\n' "$home"
|
|
691
|
+
return 3
|
|
692
|
+
fi
|
|
693
|
+
home=$(CDPATH='' cd -- "$home" 2>/dev/null && pwd -P) || return 2
|
|
694
|
+
[ -f "$home/.fm-secondmate-home" ] && [ ! -L "$home/.fm-secondmate-home" ] || return 2
|
|
695
|
+
marker=$(sed -n '1p' "$home/.fm-secondmate-home" 2>/dev/null)
|
|
696
|
+
[ "$marker" = "$id" ] || return 2
|
|
697
|
+
printf '%s\n' "$home"
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
clear_public_followup_link() {
|
|
701
|
+
local id=$1 work_home work_home_path work_id home state rc
|
|
702
|
+
public_followup_registration_valid "$id" || return 1
|
|
703
|
+
work_home=$(fm_pf_registry_get "$STATE" "$id" work_home)
|
|
704
|
+
work_id=$(fm_pf_registry_get "$STATE" "$id" work_id)
|
|
705
|
+
[ -n "$work_home" ] && [ -n "$work_id" ] || return 1
|
|
706
|
+
case "$work_home" in
|
|
707
|
+
main)
|
|
708
|
+
home=$FM_HOME
|
|
709
|
+
state=$STATE
|
|
710
|
+
;;
|
|
711
|
+
secondmate:*)
|
|
712
|
+
work_home_path=$(fm_pf_registry_get "$STATE" "$id" work_home_path)
|
|
713
|
+
case "$work_home_path" in /*) ;; *) return 1 ;; esac
|
|
714
|
+
case "$work_home_path" in *$'\n'*|*$'\r'*) return 1 ;; esac
|
|
715
|
+
rc=0
|
|
716
|
+
home=$(public_followup_secondmate_home "${work_home#secondmate:}" include-absent) || rc=$?
|
|
717
|
+
if [ "$rc" -eq 3 ]; then
|
|
718
|
+
[ "$home" = "$work_home_path" ] || return 1
|
|
719
|
+
[ ! -e "$work_home_path" ] && [ ! -L "$work_home_path" ] || return 1
|
|
720
|
+
return 0
|
|
721
|
+
fi
|
|
722
|
+
if [ "$rc" -eq 4 ]; then
|
|
723
|
+
[ ! -e "$work_home_path" ] && [ ! -L "$work_home_path" ] || return 1
|
|
724
|
+
return 0
|
|
725
|
+
fi
|
|
726
|
+
[ "$rc" -eq 0 ] || return 1
|
|
727
|
+
[ "$home" = "$work_home_path" ] || return 1
|
|
728
|
+
state="$home/state"
|
|
729
|
+
;;
|
|
730
|
+
*) return 1 ;;
|
|
731
|
+
esac
|
|
732
|
+
FM_HOME="$home" FM_STATE_OVERRIDE="$state" FM_ROOT_OVERRIDE="$FM_ROOT" \
|
|
733
|
+
"$FM_ROOT/bin/fm-x-followup.sh" --clear "$work_id" >/dev/null
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
public_followup_legacy_link_status() {
|
|
737
|
+
local payload=$1 relations work_home work_id home meta
|
|
738
|
+
if ! printf '%s' "$payload" | jq -e '
|
|
739
|
+
(.public_followup.work_relations | type == "array")
|
|
740
|
+
and all(.public_followup.work_relations[];
|
|
741
|
+
(.work_ref.home_id | type == "string")
|
|
742
|
+
and (.work_ref.task_id | type == "string")
|
|
743
|
+
)
|
|
744
|
+
' >/dev/null 2>&1; then
|
|
745
|
+
return 2
|
|
746
|
+
fi
|
|
747
|
+
relations=$(printf '%s' "$payload" | jq -r '
|
|
748
|
+
.public_followup.work_relations[]
|
|
749
|
+
| [.work_ref.home_id, .work_ref.task_id]
|
|
750
|
+
| @tsv
|
|
751
|
+
' 2>/dev/null) || return 2
|
|
752
|
+
[ -n "$relations" ] || return 2
|
|
753
|
+
while IFS=$'\t' read -r work_home work_id; do
|
|
754
|
+
[ -n "$work_home" ] && [ -n "$work_id" ] || return 2
|
|
755
|
+
case "$work_home" in
|
|
756
|
+
main) home=$FM_HOME ;;
|
|
757
|
+
secondmate:*) home=$(public_followup_secondmate_home "${work_home#secondmate:}") || return 2 ;;
|
|
758
|
+
*) return 2 ;;
|
|
759
|
+
esac
|
|
760
|
+
meta="$home/state/$work_id.meta"
|
|
761
|
+
[ -e "$meta" ] || continue
|
|
762
|
+
[ -f "$meta" ] && [ ! -L "$meta" ] || return 2
|
|
763
|
+
[ -n "$(fmx_meta_get "$meta" x_request)" ] && return 0
|
|
764
|
+
done <<EOF
|
|
765
|
+
$relations
|
|
766
|
+
EOF
|
|
767
|
+
return 1
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
record_error() {
|
|
771
|
+
local id=$1 attempt=$2 state=$3 code=$4 next=$5 tmp rc
|
|
772
|
+
tmp=$(mktemp "${TMPDIR:-/tmp}/fm-pf-error.XXXXXX") || return 1
|
|
773
|
+
if [ -n "$next" ]; then
|
|
774
|
+
jq -n --argjson a "$attempt" --arg s "$state" --arg c "$code" \
|
|
775
|
+
--arg o "$(now_rfc3339)" --arg n "$next" \
|
|
776
|
+
'{state:$s, attempt_count:$a, error_code:$c, occurred_at:$o, next_attempt_at:$n}' > "$tmp"
|
|
777
|
+
else
|
|
778
|
+
jq -n --argjson a "$attempt" --arg s "$state" --arg c "$code" --arg o "$(now_rfc3339)" \
|
|
779
|
+
'{state:$s, attempt_count:$a, error_code:$c, occurred_at:$o}' > "$tmp"
|
|
780
|
+
fi
|
|
781
|
+
tx public-followup record-error "$id" --error-file "$tmp" >/dev/null 2>&1
|
|
782
|
+
rc=$?
|
|
783
|
+
rm -f -- "$tmp"
|
|
784
|
+
return "$rc"
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
record_posted() {
|
|
788
|
+
local id=$1 attempt=$2 request=$3 platform=$4 chunks=$5 tmp rc
|
|
789
|
+
tmp=$(mktemp "${TMPDIR:-/tmp}/fm-pf-receipt.XXXXXX") || return 1
|
|
790
|
+
jq -n --argjson a "$attempt" --arg r "$request" --arg p "$platform" \
|
|
791
|
+
--argjson c "$chunks" --arg t "$(now_rfc3339)" \
|
|
792
|
+
'{state:"posted", request_id:$r, platform:$p, attempt_count:$a,
|
|
793
|
+
total_chunks:$c, posted_chunks:$c, posted_at:$t}' > "$tmp"
|
|
794
|
+
tx public-followup record-delivery "$id" --receipt-file "$tmp" >/dev/null 2>&1
|
|
795
|
+
rc=$?
|
|
796
|
+
rm -f -- "$tmp"
|
|
797
|
+
return "$rc"
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
# Delivery keeps the registration. Stamp it delivered and tell the caller the
|
|
801
|
+
# public loop is still open.
|
|
802
|
+
mark_loop_delivered() {
|
|
803
|
+
local id=$1 rc=0
|
|
804
|
+
fm_pf_registry_stamp_delivered "$STATE" "$id" "$(now_rfc3339)" || rc=$?
|
|
805
|
+
case "$rc" in
|
|
806
|
+
0) return 0 ;;
|
|
807
|
+
3) return 3 ;;
|
|
808
|
+
*) die "could not stamp registration '$id' as delivered after the public reply landed" 1 ;;
|
|
809
|
+
esac
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
print_loop_open_disposition() {
|
|
813
|
+
local id=$1 request=$2
|
|
814
|
+
printf "thread %s is still OPEN: hand it on with 'rechain ...' or close it with 'retire %s --reason ...'\n" \
|
|
815
|
+
"${request:-unknown}" "$id"
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
cmd_deliver() {
|
|
819
|
+
local id=${1:-} text_file=
|
|
820
|
+
[ -n "$id" ] || { usage; exit 2; }
|
|
821
|
+
shift
|
|
822
|
+
while [ "$#" -gt 0 ]; do
|
|
823
|
+
case "$1" in
|
|
824
|
+
--text-file) shift; text_file=${1:-} ;;
|
|
825
|
+
*) die "unknown argument '$1'" ;;
|
|
826
|
+
esac
|
|
827
|
+
shift || true
|
|
828
|
+
done
|
|
829
|
+
|
|
830
|
+
fm_pf_slug_valid "$id" || die "unsafe obligation id: $id"
|
|
831
|
+
fm_pf_relay_active "$FM_HOME" \
|
|
832
|
+
|| die "this home has not opted into the myfirstmate relay, so it cannot post a public reply" 1
|
|
833
|
+
require_tools
|
|
834
|
+
|
|
835
|
+
local payload delivery attempt request platform text tmp_text hash chunks rc receipt receipt_fields receipt_dry_run link_status
|
|
836
|
+
local loop_retained=0
|
|
837
|
+
payload=$(obligation_json "$id") || die "could not read the backlog through tasks-axi" 1
|
|
838
|
+
[ -n "$payload" ] || die "no public-followup obligation '$id' in this home's backlog" 1
|
|
839
|
+
|
|
840
|
+
delivery=$(pf_field "$payload" '.public_followup.delivery.state')
|
|
841
|
+
request=$(pf_field "$payload" '.public_followup.request.request_id')
|
|
842
|
+
platform=$(pf_field "$payload" '.public_followup.request.platform')
|
|
843
|
+
attempt=$(pf_field "$payload" '.public_followup.delivery.attempt_count')
|
|
844
|
+
case "$attempt" in ''|*[!0-9]*) attempt=0 ;; esac
|
|
845
|
+
|
|
846
|
+
case "$delivery" in
|
|
847
|
+
posted|waived)
|
|
848
|
+
if public_followup_registration_valid "$id"; then
|
|
849
|
+
if ! clear_public_followup_link "$id"; then
|
|
850
|
+
die "obligation '$id' is already $delivery, but its legacy X link could not be cleared; the registration was retained for reconciliation" 1
|
|
851
|
+
fi
|
|
852
|
+
else
|
|
853
|
+
link_status=1
|
|
854
|
+
public_followup_legacy_link_status "$payload" || link_status=$?
|
|
855
|
+
case "$link_status" in
|
|
856
|
+
0) die "obligation '$id' is already $delivery, but its legacy X link cannot be cleared without a valid registration; reconcile it before any later terminal follow-up" 1 ;;
|
|
857
|
+
1) ;;
|
|
858
|
+
*) die "obligation '$id' is already $delivery, but its registration is missing or invalid and the legacy X link cannot be verified; reconcile it before any later terminal follow-up" 1 ;;
|
|
859
|
+
esac
|
|
860
|
+
fi
|
|
861
|
+
if mark_loop_delivered "$id"; then loop_retained=1; fi
|
|
862
|
+
printf 'already delivered %s state=%s\n' "$id" "$delivery"
|
|
863
|
+
[ "$loop_retained" -eq 0 ] || print_loop_open_disposition "$id" "$request"
|
|
864
|
+
return 0
|
|
865
|
+
;;
|
|
866
|
+
ready|retry-due|context-blocked|unknown|partial)
|
|
867
|
+
public_followup_registration_valid "$id" \
|
|
868
|
+
|| die "public-followup registration for '$id' is missing or invalid; reconcile it before delivery so any legacy X link can be cleared" 1
|
|
869
|
+
;;
|
|
870
|
+
delivery-posting)
|
|
871
|
+
die "obligation '$id' is mid-delivery on attempt $attempt: a previous post was started and its outcome was never recorded. Confirm whether that post landed, then close it with 'record-posted $id --attempt $attempt --chunks <exact-count>' or reopen it for retry. Posting again here could duplicate the public reply." 1
|
|
872
|
+
;;
|
|
873
|
+
pending-work)
|
|
874
|
+
die "obligation '$id' is still waiting on its bound work; nothing to deliver yet" 1
|
|
875
|
+
;;
|
|
876
|
+
*)
|
|
877
|
+
die "obligation '$id' is in delivery state '${delivery:-unknown}', which is not deliverable" 1
|
|
878
|
+
;;
|
|
879
|
+
esac
|
|
880
|
+
|
|
881
|
+
[ -n "$request" ] || die "obligation '$id' has no relay request id; its thread binding is unusable" 1
|
|
882
|
+
|
|
883
|
+
if [ -n "$text_file" ]; then
|
|
884
|
+
[ -f "$text_file" ] || die "reply text file not found: $text_file"
|
|
885
|
+
text=$(cat "$text_file")
|
|
886
|
+
else
|
|
887
|
+
# Deterministic default: reuse the accepted terminal event's bounded
|
|
888
|
+
# public-safe outcome exactly rather than paraphrasing a landed result.
|
|
889
|
+
text=$(printf '%s' "$payload" | jq -r '
|
|
890
|
+
[(.public_followup.work_relations // [])[]
|
|
891
|
+
| (.accepted_events // [])[]
|
|
892
|
+
| .public_safe_outcome // empty] | last // empty' 2>/dev/null)
|
|
893
|
+
[ -n "$text" ] \
|
|
894
|
+
|| die "obligation '$id' carries no accepted public-safe outcome to reuse; pass --text-file with the reply you composed" 1
|
|
895
|
+
fi
|
|
896
|
+
[ -n "$text" ] || die "the reply text is empty" 2
|
|
897
|
+
|
|
898
|
+
tmp_text=$(mktemp "${TMPDIR:-/tmp}/fm-pf-text.XXXXXX") || die "could not stage the reply text" 1
|
|
899
|
+
PF_TEMP_FILES+=("$tmp_text")
|
|
900
|
+
receipt=$(mktemp "${TMPDIR:-/tmp}/fm-pf-postreceipt.XXXXXX") || die "could not stage the post receipt" 1
|
|
901
|
+
PF_TEMP_FILES+=("$receipt")
|
|
902
|
+
printf '%s' "$text" > "$tmp_text"
|
|
903
|
+
|
|
904
|
+
hash=$(fm_pf_sha256 < "$tmp_text") || die "sha256 (shasum or sha256sum) is required" 1
|
|
905
|
+
[ -n "$hash" ] || die "could not hash the reply payload" 1
|
|
906
|
+
|
|
907
|
+
# begin-delivery is what makes a retry safe: it pins the attempt and the exact
|
|
908
|
+
# payload before anything leaves the machine. The attempt is read back rather
|
|
909
|
+
# than assumed, because every later receipt or error must name it exactly.
|
|
910
|
+
local begun
|
|
911
|
+
begun=$(tx public-followup begin-delivery "$id" --payload-hash "$hash" --json 2>/dev/null) \
|
|
912
|
+
|| die "tasks-axi refused to begin delivery for '$id'" 1
|
|
913
|
+
attempt=$(printf '%s' "$begun" | jq -r '.task.public_followup.delivery.attempt_count // empty' 2>/dev/null)
|
|
914
|
+
case "$attempt" in
|
|
915
|
+
''|*[!0-9]*) die "could not read the delivery attempt for '$id' after beginning it; nothing was posted" 1 ;;
|
|
916
|
+
esac
|
|
917
|
+
|
|
918
|
+
rc=0
|
|
919
|
+
FMX_REPLY_PLATFORM="$platform" FM_HOME="$FM_HOME" \
|
|
920
|
+
"$FM_ROOT/bin/fm-x-reply.sh" "$request" --followup --receipt-file "$receipt" \
|
|
921
|
+
--text-file "$tmp_text" >/dev/null || rc=$?
|
|
922
|
+
|
|
923
|
+
if [ "$rc" -eq 0 ]; then
|
|
924
|
+
receipt_fields=$(jq -er --arg request "$request" '
|
|
925
|
+
if type != "object" or .request_id != $request or .endpoint != "followup"
|
|
926
|
+
or (.chunks | type) != "number" or (.chunks < 1) or (.chunks != (.chunks | floor))
|
|
927
|
+
or (.dry_run | type) != "boolean" then error("invalid receipt")
|
|
928
|
+
else [(.chunks | tostring), (.dry_run | tostring)] | @tsv end
|
|
929
|
+
' "$receipt" 2>/dev/null) \
|
|
930
|
+
|| die "the public reply for '$id' POSTED but its receipt is missing or invalid; inspect the relay and close it with 'record-posted $id --attempt $attempt --chunks <exact-count>' before any retry" 1
|
|
931
|
+
IFS=$'\t' read -r chunks receipt_dry_run <<EOF
|
|
932
|
+
$receipt_fields
|
|
933
|
+
EOF
|
|
934
|
+
if [ "$receipt_dry_run" = true ]; then
|
|
935
|
+
if ! record_error "$id" "$attempt" retry-due dry_run_no_post "$(next_attempt_rfc3339)"; then
|
|
936
|
+
die "dry-run for '$id' did not post and its retryable state could not be recorded; the obligation remains mid-delivery and needs explicit reconciliation before retry" 1
|
|
937
|
+
fi
|
|
938
|
+
die "dry-run for '$id' did not post; recorded as retryable and left the obligation open" 1
|
|
939
|
+
fi
|
|
940
|
+
if record_posted "$id" "$attempt" "$request" "$platform" "$chunks"; then
|
|
941
|
+
if ! clear_public_followup_link "$id"; then
|
|
942
|
+
die "the public reply for '$id' POSTED and its receipt was recorded, but its legacy X link could not be cleared; the registration was retained for reconciliation" 1
|
|
943
|
+
fi
|
|
944
|
+
if mark_loop_delivered "$id"; then loop_retained=1; fi
|
|
945
|
+
printf 'delivered %s request=%s platform=%s chunks=%s\n' "$id" "$request" "$platform" "$chunks"
|
|
946
|
+
[ "$loop_retained" -eq 0 ] || print_loop_open_disposition "$id" "$request"
|
|
947
|
+
return 0
|
|
948
|
+
fi
|
|
949
|
+
die "the public reply for '$id' POSTED but its receipt could not be recorded; close it with 'record-posted $id --attempt $attempt --chunks <exact-count>' before any retry, or the thread will get a second reply" 1
|
|
950
|
+
fi
|
|
951
|
+
|
|
952
|
+
case "$rc" in
|
|
953
|
+
8) if ! record_error "$id" "$attempt" context-blocked reply_context_unresolved ""; then
|
|
954
|
+
die "the public reply for '$id' was not posted, and its held state could not be recorded; the obligation remains mid-delivery and needs explicit reconciliation before retry" 1
|
|
955
|
+
fi
|
|
956
|
+
die "held '$id': the original thread's platform or size budget could not be resolved, so nothing was posted. Retry once the request context is recoverable." 1 ;;
|
|
957
|
+
9) if ! record_error "$id" "$attempt" expired-action-required followup_binding_exhausted ""; then
|
|
958
|
+
die "the relay rejected '$id', and its expired state could not be recorded; the obligation remains mid-delivery and needs explicit reconciliation before retry" 1
|
|
959
|
+
fi
|
|
960
|
+
die "the relay no longer accepts a follow-up for '$id' (window or cap exhausted); nothing was posted and this needs a captain decision" 1 ;;
|
|
961
|
+
*) if ! record_error "$id" "$attempt" retry-due relay_post_failed "$(next_attempt_rfc3339)"; then
|
|
962
|
+
die "posting the public reply for '$id' failed, and its retryable state could not be recorded; the obligation remains mid-delivery and needs explicit reconciliation before retry" 1
|
|
963
|
+
fi
|
|
964
|
+
die "posting the public reply for '$id' failed (exit $rc); recorded as retryable, nothing was delivered" 1 ;;
|
|
965
|
+
esac
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
# --- subcommand: record-posted ---------------------------------------------
|
|
969
|
+
|
|
970
|
+
cmd_record_posted() {
|
|
971
|
+
local id=${1:-} attempt='' chunks=''
|
|
972
|
+
[ -n "$id" ] || { usage; exit 2; }
|
|
973
|
+
shift
|
|
974
|
+
while [ "$#" -gt 0 ]; do
|
|
975
|
+
case "$1" in
|
|
976
|
+
--attempt) shift; attempt=${1:-} ;;
|
|
977
|
+
--chunks) shift; chunks=${1:-} ;;
|
|
978
|
+
*) die "unknown argument '$1'" ;;
|
|
979
|
+
esac
|
|
980
|
+
shift || true
|
|
981
|
+
done
|
|
982
|
+
fm_pf_slug_valid "$id" || die "unsafe obligation id: $id"
|
|
983
|
+
case "$attempt" in ''|*[!0-9]*) die "--attempt <n> is required and must be an integer" ;; esac
|
|
984
|
+
case "$chunks" in ''|*[!0-9]*) die "--chunks <n> is required and must be a positive integer" ;; esac
|
|
985
|
+
[ "$chunks" -ge 1 ] 2>/dev/null || die "--chunks <n> is required and must be a positive integer"
|
|
986
|
+
fm_pf_relay_active "$FM_HOME" || die "the relay is not active for this home" 1
|
|
987
|
+
public_followup_registration_valid "$id" \
|
|
988
|
+
|| die "public-followup registration for '$id' is missing or invalid; reconcile it before recording a receipt so any legacy X link can be cleared" 1
|
|
989
|
+
require_tools
|
|
990
|
+
|
|
991
|
+
local payload request platform loop_retained=0
|
|
992
|
+
payload=$(obligation_json "$id") || die "could not read the backlog through tasks-axi" 1
|
|
993
|
+
[ -n "$payload" ] || die "no public-followup obligation '$id' in this home's backlog" 1
|
|
994
|
+
request=$(pf_field "$payload" '.public_followup.request.request_id')
|
|
995
|
+
platform=$(pf_field "$payload" '.public_followup.request.platform')
|
|
996
|
+
|
|
997
|
+
record_posted "$id" "$attempt" "$request" "$platform" "$chunks" \
|
|
998
|
+
|| die "tasks-axi refused the receipt for '$id' attempt $attempt; the recorded attempt must match exactly" 1
|
|
999
|
+
if ! clear_public_followup_link "$id"; then
|
|
1000
|
+
die "the receipt for '$id' was recorded, but its legacy X link could not be cleared; the registration was retained for reconciliation" 1
|
|
1001
|
+
fi
|
|
1002
|
+
if mark_loop_delivered "$id"; then loop_retained=1; fi
|
|
1003
|
+
printf 'recorded %s attempt=%s request=%s\n' "$id" "$attempt" "$request"
|
|
1004
|
+
[ "$loop_retained" -eq 0 ] || print_loop_open_disposition "$id" "$request"
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
# --- subcommand: guard-work -------------------------------------------------
|
|
1008
|
+
|
|
1009
|
+
cmd_guard_work() {
|
|
1010
|
+
local work_home=${1:-} work_id=${2:-} bound id payload delivery task_state blocked=0
|
|
1011
|
+
[ -n "$work_home" ] && [ -n "$work_id" ] || { usage; exit 2; }
|
|
1012
|
+
fm_pf_relay_active "$FM_HOME" || exit 0
|
|
1013
|
+
fm_pf_has_registrations "$STATE" || exit 0
|
|
1014
|
+
|
|
1015
|
+
# Reading the registration records needs no tools, so establish whether this
|
|
1016
|
+
# work is bound to any commitment before deciding anything else.
|
|
1017
|
+
bound=$(fm_pf_registry_ids_for_work "$STATE" "$work_home" "$work_id")
|
|
1018
|
+
[ -n "$bound" ] || exit 0
|
|
1019
|
+
|
|
1020
|
+
# From here the work IS bound to a public promise, so an unreadable state is a
|
|
1021
|
+
# blocking answer, not a pass: cleanup must never proceed on a guess.
|
|
1022
|
+
if ! command -v jq >/dev/null 2>&1 || ! command -v tasks-axi >/dev/null 2>&1; then
|
|
1023
|
+
printf 'cannot verify the public commitments bound to %s/%s: jq and tasks-axi are required\n' \
|
|
1024
|
+
"$work_home" "$work_id"
|
|
1025
|
+
exit 3
|
|
1026
|
+
fi
|
|
1027
|
+
|
|
1028
|
+
while IFS= read -r id; do
|
|
1029
|
+
[ -n "$id" ] || continue
|
|
1030
|
+
if ! payload=$(obligation_json "$id"); then
|
|
1031
|
+
printf 'cannot read the state of public commitment %s for %s/%s\n' "$id" "$work_home" "$work_id"
|
|
1032
|
+
blocked=1
|
|
1033
|
+
continue
|
|
1034
|
+
fi
|
|
1035
|
+
# Gone from the backlog entirely (pruned after Done): nothing left to owe.
|
|
1036
|
+
[ -n "$payload" ] || continue
|
|
1037
|
+
delivery=$(pf_field "$payload" '.public_followup.delivery.state')
|
|
1038
|
+
task_state=$(pf_field "$payload" '.state')
|
|
1039
|
+
case "$task_state:$delivery" in
|
|
1040
|
+
done:*|*:posted|*:waived) continue ;;
|
|
1041
|
+
esac
|
|
1042
|
+
printf 'public commitment %s is still %s for %s/%s\n' "$id" "${delivery:-unknown}" "$work_home" "$work_id"
|
|
1043
|
+
blocked=1
|
|
1044
|
+
done <<EOF
|
|
1045
|
+
$bound
|
|
1046
|
+
EOF
|
|
1047
|
+
[ "$blocked" -eq 0 ] || exit 3
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
# --- subcommand: rechain ----------------------------------------------------
|
|
1051
|
+
|
|
1052
|
+
rechain_default_deliverable_key() {
|
|
1053
|
+
case "$1" in
|
|
1054
|
+
pr-merged) printf 'pr_url\n' ;;
|
|
1055
|
+
report-ready) printf 'report_path\n' ;;
|
|
1056
|
+
*) return 1 ;;
|
|
1057
|
+
esac
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
cmd_rechain() {
|
|
1061
|
+
local new_id=${1:-} from='' work_home='' work_id='' expected=''
|
|
1062
|
+
local -a deliverable_keys=()
|
|
1063
|
+
[ -n "$new_id" ] || { usage; exit 2; }
|
|
1064
|
+
shift
|
|
1065
|
+
while [ "$#" -gt 0 ]; do
|
|
1066
|
+
case "$1" in
|
|
1067
|
+
--from) shift; from=${1:-} ;;
|
|
1068
|
+
--work-home) shift; work_home=${1:-} ;;
|
|
1069
|
+
--work-id) shift; work_id=${1:-} ;;
|
|
1070
|
+
--expected) shift; expected=${1:-} ;;
|
|
1071
|
+
--deliverable-key) shift; deliverable_keys+=("${1:-}") ;;
|
|
1072
|
+
*) die "unknown argument '$1'" ;;
|
|
1073
|
+
esac
|
|
1074
|
+
shift || true
|
|
1075
|
+
done
|
|
1076
|
+
|
|
1077
|
+
fm_pf_relay_active "$FM_HOME" \
|
|
1078
|
+
|| die "this home has not opted into the myfirstmate relay, so it cannot own a public commitment" 1
|
|
1079
|
+
require_tools
|
|
1080
|
+
fm_pf_slug_valid "$new_id" || die "unsafe obligation id: $new_id"
|
|
1081
|
+
fm_pf_slug_valid "$from" || die "unsafe source obligation id: $from"
|
|
1082
|
+
fm_pf_slug_valid "$work_id" || die "unsafe work id: $work_id"
|
|
1083
|
+
fm_pf_home_id_valid "$work_home" \
|
|
1084
|
+
|| die "work home must be 'main' or 'secondmate:<stable-id>', got '$work_home'"
|
|
1085
|
+
case "$expected" in
|
|
1086
|
+
pr-merged|report-ready|local-main) ;;
|
|
1087
|
+
*) die "--expected must be pr-merged, report-ready, or local-main, got '$expected'" ;;
|
|
1088
|
+
esac
|
|
1089
|
+
[ "$new_id" != "$from" ] || die "the new obligation id must differ from --from" 2
|
|
1090
|
+
|
|
1091
|
+
pf_registry_lock_acquire "$from" \
|
|
1092
|
+
|| die "could not lock source registration '$from' for rechain" 1
|
|
1093
|
+
local src_file loop_state expires window ctx rechain_to source_record first_claim=0 existing
|
|
1094
|
+
src_file="$(fm_pf_registry_dir "$STATE")/$from"
|
|
1095
|
+
[ -f "$src_file" ] && [ ! -L "$src_file" ] \
|
|
1096
|
+
|| die "no registration for '$from' in this home" 1
|
|
1097
|
+
loop_state=$(fm_pf_registry_loop_state "$STATE" "$from")
|
|
1098
|
+
[ "$loop_state" = delivered ] \
|
|
1099
|
+
|| die "source '$from' is not state=delivered (got '$loop_state'); nothing to hand on until that final lands" 1
|
|
1100
|
+
fm_pf_registry_rechainable "$STATE" "$from" \
|
|
1101
|
+
|| die "source '$from' is un-rechainable: a pre-change registration has no request_context_b64. Close it with retire --reason or reconstruct the request context by hand." 1
|
|
1102
|
+
|
|
1103
|
+
expires=$(fm_pf_registry_get "$STATE" "$from" followup_expires_at)
|
|
1104
|
+
[ -n "$expires" ] || die "source '$from' has no followup_expires_at; the thread window cannot be checked" 1
|
|
1105
|
+
window=$(fm_pf_followup_window_class "$expires")
|
|
1106
|
+
case "$window" in
|
|
1107
|
+
ok|closing) ;;
|
|
1108
|
+
expired)
|
|
1109
|
+
die "followup_expires_at $expires is in the past: the thread can no longer be reached, so this loop cannot be closed publicly. This is a captain decision." 1
|
|
1110
|
+
;;
|
|
1111
|
+
*)
|
|
1112
|
+
die "followup_expires_at $expires could not be parsed: the thread window cannot be checked, so this loop cannot be rechained" 1
|
|
1113
|
+
;;
|
|
1114
|
+
esac
|
|
1115
|
+
|
|
1116
|
+
if [ "${#deliverable_keys[@]}" -eq 0 ]; then
|
|
1117
|
+
local default_key
|
|
1118
|
+
default_key=$(rechain_default_deliverable_key "$expected") \
|
|
1119
|
+
|| die "--expected $expected needs --deliverable-key <k> (no default key)"
|
|
1120
|
+
deliverable_keys+=("$default_key")
|
|
1121
|
+
fi
|
|
1122
|
+
local key
|
|
1123
|
+
for key in "${deliverable_keys[@]}"; do
|
|
1124
|
+
case "$key" in
|
|
1125
|
+
''|*[!a-z0-9_]*) die "deliverable key must be lowercase [a-z0-9_], got '$key'" ;;
|
|
1126
|
+
esac
|
|
1127
|
+
done
|
|
1128
|
+
|
|
1129
|
+
# Claim the delivered baton before publishing its destination. The claim is
|
|
1130
|
+
# retained if any later retirement step fails, so a retry may resume the same
|
|
1131
|
+
# destination but can never fork this thread into a second obligation.
|
|
1132
|
+
rechain_to=$(fm_pf_registry_get "$STATE" "$from" rechain_to)
|
|
1133
|
+
if [ -n "$rechain_to" ] && [ "$rechain_to" != "$new_id" ]; then
|
|
1134
|
+
die "source '$from' is already claimed by rechain destination '$rechain_to'; resume that destination" 1
|
|
1135
|
+
fi
|
|
1136
|
+
if [ -z "$rechain_to" ]; then
|
|
1137
|
+
existing=$(obligation_json "$new_id") \
|
|
1138
|
+
|| die "could not check whether rechain destination '$new_id' is unused" 1
|
|
1139
|
+
[ -z "$existing" ] \
|
|
1140
|
+
|| die "'$new_id' already exists and was not created by this rechain; choose another id" 1
|
|
1141
|
+
[ ! -e "$(fm_pf_registry_dir "$STATE")/$new_id" ] \
|
|
1142
|
+
&& [ ! -L "$(fm_pf_registry_dir "$STATE")/$new_id" ] \
|
|
1143
|
+
&& [ ! -e "$(fm_pf_retired_dir "$STATE")/$new_id" ] \
|
|
1144
|
+
&& [ ! -L "$(fm_pf_retired_dir "$STATE")/$new_id" ] \
|
|
1145
|
+
|| die "'$new_id' already has local public-loop state; choose another id" 1
|
|
1146
|
+
source_record=$(grep -v -E '^rechain_to=' "$src_file" 2>/dev/null) \
|
|
1147
|
+
|| die "could not read source registration '$from' while claiming it" 1
|
|
1148
|
+
printf '%s\nrechain_to=%s\n' "$source_record" "$new_id" \
|
|
1149
|
+
| fmx_private_artifact_publish_stdin "$(fm_pf_registry_dir "$STATE")" "$from" 600 \
|
|
1150
|
+
|| die "could not claim source registration '$from' for '$new_id'" 1
|
|
1151
|
+
first_claim=1
|
|
1152
|
+
fi
|
|
1153
|
+
|
|
1154
|
+
local ctx_file expected_file relation_file keys_json project src_payload
|
|
1155
|
+
ctx=$(fm_pf_registry_get "$STATE" "$from" request_context_b64)
|
|
1156
|
+
ctx_file=$(mktemp "${TMPDIR:-/tmp}/fm-pf-rechain-ctx.XXXXXX") \
|
|
1157
|
+
|| die "could not stage the retained request context" 1
|
|
1158
|
+
expected_file=$(mktemp "${TMPDIR:-/tmp}/fm-pf-rechain-exp.XXXXXX") \
|
|
1159
|
+
|| die "could not stage the expected-final document" 1
|
|
1160
|
+
relation_file=$(mktemp "${TMPDIR:-/tmp}/fm-pf-rechain-rel.XXXXXX") \
|
|
1161
|
+
|| die "could not stage the relation document" 1
|
|
1162
|
+
PF_TEMP_FILES+=("$ctx_file" "$expected_file" "$relation_file")
|
|
1163
|
+
printf '%s' "$ctx" | fm_pf_b64_decode > "$ctx_file" \
|
|
1164
|
+
|| die "could not decode request_context_b64 for '$from'" 1
|
|
1165
|
+
jq -e 'type == "object" and (.request_id | type == "string")' "$ctx_file" >/dev/null 2>&1 \
|
|
1166
|
+
|| die "decoded request context for '$from' is not usable" 1
|
|
1167
|
+
|
|
1168
|
+
keys_json=$(printf '%s\n' "${deliverable_keys[@]}" | jq -R . | jq -s -c .)
|
|
1169
|
+
project=
|
|
1170
|
+
if src_payload=$(obligation_json "$from") && [ -n "$src_payload" ]; then
|
|
1171
|
+
project=$(pf_field "$src_payload" '.public_followup.expected_final.project')
|
|
1172
|
+
fi
|
|
1173
|
+
if [ -n "$project" ]; then
|
|
1174
|
+
jq -n --arg t "$expected" --arg p "$project" --argjson keys "$keys_json" \
|
|
1175
|
+
'{type:$t, project:$p, required_deliverables:$keys, completion_policy:"all-required"}' \
|
|
1176
|
+
> "$expected_file"
|
|
1177
|
+
else
|
|
1178
|
+
jq -n --arg t "$expected" --argjson keys "$keys_json" \
|
|
1179
|
+
'{type:$t, required_deliverables:$keys, completion_policy:"all-required"}' \
|
|
1180
|
+
> "$expected_file"
|
|
1181
|
+
fi
|
|
1182
|
+
jq -n --arg h "$work_home" --arg w "$work_id" \
|
|
1183
|
+
'{relation_id:"rel-1", work_ref:{home_id:$h, task_id:$w},
|
|
1184
|
+
role:"fulfills", required:true, generation:1}' > "$relation_file"
|
|
1185
|
+
|
|
1186
|
+
local relation_count new_registry
|
|
1187
|
+
if [ "$first_claim" -eq 1 ]; then
|
|
1188
|
+
existing=
|
|
1189
|
+
else
|
|
1190
|
+
existing=$(obligation_json "$new_id") \
|
|
1191
|
+
|| die "could not read the backlog through tasks-axi" 1
|
|
1192
|
+
fi
|
|
1193
|
+
if [ -n "$existing" ]; then
|
|
1194
|
+
printf '%s' "$existing" | jq -e \
|
|
1195
|
+
--slurpfile request "$ctx_file" --slurpfile expected "$expected_file" \
|
|
1196
|
+
--arg expires "$expires" \
|
|
1197
|
+
'.public_followup as $pf
|
|
1198
|
+
| $pf.request == $request[0]
|
|
1199
|
+
and $pf.purpose == "promised-final"
|
|
1200
|
+
and $pf.expected_final == $expected[0]
|
|
1201
|
+
and $pf.obligation_expires_at == $expires' >/dev/null 2>&1 \
|
|
1202
|
+
|| die "'$new_id' already exists with different public-followup data; choose another id" 1
|
|
1203
|
+
else
|
|
1204
|
+
tx public-followup add "$new_id" --request-context-file "$ctx_file" \
|
|
1205
|
+
--purpose promised-final --expected-final-file "$expected_file" \
|
|
1206
|
+
--expires-at "$expires" >/dev/null \
|
|
1207
|
+
|| die "tasks-axi refused to add '$new_id' on the retained thread binding" 1
|
|
1208
|
+
existing=$(obligation_json "$new_id") \
|
|
1209
|
+
|| die "added '$new_id' but could not read it back through tasks-axi; retry this same rechain command" 1
|
|
1210
|
+
fi
|
|
1211
|
+
|
|
1212
|
+
relation_count=$(printf '%s' "$existing" \
|
|
1213
|
+
| jq -r '(.public_followup.work_relations // []) | length' 2>/dev/null) \
|
|
1214
|
+
|| die "could not inspect work bindings for '$new_id'" 1
|
|
1215
|
+
if [ "$relation_count" -eq 0 ]; then
|
|
1216
|
+
tx public-followup bind-work "$new_id" --relation-file "$relation_file" >/dev/null \
|
|
1217
|
+
|| die "tasks-axi refused to bind '$new_id' to $work_home/$work_id; retry this same rechain command" 1
|
|
1218
|
+
else
|
|
1219
|
+
printf '%s' "$existing" | jq -e --arg h "$work_home" --arg w "$work_id" \
|
|
1220
|
+
'(.public_followup.work_relations // []) as $relations
|
|
1221
|
+
| ($relations | length) == 1
|
|
1222
|
+
and $relations[0].relation_id == "rel-1"
|
|
1223
|
+
and $relations[0].work_ref.home_id == $h
|
|
1224
|
+
and $relations[0].work_ref.task_id == $w
|
|
1225
|
+
and $relations[0].role == "fulfills"
|
|
1226
|
+
and $relations[0].required == true
|
|
1227
|
+
and $relations[0].generation == 1' >/dev/null 2>&1 \
|
|
1228
|
+
|| die "'$new_id' already has a different work binding; choose another id" 1
|
|
1229
|
+
fi
|
|
1230
|
+
|
|
1231
|
+
new_registry="$(fm_pf_registry_dir "$STATE")/$new_id"
|
|
1232
|
+
if [ -f "$new_registry" ] && [ ! -L "$new_registry" ]; then
|
|
1233
|
+
[ "$(fm_pf_registry_get "$STATE" "$new_id" relation_id)" = rel-1 ] \
|
|
1234
|
+
&& [ "$(fm_pf_registry_get "$STATE" "$new_id" work_home)" = "$work_home" ] \
|
|
1235
|
+
&& [ "$(fm_pf_registry_get "$STATE" "$new_id" work_id)" = "$work_id" ] \
|
|
1236
|
+
&& [ "$(fm_pf_registry_get "$STATE" "$new_id" generation)" = 1 ] \
|
|
1237
|
+
|| die "registration '$new_id' already names different work; choose another id" 1
|
|
1238
|
+
else
|
|
1239
|
+
cmd_register "$new_id" --relation rel-1 --work-home "$work_home" \
|
|
1240
|
+
--work-id "$work_id" --generation 1 >/dev/null \
|
|
1241
|
+
|| die "could not register '$new_id'; retry this same rechain command" 1
|
|
1242
|
+
fi
|
|
1243
|
+
|
|
1244
|
+
cmd_retire "$from" --reason "handed on to $new_id" \
|
|
1245
|
+
|| die "registered '$new_id' but could not retire '$from'; both loops are open until '$from' is retired" 1
|
|
1246
|
+
|
|
1247
|
+
cmd_brief "$new_id"
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
# --- subcommand: retire -----------------------------------------------------
|
|
1251
|
+
|
|
1252
|
+
cmd_retire() {
|
|
1253
|
+
local id=${1:-} force=0 reason='' payload delivery task_state registry_file retired_dir retired_at
|
|
1254
|
+
local retirement_rc=0
|
|
1255
|
+
[ -n "$id" ] || { usage; exit 2; }
|
|
1256
|
+
shift
|
|
1257
|
+
while [ "$#" -gt 0 ]; do
|
|
1258
|
+
case "$1" in
|
|
1259
|
+
--force) force=1 ;;
|
|
1260
|
+
--reason) shift; reason=${1:-} ;;
|
|
1261
|
+
*) die "unknown argument '$1'" ;;
|
|
1262
|
+
esac
|
|
1263
|
+
shift || true
|
|
1264
|
+
done
|
|
1265
|
+
fm_pf_slug_valid "$id" || die "unsafe obligation id: $id"
|
|
1266
|
+
fm_pf_relay_active "$FM_HOME" || exit 0
|
|
1267
|
+
[ -n "$reason" ] || die "retire requires --reason \"<why the public loop is done>\"" 2
|
|
1268
|
+
reason=$(printf '%s' "$reason" | fm_pf_clean_outcome_text)
|
|
1269
|
+
[ -n "$reason" ] || die "retire requires --reason \"<why the public loop is done>\"" 2
|
|
1270
|
+
require_tools
|
|
1271
|
+
pf_registry_lock_acquire "$id" \
|
|
1272
|
+
|| die "could not lock registration '$id' for retirement" 1
|
|
1273
|
+
|
|
1274
|
+
payload=$(obligation_json "$id") || die "could not read the backlog through tasks-axi" 1
|
|
1275
|
+
if [ -n "$payload" ]; then
|
|
1276
|
+
delivery=$(pf_field "$payload" '.public_followup.delivery.state')
|
|
1277
|
+
task_state=$(pf_field "$payload" '.state')
|
|
1278
|
+
case "$task_state:$delivery" in
|
|
1279
|
+
done:*|*:posted|*:waived) ;;
|
|
1280
|
+
*)
|
|
1281
|
+
[ "$force" -eq 1 ] \
|
|
1282
|
+
|| die "obligation '$id' is still ${delivery:-unresolved}; retiring its registration now would hide an open public promise. Deliver it, waive it, or pass --force." 1
|
|
1283
|
+
;;
|
|
1284
|
+
esac
|
|
1285
|
+
fi
|
|
1286
|
+
if ! clear_public_followup_link "$id"; then
|
|
1287
|
+
die "could not clear the legacy X link for '$id'; its registration was retained for reconciliation" 1
|
|
1288
|
+
fi
|
|
1289
|
+
retired_dir=$(fm_pf_retired_dir "$STATE")
|
|
1290
|
+
retired_at=$(now_rfc3339)
|
|
1291
|
+
registry_file="$(fm_pf_registry_dir "$STATE")/$id"
|
|
1292
|
+
printf 'reason=%s\nretired_at=%s\n' "$reason" "$retired_at" \
|
|
1293
|
+
| fmx_private_artifact_publish_stdin "$retired_dir" "$id" 600 \
|
|
1294
|
+
|| retirement_rc=1
|
|
1295
|
+
if [ "$retirement_rc" -eq 0 ]; then
|
|
1296
|
+
if ! rm -f -- "$registry_file" 2>/dev/null \
|
|
1297
|
+
|| [ -e "$registry_file" ] || [ -L "$registry_file" ]; then
|
|
1298
|
+
retirement_rc=2
|
|
1299
|
+
fi
|
|
1300
|
+
fi
|
|
1301
|
+
pf_registry_lock_release "$id"
|
|
1302
|
+
case "$retirement_rc" in
|
|
1303
|
+
1) die "could not record the retirement reason for '$id'; the public loop remains open" 1 ;;
|
|
1304
|
+
2) die "could not remove registration for '$id'; the public loop remains open" 1 ;;
|
|
1305
|
+
esac
|
|
1306
|
+
printf 'retired %s reason=%s\n' "$id" "$reason"
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
# --- dispatch ---------------------------------------------------------------
|
|
1310
|
+
|
|
1311
|
+
CMD=${1:-}
|
|
1312
|
+
case "$CMD" in
|
|
1313
|
+
--help|-h|help) help; exit 0 ;;
|
|
1314
|
+
'') usage; exit 2 ;;
|
|
1315
|
+
esac
|
|
1316
|
+
shift
|
|
1317
|
+
|
|
1318
|
+
case "$CMD" in
|
|
1319
|
+
active) cmd_active "$@" ;;
|
|
1320
|
+
register) cmd_register "$@" ;;
|
|
1321
|
+
brief) cmd_brief "$@" ;;
|
|
1322
|
+
consume) cmd_consume "$@" ;;
|
|
1323
|
+
pending) cmd_pending "$@" ;;
|
|
1324
|
+
deliver) cmd_deliver "$@" ;;
|
|
1325
|
+
record-posted) cmd_record_posted "$@" ;;
|
|
1326
|
+
guard-work) cmd_guard_work "$@" ;;
|
|
1327
|
+
rechain) cmd_rechain "$@" ;;
|
|
1328
|
+
retire) cmd_retire "$@" ;;
|
|
1329
|
+
*) usage; exit 2 ;;
|
|
1330
|
+
esac
|