Skip to content
Closed
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
12 changes: 9 additions & 3 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,21 @@ The installation owner can persist one update target and reconcile it with the s
transaction:

```sh
maka runtime-host service update-policy --target latest \
maka runtime-host service update-policy --target latest --check-interval 4h \
--expected-service-id <service-id> \
--expected-root-path <state-root> \
--expected-root-id <root-id>
maka runtime-host service reconcile-update --json
```

Use `update-policy --target manual` to disable automatic reconciliation. Reconciliation is a
bounded one-shot command: it never interrupts active work and does not install a scheduler.
Use `update-policy --target manual` to disable automatic reconciliation. Automatic policies accept
an integer `--check-interval` from `1h` through `168h` and default to `6h`. A Maka-managed Linux or
macOS deployment installs an OS-owned hourly tick at an installation-specific minute. The policy
decides whether that tick is due before any registry request; an explicit `reconcile-update` always
checks immediately. The tick remains installed in manual mode, where it returns without network
discovery or mutation. It never interrupts active work. Service repair verifies the schedule,
service logs include its output, and uninstall removes it before deleting the managed deployment.
Services launched from a separate persistent global CLI do not receive this schedule.

## Uninstall

Expand Down
11 changes: 8 additions & 3 deletions packages/cli/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,20 @@ selector 传给 `service update --target`。该路径会先校验 archive 与解
Installation owner 可以持久化一个更新目标,并通过同一套已验证事务执行 reconciliation:

```sh
maka runtime-host service update-policy --target latest \
maka runtime-host service update-policy --target latest --check-interval 4h \
--expected-service-id <service-id> \
--expected-root-path <state-root> \
--expected-root-id <root-id>
maka runtime-host service reconcile-update --json
```

使用 `update-policy --target manual` 关闭自动 reconciliation。Reconciliation 是有界的单次命令:
它不会中断 active work,也不会安装 scheduler。
使用 `update-policy --target manual` 关闭自动 reconciliation。自动策略接受 `1h` 至 `168h` 的
整数 `--check-interval`,默认 `6h`。Maka 托管的 Linux 或 macOS deployment 会在每个 installation
专属的 minute 安装由操作系统持有的 hourly tick;在发起任何 registry 请求前,由 policy 判断本次
tick 是否到期。显式执行 `reconcile-update` 始终立即检查。manual 模式仍保留 tick,但它会直接
返回,不执行网络 discovery 或 mutation,也不会中断 active work。Service repair 会校验
schedule,service logs 会包含其输出,uninstall 会先移除 schedule,再删除 managed deployment。
由独立持久全局 CLI 启动的 service 不会安装该 schedule。

## 卸载

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ describe('managed Runtime Host service', () => {
} as const;
const updatePolicy = {
schemaVersion: 1 as const,
policy: { kind: 'channel' as const, channel: 'latest' as const },
policy: { kind: 'channel' as const, channel: 'latest' as const, checkIntervalHours: 6 },
target: expectedTarget,
};
await writeRuntimeHostManagedUpdatePolicy(deploymentRoot, updatePolicy);
Expand Down
152 changes: 145 additions & 7 deletions packages/cli/src/__tests__/runtime-host-update-reconciliation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ describe('managed Runtime Host update reconciliation', () => {
'update-policy',
'--target',
'next',
'--check-interval',
'4h',
'--expected-service-id',
TARGET.serviceId,
'--expected-root-path',
Expand All @@ -75,18 +77,59 @@ describe('managed Runtime Host update reconciliation', () => {
{
kind: 'runtime-host-service-update-policy',
json: false,
policy: { kind: 'channel', channel: 'next' },
policy: { kind: 'channel', channel: 'next', checkIntervalHours: 4 },
expectedTarget: TARGET,
},
);
assert.deepEqual(parseRuntimeHostCommand(['service', 'reconcile-update', '--json']), {
kind: 'runtime-host-service-reconcile-update',
json: true,
});
assert.deepEqual(
parseRuntimeHostCommand(['service', 'reconcile-update', '--scheduled', '--json']),
{
kind: 'runtime-host-service-reconcile-update',
json: true,
scheduled: true,
},
);
assert.equal(
parseRuntimeHostCommand(['service', 'update-policy', '--target', 'latest']).kind,
'error',
);
assert.equal(
parseRuntimeHostCommand([
'service',
'update-policy',
'--target',
'manual',
'--check-interval',
'4h',
]).kind,
'error',
);
assert.equal(
parseRuntimeHostCommand([
'service',
'update-policy',
'--target',
'latest',
'--check-interval',
'0h',
]).kind,
'error',
);
assert.equal(
parseRuntimeHostCommand([
'service',
'update-policy',
'--target',
'latest',
'--check-interval',
'169h',
]).kind,
'error',
);
});

it('persists an automatic policy against the canonical managed target and removes manual state', async (t) => {
Expand All @@ -105,7 +148,7 @@ describe('managed Runtime Host update reconciliation', () => {
await runManagedRuntimeHostUpdatePolicyCli(
{
...common,
policy: { kind: 'fixed', version: '2.0.0' },
policy: { kind: 'fixed', version: '2.0.0', checkIntervalHours: 6 },
expectedTarget: TARGET,
},
{
Expand All @@ -121,7 +164,7 @@ describe('managed Runtime Host update reconciliation', () => {
);
assert.deepEqual(await readRuntimeHostManagedUpdatePolicy(deploymentRoot), {
schemaVersion: 1,
policy: { kind: 'fixed', version: '2.0.0' },
policy: { kind: 'fixed', version: '2.0.0', checkIntervalHours: 6 },
target: { ...TARGET, rootPath: '/srv/maka' },
});
assert.equal(JSON.parse(output).updatePolicy.policy.kind, 'fixed');
Expand Down Expand Up @@ -163,12 +206,32 @@ describe('managed Runtime Host update reconciliation', () => {
);
});

it('defaults a pre-cadence automatic policy to a six-hour interval', async (t) => {
const deploymentRoot = await mkdtemp(join(tmpdir(), 'maka-update-policy-legacy-'));
t.after(() => rm(deploymentRoot, { recursive: true, force: true }));
await writeFile(
resolveRuntimeHostManagedUpdatePolicyPath(deploymentRoot),
JSON.stringify({
schemaVersion: 1,
policy: { kind: 'channel', channel: 'latest' },
target: TARGET,
}),
'utf8',
);

assert.deepEqual(await readRuntimeHostManagedUpdatePolicy(deploymentRoot), {
schemaVersion: 1,
policy: { kind: 'channel', channel: 'latest', checkIntervalHours: 6 },
target: TARGET,
});
});

it('distinguishes an uncertain policy commit and makes an absent-policy retry durable', async (t) => {
const deploymentRoot = await mkdtemp(join(tmpdir(), 'maka-update-policy-commit-'));
t.after(() => rm(deploymentRoot, { recursive: true, force: true }));
const record = {
schemaVersion: 1 as const,
policy: { kind: 'fixed' as const, version: '2.0.0' },
policy: { kind: 'fixed' as const, version: '2.0.0', checkIntervalHours: 6 },
target: TARGET,
};
const failSync = async () => {
Expand Down Expand Up @@ -199,13 +262,88 @@ describe('managed Runtime Host update reconciliation', () => {
assert.equal(syncs, 1);
});

it('checks an automatic target only when its scheduled interval is due', async (t) => {
const clientDataRoot = await mkdtemp(join(tmpdir(), 'maka-update-schedule-'));
t.after(() => rm(clientDataRoot, { recursive: true, force: true }));
const deploymentRoot = join(clientDataRoot, 'managed');
await writeRuntimeHostManagedUpdatePolicy(deploymentRoot, {
schemaVersion: 1,
policy: { kind: 'channel', channel: 'latest', checkIntervalHours: 4 },
target: TARGET,
});
let output = '';
assert.equal(
await runManagedRuntimeHostUpdateReconcileCli(
{
json: true,
framed: false,
scheduled: true,
clientDataRoot,
defaultRootPath: '/workspace',
},
{
now: () => 2 * 60 * 60 * 1_000,
manage: async () => managedStatus(deploymentRoot),
createBackend: () => unusedBackend(),
resolveSelection: async () => assert.fail('a not-due tick must not query the registry'),
writeOutput: (value) => {
output += value;
},
},
),
0,
);
assert.deepEqual(JSON.parse(output).reconciliation, {
kind: 'not_due',
checkIntervalHours: 4,
});

let resolved = false;
await runManagedRuntimeHostUpdateReconcileCli(
{
json: true,
framed: false,
scheduled: true,
clientDataRoot,
defaultRootPath: '/workspace',
},
{
now: () => 3 * 60 * 60 * 1_000,
manage: async () => managedStatus(deploymentRoot),
createBackend: () => unusedBackend(),
resolveSelection: async (options) => {
resolved = true;
return {
selector: options.selector,
candidate: { version: '1.0.0', integrity: INTEGRITY, compatibility: 1 },
outcome: { kind: 'unattended_update', compatibility: 1 },
currentCliPath: '/managed/current/cli.js',
service: SERVICE,
};
},
applySelection: async (_options, _selection, _overrides, emit) => {
emit?.({
schemaVersion: 1,
kind: 'result',
action: 'update',
service: SERVICE,
update: { kind: 'already_current', version: '1.0.0' },
});
return 0;
},
writeOutput: () => undefined,
},
);
assert.equal(resolved, true);
});

it('resolves one policy snapshot and delegates an admitted exact target to the update transaction', async (t) => {
const clientDataRoot = await mkdtemp(join(tmpdir(), 'maka-update-reconcile-'));
t.after(() => rm(clientDataRoot, { recursive: true, force: true }));
const deploymentRoot = join(clientDataRoot, 'managed');
await writeRuntimeHostManagedUpdatePolicy(deploymentRoot, {
schemaVersion: 1,
policy: { kind: 'fixed', version: '2.0.0' },
policy: { kind: 'fixed', version: '2.0.0', checkIntervalHours: 6 },
target: TARGET,
});
let output = '';
Expand Down Expand Up @@ -278,7 +416,7 @@ describe('managed Runtime Host update reconciliation', () => {
const deploymentRoot = join(clientDataRoot, 'managed');
await writeRuntimeHostManagedUpdatePolicy(deploymentRoot, {
schemaVersion: 1,
policy: { kind: 'channel', channel: 'latest' },
policy: { kind: 'channel', channel: 'latest', checkIntervalHours: 6 },
target: TARGET,
});
let output = '';
Expand Down Expand Up @@ -344,7 +482,7 @@ describe('managed Runtime Host update reconciliation', () => {

await writeRuntimeHostManagedUpdatePolicy(deploymentRoot, {
schemaVersion: 1,
policy: { kind: 'channel', channel: 'latest' },
policy: { kind: 'channel', channel: 'latest', checkIntervalHours: 6 },
target: TARGET,
});
let output = '';
Expand Down
Loading