fix(fleet): additive hook-event merge and gated-composition acceptance reads
Integration adjudication (fred, W-F1): the general arrays-replace merge rule conflicts with the gap-7 base/overlay split — base and lease overlay share the PreToolUse and Stop events, so replace semantics would silently drop the base QA hooks from every gated seat. Ruling: hook event arrays directly under the top-level hooks key concatenate (base first); all other arrays keep replace semantics; null tombstones still delete an event. - mutator-gate acceptance now asserts lease wiring against the COMPOSED gated settings (base + lease-overlay via the launcher's own merge), matching the post-split contract. - fleet subcommand canary gains the intended new 'agent' surface from T3. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
This commit is contained in:
co-authored by
Claude Fable 5
parent
0fdcfa0ff4
commit
92e790ae9d
@@ -6,6 +6,7 @@ import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
|
||||
import { afterEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { launchClaudex, type ClaudexHarnessAdapter } from '../commands/claudex.js';
|
||||
import { deepMergeSettings } from '../commands/fleet-launch-command.js';
|
||||
import {
|
||||
observeAndPromoteReceiptChallenge,
|
||||
requestBrokerReply,
|
||||
@@ -42,6 +43,19 @@ const launcherPath = join(frameworkRoot, 'tools/lease-broker/launch-runtime.py')
|
||||
const revokerPath = join(frameworkRoot, 'tools/lease-broker/revoke-lease.py');
|
||||
const compactionThreatPath = join(repositoryRoot, 'docs/architecture/compaction-revocation.md');
|
||||
const claudeSettingsPath = join(frameworkRoot, 'runtime/claude/settings.json');
|
||||
const claudeLeaseOverlayPath = join(frameworkRoot, 'runtime/claude/lease-overlay.json');
|
||||
|
||||
// The gated seat contract is the COMPOSITION of the ungated base and the
|
||||
// lease overlay (gap-7 split); assertions about lease wiring must read that
|
||||
// composed view, produced by the same merge the launcher uses.
|
||||
async function readGatedClaudeSettings(): Promise<Record<string, unknown>> {
|
||||
const base = JSON.parse(await readFile(claudeSettingsPath, 'utf8')) as Record<string, unknown>;
|
||||
const overlay = JSON.parse(await readFile(claudeLeaseOverlayPath, 'utf8')) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
return deepMergeSettings(base, overlay);
|
||||
}
|
||||
const piExtensionPath = join(frameworkRoot, 'runtime/pi/mosaic-extension.ts');
|
||||
const piLifecyclePath = join(frameworkRoot, 'runtime/pi/lease-lifecycle.ts');
|
||||
const prdyInitPath = join(frameworkRoot, 'tools/prdy/prdy-init.sh');
|
||||
@@ -364,7 +378,7 @@ describe('whole mutator-class lease gate', () => {
|
||||
expect(parserResult.status).toBe(0);
|
||||
expect(JSON.parse(parserResult.stdout)).toMatchObject({ gated: 0, total: 0 });
|
||||
|
||||
const settings = JSON.parse(await readFile(claudeSettingsPath, 'utf8')) as {
|
||||
const settings = (await readGatedClaudeSettings()) as unknown as {
|
||||
hooks: { PreToolUse: Array<{ matcher: string; hooks: Array<{ command: string }> }> };
|
||||
};
|
||||
const allToolsHook = settings.hooks.PreToolUse.find((hook) => hook.matcher === '.*');
|
||||
@@ -660,7 +674,7 @@ describe('whole mutator-class lease gate', () => {
|
||||
});
|
||||
|
||||
test('Claude and Pi compaction observer wiring is complete and fail-closed', async () => {
|
||||
const settings = JSON.parse(await readFile(claudeSettingsPath, 'utf8')) as {
|
||||
const settings = (await readGatedClaudeSettings()) as unknown as {
|
||||
hooks: Record<string, Array<{ matcher?: string; hooks: Array<{ command: string }> }>>;
|
||||
};
|
||||
expect(
|
||||
@@ -956,7 +970,7 @@ raise SystemExit(0 if len(session_id) == 64 and hook_present and observers_prese
|
||||
await promote(socket, sessionId, pending.receipt_challenge!);
|
||||
expect(runRuntimeGate(socket, sessionId, 'claude', 'Bash').status).toBe(0);
|
||||
|
||||
const settings = JSON.parse(await readFile(claudeSettingsPath, 'utf8')) as {
|
||||
const settings = (await readGatedClaudeSettings()) as unknown as {
|
||||
hooks: { PreToolUse: Array<{ matcher?: string; hooks: Array<{ command: string }> }> };
|
||||
};
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user