192 lines
7.2 KiB
JavaScript
192 lines
7.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import test from "node:test";
|
|
import {
|
|
AUTHENTICATE_ACTIVE_DEVICE_SQL,
|
|
DEACTIVATE_OWNED_HOST_SQL,
|
|
FREE_BETA_ACCESS_BOUNDARY,
|
|
REVOKE_ACTIVE_DEVICE_CREDENTIALS_SQL,
|
|
} from "../db/access-boundary.ts";
|
|
import { CLAIM_HOST_SQL } from "../db/pairing.ts";
|
|
import { REQUIRED_PUBLIC_BETA_P0_KEYS } from "../db/launch-gates.ts";
|
|
|
|
function createClaimCapacityDatabase() {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE beta_programs (
|
|
id TEXT PRIMARY KEY, state TEXT NOT NULL, capacity_slots INTEGER,
|
|
starts_at TEXT NOT NULL, ends_at TEXT, created_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE entitlement_grants (
|
|
account_id TEXT NOT NULL, state TEXT NOT NULL, capacity_slots INTEGER,
|
|
starts_at TEXT NOT NULL, ends_at TEXT, revoked_at TEXT
|
|
);
|
|
CREATE TABLE launch_gates (
|
|
key TEXT PRIMARY KEY, priority TEXT NOT NULL, status TEXT NOT NULL,
|
|
owner TEXT, notes TEXT NOT NULL, evidence_url TEXT
|
|
);
|
|
CREATE TABLE pairing_requests (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, requested_name TEXT NOT NULL,
|
|
os TEXT NOT NULL, code_hash TEXT NOT NULL, status TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL, locked_at TEXT, failed_attempts INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
CREATE TABLE hosts (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, name TEXT NOT NULL,
|
|
os TEXT NOT NULL, lifecycle TEXT NOT NULL, slot_state TEXT NOT NULL,
|
|
connection_state TEXT NOT NULL, daemon_version TEXT,
|
|
ed25519_public TEXT, x25519_public TEXT,
|
|
identity_fingerprint TEXT UNIQUE, claim_request_id TEXT, claimed_at TEXT,
|
|
created_at TEXT NOT NULL, deactivated_at TEXT
|
|
);
|
|
`);
|
|
const gate = db.prepare(
|
|
`INSERT INTO launch_gates
|
|
(key, priority, status, owner, notes, evidence_url)
|
|
VALUES (?, 'P0', 'passed', 'test', 'verified', 'https://evidence.example.test/p0')`,
|
|
);
|
|
for (const key of REQUIRED_PUBLIC_BETA_P0_KEYS) gate.run(key);
|
|
return db;
|
|
}
|
|
|
|
function claimHost(db, digit, accountId, now) {
|
|
const pairingId = `pair_${digit.repeat(32)}`;
|
|
db.prepare(
|
|
`INSERT INTO pairing_requests
|
|
(id, account_id, requested_name, os, code_hash, status, expires_at)
|
|
VALUES (?, ?, ?, 'linux', ?, 'waiting', '2026-08-12T13:00:00.000Z')`,
|
|
).run(pairingId, accountId, `Host ${digit}`, `hash-${digit}`);
|
|
return Number(db.prepare(CLAIM_HOST_SQL).run(
|
|
`host_${digit.repeat(32)}`,
|
|
`ed-${digit}`,
|
|
`x-${digit}`,
|
|
`fingerprint-${digit}`,
|
|
now,
|
|
now,
|
|
pairingId,
|
|
`hash-${digit}`,
|
|
now,
|
|
"linux",
|
|
now,
|
|
now,
|
|
now,
|
|
now,
|
|
null,
|
|
).changes);
|
|
}
|
|
|
|
test("documents the free-beta expiry operation boundary", () => {
|
|
assert.deepEqual(
|
|
FREE_BETA_ACCESS_BOUNDARY.map(({ operation, requiresCurrentEntitlement }) => [operation, requiresCurrentEntitlement]),
|
|
[
|
|
["create_pairing", true],
|
|
["claim_pairing", true],
|
|
["cancel_pairing", false],
|
|
["connect_device", false],
|
|
["revoke_host", false],
|
|
],
|
|
);
|
|
});
|
|
|
|
test("keeps an existing active device usable after entitlement expiry until explicit revocation", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE hosts (
|
|
id TEXT PRIMARY KEY,
|
|
account_id TEXT NOT NULL,
|
|
lifecycle TEXT NOT NULL,
|
|
slot_state TEXT NOT NULL,
|
|
connection_state TEXT NOT NULL,
|
|
deactivated_at TEXT
|
|
);
|
|
CREATE TABLE device_credentials (
|
|
id TEXT PRIMARY KEY,
|
|
host_id TEXT NOT NULL,
|
|
token_hash TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
expires_at TEXT,
|
|
last_used_at TEXT,
|
|
revoked_at TEXT
|
|
);
|
|
CREATE TABLE entitlement_grants (
|
|
id TEXT PRIMARY KEY,
|
|
account_id TEXT NOT NULL,
|
|
state TEXT NOT NULL,
|
|
ends_at TEXT,
|
|
revoked_at TEXT
|
|
);
|
|
INSERT INTO hosts VALUES ('host_a', 'account_a', 'active', 'active', 'online', NULL);
|
|
INSERT INTO device_credentials VALUES ('credential_a', 'host_a', 'token-hash', 'active', NULL, NULL, NULL);
|
|
INSERT INTO entitlement_grants VALUES ('grant_expired', 'account_a', 'active', '2026-08-11T00:00:00.000Z', NULL);
|
|
`);
|
|
const now = "2026-08-12T12:00:00.000Z";
|
|
assert.equal(
|
|
Number(db.prepare(AUTHENTICATE_ACTIVE_DEVICE_SQL).run(now, "host_a", "token-hash").changes),
|
|
1,
|
|
);
|
|
assert.equal(db.prepare("SELECT last_used_at FROM device_credentials WHERE id = 'credential_a'").get().last_used_at, now);
|
|
|
|
assert.equal(Number(db.prepare(REVOKE_ACTIVE_DEVICE_CREDENTIALS_SQL).run(now, "host_a").changes), 1);
|
|
assert.equal(Number(db.prepare(DEACTIVATE_OWNED_HOST_SQL).run(now, "host_a", "account_a").changes), 1);
|
|
assert.equal(
|
|
Number(db.prepare(AUTHENTICATE_ACTIVE_DEVICE_SQL).run(now, "host_a", "token-hash").changes),
|
|
0,
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("rechecks current per-account capacity atomically when a daemon claims a waiting pairing", () => {
|
|
const db = createClaimCapacityDatabase();
|
|
const now = "2026-08-12T12:00:00.000Z";
|
|
db.prepare(
|
|
`INSERT INTO beta_programs
|
|
(id, state, capacity_slots, starts_at, ends_at, created_at)
|
|
VALUES ('beta', 'active', 2, ?, NULL, ?)`,
|
|
).run(now, now);
|
|
|
|
assert.equal(claimHost(db, "1", "account_public", now), 1);
|
|
db.prepare("UPDATE beta_programs SET capacity_slots = 1 WHERE id = 'beta'").run();
|
|
assert.equal(claimHost(db, "2", "account_public", now), 0);
|
|
assert.equal(
|
|
db.prepare(
|
|
"SELECT COUNT(*) AS count FROM hosts WHERE account_id = 'account_public' AND lifecycle = 'active'",
|
|
).get().count,
|
|
1,
|
|
);
|
|
|
|
db.prepare(
|
|
`INSERT INTO entitlement_grants
|
|
(account_id, state, capacity_slots, starts_at, ends_at, revoked_at)
|
|
VALUES ('account_public', 'active', 1, ?, NULL, NULL)`,
|
|
).run(now);
|
|
assert.equal(claimHost(db, "3", "account_public", now), 1);
|
|
assert.equal(claimHost(db, "4", "account_public", now), 0);
|
|
assert.equal(
|
|
db.prepare(
|
|
"SELECT COUNT(*) AS count FROM hosts WHERE account_id = 'account_public' AND lifecycle = 'active'",
|
|
).get().count,
|
|
2,
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("keeps entitlement checks on new access while security exits stay independent", async () => {
|
|
const [pairingSql, repository, relayControl, stateModel, contract] = await Promise.all([
|
|
readFile(new URL("../db/pairing.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../db/relay-control-plane.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../docs/state-model.md", import.meta.url), "utf8"),
|
|
readFile(new URL("../docs/commercial-contract.md", import.meta.url), "utf8"),
|
|
]);
|
|
assert.match(pairingSql, /CURRENT_PAIRING_ACCESS/);
|
|
assert.match(pairingSql, /HOST_CLAIM_CAPACITY/);
|
|
assert.match(pairingSql, /PUBLIC_BETA_GATE_READY_SQL/);
|
|
assert.match(relayControl, /authorizeDeviceForRelay/);
|
|
assert.match(repository, /REVOKE_ACTIVE_DEVICE_CREDENTIALS_SQL/);
|
|
assert.match(repository, /DEACTIVATE_OWNED_HOST_SQL/);
|
|
assert.match(stateModel, /主机撤销.*撤销所有设备凭证.*释放席位/);
|
|
assert.match(stateModel, /第 N\+1 台返回 `device_capacity_exceeded`/);
|
|
assert.match(contract, /设备离线不释放席位/);
|
|
assert.match(contract, /明确撤销才释放/);
|
|
});
|