@zerwiz/ymir 0.1.16 → 0.1.18

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.
Files changed (171) hide show
  1. package/.agents/config/app-repos.yaml +38 -0
  2. package/.claude/settings.json +31 -0
  3. package/.codex/hooks.json +43 -0
  4. package/.cursor/hooks.json +33 -0
  5. package/.pi/extensions/README.md +39 -0
  6. package/.pi/extensions/gna-pi-watch.ts +15 -0
  7. package/.pi/extensions/lib/ro-assistant-layout.ts +96 -0
  8. package/.pi/extensions/lib/ro-operational-user-layout.ts +145 -0
  9. package/.pi/extensions/lib/ro-visibility.ts +99 -0
  10. package/.pi/extensions/lib/ro-working-ship.ts +243 -0
  11. package/.pi/extensions/lib/rodd-operational-input.ts +62 -0
  12. package/.pi/extensions/lib/skuld-branch-dispatch.ts +252 -0
  13. package/.pi/extensions/lib/skuld-branch-model-picker.ts +77 -0
  14. package/.pi/extensions/lib/vordr-sessionstart-supervisor.mjs +48 -0
  15. package/.pi/extensions/ro.ts +11 -0
  16. package/.pi/extensions/skuld-branch-supervision.ts +11 -0
  17. package/.pi/extensions/syn-turnend-guard.ts +11 -0
  18. package/.pi/mcp.json +16 -0
  19. package/.pi/mcp.json.example +11 -0
  20. package/.pi/npm/.gitignore +2 -0
  21. package/.pi/settings.json +8 -0
  22. package/.pi/shared/extensions/gna-pi-watch.ts +750 -0
  23. package/.pi/shared/extensions/herdr-agent-state.ts +257 -0
  24. package/.pi/shared/extensions/open-editor.ts +509 -0
  25. package/.pi/shared/extensions/ro.ts +504 -0
  26. package/.pi/shared/extensions/skuld-branch-supervision.ts +1535 -0
  27. package/.pi/shared/extensions/syn-turnend-guard.ts +683 -0
  28. package/.pi/shared/extensions/todo.ts +133 -0
  29. package/.pi/shared/extensions/ymir-subagents.ts +287 -0
  30. package/.pi/shared/extensions/ymir-well.ts +130 -0
  31. package/CHANGELOG.md +23 -0
  32. package/assets/Ymir.md +4654 -0
  33. package/assets/healer.html +717 -0
  34. package/assets/icon-family/family-sheet.png +0 -0
  35. package/assets/icon-family/hlidskjalf.svg +1 -0
  36. package/assets/icon-family/sessrumnir.svg +1 -0
  37. package/assets/icon-family/smidja.svg +1 -0
  38. package/assets/icon-family/ymir.svg +1 -0
  39. package/assets/mimirswell.html +880 -0
  40. package/assets/mock/README.md +16 -0
  41. package/assets/mock/feeds.ts +175 -0
  42. package/assets/mock/mock.ts +408 -0
  43. package/assets/reference/docs/agent-control.md +122 -0
  44. package/assets/reference/docs/apodex/Appdex_as_Orchestrator_Planner_Reviewer_Assessment.md +176 -0
  45. package/assets/reference/docs/apodex/Final Report(1).md +198 -0
  46. package/assets/reference/docs/apodex/Final Report(2).md +54 -0
  47. package/assets/reference/docs/apodex/Final Report(3).md +40 -0
  48. package/assets/reference/docs/apodex/Final Report(4).md +234 -0
  49. package/assets/reference/docs/apodex/Final Report(5).md +13 -0
  50. package/assets/reference/docs/apodex/Final Report.md +82 -0
  51. package/assets/reference/docs/architecture.md +392 -0
  52. package/assets/reference/docs/arm-pretool-check.md +247 -0
  53. package/assets/reference/docs/calm-mode-feasibility.md +541 -0
  54. package/assets/reference/docs/calm.md +59 -0
  55. package/assets/reference/docs/captain-hold-lifecycle.md +96 -0
  56. package/assets/reference/docs/cd-guard.md +165 -0
  57. package/assets/reference/docs/cmux-backend.md +133 -0
  58. package/assets/reference/docs/codex-app-backend.md +57 -0
  59. package/assets/reference/docs/configuration.md +924 -0
  60. package/assets/reference/docs/documentation-audiences.json +468 -0
  61. package/assets/reference/docs/documentation-audiences.md +28 -0
  62. package/assets/reference/docs/examples/crew-dispatch.json +26 -0
  63. package/assets/reference/docs/examples/process-event-extension/file-signal.mjs +96 -0
  64. package/assets/reference/docs/examples/process-event-extension/firstmate-extension.json +15 -0
  65. package/assets/reference/docs/examples/watched-tools.json +24 -0
  66. package/assets/reference/docs/examples/wedge-alarm +22 -0
  67. package/assets/reference/docs/extension-bindings.md +237 -0
  68. package/assets/reference/docs/fm-test-isolation-proof.json +37 -0
  69. package/assets/reference/docs/fm-test-isolation-proof.md +149 -0
  70. package/assets/reference/docs/fm-test-portable-shards.md +118 -0
  71. package/assets/reference/docs/gitlab-merge-watch.md +275 -0
  72. package/assets/reference/docs/herdr-backend.md +346 -0
  73. package/assets/reference/docs/orca-backend.md +84 -0
  74. package/assets/reference/docs/pi-supervision-branch-poster.svg +125 -0
  75. package/assets/reference/docs/pi-supervision-branch.md +96 -0
  76. package/assets/reference/docs/plans/agentic-engineering-workflow.md +395 -0
  77. package/assets/reference/docs/plans/agentic-factory-implementation-plan.md +615 -0
  78. package/assets/reference/docs/plans/command-factory-enhancement-plan.md +420 -0
  79. package/assets/reference/docs/plans/phase-1-layer3-program-design.md +134 -0
  80. package/assets/reference/docs/plans/phase-2-vertical-slices.md +100 -0
  81. package/assets/reference/docs/plans/phase-3-measurable-goals.md +106 -0
  82. package/assets/reference/docs/plans/phase-4-context-budget.md +106 -0
  83. package/assets/reference/docs/plans/phase-5-pre-mortem-confidence.md +100 -0
  84. package/assets/reference/docs/plans/phase-6-incident-to-agent.md +109 -0
  85. package/assets/reference/docs/remote-secondmates.md +268 -0
  86. package/assets/reference/docs/scripts.md +146 -0
  87. package/assets/reference/docs/sessionstart-nudge.md +115 -0
  88. package/assets/reference/docs/subagent-guard.md +383 -0
  89. package/assets/reference/docs/supervision-protocols/claude.md +27 -0
  90. package/assets/reference/docs/supervision-protocols/codex.md +16 -0
  91. package/assets/reference/docs/supervision-protocols/cursor.md +31 -0
  92. package/assets/reference/docs/supervision-protocols/grok.md +39 -0
  93. package/assets/reference/docs/supervision-protocols/opencode.md +17 -0
  94. package/assets/reference/docs/supervision-protocols/pi.md +33 -0
  95. package/assets/reference/docs/supervision-protocols/unknown.md +13 -0
  96. package/assets/reference/docs/tmux-backend.md +115 -0
  97. package/assets/reference/docs/trace-context.md +117 -0
  98. package/assets/reference/docs/turnend-guard.md +170 -0
  99. package/assets/reference/docs/verification/dispatch-auth.md +204 -0
  100. package/assets/reference/docs/verification/muse.md +221 -0
  101. package/assets/reference/docs/verification/process-event-sources.md +208 -0
  102. package/assets/reference/docs/verification/public-followup.md +119 -0
  103. package/assets/reference/docs/verification/runtime-backends.md +1022 -0
  104. package/assets/reference/docs/verification/stow-memory.md +53 -0
  105. package/assets/reference/docs/verification/supervision.md +534 -0
  106. package/assets/reference/docs/verification/trace-context.md +31 -0
  107. package/assets/reference/docs/voice-relay.md +295 -0
  108. package/assets/reference/docs/watcher-continuity.md +119 -0
  109. package/assets/reference/docs/wedge-alarm.md +39 -0
  110. package/assets/reference/docs/zellij-backend.md +114 -0
  111. package/assets/reference/index.html +1172 -0
  112. package/assets/reference/skills/afk/SKILL.md +241 -0
  113. package/assets/reference/skills/ahoy/SKILL.md +56 -0
  114. package/assets/reference/skills/ask-user-authority/SKILL.md +56 -0
  115. package/assets/reference/skills/bearings/SKILL.md +161 -0
  116. package/assets/reference/skills/bearings/assets/board-template.html +731 -0
  117. package/assets/reference/skills/bootstrap-diagnostics/SKILL.md +68 -0
  118. package/assets/reference/skills/captain-hold-lifecycle/SKILL.md +54 -0
  119. package/assets/reference/skills/decision-hold-lifecycle/SKILL.md +15 -0
  120. package/assets/reference/skills/diagnostic-reasoning/SKILL.md +53 -0
  121. package/assets/reference/skills/firstmate-codexapp/SKILL.md +110 -0
  122. package/assets/reference/skills/firstmate-codexapp/agents/openai.yaml +4 -0
  123. package/assets/reference/skills/firstmate-coding-guidelines/SKILL.md +127 -0
  124. package/assets/reference/skills/firstmate-orca/SKILL.md +92 -0
  125. package/assets/reference/skills/fmx-respond/SKILL.md +283 -0
  126. package/assets/reference/skills/harness-adapters/SKILL.md +95 -0
  127. package/assets/reference/skills/harness-adapters/references/common/control-and-recovery.md +37 -0
  128. package/assets/reference/skills/harness-adapters/references/common/dispatch.md +32 -0
  129. package/assets/reference/skills/harness-adapters/references/common/model-and-effort.md +42 -0
  130. package/assets/reference/skills/harness-adapters/references/common/primary-hooks.md +40 -0
  131. package/assets/reference/skills/harness-adapters/references/harness/claude.md +55 -0
  132. package/assets/reference/skills/harness-adapters/references/harness/codex.md +43 -0
  133. package/assets/reference/skills/harness-adapters/references/harness/cursor.md +75 -0
  134. package/assets/reference/skills/harness-adapters/references/harness/grok.md +69 -0
  135. package/assets/reference/skills/harness-adapters/references/harness/kimi.md +51 -0
  136. package/assets/reference/skills/harness-adapters/references/harness/muse.md +70 -0
  137. package/assets/reference/skills/harness-adapters/references/harness/opencode.md +42 -0
  138. package/assets/reference/skills/harness-adapters/references/harness/pi.md +56 -0
  139. package/assets/reference/skills/process-event-sources/SKILL.md +137 -0
  140. package/assets/reference/skills/project-management/SKILL.md +92 -0
  141. package/assets/reference/skills/quota-array-dispatch/SKILL.md +131 -0
  142. package/assets/reference/skills/secondmate-provisioning/SKILL.md +254 -0
  143. package/assets/reference/skills/stow/SKILL.md +309 -0
  144. package/assets/reference/skills/stuck-crewmate-recovery/SKILL.md +54 -0
  145. package/assets/reference/skills/updatefirstmate/SKILL.md +64 -0
  146. package/assets/skills/README.md +5 -0
  147. package/assets/yggdrasil.html +649 -0
  148. package/assets/ymir-banner-01.png +0 -0
  149. package/assets/ymir-banner-02.png +0 -0
  150. package/assets/ymir-banner-03.png +0 -0
  151. package/assets/ymir-banner-04.png +0 -0
  152. package/assets/ymir-banner-05.png +0 -0
  153. package/assets/ymir-banner-06.png +0 -0
  154. package/assets/ymir-emblem-darksteel.svg +63 -0
  155. package/assets/ymir-emblem-forged.svg +55 -0
  156. package/assets/ymir-emblem-slate.svg +55 -0
  157. package/assets/ymir-mark-algiz-anvil.svg +9 -0
  158. package/assets/ymir-stave.svg +53 -0
  159. package/assets/ymir.html +563 -0
  160. package/bin/ymir-install.sh +6 -1
  161. package/data/learnings.md +33 -0
  162. package/data/local-models.md +34 -0
  163. package/data/operator.md +15 -0
  164. package/data/projects.md +28 -0
  165. package/data/realm.md +1 -0
  166. package/deploy/Containerfile +27 -0
  167. package/deploy/README.md +104 -0
  168. package/deploy/compose/compose.yaml +45 -0
  169. package/deploy/env.example +30 -0
  170. package/deploy/quadlet/ymir.container +57 -0
  171. package/package.json +10 -8
@@ -0,0 +1,208 @@
1
+ # Process-to-event runner verification
2
+
3
+ Audience: maintainer verification.
4
+
5
+ This record holds reusable version-scoped evidence for the runner's active guarantees.
6
+ `docs/configuration.md` owns the operating contract, each script's header and `--help` own its mechanics, and `.agents/skills/process-event-sources/SKILL.md` owns the handling procedure.
7
+
8
+ Verified on 2026-07-31 on macOS (Darwin 25.5.0) with `lavish-axi` 0.1.45 installed.
9
+ Generic keyed-answer feed verified on 2026-08-16 on the same platform, against the same published poll response shape.
10
+ Cross-origin keyed-answer feed verified on 2026-08-19 through the real runner and Lavish adapter interface.
11
+ Trusted external `process-event-adapter/1` binding conformance and the runnable `file-signal` example were verified on 2026-08-27 on macOS (Darwin 25.5.0) with Node v25.9.0.
12
+
13
+ ## The published Lavish poll interface the adapter wraps
14
+
15
+ Verified at implementation time without upgrading the installed build:
16
+
17
+ ```sh
18
+ $ lavish-axi --version
19
+ 0.1.45
20
+ $ lavish-axi poll --help | head -1
21
+ Usage: lavish-axi poll <html-file> [--agent-reply "..."]
22
+ ```
23
+
24
+ The same help states that the command "long-polls indefinitely".
25
+ The adapter therefore registers the plain blocking form with no timeout flag, so a completion is a real server-side event rather than a timer expiry.
26
+
27
+ This build exposes no capabilities command and no multiplexed or subscription endpoint:
28
+
29
+ ```sh
30
+ $ lavish-axi capabilities --json
31
+ error: Lavish Editor expects an HTML file
32
+ code: VALIDATION_ERROR # exit 2
33
+ ```
34
+
35
+ Exit 2 with `VALIDATION_ERROR` is positive proof the subcommand does not exist, because the word is parsed as a filename.
36
+ Note that `lavish-axi <anything> --help` exits 0 for any argument, including a nonsense subcommand, so a `--help` exit code can never be used as a capability probe.
37
+
38
+ The adapter depends on none of this: it uses only the published poll shape above.
39
+
40
+ ## Why an ended Lavish review is terminal
41
+
42
+ Re-verified on 2026-08-01 against the same installed build.
43
+ The published poll help states the lifecycle directly:
44
+
45
+ ```text
46
+ $ lavish-axi poll --help | tr '.' '\n' | grep -F 'Send & End'
47
+ `Send & End` ends the session
48
+ $ lavish-axi poll --help | tr '.' '\n' | grep -F 'polling stops'
49
+ After that response, polling stops, and the agent must not reopen the session uninvited
50
+ ```
51
+
52
+ The sentence between those two, in the same help text, is "Its final feedback is still delivered once."
53
+
54
+ So the last useful response of an ended review is a `feedback` response, and every poll after it returns an empty ended session immediately.
55
+ That is why the adapter's terminal verdict covers a `feedback` response carrying `session_ended`, not only `status: ended` and a missing session: without it, one human `Send & End` leaves the source armed and each later cycle captures another empty ended result.
56
+ `session_ended` is a session-level field emitted beside `status` in the response's leading `session:` block, which is why the adapter reads it there and ignores identical text appearing in prompt payloads.
57
+
58
+ ## Why an empty board close is silent
59
+
60
+ The same published lifecycle above is the whole basis for the `silent` verdict, so no new source knowledge was needed.
61
+ `Send & End` delivers the captain's final feedback once as a `feedback` response carrying `session_ended`, and every poll after it returns an empty ended session.
62
+ A board the captain closes without saying anything therefore produces exactly one `ended` response carrying no queued content block, and announcing it put a wake in front of the handler whose entire content was that nothing happened.
63
+
64
+ The verdict is confined to that one shape and fails closed everywhere else.
65
+ A `Send & End` close carrying the captain's own answer classifies `feedback`, never `ended`, so it is announced unchanged; so is any `ended` result that still carries a `prompts` or `feedback` block, which this lifecycle is not expected to produce but which must never be dropped on that expectation.
66
+ A `waiting` session, a `missing` one, an `unknown` or unreadable result, and every error stay announced, because none of them positively proves nothing was said.
67
+ The content check anchors on column zero for the same reason the terminal check reads the leading `session:` block: content headers are top-level and their rows are indented, so captain-supplied payload text can neither forge a content block nor hide behind a fake empty one.
68
+ Any recognized block counts as present even when its declared count is zero, and a malformed top-level `prompts` or `feedback` header is indeterminate and therefore announced.
69
+
70
+ ## The loss limitation this runner cannot close
71
+
72
+ The published poll clears feedback destructively before returning it.
73
+ Measured at the protocol layer by consuming and discarding the response:
74
+
75
+ ```text
76
+ consuming read http=200
77
+ listing after: ...,open,"...",0
78
+ state.json: status= open pending= 0 prompts= [] chat entries= []
79
+ ```
80
+
81
+ Nothing remains on the source side to re-read, and there is no acknowledgement, cursor, or replay surface to reserve against.
82
+ A result lost after that clearing and before the runner reads the child's output is therefore unrecoverable.
83
+
84
+ **Consequence for wording:** the runner may describe only its own durability boundary.
85
+ Never at-least-once, no-loss, or lossless.
86
+
87
+ ## What the runner does prove
88
+
89
+ Exercised by `tests/fm-procevent.test.sh` against a fake blocking source whose completion is a process event, not a timer; for the two supervision-delivery rows below, by `tests/fm-watch-triage.test.sh` driving a real `bin/fm-watch.sh` over a real capture; and for adapter-owned application, by `tests/fm-remote-reply.test.sh` driving the real remote-reply relay end to end in an isolated home:
90
+
91
+ | Guarantee | How it is proven |
92
+ | --- | --- |
93
+ | capture before publication | the captured result exists at `0600` and its event names its committed sequence only afterward |
94
+ | proactive delivery of a captured result | a real capture into an isolated home queues its `check` record, and a healthy watcher with a fresh beacon then exits reporting that queued result as an actionable check, before any manual drain |
95
+ | single delivery per source and sequence | after that first proactive wake, a still-unhandled result keeps being re-announced onto the durable queue but never wakes the watcher again; once existing records receive the drain's post-handling acknowledgement and the source result is acknowledged, it is neither re-announced nor reported |
96
+ | proactive-delivery crash and drain boundaries | dotted and underscored source ids at the same sequence receive distinct markers; a concurrent drain cannot consume between queue revalidation and marker commit; failed output, failed marker commit, and a crash before marker commit leave replay available, while successful output still ends the actionable cycle and a crash after marker commit suppresses a duplicate |
97
+ | adapter-owned terminal verdict | two fixture adapters - one that ends on any result, one with no terminal knowledge - decide the outcome alone: the first has its registration and claim retired automatically after one capture and is never restarted, the second stays armed |
98
+ | adapter-owned application of a captured result | a remote-secondmate reply captured through the real relay in an isolated home reaches that secondmate's local status mirror, settles its correlated pending-reply expectation, re-arms the next cursor-anchored source, and is acknowledged, with no handler step or duplicate `check` wake; its new mirrored bytes remain visible to the watcher's signal gate, while a cursor-loss whole-log recapture that adds no bytes is acknowledged quietly; for an already-escalated request, the same path closes the exact decision so the open-decision fold clears and remains clear; a capture whose adapter application fails because local storage for a referenced remote document is obstructed is left unacknowledged and receives the fallback `check` wake, and the handler's own `handle` still applies it in full after storage recovers |
99
+ | generic built-in keyed-answer feed | `tests/fm-captain-hold-lifecycle.test.sh` drives a bound built-in source through the real runner with a fixture adapter that only prints keyed lines, proving any bound built-in channel reaches the one keyed-answer intake: named captain-held tasks close at capture time, a card-declared release mode frees held work, keys naming no captain-held task skip, freeform prose forges nothing, matching answer-and-mode replays are idempotent while mode mismatches refuse, an unbound source closes nothing, and capture remains independent of the handler wake. |
100
+ | adapter-owned silence verdict | an armed Lavish source driven against a stand-in poll that returns an empty ended session captures its result, records it durably handled, appends no wake, and stays silent through a later `reconcile` that would otherwise republish it, while still retiring its ended source; the same real path with a `Send & End` response carrying the captain's choice still publishes its `check` wake and is left unacknowledged for the handler |
101
+ | silence fails closed | the adapter's published `silent` command suppresses only an `ended` session with no queued content block, and announces a real answer, freeform prose, any recognized content block regardless of its declared count, a malformed top-level content header, a `waiting` or `missing` session, a server error, an unreadable result, and indented payload text imitating an empty content block; the `remote-reply` and `when` adapters, which implement no `silent` command, announce every result |
102
+ | terminal retirement preserves the result | the retired source's captured output, its announced event, its handled acknowledgement, and later explicit `retire` all still behave normally |
103
+ | registration-generation retirement | an old terminal runner preserves a concurrently replaced registration and releases ownership so the replacement runs independently; injected registration-removal failure retains a terminal claim, performs no second poll, and completes idempotently once removal recovers |
104
+ | one `Send & End`, one result | an armed Lavish source driven against a stand-in for the published poll, which delivers the final `session_ended` feedback once and empty ended sessions afterward, polls exactly once, captures exactly one result, publishes one distinct event, and retires itself |
105
+ | bounded re-announcement until handled | a durably captured result with no handled acknowledgement is re-announced by `reconcile` with the same source and sequence on every call - not only the first restart after a crash - and a presented-but-unacknowledged wake resurfaces identically after a simulated replacement session |
106
+ | handled acknowledgement | `fm-procevent.sh handled <source-id> <sequence>` atomically and idempotently records handling at mode `0600`, fails without leaving a marker when private-mode enforcement fails, reports the first call distinctly from every repeat, stops further re-announcement once recorded, and never authorizes a paired effect twice across repeat calls |
107
+ | publication-and-acknowledgement serialization | a concurrent `reconcile` cannot append a wake after `handled` wins the shared per-source boundary, so an acknowledged result is not re-announced by a publication race |
108
+ | acknowledgement precondition | `handled` is refused, with no marker created, unless matching captured result and adapter records already exist, so a premature or mistyped acknowledgement cannot suppress a future result |
109
+ | immutable adapter identity | a captured result retains its adapter after its mutable registration is removed |
110
+ | trusted classification boundary | Lavish lifecycle classification reads the leading response envelope, so prompt payload text that resembles a missing-session error cannot override a valid session status |
111
+ | result identity and ordering | each wake names the committed sequence to read, and pending sequences 1, 2, and 10 publish in numeric order |
112
+ | one owner per canonical source | a second home's `start` for the same source id reports `already owned` and publishes nothing |
113
+ | canonical physical identity | a final-component symlink and its target produce the same Lavish source id |
114
+ | isolated public start boundary | direct `start` establishes a new runner-led process group before claiming the source, so retirement cannot signal an unrelated process inherited from the caller's group |
115
+ | stale reclaim without displacement | concurrent contenders replacing one stale claim start exactly one runner, and cross-home replacement removes the old generation's staging file from its recorded state directory |
116
+ | crashed leader with a live owned group | `SIGKILL` on only the runner leader leaves its blocking child group alive; reconcile then stops that surviving group before any replacement starts, never leaves two source processes running for one canonical source, and a generation with no leader and no surviving group is still reclaimed |
117
+ | PID-reuse safety | retirement refuses to signal a live PID whose identity differs from the claim, and a reused PID never reaches the group-stop path because its leader is alive |
118
+ | coherent ownership reads | a claim replacement held inside the source boundary blocks `list` until one complete generation is visible |
119
+ | retire-start exclusion | a queued start revalidates registration after the serialized retirement boundary and executes no child |
120
+ | uncertain identity | a live owner whose identity probe transiently fails is not signaled or released, and its registration remains for retry |
121
+ | bounded home sweep | a non-mutating full-tree preflight precedes teardown, then registrations and claim-only owned sources retire through the ordinary safe path at each home-removal boundary |
122
+ | sweep refusal | uncertain identity preserves the runner, claim, registration, home, lease, and parent retirement evidence for retry |
123
+ | foreign ownership | sweeping one home removes its registration without signaling or releasing another home's live claim |
124
+ | nested and force cleanup | normal, force, and nested secondmate removal invoke each target home's sweep at its final removal boundary, a failed removal restores and rearms registrations, and failed rearming at any nested level retains and reports its recovery backup with a distinct status |
125
+ | teardown refusal ordering | a later public-followup refusal retains the home and its active process-event registration without invoking its sweep |
126
+ | healthy-home invariance | homes with no registration or owned runner claim retain ordinary registration-only supervision and teardown behavior |
127
+ | source-only supervision | a registered source with no task metadata trips the shared predicate and general guard |
128
+ | argv integrity | an argument containing spaces survives as one argument, a shell-looking argument is passed literally with no interpretation, and an unrepresentable newline is rejected at registration |
129
+ | bounded output | output beyond `FM_PROCEVENT_MAX_OUTPUT_BYTES` is drained while only the bound is staged, then truncated and captured |
130
+ | condition->action single-fire and trust | `tests/fm-procevent-when.test.sh` drives the public `when` adapter and generic runner with real commands, proving stable true fires once, a claimed fire restarts as ambiguous without a second action, concurrent arms publish one complete watch, and mutated specs or action executables are refused before execution |
131
+ | condition->action terminal outcomes | the same suite proves flapping true polls do not fire, action failure, condition error budget, deadline expiry, and a true poll completing after its deadline each produce the expected terminal captured result without an unsafe action |
132
+ | condition->action process bounds | the same suite proves action timeout terminates descendants and command-output staging remains within `FM_WHEN_OUTPUT_TAIL_BYTES` while the command runs |
133
+ | silent failure handling | a nonzero exit with no output publishes nothing and leaves the source registered for retry |
134
+ | inertness | a home with no registered source generates no state, starts no process, and does not need supervision |
135
+ | absent extension registry parity | `tests/fm-extension-binding.test.sh` drives `list` and `verify` in a fresh home while the current directory contains project files and Pi packages and an environment variable names fake package data; both commands report no bindings, create no home path, and discover nothing outside `config/extensions.d` |
136
+ | complete package and binding identity | the same suite drives the public bind and verify commands through manifest duplicate/unknown/version failures, project and task-copy confinement, canonical path and symlink rejection, hard-link rejection, owner/mode checks, a non-executable entrypoint, binding mode drift, complete-tree mutation, exact executable mutation, and a missing executable; the foreign-owner fixture executes when the platform permits constructing another uid and otherwise reports that privilege limitation, while ordinary non-privileged CI does not exercise it or claim it ran |
137
+ | external evidence write confinement | the same suite substitutes `state/procevent/` and `state/procevent-inbox/` with post-registration symlinks and proves an external start fails before bytes reach either outside target; it proves public lifecycle entry, environment, paths, and descriptors cannot forge capture authority; it proves claim release and dead-owner reconciliation remove pending or consumed capture reservations only from the recorded revalidated state root; and it proves the absent-registry built-in capture path retains its legacy state-path behavior |
138
+ | strict handshake and negotiation | manifests offering versions 2 and 1 select host protocol 1 and `process-event-adapter/1`, unknown-only versions refuse, and wrong request ids, unknown or duplicate fields, malformed JSON, and nonzero handshake exits publish no binding |
139
+ | strict invocation envelope | malformed UTF-8, a byte-order mark, unescaped controls, malformed or multiple JSON documents, duplicate or unknown fields, oversized stdout, oversized stderr, wrong request ids, crashes, nonzero exits, a successful parent that leaves a foreground descendant in its host-created invocation group, and authority-shaped result fields are rejected; leaked group members are reaped and package diagnostic text is not copied into the bounded host-produced error evidence |
140
+ | extension timeout and process-group cleanup | a bound adapter that ignores `TERM`, spawns a foreground descendant that ignores `TERM`, and exceeds its invocation timeout returns deterministic timeout evidence only after its exact invocation group is gone; deliberate process-group escape is outside this trusted-same-user protocol guarantee |
141
+ | static launch and interruption recovery | the focused extension suite runs the public host under Node's no-dynamic-code guard, interrupts a host with an active TERM-resistant package group and observes host exit only after exact-group extinction, then kills a host at the post-release crash cut and proves identity-safe binding retirement reaps that recorded group before ownership is removed |
142
+ | exact replay identity | two public host invocations carrying the same request id return the same result and advance the fixture package's request-id-keyed effect ledger once; two generic-runner starts that produce no capturable result also reuse one registration-and-next-sequence-derived request id and apply that fixture effect once |
143
+ | complete external adapter path | the shipped external `file-signal` package is copied outside the Git project, explicitly bound with its required artifact-reference consent, discovered, verified, registered with one file reference, started through the generic runner, completed by a real file appearance, durably captured, published through the existing bounded event, classified through its immutable package identity, left unhandled, and terminally retired |
144
+ | owner-matched replacement safety | two registrations for the same external source receive distinct owner tokens; unconditional external retirement and the first token cannot retire the replacement, the replacement token can, bounded home sweep derives and uses that exact token, and legacy built-in registrations retain unconditional behavior plus exact `--if-matches` retirement |
145
+ | independent homes | two homes bind the same package id/version to different content-addressed absolute paths and independently capture results and extension state, with no cross-home fallback or result path |
146
+
147
+ Run the focused external-binding evidence with:
148
+
149
+ ```sh
150
+ node --version
151
+ bin/fm-test-run.sh tests/fm-extension-binding.test.sh
152
+ FM_EXTENSION_BINDING_SEGMENT=lifecycle-invocation-cleanup bin/fm-test-run.sh tests/fm-extension-binding.test.sh
153
+ bin/fm-test-run.sh tests/fm-procevent.test.sh
154
+ bin/fm-doc-audience-check.sh
155
+ ```
156
+
157
+ ## Harness and session-provider review
158
+
159
+ The external host runs in the home that owns the process-event source and publishes the same bounded `check` record as every built-in adapter.
160
+ The 2026-08-27 review inspected `bin/fm-harness.sh`, `bin/fm-supervision-instructions.sh`, `bin/fm-supervision-lib.sh`, the process-event delivery and reconcile boundaries in `bin/fm-watch.sh`, `bin/fm-backend.sh`, and `bin/fm-config-inherit-lib.sh` before marking integration axes not applicable.
161
+
162
+ | Axis | Reviewed boundary and result |
163
+ | --- | --- |
164
+ | Claude, Codex, OpenCode, Pi, pi-signed, Grok, and Cursor primaries | Applicable only at the existing watcher continuation after one shared `check` wake; no package byte, command, state path, or verdict enters a harness-specific integration. |
165
+ | Kimi | The process-event path never enters the worker runtime, and a Kimi primary retains the existing unknown-protocol supervision fallback rather than gaining extension-specific behavior. |
166
+ | Muse | Muse remains a crewmate/scout-only runtime, so no primary process-event integration exists; external adapters still run in the owning home, not in Muse. |
167
+ | Claude, Codex, OpenCode, Pi, pi-signed, Grok, Kimi, Cursor, and Muse task workers | Not applicable after inspecting harness detection and launch ownership, because source registration has no task metadata or worker endpoint and the package is never launched through `fm-spawn`. |
168
+ | tmux, Herdr, Zellij, Orca, and cmux session providers | Not applicable after inspecting the known and spawn-capable backend dispatch sets, because process-event execution calls no backend selector, capture, send, liveness, or cleanup primitive. |
169
+ | Local and remote secondmate homes | Applicable at the home boundary only; each home owns its own binding, content-addressed package, extension state, registration, result, and watcher, and `config/extensions.d` remains outside the inherited-material allowlist. |
170
+
171
+ ## Runner lifetime and cleanup
172
+
173
+ A runner started by `reconcile` is its own process group leader and is reparented to init, so it outlives the shell that started it by design.
174
+ That means nothing about the starting context can reap it: removing a home's state directory does not stop an already-running child, and signalling only the runner leaves the blocking child alive.
175
+
176
+ Two paths therefore stop a runner, and both verify the runner-owned process group, escalate to `KILL` while that group still exists, and refuse to release ownership until the whole group is gone:
177
+
178
+ - `retire` resolves the runner PID and identity from this home's machine-wide claim, so retirement still works when the home's state is already gone.
179
+ - `reconcile` stops a runner this home owns whose source registration has been removed, and reports it as `stopped=N`.
180
+
181
+ The same group rule decides when a claim may be reclaimed, not only when a runner may be signalled.
182
+ A leader that died while its owned group kept running is not a stale generation, so `reconcile` stops that surviving group and releases its generation before starting any replacement, and preserves the claim for a later retry when it cannot prove the group stopped or another home owns it.
183
+ Signalling that group is safe precisely because only an absent leader reaches this state: a reused PID leaves the leader alive, which the identity comparison classifies as stale or uncertain, and no group signal follows.
184
+
185
+ This was found by four orphaned runners, elapsed 6-13 minutes, left by a suite whose fixture source never completed.
186
+ `tests/fm-procevent.test.sh` now covers both paths, and three consecutive suite runs leave zero runners, zero fixture children, and zero stray claims.
187
+
188
+ ## Portability finding
189
+
190
+ `setsid` is **not present on macOS**, so it cannot establish the runner's process group.
191
+ Both direct `start` and `reconcile` use a Perl launcher that forks the runner, calls `setpgrp(0, 0)` in that child, marks the expected group leader, and then executes the private start path.
192
+ The private path verifies that the runner PID is also its process-group id before it records a claim, so neither entry point can inherit and claim the caller's process group.
193
+ Without this launcher, reconcile would silently fail to start a runner on macOS and direct start could make retirement signal unrelated caller-group processes.
194
+
195
+ ## Scope
196
+
197
+ The runner is domain-neutral and creates no endpoint, task metadata, or backlog item, so the supported primary harnesses and runtime backends are unaffected except through the existing `check` and status-signal wake paths they already consume.
198
+ Built-in adapters extend the runner through `bin/fm-procevent-<adapter>.sh`; the `when` adapter also uses the runner library's locked registration publisher so its private trust state and source registration are serialized under one source boundary.
199
+ Explicit external adapters instead use the single-capability contract in [`docs/extension-bindings.md`](../extension-bindings.md), with no filename discovery or package-supplied argv.
200
+ An adapter's `terminal` command is optional and defaults to keeping the source armed.
201
+ Its `silent` command is optional in the same way and defaults to announcing every result, so an adapter with no notion of a routine no-op is unchanged.
202
+ Its `autohandle` command is optional in the same way and defaults to leaving the captured result unacknowledged, so it keeps being announced to a handler exactly as before.
203
+ The optional `self-announcing` declaration changes ordering only for an adapter with its own durable downstream announcement; the operating contract in `docs/configuration.md` owns that boundary.
204
+
205
+ Proactive delivery is inside that same boundary.
206
+ The watcher reports a queued process-event result through the one shared actionable-exit path (`wake` in `bin/fm-push-transition-lib.sh`) that every existing signal, stale, and check wake already uses, so it reads no pane, queries no backend, and names no harness.
207
+ Both axes are therefore unaffected by construction rather than by assumption: every supported primary harness re-arms from that same exit, and every runtime backend supplies endpoint state only to the pane paths this change does not touch.
208
+ While `state/.afk` exists the watcher stays one-shot as before, because this delivery ends the cycle exactly like the existing check path and leaves classification to the daemon.
@@ -0,0 +1,119 @@
1
+ # Promised public reply verification
2
+
3
+ Audience: maintainer verification.
4
+
5
+ This record supports three active guarantees for promised public replies made through the myfirstmate relay:
6
+
7
+ 1. A promised final reply survives compaction and restart, reconciles from disk alone, and lands in the original thread exactly once.
8
+ 2. A home that never opted into the relay pays nothing for any of it.
9
+ 3. Delivering a final does not close the public loop: the registration is retained as `state=delivered` until `retire --reason`, session start surfaces an `open-loop` line, and `rechain` can bind follow-on work to the same thread.
10
+
11
+ [`docs/configuration.md`](../configuration.md#promised-public-replies-statepublic-followup) owns the operator-facing contract, [`docs/architecture.md`](../architecture.md#optional-relay) owns the mechanism boundary, and `tasks-axi public-followup --help` owns the typed obligation schema.
12
+ Task chronology and delivery evidence stay outside this record.
13
+
14
+ ## Environment
15
+
16
+ Recorded 2026-08-21 on Darwin 25.5.0 (arm64) with GNU bash 5.3.9, tasks-axi 0.2.5, jq 1.8.1, and ShellCheck 0.11.0 (the version `bin/fm-lint.sh` pins).
17
+ The relay is a fakebin `curl` in every case, so no public post is ever made; `tasks-axi` and `jq` are the real tools, because stubbing the obligation state machine would verify nothing.
18
+
19
+ ## Restart end-to-end and regressions
20
+
21
+ ```sh
22
+ bash tests/fm-public-followup.test.sh
23
+ ```
24
+
25
+ ```
26
+ ok - outcome text is collapsed to one line, bounded by codepoint, and never corrupts characters
27
+ ok - restart end-to-end: typed result reconciles from disk and delivers one reply to the original thread
28
+ ok - duplicate terminal results, restart replay, and repeated delivery are all no-ops
29
+ ok - wrong source, wrong work id, stale generation, malformed, unsupported deliverable, and forged identity are all refused
30
+ ok - a relay transport failure is held as retryable with no false completion, and the retry posts once
31
+ ok - a dry-run records no public delivery and leaves the commitment retryable
32
+ ok - a late success receipt closes the exact attempt with no second post, and a mismatched attempt is refused
33
+ ok - typed terminal cleanup clears the legacy link without posting
34
+ ok - a delivery interrupted between post and receipt refuses to repost
35
+ ok - a child home reports typed results but can never become the outward-post owner
36
+ ok - typed delivery refuses to post when its cleanup registration is missing
37
+ ok - marked secondmate teardown resolves its parent and fails closed when unavailable
38
+ ok - local seeding publishes durable parent state before its identity marker
39
+ ok - a lost launch-time parent binding is recovered from the durable local record
40
+ ok - a durable local parent record does not bypass a genuinely missing parent-side registration
41
+ ok - unknown durable parent fields remain forward-compatible
42
+ ok - conflicting live and durable parent bindings fail closed
43
+ ok - unsafe durable parent records fail closed before cleanup
44
+ ok - a NUL-bearing durable parent record fails closed before cleanup
45
+ ok - relay-disabled unmarked teardown runs no public-followup work
46
+ ok - a marked child proceeds without tasks-axi when its parent relay is disabled
47
+ ok - secondmate parent resolution matches the durable registry id literally
48
+ ok - traversal-shaped registrations are rejected before path construction or posting
49
+ ok - pending keeps registrations when tasks-axi returns malformed JSON
50
+ ok - the retained private request context keeps the original thread deliverable after inbox cleanup
51
+ ok - cleanup refuses while a public reply is owed and proceeds once it has landed
52
+ ok - a relay-disabled home runs no tasks-axi call, prints nothing, and gains no artifact
53
+ ok - a relay-enabled home with no commitments makes no backlog call and stays silent
54
+ ok - a relay-exhausted follow-up binding is escalated rather than retried into the thread
55
+ ok - the relay poll stays inert without a token, silent with no commitments, and surfaces a new result once
56
+ ok - startup surfaces unresolved public commitments only in a relay home that owes one
57
+ ok - typed public-followup records carry only public-safe summaries and deliverables
58
+ ok - dropped-baton regression: delivery retains the loop and pending prints open-loop
59
+ ok - CONTROL: the identical teardown REFUSES the moment a commitment is registered
60
+ ok - rechain posts the shipped follow-on into the same thread
61
+ ok - rechain resumes the same obligation after an interrupted bind
62
+ ok - concurrent rechains cannot fork one delivered source
63
+ ok - failed rechain retirement keeps the source claimed by one resumable destination
64
+ ok - registration replay preserves delivered and retired loop states
65
+ ok - redelivery does not report a retired loop as open
66
+ ok - retire closes delivered loops after secondmate home removal
67
+ ok - retire fails closed for an unbound existing secondmate
68
+ ok - retire fails closed when a secondmate ID is reassigned
69
+ ok - rechain refuses an unrelated existing destination
70
+ ok - pending skips a registration retired during settlement
71
+ ok - retire --reason closes the loop and drops the open-loop line
72
+ ok - retention creates no false teardown refusal and pending no longer prunes
73
+ ok - expiry escalation is pinned by FMX_NOW_OVERRIDE
74
+ ok - brief fails explicitly when typed deliverable keys are unavailable
75
+ ok - pre-change registrations are open loops and un-rechainable, never a crash
76
+ ok - teardown reports an unreconciled legacy Relay link
77
+ ok - secondmate promotion matches teardown parent resolution
78
+ ```
79
+
80
+ The restart case is the end-to-end proof of guarantee 1.
81
+ It reproduces the stranded state first (work bound, no reconciled terminal result, delivery refused with "still waiting on its bound work" and zero posts), then has a secondmate-shaped child report a typed `pr-merged` result, deletes the drained inbox payload, reconciles from disk, and asserts exactly one `connector/followup` call carrying the original `request_id`, a validated `posted` receipt, and a Done obligation.
82
+
83
+ The dropped-baton case is the end-to-end proof of guarantee 3.
84
+ It delivers a `report-ready` promised-final, asserts the registration is retained and `pending` prints `open-loop`, then shows that an unbound follow-on ship is not teardown-refused (the one-variable control still refuses the moment a commitment is registered for that work).
85
+ `rechain` then binds a fresh `pr-merged` obligation onto the same request/thread, and a second follow-up carries the shipped text.
86
+ `retire --reason` records its private receipt before removal and is the only close; replayed registration cannot reopen that retired loop.
87
+ The concurrency and interrupted-bind cases verify that one delivered source cannot fork and that retry converges on the same destination obligation.
88
+ A pre-change on-disk record (no `state=`, no `request_context_b64`) is an open loop and un-rechainable rather than a crash.
89
+
90
+ The existing Relay mention suite (`tests/fm-x-mode.test.sh`) is unchanged by this work.
91
+
92
+ ## Relay-disabled zero overhead
93
+
94
+ The relay-disabled case in `tests/fm-public-followup.test.sh` invokes every public-followup entry point against a home with no `.env`, logs every `tasks-axi` invocation, and compares the state tree before and after.
95
+ It proves the feature makes no `tasks-axi` call, prints nothing, and creates no `state/public-followup` artifact without coupling that guarantee to session start's independently owned state files.
96
+
97
+ The whole added cost in that home is the activation predicate, measured over 1000 in-process calls including loop overhead:
98
+
99
+ ```sh
100
+ . bin/fm-public-followup-lib.sh
101
+ for i in $(seq 1 1000); do fm_pf_relay_active "$HOME_DIR" || true; done
102
+ ```
103
+
104
+ ```
105
+ total_ns=22305959 per_call_us=22
106
+ ```
107
+
108
+ Roughly 0.02 ms per session start, from a single `[ -f "$FM_HOME/.env" ]` test that returns false before anything else runs.
109
+
110
+ ## Compatibility axes reviewed
111
+
112
+ Primary harnesses (`claude`, `codex`, `opencode`, `pi`, `pi-signed`, `grok`, `kimi`): not applicable after inspection.
113
+ Nothing here reads or renders harness-specific state.
114
+ The only supervision surfaces touched are the session-start digest, which `bin/fm-supervision-instructions.sh` already renders per harness without knowing this section exists, and the wake payload produced by the existing relay poll, which every harness protocol consumes identically.
115
+
116
+ Runtime backends (tmux, herdr, zellij, orca, cmux): not applicable after inspection.
117
+ No command here reads `state/<id>.meta`'s backend fields, resolves an endpoint, or captures a pane.
118
+ The lifecycle integrations are backlog-handoff warnings, promotion rechain hints, and `bin/fm-teardown.sh`'s owed-reply refusal plus non-blocking open-loop and legacy `x_request=` warnings.
119
+ They inspect home, task, parent-binding, and registration records rather than backend fields or endpoints, so they behave identically on every backend.