feat(ql3): expose bounded worker session observation

This commit is contained in:
whyour
2026-08-20 12:55:44 +08:00
parent 6265e31dce
commit 4a4fa85f51
24 changed files with 1437 additions and 28 deletions
@@ -3,6 +3,7 @@ import {
PostgresApprovalRequestRepository,
PostgresProjectPolicyRepository,
PostgresWorkerCredentialManagementPlanRepository,
PostgresWorkerSessionObservationRepository,
} from '@qinglong/cluster-postgres/worker-credential-manager';
import type { PostgresPool } from '@qinglong/runtime-core';
import {
@@ -20,6 +21,10 @@ import {
type SecuritySubject,
} from '@qinglong/runtime-core/security';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import type {
WorkerSessionInspection,
WorkerSessionObservationPage,
} from '@qinglong/runtime-core/worker-session-observation';
import {
InvalidWorkerCredentialManagementPlanError,
MAX_WORKER_CREDENTIAL_MANAGEMENT_PLAN_LIFETIME_MS,
@@ -93,7 +98,8 @@ export type WorkerCredentialManagementQuotaOperation =
| 'worker-credential.plan'
| 'worker-credential.propose'
| 'worker-credential.decide'
| 'worker-credential.inspect';
| 'worker-credential.inspect'
| 'worker-session.observe';
export interface WorkerCredentialManagementQuotaPort {
consume(
@@ -166,6 +172,20 @@ export interface InspectClusterWorkerCredentialResult {
readonly stale: boolean;
}
export interface InspectClusterWorkerSessionRequest {
readonly authorityProjectId: string;
readonly workerId: string;
readonly inspectionId: string;
readonly principal: SecurityPrincipal;
}
export interface ListClusterWorkerSessionsRequest {
readonly authorityProjectId: string;
readonly afterWorkerId: string | null;
readonly inspectionId: string;
readonly principal: SecurityPrincipal;
}
export interface ClusterWorkerCredentialManagementService {
plan(
request: PlanClusterWorkerCredentialRequest,
@@ -179,6 +199,12 @@ export interface ClusterWorkerCredentialManagementService {
inspectAuthorized(
request: InspectClusterWorkerCredentialRequest,
): Promise<Readonly<InspectClusterWorkerCredentialResult>>;
inspectSession(
request: InspectClusterWorkerSessionRequest,
): Promise<Readonly<WorkerSessionInspection>>;
listSessions(
request: ListClusterWorkerSessionsRequest,
): Promise<Readonly<WorkerSessionObservationPage>>;
}
export interface ClusterWorkerCredentialManagementOptions {
@@ -324,6 +350,7 @@ export function createClusterWorkerCredentialManagementService(
const plans = new PostgresWorkerCredentialManagementPlanRepository(
options.pool,
);
const sessions = new PostgresWorkerSessionObservationRepository(options.pool);
const approvals = new PostgresApprovalRequestRepository(options.pool);
const policy = new ProjectPolicyEngine(
new PostgresProjectPolicyRepository(options.pool),
@@ -796,5 +823,74 @@ export function createClusterWorkerCredentialManagementService(
observedAtMs > plan.expiresAtMs,
});
},
async inspectSession(request: InspectClusterWorkerSessionRequest) {
exact(
request,
['authorityProjectId', 'inspectionId', 'principal', 'workerId'],
'Session inspection request',
);
const projectId = identifier(
request.authorityProjectId,
'authorityProjectId',
);
const inspectionId = identifier(request.inspectionId, 'inspectionId');
const workerId = identifier(request.workerId, 'workerId');
const authorization = await authorize(
request.principal,
projectId,
'worker.manage',
currentTime(now),
);
await consumeQuota(
projectId,
authorization.principal,
'worker-session.observe',
`session-inspect:${inspectionId}`,
);
try {
return await sessions.inspect(workerId);
} catch (error) {
throw new WorkerCredentialManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
},
async listSessions(request: ListClusterWorkerSessionsRequest) {
exact(
request,
['afterWorkerId', 'authorityProjectId', 'inspectionId', 'principal'],
'Session list request',
);
const projectId = identifier(
request.authorityProjectId,
'authorityProjectId',
);
const inspectionId = identifier(request.inspectionId, 'inspectionId');
const afterWorkerId =
request.afterWorkerId === null
? null
: identifier(request.afterWorkerId, 'afterWorkerId');
const authorization = await authorize(
request.principal,
projectId,
'worker.manage',
currentTime(now),
);
await consumeQuota(
projectId,
authorization.principal,
'worker-session.observe',
`session-list:${inspectionId}`,
);
try {
return await sessions.list(afterWorkerId);
} catch (error) {
throw new WorkerCredentialManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
},
});
}
@@ -74,11 +74,33 @@ export interface InspectClusterWorkerCredentialCommand {
};
}
export interface InspectClusterWorkerSessionCommand {
readonly schemaVersion: 1;
readonly operation: 'worker-session.inspect';
readonly request: {
readonly authorityProjectId: string;
readonly workerId: string;
readonly inspectionId: string;
};
}
export interface ListClusterWorkerSessionsCommand {
readonly schemaVersion: 1;
readonly operation: 'worker-session.list';
readonly request: {
readonly authorityProjectId: string;
readonly afterWorkerId: string | null;
readonly inspectionId: string;
};
}
export type ClusterWorkerCredentialManagementCommand =
| PlanClusterWorkerCredentialCommand
| ProposeClusterWorkerCredentialCommand
| DecideClusterWorkerCredentialCommand
| InspectClusterWorkerCredentialCommand;
| InspectClusterWorkerCredentialCommand
| InspectClusterWorkerSessionCommand
| ListClusterWorkerSessionsCommand;
type PlanSummary = ReturnType<typeof planSummary>;
type ApprovalSummary = ReturnType<typeof approvalSummary>;
@@ -109,6 +131,23 @@ export type ClusterWorkerCredentialManagementTransportResult =
plan: PlanSummary | null;
approval: ApprovalSummary | null;
stale: boolean;
}>
| Readonly<{
schemaVersion: 1;
operation: 'worker-session.inspect';
observedAtMs: number;
worker: Awaited<
ReturnType<ClusterWorkerCredentialManagementService['inspectSession']>
>['worker'];
}>
| Readonly<{
schemaVersion: 1;
operation: 'worker-session.list';
observedAtMs: number;
workers: Awaited<
ReturnType<ClusterWorkerCredentialManagementService['listSessions']>
>['workers'];
nextCursor: string | null;
}>;
export interface ClusterWorkerCredentialManagementTransport {
@@ -254,6 +293,20 @@ export function normalizeClusterWorkerCredentialManagementCommand(
'inspection request',
);
break;
case 'worker-session.inspect':
exactObject(
value.request,
['authorityProjectId', 'inspectionId', 'workerId'],
'Session inspection request',
);
break;
case 'worker-session.list':
exactObject(
value.request,
['afterWorkerId', 'authorityProjectId', 'inspectionId'],
'Session list request',
);
break;
default:
throw new ClusterWorkerCredentialManagementTransportRequestError(
'operation is not publicly available',
@@ -317,6 +370,8 @@ export function createClusterWorkerCredentialManagementTransport(
typeof options.service.propose !== 'function' ||
typeof options.service.decide !== 'function' ||
typeof options.service.inspectAuthorized !== 'function' ||
typeof options.service.inspectSession !== 'function' ||
typeof options.service.listSessions !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new ClusterWorkerCredentialManagementTransportConfigurationError(
@@ -420,6 +475,31 @@ export function createClusterWorkerCredentialManagementTransport(
stale: result.stale,
});
}
case 'worker-session.inspect': {
const result = await options.service.inspectSession({
...command.request,
principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
observedAtMs: result.observedAtMs,
worker: result.worker,
});
}
case 'worker-session.list': {
const result = await options.service.listSessions({
...command.request,
principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
observedAtMs: result.observedAtMs,
workers: result.workers,
nextCursor: result.nextCursor,
});
}
}
},
});
@@ -57,6 +57,23 @@ function boundedScalar(value: unknown): void {
}
}
function boundedText(value: unknown, nullable = false): void {
if (value === null && nullable) return;
if (
typeof value !== 'string' ||
value.length === 0 ||
value.length > 2_048 ||
CONTROL_PATTERN.test(value)
) {
invalid();
}
}
function nonNegativeInteger(value: unknown, nullable = false): void {
if (value === null && nullable) return;
if (!Number.isSafeInteger(value) || (value as number) < 0) invalid();
}
function subject(value: unknown): void {
const record = exactRecord(value, ['type', 'id']);
if (record.type !== 'user') invalid();
@@ -131,6 +148,106 @@ function approval(value: unknown): void {
}
}
const WORKER_OBSERVATION_KEYS = [
'architecture',
'availableSlots',
'compatibility',
'generation',
'lastHeartbeatAtMs',
'leaseExpiresAtMs',
'lifecycle',
'maxConcurrentRuns',
'observedAtMs',
'operatingSystem',
'protocolVersion',
'registeredAtMs',
'sessionId',
'sessionVersion',
'supportTier',
'updatedAtMs',
'workerId',
] as const;
function workerObservation(value: unknown, detailed: boolean): void {
const record = exactRecord(
value,
detailed
? [...WORKER_OBSERVATION_KEYS, 'declaredCapacity', 'runtimes']
: WORKER_OBSERVATION_KEYS,
);
boundedText(record.workerId);
boundedText(record.sessionId);
boundedText(record.protocolVersion);
boundedText(record.operatingSystem, true);
for (const key of [
'generation',
'sessionVersion',
'maxConcurrentRuns',
'availableSlots',
'registeredAtMs',
'lastHeartbeatAtMs',
'leaseExpiresAtMs',
'updatedAtMs',
'observedAtMs',
] as const) {
nonNegativeInteger(record[key]);
}
if (
!['online', 'draining', 'offline', 'lease_expired'].includes(
String(record.lifecycle),
) ||
![
'default_placement',
'explicit_placement_required',
'protocol_incompatible',
].includes(String(record.compatibility)) ||
!['tier1', 'candidate', 'experimental', 'legacy-only'].includes(
String(record.supportTier),
) ||
!['amd64', 'arm64', 'ppc64le', 's390x', 'arm/v7', 'arm/v6', '386'].includes(
String(record.architecture),
)
) {
invalid();
}
if (
(record.generation as number) < 1 ||
(record.maxConcurrentRuns as number) < 1 ||
(record.availableSlots as number) > (record.maxConcurrentRuns as number) ||
(record.lastHeartbeatAtMs as number) < (record.registeredAtMs as number) ||
(record.updatedAtMs as number) < (record.lastHeartbeatAtMs as number) ||
(record.observedAtMs as number) < (record.updatedAtMs as number) ||
(record.lifecycle !== 'offline' &&
(record.leaseExpiresAtMs as number) <=
(record.lastHeartbeatAtMs as number)) ||
(['online', 'draining'].includes(String(record.lifecycle)) &&
(record.leaseExpiresAtMs as number) <= (record.observedAtMs as number)) ||
(record.lifecycle === 'lease_expired' &&
(record.leaseExpiresAtMs as number) > (record.observedAtMs as number)) ||
(['draining', 'offline'].includes(String(record.lifecycle)) &&
(record.availableSlots as number) !== 0)
) {
invalid();
}
if (!detailed) return;
if (!Array.isArray(record.runtimes) || record.runtimes.length > 32) invalid();
for (const runtime of record.runtimes) {
const item = exactRecord(runtime, ['name', 'version']);
boundedText(item.name);
boundedText(item.version);
}
const capacity = exactRecord(record.declaredCapacity, [
'cpuCores',
'diskBytes',
'gpuCount',
'memoryBytes',
]);
nonNegativeInteger(capacity.cpuCores, true);
nonNegativeInteger(capacity.memoryBytes, true);
nonNegativeInteger(capacity.diskBytes, true);
nonNegativeInteger(capacity.gpuCount);
}
export function validateClusterWorkerCredentialManagementClientResult(
value: unknown,
command: Readonly<ClusterWorkerCredentialManagementCommand>,
@@ -143,7 +260,17 @@ export function validateClusterWorkerCredentialManagementClientResult(
? ['schemaVersion', 'operation', 'approvalStatus', 'plan', 'approval']
: operation === 'worker-credential.decide'
? ['schemaVersion', 'operation', 'status', 'approval']
: ['schemaVersion', 'operation', 'plan', 'approval', 'stale'];
: operation === 'worker-credential.inspect'
? ['schemaVersion', 'operation', 'plan', 'approval', 'stale']
: operation === 'worker-session.inspect'
? ['schemaVersion', 'operation', 'observedAtMs', 'worker']
: [
'schemaVersion',
'operation',
'observedAtMs',
'workers',
'nextCursor',
];
const record = exactRecord(value, keys);
if (record.schemaVersion !== 1 || record.operation !== operation) invalid();
if (operation === 'worker-credential.plan') {
@@ -157,10 +284,45 @@ export function validateClusterWorkerCredentialManagementClientResult(
} else if (operation === 'worker-credential.decide') {
if (!['decided', 'existing'].includes(String(record.status))) invalid();
approval(record.approval);
} else {
} else if (operation === 'worker-credential.inspect') {
if (typeof record.stale !== 'boolean') invalid();
if (record.plan !== null) plan(record.plan);
if (record.approval !== null) approval(record.approval);
} else if (operation === 'worker-session.inspect') {
nonNegativeInteger(record.observedAtMs);
if (record.worker !== null) {
workerObservation(record.worker, true);
if (
(record.worker as Record<string, unknown>).observedAtMs !==
record.observedAtMs
) {
invalid();
}
}
} else {
nonNegativeInteger(record.observedAtMs);
boundedText(record.nextCursor, true);
if (!Array.isArray(record.workers) || record.workers.length > 16) invalid();
let previous = '';
for (const worker of record.workers) {
workerObservation(worker, false);
const item = worker as Record<string, unknown>;
if (
item.observedAtMs !== record.observedAtMs ||
typeof item.workerId !== 'string' ||
item.workerId <= previous
) {
invalid();
}
previous = item.workerId;
}
if (
(record.nextCursor !== null &&
(record.workers.length === 0 || record.nextCursor !== previous)) ||
(record.nextCursor === null && record.workers.length > 16)
) {
invalid();
}
}
return Object.freeze(
record as unknown as ClusterWorkerCredentialManagementTransportResult,
@@ -4,6 +4,9 @@ const { test } = require('node:test');
const {
createClusterWorkerCredentialManagementService,
} = require('@qinglong/cluster-admin/worker-credential-management');
const {
canonicalRemoteWorkerCapabilities,
} = require('@qinglong/runtime-core/remote-dispatch');
const REQUESTER = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'operator-a' }),
@@ -20,6 +23,32 @@ const REVIEWER = Object.freeze({
assurance: 'hardware',
});
function sessionRow() {
const capabilities = canonicalRemoteWorkerCapabilities({
architecture: 'arm64',
executors: ['remote-worker'],
protocolVersion: '1.0.0',
supportTier: 'tier1',
runtimes: [{ name: 'node', version: '24.18.0' }],
});
return {
observedAtMs: 2_000,
workerId: 'worker-a',
sessionId: '018f0f5d-7b6a-7a11-8f4d-2f7b4f477001',
generation: 2,
status: 'online',
version: 5,
capabilitiesJson: capabilities.json,
capabilitiesHash: capabilities.hash,
maxConcurrentRuns: 2,
availableSlots: 1,
registeredAtMs: 1_000,
lastHeartbeatAtMs: 1_900,
leaseExpiresAtMs: 3_000,
updatedAtMs: 1_900,
};
}
function approvalFixture() {
const plans = new Map();
const approvals = new Map();
@@ -76,6 +105,9 @@ function approvalFixture() {
const stored = audits.get(values[0]);
return { rows: stored ? [stored] : [], rowCount: stored ? 1 : 0 };
}
if (text.includes('FROM "ql3"."worker_sessions"')) {
return { rows: [sessionRow()], rowCount: 1 };
}
if (text.includes('"ql3"."lock_approval_policy_fence"')) {
return { rows: [{ matches: true }], rowCount: 1 };
}
@@ -277,3 +309,50 @@ test('authorizes and consumes durable quota before reading management state', as
assert.equal(quotaCalls[1].operation, 'worker-credential.propose');
assert.equal(quotaCalls[1].planReads, readsBefore);
});
test('observes one Session and one bounded page only after worker.manage quota', async () => {
const state = approvalFixture();
const quotaCalls = [];
const service = createClusterWorkerCredentialManagementService({
pool: state.pool,
now: () => 2_000,
quota: {
async consume(command) {
quotaCalls.push(command);
return { admitted: true, retryAfterMs: null };
},
},
});
const inspection = await service.inspectSession({
authorityProjectId: 'cluster-authority',
workerId: 'worker-a',
inspectionId: 'worker-session-inspection-a',
principal: REQUESTER,
});
const page = await service.listSessions({
authorityProjectId: 'cluster-authority',
afterWorkerId: null,
inspectionId: 'worker-session-list-a',
principal: REQUESTER,
});
assert.equal(inspection.worker.workerId, 'worker-a');
assert.equal(inspection.worker.compatibility, 'default_placement');
assert.equal(page.workers.length, 1);
assert.equal(page.workers[0].workerId, 'worker-a');
assert.deepEqual(
quotaCalls.map(({ operation, idempotencyKey }) => ({
operation,
idempotencyKey,
})),
[
{
operation: 'worker-session.observe',
idempotencyKey: 'session-inspect:worker-session-inspection-a',
},
{
operation: 'worker-session.observe',
idempotencyKey: 'session-list:worker-session-list-a',
},
],
);
});
@@ -82,6 +82,39 @@ const approval = Object.freeze({
actionDigest: 'd'.repeat(64),
previewDigest: plan.previewDigest,
});
const session = Object.freeze({
workerId: 'worker-1',
sessionId: '018f0f5d-7b6a-7a11-8f4d-2f7b4f477001',
generation: 2,
sessionVersion: 5,
lifecycle: 'online',
compatibility: 'default_placement',
architecture: 'arm64',
supportTier: 'tier1',
protocolVersion: '1.0.0',
operatingSystem: 'linux',
maxConcurrentRuns: 2,
availableSlots: 1,
registeredAtMs: 900,
lastHeartbeatAtMs: 1_050,
leaseExpiresAtMs: 2_000,
updatedAtMs: 1_050,
observedAtMs: 1_100,
runtimes: Object.freeze([{ name: 'node', version: '24.18.0' }]),
declaredCapacity: Object.freeze({
cpuCores: 1,
memoryBytes: 268_435_456,
diskBytes: 1_073_741_824,
gpuCount: 0,
}),
});
const sessionSummary = Object.freeze(
Object.fromEntries(
Object.entries(session).filter(
([key]) => key !== 'runtimes' && key !== 'declaredCapacity',
),
),
);
function command(operation) {
return {
@@ -152,7 +185,7 @@ afterEach(() => {
}
});
test('validates all four low-sensitive Worker management results', () => {
test('validates all six low-sensitive Worker management results', () => {
const fixtures = [
[
'worker-credential.plan',
@@ -187,6 +220,25 @@ test('validates all four low-sensitive Worker management results', () => {
stale: false,
},
],
[
'worker-session.inspect',
{
schemaVersion: 1,
operation: 'worker-session.inspect',
observedAtMs: 1_100,
worker: session,
},
],
[
'worker-session.list',
{
schemaVersion: 1,
operation: 'worker-session.list',
observedAtMs: 1_100,
workers: [sessionSummary],
nextCursor: null,
},
],
];
for (const [operation, result] of fixtures) {
assert.equal(
@@ -223,6 +275,51 @@ test('rejects widened and secret-bearing response shapes', () => {
command('worker-credential.inspect'),
),
);
assert.throws(() =>
validateClusterWorkerCredentialManagementClientResult(
{
schemaVersion: 1,
operation: 'worker-session.inspect',
observedAtMs: 1_100,
worker: { ...session, labels: { secret: 'value' } },
},
command('worker-session.inspect'),
),
);
assert.throws(() =>
validateClusterWorkerCredentialManagementClientResult(
{
schemaVersion: 1,
operation: 'worker-session.list',
observedAtMs: 1_100,
workers: [
{ ...sessionSummary, workerId: 'worker-b' },
{ ...sessionSummary, workerId: 'worker-a' },
],
nextCursor: null,
},
command('worker-session.list'),
),
);
for (const worker of [
{ ...session, workerId: null },
{ ...session, generation: -1 },
{ ...session, availableSlots: 3 },
{ ...session, observedAtMs: 1_000 },
{ ...session, declaredCapacity: { ...session.declaredCapacity, gpuCount: -1 } },
]) {
assert.throws(() =>
validateClusterWorkerCredentialManagementClientResult(
{
schemaVersion: 1,
operation: 'worker-session.inspect',
observedAtMs: worker.observedAtMs,
worker,
},
command('worker-session.inspect'),
),
);
}
});
test('requires one matching private client certificate identity before connect', async () => {
@@ -69,6 +69,41 @@ function approval(planValue) {
});
}
const SESSION_OBSERVATION = Object.freeze({
workerId: 'worker-a',
sessionId: '018f0f5d-7b6a-7a11-8f4d-2f7b4f477001',
generation: 2,
sessionVersion: 5,
lifecycle: 'online',
compatibility: 'default_placement',
architecture: 'arm64',
supportTier: 'tier1',
protocolVersion: '1.0.0',
operatingSystem: 'linux',
maxConcurrentRuns: 2,
availableSlots: 1,
registeredAtMs: 900,
lastHeartbeatAtMs: 1_050,
leaseExpiresAtMs: 2_000,
updatedAtMs: 1_050,
observedAtMs: 1_100,
runtimes: Object.freeze([{ name: 'node', version: '24.18.0' }]),
declaredCapacity: Object.freeze({
cpuCores: 1,
memoryBytes: 268_435_456,
diskBytes: 1_073_741_824,
gpuCount: 0,
}),
});
const SESSION_SUMMARY = Object.freeze(
Object.fromEntries(
Object.entries(SESSION_OBSERVATION).filter(
([key]) => key !== 'runtimes' && key !== 'declaredCapacity',
),
),
);
function commands() {
return [
{
@@ -122,10 +157,28 @@ function commands() {
inspectionId: 'inspection-worker-a-generation-2',
},
},
{
schemaVersion: 1,
operation: 'worker-session.inspect',
request: {
authorityProjectId: 'cluster-authority',
workerId: 'worker-a',
inspectionId: 'inspection-worker-session-a',
},
},
{
schemaVersion: 1,
operation: 'worker-session.list',
request: {
authorityProjectId: 'cluster-authority',
afterWorkerId: null,
inspectionId: 'inspection-worker-session-list-a',
},
},
];
}
test('routes the four public commands with strong User authority and low-sensitive results', async () => {
test('routes six public commands with strong User authority and low-sensitive results', async () => {
const planValue = plan();
const approvalValue = approval(planValue);
const calls = [];
@@ -154,6 +207,18 @@ test('routes the four public commands with strong User authority and low-sensiti
stale: false,
};
},
async inspectSession(request) {
calls.push(['inspectSession', request]);
return { observedAtMs: 1_100, worker: SESSION_OBSERVATION };
},
async listSessions(request) {
calls.push(['listSessions', request]);
return {
observedAtMs: 1_100,
workers: [SESSION_SUMMARY],
nextCursor: null,
};
},
};
const transport = createClusterWorkerCredentialManagementTransport({
service,
@@ -170,7 +235,7 @@ test('routes the four public commands with strong User authority and low-sensiti
}
assert.deepEqual(
calls.map(([kind]) => kind),
['plan', 'propose', 'decide', 'inspect'],
['plan', 'propose', 'decide', 'inspect', 'inspectSession', 'listSessions'],
);
for (const [, request] of calls) {
assert.deepEqual(request.principal, principal());
@@ -182,11 +247,15 @@ test('routes the four public commands with strong User authority and low-sensiti
'worker-credential.propose',
'worker-credential.decide',
'worker-credential.inspect',
'worker-session.inspect',
'worker-session.list',
],
);
assert.equal(results[0].plan.planDigest, planValue.planDigest);
assert.equal(results[1].approval.actionDigest, planValue.planDigest);
assert.equal(results[3].stale, false);
assert.equal(results[4].worker.compatibility, 'default_placement');
assert.equal(results[5].workers[0].workerId, 'worker-a');
const serialized = JSON.stringify(results);
assert.doesNotMatch(serialized, /authenticationId|credential-token|secret/i);
});
@@ -194,7 +263,14 @@ test('routes the four public commands with strong User authority and low-sensiti
test('rejects weak or unavailable identity before management authority', async () => {
let calls = 0;
const service = Object.fromEntries(
['plan', 'propose', 'decide', 'inspectAuthorized'].map((name) => [
[
'plan',
'propose',
'decide',
'inspectAuthorized',
'inspectSession',
'listSessions',
].map((name) => [
name,
async () => {
calls += 1;
@@ -232,6 +308,8 @@ test('rejects widened and internal commands before authentication', async () =>
async propose() {},
async decide() {},
async inspectAuthorized() {},
async inspectSession() {},
async listSessions() {},
},
});
let authentications = 0;