openxiangda 1.0.243 → 1.0.245
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 +5 -1
- package/lib/application-environments.js +8 -0
- package/lib/cli.js +174 -17
- package/openxiangda-skills/SKILL.md +4 -2
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +3 -1
- package/package.json +5 -2
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +1 -0
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +1 -0
- package/templates/openxiangda-react-spa/AGENTS.md +4 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +1 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +1 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +2 -0
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ openxiangda app snapshot APP_XXXX --profile dev --json
|
|
|
107
107
|
|
|
108
108
|
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 durable profile-specific resource IDs and environment bindings; volatile candidate/deployment progress lives in the private `.openxiangda/releases/` journal so a release does not dirty the reviewed Git state. Durable CLI writes use a workspace lock plus atomic merge/rename so concurrent processes do not truncate another profile's state.
|
|
109
109
|
|
|
110
|
-
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/promote commands are recovery primitives. Direct `release publish` is retained only for legacy unmanaged workspaces.
|
|
110
|
+
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.
|
|
111
111
|
|
|
112
112
|
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.
|
|
113
113
|
|
|
@@ -131,6 +131,10 @@ instead of a database-column error.
|
|
|
131
131
|
|
|
132
132
|
React SPA workspaces publish their frontend with `openxiangda runtime deploy`. Every finalized Runtime release is built from a clean, committed Git `HEAD`; the CLI freezes `sourceRevision`, the current active release, and its source revision before any build/upload. Deploy fails with `RUNTIME_SOURCE_BASE_DIVERGED` when its `HEAD` does not descend from the online Runtime source, including with `--no-activate`, so an old isolated worktree cannot stage and later activate a silent rollback. `.openxiangda/`, `openspec/`, `dist/`, and other pure generated/governance/state paths do not make the source dirty, but `--no-build` cannot bypass the lineage gate. An intentional rollback requires `--allow-runtime-rollback --reason "<at least 8 characters>"`, which is persisted for audit and never bypasses dirty/non-Git checks.
|
|
133
133
|
|
|
134
|
+
Environment-managed `release ship`, recovery `release deploy`, and `release promote` accept the same audited Runtime rollback pair. It is forwarded only to `runtime-stage`, never to resource stages or `app-finalize`. Ship freezes the intent in its private journal so production confirmation can inherit it automatically; without the explicit pair, lineage divergence remains blocked.
|
|
135
|
+
|
|
136
|
+
Before publishing the OpenXiangda npm package, run `npm run test:release` once on the final clean version commit. It writes hash-bound evidence under `.openxiangda/evidence/tests/<HEAD>/release.json`. `npm publish` validates that evidence against the exact Git commit, package version, Node version, and complete passing result, then runs the mainline guard; it does not execute the full release suite a second time. Any commit, version, runtime, result, or evidence-hash drift remains fail closed.
|
|
137
|
+
|
|
134
138
|
Parallel tasks develop and test in isolated worktrees, but feature worktrees do not publish. After approved commits are merged and pushed, `sdd bundle <release-change> --changes ...` unions their exact structured scope and preserves the source changes' common Git baseline instead of adopting the post-merge `HEAD`. Old imported changes that predate source-base metadata must pass `--source-base-ref <commit>` explicitly. Commit/push the bundle, then run `release publish --change <release-change> --profile <name>` from a clean local main/master whose commit exactly equals the authoritative remote tip. It verifies without rewriting reviewed SDD files, waits for the app lease, freezes one authoritative App capture, executes exact Form/Backend/Runtime staged steps, atomically finalizes the Root App release, and records a resumable local execution journal. Runtime checks use the narrow head endpoint; immutable Git-base artifact hashes are reused across plans.
|
|
135
139
|
|
|
136
140
|
Because promotion starts from an already-pushed authoritative mainline commit, `openxiangda release integration-status --change <change> --profile <name>` should pass immediately after activation. After a managed ship has ended, `--change` recovers lineage directly from the private `ship.json` or follows its production/preproduction deployment IDs to the matching `execution.json`; a recovery failure names the missing file or field. Run it and `release end`; there is no post-release merge step.
|
|
@@ -1012,6 +1012,14 @@ function releaseExecutionPath(changeId, deploymentId, cwd = process.cwd()) {
|
|
|
1012
1012
|
|
|
1013
1013
|
function withManagedReleaseForwardedFlags(stepId, args = [], flags = {}) {
|
|
1014
1014
|
const forwarded = [...args];
|
|
1015
|
+
if (stepId === 'runtime-stage' && flags['allow-runtime-rollback']) {
|
|
1016
|
+
forwarded.push('--allow-runtime-rollback');
|
|
1017
|
+
const rollbackReason = String(flags.reason || '').trim();
|
|
1018
|
+
if (rollbackReason) {
|
|
1019
|
+
forwarded.push('--reason', rollbackReason);
|
|
1020
|
+
}
|
|
1021
|
+
return forwarded;
|
|
1022
|
+
}
|
|
1015
1023
|
const immutableResourceStages = new Set([
|
|
1016
1024
|
'backend-stage',
|
|
1017
1025
|
'workflow-stage',
|
package/lib/cli.js
CHANGED
|
@@ -336,7 +336,7 @@ Usage:
|
|
|
336
336
|
openxiangda retry <runId> [--environment preproduction|production] [--json]
|
|
337
337
|
openxiangda rollback <preproduction|production> --to <appReleaseId> [--json]
|
|
338
338
|
openxiangda release publish|begin|status|integration-status|renew|end [--change id] [--profile name] [--watch] [--json]
|
|
339
|
-
openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--replace-manifest --reason text] [--adopt-online-baseline --adoption-reason text] [--json]
|
|
339
|
+
openxiangda release ship|candidate|deploy|reconcile|test|fail|promote|rollback [--candidate id] [--deployment id] [--environment target] [--replace-manifest] [--allow-runtime-rollback --reason text] [--adopt-online-baseline --adoption-reason text] [--confirm-production] [--json]
|
|
340
340
|
openxiangda task status --change <id> [--profile name] [--watch] [--json]
|
|
341
341
|
openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]
|
|
342
342
|
openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--break-glass-adopt-verified-root --reason text] [--force-activate-without-validation] [--profile name] [--json]
|
|
@@ -1177,9 +1177,12 @@ function writeManagedShip(changeId, value) {
|
|
|
1177
1177
|
}
|
|
1178
1178
|
|
|
1179
1179
|
function normalizeManagedShipIntent(value = {}) {
|
|
1180
|
+
const enabled = Boolean(value.enabled);
|
|
1180
1181
|
return {
|
|
1181
|
-
enabled
|
|
1182
|
-
reason:
|
|
1182
|
+
enabled,
|
|
1183
|
+
reason: enabled
|
|
1184
|
+
? String(value.reason || '').trim() || null
|
|
1185
|
+
: null,
|
|
1183
1186
|
};
|
|
1184
1187
|
}
|
|
1185
1188
|
|
|
@@ -1201,8 +1204,13 @@ function resolveManagedShipIntents(flags = {}, existingShip = null) {
|
|
|
1201
1204
|
enabled: flags['adopt-online-baseline'],
|
|
1202
1205
|
reason: flags['adoption-reason'],
|
|
1203
1206
|
});
|
|
1207
|
+
const requestedRuntimeRollback = normalizeManagedShipIntent({
|
|
1208
|
+
enabled: flags['allow-runtime-rollback'],
|
|
1209
|
+
reason: flags.reason,
|
|
1210
|
+
});
|
|
1204
1211
|
const hasFrozenCandidate = Boolean(existingShip?.candidateId);
|
|
1205
1212
|
let baselineAdoption = requestedBaselineAdoption;
|
|
1213
|
+
let runtimeRollback = requestedRuntimeRollback;
|
|
1206
1214
|
|
|
1207
1215
|
if (hasFrozenCandidate) {
|
|
1208
1216
|
const recordedReplacement = normalizeManagedShipIntent(
|
|
@@ -1239,6 +1247,34 @@ function resolveManagedShipIntents(flags = {}, existingShip = null) {
|
|
|
1239
1247
|
baselineAdoption = adoptionExplicitlySupplied
|
|
1240
1248
|
? requestedBaselineAdoption
|
|
1241
1249
|
: recordedAdoption;
|
|
1250
|
+
|
|
1251
|
+
const hasRecordedRuntimeRollback = Object.prototype.hasOwnProperty.call(
|
|
1252
|
+
existingShip,
|
|
1253
|
+
'runtimeRollback'
|
|
1254
|
+
);
|
|
1255
|
+
const recordedRuntimeRollback = normalizeManagedShipIntent(
|
|
1256
|
+
existingShip.runtimeRollback
|
|
1257
|
+
);
|
|
1258
|
+
const runtimeRollbackExplicitlySupplied =
|
|
1259
|
+
Object.prototype.hasOwnProperty.call(
|
|
1260
|
+
flags,
|
|
1261
|
+
'allow-runtime-rollback'
|
|
1262
|
+
);
|
|
1263
|
+
if (
|
|
1264
|
+
hasRecordedRuntimeRollback &&
|
|
1265
|
+
runtimeRollbackExplicitlySupplied &&
|
|
1266
|
+
!managedShipIntentMatches(
|
|
1267
|
+
recordedRuntimeRollback,
|
|
1268
|
+
requestedRuntimeRollback
|
|
1269
|
+
)
|
|
1270
|
+
) {
|
|
1271
|
+
fail(
|
|
1272
|
+
'RELEASE_SHIP_RUNTIME_ROLLBACK_INTENT_MISMATCH: --allow-runtime-rollback/--reason 与该 candidate 的预发 Runtime 发布意图不一致;省略这两个参数可自动复用 ship.json 中的已冻结意图'
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
runtimeRollback = runtimeRollbackExplicitlySupplied
|
|
1276
|
+
? requestedRuntimeRollback
|
|
1277
|
+
: recordedRuntimeRollback;
|
|
1242
1278
|
}
|
|
1243
1279
|
|
|
1244
1280
|
const effectiveFlags = { ...flags };
|
|
@@ -1249,10 +1285,23 @@ function resolveManagedShipIntents(flags = {}, existingShip = null) {
|
|
|
1249
1285
|
delete effectiveFlags['adopt-online-baseline'];
|
|
1250
1286
|
delete effectiveFlags['adoption-reason'];
|
|
1251
1287
|
}
|
|
1288
|
+
if (runtimeRollback.enabled) {
|
|
1289
|
+
const existingReason = String(effectiveFlags.reason || '').trim();
|
|
1290
|
+
if (existingReason && existingReason !== runtimeRollback.reason) {
|
|
1291
|
+
fail(
|
|
1292
|
+
'RELEASE_SHIP_RUNTIME_ROLLBACK_REASON_CONFLICT: Runtime rollback 与 manifest replacement 共用 --reason 时必须使用同一审计原因'
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
effectiveFlags['allow-runtime-rollback'] = true;
|
|
1296
|
+
effectiveFlags.reason = runtimeRollback.reason;
|
|
1297
|
+
} else {
|
|
1298
|
+
delete effectiveFlags['allow-runtime-rollback'];
|
|
1299
|
+
}
|
|
1252
1300
|
|
|
1253
1301
|
return {
|
|
1254
1302
|
manifestReplacement,
|
|
1255
1303
|
baselineAdoption,
|
|
1304
|
+
runtimeRollback,
|
|
1256
1305
|
effectiveFlags,
|
|
1257
1306
|
};
|
|
1258
1307
|
}
|
|
@@ -1271,12 +1320,17 @@ function buildManagedShipCommand(
|
|
|
1271
1320
|
JSON.stringify(profileName),
|
|
1272
1321
|
];
|
|
1273
1322
|
if (options.confirmProduction) args.push('--confirm-production');
|
|
1323
|
+
let sharedReason = null;
|
|
1274
1324
|
if (intents.manifestReplacement.enabled) {
|
|
1275
|
-
args.push(
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
);
|
|
1325
|
+
args.push('--replace-manifest');
|
|
1326
|
+
sharedReason = intents.manifestReplacement.reason;
|
|
1327
|
+
}
|
|
1328
|
+
if (intents.runtimeRollback.enabled) {
|
|
1329
|
+
args.push('--allow-runtime-rollback');
|
|
1330
|
+
sharedReason = intents.runtimeRollback.reason;
|
|
1331
|
+
}
|
|
1332
|
+
if (sharedReason) {
|
|
1333
|
+
args.push('--reason', JSON.stringify(sharedReason));
|
|
1280
1334
|
}
|
|
1281
1335
|
if (intents.baselineAdoption.enabled) {
|
|
1282
1336
|
args.push(
|
|
@@ -1972,6 +2026,7 @@ async function publishWorkspaceRelease(config, target, flags = {}) {
|
|
|
1972
2026
|
if (!changeId) {
|
|
1973
2027
|
fail('用法: openxiangda release publish --change <id> --profile <name>');
|
|
1974
2028
|
}
|
|
2029
|
+
assertManagedRuntimeRollbackFlags(flags);
|
|
1975
2030
|
const releaseCandidate = flags.__verifiedCandidate
|
|
1976
2031
|
? assertLocalCandidate(flags.__verifiedCandidate)
|
|
1977
2032
|
: readReleaseCandidate(candidateId);
|
|
@@ -4390,6 +4445,7 @@ const RELEASE_SHIP_SUPPORTED_FLAGS = new Set([
|
|
|
4390
4445
|
'since',
|
|
4391
4446
|
'expand-dependencies',
|
|
4392
4447
|
'replace-manifest',
|
|
4448
|
+
'allow-runtime-rollback',
|
|
4393
4449
|
'reason',
|
|
4394
4450
|
'adopt-online-baseline',
|
|
4395
4451
|
'adoption-reason',
|
|
@@ -4414,12 +4470,19 @@ function assertReleaseShipFlags(flags = {}) {
|
|
|
4414
4470
|
throw error;
|
|
4415
4471
|
}
|
|
4416
4472
|
const replacementRequested = Boolean(flags['replace-manifest']);
|
|
4473
|
+
const runtimeRollbackRequested = Boolean(
|
|
4474
|
+
flags['allow-runtime-rollback']
|
|
4475
|
+
);
|
|
4417
4476
|
const replacementReason = String(flags.reason || '').trim();
|
|
4418
|
-
if (
|
|
4477
|
+
if (
|
|
4478
|
+
!replacementRequested &&
|
|
4479
|
+
!runtimeRollbackRequested &&
|
|
4480
|
+
replacementReason
|
|
4481
|
+
) {
|
|
4419
4482
|
const error = new Error(
|
|
4420
|
-
'
|
|
4483
|
+
'RELEASE_SHIP_SCOPED_REASON_FLAG_REQUIRED: --reason 必须与 --replace-manifest 或 --allow-runtime-rollback 一起使用;命令已在任何 candidate/deployment 写入前终止'
|
|
4421
4484
|
);
|
|
4422
|
-
error.code = '
|
|
4485
|
+
error.code = 'RELEASE_SHIP_SCOPED_REASON_FLAG_REQUIRED';
|
|
4423
4486
|
throw error;
|
|
4424
4487
|
}
|
|
4425
4488
|
if (replacementRequested && replacementReason.length < 8) {
|
|
@@ -4429,6 +4492,13 @@ function assertReleaseShipFlags(flags = {}) {
|
|
|
4429
4492
|
error.code = 'RELEASE_SHIP_REPLACE_MANIFEST_REASON_REQUIRED';
|
|
4430
4493
|
throw error;
|
|
4431
4494
|
}
|
|
4495
|
+
if (runtimeRollbackRequested && replacementReason.length < 8) {
|
|
4496
|
+
const error = new Error(
|
|
4497
|
+
'RELEASE_SHIP_RUNTIME_ROLLBACK_REASON_REQUIRED: --allow-runtime-rollback 必须提供至少 8 个字符的 --reason;命令已在任何 candidate/deployment 写入前终止'
|
|
4498
|
+
);
|
|
4499
|
+
error.code = 'RELEASE_SHIP_RUNTIME_ROLLBACK_REASON_REQUIRED';
|
|
4500
|
+
throw error;
|
|
4501
|
+
}
|
|
4432
4502
|
const adoptionRequested = Boolean(flags['adopt-online-baseline']);
|
|
4433
4503
|
const adoptionReason = String(flags['adoption-reason'] || '').trim();
|
|
4434
4504
|
if (!adoptionRequested && adoptionReason) {
|
|
@@ -4447,6 +4517,13 @@ function assertReleaseShipFlags(flags = {}) {
|
|
|
4447
4517
|
}
|
|
4448
4518
|
}
|
|
4449
4519
|
|
|
4520
|
+
function assertManagedRuntimeRollbackFlags(flags = {}) {
|
|
4521
|
+
return resolveRuntimeRollbackBypass({
|
|
4522
|
+
allowRollback: flags['allow-runtime-rollback'],
|
|
4523
|
+
reason: flags.reason,
|
|
4524
|
+
});
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4450
4527
|
async function runApplicationShipCommand(
|
|
4451
4528
|
config,
|
|
4452
4529
|
initialTarget,
|
|
@@ -4458,7 +4535,7 @@ async function runApplicationShipCommand(
|
|
|
4458
4535
|
readStringFlag(flags, 'change') || positional[0];
|
|
4459
4536
|
if (!changeId) {
|
|
4460
4537
|
fail(
|
|
4461
|
-
'用法: openxiangda release ship --change <id> [--replace-manifest --reason "..."] [--adopt-online-baseline --adoption-reason "..."] [--confirm-production] [--acceptance-note "..."]'
|
|
4538
|
+
'用法: openxiangda release ship --change <id> [--replace-manifest] [--allow-runtime-rollback] [--reason "..."] [--adopt-online-baseline --adoption-reason "..."] [--confirm-production] [--acceptance-note "..."]'
|
|
4462
4539
|
);
|
|
4463
4540
|
}
|
|
4464
4541
|
const logicalApp = requireLogicalApplication(initialTarget);
|
|
@@ -4478,6 +4555,7 @@ async function runApplicationShipCommand(
|
|
|
4478
4555
|
const {
|
|
4479
4556
|
manifestReplacement,
|
|
4480
4557
|
baselineAdoption,
|
|
4558
|
+
runtimeRollback,
|
|
4481
4559
|
effectiveFlags,
|
|
4482
4560
|
} = intents;
|
|
4483
4561
|
|
|
@@ -4526,6 +4604,7 @@ async function runApplicationShipCommand(
|
|
|
4526
4604
|
candidateFile: created.candidateFile,
|
|
4527
4605
|
manifestReplacement,
|
|
4528
4606
|
baselineAdoption,
|
|
4607
|
+
runtimeRollback,
|
|
4529
4608
|
});
|
|
4530
4609
|
} else {
|
|
4531
4610
|
assertLocalCandidate(candidate);
|
|
@@ -4563,6 +4642,7 @@ async function runApplicationShipCommand(
|
|
|
4563
4642
|
preproductionStatus: failure.deploymentStatus,
|
|
4564
4643
|
manifestReplacement,
|
|
4565
4644
|
baselineAdoption,
|
|
4645
|
+
runtimeRollback,
|
|
4566
4646
|
failure,
|
|
4567
4647
|
nextCommand: buildManagedShipCommand(
|
|
4568
4648
|
changeId,
|
|
@@ -4585,6 +4665,7 @@ async function runApplicationShipCommand(
|
|
|
4585
4665
|
preproductionStatus: deployment.status,
|
|
4586
4666
|
manifestReplacement,
|
|
4587
4667
|
baselineAdoption,
|
|
4668
|
+
runtimeRollback,
|
|
4588
4669
|
failure: null,
|
|
4589
4670
|
preparedAt:
|
|
4590
4671
|
existingShip?.preparedAt ||
|
|
@@ -4752,6 +4833,7 @@ async function runApplicationShipCommand(
|
|
|
4752
4833
|
productionStatus: failure.deploymentStatus,
|
|
4753
4834
|
manifestReplacement,
|
|
4754
4835
|
baselineAdoption,
|
|
4836
|
+
runtimeRollback,
|
|
4755
4837
|
failure,
|
|
4756
4838
|
productionPromotion: {
|
|
4757
4839
|
status: 'failed',
|
|
@@ -4816,6 +4898,7 @@ async function runApplicationShipCommand(
|
|
|
4816
4898
|
productionStatus: promoted.deployment.status,
|
|
4817
4899
|
manifestReplacement,
|
|
4818
4900
|
baselineAdoption,
|
|
4901
|
+
runtimeRollback,
|
|
4819
4902
|
failure,
|
|
4820
4903
|
productionPromotion: {
|
|
4821
4904
|
status: 'evidence_failed',
|
|
@@ -4923,6 +5006,69 @@ async function runApplicationEnvironmentReleaseCommand(
|
|
|
4923
5006
|
return { deployment: reconciled, reconciled: true };
|
|
4924
5007
|
}
|
|
4925
5008
|
|
|
5009
|
+
if (subcommand === 'fail') {
|
|
5010
|
+
if (target.environmentKind !== 'preproduction') {
|
|
5011
|
+
fail(
|
|
5012
|
+
'RELEASE_FAIL_PREPRODUCTION_ONLY: release fail 只允许 preproduction target,禁止用于 production deployment'
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
const deploymentId =
|
|
5016
|
+
readStringFlag(flags, 'deployment') ||
|
|
5017
|
+
readStringFlag(flags, 'deployment-id');
|
|
5018
|
+
const message = readStringFlag(flags, 'message');
|
|
5019
|
+
const failureCode = readStringFlag(flags, 'code');
|
|
5020
|
+
const detailsInput = readStringFlag(flags, 'details-json');
|
|
5021
|
+
if (!deploymentId || !message) {
|
|
5022
|
+
fail(
|
|
5023
|
+
'release fail 必须显式提供 --deployment <id> 和 --message <text>'
|
|
5024
|
+
);
|
|
5025
|
+
}
|
|
5026
|
+
const details = detailsInput
|
|
5027
|
+
? readJsonInput(detailsInput, 'details-json')
|
|
5028
|
+
: null;
|
|
5029
|
+
const deployment = await requestWithAuth(
|
|
5030
|
+
config,
|
|
5031
|
+
target.profileName,
|
|
5032
|
+
applicationDeploymentApiPath(logicalApp.code, deploymentId)
|
|
5033
|
+
);
|
|
5034
|
+
if (
|
|
5035
|
+
deployment?.id !== deploymentId ||
|
|
5036
|
+
deployment?.targetEnvironmentId !== target.environmentId
|
|
5037
|
+
) {
|
|
5038
|
+
fail(
|
|
5039
|
+
`RELEASE_FAIL_DEPLOYMENT_ENVIRONMENT_MISMATCH: deployment ${deploymentId} 不属于当前预发环境 ${target.targetName}(${target.environmentId}),未写入失败状态`
|
|
5040
|
+
);
|
|
5041
|
+
}
|
|
5042
|
+
const failed = await requestWithAuth(
|
|
5043
|
+
config,
|
|
5044
|
+
target.profileName,
|
|
5045
|
+
applicationDeploymentApiPath(
|
|
5046
|
+
logicalApp.code,
|
|
5047
|
+
deploymentId,
|
|
5048
|
+
'fail'
|
|
5049
|
+
),
|
|
5050
|
+
{
|
|
5051
|
+
method: 'POST',
|
|
5052
|
+
body: {
|
|
5053
|
+
...(failureCode ? { code: failureCode } : {}),
|
|
5054
|
+
message,
|
|
5055
|
+
...(details ? { details } : {}),
|
|
5056
|
+
},
|
|
5057
|
+
}
|
|
5058
|
+
);
|
|
5059
|
+
if (failed?.status !== 'failed') {
|
|
5060
|
+
fail(
|
|
5061
|
+
`DEPLOYMENT_FAILURE_NOT_CONFIRMED: deployment ${deploymentId} 标记失败后状态为 ${failed?.status || 'unknown'}`
|
|
5062
|
+
);
|
|
5063
|
+
}
|
|
5064
|
+
rememberTargetDeployment(target, failed);
|
|
5065
|
+
return { deployment: failed, failed: true };
|
|
5066
|
+
}
|
|
5067
|
+
|
|
5068
|
+
if (['deploy', 'promote'].includes(subcommand)) {
|
|
5069
|
+
assertManagedRuntimeRollbackFlags(flags);
|
|
5070
|
+
}
|
|
5071
|
+
|
|
4926
5072
|
const candidateId =
|
|
4927
5073
|
readStringFlag(flags, 'candidate') ||
|
|
4928
5074
|
readStringFlag(flags, 'candidate-id') ||
|
|
@@ -5044,15 +5190,17 @@ async function release(args) {
|
|
|
5044
5190
|
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
5045
5191
|
print([
|
|
5046
5192
|
'用法: openxiangda release publish|begin|status|explain|integration-status|renew|end [--change id] [--profile name] [--json]',
|
|
5047
|
-
' 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]',
|
|
5193
|
+
' openxiangda release ship|candidate|deploy|reconcile|test|fail|promote|rollback [--candidate id] [--deployment id] [--environment target] [--replace-manifest] [--allow-runtime-rollback --reason text] [--adopt-online-baseline --adoption-reason text] [--confirm-production] [--json]',
|
|
5048
5194
|
' openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]',
|
|
5049
5195
|
' openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-post-commit|app-retry|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--break-glass-adopt-verified-root --reason text] [--force-activate-without-validation] [--profile name] [--json]',
|
|
5050
5196
|
'常用流程:',
|
|
5051
5197
|
' openxiangda release ship --change <id> --profile <name>',
|
|
5052
5198
|
' openxiangda release ship --change <id> --profile <name> --adopt-online-baseline --adoption-reason "已审计目标来自多次历史发布"',
|
|
5053
5199
|
' openxiangda release ship --change <id> --profile <name> --replace-manifest --reason "已审查精确 Function manifest"',
|
|
5200
|
+
' openxiangda release ship --change <id> --profile <name> --allow-runtime-rollback --reason "已审计并批准 Runtime 源码基线回退"',
|
|
5054
5201
|
' # 确认预发结果后;可选 --acceptance-note 留下人工验收记录:',
|
|
5055
5202
|
' openxiangda release ship --change <id> --profile <name> --confirm-production',
|
|
5203
|
+
' openxiangda release fail --deployment <id> --message "UAT 未通过" --code UAT_FAILED --details-json <JSON|file> --environment preproduction --profile <name>',
|
|
5056
5204
|
' openxiangda release publish --change <id> --profile <name>',
|
|
5057
5205
|
' openxiangda release begin --change <id> --profile <name>',
|
|
5058
5206
|
' openxiangda resource publish function --only <code> --change <id> --profile <name>',
|
|
@@ -5068,6 +5216,8 @@ async function release(args) {
|
|
|
5068
5216
|
' - candidate 封存环境/资源绑定和两目标 Runtime 哈希产物;预发与正式部署均不现场重建,并以 deployment succeeded 闭环。',
|
|
5069
5217
|
' - 已合入主线但线上来自多次历史 lineage 的精确非删除恢复可显式使用 --adopt-online-baseline 与 --adoption-reason;预发冻结后,正式确认会从 ship.json 自动复用同一意图,仍保留冻结 Head、CAS、租约、staged children 和单次 App finalize。',
|
|
5070
5218
|
' - ship 的 --replace-manifest 与 --reason 必须成对,只透传给精确 Backend Function/Automation stage;正式确认必须复用与预发完全相同的参数。',
|
|
5219
|
+
' - --allow-runtime-rollback 与 --reason 必须成对,只透传给 runtime-stage;ship 会冻结并在正式确认自动复用该审计意图,app-finalize 不会收到这些参数。',
|
|
5220
|
+
' - release fail 只允许显式选择 preproduction target,并会先核对 deployment 的 targetEnvironmentId;message 必填,code/details 会一并写入平台审计。',
|
|
5071
5221
|
' - ship 当前不支持 --dry-run;任何未声明参数都会在 candidate/deployment 写入前 fail-closed。需要只读检查时使用 environment status/diff 与 release status。',
|
|
5072
5222
|
' - publish 默认等待租约并按私有执行日志恢复;上次写结果不确定时必须只读核对后显式 --resume-after-review。',
|
|
5073
5223
|
' - app-head 默认只输出紧凑 head 摘要;需要完整 manifest 时显式追加 --full。',
|
|
@@ -5093,9 +5243,16 @@ async function release(args) {
|
|
|
5093
5243
|
flags
|
|
5094
5244
|
);
|
|
5095
5245
|
if (
|
|
5096
|
-
[
|
|
5097
|
-
|
|
5098
|
-
|
|
5246
|
+
[
|
|
5247
|
+
'ship',
|
|
5248
|
+
'candidate',
|
|
5249
|
+
'deploy',
|
|
5250
|
+
'reconcile',
|
|
5251
|
+
'test',
|
|
5252
|
+
'fail',
|
|
5253
|
+
'promote',
|
|
5254
|
+
'rollback',
|
|
5255
|
+
].includes(subcommand)
|
|
5099
5256
|
) {
|
|
5100
5257
|
const result = await runApplicationEnvironmentReleaseCommand(
|
|
5101
5258
|
config,
|
|
@@ -16407,7 +16564,7 @@ async function commands(args) {
|
|
|
16407
16564
|
'design gates|template|review [--topic code]',
|
|
16408
16565
|
'sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive',
|
|
16409
16566
|
'environment init|attach|bind|swap|policy update|list|status|diff|use',
|
|
16410
|
-
'release ship|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',
|
|
16567
|
+
'release ship|candidate|deploy|reconcile|test|fail|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',
|
|
16411
16568
|
'studio [--no-open] [--port 0]',
|
|
16412
16569
|
'env',
|
|
16413
16570
|
'workspace init|bind|cleanup|plan|check|publish [--app-name] [--apply|--changed|--since|--form|--page|--only|--dry-run|--force|--resources|--skip-resources|--prune]',
|
|
@@ -127,7 +127,7 @@ openxiangda release ship --change <release-change> --profile <name> \
|
|
|
127
127
|
--confirm-production
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
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/promote commands are recovery primitives. 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.
|
|
130
|
+
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.
|
|
131
131
|
|
|
132
132
|
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.
|
|
133
133
|
|
|
@@ -135,9 +135,11 @@ For an audited catch-up whose exact non-delete targets are already merged but wh
|
|
|
135
135
|
|
|
136
136
|
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.
|
|
137
137
|
|
|
138
|
+
When the sealed Runtime source intentionally does not descend from the active Runtime lineage, managed `release ship`, recovery `release deploy`, and `release promote` may add `--allow-runtime-rollback --reason "..."`. The reason must be at least 8 characters. The CLI forwards the pair only to `runtime-stage`, never to Backend/Form/Workflow/configuration or `app-finalize`; ship freezes the pair in `ship.json`, and production confirmation automatically inherits it. Without the explicit pair, Runtime lineage remains fail closed.
|
|
139
|
+
|
|
138
140
|
`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.
|
|
139
141
|
|
|
140
|
-
`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.
|
|
142
|
+
`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/fail/promote, `release begin`, and child commands remain recovery/diagnostic primitives.
|
|
141
143
|
|
|
142
144
|
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.
|
|
143
145
|
|
|
@@ -138,7 +138,9 @@ When a reviewed catch-up contains exact non-delete targets that are already on a
|
|
|
138
138
|
|
|
139
139
|
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.
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
For an audited Runtime source rollback, managed `release ship`, recovery `release deploy`, and `release promote` accept `--allow-runtime-rollback --reason "..."` with a reason of at least 8 characters. This pair is scoped only to `runtime-stage` and never reaches resource stages or `app-finalize`. Ship freezes it in `ship.json`; production confirmation inherits it automatically. The default remains fail closed.
|
|
142
|
+
|
|
143
|
+
`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/fail/promote commands are recovery/diagnostic primitives. `release fail --deployment <id> --message <text> [--code <code>] [--details-json <JSON|file>] --environment preproduction` records an audited UAT failure only after verifying the deployment belongs to the selected preproduction environment; production targets and mismatched deployment identities fail before the write.
|
|
142
144
|
|
|
143
145
|
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.
|
|
144
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.245",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"test:dependency-capsule": "node scripts/dependency-capsule-smoke.mjs",
|
|
81
81
|
"test:typed-resource-plan": "node scripts/typed-resource-plan-smoke.mjs",
|
|
82
82
|
"test:integration-bundle": "node scripts/integration-bundle-smoke.mjs",
|
|
83
|
-
"prepublishOnly": "npm run
|
|
83
|
+
"prepublishOnly": "npm run release:evidence:verify && node scripts/release-mainline-guard.mjs",
|
|
84
84
|
"prepack": "npm run source:admin-list && npm run build:sdk",
|
|
85
85
|
"test:profile-isolation": "bash scripts/profile-isolation-smoke.sh",
|
|
86
86
|
"test:resource-plan": "node scripts/resource-plan-smoke.mjs",
|
|
@@ -108,6 +108,8 @@
|
|
|
108
108
|
"test:release-telemetry": "node scripts/release-telemetry-smoke.mjs",
|
|
109
109
|
"test:release-explain": "node scripts/release-explain-smoke.mjs",
|
|
110
110
|
"test:release-error-classification": "node scripts/release-error-classification-smoke.mjs",
|
|
111
|
+
"test:release-evidence": "node scripts/release-evidence-guard-smoke.mjs",
|
|
112
|
+
"release:evidence:verify": "node scripts/release-evidence-guard.mjs",
|
|
111
113
|
"test:delivery-v2-package": "node scripts/delivery-v2-package-smoke.mjs",
|
|
112
114
|
"test:delivery-v2-executor": "node scripts/delivery-v2-executor-smoke.mjs",
|
|
113
115
|
"test:task-status": "node scripts/task-status-smoke.mjs",
|
|
@@ -121,6 +123,7 @@
|
|
|
121
123
|
"test:app-release-cli": "node scripts/app-release-cli-smoke.mjs",
|
|
122
124
|
"test:application-environments": "node scripts/application-environments-smoke.mjs",
|
|
123
125
|
"test:release-ship": "node scripts/release-ship-gate-smoke.mjs",
|
|
126
|
+
"test:release-fail": "node scripts/release-fail-cli-smoke.mjs",
|
|
124
127
|
"test:environment-swap": "node scripts/environment-swap-cli-smoke.mjs",
|
|
125
128
|
"test:environment-policy": "node scripts/environment-policy-cli-smoke.mjs",
|
|
126
129
|
"test:developer-center": "node scripts/developer-center-smoke.mjs",
|
|
@@ -31,6 +31,7 @@ This is an OpenXiangda React SPA workspace using Delivery V2. See [DELIVERY.md](
|
|
|
31
31
|
- 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. A clone primary may be canonicalized to the frozen repository ID only when that ID is already in `repoAliases`; otherwise release prepare fails closed. After activation, run `integration-status` and `release end`; no post-release merge is needed.
|
|
32
32
|
- 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.
|
|
33
33
|
- Managed `release ship` always runs candidate → preproduction → production. Normally confirmation is a later invocation; an explicitly authorized emergency may put `--confirm-production` on the first invocation to run both phases in one command. The candidate seals environment/resource bindings and target-specific Runtime artifacts; deployment uses no rebuild and must finish each server deployment as `succeeded`.
|
|
34
|
+
- Record a failed preproduction UAT with `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction`; the CLI verifies target ownership before the audited write and rejects production or mismatched deployments.
|
|
34
35
|
- 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.
|
|
35
36
|
- 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.
|
|
36
37
|
- 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`.
|
|
@@ -35,6 +35,7 @@ This is an OpenXiangda React SPA workspace using Delivery V2. Read [DELIVERY.md]
|
|
|
35
35
|
- 环境托管 `release ship --adopt-online-baseline --adoption-reason "..."` 会把审计意图冻结进私有 ship journal,后续 `--confirm-production` 自动复用;显式传入不同参数会在任何请求前失败。
|
|
36
36
|
- 环境托管 `release ship --replace-manifest --reason "..."` 必须成对、reason 至少 8 字符且仅限精确 Backend selector;正式确认复用与预发完全相同的参数,不透传 Form/Workflow/Runtime/配置或全量步骤。
|
|
37
37
|
- `release ship` 始终顺序执行 candidate → 预发 → 生产。日常分两次确认;明确授权紧急发布时首条命令可带 `--confirm-production` 一次完成,但不跳过预发/证据/CAS。candidate 封存环境/资源绑定及两目标 Runtime 哈希产物,部署不重建且每条服务端 deployment 必须闭环为 `succeeded`。
|
|
38
|
+
- 预发 UAT 失败使用 `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction` 写审计;CLI 先校验 deployment 属于所选预发环境,禁止 production 或环境不匹配写入。
|
|
38
39
|
- 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`;CLI 从私有 `ship.json` 或其引用的 production/preproduction deployment execution 日志恢复血缘,无法恢复时指出实际缺失的文件或字段。
|
|
39
40
|
- 多 target 的托管工作区执行 `function invoke` 必须显式传 `--environment <target>`,并先核对 stderr 回显的最终 target 再判断响应。
|
|
40
41
|
- Promotion 必须持有 `release begin/end` 租约;`release begin` 只接受与权威远端 tip 完全一致的 clean main/master。feature branch 或未 push 主线在任何写入前失败。clone primary 只有在冻结仓库 ID 已存在于 `repoAliases` 时才会 canonicalize;无交集在 Release prepare 前失败关闭。激活后直接运行 `integration-status` 和 `release end`,不再补做发布后合并。
|
|
@@ -60,12 +60,16 @@ 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
|
+
预发 UAT 未通过时,使用 `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction --profile <name>` 留下平台审计记录。CLI 会先核对 deployment 属于所选预发环境;禁止对 production target 或不匹配的 deployment 使用。
|
|
64
|
+
|
|
63
65
|
托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`。CLI 会从私有 `ship.json` 恢复血缘,必要时自动沿 production/preproduction deployment ID 查找对应 `execution.json`;失败信息必须指出实际缺失的日志或字段。
|
|
64
66
|
|
|
65
67
|
只有已审计代码早已进入权威主线、而精确非删除目标在线上来自多次历史发布且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`。该意图会冻结进私有 `ship.json`,后续 `--confirm-production` 自动复用;它不会放松冻结 online heads、change/lease、删除/全量拒绝、服务端 CAS、staged children 或单次 App finalize 原子激活。
|
|
66
68
|
|
|
67
69
|
环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只会进入精确 Backend selector,正式确认必须复用与预发完全相同的参数,绝不扩散到表单、流程、Runtime、配置资源或全量范围。
|
|
68
70
|
|
|
71
|
+
环境托管发布确需审计式 Runtime 源码血缘回退时,`release ship`、恢复命令 `release deploy` 和 `release promote` 可成对增加 `--allow-runtime-rollback --reason "..."`;reason 至少 8 字符,只进入 `runtime-stage`,不进入资源阶段或 `app-finalize`。ship 会冻结该意图,正式确认自动复用;省略时继续失败关闭。
|
|
72
|
+
|
|
69
73
|
受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为发布边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍必须 fail closed。
|
|
70
74
|
|
|
71
75
|
`resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
|
|
@@ -39,6 +39,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
|
|
|
39
39
|
- After managed promotion, run `release integration-status --change <change> --profile <name> --check`; it recovers lineage from private `ship.json` or its referenced production/preproduction deployment execution journal, and names the missing file or field when recovery is impossible.
|
|
40
40
|
- 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. A clone primary may be canonicalized to the frozen repository ID only when that ID is already in `repoAliases`; otherwise release prepare fails closed. After activation, run `integration-status` and `release end`; no post-release merge is needed.
|
|
41
41
|
- Environment-managed workspaces never publish directly. Use `release ship`: normally stop after preproduction and confirm later; for an explicitly authorized emergency, put `--confirm-production` on the first invocation to run both ordered phases. The candidate seals bindings and target-specific Runtime artifacts, deployments do not rebuild, and each server deployment must close as `succeeded`. Keep app/resource/data IDs isolated.
|
|
42
|
+
- Record a failed preproduction UAT with `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction`; the CLI verifies target ownership before the audited write and rejects production or mismatched deployments.
|
|
42
43
|
- 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.
|
|
43
44
|
- 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`.
|
|
44
45
|
- 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,6 +37,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
|
|
|
37
37
|
- 相同 change 的 staged FormRelease 只有经服务端重新核验 immutable/inactive/non-aborted、identity/hash、冻结 schema/formType、finalized 资源、parent/base revision 与当前 Form Head 后,才可重挂接新 baseline/session。`schemaSyncedAt` 不是发布证据;禁止伪造、直发 schema 或提前激活 Form 绕过 Workflow 校验。
|
|
38
38
|
- 环境托管 `release ship --replace-manifest --reason "..."` 必须成对、reason 至少 8 字符且仅限精确 Backend selector;正式确认复用与预发完全相同的参数,不透传 Form/Workflow/Runtime/配置或全量步骤。
|
|
39
39
|
- `release ship` 始终顺序执行 candidate → 预发 → 生产。日常分两次确认;明确授权紧急发布时首条命令可带 `--confirm-production` 一次完成,但不跳过预发/证据/CAS。candidate 封存环境/资源绑定及两目标 Runtime 哈希产物,部署不重建且每条服务端 deployment 必须闭环为 `succeeded`。
|
|
40
|
+
- 预发 UAT 失败使用 `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction` 写审计;CLI 先校验 deployment 属于所选预发环境,禁止 production 或环境不匹配写入。
|
|
40
41
|
- 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`;CLI 从私有 `ship.json` 或其引用的 production/preproduction deployment execution 日志恢复血缘,无法恢复时指出实际缺失的文件或字段。
|
|
41
42
|
- 写入平台前只从与权威远端 tip 完全一致的 clean main/master 执行 `release begin`。feature branch 或未 push 主线在任何写入前失败;clone primary 只有在冻结仓库 ID 已存在于 `repoAliases` 时才会 canonicalize,无交集在 Release prepare 前失败关闭;激活后直接运行 `integration-status` 和 `release end`,无需发布后再合并。
|
|
42
43
|
- 环境托管工作区禁止直发:已有工作区先用 `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,且默认不放开副作用。
|
|
@@ -65,9 +65,11 @@ Delivery V2 自动从期望状态计算精确资源范围,封存内容寻址 A
|
|
|
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
|
+
- ✅ 预发 UAT 未通过时,使用 `release fail --deployment <id> --message "..." [--code <code>] [--details-json <JSON|file>] --environment preproduction --profile <name>` 写入平台审计。CLI 会先核对 deployment 属于所选预发环境;production target 或不匹配的 deployment 必须在写入前拒绝。
|
|
68
69
|
- ✅ 托管发布完成后运行 `release integration-status --change <change> --profile <name> --check`。CLI 会从私有 `ship.json` 恢复血缘,必要时自动沿 production/preproduction deployment ID 查找对应 `execution.json`;失败信息必须指出实际缺失的日志或字段。
|
|
69
70
|
- ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;该意图冻结进私有 `ship.json` 并由后续 `--confirm-production` 自动复用。仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
|
|
70
71
|
- ✅ 环境托管发布需要完整替换 Function/Automation manifest 时,`release ship` 可成对增加 `--replace-manifest --reason "..."`;reason 至少 8 字符,只透传精确 Backend selector,正式确认必须复用同一对参数,不扩散到 Form/Workflow/Runtime/配置或全量范围。
|
|
72
|
+
- ✅ 环境托管发布确需审计式 Runtime 源码血缘回退时,`release ship`、恢复命令 `release deploy` 和 `release promote` 可成对增加 `--allow-runtime-rollback --reason "..."`;reason 至少 8 字符,只透传 `runtime-stage`,不进入资源阶段或 `app-finalize`。ship 冻结该意图并由正式确认自动复用;默认仍失败关闭。
|
|
71
73
|
- ✅ 受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍 fail closed。
|
|
72
74
|
- ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
|
|
73
75
|
|