openxiangda 1.0.178 → 1.0.180
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 +18 -1
- package/lib/application-environments.js +282 -0
- package/lib/cli.js +1122 -49
- package/lib/developer-center.js +157 -0
- package/lib/policy.js +16 -0
- package/openxiangda-skills/SKILL.md +13 -2
- package/openxiangda-skills/references/openxiangda-api.md +40 -0
- package/openxiangda-skills/references/workspace-state.md +37 -0
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +12 -1
- package/package.json +2 -1
- package/policy/openxiangda-policy.json +7 -2
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +2 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +2 -1
- package/templates/openxiangda-react-spa/AGENTS.md +14 -2
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +3 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +2 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +2 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +4 -2
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
|
|
4
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
5
|
+
|
|
6
|
+
function json(response, status, value) {
|
|
7
|
+
response.writeHead(status, {
|
|
8
|
+
'content-type': 'application/json; charset=utf-8',
|
|
9
|
+
'cache-control': 'no-store',
|
|
10
|
+
'x-content-type-options': 'nosniff',
|
|
11
|
+
});
|
|
12
|
+
response.end(JSON.stringify(value));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readJsonBody(request) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
let size = 0;
|
|
18
|
+
const chunks = [];
|
|
19
|
+
request.on('data', chunk => {
|
|
20
|
+
size += chunk.length;
|
|
21
|
+
if (size > MAX_BODY_BYTES) {
|
|
22
|
+
reject(new Error('request body too large'));
|
|
23
|
+
request.destroy();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
chunks.push(chunk);
|
|
27
|
+
});
|
|
28
|
+
request.on('end', () => {
|
|
29
|
+
try {
|
|
30
|
+
resolve(
|
|
31
|
+
chunks.length > 0
|
|
32
|
+
? JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
|
33
|
+
: {}
|
|
34
|
+
);
|
|
35
|
+
} catch {
|
|
36
|
+
reject(new Error('request body must be valid JSON'));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
request.on('error', reject);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function studioHtml(sessionToken) {
|
|
44
|
+
const token = JSON.stringify(sessionToken);
|
|
45
|
+
return `<!doctype html>
|
|
46
|
+
<html lang="zh-CN">
|
|
47
|
+
<head>
|
|
48
|
+
<meta charset="utf-8" />
|
|
49
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
50
|
+
<meta name="color-scheme" content="dark" />
|
|
51
|
+
<title>OpenXiangda Developer Center</title>
|
|
52
|
+
<style>
|
|
53
|
+
:root{font-family:Inter,"PingFang SC",system-ui,sans-serif;color:#f7f7f5;background:#111210;--muted:#9b9e96;--line:#2c2e29;--panel:#181a17;--pre:#f0b849;--prod:#5fc49f;--bad:#ee776f}
|
|
54
|
+
*{box-sizing:border-box}body{margin:0;min-height:100vh;background:radial-gradient(circle at 80% -10%,#29352e 0,transparent 38%),#111210}
|
|
55
|
+
main{max-width:1240px;margin:auto;padding:36px 28px 64px}.eyebrow{color:#a9c7b8;font-size:12px;letter-spacing:.16em;text-transform:uppercase}
|
|
56
|
+
header{display:flex;justify-content:space-between;gap:24px;align-items:end;margin:10px 0 30px}h1{font-size:clamp(30px,4vw,54px);letter-spacing:-.045em;margin:0;font-weight:620}
|
|
57
|
+
.repo{color:var(--muted);font:13px ui-monospace,SFMono-Regular,monospace;text-align:right}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
|
|
58
|
+
.card{background:color-mix(in srgb,var(--panel) 94%,transparent);border:1px solid var(--line);border-radius:18px;padding:22px;box-shadow:0 18px 50px #0004}
|
|
59
|
+
.env{position:relative;overflow:hidden}.env:before{content:"";position:absolute;inset:0 auto 0 0;width:4px;background:var(--accent)}
|
|
60
|
+
.pre{--accent:var(--pre)}.prod{--accent:var(--prod)}.card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:22px}
|
|
61
|
+
h2{font-size:19px;margin:0}.badge{border:1px solid color-mix(in srgb,var(--accent) 45%,var(--line));color:var(--accent);padding:5px 9px;border-radius:999px;font-size:11px;letter-spacing:.08em}
|
|
62
|
+
dl{display:grid;grid-template-columns:130px 1fr;gap:11px;margin:0;font-size:13px}dt{color:var(--muted)}dd{margin:0;font-family:ui-monospace,SFMono-Regular,monospace;overflow-wrap:anywhere}
|
|
63
|
+
.wide{grid-column:1/-1}.toolbar{display:flex;flex-wrap:wrap;gap:10px;margin-top:20px}button{appearance:none;border:1px solid #3a3d36;background:#22241f;color:#f4f4ef;padding:10px 14px;border-radius:10px;font-weight:600;cursor:pointer}
|
|
64
|
+
button:hover{border-color:#777c70}button.primary{background:#e9efe9;color:#111;border-color:#e9efe9}button.danger{color:#ffaaa3;border-color:#70423e}
|
|
65
|
+
button:disabled{opacity:.4;cursor:not-allowed}.status{display:flex;gap:8px;align-items:center;color:var(--muted);font-size:13px}.dot{width:8px;height:8px;border-radius:50%;background:var(--prod);box-shadow:0 0 14px var(--prod)}
|
|
66
|
+
pre{white-space:pre-wrap;word-break:break-word;background:#10110f;border:1px solid #252722;border-radius:12px;padding:16px;color:#c7cbc2;max-height:300px;overflow:auto;font:12px/1.55 ui-monospace,SFMono-Regular,monospace}
|
|
67
|
+
dialog{border:1px solid var(--line);border-radius:18px;background:#1a1c18;color:#fff;padding:24px;max-width:520px;box-shadow:0 30px 100px #000a}dialog::backdrop{background:#000a}
|
|
68
|
+
input,textarea,select{width:100%;background:#111210;color:#fff;border:1px solid #3a3d36;border-radius:9px;padding:10px;margin:7px 0 14px}
|
|
69
|
+
@media(max-width:760px){.grid{grid-template-columns:1fr}.wide{grid-column:auto}header{align-items:start;flex-direction:column}.repo{text-align:left}main{padding:24px 16px}}
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body>
|
|
73
|
+
<main>
|
|
74
|
+
<div class="eyebrow">Local delivery control plane</div>
|
|
75
|
+
<header><h1>OpenXiangda<br/>Developer Center</h1><div class="repo" id="repo">正在读取工作区…</div></header>
|
|
76
|
+
<section class="grid">
|
|
77
|
+
<article class="card env pre"><div class="card-head"><h2>预发环境</h2><span class="badge">PREPRODUCTION</span></div><dl id="pre"></dl></article>
|
|
78
|
+
<article class="card env prod"><div class="card-head"><h2>正式环境</h2><span class="badge">PRODUCTION</span></div><dl id="prod"></dl></article>
|
|
79
|
+
<article class="card wide"><div class="card-head"><h2>交付状态</h2><div class="status"><span class="dot"></span><span id="health">连接中</span></div></div><div class="toolbar">
|
|
80
|
+
<button data-action="candidate">生成候选</button><button data-action="deploy">部署预发</button><button data-action="test">登记测试证据</button><button class="primary" data-action="promote">晋级正式</button><button class="danger" data-action="rollback">准备回退</button><button data-action="refresh">刷新</button>
|
|
81
|
+
</div><pre id="output">Developer Center 只监听 127.0.0.1;所有动作复用 OpenXiangda CLI 门禁。</pre></article>
|
|
82
|
+
</section>
|
|
83
|
+
</main>
|
|
84
|
+
<dialog id="dialog"><form method="dialog"><h2 id="dialog-title">执行操作</h2><label>候选版本 / Change / Release 参数</label><input id="value" autocomplete="off"/><label>测试证据 JSON 或回退原因(按操作需要)</label><textarea id="details" rows="8"></textarea><div class="toolbar"><button value="cancel">取消</button><button class="primary" id="confirm" value="default">确认执行</button></div></form></dialog>
|
|
85
|
+
<script>
|
|
86
|
+
const token=${token}; history.replaceState(null,"",location.pathname);
|
|
87
|
+
const out=document.querySelector("#output"), dialog=document.querySelector("#dialog");
|
|
88
|
+
let status=null, pending=null;
|
|
89
|
+
const esc=v=>String(v??"-").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">","\\"":""","'":"'"}[c]));
|
|
90
|
+
const rows=e=>[["appType",e?.appType],["公开地址",e?.publicOrigin],["AppRelease",e?.heads?.appReleaseId],["候选版本",e?.latestDeployment?.candidateId],["部署状态",e?.latestDeployment?.status],["副作用策略",JSON.stringify(e?.sideEffectPolicy||{})]].map(([k,v])=>\`<dt>\${esc(k)}</dt><dd>\${esc(v)}</dd>\`).join("");
|
|
91
|
+
async function api(path,options={}){const r=await fetch(path,{...options,headers:{"content-type":"application/json","x-openxiangda-studio-token":token,...options.headers}});const j=await r.json();if(!r.ok)throw new Error(j.message||"request failed");return j}
|
|
92
|
+
async function refresh(){try{status=await api("/api/status");document.querySelector("#repo").textContent=\`\${status.git?.branch||"-"} @ \${(status.git?.commit||"-").slice(0,12)} · \${status.git?.clean?"clean":"dirty"}\`;document.querySelector("#pre").innerHTML=rows(status.remote?.environments?.find(e=>e.kind==="preproduction"));document.querySelector("#prod").innerHTML=rows(status.remote?.environments?.find(e=>e.kind==="production"));document.querySelector("#health").textContent="状态已同步";}catch(e){document.querySelector("#health").textContent="读取失败";out.textContent=e.message}}
|
|
93
|
+
document.querySelectorAll("[data-action]").forEach(b=>b.onclick=()=>{pending=b.dataset.action;if(pending==="refresh")return refresh();document.querySelector("#dialog-title").textContent=b.textContent;document.querySelector("#value").value="";document.querySelector("#details").value=pending==="test"?JSON.stringify({outcome:"passed",requiredGates:["static","schema","appFunctions","roles","browser","lifecycle","cleanup"],results:{static:{status:"passed"},schema:{status:"passed"},appFunctions:{status:"passed"},roles:{status:"passed"},browser:{status:"passed"},lifecycle:{status:"passed"},cleanup:{status:"passed"}},cleanup:{passed:true,residueCount:0}},null,2):"";dialog.showModal()});
|
|
94
|
+
document.querySelector("#confirm").onclick=async e=>{e.preventDefault();const action=pending;dialog.close();out.textContent="执行中…";try{const result=await api("/api/action",{method:"POST",body:JSON.stringify({action,value:document.querySelector("#value").value,details:document.querySelector("#details").value,confirmProduction:["promote","rollback"].includes(action)})});out.textContent=JSON.stringify(result,null,2);await refresh()}catch(error){out.textContent=error.message}};
|
|
95
|
+
refresh();
|
|
96
|
+
</script>
|
|
97
|
+
</body></html>`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function startDeveloperCenter(options = {}) {
|
|
101
|
+
const host = '127.0.0.1';
|
|
102
|
+
const token = options.token || crypto.randomBytes(32).toString('base64url');
|
|
103
|
+
const server = http.createServer(async (request, response) => {
|
|
104
|
+
const url = new URL(request.url || '/', `http://${host}`);
|
|
105
|
+
if (
|
|
106
|
+
request.method === 'GET' &&
|
|
107
|
+
url.pathname === '/' &&
|
|
108
|
+
url.searchParams.get('session') === token
|
|
109
|
+
) {
|
|
110
|
+
response.writeHead(200, {
|
|
111
|
+
'content-type': 'text/html; charset=utf-8',
|
|
112
|
+
'cache-control': 'no-store',
|
|
113
|
+
'content-security-policy':
|
|
114
|
+
"default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
|
|
115
|
+
'x-frame-options': 'DENY',
|
|
116
|
+
'x-content-type-options': 'nosniff',
|
|
117
|
+
});
|
|
118
|
+
response.end(studioHtml(token));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (request.headers['x-openxiangda-studio-token'] !== token) {
|
|
122
|
+
json(response, 401, { message: 'invalid studio session' });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
if (request.method === 'GET' && url.pathname === '/api/status') {
|
|
127
|
+
json(response, 200, await options.getStatus());
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (request.method === 'POST' && url.pathname === '/api/action') {
|
|
131
|
+
const body = await readJsonBody(request);
|
|
132
|
+
json(response, 200, await options.runAction(body));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
json(response, 404, { message: 'not found' });
|
|
136
|
+
} catch (error) {
|
|
137
|
+
json(response, 400, {
|
|
138
|
+
code: error?.code || 'STUDIO_ACTION_FAILED',
|
|
139
|
+
message: error?.message || String(error),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
server.once('error', reject);
|
|
145
|
+
server.listen(Number(options.port || 0), host, resolve);
|
|
146
|
+
});
|
|
147
|
+
const address = server.address();
|
|
148
|
+
const url = `http://${host}:${address.port}/?session=${encodeURIComponent(
|
|
149
|
+
token
|
|
150
|
+
)}`;
|
|
151
|
+
return { server, token, url, host, port: address.port };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
startDeveloperCenter,
|
|
156
|
+
studioHtml,
|
|
157
|
+
};
|
package/lib/policy.js
CHANGED
|
@@ -38,6 +38,21 @@ function validateEngineeringPolicy(policy) {
|
|
|
38
38
|
if (policy?.release?.unsupportedGenericMode !== 'fail-closed') {
|
|
39
39
|
errors.push('release.unsupportedGenericMode 必须为 fail-closed');
|
|
40
40
|
}
|
|
41
|
+
if (
|
|
42
|
+
policy?.release?.applicationEnvironmentMode !==
|
|
43
|
+
'candidate-preproduction-evidence-production'
|
|
44
|
+
) {
|
|
45
|
+
errors.push(
|
|
46
|
+
'release.applicationEnvironmentMode 必须为 candidate-preproduction-evidence-production'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (
|
|
50
|
+
policy?.release?.candidateImmutable !== true ||
|
|
51
|
+
policy?.release?.productionDirectPublish !== false ||
|
|
52
|
+
policy?.release?.sameCandidateRequiredForPromotion !== true
|
|
53
|
+
) {
|
|
54
|
+
errors.push('应用环境发布必须冻结 candidate、禁止生产直发并同候选晋级');
|
|
55
|
+
}
|
|
41
56
|
const ttl = Number(policy?.ownership?.defaultTtlSeconds);
|
|
42
57
|
if (!Number.isSafeInteger(ttl) || ttl < 300 || ttl > 86400) {
|
|
43
58
|
errors.push('ownership.defaultTtlSeconds 必须在 300-86400 之间');
|
|
@@ -81,6 +96,7 @@ function renderEngineeringPolicyMarkdown(policy = loadEngineeringPolicy()) {
|
|
|
81
96
|
`- 风险按 ${policy.risk.classifyBy.join('、')} 分级;目录只作信号。`,
|
|
82
97
|
'- SDD verify 只读;Markdown 是按需派生视图。',
|
|
83
98
|
'- 平台仓库直接开发权威 master;应用任务可用独立 worktree,但只从干净、已推送的权威主线发布。',
|
|
99
|
+
'- 环境托管应用冻结不可变 candidate,先部署预发并提交有效测试证据,再将同一 candidate 晋级生产;禁止生产直发。',
|
|
84
100
|
`- 原子 child:${policy.release.atomicChildren.join(', ')}。`,
|
|
85
101
|
`- 尚无 staged child 的通用资源必须 ${policy.release.unsupportedGenericMode};维护直发必须显式选择并展示影响范围。`,
|
|
86
102
|
`- 激活后的副作用进入 ${policy.release.postCommitMode}。`,
|
|
@@ -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: 4 -->
|
|
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.
|
|
@@ -95,9 +95,20 @@ openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
|
95
95
|
openxiangda release publish --change <release-change> --profile <name>
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
Environment-managed application:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
openxiangda release candidate --change <release-change> --environment preproduction
|
|
102
|
+
openxiangda release deploy --candidate <candidate-id> --environment preproduction
|
|
103
|
+
openxiangda release test --deployment <deployment-id> --environment preproduction --evidence-json <file>
|
|
104
|
+
openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For a workspace registered by `openxiangda environment init` or connected to an existing set with `openxiangda environment attach`, production is never a direct publish target. The immutable candidate must first be deployed and passed in preproduction, and promotion must reuse the same candidate hash. Preproduction and production keep independent app/resource/data identities in `.openxiangda/state.json`; do not copy IDs between targets. `environment attach` may seed only the matching legacy app binding into preproduction and must leave production empty. Use `openxiangda studio` for the loopback-only developer view of bindings, drift, evidence, and safe next actions.
|
|
108
|
+
|
|
98
109
|
`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.
|
|
99
110
|
|
|
100
|
-
`release publish` is the default promotion entrypoint. 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. `release begin` and child commands remain recovery/diagnostic primitives.
|
|
111
|
+
`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 candidate/deploy/test/promote, and their execution journals are additionally scoped by deployment ID. `release begin` and child commands remain recovery/diagnostic primitives.
|
|
101
112
|
|
|
102
113
|
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.
|
|
103
114
|
|
|
@@ -846,6 +846,46 @@ Requires Bearer token. Returns runtime bootstrap information for an existing cod
|
|
|
846
846
|
|
|
847
847
|
Requires Bearer token. Returns app metadata, forms, menus, code page definitions, code page releases, and permission hints.
|
|
848
848
|
|
|
849
|
+
## Application Environments
|
|
850
|
+
|
|
851
|
+
Application environment APIs group two independent app identities under one logical application. They never copy resource IDs or business data between `preproduction` and `production`.
|
|
852
|
+
|
|
853
|
+
### POST `/environment-sets`
|
|
854
|
+
|
|
855
|
+
Creates a logical environment set and its first environment. The request includes `code`, `name`, `sourceRepositoryId`, and `initialEnvironment` (`kind`, `appType`, optional display/public URL and side-effect policy).
|
|
856
|
+
|
|
857
|
+
### GET `/environment-sets` and GET `/environment-sets/:code`
|
|
858
|
+
|
|
859
|
+
List environment sets or return one set with its bound environments.
|
|
860
|
+
|
|
861
|
+
### POST `/environment-sets/:code/environments`
|
|
862
|
+
|
|
863
|
+
Binds the missing `preproduction` or `production` app identity. An appType can belong to only one environment, and one set can contain at most one environment of each kind.
|
|
864
|
+
|
|
865
|
+
### GET `/environment-sets/:code/status`
|
|
866
|
+
|
|
867
|
+
Returns each target's active heads, latest deployment, side-effect policy, and candidate drift state for the local Developer Center.
|
|
868
|
+
|
|
869
|
+
### POST/GET `/environment-sets/:code/candidates`
|
|
870
|
+
|
|
871
|
+
Creates an immutable sealed candidate or lists candidate history. Reusing an existing candidate ID/hash is idempotent only when the canonical source revision, source bundle manifest, test-plan hash, and OpenXiangda version are identical.
|
|
872
|
+
|
|
873
|
+
### POST/GET `/environment-sets/:code/deployments`
|
|
874
|
+
|
|
875
|
+
Creates or lists a `deploy`, `promotion`, or `rollback` record. Promotion requires a successful, non-drifted preproduction deployment of the same candidate with fresh passing evidence.
|
|
876
|
+
|
|
877
|
+
### GET `/environment-sets/:code/deployments/:deploymentId`
|
|
878
|
+
|
|
879
|
+
Returns a deployment and its exact candidate/environment/AppRelease/evidence binding.
|
|
880
|
+
|
|
881
|
+
### POST `/environment-sets/:code/deployments/:deploymentId/complete`
|
|
882
|
+
|
|
883
|
+
Completes a deployment only when the canonical evidence hash matches and the evidence binds the exact deployment, environment, appType, AppRelease, candidate, and test plan. Required gates must be `passed` or `not_applicable`; preproduction cleanup must pass with zero residue; evidence validity cannot exceed 24 hours.
|
|
884
|
+
|
|
885
|
+
### POST `/environment-sets/:code/deployments/:deploymentId/fail`
|
|
886
|
+
|
|
887
|
+
Records a failed deployment without changing an active application head.
|
|
888
|
+
|
|
849
889
|
## Storage Configs
|
|
850
890
|
|
|
851
891
|
Storage config APIs manage application-level custom upload targets. The first provider is Aliyun OSS.
|
|
@@ -51,9 +51,44 @@ Tokens never belong in the project. User tokens live in `~/.openxiangda/profiles
|
|
|
51
51
|
}
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
Environment-managed workspaces add a logical application and target-specific bindings. The legacy `profiles` map may remain for compatibility, but managed commands resolve only the selected target:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"version": 1,
|
|
59
|
+
"logicalApp": {
|
|
60
|
+
"id": "ENVSET_UUID",
|
|
61
|
+
"code": "instrument-sharing",
|
|
62
|
+
"name": "大型仪器共享"
|
|
63
|
+
},
|
|
64
|
+
"currentTarget": "preproduction",
|
|
65
|
+
"targets": {
|
|
66
|
+
"preproduction": {
|
|
67
|
+
"profile": "hgy",
|
|
68
|
+
"environmentId": "PRE_ENV_UUID",
|
|
69
|
+
"kind": "preproduction",
|
|
70
|
+
"appType": "APP_PRE",
|
|
71
|
+
"sideEffectPolicy": {
|
|
72
|
+
"notificationsEnabled": false,
|
|
73
|
+
"scheduledAutomationsEnabled": false
|
|
74
|
+
},
|
|
75
|
+
"resources": {}
|
|
76
|
+
},
|
|
77
|
+
"production": {
|
|
78
|
+
"profile": "hgy",
|
|
79
|
+
"environmentId": "PROD_ENV_UUID",
|
|
80
|
+
"kind": "production",
|
|
81
|
+
"appType": "APP_PROD",
|
|
82
|
+
"resources": {}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
54
88
|
## Rules
|
|
55
89
|
|
|
56
90
|
- Profile is the deployment boundary.
|
|
91
|
+
- For an environment-managed logical application, the selected target is the deployment boundary inside the profile. `preproduction` and `production` own independent appType, resource IDs, release/deployment state, data, and side-effect policy.
|
|
57
92
|
- `baseUrl` is the backend API base. On standard private deployments it is `<origin>/service`; management pages use `/platform`, and app runtime pages use `/view`.
|
|
58
93
|
- Local state is authoritative for app binding. If a workspace has no `appType` for the target profile, create a new app/workspace with `openxiangda workspace init <dir> --profile <name> --app-name "应用名称"`.
|
|
59
94
|
- Do not search platform apps or reuse similar names unless the user explicitly asks to reuse an existing app or provides an `appType`.
|
|
@@ -65,4 +100,6 @@ Tokens never belong in the project. User tokens live in `~/.openxiangda/profiles
|
|
|
65
100
|
- CLI writes use a lock, a three-way merge at profile/resource-key granularity, and fsync + atomic rename. Concurrent writes to different profiles or logical resource keys are preserved; competing writes to the same field fail with `OPENXIANGDA_STATE_CONFLICT` and must be retried from a freshly loaded state.
|
|
66
101
|
- This protects the ID map from truncation/lost updates. Concurrent development still uses separate Git worktrees/branches; `.openxiangda/worktree-owner.json` binds one worktree to one active `CODEX_THREAD_ID` and blocks a second task at SDD context/publish time.
|
|
67
102
|
- A prod publish must not read dev IDs.
|
|
103
|
+
- Never copy target resource bindings. Use `openxiangda environment use <target>` or pass `--environment <target>` and let the CLI populate that target from its own deployment.
|
|
104
|
+
- Managed targets cannot use direct `release publish`; use candidate → preproduction deploy/test → same-candidate production promotion.
|
|
68
105
|
- Before publishing to another platform, run `openxiangda workspace bind --profile <name> --app-type <APP_XXX>`.
|
|
@@ -96,7 +96,18 @@ openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
|
96
96
|
openxiangda release publish --change <release-change> --profile <name>
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Environment-managed application:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
openxiangda release candidate --change <release-change> --environment preproduction
|
|
103
|
+
openxiangda release deploy --candidate <candidate-id> --environment preproduction
|
|
104
|
+
openxiangda release test --deployment <deployment-id> --environment preproduction --evidence-json <file>
|
|
105
|
+
openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Once `environment init` registers a logical application, or `environment attach` connects an existing workspace, `preproduction` and `production` are separate target bindings with separate app/resource/data IDs. Never copy IDs between them. Existing legacy resource mappings may seed only the target whose appType matches (normally preproduction); production stays empty. The candidate is immutable and promotion must reuse the candidate that has fresh, passing, deployment-bound preproduction evidence. Direct `release publish` to either managed target fails closed. `openxiangda studio` is the local loopback-only developer view and exposes only registered safe actions.
|
|
109
|
+
|
|
110
|
+
`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 candidate/deploy/test/promote and deployment-scoped journals. Individual release commands are recovery/diagnostic primitives.
|
|
100
111
|
|
|
101
112
|
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.
|
|
102
113
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.180",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"test:backend-release-cli": "node scripts/backend-release-cli-smoke.mjs",
|
|
111
111
|
"test:page-release-cli": "node scripts/page-release-cli-smoke.mjs",
|
|
112
112
|
"test:app-release-cli": "node scripts/app-release-cli-smoke.mjs",
|
|
113
|
+
"test:application-environments": "node scripts/application-environments-smoke.mjs",
|
|
113
114
|
"test:form-release-cas": "node scripts/form-release-cas-smoke.mjs",
|
|
114
115
|
"test:source-dependencies": "node scripts/source-dependencies-smoke.mjs",
|
|
115
116
|
"test:form-field-contract": "node scripts/form-field-contract-smoke.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "openxiangda_engineering_policy_v1",
|
|
3
|
-
"policyVersion":
|
|
3
|
+
"policyVersion": 4,
|
|
4
4
|
"risk": {
|
|
5
5
|
"levels": ["L0", "L1", "L2", "L3"],
|
|
6
6
|
"quickMaxLevel": "L1",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
},
|
|
30
30
|
"release": {
|
|
31
31
|
"planAndVerifyAreReadOnly": true,
|
|
32
|
+
"applicationEnvironmentMode": "candidate-preproduction-evidence-production",
|
|
33
|
+
"candidateImmutable": true,
|
|
34
|
+
"productionDirectPublish": false,
|
|
35
|
+
"sameCandidateRequiredForPromotion": true,
|
|
32
36
|
"atomicChildren": [
|
|
33
37
|
"RuntimeRelease",
|
|
34
38
|
"PageRelease",
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
},
|
|
47
51
|
"testing": {
|
|
48
52
|
"layers": ["changed", "contract", "release", "all"],
|
|
49
|
-
"evidenceDirectory": ".openxiangda/evidence/tests"
|
|
53
|
+
"evidenceDirectory": ".openxiangda/evidence/tests",
|
|
54
|
+
"preproductionEvidenceMaxAgeHours": 24
|
|
50
55
|
}
|
|
51
56
|
}
|
|
@@ -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
|
-
| formal promotion | `release
|
|
16
|
+
| formal promotion | managed app: `release candidate` → preproduction deploy/test → same-candidate `release promote --confirm-production` |
|
|
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 ...` |
|
|
@@ -27,6 +27,7 @@ This is an OpenXiangda React SPA workspace. See [AGENTS.md](mdc:AGENTS.md) for f
|
|
|
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
|
- SDD is streamlined by default: approval and exact structured scope are hard gates, while unfinished task/evidence/spec prose only warns. Configure `strictDocumentation: true` only when prose must block.
|
|
29
29
|
- 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. After activation, run `integration-status` and `release end`; no post-release merge is needed.
|
|
30
|
+
- 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.
|
|
30
31
|
- 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
32
|
- 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
33
|
- `src/resources/**` is the resource source of truth; use `validate -> plan -> publish`.
|
|
@@ -13,7 +13,7 @@ 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 |
|
|
16
|
+
| 正式 promotion | 环境托管应用:`release candidate` → 预发 deploy/test → 同 candidate `release promote --confirm-production` |
|
|
17
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>` |
|
|
@@ -31,6 +31,7 @@ This is an OpenXiangda React SPA workspace. Read [AGENTS.md](AGENTS.md) for full
|
|
|
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
33
|
- Promotion 必须持有 `release begin/end` 租约;`release begin` 只接受与权威远端 tip 完全一致的 clean main/master。feature branch 或未 push 主线在任何写入前失败。激活后直接运行 `integration-status` 和 `release end`,不再补做发布后合并。
|
|
34
|
+
- 已有工作区通过 `environment attach` 接入环境组,旧资源映射只迁移到 appType 相同的预发 target,正式 target 必须为空。preproduction / production 分别拥有独立 appType、资源 ID、数据和副作用策略;禁止直接 `release publish`,禁止跨环境复制 ID,使用 `openxiangda studio` 查看状态。
|
|
34
35
|
- React SPA 路由由 `src/app/router.tsx` 管理,前端包通过 `openxiangda runtime deploy` 发布。
|
|
35
36
|
- 表单附件预览使用 `AttachmentField` / `ImageField`;普通自定义页面使用 `AttachmentPreviewList` / `ImagePreviewGrid` / `useFilePreview`,不要伪造表单上下文、直接引用内部预览实现或维护扩展名白名单。
|
|
36
37
|
- 页面权限、表单权限、公开访问 grants、App Function 后端检查是授权依据;前端只做展示保护。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- OpenXiangda-Policy-Version:
|
|
1
|
+
<!-- OpenXiangda-Policy-Version: 4 -->
|
|
2
2
|
# AGENTS.md — OpenXiangda React SPA 应用工作区
|
|
3
3
|
|
|
4
4
|
本工作区是标准 React 18 + Vite + React Router 应用。默认模板只提供应用壳、账号菜单和一个首页,不是开发验证控制台。
|
|
@@ -43,11 +43,18 @@ openxiangda sdd verify <change> --changed --stage implementation
|
|
|
43
43
|
openxiangda resource plan <type> --only <code1,code2> --profile <name>
|
|
44
44
|
openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
45
45
|
openxiangda release publish --change <release-change> --profile <name>
|
|
46
|
+
openxiangda release candidate --change <release-change> --environment preproduction
|
|
47
|
+
openxiangda release deploy --candidate <candidate-id> --environment preproduction
|
|
48
|
+
openxiangda release test --deployment <deployment-id> --environment preproduction --evidence-json <file>
|
|
49
|
+
openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
|
|
50
|
+
openxiangda studio
|
|
46
51
|
openxiangda commands --json
|
|
47
52
|
```
|
|
48
53
|
|
|
49
54
|
模板已停用无范围的 `pnpm deploy` 聚合入口。日常变更必须使用 `resource plan|publish <type> --only <codes>`(单资源可用 `--code <code>`)。Form bundle、Backend Release 和 Runtime 都先暂存;CLI 会按 `--change` 自动聚合 `.openxiangda/releases/<change>/staged-resources.json`,最后只由一次 Root App finalize 原子激活。不要使用 `workspace publish --form`、单独 `runtime activate`、`pnpm publish:all`、`pnpm openxiangda:publish` 或 `lowcode-workspace publish-all`。
|
|
50
55
|
|
|
56
|
+
工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish` 即不再是入口。必须冻结不可变 candidate,部署到 preproduction,提交与 deployment/candidate/appType/AppRelease 精确绑定且仍在有效期内的通过证据,再将同一 candidate 晋级 production。两套环境的 appType、资源 ID、数据和副作用策略完全独立;旧单目标资源映射只允许迁移到 appType 相同的预发 target,正式 target 必须为空。`openxiangda studio` 提供仅本机访问的开发者页面,用于查看绑定、漂移、候选、部署、证据和下一安全动作。
|
|
57
|
+
|
|
51
58
|
`resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
|
|
52
59
|
|
|
53
60
|
完整发布顺序:
|
|
@@ -55,8 +62,13 @@ openxiangda commands --json
|
|
|
55
62
|
```bash
|
|
56
63
|
# 先合并所有 approved task commits 到 main/master 并 push
|
|
57
64
|
openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
58
|
-
# commit/push bundle
|
|
65
|
+
# 未登记环境的旧工作区:commit/push bundle 后由一个可恢复命令完成原子发布
|
|
59
66
|
openxiangda release publish --change <release-change> --profile <name>
|
|
67
|
+
# 已登记环境的工作区:冻结、预发、测试、同候选晋级
|
|
68
|
+
openxiangda release candidate --change <release-change> --environment preproduction
|
|
69
|
+
openxiangda release deploy --candidate <candidate-id> --environment preproduction
|
|
70
|
+
openxiangda release test --deployment <deployment-id> --environment preproduction --evidence-json <file>
|
|
71
|
+
openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
|
|
60
72
|
```
|
|
61
73
|
|
|
62
74
|
`pnpm build-js-code` 会检查并打包 `src/js-code-nodes/<code>/index.ts`、`src/automations/<code>/index.ts`、`src/functions/<code>/index.ts`,供 JS_CODE V2、代码自动化和 App Function 资源发布使用。批量目标使用 `pnpm build-js-code --scripts functions:a,functions:b,automations:c`,也兼容重复的 `--script a --script b --source functions`。正式 `resource plan/publish` 由 CLI 内置 scoped builder 一次批量构建选中入口及其传递/shared/ambient 依赖,不再为每个资源启动工作区 `pnpm`。缓存写入 `.openxiangda/build-cache.cli-v4.json`,稳定 `source_lineage_v1` 只按 authored source/dependencies 比较,不因构建器升级制造假冲突;需要强制重建时追加 `--force`。
|
|
@@ -6,7 +6,9 @@ const lines = [
|
|
|
6
6
|
'',
|
|
7
7
|
'请使用一个显式 change 和精确资源 selector:',
|
|
8
8
|
' openxiangda resource plan <type> --only <codes> --profile <name>',
|
|
9
|
-
' openxiangda release
|
|
9
|
+
' 环境托管应用:openxiangda release candidate --change <change> --environment preproduction',
|
|
10
|
+
' 然后依次执行 release deploy、release test、release promote --confirm-production。',
|
|
11
|
+
' 未登记环境的旧工作区:openxiangda release publish --change <change> --profile <name>',
|
|
10
12
|
'',
|
|
11
13
|
'该命令只接受已经 merge/fast-forward 并 push 的权威主分支,自动暂存并原子激活。',
|
|
12
14
|
'',
|
|
@@ -31,6 +31,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
|
|
|
31
31
|
- SDD is streamlined by default: approval and exact structured scope are hard gates, while unfinished task/evidence/spec prose only warns. Use `strictDocumentation: true` only when prose must block.
|
|
32
32
|
- Source-triggered Function/Automation publishing uses server-side source-field PATCH by default and preserves online bindings, contracts, metadata, trigger/view configuration, and enabled/published state. Whole-manifest replacement requires exact `--only/--code` plus `--replace-manifest --reason "..."`.
|
|
33
33
|
- 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. After activation, run `integration-status` and `release end`; no post-release merge is needed.
|
|
34
|
+
- Environment-managed workspaces never publish directly. Existing workspaces use `environment attach`; only an appType-matching legacy binding may seed preproduction and production starts empty. Run `release candidate`, `release deploy --environment preproduction`, `release test`, then promote that same candidate with `release promote --environment production --confirm-production`. Keep app/resource/data IDs isolated and inspect state with `openxiangda studio`.
|
|
34
35
|
- 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`.
|
|
35
36
|
- 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.
|
|
36
37
|
- Roles that create roles, assign members, grant API permissions, maintain permission groups, or manage organization accounts must declare `apiPermissionCodes` in `src/resources/roles/<code>.json`, such as `app:role:manage`, `app:page-permission-group:manage`, `app:form-permission-group:manage`, and `app:organization:manage`.
|
|
@@ -48,6 +49,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. See [AGEN
|
|
|
48
49
|
- ❌ Ask the user to paste AK / SK / appKey / appSecret into chat; use authorized `open-api credential` secure handoff for external backends.
|
|
49
50
|
- ❌ Search the platform for similar app names when `.openxiangda/state.json` has no binding.
|
|
50
51
|
- ❌ Copy `formUuid` / `pageId` / `workflowId` / `automationId` across profiles.
|
|
52
|
+
- ❌ Run `release publish` against a registered preproduction/production target or promote without valid preproduction evidence.
|
|
51
53
|
- ❌ Run a full publish for a single-file edit.
|
|
52
54
|
- ❌ Hot-patch a temporary release copy or include another task's dirty files in a release.
|
|
53
55
|
- ❌ Frontend-only button hiding, query-param checks, hardcoded roles, mock permissions, fake account IDs, or `PermissionBoundary` alone as real authorization.
|
|
@@ -31,6 +31,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
|
|
|
31
31
|
- SDD 默认 streamlined:approval 与结构化精确范围是硬门禁,未完成的 task/evidence/spec 文案只告警;只有 `strictDocumentation: true` 才阻断。
|
|
32
32
|
- Function/Automation 源码触发默认走服务端 source-field PATCH,保留线上 bindings/contracts/metadata/trigger/view/enabled/published state;整包 manifest 替换必须精确 `--only/--code` 并加 `--replace-manifest --reason "..."`。
|
|
33
33
|
- 写入平台前只从与权威远端 tip 完全一致的 clean main/master 执行 `release begin`。feature branch 或未 push 主线在任何写入前失败;激活后直接运行 `integration-status` 和 `release end`,无需发布后再合并。
|
|
34
|
+
- 环境托管工作区禁止直发:已有工作区先用 `environment attach` 接入,旧映射只能迁移到 appType 相同的预发 target,正式 target 必须为空。然后执行 `release candidate`、`release deploy --environment preproduction`、`release test`,最后将同一 candidate 用 `release promote --environment production --confirm-production` 晋级。两套环境的 appType、资源 ID 和数据不可互拷;用 `openxiangda studio` 查看状态。
|
|
34
35
|
- 单文件改动默认按 change 和逻辑目标发布:先 `workspace plan --profile <name> --change <change> --changed`,再 `workspace publish --profile <name> --change <change> --only pages/a,forms/b --dry-run`。
|
|
35
36
|
- 账号/角色/权限/RBAC/组织账号/查询参数授权需求先选权限模式:`managed-platform-account` / `existing-platform-user-assignment` / `static-role-permission` / `query-param-context`,并输出权限矩阵。
|
|
36
37
|
- 角色能新增角色、分配成员、授接口权限、维护权限组或管理组织账号时,`src/resources/roles/<code>.json` 必须声明 `apiPermissionCodes`,例如 `app:role:manage`、`app:page-permission-group:manage`、`app:form-permission-group:manage`、`app:organization:manage`。
|
|
@@ -48,6 +49,7 @@ This is a `sy-lowcode-app-workspace` managed by the `openxiangda` CLI. Read [AGE
|
|
|
48
49
|
- ❌ 要求用户在对话中粘贴 AK / SK / appKey / appSecret;获授权的外部后端集成使用 `open-api credential` 安全交付。
|
|
49
50
|
- ❌ 在没有本地 `.openxiangda/state.json` 绑定时,去平台搜索同名应用"复用"。
|
|
50
51
|
- ❌ 把 `formUuid` / `pageId` / `workflowId` / `automationId` 从 dev 复制到 prod。
|
|
52
|
+
- ❌ 对已经登记环境的 preproduction / production 执行 `release publish`,或绕过有效预发证据直接晋级正式。
|
|
51
53
|
- ❌ 改一处就全量发布。
|
|
52
54
|
- ❌ 在临时发布副本中做仓库没有的 hot patch,或让一个任务发布其他会话的 dirty 文件。
|
|
53
55
|
- ❌ 只隐藏按钮、只判断 query 参数、硬编码角色、前端模拟权限、假账号/假 ID、或用 `PermissionBoundary` 代替真实授权。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- OpenXiangda-Policy-Version:
|
|
1
|
+
<!-- OpenXiangda-Policy-Version: 4 -->
|
|
2
2
|
# AGENTS.md — OpenXiangda 工作区 AI 强约束
|
|
3
3
|
|
|
4
4
|
> 任何 AI(Qoder / Claude / Codex / Cursor / Copilot 等)在本工作区操作前 **必须先读完本文件**。
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
- ✅ 正式多资源开发优先写 `src/resources/**` 后执行 `openxiangda resource validate|plan|publish <type> --only <codes>`;单资源可用 `--code <code>`。直接 CLI 写平台资源时先 `--dry-run`,需要避免漂移就加 `--write-manifest`。
|
|
57
57
|
- ✅ `resource plan` 与 publish dry-run 严格只允许 GET/HEAD;遇到 `READ_ONLY_AUTH_REQUIRED` 时先执行 `openxiangda auth refresh --profile <name>` 或重新登录,不得在 plan 内自动 POST 刷新 token。
|
|
58
58
|
- ✅ Function/Automation 使用 Backend Release v2;正式多资源发布用精确 `--only/--code` 加 `--stage-only` 暂存,同一 child 可混合 create、source-only update 和显式 manifest replacement,再由 Root App finalize 原子激活。整包 manifest 替换必须另加 `--replace-manifest --reason "..."`。
|
|
59
|
-
- ✅
|
|
59
|
+
- ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
|
|
60
|
+
- ✅ 已通过 `environment init` 登记或 `environment attach` 接入的工作区必须走 `release candidate → release deploy --environment preproduction → release test → release promote --environment production --confirm-production`。候选不可变,正式晋级必须复用预发通过的同一候选;preproduction / production 的 appType、资源 ID、数据和副作用策略完全隔离,严禁跨环境复制 ID 或直接 `release publish`。旧单目标映射只允许按相同 appType 迁移到预发。
|
|
61
|
+
- ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
|
|
60
62
|
|
|
61
63
|
## 严禁
|
|
62
64
|
|