Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 260 additions & 0 deletions packages/amico-run/src/fleet_projection_verb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// `amico fleet status --projection` (#1068, fleet rearchitect P3b-1) — the
// fleet-AUTHORITY status: the entitlement-gated read of amicissimo's
// published, provenance-stamped projection (spec spec-20260913-114814 §3 D1,
// countermeasure row 1). The session-registry `status --session <id>` keeps
// its pinned contract; `--projection` routes here.
//
// The three properties this module exists to enforce:
// 1. ONE PARSER PATH. The projection is validated + rendered by
// @amicode/schema's fleet_projection reader (contract v1) — this verb
// never parses topology/health/locks itself. A stale/future/absent
// contract version surfaces the reader's LOUD rejection verbatim
// (invariant 5: one path, versioned — the hub rejects stale contract
// versions loudly, and so does the client read).
// 2. THE INVOCATION SEAM. The publisher is a SUBPROCESS BOUNDARY:
// `python3 -m fleet_authority publish --out <path>` with cwd = the
// resolved amicissimo checkout (amicissimo#414, the companion entry
// point — possibly unmerged at the time this lands, so the seam is a
// typed, injectable interface; the default impl spawns exactly the
// pinned command line and the tests mock it with fixture projections).
// 3. THE BOOTSTRAP EXCEPTION. Absent entitlement or absent checkout is
// base-standalone HONESTLY STATED with a pointer to the grant path,
// exiting FLEET_BOOTSTRAP_EXIT (75) — distinct from success (a silent
// no), from usage (64, the user's mistake), and from a stack trace
// (never). The mode field is untouched: stating base-standalone is a
// floor report, never a mode-machine write (spec invariant 7).
//
// Entitlement + checkout resolution follow `amico premium`'s machinery
// precedent (src/premium.ts — PREMIUM_CODE "amicissimo", the AMICISSIMO_ROOT
// ladder): the same codes file, the same ladder, the same funnel invariant —
// a not-granted machine loses nothing, it is told what it is.
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import { mkdtempSync, rmSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import * as path from "node:path";
import {
FleetContractVersionError,
freshnessAdvisory,
freshnessBetween,
readProjection,
renderFleetStatus,
type FleetProjection,
} from "@amicode/schema";
import { PREMIUM_CODE, readCodes } from "./premium.js";
import type { VerbResult } from "./verbs.js";

/** The bootstrap exception's exit code. 75: deliberately distinct from 0
* (success — a silent no would lie), 64 (usage — this is not the user's
* mistake), and 1 (an unexpected failure — this is an expected, honest
* state). The P3b-2 consumers (installer, guard) branch on it. */
export const FLEET_BOOTSTRAP_EXIT = 75;

/** The subprocess boundary's typed record — what the default impl spawns and
* what the tests mock. `program` + `args` is the full command line; `cwd` is
* the resolved amicissimo checkout (so `-m fleet_authority` resolves against
* the checkout's package); `outPath` is where the projection must land. */
export interface PublisherInvocation {
program: string;
args: string[];
cwd: string;
outPath: string;
}

export interface PublisherResult {
code: number;
stdout: string;
stderr: string;
}

export interface FleetProjectionDeps {
checkDir?: (p: string) => boolean;
readFile?: (p: string) => string | null;
/** THE invocation seam (injectable): the publisher subprocess call. */
runPublisher?: (inv: PublisherInvocation) => PublisherResult;
}

function flagValue(argv: string[], name: string): string | undefined {
const i = argv.indexOf(name);
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined;
}

/** Parse the projection-status flags: `--projection` (the routing marker,
* tolerated wherever it appears), `--checkout <dir>`, `--config <file>`,
* `--previous <projection.json>`. Anything else is a usage error naming the
* offender — never silently ignored. */
function parseFlags(argv: string[]): { ok: true; flags: { checkout?: string; config?: string; previous?: string } } | { ok: false; errors: string[] } {
const flags: { checkout?: string; config?: string; previous?: string } = {};
const takesValue = new Set(["--checkout", "--config", "--previous"]);
for (let i = 0; i < argv.length; i++) {
const tok = argv[i];
if (tok === "--projection") continue;
if (tok.startsWith("--")) {
if (!takesValue.has(tok)) return { ok: false, errors: [`unknown flag ${tok} — the projection status accepts --checkout, --config, --previous (and the routing marker --projection)`] };
const v = argv[i + 1];
if (v === undefined) return { ok: false, errors: [`${tok} requires a value`] };
flags[tok.slice(2) as "checkout" | "config" | "previous"] = v;
i++;
continue;
}
return { ok: false, errors: [`unexpected positional argument ${tok}`] };
}
return { ok: true, flags };
}

/** The default publisher subprocess — THE invocation seam's production impl.
* Exactly the pinned command line, nothing else: `python3 -m fleet_authority
* publish --out <path>` in the checkout's cwd (the #414 entry point). */
function defaultRunPublisher(inv: PublisherInvocation): PublisherResult {
const r = spawnSync(inv.program, inv.args, { cwd: inv.cwd, encoding: "utf8" });
return { code: r.status ?? -1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
}

function fail(errors: string[], extra: Record<string, unknown> = {}): VerbResult {
return { json: { verb: "fleet", subcommand: "status", projection: true, ok: false, errors, ...extra }, code: 64 };
}

/** The bootstrap exception — base-standalone honestly stated, never a mode
* write, never a crash, exit FLEET_BOOTSTRAP_EXIT. */
function bootstrap(reason: "entitlement" | "checkout", rendered: string, extra: Record<string, unknown> = {}): VerbResult {
return {
json: {
verb: "fleet",
subcommand: "status",
projection: true,
ok: false,
bootstrap: true,
reason,
mode: "standalone",
rendered,
note: "bootstrap exception — base-standalone stated, not written: the mode field is untouched (spec invariant 7); grant the entitlement + provide the checkout to light the fleet surfaces",
...extra,
},
code: FLEET_BOOTSTRAP_EXIT,
};
}

/** A section's carried value, with the base default applied when the section
* is absent (mode absent = standalone, posture absent = ok — the projection
* contract's additive-optional discipline; the base default is applied, not
* invented: the reader's render states it in provenance). */
function scalarOrBase(proj: FleetProjection, section: string, base: string): unknown {
const s = proj.sections?.[section];
return s?.value === undefined ? base : s.value;
}

/** `amico fleet status --projection` — resolve the checkout (the premium
* ladder), gate on the entitlement, invoke the publisher at the subprocess
* seam, read the result through the ONE fleet projection reader, and print
* the provenance-rendered status summary. Backs the CLI (amico.ts) and the
* MCP facade through the same fleetVerb router as the registry verbs. */
export function fleetProjectionStatus(argv: string[], deps: FleetProjectionDeps = {}): VerbResult {
const parsed = parseFlags(argv);
if (!parsed.ok) return fail(parsed.errors);

const configFile =
parsed.flags.config ?? path.join(homedir(), ".amico", "amicode", "entitlements.toml");
const checkout =
parsed.flags.checkout ?? process.env.AMICISSIMO_ROOT ?? path.join(homedir(), "harmoniqs", "amicissimo");

// ── the entitlement gate (the premium machinery precedent) ──
const codes = readCodes(configFile, { readFile: deps.readFile });
if (!codes.includes(PREMIUM_CODE)) {
const rendered = [
`fleet status: base-standalone (bootstrap exception) — this install does not hold the \`${PREMIUM_CODE}\` entitlement code,`,
"so the fleet-authority surfaces are not staged for it (the base product works fully standalone).",
"",
"Grant path: repo access to harmoniqs/amicissimo + the code in:",
` ${configFile}`,
"",
"(bootstrap exception, exit 75 — distinct from success and from usage; see `amico premium`)",
].join("\n");
return bootstrap("entitlement", rendered, { config: configFile });
}

// ── the checkout ladder (AMICISSIMO_ROOT → org-home default) ──
const existsDir =
deps.checkDir ?? ((p: string) => fs.existsSync(p) && fs.statSync(p).isDirectory());
if (!existsDir(checkout)) {
const rendered = [
`fleet status: base-standalone (bootstrap exception) — the \`${PREMIUM_CODE}\` entitlement is granted, but no amicissimo checkout is present at:`,
` ${checkout}`,
"",
"Clone harmoniqs/amicissimo there, or set AMICISSIMO_ROOT, or pass --checkout <dir>.",
"",
"(bootstrap exception, exit 75 — distinct from success and from usage)",
].join("\n");
return bootstrap("checkout", rendered, { checkout });
}

// ── the invocation seam: publish, then read ──
const outDir = mkdtempSync(path.join(tmpdir(), "fleet-projection-"));
try {
const inv: PublisherInvocation = {
program: "python3",
args: ["-m", "fleet_authority", "publish", "--out", path.join(outDir, "projection.json")],
cwd: checkout,
outPath: path.join(outDir, "projection.json"),
};
const result = deps.runPublisher ? deps.runPublisher(inv) : defaultRunPublisher(inv);
if (result.code !== 0) {
return fail(
[
`the fleet-authority publisher failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
`invoked \`${inv.program} ${inv.args.join(" ")}\` in ${checkout} — the amicissimo#414 entry point (python3 -m fleet_authority) may be absent from this checkout; nothing was read or rendered`,
],
{ checkout, out_path: inv.outPath },
);
}

let previous: FleetProjection | null = null;
if (parsed.flags.previous !== undefined) {
try {
previous = readProjection(parsed.flags.previous);
} catch (e) {
return fail([`--previous ${parsed.flags.previous}: ${(e as Error).message}`], { checkout });
}
}

let proj: FleetProjection;
try {
proj = readProjection(inv.outPath);
} catch (e) {
// The reader's LOUD rejection surfaces verbatim — a versioned contract
// refuses both directions, naming both versions (invariant 5).
const message =
e instanceof FleetContractVersionError
? `${e.message} (projection published to ${inv.outPath} speaks a contract this CLI does not)`
: `the published projection at ${inv.outPath} failed the contract read: ${(e as Error).message}`;
return fail([message], { checkout, out_path: inv.outPath });
}

const verdict = previous === null ? null : freshnessBetween(previous, proj);
const advisory = verdict === null ? "" : freshnessAdvisory(verdict);
const fresh = proj.freshness ?? {};
return {
json: {
verb: "fleet",
subcommand: "status",
projection: true,
ok: true,
checkout,
mode: scalarOrBase(proj, "mode", "standalone"),
posture: scalarOrBase(proj, "posture", "ok"),
publisher: proj.publisher ?? {},
sections: proj.sections ?? {},
freshness: {
counter: fresh.counter,
hub_epoch: fresh.hub_epoch,
...(verdict === null ? {} : { verdict, advisory: advisory === "" ? undefined : advisory }),
},
summary: renderFleetStatus(proj, previous),
note: "read through the ONE fleet projection reader (@amicode/schema fleet_projection, contract v"
+ String(proj.contract_version) + ") — amicissimo parses and publishes, amicode consumes (spec §3 D1); provenance renders beside the data, never merged",
},
code: 0,
};
} finally {
rmSync(outDir, { recursive: true, force: true });
}
}
21 changes: 19 additions & 2 deletions packages/amico-run/src/fleet_verb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
// construction (amico.ts prints `VerbResult.json`), exactly like the other spine verbs.
import { FRONTIER_MODELS, ladderRungs } from "./ledger_dispatch.js";
import { fleetDigest } from "./fleet_digest.js";
// The fleet-authority projection status (#1068, rearchitect P3b-1): the same
// `amico fleet status` verb, one more read path — `--projection` routes to the
// entitlement-gated publisher invocation + the shared reader, while the
// pinned `--session <id>` registry contract stays byte-identical.
import { fleetProjectionStatus, type FleetProjectionDeps } from "./fleet_projection_verb.js";
import {
applyEvent,
enqueueSignal,
Expand Down Expand Up @@ -580,19 +585,31 @@ export function fleetSweep(argv: string[]): VerbResult {
// ── subcommand router ────────────────────────────────────────────────────────────
const USAGE =
"amico fleet list [--state <s>] [--root D] | amico fleet status --session <id> | " +
"amico fleet status --projection [--checkout D] [--config F] [--previous <p.json>] | " +
'amico fleet steer --session <id> --message "<instruction>" | amico fleet stop --session <id> [--reason "<why>"] | ' +
"amico fleet re-tier --session <id> --model <provider/id> [--variant <v>] | amico fleet sweep [--dry-run] | " +
"amico fleet launch --session <id> --pid <n> | " +
'amico fleet finish --session <id> --outcome settled|crashed --pid <n> [--step "<s>"] | ' +
"amico fleet digest [--post <channel>] [--machines a,b] [--jobs-line \"<t>\"] [--dry-run] [--root D]";

/** Optional injection surface for the fleet verb's sub-verbs — the projection
* status's hermetic seam (publisher subprocess, entitlement file, checkout
* probe). Existing callers (amico.ts, mcp_serve.ts) pass nothing and get the
* production defaults. */
export interface FleetVerbDeps {
projection?: FleetProjectionDeps;
}

/** The `fleet` verb body: route on the subcommand. Backs BOTH the CLI (amico.ts) and the
* MCP facade (mcp_serve.ts) — one impl, two transports. */
export function fleetVerb(argv: string[]): VerbResult {
export function fleetVerb(argv: string[], deps: FleetVerbDeps = {}): VerbResult {
const sub = argv[0];
const rest = argv.slice(1);
if (sub === "list") return fleetList(rest);
if (sub === "status") return fleetStatus(rest);
if (sub === "status") {
if (rest.includes("--projection")) return fleetProjectionStatus(rest, deps.projection);
return fleetStatus(rest);
}
if (sub === "steer") return fleetSteer(rest);
if (sub === "stop") return fleetStop(rest);
if (sub === "re-tier") return fleetRetier(rest);
Expand Down
4 changes: 3 additions & 1 deletion packages/amico-run/src/premium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export const PREMIUM_CODE = "amicissimo";
// replaces both). Minimal TOML: the v1 file is `codes = [...]` (+ `expired`).
// An absent file = public-only, silently; a malformed file = no codes, silently
// (an entitlement failure never dead-ends anything — the funnel invariant).
function readCodes(file: string, deps: PremiumDeps): string[] {
// EXPORTED for the fleet projection status verb (#1068): the entitlement gate
// is the same machinery — one codes reader, never a per-verb reimplementation.
export function readCodes(file: string, deps: PremiumDeps): string[] {
const read = deps.readFile ?? ((p: string) => (fs.existsSync(p) ? fs.readFileSync(p, "utf8") : null));
const raw = read(file);
if (raw === null) return [];
Expand Down
7 changes: 6 additions & 1 deletion packages/amico-run/src/verbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,19 @@ const profile: Verb = {
// `digest` is the fourth RENDERING (unified-fleet spec slice 1): it reads the registry,
// probes configured machines, and posts the distilled block through the amico-slack
// contract — a projection, never a second state machine.
// `status --projection` is the fleet-AUTHORITY read (#1068, rearchitect P3b-1):
// entitlement-gated, it invokes amicissimo's publisher (`python3 -m fleet_authority`)
// and renders the projection through the ONE shared reader — the registry verbs and
// the authority read are deliberately one verb surface, two read paths, no second
// topology parser anywhere.
//
// Deliberate contrast with `ledger` above: the ledger is an append-only immutable JSONL
// event log; this registry is mutable per-session TOML state. They share record I/O
// conventions, single-writer discipline, and the pid probe — not a state model.
const fleet: Verb = {
name: "fleet",
summary:
"fleet registry: list/status read verbs, steer/stop/re-tier as signal enqueuers (never a record write), sweep with a pid-liveness guard, digest as the Slack projection",
"fleet registry: list/status read verbs, steer/stop/re-tier as signal enqueuers (never a record write), sweep with a pid-liveness guard, digest as the Slack projection; status --projection reads the fleet-authority projection (entitlement-gated, #1068)",
generalizes: "the fleet view + in-chat /fleet + Amico's conversational fleet questions + the Slack digest, over ~/.amico/ops/fleet",
slice: "fleet substrate (§9 step 2)",
run: fleetVerb,
Expand Down
52 changes: 52 additions & 0 deletions packages/amico-run/test/fixtures/fleet_authority/projection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"schema_version": 1,
"contract_version": 1,
"publisher": { "identity": "test-publisher", "published_at": "2026-09-13T12:00:00Z" },
"freshness": { "counter": 4, "hub_epoch": "33333333-3333-4333-8333-333333333333" },
"sections": {
"mode": {
"value": "fleet",
"provenance": { "source": "fleet.json", "parsed_from": "role='client' (vocabulary mapping)" }
},
"posture": {
"value": "ok",
"provenance": { "source": "fleet-status.json", "parsed_from": "base default (posture absent = ok)" }
},
"topology": {
"value": {
"role": "client",
"canonical": { "host": "hq-hub-01.example.internal", "port": 4096, "sshAlias": "hq-hub-01" },
"previousBinary": "/home/example/.amico/server/bin/opencode",
"previousPort": 4096
},
"provenance": {
"source": "fleet.json",
"parsed_from": "topology schema v1 fields: role, canonical, previousBinary, previousPort"
}
},
"health": {
"value": {
"collected_at": "2026-08-30T22:10:04Z",
"devices": [
{ "name": "atom-01", "reachable": true, "detail": "this machine" },
{ "name": "ion-02", "reachable": false, "detail": "ssh failed" },
{ "name": "hub-03", "reachable": true, "detail": "ssh ok" }
],
"chat_db": { "sessions": null, "last_session": "null", "server_http": "200" },
"server_guard": { "ok": false, "pid": "", "db_file": "none", "served_sessions": 100, "notes": "server not running;" },
"vault_sync": { "age_minutes": 0, "clean": true },
"repos": [
{ "name": "lab-notebook", "branch": "main", "dirty": 12, "ahead": 0, "behind": 3, "wip_branches": "wip/alpha" }
]
},
"provenance": {
"source": "fleet-status.json",
"parsed_from": "fields: chat_db, collected_at, devices[3], repos[1], server_guard, vault_sync"
}
},
"locks": {
"value": { "rows": [{ "session": "ses_0183f2", "holder": "atom-01", "leased_until": "2026-08-30T23:10:04Z" }] },
"provenance": { "source": "hub lock state", "parsed_from": "rendered from hub lock state: 1 row(s) — a rendering, never the enforcement" }
}
}
}
Loading
Loading