feat(fleet): bind advertised capabilities to the enrolled token scope

The claim path already constrained a factory to its enrolled scope, but the
heartbeat trusted self-reported capabilities — so (with enforcement on) a factory
could advertise e.g. engine:codex it was never granted, polluting the engine
picker (GET /fleet/factories) and routing/explain decisions even though a codex
job still couldn't be claimed by it.

Heartbeat now intersects the factory's self-reported capabilities with the token
scope when enforcement is ON: it may report FEWER (an engine temporarily
unavailable) but never MORE than enrolled. Enforcement OFF is unchanged
(self-reported caps pass through verbatim). Covered by new route tests.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-06-01 12:14:09 -07:00
parent a6adaee835
commit 141435fe95
2 changed files with 43 additions and 1 deletions

View File

@ -267,6 +267,40 @@ describe('fleet enrollment + scoped tokens', () => {
expect(JSON.parse(ok.body).claimed).toBe(true);
});
it('enforcement ON: heartbeat advertises only enrolled caps (out-of-scope cap dropped)', async () => {
const app = await buildApp();
const enrolled = await enrollment.enrollFactory({
productId: PID,
factoryId: 'fac_1',
capabilities: ['os:mac', 'engine:devin'],
});
process.env.FLEET_REQUIRE_FACTORY_TOKEN = '1';
const hb = await app.inject({
method: 'POST',
url: '/api/fleet/factories/heartbeat',
headers: { 'x-factory-token': enrolled.token },
// claims engine:codex it was NOT enrolled for, and omits a granted cap
payload: { factoryId: 'fac_1', capabilities: ['engine:devin', 'engine:codex'] },
});
expect(hb.statusCode).toBe(200);
const caps = JSON.parse(hb.body).factory.capabilities as string[];
expect(caps).toContain('engine:devin'); // enrolled + reported → kept
expect(caps).not.toContain('engine:codex'); // not enrolled → dropped
expect(caps).not.toContain('os:mac'); // enrolled but not reported → not invented
});
it('enforcement OFF: heartbeat advertises self-reported caps verbatim', async () => {
const app = await buildApp();
const hb = await app.inject({
method: 'POST',
url: '/api/fleet/factories/heartbeat',
payload: { factoryId: 'fac_1', capabilities: ['engine:codex'] },
});
expect(hb.statusCode).toBe(200);
expect(JSON.parse(hb.body).factory.capabilities).toEqual(['engine:codex']);
});
it('enforcement ON: revoked token → 401 on heartbeat', async () => {
const app = await buildApp();
const enrolled = await enrollment.enrollFactory({

View File

@ -319,11 +319,19 @@ export async function fleetRoutes(app: FastifyInstance) {
factoryId: parsed.data.factoryId,
});
const pid = scope?.productId ?? pidCandidate;
// §12 trust boundary: when enforcement is ON, a factory may advertise only the
// capabilities it was ENROLLED with — intersect its self-reported caps with the
// token scope (it can report fewer, e.g. an engine temporarily unavailable, but
// never MORE). This keeps a factory from advertising e.g. engine:codex it wasn't
// granted, which would otherwise pollute the engine picker and route decisions.
const advertisedCaps = scope
? (parsed.data.capabilities ?? []).filter(c => scope.capabilities.includes(c))
: parsed.data.capabilities;
await coordinator.heartbeat({
productId: pid,
factoryId: parsed.data.factoryId,
descriptor: parsed.data.descriptor,
capabilities: parsed.data.capabilities,
capabilities: advertisedCaps,
health: parsed.data.health,
load: parsed.data.load,
seatLimit: parsed.data.seatLimit,