openxiangda 1.0.253 → 1.0.254
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 -0
- package/lib/application-environments.js +26 -12
- package/openxiangda-skills/SKILL.md +2 -0
- package/openxiangda-skills/references/workspace-state.md +1 -0
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +2 -0
- package/package.json +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +2 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +1 -0
package/README.md
CHANGED
|
@@ -128,6 +128,8 @@ User tokens are stored in `~/.openxiangda/profiles.json` with `0600` permissions
|
|
|
128
128
|
|
|
129
129
|
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` always executes the same ordered candidate → preproduction → production protocol. The normal first invocation seals the candidate, deploys only to preproduction, and stops at `awaiting_production_confirmation`; a later invocation with `--confirm-production` promotes it. For an explicitly authorized emergency, supplying `--confirm-production` on the first invocation runs both phases in one command without bypassing preproduction, CAS, evidence, or production confirmation. Candidate sealing covers source, `public/`, build controls/scripts, stable environment/resource bindings, and target-specific hashed Runtime artifacts. Both deployments upload those artifacts with `--no-build`; each deployment is completed with evidence and reaches terminal `succeeded`, so it cannot leave the target slot blocked. Unrelated commits may land on authoritative mainline between phases only while the sealed commit remains an ancestor and every sealed input/binding/artifact still validates. Real human acceptance remains the recommended default and `--acceptance-note` records it. For audited historical-lineage adoption or reviewed Backend manifest replacement, the existing paired flags and exact-scope gates remain mandatory. Supported configuration resources use exact `resourceSelectors`; unknown, wildcard, destructive, and genuinely unscoped generic resources remain blocked. Lower-level candidate/deploy/test/fail/promote commands are recovery primitives. `release fail` requires an explicit preproduction target, deployment ID, and audit message; it verifies the deployment belongs to that preproduction environment before writing optional code/details to the platform failure audit. Direct `release publish` is retained only for legacy unmanaged workspaces.
|
|
130
130
|
|
|
131
|
+
DataView `status` is a last-observed lifecycle value, not a stable candidate binding: the same managed deployment legitimately moves it between `draft` and `active`. Candidate creation and validation therefore omit only `resources.dataViews.<code>.status`, including when resuming a candidate sealed by an older CLI. `dataViewId`, `materializedViewName`, `storageMode`, candidate hashes, environment identity, CAS, lease, source and every non-DataView status remain fail-closed.
|
|
132
|
+
|
|
131
133
|
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.
|
|
132
134
|
|
|
133
135
|
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.
|
|
@@ -33,6 +33,15 @@ const CANDIDATE_RESOURCE_OBSERVATION_FIELDS = new Set([
|
|
|
33
33
|
'bundlePublishedAt',
|
|
34
34
|
]);
|
|
35
35
|
|
|
36
|
+
function isCandidateResourceObservationField(pathParts, key) {
|
|
37
|
+
if (CANDIDATE_RESOURCE_OBSERVATION_FIELDS.has(key)) return true;
|
|
38
|
+
return (
|
|
39
|
+
key === 'status' &&
|
|
40
|
+
pathParts.length === 2 &&
|
|
41
|
+
pathParts[0] === 'dataViews'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
function canonicalJson(value) {
|
|
37
46
|
if (Array.isArray(value)) {
|
|
38
47
|
return `[${value.map(item => canonicalJson(item)).join(',')}]`;
|
|
@@ -707,18 +716,22 @@ function normalizeCandidateProjectStateForHash(input, hashPolicy) {
|
|
|
707
716
|
return state;
|
|
708
717
|
}
|
|
709
718
|
|
|
710
|
-
function normalizeCandidateResourceBindings(value) {
|
|
719
|
+
function normalizeCandidateResourceBindings(value, pathParts = []) {
|
|
711
720
|
if (Array.isArray(value)) {
|
|
712
|
-
return value.map(item =>
|
|
721
|
+
return value.map((item, index) =>
|
|
722
|
+
normalizeCandidateResourceBindings(item, [...pathParts, String(index)])
|
|
723
|
+
);
|
|
713
724
|
}
|
|
714
725
|
if (!value || typeof value !== 'object') return value;
|
|
715
726
|
return Object.fromEntries(
|
|
716
727
|
Object.entries(value)
|
|
717
|
-
.filter(
|
|
728
|
+
.filter(
|
|
729
|
+
([key]) => !isCandidateResourceObservationField(pathParts, key)
|
|
730
|
+
)
|
|
718
731
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
719
732
|
.map(([key, item]) => [
|
|
720
733
|
key,
|
|
721
|
-
normalizeCandidateResourceBindings(item),
|
|
734
|
+
normalizeCandidateResourceBindings(item, [...pathParts, key]),
|
|
722
735
|
])
|
|
723
736
|
);
|
|
724
737
|
}
|
|
@@ -819,18 +832,19 @@ function inspectCandidateEnvironmentBindings(candidate, state) {
|
|
|
819
832
|
});
|
|
820
833
|
}
|
|
821
834
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
)
|
|
835
|
+
const expectedResources = normalizeCandidateResourceBindings(
|
|
836
|
+
expectedBinding?.resources || {}
|
|
837
|
+
);
|
|
838
|
+
const currentResources = normalizeCandidateResourceBindings(
|
|
839
|
+
currentBinding.resources || {}
|
|
840
|
+
);
|
|
841
|
+
if (!candidateBindingValueContains(expectedResources, currentResources)) {
|
|
828
842
|
mismatches.push({
|
|
829
843
|
targetName,
|
|
830
844
|
field: 'resources',
|
|
831
845
|
reason: 'sealed-resource-binding-changed',
|
|
832
|
-
expected:
|
|
833
|
-
current:
|
|
846
|
+
expected: expectedResources,
|
|
847
|
+
current: currentResources,
|
|
834
848
|
});
|
|
835
849
|
}
|
|
836
850
|
}
|
|
@@ -141,6 +141,8 @@ openxiangda release ship --change <release-change> --profile <name> \
|
|
|
141
141
|
|
|
142
142
|
For a workspace registered by `environment init` or connected by `environment attach`, production is never a direct publish target. `release ship` always executes candidate → preproduction → production. The normal first invocation stops after preproduction; a later `--confirm-production` promotes the same candidate. If the user explicitly authorizes an emergency, putting `--confirm-production` on the first invocation runs both phases in one command without skipping preproduction, evidence, CAS, or confirmation. The sealed candidate contains source/build/public/script inputs, stable environment/resource bindings, and hashed target-specific Runtime artifacts; deployment uploads those exact artifacts with no rebuild and closes each server deployment as `succeeded`. Human acceptance remains recommended and may be recorded with `--acceptance-note`. Lower-level candidate/deploy/test/fail/promote commands are recovery primitives. `release fail` is preproduction-only: pass an explicit deployment and audit message, and the CLI verifies the deployment belongs to the selected preproduction environment before it writes optional code/details. Keep preproduction and production identities isolated, and retain the existing authorization/CAS rules for environment swap and policy changes. Use `openxiangda studio` for bindings, drift, evidence, and safe next actions.
|
|
143
143
|
|
|
144
|
+
Candidate binding checks treat only `resources.dataViews.<code>.status` as a mutable platform lifecycle observation, so a candidate survives its own `active → draft → active` staging sequence and older sealed candidates remain resumable. DataView IDs, materialized-view names, storage modes, other resource statuses, candidate hashes, environment identity, CAS, lease, and source/mainline checks remain exact.
|
|
145
|
+
|
|
144
146
|
A sealed candidate may be promoted from a later clean, pushed authoritative mainline commit only when the candidate commit remains its Git ancestor and every sealed candidate input file still has the exact recorded hash. This permits unrelated parallel merges without allowing stale candidate inputs to overwrite newer work. Do not edit private candidate metadata to bypass `CANDIDATE_INPUTS_CHANGED`. The CLI waits for both the app lease and target deployment slot; each target permits only one running or evidence-pending deployment. Emergency fixes stay on the same candidate → preproduction → production path with a narrow L1 scope. `--wait-seconds 0` is fail-fast, not a binding-contract, CAS, or production-confirmation bypass.
|
|
145
147
|
|
|
146
148
|
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 and freezes the pair before candidate/deployment creation; the later `--confirm-production` invocation automatically reuses the same intent from the private ship journal 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.
|
|
@@ -102,6 +102,7 @@ Environment-managed workspaces add a logical application and target-specific bin
|
|
|
102
102
|
- Local resource keys are logical codes.
|
|
103
103
|
- Live IDs and lightweight runtime aliases are nested under the profile that produced them.
|
|
104
104
|
- `resources.dataViews` is keyed by data view `code` and stores only profile-local platform metadata such as `dataViewId`, `materializedViewName`, and last known `status`.
|
|
105
|
+
- The stored DataView `status` is observational: managed staging legitimately moves it between `draft` and `active`. Candidate binding validation ignores only that field, including for older sealed candidates, while keeping `dataViewId`, `materializedViewName`, `storageMode`, and all non-DataView status fields strict.
|
|
105
106
|
- Data view definitions, refresh config, permissions, and source `formCode` references belong in `src/resources/data-views/*.json`, not in state.
|
|
106
107
|
- Do not store business configuration or secrets in `.openxiangda/state.json`; store those in `src/resources/`.
|
|
107
108
|
- CLI writes use a lock, a three-way merge at profile/resource-key granularity, and fsync + atomic rename. Concurrent writes to different profiles or logical resource keys are preserved; competing writes to the same field fail with `OPENXIANGDA_STATE_CONFLICT` and must be retried from a freshly loaded state.
|
|
@@ -144,6 +144,8 @@ openxiangda release ship --change <release-change> --profile <name> \
|
|
|
144
144
|
|
|
145
145
|
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 appType-matching target. `release ship` always runs candidate → preproduction → production. Normally the first invocation stops at `awaiting_production_confirmation` and a later `--confirm-production` invocation promotes the same candidate. When the user explicitly authorizes an emergency release, `--confirm-production` may be supplied on the first invocation to execute both phases in one command; it bypasses no preproduction, evidence, CAS, or confirmation gate. Candidate sealing includes source/build/public/script inputs, stable environment/resource bindings, and hashed target-specific Runtime artifacts. Deployment reuses those artifacts without rebuilding and completes both server deployment records to `succeeded`. Human acceptance remains recommended and an optional `--acceptance-note` records it. Direct `release publish` to either managed target fails closed. Environment swap and policy updates retain their existing explicit authorization, CAS, and permission rules. `openxiangda studio` is the local loopback-only developer view and exposes only registered safe actions.
|
|
146
146
|
|
|
147
|
+
Treat DataView `status` as a platform lifecycle observation during candidate binding checks. The same deployment may change `resources.dataViews.<code>.status` between `draft` and `active`, including between preproduction and production confirmation, without invalidating the sealed candidate. Continue to validate the DataView ID, materialized-view name, storage mode, every other resource status, hashes, environment identity, CAS, lease, and source/mainline evidence exactly.
|
|
148
|
+
|
|
147
149
|
A sealed candidate may continue from a later clean, pushed authoritative mainline commit only when its commit remains a Git ancestor and all sealed release inputs retain their exact hashes. If any input changed, create a new candidate; never edit the private candidate file. The CLI waits for the app lease and target deployment slot before writes, and the platform permits one running or evidence-pending deployment per target. Emergency releases still use a narrow L1 scope and the same candidate → preproduction → production path; `--wait-seconds 0` only fails fast.
|
|
148
150
|
|
|
149
151
|
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 and freezes the pair before candidate/deployment creation. The later `--confirm-production` invocation automatically reuses the same intent from `ship.json` 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.
|
package/package.json
CHANGED
|
@@ -60,6 +60,8 @@ openxiangda commands --json
|
|
|
60
60
|
|
|
61
61
|
工作区一旦通过 `environment init` 登记或 `environment attach` 接入,`release publish` 即不再是入口。`release ship` 始终按 candidate → preproduction → production 执行:日常首条命令在预发停止,后续 `--confirm-production` 晋级;用户明确授权紧急发布时,首条命令可直接携带 `--confirm-production`,在一个命令内顺序执行两阶段,但不跳过预发、证据、CAS 或确认。candidate 封存源码、public、构建配置/脚本、稳定环境/资源绑定和两目标 Runtime 哈希产物;部署不再现场构建,并将两条服务端 deployment 都闭环为 `succeeded`。两套环境身份仍完全隔离,人工验收默认建议且可用 `--acceptance-note` 留痕;swap、policy 和权限规则保持不变。`openxiangda studio` 用于查看绑定、漂移、候选、部署和证据。
|
|
62
62
|
|
|
63
|
+
DataView 的 `status` 是平台生命周期观察值;同一托管部署在预发暂存和正式确认之间发生 `active → draft → active` 不构成候选漂移。候选仍严格校验 `dataViewId`、`materializedViewName`、`storageMode`、其他资源状态、哈希、环境身份、CAS、租约和来源主线。
|
|
64
|
+
|
|
63
65
|
预发 UAT 未通过时,使用 `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction --profile <name>` 留下平台审计记录。CLI 会先核对 deployment 属于所选预发环境;禁止对 production target 或不匹配的 deployment 使用。
|
|
64
66
|
|
|
65
67
|
托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`。CLI 会从私有 `ship.json` 恢复血缘,必要时自动沿 production/preproduction deployment ID 查找对应 `execution.json`;失败信息必须指出实际缺失的日志或字段。
|
|
@@ -65,6 +65,7 @@ Delivery V2 自动从期望状态按资源指纹计算精确范围,使用 CLI
|
|
|
65
65
|
- ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
|
|
66
66
|
- ✅ 旧工作区已有 Root、且操作者明确授权无条件恢复时,可执行 `release app-activate <releaseId> --force-activate-without-validation --profile <name>`。该命令不读取 detail/capture,不要求 change、租约、baseline、源码 lineage、状态、parent、hash、resource head 或环境发布门禁;服务端直接在目标 tenant/appType 内以单事务切换 Root 与可识别的 staged children。
|
|
67
67
|
- ✅ 已通过 `environment init` 或 `environment attach` 接入的工作区使用 `release ship`,始终按 candidate → preproduction → production 执行。日常首条命令在预发停止,后续 `--confirm-production` 晋级;用户明确授权紧急发布时,首条命令可携带 `--confirm-production` 在一个命令内顺序完成两阶段,但不跳过预发、证据、CAS 或确认。candidate 封存源码、public、构建配置/脚本、稳定环境/资源绑定和两目标 Runtime 哈希产物;部署不现场重建,并将两条服务端 deployment 闭环为 `succeeded`。两套环境身份仍完全隔离,人工验收建议和 swap/policy/权限门禁保持不变。
|
|
68
|
+
- ✅ DataView `status` 仅是平台生命周期观察值;同一托管部署导致的 `active → draft → active` 不应让候选失效。`dataViewId`、`materializedViewName`、`storageMode`、其他资源状态、哈希、环境身份、CAS、租约和来源主线仍严格校验。
|
|
68
69
|
- ✅ 预发 UAT 未通过时,使用 `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction --profile <name>` 写入平台审计。CLI 会先核对 deployment 属于所选预发环境;production target 或不匹配的 deployment 必须在写入前拒绝。
|
|
69
70
|
- ✅ 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`。CLI 会从私有 `ship.json` 恢复血缘,必要时自动沿 production/preproduction deployment ID 查找对应 `execution.json`;失败信息必须指出实际缺失的日志或字段。
|
|
70
71
|
- ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;该意图冻结进私有 `ship.json` 并由后续 `--confirm-production` 自动复用。仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
|