fix(fleet): lease-broker activation, symlink-safe unit placement, named launch refusal — Wall 6 (#1292) #1297
Open
fargo
wants to merge 1 commits from
fix/1292-lease-broker-activation into next
pull from: fix/1292-lease-broker-activation
merge into: :next
:main
:docs/ri-050-release-evidence
:fred/guides-seat-identity-fleet-comms
:next
:fred/credential-fail-closed-seat-slots
:feat/ri-050-qr-evaluator
:docs/ri-050-forge-docs-fastfollow
:fix/ri-050-registry-secrets
:test/ri-050-publish-gate-negative
:fix/ri-050-verify-pglite-path
:docs/ri-050-qr-probe-inventory
:feat/ri-050-web-stale-safety
:docs/ri-050-mission-bootstrap
:fix/ri-050-forge-fail-closed
:feat/ri-050-publish-gate
:fix/1292-lease-broker-activation
:fleet/continuation-record-2026-08-17
:feat/ri-050-prd-authority
:fix/ri-050-macp-fail-closed
:fix/1280-identity-first-resolution
:feat/w-f4-store
:fix/1264-fleet-unattended-first-start
:fix/1269-ci-chain-unblock
:fix/1256-fleet-runtime-preflight
:fix/1256-fleet-pane-path-node
:fix/1257-e7-draft-transition
:fix/1017-enumeration-guard-population
:fix/1240-fleet-transport-check
:fix/1017-wire-start-agent-session
:e2e-compose
:fix/1241-launch-failure-visible
:fix/1237-fleet-v2-dispatch
:fix/1236-installer-dir-modes
:fix/installer-path-and-node
:docs/1216-trunk-parameterization
:docs/ia-merge-current
:fix/869-lease-probe-timeout
:feat/workspace-hygiene-tool-enforcement
:feat/1080-pr-edit
:fix/1182-fail-closed-launch
:fix/1179-required-security-di
:feat/p3-slice0-task5-chat-runtime-router-shaggy
:feat/p3-slice0-task5-chat-runtime-router
:feat/wf1-composition
:feat/p3-slice0-task4-web-catalog-selection
:feat/lease-promotion-and-harness-isolation
:ci/provision-pi-runtime
:feat/p3-slice0-task3-catalog-selection
:feat/p3-slice0-task2-harness-registry
:adopt/965-mos-ste-writing-standard
:fix/991-comment-url-scheme-normalise
:feat/wf2-bundle-migration
:feat/wf4-plugin-acquisition
:feat/wf5-refresh-safety
:fix/1145-coord-di-compiled-boot
:feat/p3-slice0-task1-harness-contracts
:docs/webui-phase-p-structure
:feat/1150-pi-goal-extension
:feat/webui-p3-chat
:fix/1146-ci-queue-purpose
:fix/1138-conditional-federation
:feat/webui-p2-data-auth
:fix/gateway-runner-image
:feat/webui-p1-vite-skeleton
:fix/break-c-hooks-and-web-image
:docs/webui-fleet-claude-bridge-plan
:fix/wizard-gateway-failure
:fix/ci-queue-wait-no-status
:fix/next-node-gate
:fix/mosaic-init-rce
:greenfield/fomo-lin
:fix/1099-pipefail-wake
:fix/1099-pipefail-tests
:fix/1099-pipefail-sweep
:fix/framework-shell-portability
:fix/1043-pane-git-identity
:fix/1081-issue-close-silent-comment-failure
:fix/1090-enrollment-wallclock-tolerance
:feat/1082-tea-stale-token-diagnostic
:fix/detect-platform-silent-128-outside-repo
:feat/1050-install-state-machine-red-fixture
:fix/pr-merge-message-field
:feat/1051-mosaic-brain-installer
:feat/1045-mosaic-cred
:remediation/state
:fix/1056-upgrade-rollback-control-race
:fix/1019-ci-queue-timeout-harness
:feat/rm-02-gate-registry
:fix/rm-01-reproducible-checkout
:remediation/mission-setup
:fix/hygiene-inert-format-gate
:fix/1019-queue-guard-stdin
:feat/mos-ste-writing-standard
:fix/1007-suite-hermeticity
:feat/push-guard-null-case-verification
:mos-comms-live
:docs/heartbeat-framework-layering-ms-lead
:feat/869-c4-version-coupling
:feat/869-c2-install-ordering-guard
:feat/869-c5-doctor-activation-check
:feat/per-agent-gitea-identity
:fix/875-belongs-case-insensitive-slug
:fix/ci-queue-wait-404-branch-absent
:feat/869-c1-activation-probe
:feat/869-c3-broker-supervisor
:fix/865-tea-cli-comment-invocation
:feat/glpi-skills
:fix/860-deflake-mutator-lease-gate
:fix/850-detect-platform-port-normalization
:fix/856-worktree-deps-preflight
:fix/835-pr-review-approve-reject-comment-flag
:fix/848-truthful-evidence
:fix/812-pr-review-comment
:fix/849-recovery-runtime-fixture-race
:docs/758-ledger-m5-001-sync
:feat/834-tc-server-side-doc
:feat/833-constrained-recovery-command
:feat/827-gate0-probe
:governance/gate0-probe3-amendment
:fix/795-codex-pr-diff
:fix/795-ci-base-jq
:fix/795-ci-base-git
:feat/791-pr3-fleet-regen
:feat/791-pr2-snapshot-restore
:fix/807-glpi-206
:fix/808-agent-send-false-sender
:feat/791-upgrade-config-protection
:feat/790-mosaic-yolo-claudex-pr2
:feat/790-mosaic-yolo-claudex
:feat/758-v1-v2-migrator
:fix/766-exact-fleet-comms
:test/758-reconciler-lifecycle-gates
:docs/771-kbn101-db-role-split
:test/758-example-profile-dispositions
:feat/758-shared-role-resolution
:feat/mos-logical-identity-fencing
:feat/769-kbn100-unified-schema
:docs/753-kbn010-threat-gate
:feat/758-roster-v2-compiler
:feat/756-official-discord-plugin
:docs/758-fleet-config-management
:fix/mos-option2-qualification-format
:docs/issue-758-m0
:docs/mos-option2-qualification
:mos-comms
:feat/tess-interaction-agent
:fix/tess-docs-format
:draft/mosaic-platform-prd
:fix/installer-provider-gate-and-local-gateway-redis
:release/mosaic-cli-0.0.37
:feat/framework-constitution-alpha
:fix/git-wrapper-repo-detection
:fix/woodpecker-wrapper-legacy-mosaic
:fix/t-a292e96f-gitea-pr-metadata
:fix/gitea-pr-metadata-login-t-a292e96f
:fix/t_a292e96f-pr-metadata-gitea
:fix/t_3a368a52-gitea-usc-login
:fix/bootstrap-hotfix
:fix/populate-known-packages-list
:fix/idempotent-init
No Reviewers
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Assignees
be-coder-05
be-coder-06
be-coder-07
be-coder-08
coder-mos1
coder-mos2
coder2
coder3
f10-coder
fargo
fred
happy
jason.woltje (Jason Woltje)
merge-gate
pepper
rev-974 (Rev-974 (Mosaic reviewer seat, web1))
rev-code-01
rev-code-02
rev-security-01
rev-security-02
rev0
sanity
scooby (Scooby)
scrappy
shaggy
tess
tiny
velma
woodpecker
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mosaicstack/stack#1297
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Wall 6 / #1292 — lease-broker activation, safe unit placement, and named launch refusal
Card implemented under fred's wave-3 rulings (design proposal → symlink measurement → amended design → GO with widened placement scope), branch
fix/1292-lease-broker-activation, basenext@8199261. Activation lands in the control plane, not the launcher.The measured foundations (all on throwaway systemd user instances + fomo-lin read-only probes)
systemctl enable <name>does not rewrite an existing by-path wants-symlink — rc=0, silent, the host stays on the old copy indefinitely.copyFilefollows a by-path symlink at the destination and overwrites the seed template (verified withcpANDfs.copyFile).The fix
placeUnitFile— every unit the install places (all four, including the broker, previously omitted entirely) goes through one helper: unlink destination symlink → copy → remove stale wants-symlink (viareadlink, notreadFile) →daemon-reload. Idempotent (double-install convergence asserted). SET-INDEPENDENCE warning documented for whoever adds a fifth unit.enableFleetUnitsenables the broker first, alongside the holder.fleet start/ reconciler start the broker before any holder/agent effect, then re-check the socket (never unit ActiveState) and refuse nonzero with a named code if it did not appear — re-probed on every invocation, soRemainAfterExit=yescan never make retry look like repair (the sticky half).broker {unitInstalled, socketPresent}as a first-class member — "broker died an hour ago" is now reportable drift, not invisible.start-agent-session.shpreflights the socket before any tmux effect: absent → exit 75 (EX_TEMPFAIL), namedbroker-absentmessage with socket path + remedy, no doomed pane. Exit 75 validated against the agent@ unit's measuredRestart=policy (oneshot, none — no restart loop). The preflight detects and refuses; it never starts the broker.mosaic doctornames one convention-neutral remedy:mosaic fleet install (it reconciles either enable convention).Tests — name the failure, point at the check
Failure written down: a greenfield host's seat dies ~4s at lease registration while
fleet startreports rc0; on retry,fleet startreports success again while the seat is still dead.--stopnot fenced.unitInstalled: true, socketPresent: false); broker-before-agent ordering in both command and apply paths.Sabotage controls (all restored byte-identically, sha256-verified): placement unlink removed → exactly the seed-integrity test reddens (1/8); socket re-check disabled → exactly the two preflight specs redden; shell preflight removed → the bash suite reddens rc=1. All green after restore.
Gates (rc-honest): vitest 1563/1563 · lint rc=0 · root build 25/25 · root typecheck 45/45 · bash preflight suite green. The new bash suite is CI-fit by construction (self-controlled preconditions — fake tmux, real unix socket, hermetic env; comment on the file explains why the older
test-start-agent-session.shis not, and stays unwired per #1270).Not in this PR
Test 6 — greenfield on sandbox 1124 (documented install → seat alive past 2min with advancing heartbeats → second
fleet startstill refuses if the broker is down) — runs on the sandbox after daphne's Wall-6 baseline reproduction on the same VM, coordinated with fred. This PR is the code half; 1124 is the boundary half.Implementation note (disclosed)
Mid-work, my own sabotage-restore erased my uncommitted
fleet.ts(thegit checkout --I used as "restore" was the pre-change file — copy-then-unlink applied to my own working tree). The implementation was reconstructed from the session's edit history, and all gates + all three sabotage controls were re-run on the reconstructed file — the results above are from the rebuilt version, not the erased one.Wall 6: no documented path ever enabled or started the shipped mosaic-lease-broker.service — every gated runtime died ~4s in at lease registration while fleet start reported rc0, and a broker not in the reconciler plan could not be reported as drifted. Activation lands in the control plane, not the launcher: - fleet install places ALL FOUR units through placeUnitFile — a placement helper that unlinks any by-path-enable symlink at the destination BEFORE copying (Node copyFile follows the link and overwrites the SEED template; measured on a throwaway systemd user instance 2026-08-17, with both cp and fs.copyFile), removes a stale wants-symlink pointing outside the active dir (readlink — readFile returns the target's content, not the link path), then copies and daemon-reloads. The same measurement showed systemctl enable <name> does NOT rewrite an existing by-path wants-symlink — reconciliation must be explicit. Idempotent: second install on by-path residue converges to the identical state. Until now the copy block named three units and omitted the broker, and the residue set / copy set were disjoint only by accident (fomo-lin survived copy-through because its one symlink was the one unit not copied); adding the broker made them intersect on first run. See the SET-INDEPENDENCE note on the helper before adding a fifth unit. - enableFleetUnits enables the broker first, alongside the holder. - fleet start / reconciler start the broker BEFORE any holder/agent lifecycle effect, then RE-CHECK the socket (not unit state) and exit nonzero with a named code if it did not appear. Re-probed on every invocation — a RemainAfterExit=yes dead-looking-active unit can never make retry look like repair (the sticky-retry check). - The reconciler plan carries broker {unitInstalled, socketPresent} as a first-class member; the socket is the signal (enabled-but-dead units report socketPresent=false). - start-agent-session.sh preflights the broker socket BEFORE any tmux effect (moved ahead of the ownership probe): absent -> exit 75 (EX_TEMPFAIL), named refusal with socket path and remedy, no doomed pane. The agent@ unit is Type=oneshot with no Restart=, so the message survives instead of looping. The preflight detects and refuses; it never starts the broker. - mosaic doctor's lease check names one convention-neutral remedy: 'mosaic fleet install (it reconciles either enable convention)' — written from the measurement; teaching a manual systemctl line could leave a host with competing wants-symlinks. Tests: fleet-place-unit.spec.ts (8: clean-host negative control, by-path residue -> seed bytes AND mtime unchanged [the finding-2 check], wants-residue cleared, idempotence single + double-install convergence); fleet.spec.ts broker-first enable ordering, refused start emits no holder/agent calls, second-start re-probe; reconciler broker plan member (enabled-but-dead shape) + broker-before-agent ordering in both command and apply paths; test-agent-session-broker-preflight.sh (CI-fit: fake tmux, real unix socket at a short /tmp path — AF_UNIX caps at 108 bytes, hermetic env; absent -> exit 75 + no tmux session, live socket passes, explicit env wins, --stop not fenced). 1563/1563 vitest, lint, root build 25/25, root typecheck 45/45. Sabotage controls: placement unlink removed -> exactly the seed-integrity test reddens (1/8); socket re-check disabled -> exactly the two preflight specs redden; shell preflight removed -> the bash suite reddens (6 FAIL assertions, rc=1). All restored byte-identically (sha256-verified), all green again. Test 6 (greenfield 1124, seat alive 2min + second fleet start) runs on sandbox after daphne's baseline, coordinated with fred. Note: the preflight uses exit 75 measured against the unit's Restart= policy (oneshot, none) — no restart loop.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.