feat(cluster): xcvm_core cluster probe, update pin guard and ADR 0004 (Phase 1)

- `console.php xcvm_core status` reports the loaded extension and its
  cluster API: the version against the range the panel speaks, whether
  the root is initialised (with its fingerprint), the licence binding kid
  and the clock state.
- The installer rolls back an update whose cluster API falls outside
  ClusterCryptoFactory::API_MIN..API_MAX when the installed one was
  inside it, so an update cannot silently strand enrolled nodes.
  Installing an exact pinned version still needs versioned paths in the
  binaries repo.
- ClusterExtensionIntegrationTest runs the panel facade against a real
  test-hooks build, and skips without one or without a throwaway
  XCVM_CONFIG_DIR. It covers init, token issue, the agent opening its
  token, MAIN's session, BOX both ways, per-node binding of epoch records,
  and signing with record classes. It passes against the 2.2.2 build.
- ADR 0004 records the panel's side of the contract:
  - who owns which part of the wire;
  - the canonical request/response form;
  - fail-closed rules;
  - migration renumbering (the plan's 026–032 ship as 028–034);
  - where the settings live;
  - the measured crypto budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Es8UhyPS8MNjuYTagqfGWi
This commit is contained in:
Claude
2026-09-25 10:28:53 +00:00
parent 625273d83c
commit a2ab38197e
4 changed files with 264 additions and 3 deletions
+23
View File
@@ -0,0 +1,23 @@
<?php
use PHPUnit\Framework\TestCase;
use XcVm\Cli\Commands\XcvmCoreCommand;
/**
* An xcvm_core update must not drop the cluster API this panel speaks: the
* installer rolls back a new .so whose API is outside
* ClusterCryptoFactory::API_MIN..API_MAX when the old one was inside it.
*/
final class XcvmCoreClusterPinTest extends TestCase {
public function testKeepsASupportedApi(): void {
$this->assertTrue(XcvmCoreCommand::clusterApiKept(1, 1));
$this->assertFalse(XcvmCoreCommand::clusterApiKept(1, 0), 'an extension without the cluster API');
$this->assertFalse(XcvmCoreCommand::clusterApiKept(1, 2), 'an API this panel does not speak yet');
}
public function testNothingToLoseWhenNothingWorkedBefore(): void {
$this->assertTrue(XcvmCoreCommand::clusterApiKept(0, 0));
$this->assertTrue(XcvmCoreCommand::clusterApiKept(0, 1));
$this->assertTrue(XcvmCoreCommand::clusterApiKept(2, 0));
}
}