openxiangda 1.0.210 → 1.0.211
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
|
@@ -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. 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. 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/bin/openxiangda.js
CHANGED
|
@@ -3,9 +3,24 @@
|
|
|
3
3
|
const { main } = require('../lib/cli');
|
|
4
4
|
const { maskText } = require('../lib/utils');
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const argv = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
main(argv).catch(error => {
|
|
7
9
|
const message =
|
|
8
10
|
error && error.message ? error.message : String(error || 'Unknown error');
|
|
11
|
+
if (argv.includes('--json')) {
|
|
12
|
+
const payload = {
|
|
13
|
+
success: false,
|
|
14
|
+
error: {
|
|
15
|
+
status: Number.isInteger(error?.status) ? error.status : null,
|
|
16
|
+
code: error?.code || error?.payload?.errorCode || null,
|
|
17
|
+
message,
|
|
18
|
+
data: error?.data ?? error?.payload?.data ?? null,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
console.error(maskText(JSON.stringify(payload)));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
9
24
|
console.error(maskText(message));
|
|
10
25
|
process.exit(1);
|
|
11
26
|
});
|
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] [--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]
|
|
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]
|
|
@@ -3775,7 +3775,7 @@ async function release(args) {
|
|
|
3775
3775
|
'用法: openxiangda release publish|begin|status|explain|integration-status|renew|end [--change id] [--profile name] [--json]',
|
|
3776
3776
|
' 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
3777
|
' 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] [--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]',
|
|
3779
3779
|
'常用流程:',
|
|
3780
3780
|
' openxiangda release ship --change <id> --profile <name>',
|
|
3781
3781
|
' openxiangda release ship --change <id> --profile <name> --adopt-online-baseline --adoption-reason "已审计目标来自多次历史发布"',
|
|
@@ -5843,6 +5843,12 @@ async function activateAppReleaseDetail(
|
|
|
5843
5843
|
...(options.activateStagedChildren === true
|
|
5844
5844
|
? { activateStagedChildren: true }
|
|
5845
5845
|
: {}),
|
|
5846
|
+
...(options.breakGlassAdoptVerifiedRoot === true
|
|
5847
|
+
? {
|
|
5848
|
+
breakGlassAdoptVerifiedRoot: true,
|
|
5849
|
+
breakGlassReason: options.breakGlassReason,
|
|
5850
|
+
}
|
|
5851
|
+
: {}),
|
|
5846
5852
|
protocolVersion: options.protocolVersion,
|
|
5847
5853
|
...(options.environmentControl || {}),
|
|
5848
5854
|
})
|
|
@@ -6071,6 +6077,18 @@ async function runAppReleaseCommand(
|
|
|
6071
6077
|
if (!releaseId) {
|
|
6072
6078
|
fail('用法: openxiangda release app-activate <releaseId> --change <id>');
|
|
6073
6079
|
}
|
|
6080
|
+
const breakGlassAdoptVerifiedRoot =
|
|
6081
|
+
flags['break-glass-adopt-verified-root'] === true ||
|
|
6082
|
+
String(flags['break-glass-adopt-verified-root'] || '').toLowerCase() ===
|
|
6083
|
+
'true';
|
|
6084
|
+
const breakGlassReason =
|
|
6085
|
+
readStringFlag(flags, 'reason') ||
|
|
6086
|
+
readStringFlag(flags, 'break-glass-reason');
|
|
6087
|
+
if (breakGlassAdoptVerifiedRoot && breakGlassReason.length < 12) {
|
|
6088
|
+
fail(
|
|
6089
|
+
'BREAK_GLASS_REASON_REQUIRED: --break-glass-adopt-verified-root 必须提供至少 12 个字符的 --reason'
|
|
6090
|
+
);
|
|
6091
|
+
}
|
|
6074
6092
|
const detail = await requestWithAuth(
|
|
6075
6093
|
config,
|
|
6076
6094
|
target.profileName,
|
|
@@ -6083,7 +6101,7 @@ async function runAppReleaseCommand(
|
|
|
6083
6101
|
atomicV2 ||
|
|
6084
6102
|
flags['activate-staged-children'] === true ||
|
|
6085
6103
|
String(flags['activate-staged-children'] || '').toLowerCase() === 'true';
|
|
6086
|
-
if (activatesStagedChildren) {
|
|
6104
|
+
if (activatesStagedChildren && !breakGlassAdoptVerifiedRoot) {
|
|
6087
6105
|
const capture = await fetchAppReleaseCapture(config, target, flags);
|
|
6088
6106
|
const changedResources = changedAppReleaseResources(
|
|
6089
6107
|
appReleaseManifestResources(detail),
|
|
@@ -6108,8 +6126,9 @@ async function runAppReleaseCommand(
|
|
|
6108
6126
|
releaseContext,
|
|
6109
6127
|
releaseId,
|
|
6110
6128
|
{
|
|
6111
|
-
activateStagedChildren:
|
|
6112
|
-
|
|
6129
|
+
activateStagedChildren: activatesStagedChildren,
|
|
6130
|
+
breakGlassAdoptVerifiedRoot,
|
|
6131
|
+
breakGlassReason,
|
|
6113
6132
|
protocolVersion: protocolVersion || undefined,
|
|
6114
6133
|
environmentControl: appReleaseEnvironmentControl(flags),
|
|
6115
6134
|
}
|
|
@@ -124,7 +124,7 @@ Because promotion begins from the already-pushed authoritative mainline, `releas
|
|
|
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. 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. 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. 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 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.
|
|
77
77
|
|
|
78
78
|
## Classic workspace
|
|
79
79
|
|
package/package.json
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
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
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 模式才阻断。
|
|
62
63
|
- ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;该意图冻结进私有 `ship.json` 并由后续 `--confirm-production` 自动复用。仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
|
|
63
64
|
- ✅ 环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只透传精确 Backend selector,正式确认必须复用同一对参数,不扩散到 Form/Workflow/Runtime/配置或全量范围。
|