feat(fleet): provision roster from system-type profile (H3) #665
Reference in New Issue
Block a user
Delete Branch "feat/h3-fleet-provision"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
H3 — Fleet provisioning
Turns a declared system type (a profile) into a concrete, parser-valid
roster.yaml. DRY-RUN-FIRST and reviewable.Command surface
--write): DRY RUN. Prints theroster.yamlit WOULD generate to stdout + a topology summary (name / class / runtime / reports_to / persona-resolution layer). Writes nothing, exits 0.--full: materialize the ENTIRE profile roster (all seats, multiplicity expanded). Without--full: only thefloorseats (always-staffed minimum).--write: write to<mosaicHome>/fleet/roster.yaml. Refuses to overwrite an existing roster unless--forceis also given.validateProfile, override-aware class set). Fails with a class-naming message if any class doesn't resolve — never emits a roster referencing a nonexistent persona.mosaicHome = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic').Reuse (no core changes to fleet-profiles.ts / fleet-personas.ts)
loadProfile+validateProfile+listPersonaClassesWithOverrides(fleet-profiles.ts).resolvePersona(fleet-personas.ts), override-aware, used both to confirm each class resolves and to report its baseline/override layer.registerFleetProfileCommand/registerFleetPersonaCommandexactly (samemosaicHomeForclosure on the parent--mosaic-home).Generation-rule defaults (each documented inline + reviewable)
class; N>1 →class0,class1,… (e.g.code×2 →code0/code1). Deterministic, in profile roster order.truefor floor classes + the lead; omitted otherwise.truefor non-floor, non-lead execution seats; floor/lead omit it (mirrors today's coders resetting while orchestrator/enhancer don't).runtime: claudevia a single centralizedresolveSeatRuntime(). (see OPEN QUESTION below).socket_name: mosaic-fleet,holder_session: _holder,working_directory: ~,claude+piruntimes) so output is a drop-in valid roster. No operator-personal data copied.fleet.tsagent parser (normalizeAgent→assertKnownKeys) rejects unknown agent keys, andreports_tois NOT in its allow-list — emitting it would break round-trip. Soreports_tois shown in the dry-run topology summary only, not written. Confirmed against the parser's allow-list.⚠️ OPEN QUESTION for ratification (rule 4) — runtime-per-class policy
Provisioning currently defaults every seat to
runtime: claude(the safe universal default — claude runs every persona, so the roster is always launchable). But today's live roster runs coders onpi+model_hint: openai-codex/gpt-5.5:high.Should provisioning encode a class→runtime/model map, and if so, where?
runtime/model_hint), orresolveSeatRuntime?The policy is centralized in
resolveSeatRuntime(klass, isFloor, isLead)so encoding either answer is a one-function edit; structural correctness holds regardless.Tests (vitest, all green)
orchestrator+enhancer, correct flags + valid scaffold.--full→ all seats,code×2 →code0/code1, deterministic ordering.loadFleetRoster(the real parser) → parses with the expected agent count/classes (key correctness proof; also asserts noreports_toemitted).roles.local-only custom persona resolves (no false unresolved error,persona=override); a bogus class FAILS with a clear message.--writerefuses to clobber without--force;--write --forceoverwrites;--writeto fresh home creates the file; dry-run writes nothing.Gates
pnpm install✅turbo build --filter=@mosaicstack/mosaic^...✅ (12/12)vitest run✅ 618 passed (44 files; +9 new provision tests, +1 updated fleet subcommand-list assertion)typecheck✅ ·lint✅ · prettier ✅🤖 Generated with Claude Code
Independent review-of-record — mos-claude-1 (two-perspective) — APPROVE (correctness)
Reviewed PR head
1f33cb1(3 commits) againstorigin/main: 5 files, +697/-0.Design / correctness — clean.
fleet-provision.tsowns only the profile→roster generation policy; DRY-reusesloadProfile/validateProfile(fleet-profiles) and persona resolution (fleet-personas). Every generation RULE (1 naming, 2 persistent_persona, 3 reset_between_tasks, 4 runtime, 5 scaffold, 6 reports_to) is documented inline and justified.--writepersists; refuses to clobber an existingroster.yamlwithout--force(protects operator customizations). Verified the access/exists guard logic.fleet.tsparser rejects unknown agent keys, so emitting it would break round-trip. Output is rendered via the sameyamlserializer the parser uses, so it round-trips.resolvePersonarefactored to delegate to a new reusableresolvePersonaFrom(scan-once primitive — the perf commit; backward compatible); command registered infleet.tsmirroring profile/persona;fleet.spec.tsallow-list updated (provision, alphabetical).Tests — comprehensive.
fleet-provision.spec.ts(270 lines) are real FS integration tests against the committed library: floor-only default (seats+flags+scaffold),--full(multiplicity expansion + deterministic ordering + seat count), round-trip back through the realloadFleetRosterparser (key correctness proof), override-aware resolution of a roles.local-only persona, bogus-class FAILS with a clear message, and all four--writepaths (clobber-refused /--forceoverwrite / fresh create / dry-run writes nothing).CI (independent confirmation): push pipeline 1541 on this exact HEAD
1f33cb13= success, all steps OK (ci-postgres, install, sanitization, typecheck, lint, format, test). The PR-event flakes (1542 unrelated @mosaicstack/storage vitest timeout; 1545 clone-step) are infra, not this diff (mergeable=true, no conflict).Non-blocking note: RULE 4 defaults every seat to
runtime: claude. This is intentional and flagged in the PR body — it guarantees a structurally-valid, launchable roster, and the class→runtime/model map (e.g. coders on pi + gpt-5.5) is centralized inresolveSeatRuntimeso it's a one-edit follow-up. Appropriate scope for H3.Verdict: APPROVE (correctness). Recommend merge once a PR-event pipeline goes actually green.