openxiangda 1.0.211 → 1.0.213

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -106,7 +106,7 @@ React SPA workspaces publish their frontend with `openxiangda runtime deploy`. E
106
106
 
107
107
  Parallel tasks develop and test in isolated worktrees, but feature worktrees do not publish. After approved commits are merged and pushed, `sdd bundle <release-change> --changes ...` unions their exact structured scope and preserves the source changes' common Git baseline instead of adopting the post-merge `HEAD`. Old imported changes that predate source-base metadata must pass `--source-base-ref <commit>` explicitly. Commit/push the bundle, then run `release publish --change <release-change> --profile <name>` from a clean local main/master whose commit exactly equals the authoritative remote tip. It verifies without rewriting reviewed SDD files, waits for the app lease, freezes one authoritative App capture, executes exact Form/Backend/Runtime staged steps, atomically finalizes the Root App release, and records a resumable local execution journal. Runtime checks use the narrow head endpoint; immutable Git-base artifact hashes are reused across plans.
108
108
 
109
- Because promotion starts from an already-pushed authoritative mainline commit, `openxiangda release integration-status --profile <name>` should pass immediately after activation. Run it and `release end`; there is no post-release merge step.
109
+ Because promotion starts from an already-pushed authoritative mainline commit, `openxiangda release integration-status --change <change> --profile <name>` should pass immediately after activation. After a managed ship has ended, `--change` recovers lineage directly from the private `ship.json` or follows its production/preproduction deployment IDs to the matching `execution.json`; a recovery failure names the missing file or field. Run it and `release end`; there is no post-release merge step.
110
110
 
111
111
  `openxiangda task status --change <id>` is the compact, read-only answer to
112
112
  “现在到哪一步”:it combines SDD、TaskResult、IntegrationBundle、execution
@@ -206,7 +206,7 @@ For source-only Function/Automation changes, the final command does not reconstr
206
206
 
207
207
  Page repair publishing is staged by default. It first freezes `pages/snapshot`, sends the active Page Release parent plus every page revision, and uses revision `0` only for a genuinely new page. Review with `openxiangda page head|releases|detail|diff`; activate an immutable complete release explicitly with `page activate <releaseId> --change <change>`. Historical activation requires `page rollback <releaseId> --rollback --change <change> --reason "..."`. Parent or revision conflicts are never refreshed or retried automatically.
208
208
 
209
- Use `openxiangda release app-capture` to read the platform's transactionally consistent whole-app manifest. For a normal multi-resource release, stage the changed Runtime/Page/Backend/Form child releases; commands carrying the same `--change` atomically accumulate their canonical immutable entries in `.openxiangda/releases/<change>/staged-resources.json`. Then run `app-finalize --change <change> --staged-resources-json <JSON|file>`. The CLI overlays Runtime/Page/Backend by singleton kind and Form by `formUuid`, preserves every unmodified active child from the read-only capture, and sends the complete frozen manifest through `prepare -> verify -> activate` with `activateStagedChildren=true`. Runtime staging never rewrites local active Runtime state, and a malformed staged response fails closed. The child heads and App head therefore switch in one database transaction (`atomic_staged_children_v1`); any parent, revision, asset, or hash drift stops with zero root activation and is never refreshed or retried. An aborted Form Release is never an idempotent success: rerunning the same scoped form publish removes its aborted staged index and creates a new immutable attempt automatically, after which the whole App is retried atomically. Never work around an App failure by sequentially activating forms. `app-prepare` accepts the same overlay for a manual reviewed flow, and `app-activate <releaseId> --activate-staged-children` performs the explicit atomic activation. For a legacy workspace whose already verified immutable Root cannot pass historical baseline/session compatibility, a platform administrator may use the audited break-glass path: `app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`. It skips only workspace/baseline/source compatibility checks; a fresh lease/baseline plus Root parent CAS, exact child identity/hash/parent/head, and the atomic transaction remain mandatory. Calling `app-finalize` without an overlay remains a compatibility-only retrospective aggregation of already-active children. `app-rollback <releaseId> --change <change> --reason "..."` prepares the audited rollback manifest.
209
+ Use `openxiangda release app-capture` to read the platform's transactionally consistent whole-app manifest. For a normal multi-resource release, stage the changed Runtime/Page/Backend/Form child releases; commands carrying the same `--change` atomically accumulate their canonical immutable entries in `.openxiangda/releases/<change>/staged-resources.json`. Then run `app-finalize --change <change> --staged-resources-json <JSON|file>`. The CLI overlays Runtime/Page/Backend by singleton kind and Form by `formUuid`, preserves every unmodified active child from the read-only capture, and sends the complete frozen manifest through `prepare -> verify -> activate` with `activateStagedChildren=true`. Runtime staging never rewrites local active Runtime state, and a malformed staged response fails closed. The child heads and App head therefore switch in one database transaction (`atomic_staged_children_v1`); any parent, revision, asset, or hash drift stops with zero root activation and is never refreshed or retried. An aborted Form Release is never an idempotent success: rerunning the same scoped form publish removes its aborted staged index and creates a new immutable attempt automatically, after which the whole App is retried atomically. Never work around an App failure by sequentially activating forms. `app-prepare` accepts the same overlay for a manual reviewed flow, and `app-activate <releaseId> --activate-staged-children` performs the explicit atomic activation. For a legacy workspace whose already verified immutable Root cannot pass historical baseline/session compatibility, a platform administrator may use the audited break-glass path: `app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`. It skips only workspace/baseline/source compatibility checks; a fresh lease/baseline plus Root parent CAS, exact child identity/hash/parent/head, and the atomic transaction remain mandatory. When the operator explicitly requires an unconditional legacy recovery, `app-activate <releaseId> --force-activate-without-validation --profile <name>` sends a single activation write without reading detail/capture or requiring change, lease, baseline, source, status, parent, hash, resource-head, environment, or child-lineage checks. Authentication, the bound tenant/appType data boundary, target-row existence, database constraints, and the one database transaction remain inherent execution requirements. Calling `app-finalize` without an overlay remains a compatibility-only retrospective aggregation of already-active children. `app-rollback <releaseId> --change <change> --reason "..."` prepares the audited rollback manifest.
210
210
 
211
211
  If exact FormRelease children were already staged for the same change before a new release baseline or lease is acquired, the CLI reuses them only after checking the server's immutable, inactive, non-aborted release, exact app/form identity and content hash, frozen schema/formType, finalized resources, parent/base revision, and current Form head. Verified children are rebound to the newly owned baseline/session; local `schemaSyncedAt` is not treated as release evidence. Missing or conflicting evidence still fails closed, and neither direct schema synchronization nor early Form activation is required.
212
212
 
package/lib/cli.js CHANGED
@@ -309,7 +309,7 @@ Usage:
309
309
  openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--replace-manifest --reason text] [--adopt-online-baseline --adoption-reason text] [--json]
310
310
  openxiangda task status --change <id> [--profile name] [--watch] [--json]
311
311
  openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]
312
- openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--break-glass-adopt-verified-root --reason text] [--profile name] [--json]
312
+ openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--break-glass-adopt-verified-root --reason text] [--force-activate-without-validation] [--profile name] [--json]
313
313
  openxiangda env [--profile name]
314
314
  openxiangda workspace init [dir] [--name package-name] [--runtime legacy|react-spa] [--install] [--profile name --app-type APP_XXX]
315
315
  openxiangda workspace prepare [--check] [--force] [--json]
@@ -1484,6 +1484,139 @@ function readReleaseRecoveryContext(target, changeId, deploymentId) {
1484
1484
  return { execution, context };
1485
1485
  }
1486
1486
 
1487
+ function releaseSourceRevisionFromExecution(execution) {
1488
+ return (
1489
+ execution?.releaseSourceRevision ||
1490
+ execution?.releaseContext?.releaseSourceRevision ||
1491
+ null
1492
+ );
1493
+ }
1494
+
1495
+ function privateReleasePathLabel(file) {
1496
+ return path.relative(process.cwd(), file).replace(/\\/g, '/');
1497
+ }
1498
+
1499
+ function readReleaseLineageRecovery(changeId, deploymentId) {
1500
+ if (!changeId) {
1501
+ return {
1502
+ sourceRevision: null,
1503
+ source: null,
1504
+ checked: [],
1505
+ };
1506
+ }
1507
+
1508
+ const checked = [];
1509
+ const recovered = [];
1510
+ const inspectedExecutions = new Set();
1511
+ const inspectExecution = (candidateDeploymentId, label) => {
1512
+ const normalizedDeploymentId = String(candidateDeploymentId || '').trim();
1513
+ const key = normalizedDeploymentId || '<root>';
1514
+ if (inspectedExecutions.has(key)) return;
1515
+ inspectedExecutions.add(key);
1516
+ const file = releaseExecutionFile(
1517
+ changeId,
1518
+ normalizedDeploymentId || undefined
1519
+ );
1520
+ const exists = fs.existsSync(file);
1521
+ const execution = exists
1522
+ ? readReleaseExecution(changeId, normalizedDeploymentId || undefined)
1523
+ : null;
1524
+ const sourceRevision = releaseSourceRevisionFromExecution(execution);
1525
+ const changeMatches =
1526
+ !execution?.changeId || execution.changeId === changeId;
1527
+ checked.push({
1528
+ label,
1529
+ file: privateReleasePathLabel(file),
1530
+ exists,
1531
+ field:
1532
+ sourceRevision && changeMatches
1533
+ ? 'releaseSourceRevision'
1534
+ : sourceRevision
1535
+ ? `changeId 不匹配 (${execution.changeId})`
1536
+ : '缺少 releaseContext.releaseSourceRevision/releaseSourceRevision',
1537
+ });
1538
+ if (sourceRevision && changeMatches) {
1539
+ recovered.push({
1540
+ sourceRevision,
1541
+ source: privateReleasePathLabel(file),
1542
+ });
1543
+ }
1544
+ };
1545
+
1546
+ const shipFile = managedShipFile(changeId);
1547
+ const shipExists = fs.existsSync(shipFile);
1548
+ const ship = shipExists ? readManagedShip(changeId) : null;
1549
+ const shipSourceRevision = ship?.sourceRevision || null;
1550
+ checked.push({
1551
+ label: 'managed ship journal',
1552
+ file: privateReleasePathLabel(shipFile),
1553
+ exists: shipExists,
1554
+ field: shipSourceRevision ? 'sourceRevision' : '缺少 sourceRevision',
1555
+ });
1556
+ if (shipSourceRevision) {
1557
+ return {
1558
+ sourceRevision: shipSourceRevision,
1559
+ source: `${privateReleasePathLabel(shipFile)}#sourceRevision`,
1560
+ checked,
1561
+ };
1562
+ }
1563
+
1564
+ if (deploymentId) {
1565
+ inspectExecution(deploymentId, 'explicit deployment execution');
1566
+ }
1567
+ for (const [label, candidateDeploymentId] of [
1568
+ ['production deployment execution', ship?.productionDeploymentId],
1569
+ ['preproduction deployment execution', ship?.preproductionDeploymentId],
1570
+ ]) {
1571
+ if (candidateDeploymentId) {
1572
+ inspectExecution(candidateDeploymentId, label);
1573
+ }
1574
+ }
1575
+ inspectExecution(null, 'root release execution');
1576
+
1577
+ const releaseDir = releaseExecutionDir(changeId);
1578
+ if (fs.existsSync(releaseDir)) {
1579
+ for (const entry of fs
1580
+ .readdirSync(releaseDir, { withFileTypes: true })
1581
+ .filter(item => item.isDirectory())
1582
+ .sort((left, right) => left.name.localeCompare(right.name))) {
1583
+ inspectExecution(entry.name, 'discovered deployment execution');
1584
+ }
1585
+ }
1586
+
1587
+ const distinct = new Map();
1588
+ for (const item of recovered) {
1589
+ const fingerprint = sha256Canonical({
1590
+ repo:
1591
+ item.sourceRevision?.repo ||
1592
+ item.sourceRevision?.repositoryId ||
1593
+ null,
1594
+ baseCommit:
1595
+ item.sourceRevision?.baseCommit ||
1596
+ item.sourceRevision?.commit ||
1597
+ null,
1598
+ treeHash: item.sourceRevision?.treeHash || null,
1599
+ remoteName: item.sourceRevision?.remoteName || null,
1600
+ remoteUrlHash: item.sourceRevision?.remoteUrlHash || null,
1601
+ mainBranch: item.sourceRevision?.mainBranch || null,
1602
+ });
1603
+ if (!distinct.has(fingerprint)) distinct.set(fingerprint, item);
1604
+ }
1605
+ if (distinct.size > 1) {
1606
+ fail(
1607
+ `RELEASE_SOURCE_LINEAGE_CONFLICT: change ${changeId} 的私有 deployment execution 日志包含互不一致的 releaseSourceRevision: ${Array.from(
1608
+ distinct.values(),
1609
+ item => item.source
1610
+ ).join(', ')}`
1611
+ );
1612
+ }
1613
+ return {
1614
+ sourceRevision: recovered[0]?.sourceRevision || null,
1615
+ source: recovered[0]?.source || null,
1616
+ checked,
1617
+ };
1618
+ }
1619
+
1487
1620
  function remoteLeaseOwnedByRecovery(remote, recovery) {
1488
1621
  if (!remote?.active || !recovery) return false;
1489
1622
  const holder = remote.holder;
@@ -3520,13 +3653,21 @@ async function runApplicationShipCommand(
3520
3653
  );
3521
3654
  }
3522
3655
 
3656
+ const promotionStartedAt = new Date().toISOString();
3523
3657
  writeManagedShip(changeId, {
3524
3658
  status: 'confirmed_for_production',
3525
3659
  productionConfirmation: {
3660
+ status: 'confirmed',
3526
3661
  confirmedAt,
3527
3662
  profile: preproduction.profileName,
3528
3663
  acceptanceNote: acceptanceNote || null,
3529
3664
  },
3665
+ productionPromotion: {
3666
+ status: 'running',
3667
+ startedAt: promotionStartedAt,
3668
+ environment: production.targetName,
3669
+ deploymentId: null,
3670
+ },
3530
3671
  manualAcceptance,
3531
3672
  preproductionStatus: preproductionDeployment.status,
3532
3673
  });
@@ -3553,6 +3694,15 @@ async function runApplicationShipCommand(
3553
3694
  manifestReplacement,
3554
3695
  baselineAdoption,
3555
3696
  failure,
3697
+ productionPromotion: {
3698
+ status: 'failed',
3699
+ startedAt: promotionStartedAt,
3700
+ failedAt: failure.recordedAt,
3701
+ environment: production.targetName,
3702
+ deploymentId: failure.deploymentId,
3703
+ deploymentStatus: failure.deploymentStatus,
3704
+ failure,
3705
+ },
3556
3706
  nextCommand: buildManagedShipCommand(
3557
3707
  changeId,
3558
3708
  preproduction.profileName,
@@ -3562,6 +3712,7 @@ async function runApplicationShipCommand(
3562
3712
  });
3563
3713
  throw error;
3564
3714
  }
3715
+ const completedAt = new Date().toISOString();
3565
3716
  const saved = writeManagedShip(changeId, {
3566
3717
  status: 'completed',
3567
3718
  productionEnvironment: production.targetName,
@@ -3570,7 +3721,15 @@ async function runApplicationShipCommand(
3570
3721
  promoted.deployment.targetAppReleaseId || null,
3571
3722
  productionStatus: promoted.deployment.status,
3572
3723
  failure: null,
3573
- completedAt: new Date().toISOString(),
3724
+ completedAt,
3725
+ productionPromotion: {
3726
+ status: 'completed',
3727
+ startedAt: promotionStartedAt,
3728
+ completedAt,
3729
+ environment: production.targetName,
3730
+ deploymentId: promoted.deployment.id,
3731
+ deploymentStatus: promoted.deployment.status,
3732
+ },
3574
3733
  });
3575
3734
  return {
3576
3735
  phase: 'production',
@@ -3775,7 +3934,7 @@ async function release(args) {
3775
3934
  '用法: openxiangda release publish|begin|status|explain|integration-status|renew|end [--change id] [--profile name] [--json]',
3776
3935
  ' openxiangda release ship|candidate|deploy|reconcile|test|promote|rollback [--candidate id] [--environment target] [--replace-manifest --reason text] [--adopt-online-baseline --adoption-reason text] [--confirm-production] [--json]',
3777
3936
  ' openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]',
3778
- ' openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-post-commit|app-retry|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--break-glass-adopt-verified-root --reason text] [--profile name] [--json]',
3937
+ ' openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-post-commit|app-retry|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--break-glass-adopt-verified-root --reason text] [--force-activate-without-validation] [--profile name] [--json]',
3779
3938
  '常用流程:',
3780
3939
  ' openxiangda release ship --change <id> --profile <name>',
3781
3940
  ' openxiangda release ship --change <id> --profile <name> --adopt-online-baseline --adoption-reason "已审计目标来自多次历史发布"',
@@ -3786,7 +3945,7 @@ async function release(args) {
3786
3945
  ' openxiangda release begin --change <id> --profile <name>',
3787
3946
  ' openxiangda resource publish function --only <code> --change <id> --profile <name>',
3788
3947
  ' openxiangda runtime deploy --change <id> --profile <name>',
3789
- ' openxiangda release integration-status --profile <name>',
3948
+ ' openxiangda release integration-status --change <id> --profile <name>',
3790
3949
  ' openxiangda release end --profile <name>',
3791
3950
  ' openxiangda release backend-head --profile <name>',
3792
3951
  ' openxiangda release backend-diff <fromReleaseId> <toReleaseId> --profile <name>',
@@ -3805,6 +3964,7 @@ async function release(args) {
3805
3964
  ' - 默认 TTL 为 120 秒;活动发布每 30 秒心跳并在到期前续租,客户端退出后孤儿租约最多阻塞约 2 分钟。',
3806
3965
  ' - begin 仅接受 clean 且与权威远端默认 main/master 完全一致的 HEAD;feature branch 先合并并 push,再由主分支一次发布。',
3807
3966
  ' - 因发布源已在远端主线,成功激活后 integration-status 会立即通过,end 不再等待补合并。',
3967
+ ' - 发布已结束时,integration-status --change 会先从 ship.json 恢复托管 candidate 血缘,再自动查找其 production/preproduction deployment execution 日志;恢复失败会列出实际缺失的文件或字段。',
3808
3968
  ' - integration-status --check 可作为 CI 门禁;尚未回合时返回非零。',
3809
3969
  ' - 主线目标只能来自权威 Git remote 的实时默认 main/master;参数和环境变量不能改写。',
3810
3970
  ' - 租约过期也会保留待回合证据;完成 merge/push 后仍可执行 end 清理。',
@@ -3953,23 +4113,45 @@ async function release(args) {
3953
4113
  access: 'reconciliation-read',
3954
4114
  });
3955
4115
  const changeId = positional[0] || readStringFlag(flags, 'change');
3956
- const recovered = readReleaseRecoveryContext(
3957
- target,
3958
- changeId,
3959
- readStringFlag(flags, 'deployment-id')
3960
- );
4116
+ const recovered = baseline?.releaseSourceRevision
4117
+ ? {
4118
+ sourceRevision: null,
4119
+ source: null,
4120
+ checked: [],
4121
+ }
4122
+ : readReleaseLineageRecovery(
4123
+ changeId,
4124
+ readStringFlag(flags, 'deployment-id')
4125
+ );
3961
4126
  const sourceRevision =
3962
4127
  baseline?.releaseSourceRevision ||
3963
- recovered?.context?.releaseSourceRevision;
4128
+ recovered.sourceRevision;
3964
4129
  if (!sourceRevision) {
4130
+ if (changeId) {
4131
+ const checked = recovered.checked
4132
+ .map(item =>
4133
+ item.exists
4134
+ ? `${item.file} (${item.field})`
4135
+ : `${item.file} (文件不存在)`
4136
+ )
4137
+ .join('; ');
4138
+ fail(
4139
+ `RELEASE_SOURCE_LINEAGE_REQUIRED: change ${changeId} 的私有发布记录无法恢复 releaseSourceRevision;已检查: ${checked}`
4140
+ );
4141
+ }
3965
4142
  fail(
3966
4143
  'RELEASE_SOURCE_LINEAGE_REQUIRED: 当前 release 没有冻结的 releaseSourceRevision;发布已结束时请同时传入 --change <id>,以便从私有执行日志恢复主线证据'
3967
4144
  );
3968
4145
  }
3969
- const result = inspectReleaseSourceIntegration(
3970
- sourceRevision,
3971
- { cwd: process.cwd() }
3972
- );
4146
+ const result = {
4147
+ ...inspectReleaseSourceIntegration(
4148
+ sourceRevision,
4149
+ { cwd: process.cwd() }
4150
+ ),
4151
+ lineageSource: baseline?.releaseSourceRevision
4152
+ ? '.openxiangda/state.json#promotion.changeBaseline.releaseSourceRevision'
4153
+ : recovered.source,
4154
+ };
3973
4155
  if (flags.check && !result.integrated) {
3974
4156
  assertReleaseSourceIntegrated(sourceRevision, { cwd: process.cwd() });
3975
4157
  }
@@ -5828,6 +6010,22 @@ async function activateAppReleaseDetail(
5828
6010
  releaseId,
5829
6011
  options = {}
5830
6012
  ) {
6013
+ const body = stripUndefinedValues({
6014
+ ...(options.activateStagedChildren === true
6015
+ ? { activateStagedChildren: true }
6016
+ : {}),
6017
+ ...(options.breakGlassAdoptVerifiedRoot === true
6018
+ ? {
6019
+ breakGlassAdoptVerifiedRoot: true,
6020
+ breakGlassReason: options.breakGlassReason,
6021
+ }
6022
+ : {}),
6023
+ ...(options.forceActivateWithoutValidation === true
6024
+ ? { forceActivateWithoutValidation: true }
6025
+ : {}),
6026
+ protocolVersion: options.protocolVersion,
6027
+ ...(options.environmentControl || {}),
6028
+ });
5831
6029
  return await requestWithAuth(
5832
6030
  config,
5833
6031
  target.profileName,
@@ -5837,22 +6035,10 @@ async function activateAppReleaseDetail(
5837
6035
  ),
5838
6036
  {
5839
6037
  method: 'POST',
5840
- body: appReleaseWriteBody(
5841
- releaseContext,
5842
- stripUndefinedValues({
5843
- ...(options.activateStagedChildren === true
5844
- ? { activateStagedChildren: true }
5845
- : {}),
5846
- ...(options.breakGlassAdoptVerifiedRoot === true
5847
- ? {
5848
- breakGlassAdoptVerifiedRoot: true,
5849
- breakGlassReason: options.breakGlassReason,
5850
- }
5851
- : {}),
5852
- protocolVersion: options.protocolVersion,
5853
- ...(options.environmentControl || {}),
5854
- })
5855
- ),
6038
+ body:
6039
+ options.forceActivateWithoutValidation === true
6040
+ ? body
6041
+ : appReleaseWriteBody(releaseContext, body),
5856
6042
  }
5857
6043
  );
5858
6044
  }
@@ -6075,8 +6261,26 @@ async function runAppReleaseCommand(
6075
6261
  );
6076
6262
  } else if (subcommand === 'app-activate') {
6077
6263
  if (!releaseId) {
6078
- fail('用法: openxiangda release app-activate <releaseId> --change <id>');
6264
+ fail(
6265
+ '用法: openxiangda release app-activate <releaseId> [--change <id> | --force-activate-without-validation]'
6266
+ );
6079
6267
  }
6268
+ const forceActivateWithoutValidation =
6269
+ flags['force-activate-without-validation'] === true ||
6270
+ String(flags['force-activate-without-validation'] || '').toLowerCase() ===
6271
+ 'true';
6272
+ if (forceActivateWithoutValidation) {
6273
+ data = await activateAppReleaseDetail(
6274
+ config,
6275
+ target,
6276
+ null,
6277
+ releaseId,
6278
+ {
6279
+ activateStagedChildren: true,
6280
+ forceActivateWithoutValidation: true,
6281
+ }
6282
+ );
6283
+ } else {
6080
6284
  const breakGlassAdoptVerifiedRoot =
6081
6285
  flags['break-glass-adopt-verified-root'] === true ||
6082
6286
  String(flags['break-glass-adopt-verified-root'] || '').toLowerCase() ===
@@ -6133,6 +6337,7 @@ async function runAppReleaseCommand(
6133
6337
  environmentControl: appReleaseEnvironmentControl(flags),
6134
6338
  }
6135
6339
  );
6340
+ }
6136
6341
  } else if (subcommand === 'app-finalize') {
6137
6342
  const stagedResources = readStagedAppReleaseResources(flags);
6138
6343
  let stagedScope = null;
@@ -120,11 +120,11 @@ Exact `resourceSelectors` are authoritative for supported configuration resource
120
120
 
121
121
  `release begin --change` freezes a clean committed `HEAD` only when the current branch is the authoritative default `main`/`master` and its commit exactly equals the live remote tip. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A feature worktree or unpushed main receives `RELEASE_SOURCE_MAINLINE_REQUIRED` / `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`; merge, test, push, and start the one mainline release instead of forcing it. Optional `.git` remote suffix differences and other recorded repository aliases are accepted only when the frozen source-base ID intersects `releaseSourceRevision.repoAliases`; Backend, Workflow, and Root App Release writes then use the frozen canonical ID. Genuinely different identity sets still fail closed before prepare.
122
122
 
123
- Because promotion begins from the already-pushed authoritative mainline, `release integration-status` should pass immediately after activation. Run it, then `release end`; no post-release branch merge is required.
123
+ Because promotion begins from the already-pushed authoritative mainline, `release integration-status --change <change>` should pass immediately after activation. For a completed managed ship it recovers lineage from private `ship.json`, then from the referenced production/preproduction deployment execution journal; a failure identifies the missing file or field. Run it, then `release end`; no post-release branch merge is required.
124
124
 
125
125
  `runtime deploy` separately freezes the clean committed `HEAD` as `sourceRevision` together with the current active Runtime parent before any build or upload. Any deploy whose source does not descend from the online Runtime fails with `RUNTIME_SOURCE_BASE_DIVERGED`, including `--no-activate`; this prevents staging an old preview for later activation. `--no-build` does not bypass this guard. Only an intentional audited rollback may use `--allow-runtime-rollback --reason "<at least 8 characters>"`, and that flag never permits dirty or non-Git input. SDD evidence under `openspec/` and generated/state paths do not make Runtime source dirty.
126
126
 
127
- For a whole-app release, keep changed Runtime/Page/Backend/Form children staged and run `openxiangda release app-finalize --change <change> --staged-resources-json <JSON|file> --profile <name>`. The JSON contains only changed immutable child entries; the CLI overlays Runtime/Page/Backend by singleton kind and FormRelease by `formUuid` onto one authoritative read-only capture, preserving every unmodified active child. It then performs `prepare -> verify -> activateStagedChildren=true`, switching child heads and the App head atomically (`atomic_staged_children_v1`, or `atomic_staged_children_v2` when a Backend Release v2 child is present). Never refresh or retry after a conflict. The flow requires the owned stored lease/change baseline and carries the same client session and Git lineage on every write. A platform administrator may recover a legacy, already verified immutable Root with `app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`. This audited recovery skips workspace/baseline/source compatibility only; it still requires a fresh lease/baseline and enforces Root parent CAS, exact child identity/hash/parent/head, and atomic activation. Omitting the overlay is compatibility-only retrospective aggregation of already-active children.
127
+ For a whole-app release, keep changed Runtime/Page/Backend/Form children staged and run `openxiangda release app-finalize --change <change> --staged-resources-json <JSON|file> --profile <name>`. The JSON contains only changed immutable child entries; the CLI overlays Runtime/Page/Backend by singleton kind and FormRelease by `formUuid` onto one authoritative read-only capture, preserving every unmodified active child. It then performs `prepare -> verify -> activateStagedChildren=true`, switching child heads and the App head atomically (`atomic_staged_children_v1`, or `atomic_staged_children_v2` when a Backend Release v2 child is present). Never refresh or retry after a conflict. The flow requires the owned stored lease/change baseline and carries the same client session and Git lineage on every write. A platform administrator may recover a legacy, already verified immutable Root with `app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`. This audited recovery skips workspace/baseline/source compatibility only; it still requires a fresh lease/baseline and enforces Root parent CAS, exact child identity/hash/parent/head, and atomic activation. An explicitly authorized unconditional legacy recovery may use `app-activate <releaseId> --force-activate-without-validation --profile <name>`; this sends one activation write without local reads or release control/lineage/CAS gates. Authentication, bound tenant/appType scope, target-row existence, database constraints, and transactional execution remain inherent. Omitting the overlay is compatibility-only retrospective aggregation of already-active children.
128
128
 
129
129
  When the same change already has staged FormRelease children before a fresh baseline/lease is acquired, keep them staged. The CLI may rebind each child into the new session only after server verification of immutable/inactive/non-aborted state, exact app/form identity and content hash, frozen schema/formType, finalized resources, parent/base revision, and current Form head. `schemaSyncedAt` is local cache metadata, not release evidence. Do not direct-publish or activate a Form to bypass Workflow validation; missing or conflicting staged evidence must fail closed.
130
130
 
@@ -73,7 +73,7 @@ Standard workspaces use the CLI-bundled scoped JS_CODE builder directly, includi
73
73
 
74
74
  Use `openxiangda release backend-head|backend-list|backend-detail|backend-diff` to inspect immutable history. `backend-rollback <releaseId> --change <change> --reason "..."` prepares, verifies, and activates a new release from the historical snapshot; it never mutates or directly reactivates the old row. `backend-abort` stops prepared/verified work and `backend-retry` retries only recorded post-commit side effects. Do not fetch newer revisions to replay an older payload.
75
75
 
76
- Use `openxiangda release app-capture` for the platform's authoritative transactionally consistent whole-app manifest. Normal publishing stages changed Runtime/Page/Backend/Form children first, then runs `app-finalize --change <change> --staged-resources-json <JSON|file>`. The overlay contains only changed immutable child entries; Runtime/Page/Backend use singleton kind identity and FormRelease uses `formUuid`. The CLI preserves unmodified active children from the capture and performs `prepare -> verify -> activateStagedChildren=true`, atomically switching all child heads and the App head (`atomic_staged_children_v1`). Child drift or a lost lease causes zero root activation; never auto-refresh or conflict-retry. An aborted Form Release must never be reused as an idempotent result. Rerun the same exact form resource publish: the CLI discards the aborted staged entry, the platform creates a new immutable attempt for the same logical artifact, and the Root App transaction is retried. Never sequentially activate forms as a workaround. `app-prepare` accepts the same overlay for manual review, and `app-activate <releaseId> --activate-staged-children` performs the explicit transaction switch. A platform administrator may recover a legacy, already verified immutable Root with `app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`. This audited path skips only workspace/baseline/source compatibility checks; it still requires a fresh lease/baseline and enforces Root parent CAS, exact child identity/hash/parent/head, and atomic activation. No-overlay finalize remains retrospective compatibility only. `app-rollback <releaseId> --change <change> --reason "..."` prepares an audited rollback manifest.
76
+ Use `openxiangda release app-capture` for the platform's authoritative transactionally consistent whole-app manifest. Normal publishing stages changed Runtime/Page/Backend/Form children first, then runs `app-finalize --change <change> --staged-resources-json <JSON|file>`. The overlay contains only changed immutable child entries; Runtime/Page/Backend use singleton kind identity and FormRelease uses `formUuid`. The CLI preserves unmodified active children from the capture and performs `prepare -> verify -> activateStagedChildren=true`, atomically switching all child heads and the App head (`atomic_staged_children_v1`). Child drift or a lost lease causes zero root activation; never auto-refresh or conflict-retry. An aborted Form Release must never be reused as an idempotent result. Rerun the same exact form resource publish: the CLI discards the aborted staged entry, the platform creates a new immutable attempt automatically, and the Root App transaction is retried. Never sequentially activate forms as a workaround. `app-prepare` accepts the same overlay for manual review, and `app-activate <releaseId> --activate-staged-children` performs the explicit transaction switch. A platform administrator may recover a legacy, already verified immutable Root with `app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`. This audited path skips only workspace/baseline/source compatibility checks; it still requires a fresh lease/baseline and enforces Root parent CAS, exact child identity/hash/parent/head, and atomic activation. For an explicitly authorized unconditional legacy recovery, `app-activate <releaseId> --force-activate-without-validation --profile <name>` performs one direct activation write without detail/capture/change/lease/baseline/source/status/parent/hash/resource-head/environment/child-lineage gates; only authentication, the bound tenant/appType data boundary, target-row existence, database constraints, and the transaction remain inherent. No-overlay finalize remains retrospective compatibility only. `app-rollback <releaseId> --change <change> --reason "..."` prepares an audited rollback manifest.
77
77
 
78
78
  ## Classic workspace
79
79
 
@@ -119,7 +119,7 @@ Reviewed bundle commands may retain `<profile>` as a template. The explicit real
119
119
 
120
120
  Exact `resourceSelectors` are authoritative for supported configuration resources such as `publicAccessPolicies`. A legacy `resources=true` category marker is narrowed by those exact selectors and must not become an app-wide generic-resource release. Missing selectors, unknown resource types, wildcard `*`, deletes, and genuine app-wide resource closures remain fail closed.
121
121
 
122
- `runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. It reads a narrow Runtime head instead of the full app snapshot. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. Immutable Git-base artifact hashes are cached under the Git common directory. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status` is already satisfied after activation and `release end` does not wait for a later merge.
122
+ `runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. It reads a narrow Runtime head instead of the full app snapshot. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. Immutable Git-base artifact hashes are cached under the Git common directory. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status --change <change>` is already satisfied after activation and `release end` does not wait for a later merge. For a completed managed ship, `--change` recovers lineage from private `ship.json` or the referenced production/preproduction deployment execution journal and reports the exact missing file or field when recovery fails.
123
123
 
124
124
  `release app-head` and `runtime releases` return compact summaries by default. Use `--full` only when the complete manifest is required.
125
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.211",
3
+ "version": "1.0.213",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -37,6 +37,7 @@ This is an OpenXiangda React SPA workspace. See [AGENTS.md](mdc:AGENTS.md) for f
37
37
  - A staged FormRelease for the same change may be rebound to a fresh baseline/session only after server verification of immutable/inactive/non-aborted state, identity/hash, frozen schema/formType, finalized resources, parent/base revision, and current Form head. `schemaSyncedAt` is not release evidence; never synthesize it, direct-publish the schema, or activate the Form early to bypass Workflow validation.
38
38
  - Managed `release ship --adopt-online-baseline --adoption-reason "..."` freezes that audited intent in the private ship journal; the later `--confirm-production` automatically reuses it and rejects an explicitly different pair before any request.
39
39
  - Managed `release ship --replace-manifest --reason "..."` requires the pair, reason length >= 8, and exact Backend selectors; production confirmation repeats the exact preproduction pair, with no forwarding to Form/Workflow/Runtime/config/all stages.
40
+ - After managed promotion, run `release integration-status --change <change> --profile <name> --check`; it recovers lineage from private `ship.json` or its referenced production/preproduction deployment execution journal, and names the missing file or field when recovery is impossible.
40
41
  - In a managed workspace with multiple targets, `function invoke` must include `--environment <target>`; verify the resolved target line on stderr before drawing conclusions from the response.
41
42
  - React routes live in `src/app/router.tsx`; frontend artifacts are deployed with `openxiangda runtime deploy`.
42
43
  - Use `AttachmentField` / `ImageField` for form-context previews, and `AttachmentPreviewList` / `ImagePreviewGrid` / `useFilePreview` for standalone custom pages. Do not fake form context, import internal preview implementations, or maintain local previewable-extension lists.
@@ -33,6 +33,7 @@ This is an OpenXiangda React SPA workspace. Read [AGENTS.md](AGENTS.md) for full
33
33
  - 相同 change 的 staged FormRelease 只有经服务端重新核验 immutable/inactive/non-aborted、identity/hash、冻结 schema/formType、finalized 资源、parent/base revision 与当前 Form Head 后,才可重挂接新 baseline/session。`schemaSyncedAt` 不是发布证据;禁止伪造、直发 schema 或提前激活 Form 绕过 Workflow 校验。
34
34
  - 环境托管 `release ship --adopt-online-baseline --adoption-reason "..."` 会把审计意图冻结进私有 ship journal,后续 `--confirm-production` 自动复用;显式传入不同参数会在任何请求前失败。
35
35
  - 环境托管 `release ship --replace-manifest --reason "..."` 必须成对、reason 至少 8 字符且仅限精确 Backend selector;正式确认复用与预发完全相同的参数,不透传 Form/Workflow/Runtime/配置或全量步骤。
36
+ - 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`;CLI 从私有 `ship.json` 或其引用的 production/preproduction deployment execution 日志恢复血缘,无法恢复时指出实际缺失的文件或字段。
36
37
  - 多 target 的托管工作区执行 `function invoke` 必须显式传 `--environment <target>`,并先核对 stderr 回显的最终 target 再判断响应。
37
38
  - Promotion 必须持有 `release begin/end` 租约;`release begin` 只接受与权威远端 tip 完全一致的 clean main/master。feature branch 或未 push 主线在任何写入前失败。clone primary 只有在冻结仓库 ID 已存在于 `repoAliases` 时才会 canonicalize;无交集在 Release prepare 前失败关闭。激活后直接运行 `integration-status` 和 `release end`,不再补做发布后合并。
38
39
  - 已有工作区通过 `environment attach` 接入环境组,旧资源映射只迁移到 appType 相同的预发 target,正式 target 必须为空。preproduction / production 分别拥有独立 appType、资源 ID、数据和副作用策略;禁止直接 `release publish`,禁止跨环境复制 ID,使用 `openxiangda studio` 查看状态。只有用户明确授权的投产前重分类可执行 `environment swap --reason "..." --confirm-production`;它不移动应用数据或 Release Head,且默认不放开副作用。
@@ -55,6 +55,8 @@ openxiangda commands --json
55
55
 
56
56
  工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish` 即不再是入口。日常使用两段式 `release ship`:第一次冻结不可变 candidate 并只部署到 preproduction,停止在 `awaiting_production_confirmation`;确认预发结果后,第二次命令提供 `--confirm-production`,把同一 candidate 晋级 production,不会重新构建。真实人工验收是默认建议,可用可选的 `--acceptance-note` 留痕;但用户明确授权的低风险或紧急发布不受僵硬审批门禁阻塞。两套环境的 appType、资源 ID、数据和副作用策略完全独立;旧单目标资源映射只允许迁移到 appType 相同的预发 target,正式 target 必须为空。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。单独调整某个环境的副作用策略时,先运行 `environment policy update <kind|id> ... --dry-run` 查看差异,再按 CAS revision 写入;patch 只校验本次提交字段并原样保留未知历史字段,`--full-replace` 才按完整目标删除遗漏字段。不得借用 swap,正式写入另需 `--confirm-production`。`organizationWrites=explicit_capability_only` 仍强制 `app:organization:manage`。发布硬门禁只保留明确 scope/profile/target、权限、干净且已推送主线、不可变版本、CAS/租约与生产确认;文案和人工验收说明默认是建议,只有显式 strict 模式才阻断。`openxiangda studio` 提供仅本机访问的开发者页面,用于查看绑定、漂移、候选、部署、证据和下一安全动作。
57
57
 
58
+ 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`。CLI 会从私有 `ship.json` 恢复血缘,必要时自动沿 production/preproduction deployment ID 查找对应 `execution.json`;失败信息必须指出实际缺失的日志或字段。
59
+
58
60
  只有已审计代码早已进入权威主线、而精确非删除目标在线上来自多次历史发布且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`。该意图会冻结进私有 `ship.json`,后续 `--confirm-production` 自动复用;它不会放松冻结 online heads、change/lease、删除/全量拒绝、服务端 CAS、staged children 或单次 App finalize 原子激活。
59
61
 
60
62
  环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只会进入精确 Backend selector,正式确认必须复用与预发完全相同的参数,绝不扩散到表单、流程、Runtime、配置资源或全量范围。
@@ -32,6 +32,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
32
32
  - Source-triggered Function/Automation publishing uses server-side source-field PATCH by default and preserves online bindings, contracts, metadata, trigger/view configuration, and enabled/published state. A new source-free Automation with a complete `definitionJson.version="v3"` automatically uses manifest create; replacing an existing whole manifest requires exact `--only/--code` plus `--replace-manifest --reason "..."`.
33
33
  - A staged FormRelease for the same change may be rebound to a fresh baseline/session only after server verification of immutable/inactive/non-aborted state, identity/hash, frozen schema/formType, finalized resources, parent/base revision, and current Form head. `schemaSyncedAt` is not release evidence; never synthesize it, direct-publish the schema, or activate the Form early to bypass Workflow validation.
34
34
  - Managed `release ship --replace-manifest --reason "..."` requires the pair, reason length >= 8, and exact Backend selectors; production confirmation repeats the exact preproduction pair, with no forwarding to Form/Workflow/Runtime/config/all stages.
35
+ - After managed promotion, run `release integration-status --change <change> --profile <name> --check`; it recovers lineage from private `ship.json` or its referenced production/preproduction deployment execution journal, and names the missing file or field when recovery is impossible.
35
36
  - Before platform writes, run `release begin` only from clean main/master exactly equal to the authoritative remote tip. Feature branches and unpushed mainline commits fail before writes. A clone primary may be canonicalized to the frozen repository ID only when that ID is already in `repoAliases`; otherwise release prepare fails closed. After activation, run `integration-status` and `release end`; no post-release merge is needed.
36
37
  - Environment-managed workspaces never publish directly. Existing workspaces use `environment attach`; only an appType-matching legacy binding may seed preproduction and production starts empty. Run `release candidate`, `release deploy --environment preproduction`, `release test`, then promote that same candidate with `release promote --environment production --confirm-production`. Keep app/resource/data IDs isolated and inspect state with `openxiangda studio`. Only an explicitly authorized commissioning reclassification may use `environment swap --reason "..." --confirm-production`; it preserves app data/Release Heads and keeps side effects restricted by default.
37
38
  - Change one target's side-effect policy only with `environment policy update <preproduction|production> --side-effect-policy-json <JSON|file> --reason "..."`: run `--dry-run` first, add `--confirm-production` for production, and never use `environment swap` for this. Patch mode validates only supplied fields and preserves unrecognized historical fields; `--full-replace` validates the supplied complete target and intentionally removes omissions. `organizationWrites=explicit_capability_only` removes the environment deny but still requires `app:organization:manage`. Keep release hard gates to explicit scope/profile/target, authorization, clean pushed mainline, immutable identity, CAS/lease, and production confirmation; prose and human acceptance notes are advisory unless strict mode is configured.
@@ -32,6 +32,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
32
32
  - Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state;无源码且 `definitionJson.version="v3"` 完整的新建 Automation 自动走 manifest create,只有替换已有整包 manifest 才必须精确 `--only/--code` 并加 `--replace-manifest --reason "..."`。
33
33
  - 相同 change 的 staged FormRelease 只有经服务端重新核验 immutable/inactive/non-aborted、identity/hash、冻结 schema/formType、finalized 资源、parent/base revision 与当前 Form Head 后,才可重挂接新 baseline/session。`schemaSyncedAt` 不是发布证据;禁止伪造、直发 schema 或提前激活 Form 绕过 Workflow 校验。
34
34
  - 环境托管 `release ship --replace-manifest --reason "..."` 必须成对、reason 至少 8 字符且仅限精确 Backend selector;正式确认复用与预发完全相同的参数,不透传 Form/Workflow/Runtime/配置或全量步骤。
35
+ - 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`;CLI 从私有 `ship.json` 或其引用的 production/preproduction deployment execution 日志恢复血缘,无法恢复时指出实际缺失的文件或字段。
35
36
  - 写入平台前只从与权威远端 tip 完全一致的 clean main/master 执行 `release begin`。feature branch 或未 push 主线在任何写入前失败;clone primary 只有在冻结仓库 ID 已存在于 `repoAliases` 时才会 canonicalize,无交集在 Release prepare 前失败关闭;激活后直接运行 `integration-status` 和 `release end`,无需发布后再合并。
36
37
  - 环境托管工作区禁止直发:已有工作区先用 `environment attach` 接入,旧映射只能迁移到 appType 相同的预发 target,正式 target 必须为空。然后执行 `release candidate`、`release deploy --environment preproduction`、`release test`,最后将同一 candidate 用 `release promote --environment production --confirm-production` 晋级。两套环境的 appType、资源 ID 和数据不可互拷;用 `openxiangda studio` 查看状态。只有用户明确授权的投产前重分类可执行 `environment swap --reason "..." --confirm-production`;它不移动应用数据或 Release Head,且默认不放开副作用。
37
38
  - 单独修改某个环境的副作用策略只能使用 `environment policy update <preproduction|production> --side-effect-policy-json <JSON|file> --reason "..."`:先执行 `--dry-run`,正式环境额外要求 `--confirm-production`,不得借用 `environment swap`。patch 只校验本次提交字段并原样保留未知历史字段,`--full-replace` 才按完整目标删除遗漏字段。`organizationWrites=explicit_capability_only` 只解除环境级 deny,仍强制 `app:organization:manage`。发布硬门禁只保留明确 scope/profile/target、权限、干净且已推送主线、不可变版本、CAS/租约与生产确认;文案和人工验收说明默认是建议,只有显式 strict 模式才阻断。
@@ -58,8 +58,9 @@
58
58
  - ✅ Function/Automation 使用 Backend Release v2;正式多资源发布用精确 `--only/--code` 加 `--stage-only` 暂存,同一 child 可混合源码 create、无 `sourceFile` 的完整 v3 声明式 Automation manifest create、source-only update 和显式 manifest replacement,再由 Root App finalize 原子激活。声明式 create 自动选路;替换已有资源的整包 manifest 才需要另加 `--replace-manifest --reason "..."`。clone primary 与冻结仓库 ID 不同时,只有冻结 ID 已存在于 `repoAliases` 才会统一用于 Backend/Workflow/Root App Release;无交集继续失败关闭。
59
59
  - ✅ 相同 change 已有 staged FormRelease 时,不要直发 schema、伪造 `schemaSyncedAt` 或提前激活 Form。CLI 只在重新核验服务端不可变状态、identity/hash、冻结 schema/formType、finalized 资源、parent/base revision 与当前 Form Head 后,才将 child 重挂接到新的 baseline/session;冲突继续失败关闭。
60
60
  - ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
61
- - ✅ 旧工作区已有 `verified + immutable` Root、但历史 baseline/session 兼容校验无法恢复时,只允许平台管理员显式执行 `release app-activate <releaseId> --activate-staged-children --break-glass-adopt-verified-root --reason "..."`。该审计入口仍要求新租约/基线、相同 tenant/app/holder/change,并保留 Root parent CAS、child identity/hash/parent/head 与原子事务门禁。
61
+ - ✅ 旧工作区已有 Root、且操作者明确授权无条件恢复时,可执行 `release app-activate <releaseId> --force-activate-without-validation --profile <name>`。该命令不读取 detail/capture,不要求 change、租约、baseline、源码 lineage、状态、parent、hash、resource head 或环境发布门禁;服务端直接在目标 tenant/appType 内以单事务切换 Root 与可识别的 staged children。
62
62
  - ✅ 已通过 `environment init` 登记或 `environment attach` 接入的工作区默认使用两段式 `release ship`。第一次命令只冻结 candidate 并部署 preproduction,停止等待正式晋级确认;确认预发结果后,另一次命令提供 `--confirm-production`,即可晋级同一 candidate。人工验收是默认建议,可用可选的 `--acceptance-note` 留痕,但不是所有低风险或紧急发布的硬审批门禁。preproduction / production 的 appType、资源 ID、数据和副作用策略完全隔离,严禁跨环境复制 ID 或直接 `release publish`。旧单目标映射只允许按相同 appType 迁移到预发。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。单环境副作用策略用 `environment policy update <kind|id> ... --dry-run` 预览后按 revision CAS 写入,禁止借用 swap;patch 只校验本次提交字段并原样保留未知历史字段,`--full-replace` 才按完整目标删除遗漏字段,正式写入需 `--confirm-production`,且 `organizationWrites=explicit_capability_only` 不绕过 `app:organization:manage`。发布硬门禁只保留明确 scope/profile/target、权限、干净且已推送主线、不可变版本、CAS/租约与生产确认;文案和人工验收说明默认是建议,只有显式 strict 模式才阻断。
63
+ - ✅ 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`。CLI 会从私有 `ship.json` 恢复血缘,必要时自动沿 production/preproduction deployment ID 查找对应 `execution.json`;失败信息必须指出实际缺失的日志或字段。
63
64
  - ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;该意图冻结进私有 `ship.json` 并由后续 `--confirm-production` 自动复用。仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
64
65
  - ✅ 环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只透传精确 Backend selector,正式确认必须复用同一对参数,不扩散到 Form/Workflow/Runtime/配置或全量范围。
65
66
  - ✅ 受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍 fail closed。