mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): add read-only cluster copilot console
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
@@ -47,3 +48,13 @@ test('fails closed before Docker without explicit opt-in', () => {
|
||||
assert.match(result.stderr, /QL3_CLUSTER_ADMIN_PRODUCT_LIVE=1 is required/);
|
||||
assert.equal(result.stderr.includes('spawn'), false);
|
||||
});
|
||||
|
||||
test('binds the live image gate to loopback Console assets and shutdown', () => {
|
||||
const source = fs.readFileSync(script, 'utf8');
|
||||
assert.match(source, /function runConsoleContract\(image\)/);
|
||||
assert.match(source, /\[facade, 'copilot-console'/);
|
||||
assert.match(source, /body\.includes\('Cluster field console'\)/);
|
||||
assert.match(source, /runConsoleContract\(image\);/);
|
||||
assert.match(source, /consoleLoopback: true/);
|
||||
assert.match(source, /consoleAssets: true/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const {
|
||||
auditClusterCopilotConsole,
|
||||
} = require('../../scripts/ql3-cluster-copilot-console-audit.cjs');
|
||||
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
|
||||
function intercept(target, mutate) {
|
||||
return (relativePath) => {
|
||||
const source = fs.readFileSync(path.join(root, relativePath), 'utf8');
|
||||
return relativePath === target ? mutate(source) : source;
|
||||
};
|
||||
}
|
||||
|
||||
test('keeps the QingLong 3.0 Copilot Console independent and read-only', () => {
|
||||
const report = auditClusterCopilotConsole({ root });
|
||||
assert.deepEqual(report, {
|
||||
schemaVersion: 1,
|
||||
component: 'cluster-copilot-console',
|
||||
owner: '@qinglong/cluster-admin',
|
||||
lifecycle: 'operator-workstation-loopback',
|
||||
operations: ['inspect', 'output'],
|
||||
legacyUiCoupled: false,
|
||||
kubernetesResident: false,
|
||||
assetCount: 3,
|
||||
sourceFileCount: 4,
|
||||
findings: [],
|
||||
compatible: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects a remote listener or mutation vocabulary', () => {
|
||||
const listener = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
'packages/ql3-cluster-admin/src/copilot-console/server.ts',
|
||||
(source) => source.replaceAll('127.0.0.1', '0.0.0.0'),
|
||||
),
|
||||
});
|
||||
const mutation = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
'packages/ql3-cluster-admin/src/copilot-console/contracts.ts',
|
||||
(source) =>
|
||||
source.replace(
|
||||
"'inspect' | 'output'",
|
||||
"'inspect' | 'output' | 'cancel'",
|
||||
),
|
||||
),
|
||||
});
|
||||
assert.equal(listener.compatible, false);
|
||||
assert.equal(mutation.compatible, false);
|
||||
assert.ok(
|
||||
listener.findings.some(
|
||||
({ code }) => code === 'CLUSTER_COPILOT_CONSOLE_CONTRACT_MISSING',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
mutation.findings.some(
|
||||
({ code }) => code === 'CLUSTER_COPILOT_CONSOLE_AUTHORITY_WIDENED',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects browser persistence, dynamic rendering and product drift', () => {
|
||||
for (const injected of ['localStorage', 'innerHTML', 'WebSocket']) {
|
||||
const report = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
'packages/ql3-cluster-admin/assets/copilot-console/app.js',
|
||||
(source) => source + '\n// ' + injected + '\n',
|
||||
),
|
||||
});
|
||||
assert.equal(report.compatible, false);
|
||||
assert.ok(
|
||||
report.findings.some(
|
||||
({ code }) => code === 'CLUSTER_COPILOT_CONSOLE_AUTHORITY_WIDENED',
|
||||
),
|
||||
);
|
||||
}
|
||||
const product = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
'packages/ql3-cluster-admin/src/product-cli/productCommand.ts',
|
||||
(source) => source.replace("name: 'copilot-console'", "name: 'removed'"),
|
||||
),
|
||||
});
|
||||
assert.equal(product.compatible, false);
|
||||
assert.ok(
|
||||
product.findings.some(
|
||||
({ code }) => code === 'CLUSTER_COPILOT_CONSOLE_PRODUCT_ENTRY_MISSING',
|
||||
),
|
||||
);
|
||||
const image = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
'deploy/containers/ql3-cluster-admin/Dockerfile',
|
||||
(source) =>
|
||||
source.replaceAll(
|
||||
'packages/ql3-cluster-admin/assets/copilot-console',
|
||||
'packages/ql3-cluster-admin/assets/removed',
|
||||
),
|
||||
),
|
||||
});
|
||||
assert.equal(image.compatible, false);
|
||||
assert.ok(
|
||||
image.findings.some(
|
||||
({ code, target }) =>
|
||||
code === 'CLUSTER_COPILOT_CONSOLE_CONTRACT_MISSING' &&
|
||||
target === 'deploy/containers/ql3-cluster-admin/Dockerfile',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects coupling into the legacy UI or Kubernetes workloads', () => {
|
||||
const legacyTarget = 'src/pages/login/index.tsx';
|
||||
const legacy = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
legacyTarget,
|
||||
(source) => source + '\n// ql3-copilot-console\n',
|
||||
),
|
||||
});
|
||||
const kubernetesTarget =
|
||||
'deploy/kubernetes/ql3-cluster/base/deployment.yaml';
|
||||
const kubernetes = auditClusterCopilotConsole({
|
||||
root,
|
||||
readFile: intercept(
|
||||
kubernetesTarget,
|
||||
(source) => source + '\n# ql3-copilot-console\n',
|
||||
),
|
||||
});
|
||||
assert.equal(legacy.compatible, false);
|
||||
assert.equal(kubernetes.compatible, false);
|
||||
assert.ok(
|
||||
legacy.findings.some(
|
||||
({ code, target }) =>
|
||||
code === 'CLUSTER_COPILOT_CONSOLE_LEGACY_UI_COUPLED' &&
|
||||
target === legacyTarget,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
kubernetes.findings.some(
|
||||
({ code, target }) =>
|
||||
code === 'CLUSTER_COPILOT_CONSOLE_KUBERNETES_RESIDENT' &&
|
||||
target === kubernetesTarget,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -14,6 +14,10 @@ const {
|
||||
test('ships runtime JavaScript and declarations without development maps', () => {
|
||||
for (const [packagePath, files] of [
|
||||
['packages/ql3-runtime-core', ['dist/**/*.js', 'dist/**/*.d.ts']],
|
||||
[
|
||||
'packages/ql3-cluster-admin',
|
||||
['dist/**/*.js', 'dist/**/*.d.ts', 'assets/copilot-console/*'],
|
||||
],
|
||||
[
|
||||
'packages/ql3-local-process',
|
||||
['dist/**/*.js', 'dist/**/*.d.ts', 'assets'],
|
||||
|
||||
@@ -135,7 +135,7 @@ function createFixture(t, options = {}) {
|
||||
? isControlAi
|
||||
? 'Optional QingLong 3.0 AI-enabled cluster control plane'
|
||||
: 'QingLong 3.0 PostgreSQL-backed cluster control plane'
|
||||
: 'QingLong 3.0 cluster operations and bounded stdio MCP',
|
||||
: 'QingLong 3.0 cluster operations and bounded Copilot surfaces',
|
||||
'org.opencontainers.image.licenses': 'Apache-2.0',
|
||||
'org.opencontainers.image.revision': revision,
|
||||
'org.opencontainers.image.source':
|
||||
|
||||
@@ -340,10 +340,10 @@ test('current QL3 workspace has exactly eighteen reviewed package boundaries', (
|
||||
rootSourceFileRoles: clusterAdmin.rootSourceFileRoles,
|
||||
},
|
||||
{
|
||||
sourceFiles: 116,
|
||||
sourceFiles: 120,
|
||||
rootSourceFiles: 1,
|
||||
rootSourceLines: 61,
|
||||
nestedSourceFiles: 115,
|
||||
nestedSourceFiles: 119,
|
||||
rootSourceFileRoles: {
|
||||
'modelInvocationMigrationCli.ts': 'binary_entry',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user