openxiangda 1.0.192 → 1.0.193

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
@@ -53,6 +53,8 @@ openxiangda environment init --logical-app example-app --name "示例应用" --k
53
53
  openxiangda environment bind production --app-type APP_PROD --profile dev
54
54
  openxiangda environment attach --logical-app example-app --preproduction-target example-pre --production-target example-prod --profile dev
55
55
  openxiangda environment status --json
56
+ # 仅限投产前环境角色重分类:原子交换两个既有应用的角色,应用数据和 Release Head 不移动
57
+ openxiangda environment swap --reason "投产前将现有业务应用调整为正式应用" --confirm-production --profile dev
56
58
  openxiangda studio
57
59
  # 环境托管应用日常发布:冻结候选 → 预发 → 测试证据 → 同候选晋级正式
58
60
  openxiangda release candidate --change mainline-release --environment preproduction
@@ -71,6 +73,8 @@ An environment-managed workspace keeps one logical application with independent
71
73
 
72
74
  Existing workspaces connect to a server-side environment set with `environment attach`. If the legacy `profiles.<profile>` binding has the same `appType` as one environment, its resource mappings are copied only into that matching target (normally preproduction). Production starts with an empty mapping, and later writes update only the selected target even when both targets reuse one login profile.
73
75
 
76
+ `environment swap` is a commissioning/reclassification operation, not a release shortcut. It requires `--confirm-production` and a reason, atomically exchanges the two existing environment roles, preserves each application's data and Release Heads, and remaps local resource IDs by appType. Existing side-effect policies remain restricted unless explicit replacement policy JSON is supplied.
77
+
74
78
  `openxiangda studio` starts a loopback-only local Developer Center with a random session token. It shows target bindings, candidate/deployment status, Git state, test evidence, drift, and the next safe action. Its buttons invoke only registered OpenXiangda operations; it does not expose arbitrary shell execution. Production promotion and rollback still require an explicit production confirmation.
75
79
 
76
80
  `resource plan` and `resource publish --dry-run` run behind a strict GET/HEAD-only HTTP guard. If a read receives HTTP 401, the command fails with `READ_ONLY_AUTH_REQUIRED` and never calls the token refresh POST from inside the plan. Run `openxiangda auth refresh --profile <name>` (or log in again) before retrying; a plan must not mutate auth state or platform resources.
@@ -182,9 +182,11 @@ function bindEnvironmentTarget(state, input) {
182
182
  state.targets = state.targets || {};
183
183
  const previous = state.targets[targetName] || {};
184
184
  const previousResources = previous.resources || {};
185
- const resources = hasStateResourceMappings(previousResources)
186
- ? previousResources
187
- : cloneJsonValue(input.seedResources || previousResources) || {};
185
+ const resources = input.replaceResources
186
+ ? cloneJsonValue(input.seedResources || {}) || {}
187
+ : hasStateResourceMappings(previousResources)
188
+ ? previousResources
189
+ : cloneJsonValue(input.seedResources || previousResources) || {};
188
190
  state.targets[targetName] = {
189
191
  ...previous,
190
192
  targetName,
package/lib/cli.js CHANGED
@@ -299,7 +299,7 @@ Usage:
299
299
  openxiangda open-api credential list|get|create|update|rotate-secret [id] [--profile name] [--show-secret] [--yes] [--json]
300
300
  openxiangda design gates|template|review [--topic code] [--json]
301
301
  openxiangda sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive [change] [--change id] [--json]
302
- openxiangda environment init|attach|bind|list|status|diff|use [preproduction|production] [--logical-app code] [--environment target] [--json]
302
+ openxiangda environment init|attach|bind|swap|list|status|diff|use [preproduction|production] [--logical-app code] [--environment target] [--json]
303
303
  openxiangda release publish|begin|status|integration-status|renew|end [--change id] [--profile name] [--watch] [--json]
304
304
  openxiangda release candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--json]
305
305
  openxiangda task status --change <id> [--profile name] [--watch] [--json]
@@ -1862,9 +1862,10 @@ function saveEnvironmentWorkspaceState(state, setDetail, environmentDetail, inpu
1862
1862
  };
1863
1863
  const legacyBound = state.profiles?.[input.profile];
1864
1864
  const seedResources =
1865
- legacyBound?.appType === environmentDetail.appType
1865
+ input.seedResources ??
1866
+ (legacyBound?.appType === environmentDetail.appType
1866
1867
  ? legacyBound.resources
1867
- : undefined;
1868
+ : undefined);
1868
1869
  const bound = bindEnvironmentTarget(state, {
1869
1870
  targetName: input.targetName,
1870
1871
  profile: input.profile,
@@ -1875,6 +1876,7 @@ function saveEnvironmentWorkspaceState(state, setDetail, environmentDetail, inpu
1875
1876
  publicOrigin: environmentDetail.publicOrigin,
1876
1877
  sideEffectPolicy: environmentDetail.sideEffectPolicy,
1877
1878
  seedResources,
1879
+ replaceResources: input.replaceResources,
1878
1880
  });
1879
1881
  ensureResourceBuckets(bound);
1880
1882
  saveProjectState(state);
@@ -1889,6 +1891,7 @@ async function environment(args) {
1889
1891
  '用法: openxiangda environment init --logical-app <code> --name <name> --kind preproduction --app-type APP_XXX [--target name] [--profile name]',
1890
1892
  ' openxiangda environment attach --logical-app <code> [--preproduction-target name] [--production-target name] [--profile name]',
1891
1893
  ' openxiangda environment bind preproduction|production --app-type APP_XXX [--target name] [--profile name]',
1894
+ ' openxiangda environment swap --reason <text> --confirm-production [--preproduction-side-effect-policy-json <JSON|file>] [--production-side-effect-policy-json <JSON|file>] [--profile name]',
1892
1895
  ' openxiangda environment list|status|diff [--logical-app code] [--profile name] [--json]',
1893
1896
  ' openxiangda environment use <target|preproduction|production>',
1894
1897
  ].join('\n'));
@@ -2049,6 +2052,85 @@ async function environment(args) {
2049
2052
  Object.values(state.targets || {})[0]?.profile ||
2050
2053
  config.currentProfile;
2051
2054
 
2055
+ if (subcommand === 'swap') {
2056
+ if (!flags['confirm-production']) {
2057
+ fail(
2058
+ 'environment swap 会交换真实应用的预发/正式角色,必须提供 --confirm-production'
2059
+ );
2060
+ }
2061
+ const reason = readStringFlag(flags, 'reason');
2062
+ if (!reason || reason.trim().length < 10) {
2063
+ fail('environment swap 必须提供至少 10 个字符的 --reason');
2064
+ }
2065
+ const profile = getProfile(config, selectedProfile);
2066
+ const previousTargets = Object.values(state.targets || {}).map(target =>
2067
+ JSON.parse(JSON.stringify(target))
2068
+ );
2069
+ const readOptionalPolicy = flagName =>
2070
+ flags[flagName]
2071
+ ? readJsonInput(flags[flagName], flagName)
2072
+ : undefined;
2073
+ const data = await requestWithAuth(
2074
+ config,
2075
+ profile.profileName,
2076
+ environmentSetApiPath(code, '/environments/swap-roles'),
2077
+ {
2078
+ method: 'POST',
2079
+ body: {
2080
+ confirmation: 'SWAP_PREPRODUCTION_AND_PRODUCTION',
2081
+ reason,
2082
+ preproduction: {
2083
+ displayName: flags['preproduction-display-name'],
2084
+ publicOrigin: flags['preproduction-public-origin'],
2085
+ sideEffectPolicy: readOptionalPolicy(
2086
+ 'preproduction-side-effect-policy-json'
2087
+ ),
2088
+ },
2089
+ production: {
2090
+ displayName: flags['production-display-name'],
2091
+ publicOrigin: flags['production-public-origin'],
2092
+ sideEffectPolicy: readOptionalPolicy(
2093
+ 'production-side-effect-policy-json'
2094
+ ),
2095
+ },
2096
+ },
2097
+ }
2098
+ );
2099
+ const targets = [];
2100
+ for (const environmentDetail of data.environments || []) {
2101
+ const previousKindTarget = previousTargets.find(
2102
+ target => target.kind === environmentDetail.kind
2103
+ );
2104
+ const previousAppTarget = previousTargets.find(
2105
+ target => target.appType === environmentDetail.appType
2106
+ );
2107
+ targets.push(
2108
+ saveEnvironmentWorkspaceState(state, data, environmentDetail, {
2109
+ targetName:
2110
+ previousKindTarget?.targetName || environmentDetail.kind,
2111
+ profile:
2112
+ previousKindTarget?.profile ||
2113
+ previousAppTarget?.profile ||
2114
+ profile.profileName,
2115
+ seedResources: previousAppTarget?.resources,
2116
+ replaceResources: true,
2117
+ })
2118
+ );
2119
+ }
2120
+ const result = {
2121
+ logicalApp: state.logicalApp,
2122
+ targets,
2123
+ roleSwap: data.roleSwap,
2124
+ };
2125
+ if (flags.json) return writeJson(result);
2126
+ print(
2127
+ `应用环境角色已交换: ${targets
2128
+ .map(target => `${target.kind}=${target.appType}`)
2129
+ .join(', ')}`
2130
+ );
2131
+ return;
2132
+ }
2133
+
2052
2134
  if (subcommand === 'bind') {
2053
2135
  const kind = normalizeEnvironmentKind(positional[0] || flags.kind);
2054
2136
  const appType = readStringFlag(flags, 'app-type');
@@ -2109,7 +2191,7 @@ async function environment(args) {
2109
2191
  return;
2110
2192
  }
2111
2193
 
2112
- fail('用法: openxiangda environment init|attach|bind|list|status|diff|use');
2194
+ fail('用法: openxiangda environment init|attach|bind|swap|list|status|diff|use');
2113
2195
  }
2114
2196
 
2115
2197
  function buildEnvironmentStatusDiff(status) {
@@ -13305,7 +13387,7 @@ async function commands(args) {
13305
13387
  'open-api credential list|get|create|update|rotate-secret [id]',
13306
13388
  'design gates|template|review [--topic code]',
13307
13389
  'sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive',
13308
- 'environment init|attach|bind|list|status|diff|use',
13390
+ 'environment init|attach|bind|swap|list|status|diff|use',
13309
13391
  'release candidate|deploy|test|promote|rollback|publish|begin|status|integration-status|renew|end|backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry|app-capture|app-head|app-list|app-detail|app-diff|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort',
13310
13392
  'studio [--no-open] [--port 0]',
13311
13393
  'env',
@@ -104,7 +104,7 @@ openxiangda release test --deployment <deployment-id> --environment preproductio
104
104
  openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
105
105
  ```
106
106
 
107
- For a workspace registered by `openxiangda environment init` or connected to an existing set with `openxiangda environment attach`, production is never a direct publish target. The immutable candidate must first be deployed and passed in preproduction, and promotion must reuse the same candidate hash. Preproduction and production keep independent app/resource/data identities in `.openxiangda/state.json`; do not copy IDs between targets. `environment attach` may seed only the matching legacy app binding into preproduction and must leave production empty. Use `openxiangda studio` for the loopback-only developer view of bindings, drift, evidence, and safe next actions.
107
+ For a workspace registered by `openxiangda environment init` or connected to an existing set with `openxiangda environment attach`, production is never a direct publish target. The immutable candidate must first be deployed and passed in preproduction, and promotion must reuse the same candidate hash. Preproduction and production keep independent app/resource/data identities in `.openxiangda/state.json`; do not copy IDs between targets. `environment attach` may seed only the matching legacy app binding into preproduction and must leave production empty. Only during authorized commissioning/reclassification may `environment swap --reason "..." --confirm-production` atomically exchange the two existing roles; it preserves each app's data and Release Heads, remaps local resource IDs by appType, and does not enable production side effects by default. Use `openxiangda studio` for the loopback-only developer view of bindings, drift, evidence, and safe next actions.
108
108
 
109
109
  `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.
110
110
 
@@ -862,6 +862,10 @@ List environment sets or return one set with its bound environments.
862
862
 
863
863
  Binds the missing `preproduction` or `production` app identity. An appType can belong to only one environment, and one set can contain at most one environment of each kind.
864
864
 
865
+ ### POST `/environment-sets/:code/environments/swap-roles`
866
+
867
+ Atomically exchanges the roles of the two existing app identities during an explicitly confirmed commissioning/reclassification operation. The request requires `confirmation=SWAP_PREPRODUCTION_AND_PRODUCTION` and a reason. Application data and Release Heads stay with their appType; local target resources are remapped by appType. Existing side-effect restrictions remain unless the request explicitly supplies replacement policies.
868
+
865
869
  ### GET `/environment-sets/:code/status`
866
870
 
867
871
  Returns each target's active heads, latest deployment, side-effect policy, and candidate drift state for the local Developer Center.
@@ -105,7 +105,7 @@ openxiangda release test --deployment <deployment-id> --environment preproductio
105
105
  openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
106
106
  ```
107
107
 
108
- Once `environment init` registers a logical application, or `environment attach` connects an existing workspace, `preproduction` and `production` are separate target bindings with separate app/resource/data IDs. Never copy IDs between them. Existing legacy resource mappings may seed only the target whose appType matches (normally preproduction); production stays empty. The candidate is immutable and promotion must reuse the candidate that has fresh, passing, deployment-bound preproduction evidence. Direct `release publish` to either managed target fails closed. `openxiangda studio` is the local loopback-only developer view and exposes only registered safe actions.
108
+ Once `environment init` registers a logical application, or `environment attach` connects an existing workspace, `preproduction` and `production` are separate target bindings with separate app/resource/data IDs. Never copy IDs between them. Existing legacy resource mappings may seed only the target whose appType matches (normally preproduction); production stays empty. The candidate is immutable and promotion must reuse the candidate that has fresh, passing, deployment-bound preproduction evidence. Direct `release publish` to either managed target fails closed. Only authorized commissioning/reclassification may run `environment swap --reason "..." --confirm-production`; the atomic operation preserves each app's data and Release Heads, remaps local resources by appType, and keeps existing side-effect restrictions unless explicit replacement policies are supplied. `openxiangda studio` is the local loopback-only developer view and exposes only registered safe actions.
109
109
 
110
110
  `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 candidate/deploy/test/promote and deployment-scoped journals. Individual release commands are recovery/diagnostic primitives.
111
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.192",
3
+ "version": "1.0.193",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  "build:sdk": "tsup --config packages/sdk/tsup.config.ts",
65
65
  "check": "node --check bin/openxiangda.js && node --check lib/*.js && node --check packages/sdk/src/build-source/src/cli.mjs && node --check packages/sdk/src/build-source/scripts/*.mjs && node --check packages/sdk/src/build-source/scripts/utils/*.mjs",
66
66
  "test": "npm run test:fast",
67
- "test:fast": "npm run check && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs && node scripts/release-plan-smoke.mjs && node scripts/typed-resource-plan-smoke.mjs && node scripts/integration-bundle-smoke.mjs && node scripts/content-cache-smoke.mjs && node scripts/dependency-capsule-smoke.mjs && node scripts/sdd-stages-smoke.mjs",
67
+ "test:fast": "npm run check && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs && node scripts/release-plan-smoke.mjs && node scripts/typed-resource-plan-smoke.mjs && node scripts/integration-bundle-smoke.mjs && node scripts/content-cache-smoke.mjs && node scripts/dependency-capsule-smoke.mjs && node scripts/sdd-stages-smoke.mjs && node scripts/environment-swap-cli-smoke.mjs",
68
68
  "test:changed": "npm run check && node scripts/run-test-suite.mjs --changed --evidence",
69
69
  "test:contract": "npm run check && node scripts/run-test-suite.mjs --contract --evidence",
70
70
  "test:release": "npm run check && node scripts/run-test-suite.mjs --release --evidence",
@@ -111,6 +111,7 @@
111
111
  "test:page-release-cli": "node scripts/page-release-cli-smoke.mjs",
112
112
  "test:app-release-cli": "node scripts/app-release-cli-smoke.mjs",
113
113
  "test:application-environments": "node scripts/application-environments-smoke.mjs",
114
+ "test:environment-swap": "node scripts/environment-swap-cli-smoke.mjs",
114
115
  "test:developer-center": "node scripts/developer-center-smoke.mjs",
115
116
  "test:form-release-cas": "node scripts/form-release-cas-smoke.mjs",
116
117
  "test:source-dependencies": "node scripts/source-dependencies-smoke.mjs",
@@ -27,7 +27,7 @@ This is an OpenXiangda React SPA workspace. See [AGENTS.md](mdc:AGENTS.md) for f
27
27
  - Very small copy/style/binding changes may use `openxiangda sdd quick <change> ...`; quick mode records exact low-risk scope without a redundant proposal/approval loop when the user already requested it.
28
28
  - SDD is streamlined by default: approval and exact structured scope are hard gates, while unfinished task/evidence/spec prose only warns. Configure `strictDocumentation: true` only when prose must block.
29
29
  - Before platform writes, run `release begin` from a clean local main/master that exactly equals the authoritative remote tip. Feature branches and unpushed mainline commits fail before any write. After activation, run `integration-status` and `release end`; no post-release merge is needed.
30
- - Managed preproduction and production targets own independent app/resource/data identities and side-effect policy. Existing workspaces use `environment attach`; only an appType-matching legacy binding may seed preproduction and production starts empty. Never direct-publish or copy IDs across them; use `openxiangda studio` to inspect candidate, evidence, and drift state.
30
+ - Managed preproduction and production targets own independent app/resource/data identities and side-effect policy. Existing workspaces use `environment attach`; only an appType-matching legacy binding may seed preproduction and production starts empty. Never direct-publish or copy IDs across them; use `openxiangda studio` to inspect candidate, evidence, and drift state. 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.
31
31
  - Account/role/permission/RBAC/organization-account/query-param authorization work must run `openxiangda design gates --topic permissions --json`, choose `managed-platform-account`, `existing-platform-user-assignment`, `static-role-permission`, or `query-param-context`, and write the permission matrix.
32
32
  - 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`.
33
33
  - `src/resources/**` is the resource source of truth; use `validate -> plan -> publish`.
@@ -31,7 +31,7 @@ This is an OpenXiangda React SPA workspace. Read [AGENTS.md](AGENTS.md) for full
31
31
  - 默认按逻辑资源 code 使用 `--only` 或单资源 `--code`;全类型/全应用发布必须由批准的依赖闭包明确覆盖。
32
32
  - Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state;整包 manifest 替换必须加 `--replace-manifest --reason "..."`。
33
33
  - Promotion 必须持有 `release begin/end` 租约;`release begin` 只接受与权威远端 tip 完全一致的 clean main/master。feature branch 或未 push 主线在任何写入前失败。激活后直接运行 `integration-status` 和 `release end`,不再补做发布后合并。
34
- - 已有工作区通过 `environment attach` 接入环境组,旧资源映射只迁移到 appType 相同的预发 target,正式 target 必须为空。preproduction / production 分别拥有独立 appType、资源 ID、数据和副作用策略;禁止直接 `release publish`,禁止跨环境复制 ID,使用 `openxiangda studio` 查看状态。
34
+ - 已有工作区通过 `environment attach` 接入环境组,旧资源映射只迁移到 appType 相同的预发 target,正式 target 必须为空。preproduction / production 分别拥有独立 appType、资源 ID、数据和副作用策略;禁止直接 `release publish`,禁止跨环境复制 ID,使用 `openxiangda studio` 查看状态。只有用户明确授权的投产前重分类可执行 `environment swap --reason "..." --confirm-production`;它不移动应用数据或 Release Head,且默认不放开副作用。
35
35
  - React SPA 路由由 `src/app/router.tsx` 管理,前端包通过 `openxiangda runtime deploy` 发布。
36
36
  - 表单附件预览使用 `AttachmentField` / `ImageField`;普通自定义页面使用 `AttachmentPreviewList` / `ImagePreviewGrid` / `useFilePreview`,不要伪造表单上下文、直接引用内部预览实现或维护扩展名白名单。
37
37
  - 页面权限、表单权限、公开访问 grants、App Function 后端检查是授权依据;前端只做展示保护。
@@ -53,7 +53,7 @@ openxiangda commands --json
53
53
 
54
54
  模板已停用无范围的 `pnpm deploy` 聚合入口。日常变更必须使用 `resource plan|publish <type> --only <codes>`(单资源可用 `--code <code>`)。Form bundle、Backend Release 和 Runtime 都先暂存;CLI 会按 `--change` 自动聚合 `.openxiangda/releases/<change>/staged-resources.json`,最后只由一次 Root App finalize 原子激活。不要使用 `workspace publish --form`、单独 `runtime activate`、`pnpm publish:all`、`pnpm openxiangda:publish` 或 `lowcode-workspace publish-all`。
55
55
 
56
- 工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish` 即不再是入口。必须冻结不可变 candidate,部署到 preproduction,提交与 deployment/candidate/appType/AppRelease 精确绑定且仍在有效期内的通过证据,再将同一 candidate 晋级 production。两套环境的 appType、资源 ID、数据和副作用策略完全独立;旧单目标资源映射只允许迁移到 appType 相同的预发 target,正式 target 必须为空。`openxiangda studio` 提供仅本机访问的开发者页面,用于查看绑定、漂移、候选、部署、证据和下一安全动作。
56
+ 工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish` 即不再是入口。必须冻结不可变 candidate,部署到 preproduction,提交与 deployment/candidate/appType/AppRelease 精确绑定且仍在有效期内的通过证据,再将同一 candidate 晋级 production。两套环境的 appType、资源 ID、数据和副作用策略完全独立;旧单目标资源映射只允许迁移到 appType 相同的预发 target,正式 target 必须为空。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。`openxiangda studio` 提供仅本机访问的开发者页面,用于查看绑定、漂移、候选、部署、证据和下一安全动作。
57
57
 
58
58
  `resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
59
59
 
@@ -31,7 +31,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
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
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. Whole-manifest replacement requires exact `--only/--code` plus `--replace-manifest --reason "..."`.
33
33
  - 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
- - 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`.
34
+ - 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
35
  - Routine edits should plan and publish exact change targets: `workspace plan --profile <name> --change <change> --changed`, then `workspace publish --profile <name> --change <change> --only pages/a,forms/b --dry-run`.
36
36
  - Account/role/permission/RBAC/organization-account/query-param authorization work must choose a mode first: `managed-platform-account`, `existing-platform-user-assignment`, `static-role-permission`, or `query-param-context`, then write the permission matrix.
37
37
  - Roles that create roles, assign members, grant API permissions, maintain permission groups, or manage organization accounts must declare `apiPermissionCodes` in `src/resources/roles/<code>.json`, such as `app:role:manage`, `app:page-permission-group:manage`, `app:form-permission-group:manage`, and `app:organization:manage`.
@@ -31,7 +31,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
31
31
  - SDD 默认 streamlined:approval 与结构化精确范围是硬门禁,未完成的 task/evidence/spec 文案只告警;只有 `strictDocumentation: true` 才阻断。
32
32
  - Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state;整包 manifest 替换必须精确 `--only/--code` 并加 `--replace-manifest --reason "..."`。
33
33
  - 写入平台前只从与权威远端 tip 完全一致的 clean main/master 执行 `release begin`。feature branch 或未 push 主线在任何写入前失败;激活后直接运行 `integration-status` 和 `release end`,无需发布后再合并。
34
- - 环境托管工作区禁止直发:已有工作区先用 `environment attach` 接入,旧映射只能迁移到 appType 相同的预发 target,正式 target 必须为空。然后执行 `release candidate`、`release deploy --environment preproduction`、`release test`,最后将同一 candidate 用 `release promote --environment production --confirm-production` 晋级。两套环境的 appType、资源 ID 和数据不可互拷;用 `openxiangda studio` 查看状态。
34
+ - 环境托管工作区禁止直发:已有工作区先用 `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
35
  - 单文件改动默认按 change 和逻辑目标发布:先 `workspace plan --profile <name> --change <change> --changed`,再 `workspace publish --profile <name> --change <change> --only pages/a,forms/b --dry-run`。
36
36
  - 账号/角色/权限/RBAC/组织账号/查询参数授权需求先选权限模式:`managed-platform-account` / `existing-platform-user-assignment` / `static-role-permission` / `query-param-context`,并输出权限矩阵。
37
37
  - 角色能新增角色、分配成员、授接口权限、维护权限组或管理组织账号时,`src/resources/roles/<code>.json` 必须声明 `apiPermissionCodes`,例如 `app:role:manage`、`app:page-permission-group:manage`、`app:form-permission-group:manage`、`app:organization:manage`。
@@ -57,7 +57,7 @@
57
57
  - ✅ `resource plan` 与 publish dry-run 严格只允许 GET/HEAD;遇到 `READ_ONLY_AUTH_REQUIRED` 时先执行 `openxiangda auth refresh --profile <name>` 或重新登录,不得在 plan 内自动 POST 刷新 token。
58
58
  - ✅ Function/Automation 使用 Backend Release v2;正式多资源发布用精确 `--only/--code` 加 `--stage-only` 暂存,同一 child 可混合 create、source-only update 和显式 manifest replacement,再由 Root App finalize 原子激活。整包 manifest 替换必须另加 `--replace-manifest --reason "..."`。
59
59
  - ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
60
- - ✅ 已通过 `environment init` 登记或 `environment attach` 接入的工作区必须走 `release candidate → release deploy --environment preproduction → release test → release promote --environment production --confirm-production`。候选不可变,正式晋级必须复用预发通过的同一候选;preproduction / production 的 appType、资源 ID、数据和副作用策略完全隔离,严禁跨环境复制 ID 或直接 `release publish`。旧单目标映射只允许按相同 appType 迁移到预发。
60
+ - ✅ 已通过 `environment init` 登记或 `environment attach` 接入的工作区必须走 `release candidate → release deploy --environment preproduction → release test → release promote --environment production --confirm-production`。候选不可变,正式晋级必须复用预发通过的同一候选;preproduction / production 的 appType、资源 ID、数据和副作用策略完全隔离,严禁跨环境复制 ID 或直接 `release publish`。旧单目标映射只允许按相同 appType 迁移到预发。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。
61
61
  - ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
62
62
 
63
63
  ## 严禁