mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 08:05:22 +08:00
fix(ql3): preserve adopted baseline during capture
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# QingLong 3.0 Architecture RFC
|
# QingLong 3.0 Architecture RFC
|
||||||
|
|
||||||
- D-426c1/ADR-0526(源码与 exact artifact gate 已闭合,双架构阶段实物待交付):downloadable `upgrade-cutover-rehearsal.sh` 保留既有 clean rollback 默认路径,并新增显式 `--capture-after-write <独立私有根>`。同一 reviewed stage/Owner/apply/target-active 链先通过正式 `task.put` 提交固定、无网络/Secret 的业务 Task,再要求 target stop 返回 `reconciliation_required`;随后以现有短生命周期 Operator 串行执行 reconciliation capture prepare/commit/verify,把 legacy、target、recovery、Application config、activation 与 exact stopped head/record 密封到外置 root。成功 summary 固定 `reconciliation_captured`、`legacySource=unchanged`、`target=stopped`、`rollback=not_authorized`、`next=review_required`,不自动回退或应用。Trial Kit/verification/auditor 升为 `@v9/@v7/@v6`、manifest schemaVersion 10,并增加 required `legacyUpgradeReconciliationCapture=passed`;原生 artifact job 必须在保留 clean rollback 演练的同时,用独立目录/容器第二次实跑写后 capture 并验证 manifest/receipt/assets 后才能上传。该切片不修改核心 classifier、不新增 package/依赖/daemon/listener/timer/watcher/连接或稳态资源;低配路由器默认 headless 不变。它证明的是 active target 数据权威经 Owner 产品入口发生写入后的 fail-closed capture,不冒充普通 Local API listener、2.x 老面板、自动 reconciliation、生产升级或 Public Release。
|
- D-426c1/ADR-0526(源码与 exact artifact gate 已闭合,双架构阶段实物待交付):downloadable `upgrade-cutover-rehearsal.sh` 保留既有 clean rollback 默认路径,并新增显式 `--capture-after-write <独立私有根>`。同一 reviewed stage/Owner/apply/target-active 链先通过正式 `task.put` 提交固定、无网络/Secret 的业务 Task,再要求 target stop 返回 `reconciliation_required`;随后以现有短生命周期 Operator 串行执行 reconciliation capture prepare/commit/verify,把 legacy、target、recovery、Application config、activation 与 exact stopped head/record 密封到外置 root。对 v4 adopted target,capture proof 必须以 stopped evidence 中的 exact baseline digest 重新加载并核对 durable post-apply baseline,不允许丢失 baseline 维度后再比较。成功 summary 固定 `reconciliation_captured`、`legacySource=unchanged`、`target=stopped`、`rollback=not_authorized`、`next=review_required`,不自动回退或应用。Trial Kit/verification/auditor 升为 `@v9/@v7/@v6`、manifest schemaVersion 10,并增加 required `legacyUpgradeReconciliationCapture=passed`;原生 artifact job 必须在保留 clean rollback 演练的同时,用独立目录/容器第二次实跑写后 capture 并验证 manifest/receipt/assets 后才能上传。该切片不修改核心 classifier、不新增 package/依赖/daemon/listener/timer/watcher/连接或稳态资源;低配路由器默认 headless 不变。它证明的是 active target 数据权威经 Owner 产品入口发生写入后的 fail-closed capture,不冒充普通 Local API listener、2.x 老面板、自动 reconciliation、生产升级或 Public Release。
|
||||||
|
|
||||||
- D-426b2c/ADR-0525(exact Console 双架构阶段实物已交付):Console adopted target 不再借用 fresh HTTP journey,也不以普通 Local API 启动破坏 clean rollback。`ql3-local-api` 新增显式 `--cutover-probe --config <outer-config>`:严格验证外层 loopback/deployment/Owner 配置后委托既有 Application 只读 probe,不绑定 listener、不读取 credential/pepper、不激活 recovery、scheduler、execution、plugin recovery 或产品管理面。Owner target command 可选绑定 Local API 宿主/容器配置路径,target evidence 同时摘要并校验外层 API、内层 Application、严格不同的 target path、exact read-only mounts 与 `['--cutover-probe','--config',expectedApiPath]`;省略该字段时 headless command/journal digest 不变。Trial Kit cutover summary 升为 v2,绑定 variant 与 `local-application|local-api` entrypoint;原生 workflow 对 headless/Console 均要求 `legacyUpgradeCutover=passed`,同时保留真实 Console listener/API/credential/Task journey 作为独立门。没有新增 workspace package、依赖、daemon、sidecar、timer、watcher、连接池或稳态资源;默认低配路由设备仍选择 headless,Cluster 不复用 Local POSIX/SQLite/Docker proof。Local API 80/80、Owner CLI 314 total/307 pass/7 conditional skip/0 fail、Trial Kit 12/12、Application 56 total/51 pass/5 conditional skip/0 fail,package/Cluster/Edge/image 审计 compatible。提交 `229c3cb4e826866a0c7c4d81cb5e52cdc3975eec` 的普通主 CI run `33462165722` 与 Kubernetes live run `33462165834` 成功;显式 Local Console artifact run `33463415938` 交付 amd64/arm64/milestone(artifact `9784212784`/`9784111987`/`9784288018`),三个重新下载的离线 auditor 均为 `compatible=true`,保留至 2026-10-01。正常 `ql3-local-api --config` 提供现有 3.0 Console,`--cutover-probe` 仅用于无写入的升级证据;这不声明 2.x 老面板 API 零改动兼容。
|
- D-426b2c/ADR-0525(exact Console 双架构阶段实物已交付):Console adopted target 不再借用 fresh HTTP journey,也不以普通 Local API 启动破坏 clean rollback。`ql3-local-api` 新增显式 `--cutover-probe --config <outer-config>`:严格验证外层 loopback/deployment/Owner 配置后委托既有 Application 只读 probe,不绑定 listener、不读取 credential/pepper、不激活 recovery、scheduler、execution、plugin recovery 或产品管理面。Owner target command 可选绑定 Local API 宿主/容器配置路径,target evidence 同时摘要并校验外层 API、内层 Application、严格不同的 target path、exact read-only mounts 与 `['--cutover-probe','--config',expectedApiPath]`;省略该字段时 headless command/journal digest 不变。Trial Kit cutover summary 升为 v2,绑定 variant 与 `local-application|local-api` entrypoint;原生 workflow 对 headless/Console 均要求 `legacyUpgradeCutover=passed`,同时保留真实 Console listener/API/credential/Task journey 作为独立门。没有新增 workspace package、依赖、daemon、sidecar、timer、watcher、连接池或稳态资源;默认低配路由设备仍选择 headless,Cluster 不复用 Local POSIX/SQLite/Docker proof。Local API 80/80、Owner CLI 314 total/307 pass/7 conditional skip/0 fail、Trial Kit 12/12、Application 56 total/51 pass/5 conditional skip/0 fail,package/Cluster/Edge/image 审计 compatible。提交 `229c3cb4e826866a0c7c4d81cb5e52cdc3975eec` 的普通主 CI run `33462165722` 与 Kubernetes live run `33462165834` 成功;显式 Local Console artifact run `33463415938` 交付 amd64/arm64/milestone(artifact `9784212784`/`9784111987`/`9784288018`),三个重新下载的离线 auditor 均为 `compatible=true`,保留至 2026-10-01。正常 `ql3-local-api --config` 提供现有 3.0 Console,`--cutover-probe` 仅用于无写入的升级证据;这不声明 2.x 老面板 API 零改动兼容。
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ ADR-0524/0525 已让 downloadable Trial Kit 在 headless 与 Console 两种变
|
|||||||
5. 成功后生成 `qinglong/local-alpha-upgrade-reconciliation-capture-summary@v1`,只记录 source/architecture/Profile/variant、固定 synthetic Task identity、cutover/capture digest、asset count/bytes 与 `legacySource=unchanged`、`target=stopped`、`rollback=not_authorized`、`next=review_required`。真实 capture 可能包含数据库、配置和 Secret 密文,必须继续保存在操作者指定的 owner-private root,不能上传为普通低敏 CI summary。
|
5. 成功后生成 `qinglong/local-alpha-upgrade-reconciliation-capture-summary@v1`,只记录 source/architecture/Profile/variant、固定 synthetic Task identity、cutover/capture digest、asset count/bytes 与 `legacySource=unchanged`、`target=stopped`、`rollback=not_authorized`、`next=review_required`。真实 capture 可能包含数据库、配置和 Secret 密文,必须继续保存在操作者指定的 owner-private root,不能上传为普通低敏 CI summary。
|
||||||
6. Trial Kit、verification 与 offline auditor 分别升级为 `qinglong/alpha-local-trial-kit@v9`、verification `@v7` 与 audit `@v6`;manifest schemaVersion 升为 10,并增加 required gate `legacyUpgradeReconciliationCapture=passed`。旧 v8/v6/v5 bundle 不会被新 auditor 静默接受。
|
6. Trial Kit、verification 与 offline auditor 分别升级为 `qinglong/alpha-local-trial-kit@v9`、verification `@v7` 与 audit `@v6`;manifest schemaVersion 升为 10,并增加 required gate `legacyUpgradeReconciliationCapture=passed`。旧 v8/v6/v5 bundle 不会被新 auditor 静默接受。
|
||||||
7. 原生 amd64/arm64 artifact job 在上传前先保留既有 clean rollback 演练,再从同一 unchanged Legacy fixture、同一 exact bundle 用独立 rehearsal/capture root 与容器名实跑写后 capture。workflow 必须验证 summary、terminal verify、manifest/receipt/assets、legacy 无 WAL/journal,并删除四个 synthetic 容器;任一步失败都不得形成 Local milestone。
|
7. 原生 amd64/arm64 artifact job 在上传前先保留既有 clean rollback 演练,再从同一 unchanged Legacy fixture、同一 exact bundle 用独立 rehearsal/capture root 与容器名实跑写后 capture。workflow 必须验证 summary、terminal verify、manifest/receipt/assets、legacy 无 WAL/journal,并删除四个 synthetic 容器;任一步失败都不得形成 Local milestone。
|
||||||
|
8. exact arm64 首次演练证明 `task.put` 与 `reconciliation_required` 已成功,但暴露原有 capture proof 重算 v4 adopted target 时未带入 target-stop 已验证的 post-apply baseline,因而把同一次 stopped evidence 误报为漂移。修复仅在 persisted stopped evidence 明确携带 `baselineKind=adopted_target` 时加载 durable baseline,并严格核对 Profile、instance/cutover、activation、target path 与 exact baseline digest 后才重算;缺失、篡改、换路径或换 lineage 仍失败关闭,legacy 与 service-manager 路径不变。
|
||||||
|
|
||||||
## Profile 与资源边界
|
## Profile 与资源边界
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ ADR-0524/0525 已让 downloadable Trial Kit 在 headless 与 Console 两种变
|
|||||||
|
|
||||||
- 编辑前 GitNexus:Trial Kit verification/create 为 LOW,offline auditor 为 MEDIUM(5 direct、12 total、0 process),Operator workflow auditor 为 LOW;没有 HIGH/CRITICAL 编辑目标。Shell 模板未被索引,使用 backward-compatible 参数、`sh -n`、静态 contract 与原生 Docker artifact gate 约束。
|
- 编辑前 GitNexus:Trial Kit verification/create 为 LOW,offline auditor 为 MEDIUM(5 direct、12 total、0 process),Operator workflow auditor 为 LOW;没有 HIGH/CRITICAL 编辑目标。Shell 模板未被索引,使用 backward-compatible 参数、`sh -n`、静态 contract 与原生 Docker artifact gate 约束。
|
||||||
- 聚焦 bundle/operator 静态回归为 18/18,证明 v9/v7/v6 schema、canonical script、required gate、exact `task.put`/capture command 与 workflow order 闭合。全部 18 个 `packages/ql3-*` 已重新编译并通过自身契约测试;后端脚本层全量回归为 1661 total / 1659 pass / 2 conditional skip / 0 fail。Package boundary(18 个 package、无 single/shallow source package)、Edge import、Cluster dependency 与 Local Operator image audit 全部 `compatible=true`。
|
- 聚焦 bundle/operator 静态回归为 18/18,证明 v9/v7/v6 schema、canonical script、required gate、exact `task.put`/capture command 与 workflow order 闭合。全部 18 个 `packages/ql3-*` 已重新编译并通过自身契约测试;后端脚本层全量回归为 1661 total / 1659 pass / 2 conditional skip / 0 fail。Package boundary(18 个 package、无 single/shallow source package)、Edge import、Cluster dependency 与 Local Operator image audit 全部 `compatible=true`。
|
||||||
|
- 首次 exact Console artifact run `33467541901` 在 arm64 上已真实证明写入与 `reconciliation_required`,随后于 capture prepare 因 v4 adopted baseline 重算缺口失败,且未上传 Trial Kit,因而不计为交付。修复后 reconciliation 聚焦回归为 72 total / 70 pass / 2 conditional Docker skip / 0 fail,新增 adopted-baseline 正向与篡改失败关闭用例;Owner CLI 全量为 316 total / 309 pass / 7 conditional skip / 0 fail。仍必须以新 source commit 重跑原生双架构 workflow。
|
||||||
- exact Docker 正向证据必须来自新的 workflow source commit;在该 run 与双架构 milestone 实际成功、重新下载并离线复核前,本 ADR 不宣称 D-426c1 已形成可下载阶段实物。
|
- exact Docker 正向证据必须来自新的 workflow source commit;在该 run 与双架构 milestone 实际成功、重新下载并离线复核前,本 ADR 不宣称 D-426c1 已形成可下载阶段实物。
|
||||||
|
|
||||||
## 后续
|
## 后续
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { readPrivateLocalCommandFile } from '@qinglong/local-command-file';
|
import { readPrivateLocalCommandFile } from '@qinglong/local-command-file';
|
||||||
|
|
||||||
import { LocalDeploymentConfigurationError } from '../foundation/error';
|
import { LocalDeploymentConfigurationError } from '../foundation/error';
|
||||||
|
import {
|
||||||
|
adoptedTargetBaselinePath,
|
||||||
|
readAdoptedTargetBaseline,
|
||||||
|
} from '../cutover/targetBaseline';
|
||||||
import { cutoverDigest } from '../cutover/targetEvidence';
|
import { cutoverDigest } from '../cutover/targetEvidence';
|
||||||
import {
|
import {
|
||||||
readTargetDataReconciliationEvidenceForPaths,
|
readTargetDataReconciliationEvidenceForPaths,
|
||||||
@@ -177,6 +182,11 @@ export function proveLocalReconciliationStoppedState(
|
|||||||
command.request.stoppedAuthority === 'docker'
|
command.request.stoppedAuthority === 'docker'
|
||||||
? dockerStoppedEvidence(command)
|
? dockerStoppedEvidence(command)
|
||||||
: (serviceManagerStoppedRecord(command), undefined);
|
: (serviceManagerStoppedRecord(command), undefined);
|
||||||
|
const adoptedTargetBaseline =
|
||||||
|
persisted?.baselineKind === 'adopted_target' &&
|
||||||
|
persisted.baselineDigest !== undefined
|
||||||
|
? readAdoptedTargetBaselineProjection(command, persisted.baselineDigest)
|
||||||
|
: undefined;
|
||||||
const current = readTargetDataReconciliationEvidenceForPaths(
|
const current = readTargetDataReconciliationEvidenceForPaths(
|
||||||
{
|
{
|
||||||
profile: command.request.profile,
|
profile: command.request.profile,
|
||||||
@@ -184,6 +194,7 @@ export function proveLocalReconciliationStoppedState(
|
|||||||
legacySourcePath: command.request.legacySourcePath,
|
legacySourcePath: command.request.legacySourcePath,
|
||||||
targetDatabasePath: command.request.targetDatabasePath,
|
targetDatabasePath: command.request.targetDatabasePath,
|
||||||
expectedActivationDigest: command.request.expectedActivationDigest,
|
expectedActivationDigest: command.request.expectedActivationDigest,
|
||||||
|
...(adoptedTargetBaseline === undefined ? {} : { adoptedTargetBaseline }),
|
||||||
},
|
},
|
||||||
uid,
|
uid,
|
||||||
);
|
);
|
||||||
@@ -203,3 +214,39 @@ export function proveLocalReconciliationStoppedState(
|
|||||||
});
|
});
|
||||||
return Object.freeze({ ...payload, proofDigest: cutoverDigest(payload) });
|
return Object.freeze({ ...payload, proofDigest: cutoverDigest(payload) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readAdoptedTargetBaselineProjection(
|
||||||
|
command: Readonly<LocalReconciliationCapturePrepareCommand>,
|
||||||
|
expectedBaselineDigest: string,
|
||||||
|
): Readonly<{
|
||||||
|
baselineDigest: string;
|
||||||
|
targetDevice: string;
|
||||||
|
targetInode: string;
|
||||||
|
targetSha256: string;
|
||||||
|
}> {
|
||||||
|
const baseline = readAdoptedTargetBaseline(
|
||||||
|
adoptedTargetBaselinePath(command.options.deploymentRoot),
|
||||||
|
);
|
||||||
|
const targetPathDigest = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(command.request.targetDatabasePath, 'utf8')
|
||||||
|
.digest('hex');
|
||||||
|
if (
|
||||||
|
baseline.profile !== command.request.profile ||
|
||||||
|
baseline.instanceId !== command.request.instanceId ||
|
||||||
|
baseline.cutoverId !== command.request.cutoverId ||
|
||||||
|
baseline.activationDigest !== command.request.expectedActivationDigest ||
|
||||||
|
baseline.targetPathDigest !== targetPathDigest ||
|
||||||
|
baseline.baselineDigest !== expectedBaselineDigest
|
||||||
|
) {
|
||||||
|
configurationError(
|
||||||
|
'adopted target baseline is detached from stopped evidence',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Object.freeze({
|
||||||
|
baselineDigest: baseline.baselineDigest,
|
||||||
|
targetDevice: baseline.targetDevice,
|
||||||
|
targetInode: baseline.targetInode,
|
||||||
|
targetSha256: baseline.targetSha256,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ const {
|
|||||||
const {
|
const {
|
||||||
readTargetDataReconciliationEvidenceForPaths,
|
readTargetDataReconciliationEvidenceForPaths,
|
||||||
} = require('../dist/deployment/cutover/targetDataEvidence.js');
|
} = require('../dist/deployment/cutover/targetDataEvidence.js');
|
||||||
|
const {
|
||||||
|
adoptedTargetBaselinePath,
|
||||||
|
createAdoptedTargetBaseline,
|
||||||
|
} = require('../dist/deployment/cutover/targetBaseline.js');
|
||||||
const {
|
const {
|
||||||
targetRunJournalRecord,
|
targetRunJournalRecord,
|
||||||
targetStopPhasePath,
|
targetStopPhasePath,
|
||||||
@@ -121,6 +125,7 @@ function fixture(
|
|||||||
stoppedAuthority = 'docker',
|
stoppedAuthority = 'docker',
|
||||||
profile = 'edge',
|
profile = 'edge',
|
||||||
createDefaultSidecars = true,
|
createDefaultSidecars = true,
|
||||||
|
useAdoptedTargetBaseline = false,
|
||||||
initializeDatabases,
|
initializeDatabases,
|
||||||
mutateTarget,
|
mutateTarget,
|
||||||
} = {},
|
} = {},
|
||||||
@@ -323,6 +328,32 @@ function fixture(
|
|||||||
};
|
};
|
||||||
const applicationContents = `${JSON.stringify(application, null, 2)}\n`;
|
const applicationContents = `${JSON.stringify(application, null, 2)}\n`;
|
||||||
fs.writeFileSync(applicationConfigPath, applicationContents, { mode: 0o600 });
|
fs.writeFileSync(applicationConfigPath, applicationContents, { mode: 0o600 });
|
||||||
|
let adoptedTargetBaseline;
|
||||||
|
if (useAdoptedTargetBaseline) {
|
||||||
|
adoptedTargetBaseline = createAdoptedTargetBaseline({
|
||||||
|
preparedAtMs: 1_350,
|
||||||
|
profile,
|
||||||
|
instanceId: 'edge-router-1',
|
||||||
|
cutoverId,
|
||||||
|
activationDigest,
|
||||||
|
commitmentDigest,
|
||||||
|
applicationConfigDigest: crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(applicationContents, 'utf8')
|
||||||
|
.digest('hex'),
|
||||||
|
legacyDataApplicationCommitDigest: dataCommit.commitDigest,
|
||||||
|
legacyDataApplicationReceiptDigest: dataCommit.receiptDigest,
|
||||||
|
targetPathDigest: activationPayload.targetPathDigest,
|
||||||
|
targetDevice: activationPayload.targetDevice,
|
||||||
|
targetInode: activationPayload.targetInode,
|
||||||
|
targetSha256: activationPayload.targetSha256,
|
||||||
|
});
|
||||||
|
fs.writeFileSync(
|
||||||
|
adoptedTargetBaselinePath(deploymentRoot),
|
||||||
|
`${JSON.stringify(adoptedTargetBaseline)}\n`,
|
||||||
|
{ mode: 0o600 },
|
||||||
|
);
|
||||||
|
}
|
||||||
const adoptedBundlePayload = {
|
const adoptedBundlePayload = {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
kind: 'qinglong3-local-adopted-deployment-bundle',
|
kind: 'qinglong3-local-adopted-deployment-bundle',
|
||||||
@@ -397,6 +428,16 @@ function fixture(
|
|||||||
legacySourcePath,
|
legacySourcePath,
|
||||||
targetDatabasePath,
|
targetDatabasePath,
|
||||||
expectedActivationDigest: activationDigest,
|
expectedActivationDigest: activationDigest,
|
||||||
|
...(adoptedTargetBaseline === undefined
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
adoptedTargetBaseline: {
|
||||||
|
baselineDigest: adoptedTargetBaseline.baselineDigest,
|
||||||
|
targetDevice: adoptedTargetBaseline.targetDevice,
|
||||||
|
targetInode: adoptedTargetBaseline.targetInode,
|
||||||
|
targetSha256: adoptedTargetBaseline.targetSha256,
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
uid,
|
uid,
|
||||||
);
|
);
|
||||||
@@ -514,6 +555,8 @@ function fixture(
|
|||||||
legacySourcePath,
|
legacySourcePath,
|
||||||
targetDatabasePath,
|
targetDatabasePath,
|
||||||
recoveryPath,
|
recoveryPath,
|
||||||
|
adoptedTargetBaseline,
|
||||||
|
reconciliation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,6 +630,42 @@ test('capture prepare rejects rollback-candidate stopped data', (t) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('capture prepare preserves exact adopted-target baseline evidence', (t) => {
|
||||||
|
const state = fixture(t, {
|
||||||
|
createDefaultSidecars: false,
|
||||||
|
useAdoptedTargetBaseline: true,
|
||||||
|
});
|
||||||
|
assert.ok(state.adoptedTargetBaseline);
|
||||||
|
assert.equal(state.reconciliation.baselineKind, 'adopted_target');
|
||||||
|
assert.equal(
|
||||||
|
state.reconciliation.baselineDigest,
|
||||||
|
state.adoptedTargetBaseline.baselineDigest,
|
||||||
|
);
|
||||||
|
assert.equal(state.reconciliation.targetMatchesBaseline, false);
|
||||||
|
const prepared = prepareLocalReconciliationCapture(state.command);
|
||||||
|
assert.equal(prepared.status, 'prepared');
|
||||||
|
assert.equal(prepared.state, 'reconciliation_capture_prepared');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('capture prepare rejects a valid but detached adopted-target baseline', (t) => {
|
||||||
|
const state = fixture(t, {
|
||||||
|
createDefaultSidecars: false,
|
||||||
|
useAdoptedTargetBaseline: true,
|
||||||
|
});
|
||||||
|
const baselinePath = adoptedTargetBaselinePath(state.deploymentRoot);
|
||||||
|
const detachedBaseline = createAdoptedTargetBaseline({
|
||||||
|
...state.adoptedTargetBaseline,
|
||||||
|
targetSha256: 'f'.repeat(64),
|
||||||
|
});
|
||||||
|
fs.writeFileSync(baselinePath, `${JSON.stringify(detachedBaseline)}\n`, {
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
assert.throws(
|
||||||
|
() => prepareLocalReconciliationCapture(state.command),
|
||||||
|
/adopted target baseline is detached from stopped evidence/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('service-manager stopped authority uses the same capture fence', (t) => {
|
test('service-manager stopped authority uses the same capture fence', (t) => {
|
||||||
const state = fixture(t, { stoppedAuthority: 'service-manager' });
|
const state = fixture(t, { stoppedAuthority: 'service-manager' });
|
||||||
const prepared = prepareLocalReconciliationCapture(state.command);
|
const prepared = prepareLocalReconciliationCapture(state.command);
|
||||||
|
|||||||
Reference in New Issue
Block a user