openxiangda 1.0.202 → 1.0.204

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
@@ -66,6 +66,11 @@ openxiangda studio
66
66
  # 环境托管应用日常发布:首次命令只部署预发并停止
67
67
  openxiangda release ship --change mainline-release --profile dev
68
68
 
69
+ # 仅限已审计的多历史 lineage、精确非删除恢复;仍保留冻结 Head、CAS、租约和原子 finalize
70
+ openxiangda release ship --change mainline-release --profile dev \
71
+ --adopt-online-baseline \
72
+ --adoption-reason "已合入主线的目标来自多次历史发布,无法对应单一 Git 基线"
73
+
69
74
  # 确认预发结果后,再以同一 candidate 晋级正式;验收备注可选
70
75
  openxiangda release ship --change mainline-release --profile dev \
71
76
  --confirm-production
@@ -77,7 +82,7 @@ openxiangda app snapshot APP_XXXX --profile dev --json
77
82
 
78
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.
79
84
 
80
- 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. 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. 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.
81
86
 
82
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.
83
88
 
@@ -532,13 +532,12 @@ function withManagedReleaseForwardedFlags(stepId, args = [], flags = {}) {
532
532
  ) {
533
533
  forwarded.push('--include-noop');
534
534
  }
535
- const baselineAdoptionStages = new Set([
536
- 'form-stage',
537
- 'backend-stage',
538
- 'workflow-stage',
539
- ]);
535
+ const isExactResourcePublish =
536
+ forwarded[0] === 'resource' &&
537
+ forwarded[1] === 'publish' &&
538
+ (forwarded.includes('--only') || forwarded.includes('--code'));
540
539
  if (
541
- !baselineAdoptionStages.has(stepId) ||
540
+ !isExactResourcePublish ||
542
541
  !flags['adopt-online-baseline']
543
542
  ) {
544
543
  return forwarded;
@@ -976,6 +976,10 @@ function buildIsolatedEnvironment(
976
976
  }
977
977
  return {
978
978
  ...isolated,
979
+ // Git-base builds are always non-interactive. Force CI even when the
980
+ // parent shell explicitly carries CI=false so pnpm never prompts to purge
981
+ // a linked node_modules directory and then aborts because no TTY exists.
982
+ CI: 'true',
979
983
  INIT_CWD: buildRoot,
980
984
  LOWCODE_WORKSPACE_ROOT: buildRoot,
981
985
  OPENXIANGDA_WORKSPACE_ROOT: buildRoot,
package/lib/cli.js CHANGED
@@ -68,6 +68,7 @@ const {
68
68
  buildWorkspaceReleaseCommands,
69
69
  buildWorkspaceReleaseSteps,
70
70
  commandFromArgs,
71
+ directResourceTypeForKey,
71
72
  normalizeReleaseTargets,
72
73
  releasePlanHash,
73
74
  } = require('./release-plan');
@@ -302,7 +303,7 @@ Usage:
302
303
  openxiangda sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive [change] [--change id] [--json]
303
304
  openxiangda environment init|attach|bind|swap|policy update|list|status|diff|use [preproduction|production] [--logical-app code] [--environment target] [--json]
304
305
  openxiangda release publish|begin|status|integration-status|renew|end [--change id] [--profile name] [--watch] [--json]
305
- openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--json]
306
+ openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--adopt-online-baseline --adoption-reason text] [--json]
306
307
  openxiangda task status --change <id> [--profile name] [--watch] [--json]
307
308
  openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]
308
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]
@@ -3086,6 +3087,8 @@ const RELEASE_SHIP_SUPPORTED_FLAGS = new Set([
3086
3087
  'app-type',
3087
3088
  'since',
3088
3089
  'expand-dependencies',
3090
+ 'adopt-online-baseline',
3091
+ 'adoption-reason',
3089
3092
  'confirm-production',
3090
3093
  'acceptance-note',
3091
3094
  'evidence-json',
@@ -3106,6 +3109,22 @@ function assertReleaseShipFlags(flags = {}) {
3106
3109
  error.unsupportedFlags = unsupported;
3107
3110
  throw error;
3108
3111
  }
3112
+ const adoptionRequested = Boolean(flags['adopt-online-baseline']);
3113
+ const adoptionReason = String(flags['adoption-reason'] || '').trim();
3114
+ if (!adoptionRequested && adoptionReason) {
3115
+ const error = new Error(
3116
+ 'RELEASE_SHIP_ADOPTION_FLAG_REQUIRED: --adoption-reason 必须与 --adopt-online-baseline 一起使用;命令已在任何 candidate/deployment 写入前终止'
3117
+ );
3118
+ error.code = 'RELEASE_SHIP_ADOPTION_FLAG_REQUIRED';
3119
+ throw error;
3120
+ }
3121
+ if (adoptionRequested && adoptionReason.length < 8) {
3122
+ const error = new Error(
3123
+ 'RELEASE_SHIP_ADOPTION_REASON_REQUIRED: --adopt-online-baseline 必须提供至少 8 个字符的 --adoption-reason;命令已在任何 candidate/deployment 写入前终止'
3124
+ );
3125
+ error.code = 'RELEASE_SHIP_ADOPTION_REASON_REQUIRED';
3126
+ throw error;
3127
+ }
3109
3128
  }
3110
3129
 
3111
3130
  async function runApplicationShipCommand(
@@ -3119,7 +3138,7 @@ async function runApplicationShipCommand(
3119
3138
  readStringFlag(flags, 'change') || positional[0];
3120
3139
  if (!changeId) {
3121
3140
  fail(
3122
- '用法: openxiangda release ship --change <id> [--confirm-production] [--acceptance-note "..."]'
3141
+ '用法: openxiangda release ship --change <id> [--adopt-online-baseline --adoption-reason "..."] [--confirm-production] [--acceptance-note "..."]'
3123
3142
  );
3124
3143
  }
3125
3144
  const logicalApp = requireLogicalApplication(initialTarget);
@@ -3550,11 +3569,12 @@ async function release(args) {
3550
3569
  if (wantsSubcommandHelp(subcommand, flags)) {
3551
3570
  print([
3552
3571
  '用法: openxiangda release publish|begin|status|explain|integration-status|renew|end [--change id] [--profile name] [--json]',
3553
- ' openxiangda release ship|candidate|deploy|reconcile|test|promote|rollback [--candidate id] [--environment target] [--confirm-production] [--json]',
3572
+ ' openxiangda release ship|candidate|deploy|reconcile|test|promote|rollback [--candidate id] [--environment target] [--adopt-online-baseline --adoption-reason text] [--confirm-production] [--json]',
3554
3573
  ' openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]',
3555
3574
  ' 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]',
3556
3575
  '常用流程:',
3557
3576
  ' openxiangda release ship --change <id> --profile <name>',
3577
+ ' openxiangda release ship --change <id> --profile <name> --adopt-online-baseline --adoption-reason "已审计目标来自多次历史发布"',
3558
3578
  ' # 确认预发结果后;可选 --acceptance-note 留下人工验收记录:',
3559
3579
  ' openxiangda release ship --change <id> --profile <name> --confirm-production',
3560
3580
  ' openxiangda release publish --change <id> --profile <name>',
@@ -3569,6 +3589,7 @@ async function release(args) {
3569
3589
  ' openxiangda release app-finalize --change <id> --staged-resources-json <JSON|file> --profile <name>',
3570
3590
  '说明:',
3571
3591
  ' - ship 是环境托管应用的两段式快车道:首次只部署预发并停止;确认预发结果后,第二次以同一 candidate 晋级正式。人工验收备注可选。',
3592
+ ' - 已合入主线但线上来自多次历史 lineage 的精确非删除恢复可显式使用 --adopt-online-baseline 与 --adoption-reason;仍保留冻结 Head、CAS、租约、staged children 和单次 App finalize。',
3572
3593
  ' - ship 当前不支持 --dry-run;任何未声明参数都会在 candidate/deployment 写入前 fail-closed。需要只读检查时使用 environment status/diff 与 release status。',
3573
3594
  ' - publish 默认等待租约并按私有执行日志恢复;上次写结果不确定时必须只读核对后显式 --resume-after-review。',
3574
3595
  ' - app-head 默认只输出紧凑 head 摘要;需要完整 manifest 时显式追加 --full。',
@@ -12901,8 +12922,21 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
12901
12922
  'workflows',
12902
12923
  'formSettings',
12903
12924
  ]);
12925
+ const resourceSelectors = Object.fromEntries(
12926
+ RESOURCE_SPECS
12927
+ .filter(
12928
+ spec =>
12929
+ !codeResourceKeys.has(spec.key) &&
12930
+ directResourceTypeForKey(spec.key)
12931
+ )
12932
+ .map(spec => [spec.key, codes(spec.key)])
12933
+ .filter(([, selectedCodes]) => selectedCodes.length > 0)
12934
+ );
12904
12935
  const hasGenericResources = RESOURCE_SPECS.some(
12905
- spec => !codeResourceKeys.has(spec.key) && (manifest[spec.key] || []).length > 0
12936
+ spec =>
12937
+ !codeResourceKeys.has(spec.key) &&
12938
+ !directResourceTypeForKey(spec.key) &&
12939
+ (manifest[spec.key] || []).length > 0
12906
12940
  ) || (planActions || []).some(
12907
12941
  action =>
12908
12942
  action?.action === 'delete' &&
@@ -12919,6 +12953,7 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
12919
12953
  workflows: unique([...codes('workflows'), ...deletedCodes('workflow')]).sort(),
12920
12954
  jsCodeNodes: [],
12921
12955
  resources: hasGenericResources,
12956
+ resourceSelectors,
12922
12957
  runtime: false,
12923
12958
  other: [],
12924
12959
  };
@@ -26887,5 +26922,6 @@ function buildWorkspacePublishEnv(
26887
26922
  }
26888
26923
 
26889
26924
  module.exports = {
26925
+ buildResourceManifestSddTargets,
26890
26926
  main,
26891
26927
  };
@@ -21,6 +21,14 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
21
21
  .map(([type, values]) => [type, uniqueSorted(values || [])])
22
22
  .filter(([, values]) => values.length > 0)
23
23
  );
24
+ const hasExactDirectResourceScope =
25
+ Object.entries(resourceSelectors).length > 0 &&
26
+ Object.entries(resourceSelectors).every(
27
+ ([type, selectors]) =>
28
+ directResourceTypeForKey(type) &&
29
+ type !== 'unknown' &&
30
+ (selectors || []).every(selector => selector !== '*')
31
+ );
24
32
  const logicalTargets = {
25
33
  forms: uniqueSorted(targets.forms || []),
26
34
  pages: uniqueSorted(targets.pages || []),
@@ -28,7 +36,11 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
28
36
  automations: uniqueSorted(targets.automations || []),
29
37
  workflows: uniqueSorted(targets.workflows || []),
30
38
  jsCodeNodes: uniqueSorted(targets.jsCodeNodes || []),
31
- resources: Boolean(targets.resources),
39
+ // Older SDD records used resources=true as a category marker even when
40
+ // they also carried an exact selector. Exact supported selectors are the
41
+ // authoritative boundary; only an unscoped/unknown/wildcard declaration
42
+ // remains a generic resource closure.
43
+ resources: Boolean(targets.resources) && !hasExactDirectResourceScope,
32
44
  resourceSelectors,
33
45
  resourceDeletes,
34
46
  runtime: Boolean(targets.runtime),
@@ -56,7 +68,9 @@ function unsupportedAtomicReleaseTargets(targets = {}) {
56
68
  )) {
57
69
  if (
58
70
  (selectors || []).length > 0 &&
59
- (!DIRECT_RESOURCE_TYPE_BY_KEY[type] || type === 'unknown')
71
+ (!directResourceTypeForKey(type) ||
72
+ type === 'unknown' ||
73
+ selectors.includes('*'))
60
74
  ) {
61
75
  unsupported.push(`${type}:${selectors.join(',')}`);
62
76
  }
@@ -114,6 +128,10 @@ const DIRECT_RESOURCE_RELEASE_ORDER = Object.freeze([
114
128
  'notifications',
115
129
  ]);
116
130
 
131
+ function directResourceTypeForKey(key) {
132
+ return DIRECT_RESOURCE_TYPE_BY_KEY[key] || null;
133
+ }
134
+
117
135
  function assertAtomicReleaseTargetsSupported(targets) {
118
136
  const unsupported = unsupportedAtomicReleaseTargets(targets);
119
137
  if (unsupported.length === 0) return;
@@ -238,7 +256,7 @@ function buildDirectConfigurationReleaseSteps(
238
256
  return Object.entries(targets.resourceSelectors || {})
239
257
  .filter(
240
258
  ([type, selectors]) =>
241
- DIRECT_RESOURCE_TYPE_BY_KEY[type] &&
259
+ directResourceTypeForKey(type) &&
242
260
  uniqueSorted(selectors || []).length > 0
243
261
  )
244
262
  .sort(
@@ -248,12 +266,12 @@ function buildDirectConfigurationReleaseSteps(
248
266
  )
249
267
  .map(([type, selectors]) =>
250
268
  createStep(
251
- `config-${DIRECT_RESOURCE_TYPE_BY_KEY[type]}`,
269
+ `config-${directResourceTypeForKey(type)}`,
252
270
  appendProfileAndChange(
253
271
  [
254
272
  'resource',
255
273
  'publish',
256
- DIRECT_RESOURCE_TYPE_BY_KEY[type],
274
+ directResourceTypeForKey(type),
257
275
  '--only',
258
276
  uniqueSorted(selectors).join(','),
259
277
  ],
@@ -476,6 +494,7 @@ module.exports = {
476
494
  buildWorkspaceReleaseCommands,
477
495
  buildWorkspaceReleaseSteps,
478
496
  commandFromArgs,
497
+ directResourceTypeForKey,
479
498
  normalizeReleaseTargets,
480
499
  releasePlanHash,
481
500
  unsupportedAtomicReleaseTargets,
@@ -106,12 +106,16 @@ openxiangda release ship --change <release-change> --profile <name> \
106
106
 
107
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. Use the two-phase `release ship` fast path by default. Its first invocation creates/deploys the immutable candidate to preproduction and always returns `awaiting_production_confirmation`; it cannot promote. A second invocation with `--confirm-production` promotes the exact same candidate hash. Real human acceptance is the recommended normal practice and can be recorded with optional `--acceptance-note`, but do not invent an acceptance note and do not treat it as a universal hard platform gate when the user explicitly authorizes a low-risk or emergency promotion. The lower-level candidate/deploy/test/promote commands are recovery primitives, not the normal agent loop. 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. To change only one environment's side-effect policy, run `environment policy update <kind|id> --side-effect-policy-json <JSON|file> --reason "..." --dry-run` first, then repeat without `--dry-run`; never use swap for this. Policy reads are forward-compatible: patch mode validates only caller-supplied fields and preserves unrecognized historical fields, while `--full-replace` validates the supplied complete target and intentionally removes omissions. The update is revision-CAS, production needs `--confirm-production`, and `organizationWrites=explicit_capability_only` still requires `app:organization:manage`. Keep release hard gates limited 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 was intentionally configured. Use `openxiangda studio` for the loopback-only developer view of bindings, drift, evidence, and safe next actions.
108
108
 
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
+
109
111
  `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
112
 
111
113
  `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.
112
114
 
113
115
  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 are logical coverage targets and activate through one Runtime child; they do not require PageRelease. `release app-head` and `runtime releases` are compact by default; use `--full` only when the complete manifest is required.
114
116
 
117
+ Exact `resourceSelectors` are authoritative for supported configuration resources such as `publicAccessPolicies`. A legacy `resources=true` category marker is narrowed by those exact selectors and must not become an app-wide generic-resource release. Missing selectors, unknown resource types, wildcard `*`, deletes, and genuine app-wide resource closures remain fail closed.
118
+
115
119
  `release begin --change` freezes a clean committed `HEAD` only when the current branch is the authoritative default `main`/`master` and its commit exactly equals the live remote tip. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A feature worktree or unpushed main receives `RELEASE_SOURCE_MAINLINE_REQUIRED` / `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`; merge, test, push, and start the one mainline release instead of forcing it. Optional `.git` remote suffix differences are aliases of the same repository; genuinely different remotes still fail closed.
116
120
 
117
121
  Because promotion begins from the already-pushed authoritative mainline, `release integration-status` should pass immediately after activation. Run it, then `release end`; no post-release branch merge is required.
@@ -107,10 +107,14 @@ openxiangda release ship --change <release-change> --profile <name> \
107
107
 
108
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 normal path is the two-phase `release ship`. Its first invocation creates and deploys the immutable candidate only to preproduction and stops at `awaiting_production_confirmation`. A different, later invocation with `--confirm-production` promotes the exact same candidate and fresh deployment-bound evidence. Human acceptance is recommended and an optional `--acceptance-note` records it, but it is not a universal hard platform gate for explicitly authorized low-risk or emergency releases. 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. A single environment policy change uses `environment policy update <kind|id>` with a GET-only `--dry-run` first; patch mode validates only caller-supplied fields and preserves unrecognized historical fields, while `--full-replace` validates the supplied complete target and intentionally removes omissions. It uses revision CAS and never swaps roles or Release Heads. Production writes require `--confirm-production`. `organizationWrites=explicit_capability_only` only removes the environment deny and never bypasses `app:organization:manage`. Keep release hard gates limited 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 was intentionally configured. `openxiangda studio` is the local loopback-only developer view and exposes only registered safe actions.
109
109
 
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
+
110
112
  `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.
111
113
 
112
114
  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.
113
115
 
116
+ Exact `resourceSelectors` are authoritative for supported configuration resources such as `publicAccessPolicies`. A legacy `resources=true` category marker is narrowed by those exact selectors and must not become an app-wide generic-resource release. Missing selectors, unknown resource types, wildcard `*`, deletes, and genuine app-wide resource closures remain fail closed.
117
+
114
118
  `runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. It reads a narrow Runtime head instead of the full app snapshot. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. Immutable Git-base artifact hashes are cached under the Git common directory. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status` is already satisfied after activation and `release end` does not wait for a later merge.
115
119
 
116
120
  `release app-head` and `runtime releases` return compact summaries by default. Use `--full` only when the complete manifest is required.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.202",
3
+ "version": "1.0.204",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -55,6 +55,10 @@ openxiangda commands --json
55
55
 
56
56
  工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish` 即不再是入口。日常使用两段式 `release ship`:第一次冻结不可变 candidate 并只部署到 preproduction,停止在 `awaiting_production_confirmation`;确认预发结果后,第二次命令提供 `--confirm-production`,把同一 candidate 晋级 production,不会重新构建。真实人工验收是默认建议,可用可选的 `--acceptance-note` 留痕;但用户明确授权的低风险或紧急发布不受僵硬审批门禁阻塞。两套环境的 appType、资源 ID、数据和副作用策略完全独立;旧单目标资源映射只允许迁移到 appType 相同的预发 target,正式 target 必须为空。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。单独调整某个环境的副作用策略时,先运行 `environment policy update <kind|id> ... --dry-run` 查看差异,再按 CAS revision 写入;patch 只校验本次提交字段并原样保留未知历史字段,`--full-replace` 才按完整目标删除遗漏字段。不得借用 swap,正式写入另需 `--confirm-production`。`organizationWrites=explicit_capability_only` 仍强制 `app:organization:manage`。发布硬门禁只保留明确 scope/profile/target、权限、干净且已推送主线、不可变版本、CAS/租约与生产确认;文案和人工验收说明默认是建议,只有显式 strict 模式才阻断。`openxiangda studio` 提供仅本机访问的开发者页面,用于查看绑定、漂移、候选、部署、证据和下一安全动作。
57
57
 
58
+ 只有已审计代码早已进入权威主线、而精确非删除目标在线上来自多次历史发布且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`。该参数不会放松冻结 online heads、change/lease、删除/全量拒绝、服务端 CAS、staged children 或单次 App finalize 原子激活。
59
+
60
+ 受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为发布边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍必须 fail closed。
61
+
58
62
  `resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
59
63
 
60
64
  完整发布顺序:
@@ -58,6 +58,8 @@
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
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
+ - ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
62
+ - ✅ 受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍 fail closed。
61
63
  - ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
62
64
 
63
65
  ## 严禁