feat(ql3): observe package installations in console

This commit is contained in:
whyour
2026-08-20 14:42:54 +08:00
parent 344680d64a
commit ffa4b4e7cb
29 changed files with 1259 additions and 36 deletions
+4 -2
View File
@@ -8,8 +8,10 @@ Console is a loopback-only read BFF serving digest-bound static assets. Neither
opens database or Kubernetes authority, enters the legacy 2.x Web application,
or resides in `cluster-control`.
The Console accepts thirteen exact Run, Task, Workflow and Copilot reads. The
browser cannot provide an upstream path or HTTP method, and every list page and
The Console accepts thirteen default Run, Task, Workflow and Copilot reads,
plus independently enabled Run cancellation, Worker and Plugin Package
installation observations, for at most twenty exact operations. The browser
cannot provide an upstream path or HTTP method, and every list page and
detail/evidence read requires an explicit click. Its Cluster API credential
stays in a canonical owner-private file and is reread for each upstream
request; browser JavaScript receives only a separate session token which cannot
@@ -11,6 +11,8 @@
run_cancellation_inspect: '/api/v1/run-management/cancellation-inspect',
worker_list: '/api/v1/worker-management/workers',
worker_inspect: '/api/v1/worker-management/worker',
package_list: '/api/v1/package-management/installations',
package_inspect: '/api/v1/package-management/installation',
run_list: '/api/v1/observe/run-list',
run_read: '/api/v1/observe/run',
run_event_list: '/api/v1/observe/run-events',
@@ -31,6 +33,8 @@
run_cancellation_inspect: '取消诊断',
worker_list: 'Worker 目录',
worker_inspect: 'Worker 详情',
package_list: 'Package 安装目录',
package_inspect: 'Package 安装详情',
run_list: 'Run 目录',
run_read: 'Run 详情',
run_event_list: 'Run Events',
@@ -131,6 +135,10 @@
result.afterWorkerId = null;
} else if (operation === 'worker_inspect') {
result.workerId = value('worker-id');
} else if (operation === 'package_list') {
result.afterPackageName = null;
} else if (operation === 'package_inspect') {
result.packageName = value('installation-package-name');
} else if (operation === 'run_list') {
result.afterCreatedAtMs = null;
result.afterRunId = null;
@@ -188,6 +196,11 @@
typeof fact.nextAfterWorkerId === 'string'
) {
next.afterWorkerId = fact.nextAfterWorkerId;
} else if (
operation === 'package_list' &&
typeof fact.nextAfterPackageName === 'string'
) {
next.afterPackageName = fact.nextAfterPackageName;
} else if (operation === 'run_list' && fact.hasMore === true && fact.next) {
next.afterCreatedAtMs = fact.next.createdAtMs;
next.afterRunId = fact.next.runId;
@@ -260,6 +273,23 @@
});
return;
}
if (operation === 'package_list' && Array.isArray(fact.installations)) {
fact.installations.forEach(function (installation) {
if (!installation || typeof installation.packageName !== 'string') {
return;
}
const button = document.createElement('button');
button.type = 'button';
button.textContent = '显式检查 ' + installation.packageName;
button.addEventListener('click', function () {
document.getElementById('installation-package-name').value =
installation.packageName;
void execute('package_inspect');
});
entry.append(button);
});
return;
}
if (
operation !== 'run_cancellation_blocked_list' ||
!Array.isArray(fact.items)
@@ -33,6 +33,8 @@
'run_cancellation_inspect',
'worker_list',
'worker_inspect',
'package_list',
'package_inspect',
'run_list',
'run_read',
'run_event_list',
@@ -54,6 +56,8 @@
run_cancellation_inspect: ['projectId', 'requestId', 'runId'],
worker_list: ['afterWorkerId', 'projectId', 'requestId'],
worker_inspect: ['projectId', 'requestId', 'workerId'],
package_list: ['afterPackageName', 'projectId', 'requestId'],
package_inspect: ['packageName', 'projectId', 'requestId'],
run_list: [
'afterCreatedAtMs',
'afterRunId',
@@ -122,6 +126,7 @@
afterStepRunId: 'step',
afterTaskId: 'task',
afterWorkerId: 'worker',
afterPackageName: 'package',
artifactId: 'artifact',
attemptId: 'attempt',
contentDigest: 'digest',
@@ -131,6 +136,7 @@
id: 'identifier',
modelId: 'model',
nextAfterWorkerId: 'worker',
nextAfterPackageName: 'package',
outputRef: 'artifact',
packageName: 'package',
projectId: 'project',
@@ -170,6 +176,8 @@
'runtimes',
'worker',
'workers',
'installation',
'installations',
'usage',
'workflow',
'workflows',
@@ -207,6 +215,12 @@
'status',
'supportTier',
'operatingSystem',
'availability',
'failureReason',
'installOperation',
'quarantineReason',
'recoveryAction',
'state',
]);
const safeEnumValues = new Set([
'accepted',
@@ -302,13 +316,31 @@
'ok',
'unavailable',
'workflow',
'not_active',
'install',
'reinstall',
'upgrade',
'rollback',
'resume_stage',
'resume_activation',
'inspect_activation',
'source_unavailable',
'source_mismatch',
'stage_failed',
'activation_failed',
'activation_fact_conflict',
'approval_expired',
'policy_fence_changed',
'resource_exhausted',
'suspected_key_compromise',
'confirmed_key_compromise',
]);
const sensitiveKey =
/credential|token|authorization|secret|session|password|cookie|private|keyring/iu;
const freeTextKey =
/text|content|stdout|stderr|command|input|output|environment|reason|error|message|description|name|path|url|uri|host|endpoint/iu;
const numericKey =
/^(?:schemaVersion|version|revision|sequence|attempt|priority|limit|offset|size|total|count|exitCode|pending|leased|retryWait|dispatched|blocked|due|expiredLease|identityMismatch|pidMismatch|unsupported|invalid|availableSlots|maxConcurrentRuns|cpuCores|[A-Za-z0-9_]*(?:AtMs|TimeMs|DurationMs|Bytes|Tokens|Micros|Sequence|Version|Count|Limit|Offset|Size|Total))$/u;
/^(?:schemaVersion|version|revision|sequence|attempt|priority|limit|offset|size|total|count|exitCode|pending|leased|retryWait|dispatched|blocked|due|expiredLease|identityMismatch|pidMismatch|unsupported|invalid|availableSlots|maxConcurrentRuns|cpuCores|targetGeneration|[A-Za-z0-9_]*(?:AtMs|TimeMs|DurationMs|Bytes|Tokens|Micros|Sequence|Version|Count|Limit|Offset|Size|Total))$/u;
const schemaValue = /^[a-z0-9][a-z0-9./_-]{0,126}@[a-z0-9._-]{1,16}$/u;
class ClusterConsoleEvidenceBundleError extends TypeError {
@@ -24,7 +24,7 @@
<div class="boundary" aria-label="当前权限边界">
<span class="boundary-dot" aria-hidden="true"></span>
<span>本机只读 BFF</span>
<strong>Run · Task · Workflow · Worker · Copilot</strong>
<strong>Run · Task · Workflow · Worker · Package · Copilot</strong>
</div>
</header>
@@ -33,7 +33,7 @@
<div class="section-heading">
<p class="eyebrow">Observation coordinates</p>
<h2 id="control-title">选择要读取的事实</h2>
<p>每次按钮点击只发起一次有界 GET。页面不创建、取消、重试、轮询或缓存任何任务。</p>
<p>每次按钮点击只发起一次有界读取。页面不创建、安装、升级、回滚、取消、重试、轮询或缓存任何任务。</p>
</div>
<form id="session-form" class="session-gate" autocomplete="off">
@@ -53,6 +53,7 @@
<button type="button" class="mode-tab active" data-panel="runtime-panel" aria-pressed="true">运行态</button>
<button type="button" class="mode-tab" data-panel="management-panel" aria-pressed="false">取消可用性</button>
<button type="button" class="mode-tab" data-panel="worker-panel" aria-pressed="false">Worker</button>
<button type="button" class="mode-tab" data-panel="package-panel" aria-pressed="false">Package</button>
<button type="button" class="mode-tab" data-panel="workflow-panel" aria-pressed="false">工作流</button>
<button type="button" class="mode-tab" data-panel="copilot-panel" aria-pressed="false">Copilot</button>
</nav>
@@ -81,6 +82,20 @@
</div>
</section>
<section id="package-panel" class="mode-panel" hidden>
<div class="control-group">
<div class="control-title"><span>01</span><strong>Package 安装目录</strong></div>
<button type="button" class="primary" data-read="package_list">读取首屏 Installations</button>
<p class="field-note">只有启动进程显式提供独立 Package management 配置与短期 assertion 时可用;固定 16 项,下一页必须再次点击。</p>
</div>
<div class="control-group">
<div class="control-title"><span>02</span><strong>单 Package 安装状态</strong></div>
<input id="installation-package-name" type="text" maxlength="63" autocomplete="off" spellcheck="false" placeholder="ops-package" />
<button type="button" data-read="package_inspect">读取 Package 安装详情</button>
<p class="field-note">只投影版本、安装状态、可用性与恢复提示;没有 propose、decide、install、upgrade、rollback、disable 或 uninstall mutation。</p>
</div>
</section>
<section id="worker-panel" class="mode-panel" hidden>
<div class="control-group">
<div class="control-title"><span>01</span><strong>Worker 目录</strong></div>
@@ -145,7 +160,7 @@
<aside class="trust-note">
<span>Authority boundary</span>
<p>Project credential、可选 Run authority 与可选 Worker authority 只由本机进程从彼此独立的私有文件读取。浏览器无法提交任意路径,也没有 start、stop、retry、rearm、drain、revoke 或 diagnose 权限入口。脱敏导出只处理本页已读事实,不补读或上传。</p>
<p>Project credential、可选 Run、Worker 与 Package authority 只由本机进程从彼此独立的私有文件读取。浏览器无法提交任意路径,也没有 start、stop、retry、rearm、drain、revoke、propose、decide、install、upgrade、rollback、disable、uninstall 或 diagnose 权限入口。脱敏导出只处理本页已读事实,不补读或上传。</p>
</aside>
</aside>
@@ -176,7 +191,7 @@
<footer>
<span>Loopback only · explicit reads · zero polling</span>
<span>QingLong 3.0 incubation / D-375</span>
<span>QingLong 3.0 incubation / D-376</span>
</footer>
</div>
</body>
@@ -24,7 +24,7 @@ const ASSETS = Object.freeze([
name: 'index.html',
field: 'html',
maximumBytes: 32 * 1024,
digest: '363fcf2d52ff86e5b2a1c9ed8b7810226920a9270b6d0c41a1f61ce24f957832',
digest: '429d7b3dd2da4989865be6ac07180cc9c3ebdcbbac5028054cddaac870ad520c',
}),
Object.freeze({
name: 'app.css',
@@ -36,13 +36,13 @@ const ASSETS = Object.freeze([
name: 'evidence-bundle.js',
field: 'evidenceBundle',
maximumBytes: 32 * 1024,
digest: 'ae4a08572cfc3296284c56549a3850f530474151573e2705401996730bf0466e',
digest: '83d17dfa815c175161b35c1aca5f270b15005a16cb79be41c2884b490f617783',
}),
Object.freeze({
name: 'app.js',
field: 'javascript',
maximumBytes: 32 * 1024,
digest: '4b13da1a85d59e29a606da3b1a3327419926a496a498a06ddc323365ce5230c1',
digest: '365ccd43ae2aa4b11a0ab3d142cd04e89ec0b96711bef253f63584e8182589ee',
}),
] as const);
@@ -10,7 +10,16 @@ import {
validateClusterCopilotClientCredentialFile,
} from '../copilot-client/client';
import { readCanonicalFile } from '../management-support/managementClientConfiguration';
import { validateClusterAuthenticatedManagementClientConfiguration } from '../management-support/pluginPackageManagementClient';
import {
executeClusterPluginPackageManagementCommand,
validateClusterAuthenticatedManagementClientConfiguration,
} from '../management-support/pluginPackageManagementClient';
import {
createPluginPackageInstallationInspectionCommand,
createPluginPackageInstallationListCommand,
projectPluginPackageInstallationInspection,
projectPluginPackageInstallationList,
} from '../plugin-package/management/pluginPackageInstallationProduct';
import {
createRunCancellationBlockedListCommand,
projectRunCancellationBlockedList,
@@ -50,6 +59,7 @@ const USAGE = [
' ql3-copilot-console --container-published-loopback --port=1024..65535 --config /absolute/client.json --credential /absolute/credential --session /absolute/session [--check]',
' Optional Run reads: --run-management-config /absolute/run-client.json --run-management-assertion /absolute/assertion.jwt',
' Optional Worker reads: --worker-management-config /absolute/worker-client.json --worker-management-assertion /absolute/assertion.jwt',
' Optional Package reads: --package-management-config /absolute/package-client.json --package-management-assertion /absolute/assertion.jwt',
'',
'Native mode binds 127.0.0.1. Container mode requires host-loopback port publication.',
'The browser session key remains in a separate owner-private 0600 file.',
@@ -60,6 +70,8 @@ interface ClusterCopilotConsoleCliArguments {
readonly configFile: string;
readonly credentialFile: string;
readonly networkBoundary: 'host-loopback' | 'container-published-loopback';
readonly packageManagementAssertionFile?: string;
readonly packageManagementConfigFile?: string;
readonly runManagementAssertionFile?: string;
readonly runManagementConfigFile?: string;
readonly workerManagementAssertionFile?: string;
@@ -78,6 +90,10 @@ const RUN_MANAGEMENT_OPERATIONS = new Set([
'run_cancellation_inspect',
]);
const WORKER_MANAGEMENT_OPERATIONS = new Set(['worker_list', 'worker_inspect']);
const PACKAGE_MANAGEMENT_OPERATIONS = new Set([
'package_list',
'package_inspect',
]);
function usageFailure(): never {
process.stderr.write(USAGE + '\n');
@@ -118,6 +134,8 @@ export function parseClusterCopilotConsoleCliArguments(
let runManagementAssertionFile: string | undefined;
let workerManagementConfigFile: string | undefined;
let workerManagementAssertionFile: string | undefined;
let packageManagementConfigFile: string | undefined;
let packageManagementAssertionFile: string | undefined;
let port = 0;
let portSeen = false;
let containerPublishedLoopback = false;
@@ -201,6 +219,28 @@ export function parseClusterCopilotConsoleCliArguments(
index += workerManagementAssertion.consumed;
continue;
}
const packageManagementConfig = argumentValue(
argv,
index,
'--package-management-config',
);
if (packageManagementConfig) {
if (packageManagementConfigFile !== undefined) return usageFailure();
packageManagementConfigFile = packageManagementConfig.value;
index += packageManagementConfig.consumed;
continue;
}
const packageManagementAssertion = argumentValue(
argv,
index,
'--package-management-assertion',
);
if (packageManagementAssertion) {
if (packageManagementAssertionFile !== undefined) return usageFailure();
packageManagementAssertionFile = packageManagementAssertion.value;
index += packageManagementAssertion.consumed;
continue;
}
const portArgument = argumentValue(argv, index, '--port');
if (portArgument) {
if (portSeen || !/^(?:0|[1-9][0-9]{0,4})$/.test(portArgument.value)) {
@@ -227,6 +267,8 @@ export function parseClusterCopilotConsoleCliArguments(
(runManagementAssertionFile === undefined) ||
(workerManagementConfigFile === undefined) !==
(workerManagementAssertionFile === undefined) ||
(packageManagementConfigFile === undefined) !==
(packageManagementAssertionFile === undefined) ||
(containerPublishedLoopback && port === 0) ||
(!containerPublishedLoopback && check && port !== 0)
) {
@@ -247,6 +289,10 @@ export function parseClusterCopilotConsoleCliArguments(
workerManagementAssertionFile !== undefined
? { workerManagementConfigFile, workerManagementAssertionFile }
: {}),
...(packageManagementConfigFile !== undefined &&
packageManagementAssertionFile !== undefined
? { packageManagementConfigFile, packageManagementAssertionFile }
: {}),
sessionFile,
port,
});
@@ -255,7 +301,7 @@ export function parseClusterCopilotConsoleCliArguments(
function validateManagementAuthority(
configFile: string | undefined,
assertionFile: string | undefined,
kind: 'run' | 'worker',
kind: 'package' | 'run' | 'worker',
): boolean {
if (configFile === undefined || assertionFile === undefined) return false;
validateClusterAuthenticatedManagementClientConfiguration(configFile, kind);
@@ -281,12 +327,15 @@ function validateManagementAuthority(
function availableOperations(
runManagementAuthority: boolean,
workerManagementAuthority: boolean,
packageManagementAuthority: boolean,
) {
return CLUSTER_COPILOT_CONSOLE_READ_OPERATIONS.filter(
(operation) =>
(runManagementAuthority || !RUN_MANAGEMENT_OPERATIONS.has(operation)) &&
(workerManagementAuthority ||
!WORKER_MANAGEMENT_OPERATIONS.has(operation)),
!WORKER_MANAGEMENT_OPERATIONS.has(operation)) &&
(packageManagementAuthority ||
!PACKAGE_MANAGEMENT_OPERATIONS.has(operation)),
);
}
@@ -393,6 +442,48 @@ async function executeConsoleRead(
result: projected as unknown as Readonly<Record<string, unknown>>,
});
}
if (
request.operation === 'package_list' ||
request.operation === 'package_inspect'
) {
if (
parsed.packageManagementConfigFile === undefined ||
parsed.packageManagementAssertionFile === undefined
) {
throw new Error('Package management authority is disabled');
}
const createUuid = commandIdSource(request.requestId);
const command =
request.operation === 'package_list'
? createPluginPackageInstallationListCommand(
request.projectId,
request.afterPackageName ?? undefined,
createUuid,
)
: createPluginPackageInstallationInspectionCommand(
request.projectId,
request.packageName,
createUuid,
);
const result = await executeClusterPluginPackageManagementCommand({
configFile: parsed.packageManagementConfigFile,
assertionFile: parsed.packageManagementAssertionFile,
command,
});
const projected =
request.operation === 'package_list'
? projectPluginPackageInstallationList(request.projectId, result)
: projectPluginPackageInstallationInspection(
request.projectId,
request.packageName,
result,
);
return Object.freeze({
schemaVersion: 1 as const,
requestId: request.requestId,
result: projected as unknown as Readonly<Record<string, unknown>>,
});
}
return executeClusterProjectApiRead({
configFile: parsed.configFile,
credentialFile: parsed.credentialFile,
@@ -439,9 +530,15 @@ async function main(): Promise<void> {
parsed.workerManagementAssertionFile,
'worker',
);
const packageManagementAuthority = validateManagementAuthority(
parsed.packageManagementConfigFile,
parsed.packageManagementAssertionFile,
'package',
);
const operations = availableOperations(
runManagementAuthority,
workerManagementAuthority,
packageManagementAuthority,
);
const sessionDigest = readSessionDigest(parsed.sessionFile);
if (parsed.check) {
@@ -465,6 +562,9 @@ async function main(): Promise<void> {
workerManagementAuthority: workerManagementAuthority
? 'server_only'
: 'disabled',
packageManagementAuthority: packageManagementAuthority
? 'server_only'
: 'disabled',
operations,
mutation: false,
}) + '\n',
@@ -504,6 +604,9 @@ async function main(): Promise<void> {
workerManagementAuthority: workerManagementAuthority
? 'server_only'
: 'disabled',
packageManagementAuthority: packageManagementAuthority
? 'server_only'
: 'disabled',
operations,
mutation: false,
}) + '\n',
@@ -16,6 +16,8 @@ export const CLUSTER_COPILOT_CONSOLE_READ_OPERATIONS = Object.freeze([
'run_cancellation_inspect',
'worker_list',
'worker_inspect',
'package_list',
'package_inspect',
'run_list',
'run_read',
'run_event_list',
@@ -51,6 +53,9 @@ export type ClusterCopilotConsoleReadRequest =
| (BaseReadRequest<'worker_list'> &
Readonly<{ afterWorkerId: string | null }>)
| (BaseReadRequest<'worker_inspect'> & Readonly<{ workerId: string }>)
| (BaseReadRequest<'package_list'> &
Readonly<{ afterPackageName: string | null }>)
| (BaseReadRequest<'package_inspect'> & Readonly<{ packageName: string }>)
| (BaseReadRequest<'run_list'> &
Readonly<{
afterCreatedAtMs: number | null;
@@ -267,6 +272,33 @@ export function normalizeClusterCopilotConsoleReadRequest(
workerId: record.workerId,
});
}
if (op === 'package_list') {
exact(record, op, ['afterPackageName']);
if (
record.afterPackageName !== null &&
(typeof record.afterPackageName !== 'string' ||
!PACKAGE_NAME.test(record.afterPackageName))
)
invalid();
return Object.freeze({
...common(record),
operation: op,
afterPackageName: record.afterPackageName as string | null,
});
}
if (op === 'package_inspect') {
exact(record, op, ['packageName']);
if (
typeof record.packageName !== 'string' ||
!PACKAGE_NAME.test(record.packageName)
)
invalid();
return Object.freeze({
...common(record),
operation: op,
packageName: record.packageName,
});
}
if (op === 'run_list') {
exact(record, op, ['afterCreatedAtMs', 'afterRunId', 'limit']);
if (
@@ -502,7 +534,9 @@ export function clusterCopilotConsoleProjectReadPath(
normalized.operation === 'run_cancellation_blocked_list' ||
normalized.operation === 'run_cancellation_inspect' ||
normalized.operation === 'worker_list' ||
normalized.operation === 'worker_inspect'
normalized.operation === 'worker_inspect' ||
normalized.operation === 'package_list' ||
normalized.operation === 'package_inspect'
)
invalid();
const project = '/api/v3/projects/' + encoded(normalized.projectId);
@@ -35,6 +35,8 @@ const OPERATIONS = Object.freeze([
'run_cancellation_inspect',
'worker_list',
'worker_inspect',
'package_list',
'package_inspect',
'run_list',
'run_read',
'run_event_list',
@@ -66,6 +68,8 @@ const REQUEST_FIELDS: Readonly<Record<EvidenceOperation, readonly string[]>> =
]),
worker_list: Object.freeze(['afterWorkerId', 'projectId', 'requestId']),
worker_inspect: Object.freeze(['projectId', 'requestId', 'workerId']),
package_list: Object.freeze(['afterPackageName', 'projectId', 'requestId']),
package_inspect: Object.freeze(['packageName', 'projectId', 'requestId']),
run_list: Object.freeze([
'afterCreatedAtMs',
'afterRunId',
@@ -139,6 +143,7 @@ const IDENTIFIER_DOMAINS: Readonly<Record<string, string>> = Object.freeze({
afterStepRunId: 'step',
afterTaskId: 'task',
afterWorkerId: 'worker',
afterPackageName: 'package',
artifactId: 'artifact',
attemptId: 'attempt',
contentDigest: 'digest',
@@ -148,6 +153,7 @@ const IDENTIFIER_DOMAINS: Readonly<Record<string, string>> = Object.freeze({
id: 'identifier',
modelId: 'model',
nextAfterWorkerId: 'worker',
nextAfterPackageName: 'package',
outputRef: 'artifact',
packageName: 'package',
projectId: 'project',
@@ -187,6 +193,8 @@ const SAFE_CONTAINERS = new Set([
'runtimes',
'worker',
'workers',
'installation',
'installations',
'usage',
'workflow',
'workflows',
@@ -224,6 +232,12 @@ const SAFE_ENUM_KEYS = new Set([
'status',
'supportTier',
'operatingSystem',
'availability',
'failureReason',
'installOperation',
'quarantineReason',
'recoveryAction',
'state',
]);
const SAFE_ENUM_VALUES = new Set([
'accepted',
@@ -319,9 +333,27 @@ const SAFE_ENUM_VALUES = new Set([
'ok',
'unavailable',
'workflow',
'not_active',
'install',
'reinstall',
'upgrade',
'rollback',
'resume_stage',
'resume_activation',
'inspect_activation',
'source_unavailable',
'source_mismatch',
'stage_failed',
'activation_failed',
'activation_fact_conflict',
'approval_expired',
'policy_fence_changed',
'resource_exhausted',
'suspected_key_compromise',
'confirmed_key_compromise',
]);
const NUMERIC_KEY =
/^(?:schemaVersion|version|revision|sequence|attempt|priority|limit|offset|size|total|count|exitCode|pending|leased|retryWait|dispatched|blocked|due|expiredLease|identityMismatch|pidMismatch|unsupported|invalid|availableSlots|maxConcurrentRuns|cpuCores|[A-Za-z0-9_]*(?:AtMs|TimeMs|DurationMs|Bytes|Tokens|Micros|Sequence|Version|Count|Limit|Offset|Size|Total))$/u;
/^(?:schemaVersion|version|revision|sequence|attempt|priority|limit|offset|size|total|count|exitCode|pending|leased|retryWait|dispatched|blocked|due|expiredLease|identityMismatch|pidMismatch|unsupported|invalid|availableSlots|maxConcurrentRuns|cpuCores|targetGeneration|[A-Za-z0-9_]*(?:AtMs|TimeMs|DurationMs|Bytes|Tokens|Micros|Sequence|Version|Count|Limit|Offset|Size|Total))$/u;
const SCHEMA_VALUE = /^[a-z0-9][a-z0-9./_-]{0,126}@[a-z0-9._-]{1,16}$/u;
const SHA256 = /^[0-9a-f]{64}$/u;
const CONTROL = /[\0-\x1f\x7f]/u;
@@ -197,6 +197,8 @@ const READ_ROUTES: Readonly<
'/api/v1/run-management/cancellation-inspect': 'run_cancellation_inspect',
'/api/v1/worker-management/workers': 'worker_list',
'/api/v1/worker-management/worker': 'worker_inspect',
'/api/v1/package-management/installations': 'package_list',
'/api/v1/package-management/installation': 'package_inspect',
'/api/v1/observe/run-list': 'run_list',
'/api/v1/observe/run': 'run_read',
'/api/v1/observe/run-events': 'run_event_list',
@@ -598,16 +598,18 @@ function validateSecretBindingPlanSummary(
}
if (
summary.actionRef !== command.request.actionRef ||
command.operation === 'plugin-package.secret-binding.plan' &&
(summary.projectId !== command.request.projectId ||
summary.packageName !== command.request.packageName ||
summary.entries.length !== command.request.assignments.length ||
command.request.assignments.some((assignment) => {
const responseEntry = (summary.entries as JsonObject[]).find(
(entry) => entry.name === assignment.name,
);
return !responseEntry || responseEntry.secretRef !== assignment.secretRef;
}))
(command.operation === 'plugin-package.secret-binding.plan' &&
(summary.projectId !== command.request.projectId ||
summary.packageName !== command.request.packageName ||
summary.entries.length !== command.request.assignments.length ||
command.request.assignments.some((assignment) => {
const responseEntry = (summary.entries as JsonObject[]).find(
(entry) => entry.name === assignment.name,
);
return (
!responseEntry || responseEntry.secretRef !== assignment.secretRef
);
})))
) {
throw new ClusterPluginPackageManagementClientRequestError();
}
@@ -707,7 +709,9 @@ function validateSecretBindingTransitionPlanSummary(
throw new ClusterPluginPackageManagementClientRequestError();
}
} catch (error) {
if (error instanceof ClusterPluginPackageManagementClientRequestError) {
if (
error instanceof ClusterPluginPackageManagementClientRequestError
) {
throw error;
}
throw new ClusterPluginPackageManagementClientRequestError();
@@ -758,7 +762,9 @@ function validateResult(
result as unknown as ClusterPluginPackageManagementTransportResult,
);
}
if (command.operation === 'plugin-package.secret-binding.transition.propose') {
if (
command.operation === 'plugin-package.secret-binding.transition.propose'
) {
const result = exactResponseObject(value, [
'schemaVersion',
'operation',
@@ -789,7 +795,9 @@ function validateResult(
result as unknown as ClusterPluginPackageManagementTransportResult,
);
}
if (command.operation === 'plugin-package.secret-binding.transition.inspect') {
if (
command.operation === 'plugin-package.secret-binding.transition.inspect'
) {
const result = exactResponseObject(value, [
'schemaVersion',
'operation',
@@ -893,7 +901,7 @@ function validateResult(
result.schemaVersion !== 1 ||
result.operation !== command.operation ||
typeof result.stale !== 'boolean' ||
result.plan === null && result.approval === null
(result.plan === null && result.approval === null)
) {
throw new ClusterPluginPackageManagementClientRequestError();
}
@@ -903,8 +911,7 @@ function validateResult(
if (result.approval !== null) {
validateScalarSummary(result.approval, APPROVAL_KEYS);
if (
(result.approval as JsonObject).id !==
command.request.approvalRequestId
(result.approval as JsonObject).id !== command.request.approvalRequestId
) {
throw new ClusterPluginPackageManagementClientRequestError();
}
@@ -1486,3 +1493,14 @@ export async function executeClusterPluginPackageManagementClient(
connectionOptions,
);
}
export async function executeClusterPluginPackageManagementCommand(
execution: ClusterAuthenticatedManagementCommandExecution<ClusterPluginPackageManagementCommand>,
connectionOptions?: ClusterPluginPackageManagementClientConnectionOptions,
): Promise<Readonly<ClusterPluginPackageManagementClientResult>> {
return executeClusterAuthenticatedManagementClient(
execution,
PLUGIN_PACKAGE_MANAGEMENT_CLIENT_PROTOCOL,
connectionOptions,
);
}
@@ -0,0 +1,255 @@
/** Bounded commands and low-sensitive product projections for Package installations. */
import { randomUUID } from 'node:crypto';
import type { ClusterPluginPackageManagementClientResult } from '../../management-support/pluginPackageManagementClient';
import type {
ClusterPluginPackageManagementCommand,
ClusterPluginPackageManagementTransportResult,
} from './pluginPackageManagementTransport';
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const PACKAGE_VERSION = /^[0-9A-Za-z](?:[0-9A-Za-z.+-]{0,126}[0-9A-Za-z])?$/;
const PAGE_SIZE = 16;
const INSTALL_OPERATIONS = new Set([
'install',
'reinstall',
'upgrade',
'rollback',
]);
const INSTALL_STATES = new Set([
'queued',
'staged',
'activating',
'active',
'failed',
]);
const RECOVERY_ACTIONS = new Set([
'resume_stage',
'resume_activation',
'inspect_activation',
'none',
]);
const AVAILABILITY = new Set(['active', 'not_active', 'quarantined']);
const FAILURE_REASONS = new Set([
'source_unavailable',
'source_mismatch',
'stage_failed',
'activation_failed',
'activation_fact_conflict',
'approval_expired',
'policy_fence_changed',
'resource_exhausted',
]);
const QUARANTINE_REASONS = new Set([
'suspected_key_compromise',
'confirmed_key_compromise',
]);
type InspectCommand = Extract<
ClusterPluginPackageManagementCommand,
{ readonly operation: 'plugin-package.installation.inspect' }
>;
type ListCommand = Extract<
ClusterPluginPackageManagementCommand,
{ readonly operation: 'plugin-package.installation.list' }
>;
type InspectResult = Extract<
ClusterPluginPackageManagementTransportResult,
{ readonly operation: 'plugin-package.installation.inspect' }
>;
type ListResult = Extract<
ClusterPluginPackageManagementTransportResult,
{ readonly operation: 'plugin-package.installation.list' }
>;
type Installation = NonNullable<InspectResult['installation']>;
export interface PluginPackageInstallationObservation {
readonly packageName: string;
readonly packageVersion: string;
readonly installOperation: Installation['operation'];
readonly state: Installation['state'];
readonly targetGeneration: number;
readonly recoveryAction: Installation['recoveryAction'];
readonly availability: Installation['availability'];
readonly quarantineReason: Installation['quarantineReason'];
readonly failureReason: Installation['failureReason'];
readonly version: number;
readonly createdAtMs: number;
readonly updatedAtMs: number;
}
export interface PluginPackageInstallationInspection {
readonly schema: 'qinglong/plugin-package-installation-inspection@v1';
readonly projectId: string;
readonly packageName: string;
readonly found: boolean;
readonly installation: Readonly<PluginPackageInstallationObservation> | null;
}
export interface PluginPackageInstallationList {
readonly schema: 'qinglong/plugin-package-installation-list@v1';
readonly projectId: string;
readonly count: number;
readonly installations: readonly Readonly<PluginPackageInstallationObservation>[];
readonly truncated: boolean;
readonly nextAfterPackageName: string | null;
}
export class ClusterPluginPackageInstallationProductError extends TypeError {
readonly code = 'QL3_PLUGIN_PACKAGE_INSTALLATION_PRODUCT_INPUT_INVALID';
constructor() {
super('Plugin Package installation product input is invalid');
this.name = 'ClusterPluginPackageInstallationProductError';
}
}
function identifier(value: string): string {
if (!IDENTIFIER.test(value)) {
throw new ClusterPluginPackageInstallationProductError();
}
return value;
}
function packageName(value: string): string {
if (!PACKAGE_NAME.test(value)) {
throw new ClusterPluginPackageInstallationProductError();
}
return value;
}
function packageVersion(value: string): string {
if (!PACKAGE_VERSION.test(value)) {
throw new ClusterPluginPackageInstallationProductError();
}
return value;
}
function enumValue<T extends string>(value: T, values: ReadonlySet<string>): T {
if (!values.has(value)) {
throw new ClusterPluginPackageInstallationProductError();
}
return value;
}
function nullableEnum<T extends string>(
value: T | null,
values: ReadonlySet<string>,
): T | null {
return value === null ? null : enumValue(value, values);
}
function safeInteger(value: number): number {
if (!Number.isSafeInteger(value) || value < 0) {
throw new ClusterPluginPackageInstallationProductError();
}
return value;
}
function inspectionId(createId: () => string): string {
return identifier(createId());
}
export function createPluginPackageInstallationInspectionCommand(
projectId: string,
name: string,
createId: () => string = randomUUID,
): Readonly<InspectCommand> {
return Object.freeze({
schemaVersion: 1,
operation: 'plugin-package.installation.inspect',
request: Object.freeze({
projectId: identifier(projectId),
packageName: packageName(name),
inspectionId: inspectionId(createId),
}),
});
}
export function createPluginPackageInstallationListCommand(
projectId: string,
afterPackageName?: string,
createId: () => string = randomUUID,
): Readonly<ListCommand> {
return Object.freeze({
schemaVersion: 1,
operation: 'plugin-package.installation.list',
request: Object.freeze({
projectId: identifier(projectId),
limit: PAGE_SIZE,
...(afterPackageName === undefined
? {}
: {
after: Object.freeze({
packageName: packageName(afterPackageName),
}),
}),
inspectionId: inspectionId(createId),
}),
});
}
function projectInstallation(
installation: Installation | ListResult['installations'][number],
): Readonly<PluginPackageInstallationObservation> {
return Object.freeze({
packageName: packageName(installation.packageName),
packageVersion: packageVersion(installation.packageVersion),
installOperation: enumValue(installation.operation, INSTALL_OPERATIONS),
state: enumValue(installation.state, INSTALL_STATES),
targetGeneration: safeInteger(installation.targetGeneration),
recoveryAction: enumValue(installation.recoveryAction, RECOVERY_ACTIONS),
availability: enumValue(installation.availability, AVAILABILITY),
quarantineReason: nullableEnum(
installation.quarantineReason,
QUARANTINE_REASONS,
),
failureReason: nullableEnum(installation.failureReason, FAILURE_REASONS),
version: safeInteger(installation.version),
createdAtMs: safeInteger(installation.createdAtMs),
updatedAtMs: safeInteger(installation.updatedAtMs),
});
}
export function projectPluginPackageInstallationInspection(
projectId: string,
name: string,
response: Readonly<ClusterPluginPackageManagementClientResult>,
): Readonly<PluginPackageInstallationInspection> {
if (response.result.operation !== 'plugin-package.installation.inspect') {
throw new ClusterPluginPackageInstallationProductError();
}
const installation = response.result.installation;
if (installation !== null && installation.packageName !== name) {
throw new ClusterPluginPackageInstallationProductError();
}
return Object.freeze({
schema: 'qinglong/plugin-package-installation-inspection@v1',
projectId: identifier(projectId),
packageName: packageName(name),
found: installation !== null,
installation:
installation === null ? null : projectInstallation(installation),
});
}
export function projectPluginPackageInstallationList(
projectId: string,
response: Readonly<ClusterPluginPackageManagementClientResult>,
): Readonly<PluginPackageInstallationList> {
if (response.result.operation !== 'plugin-package.installation.list') {
throw new ClusterPluginPackageInstallationProductError();
}
const installations = Object.freeze(
response.result.installations.map(projectInstallation),
);
return Object.freeze({
schema: 'qinglong/plugin-package-installation-list@v1',
projectId: identifier(projectId),
count: installations.length,
installations,
truncated: response.result.truncated,
nextAfterPackageName: response.result.next?.packageName ?? null,
});
}
@@ -265,6 +265,23 @@ test('normalizes Copilot and fixed Project observation operations without arbitr
assert.throws(() => clusterCopilotConsoleProjectReadPath(workerList), {
code: 'QL3_CLUSTER_COPILOT_CONSOLE_READ_REQUEST_INVALID',
});
const packageList = normalizeClusterCopilotConsoleReadRequest({
schema: CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA,
operation: 'package_list',
projectId: 'project-main',
requestId: 'console-package-list',
afterPackageName: 'ops-package',
});
assert.deepEqual(packageList, {
schema: CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA,
operation: 'package_list',
projectId: 'project-main',
requestId: 'console-package-list',
afterPackageName: 'ops-package',
});
assert.throws(() => clusterCopilotConsoleProjectReadPath(packageList), {
code: 'QL3_CLUSTER_COPILOT_CONSOLE_READ_REQUEST_INVALID',
});
assert.throws(
() =>
normalizeClusterCopilotConsoleReadRequest({
@@ -690,6 +707,62 @@ test('routes only fixed Worker list and inspect reads with a bounded cursor', as
assert.equal(reads.length, 2);
});
test('routes only fixed Package installation list and inspect reads', async (t) => {
const reads = [];
const { server, headers } = await fixture(async (read) => {
reads.push(read);
return {
schemaVersion: 1,
requestId: read.requestId,
result: { operation: read.operation },
};
});
t.after(() => server.close());
const cases = [
[
'/api/v1/package-management/installations',
{
schema: CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA,
operation: 'package_list',
projectId: 'project-main',
requestId: 'console-package-list-1',
afterPackageName: null,
},
],
[
'/api/v1/package-management/installation',
{
schema: CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA,
operation: 'package_inspect',
projectId: 'project-main',
requestId: 'console-package-inspect-1',
packageName: 'ops-package',
},
],
];
for (const [path, body] of cases) {
const response = await request(server.origin, {
method: 'POST',
path,
headers,
body,
});
assert.equal(response.statusCode, 200);
}
assert.deepEqual(
reads,
cases.map(([, body]) => body),
);
const invalidCursor = await request(server.origin, {
method: 'POST',
path: '/api/v1/package-management/installations',
headers,
body: { ...cases[0][1], afterPackageName: 'Contains_underscore' },
});
assert.equal(invalidCursor.statusCode, 400);
assert.equal(reads.length, 2);
});
test('returns model text as JSON data only after an explicit output read', async (t) => {
const { server, headers } = await fixture(async (command) => {
assert.equal(command.operation, 'output');
@@ -40,6 +40,7 @@ const runManagementOperations = [
'run_cancellation_inspect',
];
const workerManagementOperations = ['worker_list', 'worker_inspect'];
const packageManagementOperations = ['package_list', 'package_inspect'];
function workerSummary(workerId = 'worker-a') {
return {
@@ -63,6 +64,36 @@ function workerSummary(workerId = 'worker-a') {
};
}
function packageInstallationSummary(packageName = 'ops-package') {
return {
installationId: 'installation-transport-hidden',
projectId: 'project-main',
packageName,
packageVersion: '3.1.0',
operation: 'upgrade',
state: 'active',
targetGeneration: 4,
activeLockDigest: 'a'.repeat(64),
previousActiveLockDigest: 'b'.repeat(64),
recoveryAction: 'none',
availability: 'active',
quarantineReason: null,
quarantineAuthorizationMode: null,
quarantineEventDigest: null,
quarantinedAtMs: null,
withdrawalStatus: null,
withdrawalReceiptDigest: null,
withdrawalCommittedAtMs: null,
failureReason: null,
failedFrom: null,
failedAtMs: null,
version: 7,
createdAtMs: 1_000,
updatedAtMs: 1_100,
recordDigest: 'c'.repeat(64),
};
}
function privateFile(directory, name, contents) {
const filePath = path.join(directory, name);
fs.writeFileSync(filePath, contents, { mode: 0o600 });
@@ -201,7 +232,29 @@ async function fixture(t) {
command,
});
const body =
command?.operation === 'worker-session.list'
command?.operation === 'plugin-package.installation.list'
? {
schemaVersion: 1,
requestId: 'package-transport-hidden',
result: {
schemaVersion: 1,
operation: 'plugin-package.installation.list',
installations: [packageInstallationSummary()],
truncated: true,
next: { packageName: 'ops-package' },
},
}
: command?.operation === 'plugin-package.installation.inspect'
? {
schemaVersion: 1,
requestId: 'package-transport-hidden',
result: {
schemaVersion: 1,
operation: 'plugin-package.installation.inspect',
installation: packageInstallationSummary(),
},
}
: command?.operation === 'worker-session.list'
? {
schemaVersion: 1,
requestId: 'worker-transport-hidden',
@@ -341,6 +394,19 @@ async function fixture(t) {
requestTimeoutMs: 2_000,
}),
);
const packageManagementConfigFile = privateFile(
directory,
'package-client.json',
JSON.stringify({
schemaVersion: 1,
endpoint: `https://localhost:${
server.address().port
}/api/v3/plugin-packages/management`,
servername: 'localhost',
caFile,
requestTimeoutMs: 2_000,
}),
);
const sessionToken = randomBytes(32).toString('base64url');
return {
requests,
@@ -360,6 +426,12 @@ async function fixture(t) {
'worker-assertion.jwt',
'eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJvcGVyYXRvci0xIn0.c2lnbmF0dXJl',
),
packageManagementConfigFile,
packageManagementAssertionFile: privateFile(
directory,
'package-assertion.jwt',
'eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJvcGVyYXRvci0xIn0.c2lnbmF0dXJl',
),
};
}
@@ -371,6 +443,7 @@ test('CLI exposes deterministic help and a low-sensitive failure surface', async
' ql3-copilot-console --container-published-loopback --port=1024..65535 --config /absolute/client.json --credential /absolute/credential --session /absolute/session [--check]',
' Optional Run reads: --run-management-config /absolute/run-client.json --run-management-assertion /absolute/assertion.jwt',
' Optional Worker reads: --worker-management-config /absolute/worker-client.json --worker-management-assertion /absolute/assertion.jwt',
' Optional Package reads: --package-management-config /absolute/package-client.json --package-management-assertion /absolute/assertion.jwt',
'',
'Native mode binds 127.0.0.1. Container mode requires host-loopback port publication.',
'The browser session key remains in a separate owner-private 0600 file.',
@@ -426,6 +499,7 @@ test('preflight proves private authority and unauthenticated TLS 1.3 readiness',
clusterCredential: 'server_only',
runManagementAuthority: 'disabled',
workerManagementAuthority: 'disabled',
packageManagementAuthority: 'disabled',
operations: consoleOperations,
mutation: false,
});
@@ -521,6 +595,47 @@ test('preflight enables exactly two Worker reads only with canonical config and
assert.equal(incomplete.status, 64);
});
test('preflight enables exactly two Package reads only with canonical config and assertion', async (t) => {
const value = await fixture(t);
const result = await runCli([
'--check',
'--config',
value.configFile,
'--credential',
value.credentialFile,
'--session',
value.sessionFile,
'--package-management-config',
value.packageManagementConfigFile,
'--package-management-assertion',
value.packageManagementAssertionFile,
]);
assert.equal(result.status, 0, result.stderr);
const fact = JSON.parse(result.stdout);
assert.equal(fact.runManagementAuthority, 'disabled');
assert.equal(fact.workerManagementAuthority, 'disabled');
assert.equal(fact.packageManagementAuthority, 'server_only');
assert.deepEqual(fact.operations, [
'inspect',
'output',
...packageManagementOperations,
...consoleOperations.slice(2),
]);
assert.equal(value.requests.length, 1);
const incomplete = await runCli([
'--config',
value.configFile,
'--credential',
value.credentialFile,
'--session',
value.sessionFile,
'--package-management-config',
value.packageManagementConfigFile,
]);
assert.equal(incomplete.status, 64);
});
test('serve mode starts an ephemeral loopback origin and shuts down cleanly', async (t) => {
const value = await fixture(t);
const child = spawn(
@@ -548,6 +663,7 @@ test('serve mode starts an ephemeral loopback origin and shuts down cleanly', as
assert.equal(started.mutation, false);
assert.equal(started.runManagementAuthority, 'disabled');
assert.equal(started.workerManagementAuthority, 'disabled');
assert.equal(started.packageManagementAuthority, 'disabled');
assert.equal(started.networkBoundary, 'host-loopback');
assert.equal(started.publishedHostAddress, '127.0.0.1');
const shell = await get(started.origin);
@@ -687,6 +803,76 @@ test('serve mode performs one canonical Worker page read without exposing transp
assert.deepEqual(exit, { status: 0, signal: null });
});
test('serve mode performs one canonical Package page read without exposing durable identity', async (t) => {
const value = await fixture(t);
const child = spawn(
process.execPath,
[
cliPath,
'--config',
value.configFile,
'--credential',
value.credentialFile,
'--session',
value.sessionFile,
'--package-management-config',
value.packageManagementConfigFile,
'--package-management-assertion',
value.packageManagementAssertionFile,
'--port=0',
],
{ cwd: packageRoot, stdio: ['ignore', 'pipe', 'pipe'] },
);
t.after(() => {
if (child.exitCode === null && child.signalCode === null)
child.kill('SIGKILL');
});
const started = JSON.parse(await firstLine(child.stdout));
assert.equal(started.packageManagementAuthority, 'server_only');
const response = await post(
started.origin,
value.sessionToken,
'/api/v1/package-management/installations',
{
schema: 'qinglong/cluster-copilot-console-read-request@v1',
operation: 'package_list',
projectId: 'project-main',
requestId: 'console-package-list-1',
afterPackageName: null,
},
);
assert.equal(response.statusCode, 200);
assert.equal(
response.body.result.result.schema,
'qinglong/plugin-package-installation-list@v1',
);
assert.equal(response.body.result.result.count, 1);
assert.equal(response.body.result.result.nextAfterPackageName, 'ops-package');
const encoded = JSON.stringify(response.body);
assert.equal(encoded.includes('package-transport-hidden'), false);
assert.equal(encoded.includes('installation-transport-hidden'), false);
assert.equal(encoded.includes('recordDigest'), false);
const management = value.requests.find(
(request) => request.path === '/api/v3/plugin-packages/management',
);
assert.equal(management.method, 'POST');
assert.equal(
management.command.operation,
'plugin-package.installation.list',
);
assert.equal(
management.command.request.inspectionId,
'console-package-list-1',
);
assert.equal(management.command.request.limit, 16);
child.kill('SIGTERM');
const exit = await new Promise((resolve, reject) => {
child.once('error', reject);
child.once('close', (status, signal) => resolve({ status, signal }));
});
assert.deepEqual(exit, { status: 0, signal: null });
});
test('container mode requires an explicit publish port before any authority read', async () => {
const result = await runCli([
'--container-published-loopback',
@@ -294,6 +294,65 @@ test('redacts Worker identity and preserves only bounded placement and capacity
);
});
test('redacts Package transport identity and keeps bounded installation state', async () => {
const bundle = await createClusterConsoleEvidenceBundle(
[
{
operation: 'package_list',
observedAtMs: 1_700_000_005_000,
request: {
schema: requestSchema,
operation: 'package_list',
afterPackageName: null,
projectId: 'project-sensitive',
requestId: 'package-request-sensitive',
},
fact: {
schema: 'qinglong/plugin-package-installation-list@v1',
projectId: 'project-sensitive',
count: 1,
installations: [
{
packageName: 'ops-package-sensitive',
packageVersion: '3.1.0',
installOperation: 'upgrade',
state: 'active',
targetGeneration: 4,
recoveryAction: 'none',
availability: 'active',
quarantineReason: null,
failureReason: null,
version: 7,
createdAtMs: 1_700_000_001_000,
updatedAtMs: 1_700_000_004_000,
installationId: 'must-not-export',
recordDigest: 'a'.repeat(64),
},
],
truncated: true,
nextAfterPackageName: 'ops-package-sensitive',
},
},
],
1_700_000_006_000,
webcrypto,
);
const entry = bundle.entries[0];
assert.equal(entry.operation, 'package_list');
assert.equal(entry.fact.installations[0].packageName, 'package-001');
assert.equal(entry.fact.nextAfterPackageName, 'package-001');
assert.equal(entry.fact.installations[0].installOperation, 'upgrade');
assert.equal(entry.fact.installations[0].state, 'active');
assert.equal(entry.fact.installations[0].targetGeneration, 4);
assert.equal(entry.fact.installations[0].installationId, undefined);
assert.equal(entry.fact.installations[0].recordDigest, 'digest-001');
assert.equal(entry.fact.installations[0].packageVersion, undefined);
assert.doesNotMatch(
JSON.stringify(bundle),
/ops-package-sensitive|package-request-sensitive|project-sensitive|must-not-export/,
);
});
test('fails closed on widened records, unsafe JSON and every capacity ceiling', async () => {
const error = { code: 'QL3_CLUSTER_CONSOLE_EVIDENCE_BUNDLE_INVALID' };
assert.throws(() => measureClusterConsoleEvidenceRecord(null), error);
@@ -147,6 +147,16 @@ test('cross-verifies every fixed Console read operation', async (t) => {
requestId: 'q-worker-inspect',
workerId: 'worker-1',
},
package_list: {
afterPackageName: null,
projectId: 'p-package',
requestId: 'q-package-list',
},
package_inspect: {
packageName: 'ops-package',
projectId: 'p-package',
requestId: 'q-package-inspect',
},
run_list: {
afterCreatedAtMs: null,
afterRunId: null,
@@ -247,7 +257,7 @@ test('cross-verifies every fixed Console read operation', async (t) => {
assert.equal(result.status, 'verified');
verified += result.bundle.entryCount;
}
assert.equal(verified, 18);
assert.equal(verified, 20);
});
test('CLI is secret-free on success, invalid input and usage errors', async (t) => {
@@ -0,0 +1,170 @@
'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const {
ClusterPluginPackageInstallationProductError,
createPluginPackageInstallationInspectionCommand,
createPluginPackageInstallationListCommand,
projectPluginPackageInstallationInspection,
projectPluginPackageInstallationList,
} = require('../dist/plugin-package/management/pluginPackageInstallationProduct.js');
function installation(overrides = {}) {
return {
installationId: 'installation-sensitive',
projectId: 'project-main',
packageName: 'ops-package',
packageVersion: '3.1.0',
operation: 'upgrade',
state: 'active',
targetGeneration: 4,
activeLockDigest: 'a'.repeat(64),
previousActiveLockDigest: 'b'.repeat(64),
recoveryAction: 'none',
availability: 'active',
quarantineReason: null,
quarantineAuthorizationMode: null,
quarantineEventDigest: null,
quarantinedAtMs: null,
withdrawalStatus: null,
withdrawalReceiptDigest: null,
withdrawalCommittedAtMs: null,
failureReason: null,
failedFrom: null,
failedAtMs: null,
version: 7,
createdAtMs: 100,
updatedAtMs: 200,
recordDigest: 'c'.repeat(64),
...overrides,
};
}
test('builds fixed bounded installation observation commands', () => {
assert.deepEqual(
createPluginPackageInstallationListCommand(
'project-main',
'after-package',
() => 'inspection-list',
),
{
schemaVersion: 1,
operation: 'plugin-package.installation.list',
request: {
projectId: 'project-main',
limit: 16,
after: { packageName: 'after-package' },
inspectionId: 'inspection-list',
},
},
);
assert.deepEqual(
createPluginPackageInstallationInspectionCommand(
'project-main',
'ops-package',
() => 'inspection-one',
),
{
schemaVersion: 1,
operation: 'plugin-package.installation.inspect',
request: {
projectId: 'project-main',
packageName: 'ops-package',
inspectionId: 'inspection-one',
},
},
);
});
test('projects installation facts without transport or durable identifiers', () => {
const fact = projectPluginPackageInstallationList('project-main', {
schemaVersion: 1,
requestId: 'transport-request-sensitive',
result: {
operation: 'plugin-package.installation.list',
installations: [installation()],
truncated: true,
next: { packageName: 'ops-package' },
},
});
assert.deepEqual(fact, {
schema: 'qinglong/plugin-package-installation-list@v1',
projectId: 'project-main',
count: 1,
installations: [
{
packageName: 'ops-package',
packageVersion: '3.1.0',
installOperation: 'upgrade',
state: 'active',
targetGeneration: 4,
recoveryAction: 'none',
availability: 'active',
quarantineReason: null,
failureReason: null,
version: 7,
createdAtMs: 100,
updatedAtMs: 200,
},
],
truncated: true,
nextAfterPackageName: 'ops-package',
});
const encoded = JSON.stringify(fact);
for (const secret of [
'installation-sensitive',
'transport-request-sensitive',
'activeLockDigest',
'recordDigest',
]) {
assert.equal(encoded.includes(secret), false);
}
});
test('inspection binds the selected package and fails closed on drift', () => {
const response = {
schemaVersion: 1,
requestId: 'request-one',
result: {
operation: 'plugin-package.installation.inspect',
installation: installation(),
},
};
assert.equal(
projectPluginPackageInstallationInspection(
'project-main',
'ops-package',
response,
).found,
true,
);
assert.throws(
() =>
projectPluginPackageInstallationInspection(
'project-main',
'other-package',
response,
),
ClusterPluginPackageInstallationProductError,
);
assert.throws(
() =>
projectPluginPackageInstallationInspection(
'project-main',
'ops-package',
{
...response,
result: {
...response.result,
installation: installation({
recoveryAction: 'run-arbitrary-code',
}),
},
},
),
ClusterPluginPackageInstallationProductError,
);
});