openxiangda 1.0.162 → 1.0.164
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 +6 -2
- package/lib/change-baseline.js +88 -2
- package/lib/cli.js +565 -73
- package/lib/js-code-build.js +120 -8
- package/lib/publish-lease.js +13 -1
- package/lib/release-mainline.js +545 -0
- package/lib/runtime-lineage.js +19 -12
- package/lib/sdd.js +137 -0
- package/lib/workspace-bootstrap.js +36 -7
- package/lib/workspace-init.js +12 -3
- package/lib/worktree-owner.js +13 -0
- package/openxiangda-skills/SKILL.md +9 -3
- package/openxiangda-skills/references/automation-v3.md +1 -1
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +1 -1
- package/openxiangda-skills/references/workflow-v3.md +1 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +8 -2
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +2 -2
- package/package.json +3 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +2 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +2 -2
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +2 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +3 -3
- package/templates/openxiangda-react-spa/AGENTS.md +8 -6
- package/templates/openxiangda-react-spa/scripts/build-js-code.mjs +172 -20
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +19 -39
- package/templates/openxiangda-react-spa/scripts/guard-publish.mjs +4 -5
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +3 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +1 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +3 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +1 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +4 -4
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +172 -20
- package/templates/sy-lowcode-app-workspace/scripts/guard-publish.mjs +4 -5
package/lib/sdd.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { readGitSourceBase } = require('./change-baseline');
|
|
5
|
+
const { assertReleaseSourceIntegrated } = require('./release-mainline');
|
|
5
6
|
|
|
6
7
|
const DEFAULT_SDD_DIR = 'openspec';
|
|
7
8
|
const SDD_CONFIG_FILE = 'config.yaml';
|
|
@@ -326,6 +327,26 @@ function normalizeAffected(input = {}) {
|
|
|
326
327
|
};
|
|
327
328
|
}
|
|
328
329
|
|
|
330
|
+
function mergeAffectedScopes(...inputs) {
|
|
331
|
+
const scopes = inputs.filter(Boolean).map(input => normalizeAffected(input));
|
|
332
|
+
const mergeList = key =>
|
|
333
|
+
Array.from(new Set(scopes.flatMap(scope => scope[key] || []))).sort();
|
|
334
|
+
const hasFlag = key => scopes.some(scope => Boolean(scope[key]));
|
|
335
|
+
return {
|
|
336
|
+
forms: mergeList('forms'),
|
|
337
|
+
pages: mergeList('pages'),
|
|
338
|
+
functions: mergeList('functions'),
|
|
339
|
+
automations: mergeList('automations'),
|
|
340
|
+
workflows: mergeList('workflows'),
|
|
341
|
+
jsCodeNodes: mergeList('jsCodeNodes'),
|
|
342
|
+
files: mergeList('files'),
|
|
343
|
+
resources: hasFlag('resources'),
|
|
344
|
+
runtime: hasFlag('runtime'),
|
|
345
|
+
backend: hasFlag('backend'),
|
|
346
|
+
contracts: hasFlag('contracts'),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
329
350
|
function normalizeQuickScopeFile(value) {
|
|
330
351
|
const file = String(value || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
331
352
|
if (
|
|
@@ -619,6 +640,7 @@ function createTasksContent(meta = {}) {
|
|
|
619
640
|
'## Postpublish',
|
|
620
641
|
'',
|
|
621
642
|
'- [ ] Record publish/deploy result',
|
|
643
|
+
'- [ ] Merge/fast-forward the frozen release SHA and push the authoritative default branch',
|
|
622
644
|
'',
|
|
623
645
|
'## Archive',
|
|
624
646
|
'',
|
|
@@ -644,6 +666,7 @@ function createTasksContent(meta = {}) {
|
|
|
644
666
|
'## Postpublish',
|
|
645
667
|
'',
|
|
646
668
|
'- [ ] Record publish/deploy result',
|
|
669
|
+
'- [ ] Merge/fast-forward the frozen release SHA and push the authoritative default branch',
|
|
647
670
|
'',
|
|
648
671
|
'## Archive',
|
|
649
672
|
'',
|
|
@@ -664,6 +687,7 @@ function createEvidenceContent() {
|
|
|
664
687
|
'## Release',
|
|
665
688
|
'',
|
|
666
689
|
'- [ ] Record publish/deploy command output or explain why this change was not released',
|
|
690
|
+
'- [ ] Record `openxiangda release integration-status` proving the frozen SHA is on the authoritative default branch',
|
|
667
691
|
'',
|
|
668
692
|
'## Bypass Log',
|
|
669
693
|
'',
|
|
@@ -1659,6 +1683,64 @@ function updateReleaseManifest(loaded, releasePlan, verificationStage = 'default
|
|
|
1659
1683
|
return release;
|
|
1660
1684
|
}
|
|
1661
1685
|
|
|
1686
|
+
function recordSddReleaseSourceRevision(options = {}) {
|
|
1687
|
+
const loaded = loadSddChange(options);
|
|
1688
|
+
const input = options.releaseSourceRevision;
|
|
1689
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1690
|
+
throw new Error('releaseSourceRevision 必须是对象');
|
|
1691
|
+
}
|
|
1692
|
+
const releaseSourceRevision = {
|
|
1693
|
+
repo: String(input.repo || input.repositoryId || '').trim(),
|
|
1694
|
+
baseCommit: String(input.baseCommit || '').trim().toLowerCase(),
|
|
1695
|
+
treeHash: String(input.treeHash || '').trim().toLowerCase(),
|
|
1696
|
+
branch: String(input.branch || '').trim() || null,
|
|
1697
|
+
remoteName: String(input.remoteName || '').trim() || null,
|
|
1698
|
+
remoteUrlHash: String(input.remoteUrlHash || '').trim().toLowerCase() || null,
|
|
1699
|
+
mainBranch: String(input.mainBranch || '').trim() || null,
|
|
1700
|
+
mainTipCommit: String(input.mainTipCommit || '').trim().toLowerCase() || null,
|
|
1701
|
+
capturedAt: input.capturedAt || nowIso(),
|
|
1702
|
+
};
|
|
1703
|
+
if (
|
|
1704
|
+
!/^sha256:[0-9a-f]{64}$/i.test(releaseSourceRevision.repo) ||
|
|
1705
|
+
!/^[0-9a-f]{40,64}$/.test(releaseSourceRevision.baseCommit) ||
|
|
1706
|
+
!/^[0-9a-f]{40,64}$/.test(releaseSourceRevision.treeHash) ||
|
|
1707
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(releaseSourceRevision.remoteName || '') ||
|
|
1708
|
+
!/^sha256:[0-9a-f]{64}$/.test(releaseSourceRevision.remoteUrlHash || '') ||
|
|
1709
|
+
!['main', 'master'].includes(releaseSourceRevision.mainBranch)
|
|
1710
|
+
) {
|
|
1711
|
+
throw new Error(
|
|
1712
|
+
'releaseSourceRevision 缺少有效的 repo/baseCommit/treeHash/remoteName/remoteUrlHash/mainBranch'
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const existing = loaded.release?.releaseSourceRevision;
|
|
1716
|
+
if (
|
|
1717
|
+
existing?.baseCommit &&
|
|
1718
|
+
(existing.baseCommit !== releaseSourceRevision.baseCommit ||
|
|
1719
|
+
existing.treeHash !== releaseSourceRevision.treeHash ||
|
|
1720
|
+
existing.repo !== releaseSourceRevision.repo ||
|
|
1721
|
+
existing.remoteName !== releaseSourceRevision.remoteName ||
|
|
1722
|
+
existing.remoteUrlHash !== releaseSourceRevision.remoteUrlHash ||
|
|
1723
|
+
existing.mainBranch !== releaseSourceRevision.mainBranch)
|
|
1724
|
+
) {
|
|
1725
|
+
const error = new Error(
|
|
1726
|
+
`RELEASE_PUBLISH_REVISION_CHANGED: change ${loaded.changeId} 已冻结 ${existing.baseCommit},不能改为 ${releaseSourceRevision.baseCommit}`
|
|
1727
|
+
);
|
|
1728
|
+
error.code = 'RELEASE_PUBLISH_REVISION_CHANGED';
|
|
1729
|
+
throw error;
|
|
1730
|
+
}
|
|
1731
|
+
const release = {
|
|
1732
|
+
...(loaded.release && !loaded.release.__invalid ? loaded.release : {}),
|
|
1733
|
+
schemaVersion: SDD_SCHEMA_VERSION,
|
|
1734
|
+
changeId: loaded.changeId,
|
|
1735
|
+
mainlinePolicy: 'merge-after-publish-v1',
|
|
1736
|
+
releaseSourceRevision,
|
|
1737
|
+
};
|
|
1738
|
+
if (JSON.stringify(release) !== JSON.stringify(loaded.release)) {
|
|
1739
|
+
writeJsonFile(loaded.releaseFile, release);
|
|
1740
|
+
}
|
|
1741
|
+
return release;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1662
1744
|
function validateReleaseManifest(release, releasePlan, legacyMode = false) {
|
|
1663
1745
|
const errors = [];
|
|
1664
1746
|
const warnings = [];
|
|
@@ -1982,6 +2064,60 @@ function syncSddChange(options = {}) {
|
|
|
1982
2064
|
}
|
|
1983
2065
|
|
|
1984
2066
|
function archiveSddChange(options = {}) {
|
|
2067
|
+
const releaseLoaded = loadSddChange(options);
|
|
2068
|
+
const coverageTargets = normalizeCoverageResources(releaseLoaded.coverage, {});
|
|
2069
|
+
const releaseTargets = mergeAffectedScopes(
|
|
2070
|
+
options.targets,
|
|
2071
|
+
options.releasePlan?.targets,
|
|
2072
|
+
releaseLoaded.release?.targets,
|
|
2073
|
+
coverageTargets,
|
|
2074
|
+
releaseLoaded.meta,
|
|
2075
|
+
{ files: options.files },
|
|
2076
|
+
{ files: options.releasePlan?.changedFiles },
|
|
2077
|
+
{ files: releaseLoaded.release?.changedFiles }
|
|
2078
|
+
);
|
|
2079
|
+
const riskAssessment =
|
|
2080
|
+
releaseLoaded.meta?.riskMetadata?.assessment ||
|
|
2081
|
+
releaseLoaded.meta?.riskAssessment ||
|
|
2082
|
+
{};
|
|
2083
|
+
const metadataDeclaresHighRisk =
|
|
2084
|
+
['l2', 'l3'].includes(String(releaseLoaded.meta?.riskTier || '').toLowerCase()) ||
|
|
2085
|
+
['high', 'critical'].includes(
|
|
2086
|
+
String(releaseLoaded.meta?.riskLevel || '').toLowerCase()
|
|
2087
|
+
) ||
|
|
2088
|
+
[
|
|
2089
|
+
'schema',
|
|
2090
|
+
'permission',
|
|
2091
|
+
'auth',
|
|
2092
|
+
'publicAccess',
|
|
2093
|
+
'dataMigration',
|
|
2094
|
+
'destructive',
|
|
2095
|
+
'crossResource',
|
|
2096
|
+
].some(key => riskAssessment[key] === true);
|
|
2097
|
+
const requiresMainlineClosure =
|
|
2098
|
+
hasHighRiskTargets(releaseTargets) ||
|
|
2099
|
+
Boolean(releaseTargets.backend) ||
|
|
2100
|
+
Boolean(releaseTargets.contracts) ||
|
|
2101
|
+
classifyHighRiskFiles(releaseTargets.files).length > 0 ||
|
|
2102
|
+
metadataDeclaresHighRisk ||
|
|
2103
|
+
Boolean(releaseLoaded.release?.mainlinePolicy) ||
|
|
2104
|
+
Boolean(releaseLoaded.release?.releaseSourceRevision);
|
|
2105
|
+
if (requiresMainlineClosure) {
|
|
2106
|
+
if (
|
|
2107
|
+
!releaseLoaded.release ||
|
|
2108
|
+
releaseLoaded.release.__invalid ||
|
|
2109
|
+
!releaseLoaded.release.releaseSourceRevision
|
|
2110
|
+
) {
|
|
2111
|
+
const error = new Error(
|
|
2112
|
+
'SDD_RELEASE_SOURCE_LINEAGE_REQUIRED: 高风险 change 归档必须保留 release.json 的冻结发布提交;--force 不能绕过主分支回合证据'
|
|
2113
|
+
);
|
|
2114
|
+
error.code = 'SDD_RELEASE_SOURCE_LINEAGE_REQUIRED';
|
|
2115
|
+
throw error;
|
|
2116
|
+
}
|
|
2117
|
+
assertReleaseSourceIntegrated(releaseLoaded.release.releaseSourceRevision, {
|
|
2118
|
+
cwd: options.cwd || process.cwd(),
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
1985
2121
|
const verify = verifySddChange({ ...options, stage: 'archive' });
|
|
1986
2122
|
if (!verify.passed && !options.force) {
|
|
1987
2123
|
const first = verify.errors[0];
|
|
@@ -2032,6 +2168,7 @@ module.exports = {
|
|
|
2032
2168
|
normalizeSddVerifyStage,
|
|
2033
2169
|
proposeQuickSddChange,
|
|
2034
2170
|
proposeSddChange,
|
|
2171
|
+
recordSddReleaseSourceRevision,
|
|
2035
2172
|
approveSddChange,
|
|
2036
2173
|
syncSddChange,
|
|
2037
2174
|
validateQuickSddScope,
|
|
@@ -2,10 +2,20 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
const ROOT_DIR = path.join(__dirname, '..');
|
|
5
|
-
const
|
|
5
|
+
const LEGACY_TEMPLATE_DIR = path.join(
|
|
6
|
+
ROOT_DIR,
|
|
7
|
+
'templates',
|
|
8
|
+
'sy-lowcode-app-workspace'
|
|
9
|
+
);
|
|
10
|
+
const REACT_SPA_TEMPLATE_DIR = path.join(
|
|
11
|
+
ROOT_DIR,
|
|
12
|
+
'templates',
|
|
13
|
+
'openxiangda-react-spa'
|
|
14
|
+
);
|
|
15
|
+
const TEMPLATE_DIR = LEGACY_TEMPLATE_DIR;
|
|
6
16
|
|
|
7
17
|
// AI 引导/守卫"四件套 + glob rules"清单。
|
|
8
|
-
//
|
|
18
|
+
// 这些路径相对于 workspace 根;源模板必须与现有 workspace runtime 匹配。
|
|
9
19
|
const BOOTSTRAP_FILES = [
|
|
10
20
|
'AGENTS.md',
|
|
11
21
|
'scripts/guard-publish.mjs',
|
|
@@ -36,15 +46,18 @@ function bootstrapWorkspace(options = {}) {
|
|
|
36
46
|
const targetDir = path.resolve(options.dir || process.cwd());
|
|
37
47
|
const force = Boolean(options.force);
|
|
38
48
|
const dryRun = Boolean(options.dryRun);
|
|
49
|
+
const templateDir = resolveWorkspaceTemplateDir(targetDir, options.runtime);
|
|
39
50
|
|
|
40
|
-
if (!fs.existsSync(
|
|
41
|
-
throw new Error(`workspace 模板不存在: ${
|
|
51
|
+
if (!fs.existsSync(templateDir)) {
|
|
52
|
+
throw new Error(`workspace 模板不存在: ${templateDir}`);
|
|
42
53
|
}
|
|
43
54
|
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
44
55
|
throw new Error(`目标目录不存在或不是目录: ${targetDir}`);
|
|
45
56
|
}
|
|
46
57
|
|
|
47
|
-
const fileOps = BOOTSTRAP_FILES.map(rel =>
|
|
58
|
+
const fileOps = BOOTSTRAP_FILES.map(rel =>
|
|
59
|
+
planFileOp(rel, targetDir, force, templateDir)
|
|
60
|
+
);
|
|
48
61
|
const packageJsonOp = planPackageJsonOp(targetDir, force);
|
|
49
62
|
|
|
50
63
|
if (!dryRun) {
|
|
@@ -61,6 +74,7 @@ function bootstrapWorkspace(options = {}) {
|
|
|
61
74
|
|
|
62
75
|
return {
|
|
63
76
|
targetDir,
|
|
77
|
+
runtime: templateDir === REACT_SPA_TEMPLATE_DIR ? 'react-spa' : 'legacy',
|
|
64
78
|
dryRun,
|
|
65
79
|
force,
|
|
66
80
|
files: fileOps.map(op => ({
|
|
@@ -79,8 +93,8 @@ function bootstrapWorkspace(options = {}) {
|
|
|
79
93
|
};
|
|
80
94
|
}
|
|
81
95
|
|
|
82
|
-
function planFileOp(relativePath, targetDir, force) {
|
|
83
|
-
const sourcePath = path.join(
|
|
96
|
+
function planFileOp(relativePath, targetDir, force, templateDir = TEMPLATE_DIR) {
|
|
97
|
+
const sourcePath = path.join(templateDir, relativePath);
|
|
84
98
|
const targetPath = path.join(targetDir, relativePath);
|
|
85
99
|
if (!fs.existsSync(sourcePath)) {
|
|
86
100
|
return {
|
|
@@ -126,6 +140,20 @@ function planFileOp(relativePath, targetDir, force) {
|
|
|
126
140
|
};
|
|
127
141
|
}
|
|
128
142
|
|
|
143
|
+
function resolveWorkspaceTemplateDir(targetDir, explicitRuntime) {
|
|
144
|
+
const normalized = String(explicitRuntime || '').trim().toLowerCase();
|
|
145
|
+
if (['react-spa', 'spa'].includes(normalized)) return REACT_SPA_TEMPLATE_DIR;
|
|
146
|
+
if (['legacy', 'classic'].includes(normalized)) return LEGACY_TEMPLATE_DIR;
|
|
147
|
+
const configFile = path.join(targetDir, 'app-workspace.config.ts');
|
|
148
|
+
if (fs.existsSync(configFile)) {
|
|
149
|
+
const configText = fs.readFileSync(configFile, 'utf8');
|
|
150
|
+
if (/runtimeMode\s*:\s*["']react-spa["']/i.test(configText)) {
|
|
151
|
+
return REACT_SPA_TEMPLATE_DIR;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return LEGACY_TEMPLATE_DIR;
|
|
155
|
+
}
|
|
156
|
+
|
|
129
157
|
function planPackageJsonOp(targetDir, force) {
|
|
130
158
|
const targetPath = path.join(targetDir, 'package.json');
|
|
131
159
|
const relativePath = 'package.json';
|
|
@@ -235,4 +263,5 @@ module.exports = {
|
|
|
235
263
|
BOOTSTRAP_FILES,
|
|
236
264
|
PACKAGE_JSON_GUARD_SCRIPTS,
|
|
237
265
|
bootstrapWorkspace,
|
|
266
|
+
resolveWorkspaceTemplateDir,
|
|
238
267
|
};
|
package/lib/workspace-init.js
CHANGED
|
@@ -96,7 +96,7 @@ function initWorkspace(options = {}) {
|
|
|
96
96
|
installedDependencies: install,
|
|
97
97
|
bound,
|
|
98
98
|
sdd,
|
|
99
|
-
nextSteps: buildNextSteps(targetDir, install, bound),
|
|
99
|
+
nextSteps: buildNextSteps(targetDir, install, bound, runtime),
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -173,7 +173,7 @@ function runInstall(targetDir) {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
function buildNextSteps(targetDir, installedDependencies, bound) {
|
|
176
|
+
function buildNextSteps(targetDir, installedDependencies, bound, runtime) {
|
|
177
177
|
const steps = [`cd ${targetDir}`];
|
|
178
178
|
if (!installedDependencies) steps.push('pnpm install');
|
|
179
179
|
if (!bound) {
|
|
@@ -181,7 +181,16 @@ function buildNextSteps(targetDir, installedDependencies, bound) {
|
|
|
181
181
|
}
|
|
182
182
|
const profileArg = bound ? bound.profile : '<name>';
|
|
183
183
|
steps.push(`openxiangda env --profile ${profileArg}`);
|
|
184
|
-
steps.push(
|
|
184
|
+
steps.push('git switch -c codex/<change>');
|
|
185
|
+
steps.push('openxiangda sdd propose <change>');
|
|
186
|
+
steps.push(
|
|
187
|
+
runtime === 'react-spa'
|
|
188
|
+
? `openxiangda runtime deploy --no-activate --change <change> --profile ${profileArg}`
|
|
189
|
+
: `openxiangda workspace publish --dry-run --only <exact-codes> --change <change> --profile ${profileArg}`
|
|
190
|
+
);
|
|
191
|
+
steps.push(
|
|
192
|
+
`发布后将冻结提交 merge/fast-forward 并 push 到主分支,再运行 openxiangda release end --profile ${profileArg}`
|
|
193
|
+
);
|
|
185
194
|
return steps;
|
|
186
195
|
}
|
|
187
196
|
|
package/lib/worktree-owner.js
CHANGED
|
@@ -99,6 +99,19 @@ function releaseWorktreeOwner(options = {}) {
|
|
|
99
99
|
return withOwnerLock(file, () => {
|
|
100
100
|
const current = readOwnerFile(file);
|
|
101
101
|
if (!current) return { released: false, active: false, file };
|
|
102
|
+
const expectedThreadId = text(options.expectedThreadId);
|
|
103
|
+
const expectedChangeId = text(options.expectedChangeId);
|
|
104
|
+
if (
|
|
105
|
+
(expectedThreadId && text(current.threadId) !== expectedThreadId) ||
|
|
106
|
+
(expectedChangeId && text(current.changeId) !== expectedChangeId)
|
|
107
|
+
) {
|
|
108
|
+
const error = new Error(
|
|
109
|
+
'WORKTREE_OWNER_STATE_CHANGED: worktree owner 与待释放的精确任务/change 不一致,禁止清理'
|
|
110
|
+
);
|
|
111
|
+
error.code = 'WORKTREE_OWNER_STATE_CHANGED';
|
|
112
|
+
error.owner = current;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
102
115
|
const foreign =
|
|
103
116
|
identity.threadId &&
|
|
104
117
|
current.threadId &&
|
|
@@ -26,11 +26,13 @@ This file is a router and safety card. Read only the one or two subskills select
|
|
|
26
26
|
## Scope before work
|
|
27
27
|
|
|
28
28
|
1. Work from the app workspace root and pass `--profile <name>` to every write or release command.
|
|
29
|
-
2. In a shared or dirty checkout, use an isolated Git worktree/branch. `sdd propose|quick|context` automatically claims the current worktree for `CODEX_THREAD_ID`; if another task owns it, stop and move to a separate worktree instead of taking over its files.
|
|
29
|
+
2. In a shared or dirty checkout, use an isolated Git worktree/branch. `sdd propose|quick|context` automatically claims the current worktree for `CODEX_THREAD_ID`; if another task owns it, stop and move to a separate worktree instead of taking over its files. A release is not closed until its exact frozen commit has been merged (without squash/rebase) and pushed to the authoritative default branch.
|
|
30
30
|
3. Give the task one stable SDD change id. Use `--change <id>` on context, plan, check, verify, publish, and archive commands when supported.
|
|
31
31
|
4. Plan and publish exact resource codes with `--only <codes>` or `--code <code>`; a type-only or full-resource publish is allowed only when the dependency closure intentionally contains the whole type/application.
|
|
32
32
|
5. A React SPA page change rebuilds one application runtime. Commit the complete build input first, upload/preview with `runtime deploy --no-activate`, and promote only from a clean merged release head that descends from the online Runtime source revision.
|
|
33
33
|
|
|
34
|
+
An exact Function/Automation selector is an early scope boundary: unrelated manifests, source dependency graphs, and JS_CODE targets must not be read, typechecked, or built. Transitive/shared/ambient dependencies of the selected entry remain included. No selector intentionally keeps full-workspace behavior.
|
|
35
|
+
|
|
34
36
|
Never infer a release scope from a globally dirty `git status` when multiple changes are present. Never publish a temporary source tree that differs from the reviewed patch.
|
|
35
37
|
|
|
36
38
|
## Risk and approval
|
|
@@ -79,12 +81,16 @@ openxiangda sdd verify <change> --changed --stage prepublish
|
|
|
79
81
|
openxiangda release begin --change <change> --profile <name>
|
|
80
82
|
openxiangda resource publish <type> --only <codes> --change <change> --profile <name>
|
|
81
83
|
openxiangda runtime activate <reviewedReleaseId> --change <change> --profile <name>
|
|
84
|
+
# Merge/fast-forward the frozen release commit into the remote default branch.
|
|
85
|
+
openxiangda release integration-status --profile <name>
|
|
82
86
|
openxiangda release end --profile <name>
|
|
83
87
|
```
|
|
84
88
|
|
|
85
89
|
`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.
|
|
86
90
|
|
|
87
|
-
`release begin --change` freezes the
|
|
91
|
+
`release begin --change` separately freezes the change base and the actual publish source: a clean committed `HEAD` on a named branch that already contains the current authoritative remote default branch. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A worktree based on obsolete source fails with `SOURCE_BASE_DIVERGED`/`RELEASE_SOURCE_BEHIND_MAIN`; a selected remote field changed after the baseline fails with `RESOURCE_FIELD_CONFLICT`. Resolve/merge, rebuild, and re-plan instead of forcing the old release.
|
|
92
|
+
|
|
93
|
+
After promotion, immediately merge or fast-forward the exact frozen publish commit into the authoritative remote default branch and push it. `release integration-status` reports the result; normal `release end` refuses with `RELEASE_MAIN_MERGE_PENDING` until the remote mainline contains that exact SHA, so squash/rebase merges do not count. The authoritative target comes only from the live remote default `main`/`master`; local-only refs, overrides, and lease expiry cannot clear this obligation. Archive the SDD change from the updated mainline.
|
|
88
94
|
|
|
89
95
|
`runtime deploy` separately freezes the clean committed `HEAD` as `sourceRevision` together with the current active Runtime parent before any build or upload. Any deploy whose source does not descend from the online Runtime fails with `RUNTIME_SOURCE_BASE_DIVERGED`, including `--no-activate`; this prevents staging an old preview for later activation. `--no-build` does not bypass this guard. Only an intentional audited rollback may use `--allow-runtime-rollback --reason "<at least 8 characters>"`, and that flag never permits dirty or non-Git input. SDD evidence under `openspec/` and generated/state paths do not make Runtime source dirty.
|
|
90
96
|
|
|
@@ -92,7 +98,7 @@ For a whole-app release, keep changed Runtime/Page/Backend/Form children staged
|
|
|
92
98
|
|
|
93
99
|
When a Function or Automation is selected because its TypeScript source changed, publishing is source-only by default. Eligible existing Function/Automation updates in the same command use one immutable Backend Release (`prepare -> verify -> activate`) and one database transaction, so a stale member produces zero resource writes and noops do not advance versions/timestamps. Online bindings, input/output contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged. Only an explicit Backend Release `head` HTTP 404 permits the warned compatibility source PATCH. A complete manifest replacement is exceptional and requires exact `--only/--code` plus `--replace-manifest --reason "..."`.
|
|
94
100
|
|
|
95
|
-
The lease is app-level promotion ownership, while worktree ownership prevents two Codex tasks from editing/publishing through the same source directory. Different worktrees may keep developing and validating.
|
|
101
|
+
The lease is app-level promotion ownership, while worktree ownership prevents two Codex tasks from editing/publishing through the same source directory. Different worktrees may keep developing and validating. After promotion, merge/push the frozen SHA and run normal `release end`; an expired lease still retains reconciliation evidence. Use full resource/runtime publish only when the approved dependency closure is app-wide.
|
|
96
102
|
|
|
97
103
|
## Always
|
|
98
104
|
|
|
@@ -287,7 +287,7 @@ Use JS_CODE V2 when the script is local to one automation graph node.
|
|
|
287
287
|
}
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
Author source in `src/js-code-nodes/<scriptCode>/index.ts`. AI-authored source must be TypeScript. Build with `pnpm build-js-code --script <scriptCode>`; the
|
|
290
|
+
Author source in `src/js-code-nodes/<scriptCode>/index.ts`. AI-authored source must be TypeScript. Build with `pnpm build-js-code --script <scriptCode>`; the explicit selector typechecks only that entry plus its transitive/shared/ambient dependencies before bundling, while an unscoped build validates the whole workspace. During validate/create, the CLI uploads the generated bundle, replaces `sourceFile.localPath` with `{ bucketName, objectName, sha256, ... }`, and the backend verifies sha256 before execution.
|
|
291
291
|
|
|
292
292
|
The backend runs the snapshot in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`. Scripts may use `export default async function (ctx) {}`, `module.exports = async (ctx) => {}`, `require`, `process`, `Buffer`, arbitrary HTTP, and `platform.api` for `/openxiangda-api/v1`.
|
|
293
293
|
|
|
@@ -754,7 +754,7 @@ export default async function (ctx) {
|
|
|
754
754
|
}
|
|
755
755
|
```
|
|
756
756
|
|
|
757
|
-
构建:`pnpm build-js-code --script sync_customer
|
|
757
|
+
构建:`pnpm build-js-code --script sync_customer`(只校验该入口及其传递/shared/ambient 依赖,再打包到 `dist/js-code-nodes/<code>/index.cjs`;仅无 selector 时全量校验)。CLI validate/create/publish 时会上传快照、用 `{ bucketName, objectName, sha256 }` 替换 `sourceFile.localPath`。
|
|
758
758
|
|
|
759
759
|
`ctx.methods.*` 仍可用于兼容旧脚本;新脚本优先使用 `ctx.resources`、`ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.platform.roles` 和 `ctx.platform.api`。
|
|
760
760
|
|
|
@@ -371,7 +371,7 @@ File snapshot:
|
|
|
371
371
|
}
|
|
372
372
|
```
|
|
373
373
|
|
|
374
|
-
AI-authored JS_CODE source must be TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`. When validating or creating, the CLI runs `pnpm build-js-code --script <scriptCode>`, which
|
|
374
|
+
AI-authored JS_CODE source must be TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`. When validating or creating, the CLI runs `pnpm build-js-code --script <scriptCode>`, which typechecks only the selected entry plus its transitive/shared/ambient dependencies, bundles to `dist/js-code-nodes/<scriptCode>/index.cjs`, uploads the bundle, and replaces `sourceFile.localPath` with immutable snapshot metadata. An intentionally unscoped build retains full-workspace TypeScript validation. The backend verifies snapshot `sha256`, runs it in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores console/runtime logs in the execution record, and writes the returned value to the node output and `variables.node_<nodeId>`.
|
|
375
375
|
|
|
376
376
|
For reusable backend logic that should be shared by pages, automations, and workflows, prefer an App Function under `src/functions/<functionCode>/index.ts` plus `src/resources/functions/<functionCode>.json`. Workflow graphs that support App Function nodes can call it with:
|
|
377
377
|
|
|
@@ -24,7 +24,7 @@ openxiangda sdd context --change <change> --changed --json
|
|
|
24
24
|
|
|
25
25
|
Run `openxiangda update check --json` once per substantial task, on a suspected mismatch, or when the cached check is older than one day. If an update is installed, refresh skills once with `openxiangda skill install --force`.
|
|
26
26
|
|
|
27
|
-
Every write/release command must include `--profile <name>`. In a shared checkout, create an isolated Git worktree before editing. `sdd propose|quick|context` claims `.openxiangda/worktree-owner.json` for the current `CODEX_THREAD_ID`; a foreign owner is a hard stop. `.openxiangda/state.json` maps profile-local IDs; it is not a collaboration lock.
|
|
27
|
+
Every write/release command must include `--profile <name>`. In a shared checkout, create an isolated Git worktree and named feature branch before editing. `sdd propose|quick|context` claims `.openxiangda/worktree-owner.json` for the current `CODEX_THREAD_ID`; a foreign owner is a hard stop. `.openxiangda/state.json` maps profile-local IDs; it is not a collaboration lock. The task is not complete until the exact frozen publish SHA is merged without squash/rebase and pushed to the authoritative default branch.
|
|
28
28
|
|
|
29
29
|
First-class direct configuration writes (form/page/menu/role/permission/workflow/connector/notification/auth/route/public-access/scope/data-view/storage/settings) also require `--change <id>` or a complete stored session created by `openxiangda release begin --change <id>`. Missing context fails locally with `PUBLISH_CONTEXT_REQUIRED` before any HTTP write. Dry-run, schema planning, validation, query/invoke/export/submit/execution/upload, and other data-plane actions do not acquire a lease. Direct Workflow publish/unpublish/delete additionally requires the frozen `--expected-revision`; never fetch the latest revision merely to replay an older payload.
|
|
30
30
|
|
|
@@ -53,6 +53,8 @@ openxiangda resource publish function --only function_a,function_b --profile <na
|
|
|
53
53
|
|
|
54
54
|
`--code <code>` is the single-resource equivalent of `--only <codes>`. Type-only planning is appropriate for an intentional shared dependency closure. Unscoped full-resource planning/publishing is app-wide and requires an app-wide approved change.
|
|
55
55
|
|
|
56
|
+
Selectors apply before manifest parsing, source dependency analysis, and JS_CODE typecheck/build. A scoped Function/Automation plan must touch only the selected targets plus their transitive/shared/ambient dependencies; an unscoped plan intentionally retains full-workspace behavior. Resource commands use the canonical scoped builder packaged with the installed CLI for standard workspaces, so old checked-in builders still receive the optimization; refresh/bootstrap the workspace script only for equivalent manual `pnpm build-js-code` behavior. Nonstandard custom builders remain an explicit compatibility fallback.
|
|
57
|
+
|
|
56
58
|
Source-triggered Function/Automation targets use one immutable Backend Release by default. The CLI uploads every eligible existing source-only target, freezes the current Backend Release parent plus Git/change baseline, then runs `prepare -> verify -> activate`; activation first CAS-checks the entire set and applies all updates in one transaction. Never fall back to sequential PATCH after prepare/verify/activate has started. Compatibility fallback is allowed only when the Backend Release `head` feature probe is an explicit HTTP 404, and must warn. Existing online bindings, contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged; noops do not advance resource versions/timestamps. A deliberate whole-definition replacement requires exact `--only/--code` and `--replace-manifest --reason "<why>"`; SDD bypass does not imply replacement authority.
|
|
57
59
|
|
|
58
60
|
Use `openxiangda release backend-head|backend-list|backend-detail|backend-diff` to inspect immutable history. `backend-rollback <releaseId> --change <change> --reason "..."` prepares, verifies, and activates a new release from the historical snapshot; it never mutates or directly reactivates the old row. `backend-abort` stops prepared/verified work and `backend-retry` retries only recorded post-commit side effects. Do not fetch newer revisions to replay an older payload.
|
|
@@ -84,10 +86,12 @@ openxiangda sdd verify <change> --changed --stage prepublish
|
|
|
84
86
|
openxiangda release begin --change <change> --profile <name>
|
|
85
87
|
openxiangda resource publish <type> --only <codes> --change <change> --profile <name>
|
|
86
88
|
openxiangda runtime activate <reviewedReleaseId> --change <change> --profile <name>
|
|
89
|
+
# Merge/fast-forward the frozen release SHA to the remote default branch.
|
|
90
|
+
openxiangda release integration-status --profile <name>
|
|
87
91
|
openxiangda release end --profile <name>
|
|
88
92
|
```
|
|
89
93
|
|
|
90
|
-
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed `HEAD`. Before any build/upload, every Runtime deploy acquires the app publish lease and freezes that `sourceRevision` plus the current active Runtime parent. Both staged and activating deploys are rejected when the local source does not descend from the online source revision, including with `--no-build`; an intentional rollback requires `--allow-runtime-rollback --reason "<at least 8 characters>"` and remains auditable. SDD evidence in `openspec/` and generated/state directories are excluded from the source-clean check. `release begin --change` obtains app-level promotion ownership
|
|
94
|
+
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed `HEAD`. Before any build/upload, every Runtime deploy acquires the app publish lease and freezes that `sourceRevision` plus the current active Runtime parent. Both staged and activating deploys are rejected when the local source does not descend from the online source revision, including with `--no-build`; an intentional rollback requires `--allow-runtime-rollback --reason "<at least 8 characters>"` and remains auditable. SDD evidence in `openspec/` and generated/state directories are excluded from the source-clean check. `release begin --change` obtains app-level promotion ownership, freezes the actual clean publish `HEAD` independently from the earlier change base, and requires it to contain the current remote default branch. Before the first live write, one preflight validates the complete target set. Long asynchronous publish phases run a 30-second lease heartbeat; `PUBLISH_LEASE_LOST` is a hard stop and no later write may run. Synchronous Runtime builds first extend to the platform maximum TTL and must renew successfully before the next write. Do not move Runtime build ahead of lease/upload-plan: direct-storage releases compile the provider-specific `assetBaseUrl` returned by that protected plan, and silently substituting the gateway URL changes chunk/publicPath behavior. Parallel development remains available in other worktrees. After promotion, merge/fast-forward and push the frozen SHA to the authoritative default branch, check `release integration-status`, then run normal `release end`. The target is frozen from the live remote default `main`/`master`; overrides, local-only refs, and lease expiry cannot clear the pending-mainline evidence.
|
|
91
95
|
|
|
92
96
|
`resource plan`, `resource publish`, and `runtime deploy` report coarse phases on stderr and emit a heartbeat after 15 seconds without completion. JSON stdout stays machine-readable and includes `timings`; use it to identify whether time is spent in plan, preflight, build, upload, write, or activate instead of rerunning an opaque command.
|
|
93
97
|
|
|
@@ -125,6 +129,8 @@ openxiangda workspace bind --profile dev --app-type APP_XXXX
|
|
|
125
129
|
- `RUNTIME_SOURCE_DIRTY` or `RUNTIME_SOURCE_LINEAGE_REQUIRED`: commit all build inputs in an auditable Git repository; generated `dist/` and `.openxiangda/` state do not count, and `--no-build` is not an escape hatch.
|
|
126
130
|
- `PUBLISH_LEASE_HELD`: another change owns promotion; keep developing/testing locally or wait for its release/expiry. Never bypass it with an unleased write.
|
|
127
131
|
- `PUBLISH_LEASE_LOST`: heartbeat renewal failed or the lease expired during a long phase; stop immediately, inspect the active head, re-plan, and start a new release. Never replay the remaining writes with a newly fetched revision.
|
|
132
|
+
- `RELEASE_SOURCE_DIRTY`, `RELEASE_SOURCE_BEHIND_MAIN`, or `RELEASE_PUBLISH_REVISION_CHANGED`: commit all build inputs, merge the current remote mainline into the feature branch, and restart the release from the tested SHA.
|
|
133
|
+
- `RELEASE_MAIN_MERGE_PENDING`: the live release SHA is not yet contained in the authoritative remote default branch. Merge without squash/rebase, push, rerun `release integration-status`, then `release end`.
|
|
128
134
|
- `WORKTREE_OWNED_BY_ANOTHER_TASK`: another Codex task owns this source directory; switch to an isolated worktree. Only force-claim with an explicit reason after confirming the original task ended.
|
|
129
135
|
- Plan unexpectedly includes unrelated resources: do not publish; correct `--change`/`--only` or dependency closure.
|
|
130
136
|
- A write updates state: let the CLI use its lock/atomic merge; do not hand-edit state or run conflicting legacy clients.
|
|
@@ -29,7 +29,7 @@ Create a workflow only when the scenario has **real approval semantics**: approv
|
|
|
29
29
|
- ✅ Delayed approval start: save the process-form record first with `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`, then start approval in place with `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`.
|
|
30
30
|
- ✅ Return-to-initiator flows: use `returnPolicy: { scopeType: "initiator", resubmitMode: "resume_current" | "replay" }` or `flow.action.returnToInitiator()`. The runtime capabilities protocol exposes `resubmit` only for pending `originator_return` tasks; return to a previous approval node continues through the normal `approve` action.
|
|
31
31
|
- ✅ Reusable backend business logic: prefer **App Function** (`src/functions/<functionCode>/index.ts` + `src/resources/functions/<functionCode>.json`), then call it from page `sdk.function.invoke` or automation/workflow `function_call`.
|
|
32
|
-
- ✅ JS_CODE V2 trusted_node: source in TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. Run `pnpm build-js-code --script <code
|
|
32
|
+
- ✅ JS_CODE V2 trusted_node: source in TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. Run `pnpm build-js-code --script <code>`; an explicit selector validates/builds only that entry plus its transitive/shared/ambient dependencies, while an unscoped command validates the whole workspace.
|
|
33
33
|
- ✅ Use `trigger_v2` for new automation triggers; CLI fills root `appType` / `formUuid` from active profile when `--form-code` is provided.
|
|
34
34
|
- ✅ Use logical `workflowCode` / `automationCode` locally; live IDs are profile-isolated under `.openxiangda/state.json`.
|
|
35
35
|
- ✅ `ctx.logger.debug/info/warn/error(message, data?)` at every important step — inspect via `automation executions` / `automation logs` / `automation diagnose`.
|
|
@@ -112,7 +112,7 @@ For new AI-authored workflows where users do not need canvas editing, prefer com
|
|
|
112
112
|
Do not use JS_CODE for simple UI interactions, ordinary form validation, display-only page behavior, or logic that belongs in a normal React code page. For non-trivial backend logic, prefer JS_CODE V2 trusted Node scripts over large inline snippets. AI-authored JS_CODE source must be TypeScript:
|
|
113
113
|
|
|
114
114
|
1. Put source in `src/js-code-nodes/<scriptCode>/index.ts`.
|
|
115
|
-
2. Run `pnpm build-js-code --script <scriptCode>`.
|
|
115
|
+
2. Run `pnpm build-js-code --script <scriptCode>`. In a refreshed workspace this command typechecks only the selected entry and its transitive/shared/ambient dependencies, then bundles after validation passes; omit the selector only for an intentional full-workspace validation. Installed CLI resource commands independently use their packaged canonical scoped builder, including against standard older workspaces.
|
|
116
116
|
3. In workflow or automation JSON, use:
|
|
117
117
|
```json
|
|
118
118
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.164",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"build:sdk": "tsup --config packages/sdk/tsup.config.ts",
|
|
64
64
|
"check": "node --check bin/openxiangda.js && node --check lib/*.js && node --check packages/sdk/src/build-source/src/cli.mjs && node --check packages/sdk/src/build-source/scripts/*.mjs && node --check packages/sdk/src/build-source/scripts/utils/*.mjs",
|
|
65
65
|
"test": "npm run check && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs",
|
|
66
|
+
"prepublishOnly": "node scripts/release-mainline-guard.mjs",
|
|
66
67
|
"prepack": "npm run build:sdk",
|
|
67
68
|
"test:profile-isolation": "bash scripts/profile-isolation-smoke.sh",
|
|
68
69
|
"test:resource-plan": "node scripts/resource-plan-smoke.mjs",
|
|
@@ -84,6 +85,7 @@
|
|
|
84
85
|
"test:publish-lease-state": "node scripts/publish-lease-state-smoke.mjs",
|
|
85
86
|
"test:publish-lease-heartbeat": "node scripts/publish-lease-heartbeat-smoke.mjs",
|
|
86
87
|
"test:release-telemetry": "node scripts/release-telemetry-smoke.mjs",
|
|
88
|
+
"test:release-mainline": "node scripts/release-mainline-smoke.mjs",
|
|
87
89
|
"test:publish-lease-cli": "node scripts/publish-lease-cli-smoke.mjs",
|
|
88
90
|
"test:publish-context": "node scripts/publish-context-smoke.mjs",
|
|
89
91
|
"test:backend-release-cli": "node scripts/backend-release-cli-smoke.mjs",
|
|
@@ -24,6 +24,7 @@ openxiangda resource typegen --profile <name>
|
|
|
24
24
|
- Guest access to forms, dataViews, functions, and connectors requires explicit policy `grants`.
|
|
25
25
|
- Connector secrets and third-party credentials belong in the platform backend, never in manifests or page source.
|
|
26
26
|
- Formal changes should keep Git as the source of truth: edit manifests, validate, plan, then publish.
|
|
27
|
+
- Exact selectors apply before manifest/source analysis and JS_CODE build: touch only selected targets plus transitive/shared/ambient dependencies; omit selectors only for intentional full-workspace work.
|
|
27
28
|
- `resource plan` and publish dry-runs are GET/HEAD-only. On `READ_ONLY_AUTH_REQUIRED`, run `openxiangda auth refresh --profile <name>` or log in again before retrying; never refresh inside the plan.
|
|
28
29
|
- Source-triggered Function/Automation publishing patches only source fields on the server and preserves online bindings/contracts/metadata/state. Whole-manifest replacement requires `--replace-manifest --reason "..."`.
|
|
29
|
-
- Formal promotion
|
|
30
|
+
- Formal promotion freezes the clean publish HEAD separately from the change base, preflights the complete set, and rejects `SOURCE_BASE_DIVERGED` / `RELEASE_SOURCE_BEHIND_MAIN` / `RESOURCE_FIELD_CONFLICT`. After activation, merge/fast-forward and push the frozen SHA, verify `release integration-status`, then run normal `release end`; squash/rebase does not preserve the released source.
|
|
@@ -13,7 +13,7 @@ This is an OpenXiangda React SPA workspace. See [AGENTS.md](mdc:AGENTS.md) for f
|
|
|
13
13
|
|---|---|
|
|
14
14
|
| publish resources | `openxiangda resource publish <type> --only <codes> --profile <name> --change <change>` |
|
|
15
15
|
| deploy frontend | `openxiangda runtime deploy --profile <name> --no-activate` |
|
|
16
|
-
|
|
|
16
|
+
| formal promotion | `release begin` → exact publish/activate → merge/push frozen SHA → `integration-status` → `release end` |
|
|
17
17
|
| diff / plan | `openxiangda resource plan <type> --only <codes> --profile <name>` |
|
|
18
18
|
| diagnose | `openxiangda doctor --profile <name> --json` |
|
|
19
19
|
| external backend Open API | `openxiangda-open-api` + `openxiangda open-api spec describe ...` |
|
|
@@ -26,7 +26,7 @@ This is an OpenXiangda React SPA workspace. See [AGENTS.md](mdc:AGENTS.md) for f
|
|
|
26
26
|
- L0 read-only/docs/tests need no SDD; L1 narrow reversible fixes record exact scope without a redundant second confirmation; schema, business Functions, Automation/Workflow, permissions, auth/public access, data writes, and runtime/config are L2/L3 full-SDD work. Live evidence/archive are post-release stages.
|
|
27
27
|
- Very small copy/style/binding changes may use `openxiangda sdd quick <change> ...`; quick mode records exact low-risk scope without a redundant proposal/approval loop when the user already requested it.
|
|
28
28
|
- Use staged verification: `--stage implementation` while developing, `--stage prepublish` before promotion, and `--stage postpublish` after activation.
|
|
29
|
-
- Before platform writes, run `openxiangda release begin --change <change> --profile <name>` to freeze
|
|
29
|
+
- Before platform writes, run `openxiangda release begin --change <change> --profile <name>` to freeze the actual clean publish HEAD separately from the change base, require it to contain the authoritative remote mainline, and preflight the complete target set. After promotion, merge/fast-forward and push that exact SHA (no squash/rebase), run `release integration-status`, then normal `release end`. Only the live remote default `main`/`master` is authoritative; local refs, overrides, and lease expiry cannot clear the evidence.
|
|
30
30
|
- 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.
|
|
31
31
|
- 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`.
|
|
32
32
|
- `src/resources/**` is the resource source of truth; use `validate -> plan -> publish`.
|
|
@@ -24,6 +24,7 @@ openxiangda resource typegen --profile <name>
|
|
|
24
24
|
- Guest access to forms, dataViews, functions, and connectors requires explicit policy `grants`.
|
|
25
25
|
- Connector secrets and third-party credentials belong in the platform backend, never in manifests or page source.
|
|
26
26
|
- Formal changes should keep Git as the source of truth: edit manifests, validate, plan, then publish.
|
|
27
|
+
- Exact selectors apply before manifest/source analysis and JS_CODE build: touch only selected targets plus transitive/shared/ambient dependencies; omit selectors only for intentional full-workspace work.
|
|
27
28
|
- `resource plan` and publish dry-runs are GET/HEAD-only. On `READ_ONLY_AUTH_REQUIRED`, run `openxiangda auth refresh --profile <name>` or log in again before retrying; never refresh inside the plan.
|
|
28
29
|
- Source-triggered Function/Automation publishing patches only source fields on the server and preserves online bindings/contracts/metadata/state. Whole-manifest replacement requires `--replace-manifest --reason "..."`.
|
|
29
|
-
- Formal promotion
|
|
30
|
+
- Formal promotion freezes the clean publish HEAD separately from the change base, preflights the complete set, and rejects `SOURCE_BASE_DIVERGED` / `RELEASE_SOURCE_BEHIND_MAIN` / `RESOURCE_FIELD_CONFLICT`. After activation, merge/fast-forward and push the frozen SHA, verify `release integration-status`, then run normal `release end`; squash/rebase does not preserve the released source.
|
|
@@ -13,8 +13,8 @@ This is an OpenXiangda React SPA workspace. Read [AGENTS.md](AGENTS.md) for full
|
|
|
13
13
|
|---|---|
|
|
14
14
|
| 发布资源 / publish resources | `openxiangda resource publish <type> --only <codes> --change <change> --profile <name>` |
|
|
15
15
|
| 发布前端预览 / deploy preview | `openxiangda runtime deploy --profile <name> --no-activate --json` |
|
|
16
|
-
| 正式 promotion | `release begin --change <change>` → publish/activate → `release end` |
|
|
17
|
-
| 完整部署 / deploy | `pnpm deploy
|
|
16
|
+
| 正式 promotion | `release begin --change <change>` → publish/activate → merge/push frozen SHA → `integration-status` → `release end` |
|
|
17
|
+
| 完整部署 / deploy | 使用同一 change + 精确 `--only` 的显式命令;无范围 `pnpm deploy` 已停用 |
|
|
18
18
|
| 资源计划 / diff | `openxiangda resource plan <type> --only <codes> --profile <name>` |
|
|
19
19
|
| 诊断 / 环境 | `openxiangda doctor --profile <name> --json` / `openxiangda env --profile <name>` |
|
|
20
20
|
| 外部后端 / 三方系统开放接口 | `openxiangda-open-api` + `openxiangda open-api spec describe ...` |
|
|
@@ -30,7 +30,7 @@ This is an OpenXiangda React SPA workspace. Read [AGENTS.md](AGENTS.md) for full
|
|
|
30
30
|
- `src/resources/**` 是工程化资源来源,正式多资源变更走 `validate -> plan -> publish`。
|
|
31
31
|
- 默认按逻辑资源 code 使用 `--only` 或单资源 `--code`;全类型/全应用发布必须由批准的依赖闭包明确覆盖。
|
|
32
32
|
- Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state;整包 manifest 替换必须加 `--replace-manifest --reason "..."`。
|
|
33
|
-
- Promotion 必须持有 `release begin/end` 应用租约并沿用同一 `--change`。`release begin`
|
|
33
|
+
- Promotion 必须持有 `release begin/end` 应用租约并沿用同一 `--change`。`release begin` 分别冻结 change base 与实际 clean publish HEAD,并要求后者包含当前权威远端主分支;`SOURCE_BASE_DIVERGED` / `RELEASE_SOURCE_BEHIND_MAIN` / `RESOURCE_FIELD_CONFLICT` 必须合并、重建、重新计划。上线后 merge/fast-forward 并 push 冻结 SHA,运行 `release integration-status` 后才能正常 `release end`;squash/rebase 不算回合。权威目标只取远端实时默认 `main`/`master`,本地 ref、覆盖参数和租约过期均不能清除证据。
|
|
34
34
|
- React SPA 路由由 `src/app/router.tsx` 管理,前端包通过 `openxiangda runtime deploy` 发布。
|
|
35
35
|
- 表单附件预览使用 `AttachmentField` / `ImageField`;普通自定义页面使用 `AttachmentPreviewList` / `ImagePreviewGrid` / `useFilePreview`,不要伪造表单上下文、直接引用内部预览实现或维护扩展名白名单。
|
|
36
36
|
- 页面权限、表单权限、公开访问 grants、App Function 后端检查是授权依据;前端只做展示保护。
|