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:
parent
a6adaee835
commit
141435fe95
@ -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({
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user