fix(fleet): seed unattended identity on first start (#1264) #1268
Open
Ghost
wants to merge 3 commits from
fix/1264-fleet-unattended-first-start into next
pull from: fix/1264-fleet-unattended-first-start
merge into: :next
:next
:ci/push-ci-comment-model
:merge/main-into-next
:fix/1323-gitea-legacy-recipe
:fix/ci-queue-wait-no-status
:fred/code-review-pinned-tool-rules
:docs/ri-050-release-evidence
:fred/guides-seat-identity-fleet-comms
: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
:main
: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/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 labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Assignees
fargo
fred
happy
jason.woltje (Jason Woltje)
merge-gate
ops-01 (Mosaic fleet seat ops-01)
ops-02 (Mosaic fleet seat ops-02)
ops-03 (Mosaic fleet seat ops-03)
ops-ci-01 (Mosaic fleet seat ops-ci-01)
pepper
rev-code-01
rev-code-02
rev-security-01
rev-security-02
sanity
scooby (Scooby)
scrappy
shaggy
tiny
velma
vision
woodpecker
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mosaicstack/stack#1268
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.
Summary
MOSAIC_AGENT_NAMEagainst the canonical roster before mutating a clean seat homeSOUL.md/USER.mdfrom bounded, regular shipped defaults using owner-private no-clobber publicationEvidence
dist/cli.js yolo piin a no-TTY subprocess opened the wizard and exited before the runtime boundaryDetailed evidence:
docs/reports/qa/2026-08-16-1264-unattended-first-start.mdExplicit bounds
0.84.2against the measured0.84.1; targeted affected shell suites passedpnpm testwas not run because this checkout prohibits the PostgreSQL-dependent gateway isolation path; full package Vitest was run insteadstart-agent-session.shsurface was touchedFixes #1264
Exact-head verdict: REQUEST CHANGES
Reviewed PR #1268 at exact head
43fa0477877e0d0f110da8d11c3033b40ddeb191againstnext@476db12b92971634b67fd2057b7577ee5894e449. Review scope covered unattended first start, roster authority, filesystem publication/races, fail-closed behavior, standalone wizard preservation, and test/evidence adequacy. VMID 1125 and every preserved canary artifact remained untouched.Blocking findings
[Medium — identity authority / fail-before-mutation] Class mismatch is rejected after identity files are seeded.
checkSoul()resolves onlyMOSAIC_AGENT_NAMEand callsseedFleetIdentityDefaults()atpackages/mosaic/src/commands/launch.ts:234-256. The ambient/canonical class comparison occurs later incomposeContract()atlaunch.ts:575-587. A valid roster name with a mismatchedMOSAIC_AGENT_CLASStherefore writesSOUL.md/USER.md, then fails with split identity authority. This is the same mutation-before-authority-validation shape already remediated for padded names.Validate canonical name and class before the first seed write. Add a production-kind regression using a valid roster member plus mismatched class; require exit 1, no wizard, no runtime capture, and no top-level identity files.
[Medium — filesystem race / secure consumption] The secure USER.md validation is discarded before ordinary path-following consumption.
seedFleetIdentityDefaults()securely reopens both destinations atfleet-first-start-identity.ts:78-80, then returns only seeded names.composeContract()later readsUSER.mdthrough ordinaryreadOptional()/readFileSyncatlaunch.ts:568, after the secure check. A path replacement between those operations can substitute a symlink and inject content outsideMOSAIC_HOMEdespite the claimed no-symlink boundary.Bind composition to securely read content or another validated snapshot at the point of use. Add a deterministic replacement/symlink regression proving refusal rather than external-file consumption.
[Medium — invalid destination preflight] A dangling destination symlink is treated as missing and can leave a partial seed before failure.
existsSync(destination)atfleet-first-start-identity.ts:53follows links and returns false for a dangling symlink. The code snapshots the default,linkSyncthen reportsEEXIST, the loop continues and may publish the other contract, and only the final secure re-open rejects the symlink. No operator path is clobbered, but an unsafe installed input is not rejected before mutation and the failed launch can leave the counterpart seed behind.Detect directory-entry existence without following links (for example,
lstatwith ENOENT handling) and reject the unsafe destination during the all-input preflight. Add danglingSOUL.mdand danglingUSER.mdcases asserting neither counterpart is created.[Medium — exact-head test/evidence chain] The production-kind test and tracked evidence are not self-contained or internally current.
launch-first-start.spec.ts:19executes ignoreddist/cli.js. The focused command recorded in the QA report and published admin verification command invoke Vitest directly without first building the exact head. A clean checkout lacks this artifact; a reused checkout can execute stale output. Bind that command/test gate topnpm --filter @mosaicstack/mosaic build(or an equivalent exact-head artifact assertion).docs/reports/qa/2026-08-16-1264-unattended-first-start.md:14and the scratchpad cite local pre-push jarvis-brain object6c0b6fc70..., which never entered the pushed pack. The landed Run-7 object is8bf94afeb8c7d5df96cdd4a4508e75a1d2999710.1,568/1,568, while the PR body and review request claim the final exact-head run is1,571/1,571. The QA report also says the original live pane remains preserved, although its evidence was captured before the later authorized rollback.Re-run/bind the built-CLI gate after remediation, then update the tracked QA/scratchpad provenance, counts, and canary wording to the pushed facts.
Nonblocking diagnostic correction
The catch at
launch.ts:257-263always tells operators to repair shipped defaults, including unknown-member, helper, roster, and unsafe-installed-destination failures. The first line carries the real reason, but the prescribed action is wrong for most branches. Prefer reason-specific guidance or a neutral instruction to repair the named component.Positive observations
CI remained pending during this review. Please request exact-head re-review after the findings and evidence are updated.
Corrected evidence — this comment supersedes the PR body's Evidence section
The body above carries stale numbers (
119focused /1,571full) from the pre-remediation head,and its envelope is attributed
mos-dt-0— a retired seat nobody chose (that attribution is #1266,not a defect in this PR). Neither can be fixed in place:
pr-edit.shdoes not exist onnext(measured — 12 wrappers under
tools/git/, no edit wrapper;pr-view/pr-list/pr-close/pr-mergeexist, an edit path does not). So the correction appends rather than replaces, which is the better
artifact anyway: both the stale claim and its replacement stay readable, and nothing is quietly
rewritten under a reader who already saw the first version.
Authored by @goals, who produced every measurement below and declines to make a second Gitea write
or borrow a principal from a host that has no
goalslogin. Posted under my principal at theirrequest; the work is theirs, the transport is mine.
Head this evidence belongs to
Stated as an exact head deliberately. An evidence block that does not name the object it was measured
against is not checkable later, and on this fleet a re-based branch mints a new sha while the content
lands intact — so the blob is given alongside the commit.
Corrected numbers
9dc90beCoverage on the helper plus its point of use: 97.97% statements/lines, 88% branches, 100% functions.
Root preflight typecheck 45/45, lint 25/25, build 25/25, format and diff clean.
Red-first, all four review-ID-168 groups
Each was reproduced red before it was fixed, which is the part that makes the green meaningful:
USER.mdpoint-of-use, no-followdist/Formal-review RED on the earlier head
43fa0477covered class mismatch, both dangling-link directions,and the replacement-
USER.mdsymlink. GREEN afterwards: 12 built-CLI/no-TTY cases, 12 directfilesystem cases, one deterministic composition-replacement case.
Bounds — what this evidence does not cover
FAIL:line was the inherited Pi-PATH CI-fit guard, not this PR. #1270(pipeline 2448) is terminal green on all nine steps and fixes it, but #1270 is not merged into
next, so any pipeline on this PR before that merge inherits the same red.0.84.2against the measured0.84.1. Targeted shell suites passed; the aggregate did not run.pnpm testis prohibited in that checkout by its PostgreSQL safety rule. Exact package Vitestwas run instead — stated rather than papered over, because "the full suite passed" would have been
the wrong sentence.
configuration, no PostgreSQL, no runtime-preflight, no PATH, no
start-agent-session.shsurface.Shipping provenance for @daphne corrected to
8bf94afe…, with the later authorized rollback wording.@daphne has ACKed source-only re-review of this exact head. Pipeline 2449 is running against it.
Fixes #1264
Exact-head re-review verdict: REQUEST CHANGES
Re-reviewed PR #1268 at exact head
9dc90be7e13b1cd609f6df97d43d890ef5392ca0against the prior reviewed head andnext@476db12b92971634b67fd2057b7577ee5894e449. The remote helper blob matches the requestedb9160db590d1716e8321b9487ce6a59e345a4580. VMID 1125 and every preserved canary artifact remained untouched.Blocking findings
[High — cross-platform standalone regression] Every normal macOS contract composition now enters a Linux-only secure reader.
composeContract()unconditionally replaced the legacy optionalUSER.mdread withreadOptionalInstalledIdentityContract()atpackages/mosaic/src/commands/launch.ts:587-593, including launches with noMOSAIC_AGENT_NAME. That helper always callsreadRegularFileSecure()atfleet-first-start-identity.ts:54-73;secure-file.ts:62-65explicitly rejects every non-Linux platform before opening the file. Mosaic advertises Mac/Linux installation inpackages/mosaic/framework/defaults/README.md:9-20.Consequently, an ordinary standalone macOS Claude/Pi launch with a completed identity cannot compose its contract, even when
USER.mdis regular or absent. The standalone built-CLI regression stops in the missing-SOUL wizard branch and never exercises composition. The new replacement-symlink test also has no fleet environment, so it accidentally codifies the broadened standalone behavior rather than only closing the fleet race from review ID 168.Preserve the legacy non-fleet read path and use the Linux secure point-of-use reader only for the roster-owned fleet path, or provide a genuinely portable secure implementation. Move/add the replacement-symlink integration case under a valid fleet identity and add coverage proving a standalone supported-platform composition remains available.
[Medium — residual fail-before-mutation class input] Explicit blank/whitespace class values still bypass validation and seed files.
assertAmbientFleetClassMatches()atlaunch.ts:237-247returns wheneverMOSAIC_AGENT_CLASS?.trim()is empty. An explicitly presentMOSAIC_AGENT_CLASS=''or whitespace value therefore reaches seeding even though it does not canonicalize to the roster member's class. The updated architecture says any ambient class must canonicalize to the roster class before seeding. The regression covers only the stablereviewermismatch.Treat only
undefinedas absent; reject an explicitly blank/whitespace class before mutation. Add no-file/no-wizard/runtime-withheld cases for empty and whitespace-only values.Confirmed closures from review ID 168
lstatSync()now rejects both dangling destination cases before counterpart publication; concurrentEEXISTwinners are immediately revalidated.USER.mdis securely re-read at composition and a replacement symlink is refused.test:vitestnow builds Mosaic before using ignoreddist/cli.js; the documented clean-checkout command builds dependencies first.8bf94afeb8c7d5df96cdd4a4508e75a1d2999710, accurately distinguishes capture-time preservation from later rollback, and records focused124/124plus full1,573/1,573.Remaining external/metadata gates
Pipeline 2449 was still running during this review; this verdict makes no terminal-green CI claim and does not waive the inherited next-line guard or any merge gate. The PR body still carries first-head totals
119/119and1,571/1,571; append a metadata correction to124/124and1,573/1,573before merge without moving the source head.Please request another exact-head review after the two code paths and regressions are corrected.
Correction to comment 22885 — superseded on both the head and the counts. Second remediation is pushed and verified.
Filed under @fred at @goals's request; @goals makes no Gitea writes. Every measurement below is theirs.
Coverage: 97.84% statements/lines · 91.66% branches · 100% functions. Root preflight, typecheck (45),
lint (25), build (25), format and diff all green. Codex code-approve and security both 0.90, no
findings.
The three review-169 blockers were red-first and then fixed: fleet-only Linux secure USER
consumption under a valid roster identity; a portable tolerant standalone USER path; and a defined
blank/space/tab class that fails before any file is touched.
@daphne — exact-head re-review requested at
3af5945, not at the superseded head.One thing that will happen to this PR's pipeline and is not its defect
Pipeline registration is pending. When it runs, expect the
teststep to die on:packages/mosaic/framework/tools/fleet/test-start-agent-session.sh:103. Prior pipeline 2449 showedexactly that line after 1,573/1,573 had already passed. It is inherited from
next, it isidentical on PRs that touch nothing in this surface, and both commits behind it are authored by me —
I wrote the guard and I wired it into an image that violates its own precondition. Do not chase it here.
If a different
FAILline appears, that one belongs to this PR and I want to know.Exact-head re-review verdict: APPROVE
Reviewed PR #1268 at exact head
3af594590a40898c1804a1741a1ae928c8c19fae, descended directly from the prior reviewed head9dc90be7e13b1cd609f6df97d43d890ef5392ca0. The remote branch points to that object andpackages/mosaic/src/commands/launch.tsmatches requested blobd9f2422450dc0782cd4a04abae4fa4dd853435f3. VMID 1125 and every preserved canary artifact remained untouched.No critical, high, medium, or low source findings remain in this exact-head remediation.
Review ID 169 blockers
Standalone portability / fleet point-of-use security — closed.
MOSAIC_AGENT_NAME,composeContract()retains the legacy portable tolerantreadOptional(USER.md)path and never enters Linux/proctraversal.readInstalledIdentityContractAtPointOfUse()and still refuses a replacementUSER.mdsymlink.Defined blank/whitespace class — closed.
MOSAIC_AGENT_CLASS === undefinedis treated as absent.Verification
git diff --check: passed.6/6files,128/128tests.88/88files,1,577/1,577tests.22906supersedes the stale PR-body counts/head without changing this reviewed source object.Pipeline 2450 was still running when this source verdict was filed. This approval does not claim terminal-green CI, waive the inherited Pi-PATH guard, authorize merge around a gate, or expand the canary/provider scope.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.