openxiangda 1.0.252 → 1.0.253
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 +7 -2
- package/lib/cli.js +181 -0
- package/lib/delivery-v2-executor.js +26 -1
- package/lib/policy.js +5 -1
- package/openxiangda-skills/SKILL.md +5 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +5 -1
- package/package.json +1 -1
- package/policy/openxiangda-policy.json +3 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +1 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +2 -2
- package/templates/openxiangda-react-spa/DELIVERY.md +5 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +1 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +1 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +2 -2
- package/templates/sy-lowcode-app-workspace/DELIVERY.md +5 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- OpenXiangda-Policy-Version:
|
|
1
|
+
<!-- OpenXiangda-Policy-Version: 7 -->
|
|
2
2
|
# OpenXiangda
|
|
3
3
|
|
|
4
4
|
OpenXiangda is a lightweight CLI and skill package for private low-code platforms.
|
|
@@ -38,7 +38,12 @@ path is used.
|
|
|
38
38
|
`status` and `retry` locate the run across preproduction and production when
|
|
39
39
|
`--environment` is omitted. Retry resumes only unfinished checkpoints, safely
|
|
40
40
|
replays local Form bindings on another machine, and uses a server-side attempt
|
|
41
|
-
fence so an older executor cannot continue writing after takeover.
|
|
41
|
+
fence so an older executor cannot continue writing after takeover. Runtime
|
|
42
|
+
build IDs include both the Runtime layer and sealed package digests, so a newly
|
|
43
|
+
sealed package cannot collide with an older package that happens to contain the
|
|
44
|
+
same Runtime bytes. An exact same-package retry may reuse an `uploaded` release
|
|
45
|
+
only after verifying its content hash, source revision, and Runtime parent;
|
|
46
|
+
immutable storage objects are never overwritten.
|
|
42
47
|
|
|
43
48
|
The lower-level commands later in this README remain available for V1
|
|
44
49
|
compatibility and diagnostics; do not use them to assemble a normal V2 release.
|
package/lib/cli.js
CHANGED
|
@@ -15674,7 +15674,13 @@ async function runtime(args) {
|
|
|
15674
15674
|
const files = collectRuntimeDistFiles(distDir, {
|
|
15675
15675
|
includeSourceMaps: Boolean(flags['include-sourcemaps']),
|
|
15676
15676
|
});
|
|
15677
|
+
const declaredReleaseFiles = runtimeReleaseFileDescriptors(files);
|
|
15677
15678
|
const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
15679
|
+
const reusableRuntimeRelease = await telemetry.runPhase(
|
|
15680
|
+
'reuse-check',
|
|
15681
|
+
async () =>
|
|
15682
|
+
findRuntimeReleaseByBuildId(config, target, buildId)
|
|
15683
|
+
);
|
|
15678
15684
|
printRuntimeProgress(
|
|
15679
15685
|
`runtime release upload: mode=${effectiveUploadMode} traceId=${traceId} files=${files.length} size=${formatBytes(totalBytes)} timeout=${uploadTimeoutMs}ms`
|
|
15680
15686
|
);
|
|
@@ -15682,6 +15688,20 @@ async function runtime(args) {
|
|
|
15682
15688
|
'upload',
|
|
15683
15689
|
{ fileCount: files.length, totalBytes, mode: effectiveUploadMode },
|
|
15684
15690
|
async () => {
|
|
15691
|
+
if (reusableRuntimeRelease) {
|
|
15692
|
+
assertRuntimeReleaseReusable(reusableRuntimeRelease, {
|
|
15693
|
+
appType: target.appType,
|
|
15694
|
+
buildId,
|
|
15695
|
+
files: declaredReleaseFiles,
|
|
15696
|
+
sourceRevision: runtimeLineage.sourceRevision,
|
|
15697
|
+
parentReleaseId: runtimeLineage.parentReleaseId,
|
|
15698
|
+
});
|
|
15699
|
+
effectiveUploadMode = 'reuse-existing';
|
|
15700
|
+
printRuntimeProgress(
|
|
15701
|
+
`runtime immutable release reused: releaseId=${reusableRuntimeRelease.id} buildId=${buildId} traceId=${traceId}`
|
|
15702
|
+
);
|
|
15703
|
+
return declaredReleaseFiles;
|
|
15704
|
+
}
|
|
15685
15705
|
if (uploadMode === 'legacy-json') {
|
|
15686
15706
|
effectiveUploadMode = 'legacy-json';
|
|
15687
15707
|
return files.map(file => ({
|
|
@@ -15714,6 +15734,27 @@ async function runtime(args) {
|
|
|
15714
15734
|
timeoutMs: uploadTimeoutMs,
|
|
15715
15735
|
});
|
|
15716
15736
|
} catch (error) {
|
|
15737
|
+
if (isRuntimeBuildImmutableConflict(error)) {
|
|
15738
|
+
const racedRuntimeRelease = await findRuntimeReleaseByBuildId(
|
|
15739
|
+
config,
|
|
15740
|
+
target,
|
|
15741
|
+
buildId
|
|
15742
|
+
);
|
|
15743
|
+
if (racedRuntimeRelease) {
|
|
15744
|
+
assertRuntimeReleaseReusable(racedRuntimeRelease, {
|
|
15745
|
+
appType: target.appType,
|
|
15746
|
+
buildId,
|
|
15747
|
+
files: declaredReleaseFiles,
|
|
15748
|
+
sourceRevision: runtimeLineage.sourceRevision,
|
|
15749
|
+
parentReleaseId: runtimeLineage.parentReleaseId,
|
|
15750
|
+
});
|
|
15751
|
+
effectiveUploadMode = 'reuse-existing';
|
|
15752
|
+
printRuntimeProgress(
|
|
15753
|
+
`runtime immutable release won upload race and was reused: releaseId=${racedRuntimeRelease.id} buildId=${buildId} traceId=${traceId}`
|
|
15754
|
+
);
|
|
15755
|
+
return declaredReleaseFiles;
|
|
15756
|
+
}
|
|
15757
|
+
}
|
|
15717
15758
|
if (uploadMode !== 'auto' || !isRuntimeStagedUploadBlocked(error)) {
|
|
15718
15759
|
throw error;
|
|
15719
15760
|
}
|
|
@@ -16076,6 +16117,134 @@ function collectRuntimeDistFiles(distDir, options = {}) {
|
|
|
16076
16117
|
return files;
|
|
16077
16118
|
}
|
|
16078
16119
|
|
|
16120
|
+
function runtimeReleaseFileDescriptors(files) {
|
|
16121
|
+
return (files || []).map(file => ({
|
|
16122
|
+
path: file.path,
|
|
16123
|
+
size: file.size,
|
|
16124
|
+
sha256: file.sha256,
|
|
16125
|
+
contentType: file.contentType,
|
|
16126
|
+
}));
|
|
16127
|
+
}
|
|
16128
|
+
|
|
16129
|
+
function calculateRuntimeReleaseContentHash(files) {
|
|
16130
|
+
const hash = crypto.createHash('sha256');
|
|
16131
|
+
for (const file of files || []) {
|
|
16132
|
+
const filePath = String(file?.path || '').trim();
|
|
16133
|
+
const fileSha256 = String(file?.sha256 || '').trim().toLowerCase();
|
|
16134
|
+
if (!filePath || !/^[a-f0-9]{64}$/.test(fileSha256)) {
|
|
16135
|
+
throw runtimeReleaseReuseError(
|
|
16136
|
+
'RUNTIME_RELEASE_REUSE_LOCAL_MANIFEST_INVALID',
|
|
16137
|
+
`本地 Runtime 文件描述不完整: ${filePath || '(unknown)'}`,
|
|
16138
|
+
{ filePath: filePath || null }
|
|
16139
|
+
);
|
|
16140
|
+
}
|
|
16141
|
+
hash.update(filePath);
|
|
16142
|
+
hash.update(fileSha256);
|
|
16143
|
+
}
|
|
16144
|
+
return hash.digest('hex');
|
|
16145
|
+
}
|
|
16146
|
+
|
|
16147
|
+
async function findRuntimeReleaseByBuildId(config, target, buildId) {
|
|
16148
|
+
const appPath = `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/runtime/releases`;
|
|
16149
|
+
try {
|
|
16150
|
+
const exact = await requestWithAuth(
|
|
16151
|
+
config,
|
|
16152
|
+
target.profileName,
|
|
16153
|
+
`${appPath}/by-build/${encodeURIComponent(buildId)}`
|
|
16154
|
+
);
|
|
16155
|
+
return exact?.id ? exact : null;
|
|
16156
|
+
} catch (error) {
|
|
16157
|
+
if (Number(error?.status) !== 404) throw error;
|
|
16158
|
+
}
|
|
16159
|
+
const releases = await requestWithAuth(
|
|
16160
|
+
config,
|
|
16161
|
+
target.profileName,
|
|
16162
|
+
appPath
|
|
16163
|
+
);
|
|
16164
|
+
const items = Array.isArray(releases)
|
|
16165
|
+
? releases
|
|
16166
|
+
: Array.isArray(releases?.items)
|
|
16167
|
+
? releases.items
|
|
16168
|
+
: [];
|
|
16169
|
+
return (
|
|
16170
|
+
items.find(item => String(item?.buildId || '') === String(buildId)) ||
|
|
16171
|
+
null
|
|
16172
|
+
);
|
|
16173
|
+
}
|
|
16174
|
+
|
|
16175
|
+
function assertRuntimeReleaseReusable(existing, expected) {
|
|
16176
|
+
const context = {
|
|
16177
|
+
appType: expected.appType,
|
|
16178
|
+
buildId: expected.buildId,
|
|
16179
|
+
existingReleaseId: existing?.id || null,
|
|
16180
|
+
};
|
|
16181
|
+
if (String(existing?.status || '') !== 'uploaded') {
|
|
16182
|
+
throw runtimeReleaseReuseError(
|
|
16183
|
+
'RUNTIME_RELEASE_REUSE_STATUS_INVALID',
|
|
16184
|
+
`相同 buildId 的 Runtime Release 状态为 ${existing?.status || '(unknown)'},仅 uploaded 状态可作为失败发布的暂存恢复点`,
|
|
16185
|
+
{ ...context, existingStatus: existing?.status || null }
|
|
16186
|
+
);
|
|
16187
|
+
}
|
|
16188
|
+
const expectedContentHash = calculateRuntimeReleaseContentHash(
|
|
16189
|
+
expected.files
|
|
16190
|
+
);
|
|
16191
|
+
if (
|
|
16192
|
+
String(existing?.contentHash || '').trim().toLowerCase() !==
|
|
16193
|
+
expectedContentHash
|
|
16194
|
+
) {
|
|
16195
|
+
throw runtimeReleaseReuseError(
|
|
16196
|
+
'RUNTIME_RELEASE_REUSE_CONTENT_MISMATCH',
|
|
16197
|
+
'相同 buildId 的历史 Runtime Release 内容与当前密封包不一致,拒绝复用',
|
|
16198
|
+
{
|
|
16199
|
+
...context,
|
|
16200
|
+
expectedContentHash,
|
|
16201
|
+
existingContentHash: existing?.contentHash || null,
|
|
16202
|
+
}
|
|
16203
|
+
);
|
|
16204
|
+
}
|
|
16205
|
+
if (
|
|
16206
|
+
!runtimeSourceRevisionEquals(
|
|
16207
|
+
existing?.sourceRevision,
|
|
16208
|
+
expected.sourceRevision
|
|
16209
|
+
)
|
|
16210
|
+
) {
|
|
16211
|
+
throw runtimeReleaseReuseError(
|
|
16212
|
+
'RUNTIME_RELEASE_REUSE_SOURCE_MISMATCH',
|
|
16213
|
+
'相同 buildId 的历史 Runtime Release 源码血缘与当前密封包不一致,拒绝复用',
|
|
16214
|
+
context
|
|
16215
|
+
);
|
|
16216
|
+
}
|
|
16217
|
+
const existingParentReleaseId =
|
|
16218
|
+
String(existing?.parentReleaseId || '').trim() || null;
|
|
16219
|
+
const expectedParentReleaseId =
|
|
16220
|
+
String(expected.parentReleaseId || '').trim() || null;
|
|
16221
|
+
if (existingParentReleaseId !== expectedParentReleaseId) {
|
|
16222
|
+
throw runtimeReleaseReuseError(
|
|
16223
|
+
'RUNTIME_RELEASE_REUSE_PARENT_MISMATCH',
|
|
16224
|
+
'相同 buildId 的历史 Runtime Release 父版本已与当前 Runtime head 不一致,拒绝复用',
|
|
16225
|
+
{
|
|
16226
|
+
...context,
|
|
16227
|
+
existingParentReleaseId,
|
|
16228
|
+
expectedParentReleaseId,
|
|
16229
|
+
}
|
|
16230
|
+
);
|
|
16231
|
+
}
|
|
16232
|
+
return existing;
|
|
16233
|
+
}
|
|
16234
|
+
|
|
16235
|
+
function runtimeSourceRevisionEquals(left, right) {
|
|
16236
|
+
return ['repo', 'baseCommit', 'treeHash'].every(
|
|
16237
|
+
key => String(left?.[key] || '').trim() === String(right?.[key] || '').trim()
|
|
16238
|
+
);
|
|
16239
|
+
}
|
|
16240
|
+
|
|
16241
|
+
function runtimeReleaseReuseError(code, message, data = {}) {
|
|
16242
|
+
const error = new Error(`${code}: ${message}`);
|
|
16243
|
+
error.code = code;
|
|
16244
|
+
error.data = data;
|
|
16245
|
+
return error;
|
|
16246
|
+
}
|
|
16247
|
+
|
|
16079
16248
|
function walkRuntimeDist(rootDir, currentDir, files, options) {
|
|
16080
16249
|
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
16081
16250
|
for (const entry of entries) {
|
|
@@ -16311,6 +16480,16 @@ function isRuntimeStagedUploadBlocked(error) {
|
|
|
16311
16480
|
return /^HTTP 403\b/.test(String(error?.message || ''));
|
|
16312
16481
|
}
|
|
16313
16482
|
|
|
16483
|
+
function isRuntimeBuildImmutableConflict(error) {
|
|
16484
|
+
return (
|
|
16485
|
+
Number(error?.status) === 409 &&
|
|
16486
|
+
(String(error?.code || '') === 'RUNTIME_BUILD_IMMUTABLE' ||
|
|
16487
|
+
/Runtime buildId .*禁止覆盖|RUNTIME_BUILD_IMMUTABLE/.test(
|
|
16488
|
+
String(error?.message || '')
|
|
16489
|
+
))
|
|
16490
|
+
);
|
|
16491
|
+
}
|
|
16492
|
+
|
|
16314
16493
|
function normalizeRuntimeUploadTimeoutMs(value) {
|
|
16315
16494
|
const candidate =
|
|
16316
16495
|
value === undefined || value === null || value === ''
|
|
@@ -30664,12 +30843,14 @@ function buildWorkspacePublishEnv(
|
|
|
30664
30843
|
}
|
|
30665
30844
|
|
|
30666
30845
|
module.exports = {
|
|
30846
|
+
assertRuntimeReleaseReusable,
|
|
30667
30847
|
buildResourceManifestSddTargets,
|
|
30668
30848
|
buildScopeGrantSourceSyncDecision,
|
|
30669
30849
|
deploymentEvidencePendingError,
|
|
30670
30850
|
deploymentQueueWaitDecision,
|
|
30671
30851
|
inspectSucceededPreproductionDeployment,
|
|
30672
30852
|
main,
|
|
30853
|
+
calculateRuntimeReleaseContentHash,
|
|
30673
30854
|
recoveredPreproductionShipPatch,
|
|
30674
30855
|
resolveReleaseCommandScopedFiles,
|
|
30675
30856
|
selectSucceededPreproductionDeployment,
|
|
@@ -1096,7 +1096,7 @@ function decorateSteps(
|
|
|
1096
1096
|
'--dist',
|
|
1097
1097
|
path.join(executionRoot, 'dist'),
|
|
1098
1098
|
'--build-id',
|
|
1099
|
-
|
|
1099
|
+
deliveryV2RuntimeBuildId(runtime?.digest, run?.packageDigest),
|
|
1100
1100
|
'--upload-mode',
|
|
1101
1101
|
'staged'
|
|
1102
1102
|
);
|
|
@@ -1129,6 +1129,30 @@ function decorateSteps(
|
|
|
1129
1129
|
return result;
|
|
1130
1130
|
}
|
|
1131
1131
|
|
|
1132
|
+
function deliveryV2RuntimeBuildId(runtimeDigest, packageDigest) {
|
|
1133
|
+
const normalizedRuntimeDigest = String(runtimeDigest || '')
|
|
1134
|
+
.trim()
|
|
1135
|
+
.toLowerCase();
|
|
1136
|
+
const normalizedPackageDigest = String(packageDigest || '')
|
|
1137
|
+
.trim()
|
|
1138
|
+
.toLowerCase();
|
|
1139
|
+
if (!/^[a-f0-9]{64}$/.test(normalizedRuntimeDigest)) {
|
|
1140
|
+
throw deliveryError(
|
|
1141
|
+
'DELIVERY_RUNTIME_LAYER_DIGEST_INVALID',
|
|
1142
|
+
'Runtime layer digest 必须是 64 位 SHA-256',
|
|
1143
|
+
false
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
if (!/^[a-f0-9]{64}$/.test(normalizedPackageDigest)) {
|
|
1147
|
+
throw deliveryError(
|
|
1148
|
+
'DELIVERY_PACKAGE_DIGEST_INVALID',
|
|
1149
|
+
'package digest 必须是 64 位 SHA-256',
|
|
1150
|
+
false
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
return `pkg-${normalizedRuntimeDigest.slice(0, 20)}-${normalizedPackageDigest.slice(0, 12)}`;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1132
1156
|
function packageSummary(manifest, previousManifest) {
|
|
1133
1157
|
return {
|
|
1134
1158
|
sourceFileCount: layerByKind(manifest, 'source')?.fileCount || 0,
|
|
@@ -1668,6 +1692,7 @@ module.exports = {
|
|
|
1668
1692
|
compactStepResult,
|
|
1669
1693
|
createDeliveryV2Executor,
|
|
1670
1694
|
decorateSteps,
|
|
1695
|
+
deliveryV2RuntimeBuildId,
|
|
1671
1696
|
normalizeLegacyPackageTargetsForExecution,
|
|
1672
1697
|
packageWorkspaceRoot,
|
|
1673
1698
|
prepareAppFinalizeStep,
|
package/lib/policy.js
CHANGED
|
@@ -60,10 +60,14 @@ function validateEngineeringPolicy(policy) {
|
|
|
60
60
|
policy?.release?.deletionsFailClosed !== true ||
|
|
61
61
|
policy?.release?.attemptFencing !== true ||
|
|
62
62
|
policy?.release?.crossMachineBindingReplay !== true ||
|
|
63
|
+
policy?.release?.deterministicRuntimeBuildIdentity !==
|
|
64
|
+
'runtime-layer-plus-package-digest' ||
|
|
65
|
+
policy?.release?.deterministicRuntimeUploadedReleaseReuse !==
|
|
66
|
+
'exact-content-source-parent-only' ||
|
|
63
67
|
policy?.release?.statusAutoResolvesEnvironment !== true
|
|
64
68
|
) {
|
|
65
69
|
errors.push(
|
|
66
|
-
'Delivery V2 必须启用密封构建、精确差异、删除关闭、attempt
|
|
70
|
+
'Delivery V2 必须启用密封构建、精确差异、删除关闭、attempt 隔离、跨机器绑定恢复、Runtime 包级身份与严格恢复,以及环境自动解析'
|
|
67
71
|
);
|
|
68
72
|
}
|
|
69
73
|
const ttl = Number(policy?.ownership?.defaultTtlSeconds);
|
|
@@ -3,7 +3,7 @@ name: openxiangda
|
|
|
3
3
|
description: "Use OpenXiangda for private low-code platform work: app workspaces, forms, pages, resources, functions, automations, workflows, permissions, publishing, deployment, diagnosis, profiles, and the openxiangda CLI."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- OpenXiangda-Policy-Version:
|
|
6
|
+
<!-- OpenXiangda-Policy-Version: 7 -->
|
|
7
7
|
# OpenXiangda
|
|
8
8
|
|
|
9
9
|
OpenXiangda connects an AI coding workspace to the private low-code platform through a normal-user profile and `/openxiangda-api/v1`. External backends using AK/SK are a separate `openxiangda-open-api` flow.
|
|
@@ -37,6 +37,10 @@ Keep the `runId`; failures preserve successful checkpoints. `status` and
|
|
|
37
37
|
`retry` auto-locate preproduction or production when no environment is given.
|
|
38
38
|
Retry fences the previous attempt and safely replays local Form bindings, so it
|
|
39
39
|
can resume from another machine without recreating completed platform writes.
|
|
40
|
+
Deterministic Runtime build IDs combine the Runtime layer and sealed package
|
|
41
|
+
digests, preventing cross-package provenance collisions. An exact same-package
|
|
42
|
+
retry reuses an existing `uploaded` release only after exact
|
|
43
|
+
content/source/parent verification and never overwrites immutable storage.
|
|
40
44
|
|
|
41
45
|
## Keep the agent loop small
|
|
42
46
|
|
|
@@ -30,7 +30,11 @@ unsupported and fail before remote writes. V2 executes authored builds with the
|
|
|
30
30
|
CLI-sealed toolchain, never a workspace `node_modules` or custom builder.
|
|
31
31
|
`status`/`retry` auto-resolve the run environment if omitted. Retry preserves
|
|
32
32
|
completed checkpoints, fences the older attempt, and replays only local Form
|
|
33
|
-
bindings when resuming on a different machine.
|
|
33
|
+
bindings when resuming on a different machine. Deterministic Runtime build IDs
|
|
34
|
+
combine the Runtime layer and sealed package digests, preventing provenance
|
|
35
|
+
collisions across packages with identical Runtime bytes. An `uploaded` release
|
|
36
|
+
is reused only for an exact same-package retry when its content hash, source
|
|
37
|
+
revision, and parent Runtime release match; all other identities fail closed.
|
|
34
38
|
|
|
35
39
|
## Resolve the boundary
|
|
36
40
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "openxiangda_engineering_policy_v1",
|
|
3
|
-
"policyVersion":
|
|
3
|
+
"policyVersion": 7,
|
|
4
4
|
"risk": {
|
|
5
5
|
"levels": ["L0", "L1", "L2", "L3"],
|
|
6
6
|
"quickMaxLevel": "L1",
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"deletionsFailClosed": true,
|
|
40
40
|
"attemptFencing": true,
|
|
41
41
|
"crossMachineBindingReplay": true,
|
|
42
|
+
"deterministicRuntimeBuildIdentity": "runtime-layer-plus-package-digest",
|
|
43
|
+
"deterministicRuntimeUploadedReleaseReuse": "exact-content-source-parent-only",
|
|
42
44
|
"statusAutoResolvesEnvironment": true,
|
|
43
45
|
"v2RequiresSdd": false,
|
|
44
46
|
"v2RequiresGitMainline": false,
|
|
@@ -21,7 +21,7 @@ This is an OpenXiangda React SPA workspace using Delivery V2. See [DELIVERY.md](
|
|
|
21
21
|
## Always
|
|
22
22
|
|
|
23
23
|
- Delivery V2 derives exact scope and does not require SDD, Git clean, `--change`, `--only`, candidate, or ship.
|
|
24
|
-
- Treat `check --json` as the authoritative exact delta and execution plan. Deletions and unsealed third-party build dependencies fail before writes; package execution uses only the CLI toolchain and never links workspace `node_modules`. `status`/`retry` auto-locate the environment, while retry fences the old attempt and replays only cross-machine local Form bindings.
|
|
24
|
+
- Treat `check --json` as the authoritative exact delta and execution plan. Deletions and unsealed third-party build dependencies fail before writes; package execution uses only the CLI toolchain and never links workspace `node_modules`. `status`/`retry` auto-locate the environment, while retry fences the old attempt and replays only cross-machine local Form bindings. Runtime build IDs combine the Runtime layer and sealed package digests; only an exact same-package `uploaded` release with matching content, source, and parent may be reused, and immutable storage is never overwritten.
|
|
25
25
|
- Writes and deploys must pass `--profile <name>` explicitly.
|
|
26
26
|
- Architecture-class work is plan-gated: run `openxiangda design gates --topic <code> --json` and wait for confirmation.
|
|
27
27
|
- Give every task an isolated worktree/branch and one explicit development change; feature worktrees never publish. Merge approved commits to the remote default branch, create one `sdd bundle <release-change> --changes ...`, commit/push it, then publish once from the synchronized clean main/master checkout.
|
|
@@ -21,7 +21,7 @@ This is an OpenXiangda React SPA workspace using Delivery V2. Read [DELIVERY.md]
|
|
|
21
21
|
## Always
|
|
22
22
|
|
|
23
23
|
- Delivery V2 自动计算精确范围,不要求 SDD、Git clean、`--change`、`--only` 或 candidate/ship。
|
|
24
|
-
- `check --json` 是 V2 权威预检:确认精确资源差异和执行计划。删除与未密封第三方构建依赖在写入前失败;包执行只用 CLI 工具链且不链接工作区 `node_modules`。`status`/`retry` 自动定位环境,retry 以 attempt 隔离旧执行器并仅重放跨机器所需的本地 Form 绑定。
|
|
24
|
+
- `check --json` 是 V2 权威预检:确认精确资源差异和执行计划。删除与未密封第三方构建依赖在写入前失败;包执行只用 CLI 工具链且不链接工作区 `node_modules`。`status`/`retry` 自动定位环境,retry 以 attempt 隔离旧执行器并仅重放跨机器所需的本地 Form 绑定。Runtime buildId 同时包含 Runtime layer 与密封包摘要;仅同包 `uploaded` Release 在内容、源码、父版本完全一致时复用,不覆盖不可变存储对象。
|
|
25
25
|
- 发布和写平台资源必须显式传 `--profile <name>`。
|
|
26
26
|
- 架构类需求先跑 `openxiangda design gates --topic <code> --json` 并等用户确认。
|
|
27
27
|
- 每个任务使用独立 worktree/branch 和一个开发 change,但 feature worktree 不发布。批准提交先合并并 push 到远端默认主分支,再创建一个 `sdd bundle <release-change> --changes ...`,从同步且干净的 main/master 一次发布。
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
<!-- OpenXiangda-Policy-Version:
|
|
1
|
+
<!-- OpenXiangda-Policy-Version: 7 -->
|
|
2
2
|
# AGENTS.md — OpenXiangda React SPA 应用工作区
|
|
3
3
|
|
|
4
4
|
本工作区是标准 React 18 + Vite + React Router 应用。默认模板只提供应用壳、账号菜单和一个首页,不是开发验证控制台。
|
|
5
5
|
|
|
6
6
|
## Delivery V2(发布唯一入口)
|
|
7
7
|
|
|
8
|
-
本工作区声明 `deliveryVersion: 2`,正常发布只使用 `openxiangda check`、`openxiangda deploy`、`openxiangda status`、`openxiangda retry`、`openxiangda rollback`,完整约定见 [DELIVERY.md](DELIVERY.md)。V2 按资源指纹计算精确范围,使用不依赖工作区 `node_modules` 的 CLI 密封工具链,并在服务端持久化 ReleaseRun、attempt 和检查点;跨机器重试只重放本地 Form
|
|
8
|
+
本工作区声明 `deliveryVersion: 2`,正常发布只使用 `openxiangda check`、`openxiangda deploy`、`openxiangda status`、`openxiangda retry`、`openxiangda rollback`,完整约定见 [DELIVERY.md](DELIVERY.md)。V2 按资源指纹计算精确范围,使用不依赖工作区 `node_modules` 的 CLI 密封工具链,并在服务端持久化 ReleaseRun、attempt 和检查点;跨机器重试只重放本地 Form 绑定。Runtime buildId 同时包含 Runtime layer 与密封包摘要,避免相同 Runtime 字节在不同包之间发生来源碰撞;同包重试仅在历史 Release 为 `uploaded` 且内容、源码、父版本完全一致时复用,不覆盖不可变存储对象。删除和未密封构建依赖在写入前失败关闭。`status`/`retry` 未指定环境时会自动定位预发或生产。V2 不要求 SDD、Git clean、主线 ancestry、`--change`、`--only` 或生产确认参数。本文后续出现的 `resource publish`、`runtime deploy`、`release publish/ship`、candidate、SDD/mainline 发布门禁均为 V1 底层兼容说明,不得用于 V2 正常发布。
|
|
9
9
|
|
|
10
10
|
## 开发原则
|
|
11
11
|
|
|
@@ -30,7 +30,11 @@ Rules:
|
|
|
30
30
|
dependencies fail preflight.
|
|
31
31
|
- On failure, keep the `runId`. `status` and `retry` auto-locate its environment
|
|
32
32
|
when omitted. Retry preserves successful checkpoints, fences the previous
|
|
33
|
-
attempt, and safely replays local Form bindings on another machine.
|
|
33
|
+
attempt, and safely replays local Form bindings on another machine. Runtime
|
|
34
|
+
build IDs include the Runtime layer and sealed package digests, preventing
|
|
35
|
+
cross-package provenance collisions. A same-package retry reuses an existing
|
|
36
|
+
Runtime release only in `uploaded` state after exact content, source, and
|
|
37
|
+
parent verification; storage is never overwritten.
|
|
34
38
|
- Project `.env`, credentials, private keys, `.git`, `node_modules`, `dist`,
|
|
35
39
|
and generated `.openxiangda` state are never included in the authored source
|
|
36
40
|
layer. Runtime output is a separate immutable layer.
|
|
@@ -27,7 +27,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
|
|
|
27
27
|
|
|
28
28
|
## Always
|
|
29
29
|
|
|
30
|
-
- For `deliveryVersion: 2`, use only Delivery V2 commands. Later V1 SDD/mainline/`--only`/candidate/ship text is compatibility guidance and must not be mixed into V2.
|
|
30
|
+
- For `deliveryVersion: 2`, use only Delivery V2 commands. Runtime build IDs combine the Runtime layer and sealed package digests; retry may reuse only an exact same-package `uploaded` release whose content, source, and parent match. Immutable storage is never overwritten. Later V1 SDD/mainline/`--only`/candidate/ship text is compatibility guidance and must not be mixed into V2.
|
|
31
31
|
- Treat V2 `check --json` as the authoritative exact delta and execution plan. Deletions and unsealed third-party build dependencies fail before writes; package execution uses only the CLI toolchain and never links workspace `node_modules`. `status`/`retry` auto-locate the environment, while retry fences the old attempt and replays only cross-machine local Form bindings.
|
|
32
32
|
- Give every task an isolated worktree/branch and one development change; feature worktrees never publish. Merge approved commits to the remote default branch, create one `sdd bundle`, commit/push it, and publish once from synchronized clean main/master.
|
|
33
33
|
- Close tasks from canonical main only. Review `openxiangda workspace cleanup`, then run `openxiangda workspace cleanup --apply` for that exact hashed `SAFE` plan; it revalidates managed worktrees under the owner lock, never picks up newly-safe entries, and never runs global `git worktree prune`. Review stale records separately.
|
|
@@ -27,7 +27,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
|
|
|
27
27
|
|
|
28
28
|
## Always
|
|
29
29
|
|
|
30
|
-
- `deliveryVersion: 2` 的正常发布只允许 Delivery V2
|
|
30
|
+
- `deliveryVersion: 2` 的正常发布只允许 Delivery V2 命令;Runtime buildId 同时包含 Runtime layer 与密封包摘要,仅同包 `uploaded` Release 在内容、源码、父版本完全一致时复用,不覆盖不可变存储对象;后续 V1 SDD/mainline/`--only`/candidate/ship 内容仅作兼容说明,不得混入 V2。
|
|
31
31
|
- V2 `check --json` 是权威预检:确认精确资源差异和执行计划。删除与未密封第三方构建依赖在写入前失败;包执行只用 CLI 工具链且不链接工作区 `node_modules`。`status`/`retry` 自动定位环境,retry 以 attempt 隔离旧执行器并仅重放跨机器所需的本地 Form 绑定。
|
|
32
32
|
- 每个任务使用独立 worktree/branch 和一个开发 change,但 feature worktree 不发布。批准提交先合并并 push 到远端默认主分支,再创建一个 `sdd bundle`,从同步且干净的 main/master 一次发布。
|
|
33
33
|
- 合并、push、发布和 `release end` 后,回到 canonical 主工作区先审阅 `openxiangda workspace cleanup`,再执行 `openxiangda workspace cleanup --apply` 应用精确哈希计划。CLI 在 owner lock 内复核,只删除已审阅的 managed `SAFE` 项,不顺带处理后来才安全的项,也不执行全局 `git worktree prune`;stale 记录单独人工审阅。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- OpenXiangda-Policy-Version:
|
|
1
|
+
<!-- OpenXiangda-Policy-Version: 7 -->
|
|
2
2
|
# AGENTS.md — OpenXiangda 工作区 AI 强约束
|
|
3
3
|
|
|
4
4
|
> 任何 AI(Qoder / Claude / Codex / Cursor / Copilot 等)在本工作区操作前 **必须先读完本文件**。
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**本工作区声明 `deliveryVersion: 2`。所有“发布 / 上线 / 部署 / publish / deploy / ship / release”请求只使用 `openxiangda check`、`openxiangda deploy`、`openxiangda status`、`openxiangda retry`、`openxiangda rollback`,完整约定见 [DELIVERY.md](DELIVERY.md)。**
|
|
10
10
|
|
|
11
|
-
Delivery V2 自动从期望状态按资源指纹计算精确范围,使用 CLI 密封工具链封存内容寻址 App Package,并在服务端持久化 ReleaseRun、attempt 和检查点;包执行不链接工作区 `node_modules`。`status`/`retry` 可自动定位预发或生产环境,跨机器重试只重放本地 Form
|
|
11
|
+
Delivery V2 自动从期望状态按资源指纹计算精确范围,使用 CLI 密封工具链封存内容寻址 App Package,并在服务端持久化 ReleaseRun、attempt 和检查点;包执行不链接工作区 `node_modules`。`status`/`retry` 可自动定位预发或生产环境,跨机器重试只重放本地 Form 绑定并跳过已完成的平台写入。Runtime buildId 同时包含 Runtime layer 与密封包摘要,避免相同 Runtime 字节在不同包之间发生来源碰撞;同包重试仅在历史 Release 为 `uploaded` 且内容、源码、父版本完全一致时复用,绝不覆盖不可变存储对象。删除和未密封的第三方构建依赖会在远程写入前失败关闭。它不要求 SDD、Git clean、主线 ancestry、`--change`、`--only` 或生产确认参数。本文后续出现的 `workspace publish`、`resource publish`、`runtime deploy`、`release publish/ship`、candidate、SDD/mainline 发布门禁均属于 V1 底层兼容说明,不得用于 V2 正常发布。
|
|
12
12
|
|
|
13
13
|
**架构类需求先过设计门。** 新应用、复杂页面、登录注册、公开访问、权限数据范围、流程自动化、连接器/通知等需求,先 `openxiangda doctor --json` + `openxiangda design gates --topic <code> --json`。只有仍存在会改变实现方向的业务、安全或数据选择时才输出设计并等待确认;用户已经给出具体需求与验收标准时,直接记录结构化 SDD 范围并实现,不再写长篇设计或重复确认。
|
|
14
14
|
|
|
@@ -30,7 +30,11 @@ Rules:
|
|
|
30
30
|
dependencies fail preflight.
|
|
31
31
|
- On failure, keep the `runId`. `status` and `retry` auto-locate its environment
|
|
32
32
|
when omitted. Retry preserves successful checkpoints, fences the previous
|
|
33
|
-
attempt, and safely replays local Form bindings on another machine.
|
|
33
|
+
attempt, and safely replays local Form bindings on another machine. Runtime
|
|
34
|
+
build IDs include the Runtime layer and sealed package digests, preventing
|
|
35
|
+
cross-package provenance collisions. A same-package retry reuses an existing
|
|
36
|
+
Runtime release only in `uploaded` state after exact content, source, and
|
|
37
|
+
parent verification; storage is never overwritten.
|
|
34
38
|
- Project `.env`, credentials, private keys, `.git`, `node_modules`, `dist`,
|
|
35
39
|
and generated `.openxiangda` state are never included in the authored source
|
|
36
40
|
layer. Runtime output is a separate immutable layer.
|