From 4541d142d3caff3bfab6ca7e7f86b6b7c6fce078 Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 22 Aug 2026 20:48:22 +0800 Subject: [PATCH] fix(ci): authorize reconciliation apply boundaries --- docs/QINGLONG_3_0_ARCHITECTURE_RFC.md | 2 +- scripts/ql3-cluster-dependency-audit.cjs | 23 ++++++ test/back/ql3ClusterDependencyAudit.test.cjs | 78 ++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/docs/QINGLONG_3_0_ARCHITECTURE_RFC.md b/docs/QINGLONG_3_0_ARCHITECTURE_RFC.md index 3b3b0657..515b55d7 100644 --- a/docs/QINGLONG_3_0_ARCHITECTURE_RFC.md +++ b/docs/QINGLONG_3_0_ARCHITECTURE_RFC.md @@ -75,7 +75,7 @@ `44 total / 42 pass / 2 conditional Docker skip / 0 fail`,但成功场景现在额外覆盖首次 apply、mutation replay、不同 reviewer 拒绝、六个 backup/apply/rollback response-loss 窗口、content-free CLI verify、identity-preserving rollback 与 rollback replay。完整 Local Owner 在受限沙箱为 `266 total / 256 pass / 7 conditional skip / 3 loopback-listen EPERM`,两个对应文件在沙箱外 `15/15` 通过;package/dependency boundary 为 - `69/69`。workspace 仍为 18 packages、`singleSourcePackages=[]`、`shallowSourcePackages=[]`,Local Owner 为 + `70/70`。workspace 仍为 18 packages、`singleSourcePackages=[]`、`shallowSourcePackages=[]`,Local Owner 为 `168 source / 167 nested / 1 root binary entry`,没有新增 package、production dependency、daemon、listener、timer 或 `src/` 根平铺。 D-393 仍未代表完整 reconciliation:Automation 之外的 Secret、Plugin、Identity、history 等领域 adapter 尚未 apply,target service 也未获得 restart authority;后续必须先完成 apply-root 的 retention/seal 策略和跨领域 completion fence,再进入 target restart/readiness。 diff --git a/scripts/ql3-cluster-dependency-audit.cjs b/scripts/ql3-cluster-dependency-audit.cjs index 34930de8..9aa7829d 100644 --- a/scripts/ql3-cluster-dependency-audit.cjs +++ b/scripts/ql3-cluster-dependency-audit.cjs @@ -2027,6 +2027,21 @@ function auditSourceImports(root, packagePath, findings) { '@qinglong/local-sqlite/authentication-read', ].includes(specifier) ) && + !( + path.relative(packageDirectory, filePath) === + 'src/deployment/reconciliation/application/automation/applyCoordinator.ts' && + [ + '@qinglong/local-admin/reconciliation-automation-decision', + '@qinglong/local-owner-console/authenticated-command', + '@qinglong/local-sqlite/authentication-read', + '@qinglong/local-sqlite/rollout-safety', + ].includes(specifier) + ) && + !( + path.relative(packageDirectory, filePath) === + 'src/deployment/reconciliation/application/automation/applyEvidence.ts' && + specifier === '@qinglong/local-sqlite/rollout-safety' + ) && !( path.relative(packageDirectory, filePath) === 'src/deployment/reconciliation/application/automation/planReader.ts' && @@ -2691,6 +2706,14 @@ function auditSourceImports(root, packagePath, findings) { file: 'src/deployment/reconciliation/review/issuerKeyring.ts', specifiers: ['@qinglong/runtime-core/local-secret'], }, + { + file: 'src/deployment/reconciliation/application/automation/decisionCoordinator.ts', + specifiers: ['@qinglong/runtime-core/security'], + }, + { + file: 'src/deployment/reconciliation/application/automation/applyCoordinator.ts', + specifiers: ['@qinglong/runtime-core/security'], + }, ].some( ({ file, specifiers }) => path.relative(packageDirectory, filePath) === file && diff --git a/test/back/ql3ClusterDependencyAudit.test.cjs b/test/back/ql3ClusterDependencyAudit.test.cjs index 69ad6907..34097a67 100644 --- a/test/back/ql3ClusterDependencyAudit.test.cjs +++ b/test/back/ql3ClusterDependencyAudit.test.cjs @@ -2150,6 +2150,84 @@ test('confines reconciliation review authentication to exact read-only owners', ); }); +test('confines reconciliation automation apply authority to exact coordinators', (t) => { + const root = fs.mkdtempSync( + path.join(os.tmpdir(), 'ql3-reconciliation-automation-apply-boundary-'), + ); + const automationDirectory = path.join( + root, + 'packages/ql3-local-owner-cli/src/deployment/reconciliation/application/automation', + ); + fs.mkdirSync(automationDirectory, { recursive: true }); + fs.writeFileSync( + path.join(automationDirectory, 'decisionCoordinator.ts'), + [ + "import { decide } from '@qinglong/local-admin/reconciliation-automation-decision';", + "import { authenticate } from '@qinglong/local-owner-console/authenticated-command';", + "import { database } from '@qinglong/local-sqlite/authentication-read';", + "import type { Principal } from '@qinglong/runtime-core/security';", + ].join('\n'), + ); + fs.writeFileSync( + path.join(automationDirectory, 'applyCoordinator.ts'), + [ + "import { apply } from '@qinglong/local-admin/reconciliation-automation-decision';", + "import { authenticate } from '@qinglong/local-owner-console/authenticated-command';", + "import { database } from '@qinglong/local-sqlite/authentication-read';", + "import { backup } from '@qinglong/local-sqlite/rollout-safety';", + "import type { Principal } from '@qinglong/runtime-core/security';", + ].join('\n'), + ); + fs.writeFileSync( + path.join(automationDirectory, 'applyEvidence.ts'), + "import type { Evidence } from '@qinglong/local-sqlite/rollout-safety';", + ); + fs.writeFileSync( + path.join(automationDirectory, 'neighbor.ts'), + [ + "import { apply } from '@qinglong/local-admin/reconciliation-automation-decision';", + "import { authenticate } from '@qinglong/local-owner-console/authenticated-command';", + "import { database } from '@qinglong/local-sqlite/authentication-read';", + "import { backup } from '@qinglong/local-sqlite/rollout-safety';", + "import type { Principal } from '@qinglong/runtime-core/security';", + ].join('\n'), + ); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + + const findings = []; + auditSourceImports(root, 'packages/ql3-local-owner-cli', findings); + assert.deepEqual( + findings.map(({ code, file, specifier }) => ({ code, file, specifier })), + [ + { + code: 'FORBIDDEN_LOCAL_ADOPTION_CLI_AUTHORITY_IMPORT', + file: 'packages/ql3-local-owner-cli/src/deployment/reconciliation/application/automation/neighbor.ts', + specifier: '@qinglong/local-admin/reconciliation-automation-decision', + }, + { + code: 'FORBIDDEN_LOCAL_ADOPTION_CLI_AUTHORITY_IMPORT', + file: 'packages/ql3-local-owner-cli/src/deployment/reconciliation/application/automation/neighbor.ts', + specifier: '@qinglong/local-owner-console/authenticated-command', + }, + { + code: 'FORBIDDEN_LOCAL_ADOPTION_CLI_AUTHORITY_IMPORT', + file: 'packages/ql3-local-owner-cli/src/deployment/reconciliation/application/automation/neighbor.ts', + specifier: '@qinglong/local-sqlite/authentication-read', + }, + { + code: 'FORBIDDEN_LOCAL_ADOPTION_CLI_AUTHORITY_IMPORT', + file: 'packages/ql3-local-owner-cli/src/deployment/reconciliation/application/automation/neighbor.ts', + specifier: '@qinglong/local-sqlite/rollout-safety', + }, + { + code: 'FORBIDDEN_PACKAGE_SOURCE_IMPORT', + file: 'packages/ql3-local-owner-cli/src/deployment/reconciliation/application/automation/neighbor.ts', + specifier: '@qinglong/runtime-core/security', + }, + ], + ); +}); + test('deleted Owner ceremony package names remain dependency tombstones', (t) => { const root = fixture( t,