openxiangda 1.0.204 → 1.0.206
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 +2 -2
- package/lib/application-environments.js +16 -10
- package/lib/cli.js +144 -16
- package/openxiangda-skills/SKILL.md +3 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +3 -1
- package/package.json +1 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +1 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +2 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +1 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +2 -1
- package/templates/openxiangda-react-spa/AGENTS.md +3 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +1 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +2 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +1 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +2 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +2 -1
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ openxiangda app snapshot APP_XXXX --profile dev --json
|
|
|
82
82
|
|
|
83
83
|
User tokens are stored in `~/.openxiangda/profiles.json` with `0600` permissions. Shared workspace environment values, including `APP_OSS_*`, can live in `~/.openxiangda/.env` and are inherited by new workspaces. Project `.env` files still work and override the global defaults. Project state is stored in `.openxiangda/state.json` and contains only profile-specific resource IDs; CLI writes use a workspace lock plus atomic merge/rename so concurrent processes do not truncate another profile's state.
|
|
84
84
|
|
|
85
|
-
An environment-managed workspace keeps one logical application with independent `preproduction` and `production` targets. Each target owns its own `appType`, resource IDs, release heads, data, and side-effect policy; IDs must never be copied across targets. `release ship` is deliberately two-phase: the first invocation seals the clean authoritative Git revision and deploys one immutable candidate only to preproduction; it always stops at `awaiting_production_confirmation`. A later invocation includes `--confirm-production` and promotes that exact candidate to production without rebuilding it. Real human acceptance is the recommended default; an optional `--acceptance-note` records it in the deployment evidence, but the platform does not force every low-risk or emergency release through a rigid approval gate. It is impossible to prepare and promote in one invocation. For an audited catch-up whose exact non-delete targets are already merged but whose active resources came from several historical release lineages, the first invocation may explicitly add `--adopt-online-baseline --adoption-reason "..."`; ship validates this pair before creating a candidate/deployment, then forwards it only to exact scoped resource stages. Frozen online heads, change/lease ownership, server CAS, staged children, and the single App finalize remain mandatory. Supported configuration resources use exact `resourceSelectors`; a legacy `resources=true` category marker is narrowed by those selectors, while unknown, wildcard, destructive, and genuinely unscoped generic resources remain blocked. The lower-level `candidate/deploy/test/promote` commands remain available for recovery and diagnostics. Direct `release publish` is retained only for legacy, unmanaged workspaces and fails closed for either target of an environment-managed application.
|
|
85
|
+
An environment-managed workspace keeps one logical application with independent `preproduction` and `production` targets. Each target owns its own `appType`, resource IDs, release heads, data, and side-effect policy; IDs must never be copied across targets. `release ship` is deliberately two-phase: the first invocation seals the clean authoritative Git revision and deploys one immutable candidate only to preproduction; it always stops at `awaiting_production_confirmation`. A later invocation includes `--confirm-production` and promotes that exact candidate to production without rebuilding it. Real human acceptance is the recommended default; an optional `--acceptance-note` records it in the deployment evidence, but the platform does not force every low-risk or emergency release through a rigid approval gate. It is impossible to prepare and promote in one invocation. For an audited catch-up whose exact non-delete targets are already merged but whose active resources came from several historical release lineages, the first invocation may explicitly add `--adopt-online-baseline --adoption-reason "..."`; ship validates this pair before creating a candidate/deployment, then forwards it only to exact scoped resource stages. A reviewed complete Function/Automation manifest replacement may similarly add the inseparable `--replace-manifest --reason "..."` pair; ship forwards it only to an exact Backend stage and requires the production confirmation to repeat the exact preproduction intent. Frozen online heads, change/lease ownership, server CAS, staged children, and the single App finalize remain mandatory. Supported configuration resources use exact `resourceSelectors`; a legacy `resources=true` category marker is narrowed by those selectors, while unknown, wildcard, destructive, and genuinely unscoped generic resources remain blocked. The lower-level `candidate/deploy/test/promote` commands remain available for recovery and diagnostics. Direct `release publish` is retained only for legacy, unmanaged workspaces and fails closed for either target of an environment-managed application.
|
|
86
86
|
|
|
87
87
|
Exact, non-destructive configuration selectors such as Data Views and permission groups are now sequenced automatically inside the same ship journal instead of requiring separate SDD changes. New forms are idempotently ensured per environment before their immutable FormRelease is staged. Unscoped resources and destructive configuration deletes remain fail-closed.
|
|
88
88
|
|
|
@@ -135,7 +135,7 @@ openxiangda policy render
|
|
|
135
135
|
entrypoints. Generated documents carry `OpenXiangda-Policy-Version`; edit the
|
|
136
136
|
canonical policy first instead of independently changing duplicate instructions.
|
|
137
137
|
|
|
138
|
-
Source-triggered Function and Automation publishing is source-only by default. Backend Release v2 can mix create, source-only update, and manifest replacement update in one immutable child; a stale target therefore causes zero resource writes instead of failing halfway through 88 Functions and 11 Automations. `--stage-only` stops after verification, reports all handled selectors, and never falls back to direct Function/Automation writes when the platform is missing or incompatible. The release preserves online `resourceBindings`, input/output contracts, descriptive metadata, trigger/view configuration, and enabled/published state, and planned noops do not advance resource versions or timestamps. To intentionally make a complete local manifest replace
|
|
138
|
+
Source-triggered Function and Automation publishing is source-only by default. Backend Release v2 can mix source-backed create, source-free declarative Automation manifest create, source-only update, and manifest replacement update in one immutable child; a stale target therefore causes zero resource writes instead of failing halfway through 88 Functions and 11 Automations. A new Automation with a complete `definitionJson.version="v3"` definition and no `sourceFile` automatically uses manifest create and does not require `--replace-manifest`; incomplete definitions still fail closed. `--stage-only` stops after verification, reports all handled selectors, and never falls back to direct Function/Automation writes when the platform is missing or incompatible. The release preserves online `resourceBindings`, input/output contracts, descriptive metadata, trigger/view configuration, and enabled/published state, and planned noops do not advance resource versions or timestamps. To intentionally make a complete local manifest replace an existing online definition, select exact codes and opt in explicitly with `--replace-manifest --reason "<why this replacement is safe>"`; `--sdd-bypass` does not widen that scope. Standard workspaces use the CLI-bundled scoped builder without spawning workspace `pnpm`, and `source_lineage_v1` compares authored source/dependencies independently from generated bundle bytes.
|
|
139
139
|
|
|
140
140
|
React SPA templates include stable Vite manual chunk grouping for React, antd, ECharts, editor dependencies, and OpenXiangda runtime/component entrypoints. The SDK keeps large UI dependencies such as `antd-mobile` and `dayjs` external so the application bundler can split them by route. Applications should import from public package entrypoints such as `openxiangda`, `openxiangda/runtime`, and `openxiangda/runtime/react`; do not import `openxiangda/packages/sdk/dist/...` internals to chase bundle size.
|
|
141
141
|
|
|
@@ -536,18 +536,24 @@ function withManagedReleaseForwardedFlags(stepId, args = [], flags = {}) {
|
|
|
536
536
|
forwarded[0] === 'resource' &&
|
|
537
537
|
forwarded[1] === 'publish' &&
|
|
538
538
|
(forwarded.includes('--only') || forwarded.includes('--code'));
|
|
539
|
-
if (
|
|
540
|
-
!isExactResourcePublish ||
|
|
541
|
-
!flags['adopt-online-baseline']
|
|
542
|
-
) {
|
|
539
|
+
if (!isExactResourcePublish) {
|
|
543
540
|
return forwarded;
|
|
544
541
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
542
|
+
if (stepId === 'backend-stage' && flags['replace-manifest']) {
|
|
543
|
+
forwarded.push('--replace-manifest');
|
|
544
|
+
const replacementReason = String(flags.reason || '').trim();
|
|
545
|
+
if (replacementReason) {
|
|
546
|
+
forwarded.push('--reason', replacementReason);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (flags['adopt-online-baseline']) {
|
|
550
|
+
forwarded.push('--adopt-online-baseline');
|
|
551
|
+
const adoptionReason = String(
|
|
552
|
+
flags['adoption-reason'] || flags.reason || ''
|
|
553
|
+
).trim();
|
|
554
|
+
if (adoptionReason) {
|
|
555
|
+
forwarded.push('--adoption-reason', adoptionReason);
|
|
556
|
+
}
|
|
551
557
|
}
|
|
552
558
|
return forwarded;
|
|
553
559
|
}
|
package/lib/cli.js
CHANGED
|
@@ -303,7 +303,7 @@ Usage:
|
|
|
303
303
|
openxiangda sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive [change] [--change id] [--json]
|
|
304
304
|
openxiangda environment init|attach|bind|swap|policy update|list|status|diff|use [preproduction|production] [--logical-app code] [--environment target] [--json]
|
|
305
305
|
openxiangda release publish|begin|status|integration-status|renew|end [--change id] [--profile name] [--watch] [--json]
|
|
306
|
-
openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--adopt-online-baseline --adoption-reason text] [--json]
|
|
306
|
+
openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--replace-manifest --reason text] [--adopt-online-baseline --adoption-reason text] [--json]
|
|
307
307
|
openxiangda task status --change <id> [--profile name] [--watch] [--json]
|
|
308
308
|
openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]
|
|
309
309
|
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]
|
|
@@ -3087,6 +3087,8 @@ const RELEASE_SHIP_SUPPORTED_FLAGS = new Set([
|
|
|
3087
3087
|
'app-type',
|
|
3088
3088
|
'since',
|
|
3089
3089
|
'expand-dependencies',
|
|
3090
|
+
'replace-manifest',
|
|
3091
|
+
'reason',
|
|
3090
3092
|
'adopt-online-baseline',
|
|
3091
3093
|
'adoption-reason',
|
|
3092
3094
|
'confirm-production',
|
|
@@ -3109,6 +3111,22 @@ function assertReleaseShipFlags(flags = {}) {
|
|
|
3109
3111
|
error.unsupportedFlags = unsupported;
|
|
3110
3112
|
throw error;
|
|
3111
3113
|
}
|
|
3114
|
+
const replacementRequested = Boolean(flags['replace-manifest']);
|
|
3115
|
+
const replacementReason = String(flags.reason || '').trim();
|
|
3116
|
+
if (!replacementRequested && replacementReason) {
|
|
3117
|
+
const error = new Error(
|
|
3118
|
+
'RELEASE_SHIP_REPLACE_MANIFEST_FLAG_REQUIRED: --reason 必须与 --replace-manifest 一起使用;命令已在任何 candidate/deployment 写入前终止'
|
|
3119
|
+
);
|
|
3120
|
+
error.code = 'RELEASE_SHIP_REPLACE_MANIFEST_FLAG_REQUIRED';
|
|
3121
|
+
throw error;
|
|
3122
|
+
}
|
|
3123
|
+
if (replacementRequested && replacementReason.length < 8) {
|
|
3124
|
+
const error = new Error(
|
|
3125
|
+
'RELEASE_SHIP_REPLACE_MANIFEST_REASON_REQUIRED: --replace-manifest 必须提供至少 8 个字符的 --reason;命令已在任何 candidate/deployment 写入前终止'
|
|
3126
|
+
);
|
|
3127
|
+
error.code = 'RELEASE_SHIP_REPLACE_MANIFEST_REASON_REQUIRED';
|
|
3128
|
+
throw error;
|
|
3129
|
+
}
|
|
3112
3130
|
const adoptionRequested = Boolean(flags['adopt-online-baseline']);
|
|
3113
3131
|
const adoptionReason = String(flags['adoption-reason'] || '').trim();
|
|
3114
3132
|
if (!adoptionRequested && adoptionReason) {
|
|
@@ -3138,7 +3156,7 @@ async function runApplicationShipCommand(
|
|
|
3138
3156
|
readStringFlag(flags, 'change') || positional[0];
|
|
3139
3157
|
if (!changeId) {
|
|
3140
3158
|
fail(
|
|
3141
|
-
'用法: openxiangda release ship --change <id> [--adopt-online-baseline --adoption-reason "..."] [--confirm-production] [--acceptance-note "..."]'
|
|
3159
|
+
'用法: openxiangda release ship --change <id> [--replace-manifest --reason "..."] [--adopt-online-baseline --adoption-reason "..."] [--confirm-production] [--acceptance-note "..."]'
|
|
3142
3160
|
);
|
|
3143
3161
|
}
|
|
3144
3162
|
const logicalApp = requireLogicalApplication(initialTarget);
|
|
@@ -3154,6 +3172,26 @@ async function runApplicationShipCommand(
|
|
|
3154
3172
|
);
|
|
3155
3173
|
const existingShip = readManagedShip(changeId);
|
|
3156
3174
|
const confirmProduction = Boolean(flags['confirm-production']);
|
|
3175
|
+
const manifestReplacement = {
|
|
3176
|
+
enabled: Boolean(flags['replace-manifest']),
|
|
3177
|
+
reason: String(flags.reason || '').trim() || null,
|
|
3178
|
+
};
|
|
3179
|
+
|
|
3180
|
+
if (existingShip?.candidateId) {
|
|
3181
|
+
const recordedReplacement = existingShip.manifestReplacement || {
|
|
3182
|
+
enabled: false,
|
|
3183
|
+
reason: null,
|
|
3184
|
+
};
|
|
3185
|
+
if (
|
|
3186
|
+
Boolean(recordedReplacement.enabled) !== manifestReplacement.enabled ||
|
|
3187
|
+
String(recordedReplacement.reason || '') !==
|
|
3188
|
+
String(manifestReplacement.reason || '')
|
|
3189
|
+
) {
|
|
3190
|
+
fail(
|
|
3191
|
+
'RELEASE_SHIP_REPLACE_MANIFEST_INTENT_MISMATCH: --replace-manifest/--reason 必须与该 candidate 的预发发布意图完全一致;请使用 ship.json 记录的同一对参数'
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3157
3195
|
|
|
3158
3196
|
if (confirmProduction && !existingShip?.preproductionDeploymentId) {
|
|
3159
3197
|
fail(
|
|
@@ -3185,6 +3223,7 @@ async function runApplicationShipCommand(
|
|
|
3185
3223
|
candidateHash: candidate.candidateHash,
|
|
3186
3224
|
sourceRevision: candidate.sourceRevision,
|
|
3187
3225
|
candidateFile: created.candidateFile,
|
|
3226
|
+
manifestReplacement,
|
|
3188
3227
|
});
|
|
3189
3228
|
} else {
|
|
3190
3229
|
assertLocalCandidate(candidate);
|
|
@@ -3221,12 +3260,18 @@ async function runApplicationShipCommand(
|
|
|
3221
3260
|
preproductionAppReleaseId:
|
|
3222
3261
|
deployment.targetAppReleaseId || null,
|
|
3223
3262
|
preproductionStatus: deployment.status,
|
|
3263
|
+
manifestReplacement,
|
|
3224
3264
|
preparedAt:
|
|
3225
3265
|
existingShip?.preparedAt ||
|
|
3226
3266
|
new Date().toISOString(),
|
|
3227
3267
|
nextCommand:
|
|
3228
3268
|
`openxiangda release ship --change ${changeId} ` +
|
|
3229
|
-
'--confirm-production'
|
|
3269
|
+
'--confirm-production' +
|
|
3270
|
+
(manifestReplacement.enabled
|
|
3271
|
+
? ` --replace-manifest --reason ${JSON.stringify(
|
|
3272
|
+
manifestReplacement.reason
|
|
3273
|
+
)}`
|
|
3274
|
+
: ''),
|
|
3230
3275
|
});
|
|
3231
3276
|
return {
|
|
3232
3277
|
phase: 'preproduction',
|
|
@@ -3569,12 +3614,13 @@ async function release(args) {
|
|
|
3569
3614
|
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
3570
3615
|
print([
|
|
3571
3616
|
'用法: openxiangda release publish|begin|status|explain|integration-status|renew|end [--change id] [--profile name] [--json]',
|
|
3572
|
-
' openxiangda release ship|candidate|deploy|reconcile|test|promote|rollback [--candidate id] [--environment target] [--adopt-online-baseline --adoption-reason text] [--confirm-production] [--json]',
|
|
3617
|
+
' 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]',
|
|
3573
3618
|
' openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]',
|
|
3574
3619
|
' 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]',
|
|
3575
3620
|
'常用流程:',
|
|
3576
3621
|
' openxiangda release ship --change <id> --profile <name>',
|
|
3577
3622
|
' openxiangda release ship --change <id> --profile <name> --adopt-online-baseline --adoption-reason "已审计目标来自多次历史发布"',
|
|
3623
|
+
' openxiangda release ship --change <id> --profile <name> --replace-manifest --reason "已审查精确 Function manifest"',
|
|
3578
3624
|
' # 确认预发结果后;可选 --acceptance-note 留下人工验收记录:',
|
|
3579
3625
|
' openxiangda release ship --change <id> --profile <name> --confirm-production',
|
|
3580
3626
|
' openxiangda release publish --change <id> --profile <name>',
|
|
@@ -3590,6 +3636,7 @@ async function release(args) {
|
|
|
3590
3636
|
'说明:',
|
|
3591
3637
|
' - ship 是环境托管应用的两段式快车道:首次只部署预发并停止;确认预发结果后,第二次以同一 candidate 晋级正式。人工验收备注可选。',
|
|
3592
3638
|
' - 已合入主线但线上来自多次历史 lineage 的精确非删除恢复可显式使用 --adopt-online-baseline 与 --adoption-reason;仍保留冻结 Head、CAS、租约、staged children 和单次 App finalize。',
|
|
3639
|
+
' - ship 的 --replace-manifest 与 --reason 必须成对,只透传给精确 Backend Function/Automation stage;正式确认必须复用与预发完全相同的参数。',
|
|
3593
3640
|
' - ship 当前不支持 --dry-run;任何未声明参数都会在 candidate/deployment 写入前 fail-closed。需要只读检查时使用 environment status/diff 与 release status。',
|
|
3594
3641
|
' - publish 默认等待租约并按私有执行日志恢复;上次写结果不确定时必须只读核对后显式 --resume-after-review。',
|
|
3595
3642
|
' - app-head 默认只输出紧凑 head 摘要;需要完整 manifest 时显式追加 --full。',
|
|
@@ -17855,6 +17902,66 @@ function buildAutomationBackendReleaseSources(definition, label) {
|
|
|
17855
17902
|
return sources;
|
|
17856
17903
|
}
|
|
17857
17904
|
|
|
17905
|
+
function isCompleteDeclarativeAutomationDefinition(definition) {
|
|
17906
|
+
if (
|
|
17907
|
+
!definition ||
|
|
17908
|
+
typeof definition !== 'object' ||
|
|
17909
|
+
Array.isArray(definition) ||
|
|
17910
|
+
definition.version !== 'v3' ||
|
|
17911
|
+
!Array.isArray(definition.nodes) ||
|
|
17912
|
+
definition.nodes.length === 0 ||
|
|
17913
|
+
!Array.isArray(definition.edges)
|
|
17914
|
+
) {
|
|
17915
|
+
return false;
|
|
17916
|
+
}
|
|
17917
|
+
const nodeIds = new Set();
|
|
17918
|
+
let startCount = 0;
|
|
17919
|
+
for (const node of definition.nodes) {
|
|
17920
|
+
if (
|
|
17921
|
+
!node ||
|
|
17922
|
+
typeof node !== 'object' ||
|
|
17923
|
+
!String(node.id || '').trim() ||
|
|
17924
|
+
!String(node.type || '').trim() ||
|
|
17925
|
+
!node.data ||
|
|
17926
|
+
typeof node.data !== 'object' ||
|
|
17927
|
+
Array.isArray(node.data)
|
|
17928
|
+
) {
|
|
17929
|
+
return false;
|
|
17930
|
+
}
|
|
17931
|
+
const nodeId = String(node.id);
|
|
17932
|
+
if (nodeIds.has(nodeId)) return false;
|
|
17933
|
+
nodeIds.add(nodeId);
|
|
17934
|
+
if (node.type === 'start') startCount += 1;
|
|
17935
|
+
}
|
|
17936
|
+
if (startCount !== 1) return false;
|
|
17937
|
+
return definition.edges.every(
|
|
17938
|
+
edge =>
|
|
17939
|
+
edge &&
|
|
17940
|
+
typeof edge === 'object' &&
|
|
17941
|
+
String(edge.id || '').trim() &&
|
|
17942
|
+
nodeIds.has(String(edge.source || '')) &&
|
|
17943
|
+
nodeIds.has(String(edge.target || ''))
|
|
17944
|
+
);
|
|
17945
|
+
}
|
|
17946
|
+
|
|
17947
|
+
function resolveAutomationCreateBackendReleaseMode(candidate, definition) {
|
|
17948
|
+
if (
|
|
17949
|
+
candidate.operation !== 'create' ||
|
|
17950
|
+
candidate.mode === 'manifest_replacement'
|
|
17951
|
+
) {
|
|
17952
|
+
return candidate.mode;
|
|
17953
|
+
}
|
|
17954
|
+
if (buildAutomationSourcePatchSources({}, definition).length > 0) {
|
|
17955
|
+
return 'source_only';
|
|
17956
|
+
}
|
|
17957
|
+
if (isCompleteDeclarativeAutomationDefinition(definition)) {
|
|
17958
|
+
return 'manifest_replacement';
|
|
17959
|
+
}
|
|
17960
|
+
fail(
|
|
17961
|
+
`Automation:${candidate.code} 新建资源既不包含可发布的源码快照,也不包含完整的 definitionJson.version=v3 声明式定义`
|
|
17962
|
+
);
|
|
17963
|
+
}
|
|
17964
|
+
|
|
17858
17965
|
async function buildBackendReleaseResourceInput(
|
|
17859
17966
|
config,
|
|
17860
17967
|
target,
|
|
@@ -17945,21 +18052,37 @@ async function buildBackendReleaseResourceInput(
|
|
|
17945
18052
|
true,
|
|
17946
18053
|
uploadCache
|
|
17947
18054
|
));
|
|
18055
|
+
const mode = resolveAutomationCreateBackendReleaseMode(
|
|
18056
|
+
candidate,
|
|
18057
|
+
definitionJson
|
|
18058
|
+
);
|
|
18059
|
+
candidate.mode = mode;
|
|
17948
18060
|
const common = {
|
|
17949
18061
|
kind: 'Automation',
|
|
17950
18062
|
code: candidate.code,
|
|
17951
|
-
mode
|
|
18063
|
+
mode,
|
|
17952
18064
|
operation: candidate.operation,
|
|
17953
18065
|
};
|
|
17954
|
-
if (
|
|
18066
|
+
if (mode === 'manifest_replacement') {
|
|
17955
18067
|
return {
|
|
17956
18068
|
...common,
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
18069
|
+
...(candidate.operation === 'create'
|
|
18070
|
+
? {
|
|
18071
|
+
create: buildBackendReleaseAutomationMetadata(
|
|
18072
|
+
target,
|
|
18073
|
+
candidate.item,
|
|
18074
|
+
definitionJson,
|
|
18075
|
+
viewJson
|
|
18076
|
+
),
|
|
18077
|
+
}
|
|
18078
|
+
: {
|
|
18079
|
+
replacement: buildBackendReleaseAutomationMetadata(
|
|
18080
|
+
target,
|
|
18081
|
+
candidate.item,
|
|
18082
|
+
definitionJson,
|
|
18083
|
+
viewJson
|
|
18084
|
+
),
|
|
18085
|
+
}),
|
|
17963
18086
|
};
|
|
17964
18087
|
}
|
|
17965
18088
|
return {
|
|
@@ -18358,9 +18481,6 @@ async function publishBackendReleaseResources(
|
|
|
18358
18481
|
`${candidate.kind === 'Function' ? 'function' : 'automation'}:${candidate.code}`
|
|
18359
18482
|
)
|
|
18360
18483
|
.sort();
|
|
18361
|
-
const resourceModes = unique(candidates.map(candidate => candidate.mode));
|
|
18362
|
-
const releaseMode =
|
|
18363
|
-
resourceModes.length > 1 ? 'mixed' : resourceModes[0] || 'source_only';
|
|
18364
18484
|
const replacementReason = String(
|
|
18365
18485
|
options.publishLeaseFlags?.reason ||
|
|
18366
18486
|
options.publishLeaseFlags?.['replace-reason'] ||
|
|
@@ -18418,6 +18538,14 @@ async function publishBackendReleaseResources(
|
|
|
18418
18538
|
uploadCache
|
|
18419
18539
|
)
|
|
18420
18540
|
);
|
|
18541
|
+
const resourceModes = unique(resources.map(resource => resource.mode));
|
|
18542
|
+
const releaseMode =
|
|
18543
|
+
resourceModes.length > 1 ? 'mixed' : resourceModes[0] || 'source_only';
|
|
18544
|
+
const requiresReplacementReason = resources.some(
|
|
18545
|
+
resource =>
|
|
18546
|
+
resource.mode === 'manifest_replacement' &&
|
|
18547
|
+
resource.operation === 'update'
|
|
18548
|
+
);
|
|
18421
18549
|
const prepared = await requestWithAuth(
|
|
18422
18550
|
config,
|
|
18423
18551
|
target.profileName,
|
|
@@ -18429,7 +18557,7 @@ async function publishBackendReleaseResources(
|
|
|
18429
18557
|
...(protocolVersion ? { protocolVersion } : {}),
|
|
18430
18558
|
parentReleaseId: head?.activeBackendReleaseId || null,
|
|
18431
18559
|
mode: releaseMode,
|
|
18432
|
-
...(
|
|
18560
|
+
...(requiresReplacementReason
|
|
18433
18561
|
? { replacementReason }
|
|
18434
18562
|
: {}),
|
|
18435
18563
|
sourceRepositoryId: releaseContext.sourceRepositoryId,
|
|
@@ -108,6 +108,8 @@ For a workspace registered by `openxiangda environment init` or connected to an
|
|
|
108
108
|
|
|
109
109
|
For an audited catch-up whose exact non-delete targets are already merged but whose active resources combine multiple historical release lineages, the first ship invocation may add `--adopt-online-baseline --adoption-reason "..."`. Ship validates the pair before candidate/deployment creation and forwards it only to exact scoped resource stages; frozen online heads, change/lease ownership, delete/prune/force rejection, server CAS, staged-child verification, and the single atomic App finalize remain mandatory.
|
|
110
110
|
|
|
111
|
+
When an environment-managed release intentionally replaces complete Function/Automation manifests, `release ship` may add the inseparable `--replace-manifest --reason "..."` pair. The reason must be at least 8 characters; only an exact Backend selector receives it, and the production confirmation must repeat the exact preproduction pair. It never widens Form, Workflow, Runtime, configuration, wildcard, or app-wide stages.
|
|
112
|
+
|
|
111
113
|
`resource plan` and publish dry-runs are strictly GET/HEAD-only. `READ_ONLY_AUTH_REQUIRED` means the access token expired; run `openxiangda auth refresh --profile <name>` or log in again before retrying. Never add an automatic refresh POST inside a plan.
|
|
112
114
|
|
|
113
115
|
`release publish` is the default promotion entrypoint only for legacy unmanaged workspaces. It verifies without rewriting reviewed `change.json`/`release.json`, waits for the app lease, freezes the App capture after ownership is acquired, executes deterministic exact staged steps, resumes from `.openxiangda/releases/<change>/execution.json`, atomically finalizes, verifies mainline integration, and releases the lease. Environment-managed applications use the two-phase `release ship`; candidate/deploy/test/promote, `release begin`, and child commands remain recovery/diagnostic primitives.
|
|
@@ -124,7 +126,7 @@ Because promotion begins from the already-pushed authoritative mainline, `releas
|
|
|
124
126
|
|
|
125
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.
|
|
126
128
|
|
|
127
|
-
When a Function or Automation is selected because its TypeScript source changed, publishing is source-only by default. Backend Release v2 accepts
|
|
129
|
+
When a Function or Automation is selected because its TypeScript source changed, publishing is source-only by default. Backend Release v2 accepts source-backed create, source-free declarative Automation manifest create, source-only update, and manifest replacement update in one immutable child and one database transaction, so a stale member produces zero resource writes and noops do not advance versions/timestamps. A new Automation with a complete `definitionJson.version="v3"` and no `sourceFile` automatically uses manifest create without `--replace-manifest`; an incomplete definition still fails closed. `--stage-only` is fail-closed: every selected mutation must enter that child, and a missing/incompatible Backend Release API never falls back to direct writes. Online bindings, input/output contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged unless exact `--replace-manifest --reason "..."` authority was provided for an existing resource.
|
|
128
130
|
|
|
129
131
|
An App Function may declare metadata-only top-level `secretRefs: [{ name, required }]` only with `function_v2` + `trusted_node_v2`; source resolves values with `await ctx.secrets.get(name)` and uses `ctx.utils.http` for controlled public HTTPS. Create/rotate values through hidden TTY or `openxiangda secret ... --value-stdin --change <change> --profile <name>`. Never put values in arguments, files, manifests, state, plans, logs, errors, or chat. Secret bindings require `backend_release_v2` and whole-app `atomic_staged_children_v2`; a missing capability is fail-closed and never uses the legacy source PATCH. For whole-app activation use exact-scope `resource publish <type> --only <code> --stage-only`, then pass the returned verified `stagedResource` to `release app-finalize`; an active Backend Release is never labeled staged.
|
|
130
132
|
|
|
@@ -61,7 +61,7 @@ Selectors apply before manifest parsing, source dependency analysis, and JS_CODE
|
|
|
61
61
|
|
|
62
62
|
Function/Automation source analysis also extracts statically declared Form filter and order fields. `resource plan` compares them with each bound Form's frozen online schema and reports `formFieldContracts`; publish fails before lease/write when a binding or field is missing. Fix the source or stage the Form schema in the same reviewed release instead of waiting for a production SQL-column error. Dynamic field names remain runtime-validated by the platform and return `FORM_FIELD_NOT_FOUND` as a configuration error.
|
|
63
63
|
|
|
64
|
-
Source-triggered Function/Automation targets use Backend Release v2 when the platform exposes that capability. One child may mix create, source-only update, and manifest replacement update through explicit per-resource `operation/mode`; the CLI freezes the current Backend Release parent plus Git/change baseline, then runs `prepare -> verify -> activate` or stops verified for `--stage-only`. Activation CAS-checks the entire set and applies all updates in one transaction. `--stage-only` and Secret-bound Function publishing fail closed when Backend Release v2 is unavailable; compatibility fallback is limited to non-staged, non-Secret publishing after an explicit Backend head 404. Existing online bindings, contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged; noops do not advance versions/timestamps. A deliberate whole-definition replacement requires exact `--only/--code` and `--replace-manifest --reason "<why>"`; SDD bypass does not imply replacement authority.
|
|
64
|
+
Source-triggered Function/Automation targets use Backend Release v2 when the platform exposes that capability. One child may mix source-backed create, source-free declarative Automation manifest create, source-only update, and manifest replacement update through explicit per-resource `operation/mode`; the CLI freezes the current Backend Release parent plus Git/change baseline, then runs `prepare -> verify -> activate` or stops verified for `--stage-only`. A new Automation with a complete `definitionJson.version="v3"` and no `sourceFile` automatically uses manifest create without `--replace-manifest`; an incomplete definition still fails closed. Activation CAS-checks the entire set and applies all updates in one transaction. `--stage-only` and Secret-bound Function publishing fail closed when Backend Release v2 is unavailable; compatibility fallback is limited to non-staged, non-Secret publishing after an explicit Backend head 404. Existing online bindings, contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged; noops do not advance versions/timestamps. A deliberate whole-definition replacement of an existing resource requires exact `--only/--code` and `--replace-manifest --reason "<why>"`; SDD bypass does not imply replacement authority.
|
|
65
65
|
|
|
66
66
|
Functions with a top-level `secretRefs` field use `backend_release_v2`, including an explicit empty list that removes bindings. This path never falls back to source PATCH. It requires the per-app Secret capability probe to grant `app_function_secrets_v1`, `trusted_node_v2`, `backend_release_v2`, and `atomic_staged_children_v2`; otherwise plan/publish fails closed.
|
|
67
67
|
|
|
@@ -109,6 +109,8 @@ Once `environment init` registers a logical application, or `environment attach`
|
|
|
109
109
|
|
|
110
110
|
When a reviewed catch-up contains exact non-delete targets that are already on authoritative mainline but the active application combines several historical release lineages, the first ship invocation may add `--adopt-online-baseline --adoption-reason "..."`. Ship validates the pair before candidate/deployment creation and forwards it only to exact scoped resource stages. It does not relax frozen online heads, change/lease ownership, delete/prune/force rejection, server CAS, staged-child verification, or the single atomic App finalize.
|
|
111
111
|
|
|
112
|
+
An environment-managed release may intentionally replace complete Function/Automation manifests with `release ship --replace-manifest --reason "..."`. The two flags are inseparable, the reason is at least 8 characters, only an exact Backend stage receives them, and production confirmation must repeat the exact preproduction intent. They never widen Form, Workflow, Runtime, configuration, wildcard, or app-wide stages.
|
|
113
|
+
|
|
112
114
|
`release publish` is the normal whole-app entrypoint for legacy unmanaged workspaces: it verifies SDD without mutating reviewed files, waits for the promotion lease, freezes one App capture, stages the exact Form/Backend/Runtime children, resumes from a private execution journal, finalizes once, and releases the lease. Managed applications use two-phase `release ship` and its deployment-scoped journal. Individual candidate/deploy/test/promote commands are recovery/diagnostic primitives.
|
|
113
115
|
|
|
114
116
|
Reviewed bundle commands may retain `<profile>` as a template. The explicit real `release publish --profile <name>` value is bound to actual child argv without rewriting tracked SDD. React SPA page codes remain logical coverage targets and activate through the single Runtime child; they do not require PageRelease. If local lease state disappears, `release end --change <id>` reconciles a self-owned remote lease from the private execution journal and never reports inactive while a remote lease is active.
|
package/package.json
CHANGED
|
@@ -28,5 +28,5 @@ openxiangda resource typegen --profile <name>
|
|
|
28
28
|
- Formal changes should keep Git as the source of truth: edit manifests, validate, plan, then publish.
|
|
29
29
|
- Exact selectors apply before manifest/source analysis and JS_CODE build: touch only selected targets plus transitive/shared/ambient dependencies; omit selectors only for intentional full-workspace work.
|
|
30
30
|
- `resource plan` and publish dry-runs are GET/HEAD-only. On `READ_ONLY_AUTH_REQUIRED`, run `openxiangda auth refresh --profile <name>` or log in again before retrying; never refresh inside the plan.
|
|
31
|
-
- Source-triggered Function/Automation publishing patches only source fields on the server and preserves online bindings/contracts/metadata/state.
|
|
31
|
+
- Source-triggered Function/Automation publishing patches only source fields on the server and preserves online bindings/contracts/metadata/state. A new source-free Automation with a complete `definitionJson.version="v3"` automatically uses manifest create; replacing an existing whole manifest requires `--replace-manifest --reason "..."`.
|
|
32
32
|
- Formal promotion freezes the clean publish HEAD separately from the change base, preflights the complete set, and rejects `SOURCE_BASE_DIVERGED` / `RELEASE_SOURCE_BEHIND_MAIN` / `RESOURCE_FIELD_CONFLICT`. After activation, merge/fast-forward and push the frozen SHA, verify `release integration-status`, then run normal `release end`; squash/rebase does not preserve the released source.
|
|
@@ -33,7 +33,8 @@ This is an OpenXiangda React SPA workspace. See [AGENTS.md](mdc:AGENTS.md) for f
|
|
|
33
33
|
- Roles that create roles, assign members, grant API permissions, maintain permission groups, or manage organization accounts must declare `apiPermissionCodes`, such as `app:role:manage`, `app:page-permission-group:manage`, `app:form-permission-group:manage`, and `app:organization:manage`.
|
|
34
34
|
- `src/resources/**` is the resource source of truth; use `validate -> plan -> publish`.
|
|
35
35
|
- Select logical resource codes with `--only`, or one code with `--code`; type-wide/app-wide release requires an approved dependency closure.
|
|
36
|
-
- Source-triggered Function/Automation publishing uses server-side source-field PATCH and preserves online bindings, contracts, metadata, trigger/view configuration, and enabled/published state.
|
|
36
|
+
- Source-triggered Function/Automation publishing uses server-side source-field PATCH 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 Backend Release manifest create without `--replace-manifest`; replacing an existing whole manifest requires `--replace-manifest --reason "..."`. On `SOURCE_BASE_DIVERGED` or `RESOURCE_FIELD_CONFLICT`, reconcile, rebuild, and re-plan.
|
|
37
|
+
- 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.
|
|
37
38
|
- React routes live in `src/app/router.tsx`; frontend artifacts are deployed with `openxiangda runtime deploy`.
|
|
38
39
|
- 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.
|
|
39
40
|
- Backend permissions, public-access grants, and App Function role/scope checks are authoritative.
|
|
@@ -28,5 +28,5 @@ openxiangda resource typegen --profile <name>
|
|
|
28
28
|
- Formal changes should keep Git as the source of truth: edit manifests, validate, plan, then publish.
|
|
29
29
|
- Exact selectors apply before manifest/source analysis and JS_CODE build: touch only selected targets plus transitive/shared/ambient dependencies; omit selectors only for intentional full-workspace work.
|
|
30
30
|
- `resource plan` and publish dry-runs are GET/HEAD-only. On `READ_ONLY_AUTH_REQUIRED`, run `openxiangda auth refresh --profile <name>` or log in again before retrying; never refresh inside the plan.
|
|
31
|
-
- Source-triggered Function/Automation publishing patches only source fields on the server and preserves online bindings/contracts/metadata/state.
|
|
31
|
+
- Source-triggered Function/Automation publishing patches only source fields on the server and preserves online bindings/contracts/metadata/state. A new source-free Automation with a complete `definitionJson.version="v3"` automatically uses manifest create; replacing an existing whole manifest requires `--replace-manifest --reason "..."`.
|
|
32
32
|
- Formal promotion freezes the clean publish HEAD separately from the change base, preflights the complete set, and rejects `SOURCE_BASE_DIVERGED` / `RELEASE_SOURCE_BEHIND_MAIN` / `RESOURCE_FIELD_CONFLICT`. After activation, merge/fast-forward and push the frozen SHA, verify `release integration-status`, then run normal `release end`; squash/rebase does not preserve the released source.
|
|
@@ -29,7 +29,8 @@ This is an OpenXiangda React SPA workspace. Read [AGENTS.md](AGENTS.md) for full
|
|
|
29
29
|
- 角色能新增角色、分配成员、授接口权限、维护权限组或管理组织账号时,角色资源必须声明 `apiPermissionCodes`,例如 `app:role:manage`、`app:page-permission-group:manage`、`app:form-permission-group:manage`、`app:organization:manage`。
|
|
30
30
|
- `src/resources/**` 是工程化资源来源,正式多资源变更走 `validate -> plan -> publish`。
|
|
31
31
|
- 默认按逻辑资源 code 使用 `--only` 或单资源 `--code`;全类型/全应用发布必须由批准的依赖闭包明确覆盖。
|
|
32
|
-
- Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state
|
|
32
|
+
- Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state;无源码且 `definitionJson.version="v3"` 完整的新建 Automation 自动走 manifest create,只有替换已有整包 manifest 才必须加 `--replace-manifest --reason "..."`。
|
|
33
|
+
- 环境托管 `release ship --replace-manifest --reason "..."` 必须成对、reason 至少 8 字符且仅限精确 Backend selector;正式确认复用与预发完全相同的参数,不透传 Form/Workflow/Runtime/配置或全量步骤。
|
|
33
34
|
- Promotion 必须持有 `release begin/end` 租约;`release begin` 只接受与权威远端 tip 完全一致的 clean main/master。feature branch 或未 push 主线在任何写入前失败。激活后直接运行 `integration-status` 和 `release end`,不再补做发布后合并。
|
|
34
35
|
- 已有工作区通过 `environment attach` 接入环境组,旧资源映射只迁移到 appType 相同的预发 target,正式 target 必须为空。preproduction / production 分别拥有独立 appType、资源 ID、数据和副作用策略;禁止直接 `release publish`,禁止跨环境复制 ID,使用 `openxiangda studio` 查看状态。只有用户明确授权的投产前重分类可执行 `environment swap --reason "..." --confirm-production`;它不移动应用数据或 Release Head,且默认不放开副作用。
|
|
35
36
|
- 单独修改某个环境的副作用策略只能使用 `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 模式才阻断。
|
|
@@ -57,6 +57,8 @@ openxiangda commands --json
|
|
|
57
57
|
|
|
58
58
|
只有已审计代码早已进入权威主线、而精确非删除目标在线上来自多次历史发布且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`。该参数不会放松冻结 online heads、change/lease、删除/全量拒绝、服务端 CAS、staged children 或单次 App finalize 原子激活。
|
|
59
59
|
|
|
60
|
+
环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只会进入精确 Backend selector,正式确认必须复用与预发完全相同的参数,绝不扩散到表单、流程、Runtime、配置资源或全量范围。
|
|
61
|
+
|
|
60
62
|
受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为发布边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍必须 fail closed。
|
|
61
63
|
|
|
62
64
|
`resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
|
|
@@ -80,7 +82,7 @@ App Function 第三方凭据只能在 Function manifest 顶层声明 `secretRefs
|
|
|
80
82
|
|
|
81
83
|
`openxiangda runtime deploy --no-activate` 会构建并上传不可变预览版本;发布前先提交所有可能进入构建的源码/配置。所有 Runtime deploy(包括 `--no-activate`)都会先获取应用发布 lease,并在任何构建和上传前冻结 clean `HEAD` 与当前 active Runtime 父血缘;旧分支返回 `RUNTIME_SOURCE_BASE_DIVERGED`,不能先上传旧 preview 再激活。`openspec/` SDD 证据和生成/状态目录不算源码 dirty。仅审批的回退可使用 `--allow-runtime-rollback --reason "至少 8 个字符"`;`--no-build` 不会跳过守卫。不要手工修改 `dist/index.html`。
|
|
82
84
|
|
|
83
|
-
Function/Automation 走 Backend Release v2;同一个 child
|
|
85
|
+
Function/Automation 走 Backend Release v2;同一个 child 可以混合源码 create、无 `sourceFile` 的完整 v3 声明式 Automation manifest create、source-only update 与显式 manifest replacement,并对整个集合做 CAS。声明式 create 自动选路且不需要 `--replace-manifest`;替换已有资源才需要该显式授权。正式多资源发布必须使用 canonical 精确 selector 和 `--stage-only`。`release begin` 只接受与权威远端默认主分支完全一致的 clean HEAD;feature branch 或未 push 的 main 会在任何平台写入前失败。成功激活后主线证据天然成立,不再补做发布后合并。
|
|
84
86
|
|
|
85
87
|
## 应用结构
|
|
86
88
|
|
|
@@ -31,7 +31,7 @@ Exact `--only/--code` selectors apply before manifest/source analysis and JS_COD
|
|
|
31
31
|
|
|
32
32
|
App Function manifests may contain only logical names in top-level `secretRefs`; use `function_v2` / `trusted_node_v2`, resolve values with `ctx.secrets.get(name)`, and manage values only with `openxiangda secret ... --value-stdin --change ... --profile ...`. Values never belong in Git, `.env`, manifests, source, builds, plans, logs, or errors.
|
|
33
33
|
|
|
34
|
-
When a Function/Automation enters scope only through source changes, publishing uses a server-side source-field PATCH and preserves online bindings, contracts, metadata, trigger/view configuration, and enabled/published state.
|
|
34
|
+
When a Function/Automation enters scope only through source changes, publishing uses a server-side source-field PATCH 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 `--replace-manifest --reason "..."`. Formal promotion freezes the clean publish HEAD separately from the change/remote baseline and preflights the whole set; `SOURCE_BASE_DIVERGED`, `RELEASE_SOURCE_BEHIND_MAIN`, and `RESOURCE_FIELD_CONFLICT` require reconciliation. After activation, merge/push the frozen SHA, verify `release integration-status`, then run normal `release end`.
|
|
35
35
|
|
|
36
36
|
Before editing `roles`, `permissions/page-groups`, or `permissions/form-groups` for account/role/data-scope/RBAC/query-param authorization work, run `openxiangda design gates --topic permissions --json`, choose the permission mode, and write the permission matrix.
|
|
37
37
|
|
|
@@ -29,7 +29,8 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
|
|
|
29
29
|
- L0 read-only/docs/tests need no SDD; L1 narrow reversible fixes record exact scope without a redundant second confirmation; schema, business Functions, Automation/Workflow, permissions, auth/public access, data writes, and runtime/config are L2/L3 full-SDD work. Live evidence/archive are post-release stages.
|
|
30
30
|
- Very small copy/style/binding changes may use `openxiangda sdd quick <change> ...`; quick mode is limited to an exact low-risk scope and does not add a redundant proposal/approval loop when the user already requested that exact edit.
|
|
31
31
|
- SDD is streamlined by default: approval and exact structured scope are hard gates, while unfinished task/evidence/spec prose only warns. Use `strictDocumentation: true` only when prose must block.
|
|
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.
|
|
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
|
+
- 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.
|
|
33
34
|
- 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. After activation, run `integration-status` and `release end`; no post-release merge is needed.
|
|
34
35
|
- 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.
|
|
35
36
|
- 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.
|
|
@@ -41,7 +41,7 @@ openxiangda resource publish <type> --code <code> --profile <name> # 单资源
|
|
|
41
41
|
openxiangda resource pull --profile <name> # 平台 → 本地
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Function/Automation 仅因源码变化进入 scope 时,默认通过服务端字段 PATCH 只更新 source snapshot,并保留线上 bindings、contracts、metadata、trigger/view 配置和 enabled/published state
|
|
44
|
+
Function/Automation 仅因源码变化进入 scope 时,默认通过服务端字段 PATCH 只更新 source snapshot,并保留线上 bindings、contracts、metadata、trigger/view 配置和 enabled/published state。无源码且 `definitionJson.version="v3"` 完整的新建 Automation 自动走 manifest create;确需让本地整包 manifest 覆盖已有线上定义时,必须追加 `--replace-manifest --reason "..."`。正式发布先 `release begin --change <change>` 分别冻结 clean publish HEAD 与 change/远端 baseline 并整体 preflight;`SOURCE_BASE_DIVERGED` / `RELEASE_SOURCE_BEHIND_MAIN` / `RESOURCE_FIELD_CONFLICT` 不能强行重试。上线后 merge/push 冻结 SHA,验证 `release integration-status`,再正常 `release end`。
|
|
45
45
|
|
|
46
46
|
`resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
|
|
47
47
|
|
|
@@ -29,7 +29,8 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
|
|
|
29
29
|
- L0 只读/文档/测试无需 SDD;L1 窄小可逆改动记录精确范围且不重复确认;表单结构、业务函数、自动化/流程、权限、登录/公开访问、数据写入、runtime/config 等 L2/L3 走完整 SDD,live evidence/archive 放到发布后。
|
|
30
30
|
- 极小的文案、样式、绑定修正可使用 `openxiangda sdd quick <change> ...`,但必须限制在精确的低风险范围内;用户已明确要求该小改时不再重复 propose/approve。
|
|
31
31
|
- SDD 默认 streamlined:approval 与结构化精确范围是硬门禁,未完成的 task/evidence/spec 文案只告警;只有 `strictDocumentation: true` 才阻断。
|
|
32
|
-
- Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state
|
|
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
|
+
- 环境托管 `release ship --replace-manifest --reason "..."` 必须成对、reason 至少 8 字符且仅限精确 Backend selector;正式确认复用与预发完全相同的参数,不透传 Form/Workflow/Runtime/配置或全量步骤。
|
|
33
34
|
- 写入平台前只从与权威远端 tip 完全一致的 clean main/master 执行 `release begin`。feature branch 或未 push 主线在任何写入前失败;激活后直接运行 `integration-status` 和 `release end`,无需发布后再合并。
|
|
34
35
|
- 环境托管工作区禁止直发:已有工作区先用 `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,且默认不放开副作用。
|
|
35
36
|
- 单独修改某个环境的副作用策略只能使用 `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 模式才阻断。
|
|
@@ -55,10 +55,11 @@
|
|
|
55
55
|
- ✅ 发现平台缺陷、能力缺口、规则不清、反复 workaround、AI 不确定点、用户可见体验问题时,主动 `openxiangda feedback submit --yes`;发送后告诉用户反馈内容和 fingerprint。
|
|
56
56
|
- ✅ 正式多资源开发优先写 `src/resources/**` 后执行 `openxiangda resource validate|plan|publish <type> --only <codes>`;单资源可用 `--code <code>`。直接 CLI 写平台资源时先 `--dry-run`,需要避免漂移就加 `--write-manifest`。
|
|
57
57
|
- ✅ `resource plan` 与 publish dry-run 严格只允许 GET/HEAD;遇到 `READ_ONLY_AUTH_REQUIRED` 时先执行 `openxiangda auth refresh --profile <name>` 或重新登录,不得在 plan 内自动 POST 刷新 token。
|
|
58
|
-
- ✅ Function/Automation 使用 Backend Release v2;正式多资源发布用精确 `--only/--code` 加 `--stage-only` 暂存,同一 child
|
|
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 "..."`。
|
|
59
59
|
- ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
|
|
60
60
|
- ✅ 已通过 `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 模式才阻断。
|
|
61
61
|
- ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
|
|
62
|
+
- ✅ 环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只透传精确 Backend selector,正式确认必须复用同一对参数,不扩散到 Form/Workflow/Runtime/配置或全量范围。
|
|
62
63
|
- ✅ 受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍 fail closed。
|
|
63
64
|
- ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
|
|
64
65
|
|