openxiangda 1.0.212 → 1.0.214
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/cli.js +194 -12
- package/openxiangda-skills/SKILL.md +1 -1
- package/openxiangda-skills/references/connector-resources.md +1 -1
- package/openxiangda-skills/references/workflow-v3.md +1 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +1 -1
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +2 -2
- package/package.json +1 -1
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +37 -1
- package/packages/sdk/dist/runtime/react.d.ts +37 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +1 -0
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +1 -0
- package/templates/openxiangda-react-spa/AGENTS.md +2 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +1 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +1 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +1 -0
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ React SPA workspaces publish their frontend with `openxiangda runtime deploy`. E
|
|
|
106
106
|
|
|
107
107
|
Parallel tasks develop and test in isolated worktrees, but feature worktrees do not publish. After approved commits are merged and pushed, `sdd bundle <release-change> --changes ...` unions their exact structured scope and preserves the source changes' common Git baseline instead of adopting the post-merge `HEAD`. Old imported changes that predate source-base metadata must pass `--source-base-ref <commit>` explicitly. Commit/push the bundle, then run `release publish --change <release-change> --profile <name>` from a clean local main/master whose commit exactly equals the authoritative remote tip. It verifies without rewriting reviewed SDD files, waits for the app lease, freezes one authoritative App capture, executes exact Form/Backend/Runtime staged steps, atomically finalizes the Root App release, and records a resumable local execution journal. Runtime checks use the narrow head endpoint; immutable Git-base artifact hashes are reused across plans.
|
|
108
108
|
|
|
109
|
-
Because promotion starts from an already-pushed authoritative mainline commit, `openxiangda release integration-status --profile <name>` should pass immediately after activation. Run it and `release end`; there is no post-release merge step.
|
|
109
|
+
Because promotion starts from an already-pushed authoritative mainline commit, `openxiangda release integration-status --change <change> --profile <name>` should pass immediately after activation. After a managed ship has ended, `--change` recovers lineage directly from the private `ship.json` or follows its production/preproduction deployment IDs to the matching `execution.json`; a recovery failure names the missing file or field. Run it and `release end`; there is no post-release merge step.
|
|
110
110
|
|
|
111
111
|
`openxiangda task status --change <id>` is the compact, read-only answer to
|
|
112
112
|
“现在到哪一步”:it combines SDD、TaskResult、IntegrationBundle、execution
|
|
@@ -502,7 +502,7 @@ const PublicAccessError = ({ error }: { error: { message?: string } }) => (
|
|
|
502
502
|
|
|
503
503
|
多表只读查询和固定口径统计优先声明 `src/resources/data-views/*.json` 数据视图,而不是在页面里手写多次单表查询再拼数据。默认 `storageMode: "materialized"` 会创建 PostgreSQL materialized view,适合读多写少和可接受刷新延迟的列表/报表;`storageMode: "live"` 每次查询实时编译逻辑视图,适合强实时但数据量可控的复杂查询。`viewType: "aggregate"` 是统计聚合视图,适合按客户、状态、月份等维度聚合 count/sum/avg/min/max。发布时 CLI 会把 `formCode` 解析为当前 profile 的 `formUuid`;页面通过 `sdk.dataView.query(code, params)` 查询行级视图,通过 `sdk.dataView.stats(code, params)` 查询聚合视图,也可以用 `sdk.dataSource.run()` 路由 `dataView.query` / `dataView.stats`。materialized 模式应为常用筛选、排序、统计维度和时间桶声明 `indexes`,并确认用户能接受的刷新延迟;live 模式忽略 `indexes`,不需要刷新。
|
|
504
504
|
|
|
505
|
-
后端业务逻辑优先声明为 App Function:源码放在 `src/functions/<functionCode>/index.ts`,资源 manifest 放在 `src/resources/functions/<functionCode>.json`。函数运行在 trusted_node 中,通过 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.organization`、`ctx.platform.roles`、`ctx.platform.api` 等受控 API 访问平台能力;自动化、流程和运行时接口都可以调用同一个 function
|
|
505
|
+
后端业务逻辑优先声明为 App Function:源码放在 `src/functions/<functionCode>/index.ts`,资源 manifest 放在 `src/resources/functions/<functionCode>.json`。函数运行在 trusted_node 中,通过 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.process.startFromExistingInstance/resolveCapabilities/resubmitTask/withdraw/transferTask`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.organization`、`ctx.platform.roles`、`ctx.platform.api` 等受控 API 访问平台能力;自动化、流程和运行时接口都可以调用同一个 function。`ctx.process` 复用正式工作流服务,以真实运行时 operator 执行,并严格限制在当前应用及 Function `resources.forms` 已绑定的流程表单;流程实例、任务授权、操作日志、事件和 replay 语义与 PageSdk 后端接口一致。角色查询和角色成员维护优先使用 `ctx.platform.roles.list/findByCode/addUsers/removeUser`,底层 `ctx.platform.api` 返回 HTTP 包装和平台 envelope,需要业务代码自行解包。运行时页面调用会在 `ctx.operator`、`ctx.currentUser`、`ctx.permissions` 中注入可信的 `roleCodes`、`currentRoleCode`、`hasFullAccess`、`isAppAdmin`、`isPlatformAdmin`,敏感动作必须读取这些服务端上下文做授权,不要信任页面 input 里传入的角色字段。`ctx.form.createOne/updateOne/updateById` 是后端受控写入,授权边界是函数 manifest 的 `resources.forms` 绑定和函数调用授权,不是页面用户对目标表单的直接提交入口;报名、签到、福利选择等内部多表写入应走 App Function,不要为了写内部表单给普通用户开放原始 submit 权限。JS_CODE V2 仍兼容,但新逻辑建议写成 function,再由 `function_call` 节点、`sdk.function.invoke(code, { input })` 或 `/:appType/v1/functions/:code/invoke.json` 调用。直接运行时接口默认要求调用者具备应用自动化管理权限;普通页面调用必须在函数 `definitionJson.runtimeInvoke.audience` 声明 `authenticated`、`page_permission_group`、`app_roles` 或 `scope_policy`,`roleCodes` 只用于当前应用角色精确匹配,不支持 `"*"` / `"all-app-roles"`。自动化/流程内部调用走服务端受控上下文。内部业务表单需要关闭原始写入接口时,在表单 settings 中设置 `runtimeWrite.mode="function_only"`。
|
|
506
506
|
|
|
507
507
|
App Function 访问第三方凭据时使用 `app_function_secrets_v1`:manifest 顶层只声明 `secretRefs: [{ "name": "dingtalk_org_app_key", "required": true }]`,同时使用 `definitionJson.version="function_v2"`、`runtimeContractVersion="trusted_node_v2"`;源码通过 `await ctx.secrets.get(name)` 解析,并通过 `ctx.utils.http` 访问受控公网 HTTPS(该桥接不会携带平台 Runtime token)。值只能经 `openxiangda secret create|rotate --value-stdin --change <id> --profile <name>` 或隐藏 TTY 输入,禁止进入 Git、manifest、源码、构建产物、plan、日志或异常。带 `secretRefs` 的 Function 必须走 `backend_release_v2`;需要整应用原子发布时先执行 `resource publish function --only <code> --stage-only`,再把返回的真实 `stagedResource` 交给 `release app-finalize --staged-resources-json ...` 完成 `atomic_staged_children_v2`。默认直接激活的 Backend Release 只返回 `activeResource`,不会伪装成 staged;旧平台 capability 不完整时 CLI 会失败关闭,绝不忽略绑定。
|
|
508
508
|
|
package/lib/cli.js
CHANGED
|
@@ -1484,6 +1484,139 @@ function readReleaseRecoveryContext(target, changeId, deploymentId) {
|
|
|
1484
1484
|
return { execution, context };
|
|
1485
1485
|
}
|
|
1486
1486
|
|
|
1487
|
+
function releaseSourceRevisionFromExecution(execution) {
|
|
1488
|
+
return (
|
|
1489
|
+
execution?.releaseSourceRevision ||
|
|
1490
|
+
execution?.releaseContext?.releaseSourceRevision ||
|
|
1491
|
+
null
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function privateReleasePathLabel(file) {
|
|
1496
|
+
return path.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function readReleaseLineageRecovery(changeId, deploymentId) {
|
|
1500
|
+
if (!changeId) {
|
|
1501
|
+
return {
|
|
1502
|
+
sourceRevision: null,
|
|
1503
|
+
source: null,
|
|
1504
|
+
checked: [],
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
const checked = [];
|
|
1509
|
+
const recovered = [];
|
|
1510
|
+
const inspectedExecutions = new Set();
|
|
1511
|
+
const inspectExecution = (candidateDeploymentId, label) => {
|
|
1512
|
+
const normalizedDeploymentId = String(candidateDeploymentId || '').trim();
|
|
1513
|
+
const key = normalizedDeploymentId || '<root>';
|
|
1514
|
+
if (inspectedExecutions.has(key)) return;
|
|
1515
|
+
inspectedExecutions.add(key);
|
|
1516
|
+
const file = releaseExecutionFile(
|
|
1517
|
+
changeId,
|
|
1518
|
+
normalizedDeploymentId || undefined
|
|
1519
|
+
);
|
|
1520
|
+
const exists = fs.existsSync(file);
|
|
1521
|
+
const execution = exists
|
|
1522
|
+
? readReleaseExecution(changeId, normalizedDeploymentId || undefined)
|
|
1523
|
+
: null;
|
|
1524
|
+
const sourceRevision = releaseSourceRevisionFromExecution(execution);
|
|
1525
|
+
const changeMatches =
|
|
1526
|
+
!execution?.changeId || execution.changeId === changeId;
|
|
1527
|
+
checked.push({
|
|
1528
|
+
label,
|
|
1529
|
+
file: privateReleasePathLabel(file),
|
|
1530
|
+
exists,
|
|
1531
|
+
field:
|
|
1532
|
+
sourceRevision && changeMatches
|
|
1533
|
+
? 'releaseSourceRevision'
|
|
1534
|
+
: sourceRevision
|
|
1535
|
+
? `changeId 不匹配 (${execution.changeId})`
|
|
1536
|
+
: '缺少 releaseContext.releaseSourceRevision/releaseSourceRevision',
|
|
1537
|
+
});
|
|
1538
|
+
if (sourceRevision && changeMatches) {
|
|
1539
|
+
recovered.push({
|
|
1540
|
+
sourceRevision,
|
|
1541
|
+
source: privateReleasePathLabel(file),
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1546
|
+
const shipFile = managedShipFile(changeId);
|
|
1547
|
+
const shipExists = fs.existsSync(shipFile);
|
|
1548
|
+
const ship = shipExists ? readManagedShip(changeId) : null;
|
|
1549
|
+
const shipSourceRevision = ship?.sourceRevision || null;
|
|
1550
|
+
checked.push({
|
|
1551
|
+
label: 'managed ship journal',
|
|
1552
|
+
file: privateReleasePathLabel(shipFile),
|
|
1553
|
+
exists: shipExists,
|
|
1554
|
+
field: shipSourceRevision ? 'sourceRevision' : '缺少 sourceRevision',
|
|
1555
|
+
});
|
|
1556
|
+
if (shipSourceRevision) {
|
|
1557
|
+
return {
|
|
1558
|
+
sourceRevision: shipSourceRevision,
|
|
1559
|
+
source: `${privateReleasePathLabel(shipFile)}#sourceRevision`,
|
|
1560
|
+
checked,
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
if (deploymentId) {
|
|
1565
|
+
inspectExecution(deploymentId, 'explicit deployment execution');
|
|
1566
|
+
}
|
|
1567
|
+
for (const [label, candidateDeploymentId] of [
|
|
1568
|
+
['production deployment execution', ship?.productionDeploymentId],
|
|
1569
|
+
['preproduction deployment execution', ship?.preproductionDeploymentId],
|
|
1570
|
+
]) {
|
|
1571
|
+
if (candidateDeploymentId) {
|
|
1572
|
+
inspectExecution(candidateDeploymentId, label);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
inspectExecution(null, 'root release execution');
|
|
1576
|
+
|
|
1577
|
+
const releaseDir = releaseExecutionDir(changeId);
|
|
1578
|
+
if (fs.existsSync(releaseDir)) {
|
|
1579
|
+
for (const entry of fs
|
|
1580
|
+
.readdirSync(releaseDir, { withFileTypes: true })
|
|
1581
|
+
.filter(item => item.isDirectory())
|
|
1582
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1583
|
+
inspectExecution(entry.name, 'discovered deployment execution');
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const distinct = new Map();
|
|
1588
|
+
for (const item of recovered) {
|
|
1589
|
+
const fingerprint = sha256Canonical({
|
|
1590
|
+
repo:
|
|
1591
|
+
item.sourceRevision?.repo ||
|
|
1592
|
+
item.sourceRevision?.repositoryId ||
|
|
1593
|
+
null,
|
|
1594
|
+
baseCommit:
|
|
1595
|
+
item.sourceRevision?.baseCommit ||
|
|
1596
|
+
item.sourceRevision?.commit ||
|
|
1597
|
+
null,
|
|
1598
|
+
treeHash: item.sourceRevision?.treeHash || null,
|
|
1599
|
+
remoteName: item.sourceRevision?.remoteName || null,
|
|
1600
|
+
remoteUrlHash: item.sourceRevision?.remoteUrlHash || null,
|
|
1601
|
+
mainBranch: item.sourceRevision?.mainBranch || null,
|
|
1602
|
+
});
|
|
1603
|
+
if (!distinct.has(fingerprint)) distinct.set(fingerprint, item);
|
|
1604
|
+
}
|
|
1605
|
+
if (distinct.size > 1) {
|
|
1606
|
+
fail(
|
|
1607
|
+
`RELEASE_SOURCE_LINEAGE_CONFLICT: change ${changeId} 的私有 deployment execution 日志包含互不一致的 releaseSourceRevision: ${Array.from(
|
|
1608
|
+
distinct.values(),
|
|
1609
|
+
item => item.source
|
|
1610
|
+
).join(', ')}`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
return {
|
|
1614
|
+
sourceRevision: recovered[0]?.sourceRevision || null,
|
|
1615
|
+
source: recovered[0]?.source || null,
|
|
1616
|
+
checked,
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1487
1620
|
function remoteLeaseOwnedByRecovery(remote, recovery) {
|
|
1488
1621
|
if (!remote?.active || !recovery) return false;
|
|
1489
1622
|
const holder = remote.holder;
|
|
@@ -3520,13 +3653,21 @@ async function runApplicationShipCommand(
|
|
|
3520
3653
|
);
|
|
3521
3654
|
}
|
|
3522
3655
|
|
|
3656
|
+
const promotionStartedAt = new Date().toISOString();
|
|
3523
3657
|
writeManagedShip(changeId, {
|
|
3524
3658
|
status: 'confirmed_for_production',
|
|
3525
3659
|
productionConfirmation: {
|
|
3660
|
+
status: 'confirmed',
|
|
3526
3661
|
confirmedAt,
|
|
3527
3662
|
profile: preproduction.profileName,
|
|
3528
3663
|
acceptanceNote: acceptanceNote || null,
|
|
3529
3664
|
},
|
|
3665
|
+
productionPromotion: {
|
|
3666
|
+
status: 'running',
|
|
3667
|
+
startedAt: promotionStartedAt,
|
|
3668
|
+
environment: production.targetName,
|
|
3669
|
+
deploymentId: null,
|
|
3670
|
+
},
|
|
3530
3671
|
manualAcceptance,
|
|
3531
3672
|
preproductionStatus: preproductionDeployment.status,
|
|
3532
3673
|
});
|
|
@@ -3553,6 +3694,15 @@ async function runApplicationShipCommand(
|
|
|
3553
3694
|
manifestReplacement,
|
|
3554
3695
|
baselineAdoption,
|
|
3555
3696
|
failure,
|
|
3697
|
+
productionPromotion: {
|
|
3698
|
+
status: 'failed',
|
|
3699
|
+
startedAt: promotionStartedAt,
|
|
3700
|
+
failedAt: failure.recordedAt,
|
|
3701
|
+
environment: production.targetName,
|
|
3702
|
+
deploymentId: failure.deploymentId,
|
|
3703
|
+
deploymentStatus: failure.deploymentStatus,
|
|
3704
|
+
failure,
|
|
3705
|
+
},
|
|
3556
3706
|
nextCommand: buildManagedShipCommand(
|
|
3557
3707
|
changeId,
|
|
3558
3708
|
preproduction.profileName,
|
|
@@ -3562,6 +3712,7 @@ async function runApplicationShipCommand(
|
|
|
3562
3712
|
});
|
|
3563
3713
|
throw error;
|
|
3564
3714
|
}
|
|
3715
|
+
const completedAt = new Date().toISOString();
|
|
3565
3716
|
const saved = writeManagedShip(changeId, {
|
|
3566
3717
|
status: 'completed',
|
|
3567
3718
|
productionEnvironment: production.targetName,
|
|
@@ -3570,7 +3721,15 @@ async function runApplicationShipCommand(
|
|
|
3570
3721
|
promoted.deployment.targetAppReleaseId || null,
|
|
3571
3722
|
productionStatus: promoted.deployment.status,
|
|
3572
3723
|
failure: null,
|
|
3573
|
-
completedAt
|
|
3724
|
+
completedAt,
|
|
3725
|
+
productionPromotion: {
|
|
3726
|
+
status: 'completed',
|
|
3727
|
+
startedAt: promotionStartedAt,
|
|
3728
|
+
completedAt,
|
|
3729
|
+
environment: production.targetName,
|
|
3730
|
+
deploymentId: promoted.deployment.id,
|
|
3731
|
+
deploymentStatus: promoted.deployment.status,
|
|
3732
|
+
},
|
|
3574
3733
|
});
|
|
3575
3734
|
return {
|
|
3576
3735
|
phase: 'production',
|
|
@@ -3786,7 +3945,7 @@ async function release(args) {
|
|
|
3786
3945
|
' openxiangda release begin --change <id> --profile <name>',
|
|
3787
3946
|
' openxiangda resource publish function --only <code> --change <id> --profile <name>',
|
|
3788
3947
|
' openxiangda runtime deploy --change <id> --profile <name>',
|
|
3789
|
-
' openxiangda release integration-status --profile <name>',
|
|
3948
|
+
' openxiangda release integration-status --change <id> --profile <name>',
|
|
3790
3949
|
' openxiangda release end --profile <name>',
|
|
3791
3950
|
' openxiangda release backend-head --profile <name>',
|
|
3792
3951
|
' openxiangda release backend-diff <fromReleaseId> <toReleaseId> --profile <name>',
|
|
@@ -3805,6 +3964,7 @@ async function release(args) {
|
|
|
3805
3964
|
' - 默认 TTL 为 120 秒;活动发布每 30 秒心跳并在到期前续租,客户端退出后孤儿租约最多阻塞约 2 分钟。',
|
|
3806
3965
|
' - begin 仅接受 clean 且与权威远端默认 main/master 完全一致的 HEAD;feature branch 先合并并 push,再由主分支一次发布。',
|
|
3807
3966
|
' - 因发布源已在远端主线,成功激活后 integration-status 会立即通过,end 不再等待补合并。',
|
|
3967
|
+
' - 发布已结束时,integration-status --change 会先从 ship.json 恢复托管 candidate 血缘,再自动查找其 production/preproduction deployment execution 日志;恢复失败会列出实际缺失的文件或字段。',
|
|
3808
3968
|
' - integration-status --check 可作为 CI 门禁;尚未回合时返回非零。',
|
|
3809
3969
|
' - 主线目标只能来自权威 Git remote 的实时默认 main/master;参数和环境变量不能改写。',
|
|
3810
3970
|
' - 租约过期也会保留待回合证据;完成 merge/push 后仍可执行 end 清理。',
|
|
@@ -3953,23 +4113,45 @@ async function release(args) {
|
|
|
3953
4113
|
access: 'reconciliation-read',
|
|
3954
4114
|
});
|
|
3955
4115
|
const changeId = positional[0] || readStringFlag(flags, 'change');
|
|
3956
|
-
const recovered =
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
4116
|
+
const recovered = baseline?.releaseSourceRevision
|
|
4117
|
+
? {
|
|
4118
|
+
sourceRevision: null,
|
|
4119
|
+
source: null,
|
|
4120
|
+
checked: [],
|
|
4121
|
+
}
|
|
4122
|
+
: readReleaseLineageRecovery(
|
|
4123
|
+
changeId,
|
|
4124
|
+
readStringFlag(flags, 'deployment-id')
|
|
4125
|
+
);
|
|
3961
4126
|
const sourceRevision =
|
|
3962
4127
|
baseline?.releaseSourceRevision ||
|
|
3963
|
-
recovered
|
|
4128
|
+
recovered.sourceRevision;
|
|
3964
4129
|
if (!sourceRevision) {
|
|
4130
|
+
if (changeId) {
|
|
4131
|
+
const checked = recovered.checked
|
|
4132
|
+
.map(item =>
|
|
4133
|
+
item.exists
|
|
4134
|
+
? `${item.file} (${item.field})`
|
|
4135
|
+
: `${item.file} (文件不存在)`
|
|
4136
|
+
)
|
|
4137
|
+
.join('; ');
|
|
4138
|
+
fail(
|
|
4139
|
+
`RELEASE_SOURCE_LINEAGE_REQUIRED: change ${changeId} 的私有发布记录无法恢复 releaseSourceRevision;已检查: ${checked}`
|
|
4140
|
+
);
|
|
4141
|
+
}
|
|
3965
4142
|
fail(
|
|
3966
4143
|
'RELEASE_SOURCE_LINEAGE_REQUIRED: 当前 release 没有冻结的 releaseSourceRevision;发布已结束时请同时传入 --change <id>,以便从私有执行日志恢复主线证据'
|
|
3967
4144
|
);
|
|
3968
4145
|
}
|
|
3969
|
-
const result =
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
4146
|
+
const result = {
|
|
4147
|
+
...inspectReleaseSourceIntegration(
|
|
4148
|
+
sourceRevision,
|
|
4149
|
+
{ cwd: process.cwd() }
|
|
4150
|
+
),
|
|
4151
|
+
lineageSource: baseline?.releaseSourceRevision
|
|
4152
|
+
? '.openxiangda/state.json#promotion.changeBaseline.releaseSourceRevision'
|
|
4153
|
+
: recovered.source,
|
|
4154
|
+
};
|
|
3973
4155
|
if (flags.check && !result.integrated) {
|
|
3974
4156
|
assertReleaseSourceIntegrated(sourceRevision, { cwd: process.cwd() });
|
|
3975
4157
|
}
|
|
@@ -120,7 +120,7 @@ Exact `resourceSelectors` are authoritative for supported configuration resource
|
|
|
120
120
|
|
|
121
121
|
`release begin --change` freezes a clean committed `HEAD` only when the current branch is the authoritative default `main`/`master` and its commit exactly equals the live remote tip. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A feature worktree or unpushed main receives `RELEASE_SOURCE_MAINLINE_REQUIRED` / `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`; merge, test, push, and start the one mainline release instead of forcing it. Optional `.git` remote suffix differences and other recorded repository aliases are accepted only when the frozen source-base ID intersects `releaseSourceRevision.repoAliases`; Backend, Workflow, and Root App Release writes then use the frozen canonical ID. Genuinely different identity sets still fail closed before prepare.
|
|
122
122
|
|
|
123
|
-
Because promotion begins from the already-pushed authoritative mainline, `release integration-status
|
|
123
|
+
Because promotion begins from the already-pushed authoritative mainline, `release integration-status --change <change>` should pass immediately after activation. For a completed managed ship it recovers lineage from private `ship.json`, then from the referenced production/preproduction deployment execution journal; a failure identifies the missing file or field. Run it, then `release end`; no post-release branch merge is required.
|
|
124
124
|
|
|
125
125
|
`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.
|
|
126
126
|
|
|
@@ -32,7 +32,7 @@ Notification manifests live under `src/resources/notifications/` and contain `te
|
|
|
32
32
|
|
|
33
33
|
Data view manifests live under `src/resources/data-views/` and define read-only joined or aggregate query resources. Use `storageMode: "materialized"` for refreshed lists/reports where lag is acceptable, and `storageMode: "live"` for bounded real-time query shapes. Use row views with `sdk.dataView.query` and aggregate views with `sdk.dataView.stats`. Before generating one, confirm freshness tolerance, query bounds, and indexes for materialized filters/sort fields/dimensions/date buckets. Do not use them for single-form CRUD, simple linkedForm selects, writes, write-back, or ad-hoc BI. See `data-views.md` before generating one.
|
|
34
34
|
|
|
35
|
-
App Function manifests live under `src/resources/functions/`, with source in `src/functions/<functionCode>/index.ts`. Use them for reusable server-side logic that pages, automations, and workflows can share through `sdk.function.invoke` or `function_call` nodes. App Functions expose controlled runtime helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles`, and `ctx.platform.api`; they do not expose raw SQL or Redis in the current MVP. Use `ctx.platform.roles.list/findByCode/addUsers/removeUser` for app role lookup and membership changes; raw `ctx.platform.api` returns an HTTP response plus platform envelope. Runtime page invocations also expose trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use that context for server-side authorization, and never trust page-submitted role codes for sensitive actions. Page-call grants should use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`); `roleCodes` is only for current app-role grants and does not support `"*"` or `"all-app-roles"`. App Function form writes are trusted backend writes governed by declared `resources.forms` and function invocation authorization, not by direct page-user submit access to the target form. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` so raw write endpoints are closed. App Function organization writes require `app:organization:manage` on the real operator/audit actor. Use JS_CODE V2 only for node-local workflow/automation scripts.
|
|
35
|
+
App Function manifests live under `src/resources/functions/`, with source in `src/functions/<functionCode>/index.ts`. Use them for reusable server-side logic that pages, automations, and workflows can share through `sdk.function.invoke` or `function_call` nodes. App Functions expose controlled runtime helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.process.startFromExistingInstance/resolveCapabilities/resubmitTask/withdraw/transferTask`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles`, and `ctx.platform.api`; they do not expose raw SQL or Redis in the current MVP. `ctx.process` uses the official workflow services with the real operator and is restricted to the current app plus Function `resources.forms` bindings. Use `ctx.platform.roles.list/findByCode/addUsers/removeUser` for app role lookup and membership changes; raw `ctx.platform.api` returns an HTTP response plus platform envelope. Runtime page invocations also expose trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use that context for server-side authorization, and never trust page-submitted role codes for sensitive actions. Page-call grants should use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`); `roleCodes` is only for current app-role grants and does not support `"*"` or `"all-app-roles"`. App Function form writes are trusted backend writes governed by declared `resources.forms` and function invocation authorization, not by direct page-user submit access to the target form. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` so raw write endpoints are closed. App Function organization writes require `app:organization:manage` on the real operator/audit actor. Use JS_CODE V2 only for node-local workflow/automation scripts.
|
|
36
36
|
|
|
37
37
|
Auth manifests live under `src/resources/auth/`. Use them to enable app-level login methods and bind phone-code/CAS/custom providers to App Functions. Auth provider functions are called only by the platform auth flow. They validate external credentials and return identity assertions such as `phone`, `email`, `externalId`, or `unionId`; they must not issue tokens, set cookies, or mutate platform user/binding tables.
|
|
38
38
|
|
|
@@ -389,7 +389,7 @@ For reusable backend logic that should be shared by pages, automations, and work
|
|
|
389
389
|
|
|
390
390
|
Use JS_CODE V2 when the script is local to one workflow node.
|
|
391
391
|
|
|
392
|
-
Scripts can export `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, `ctx.resources`, `ctx.form`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles.*`, `ctx.platform.api.*`, `ctx.utils`, `require`, `process`, and `Buffer`. Prefer `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, `ctx.connector.call/invoke`, and `ctx.platform.roles.findByCode/addUsers/removeUser` for platform-side work. Use `ctx.organization.departments.*` and `ctx.organization.accounts.*` only for intentional organization-management flows; the real operator/audit actor must hold `app:organization:manage`. App Function and trusted-node form writes are backend operations over declared resources, not direct page-user submit access; keep internal forms closed to raw user submission unless the business requires it. Raw `ctx.platform.api.*` calls return HTTP response plus platform envelope. Legacy data/process bridge methods remain available as `ctx.methods.queryOneData/queryManyData/getDataByFormInstanceId/updateOneData/updateDataByFormInstanceId/updateManyData/createOneData/terminateProcess/getAllParentDepartments`.
|
|
392
|
+
Scripts can export `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, `ctx.resources`, `ctx.form`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles.*`, `ctx.platform.api.*`, `ctx.utils`, `require`, `process`, and `Buffer`. App Functions additionally expose `ctx.process.startFromExistingInstance/resolveCapabilities/resubmitTask/withdraw/transferTask`; these calls are restricted to the current app and declared `resources.forms`, and reuse the real operator's official workflow permission, operation-log, event, idempotency, and replay paths. Prefer `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, `ctx.connector.call/invoke`, and `ctx.platform.roles.findByCode/addUsers/removeUser` for platform-side work. Use `ctx.organization.departments.*` and `ctx.organization.accounts.*` only for intentional organization-management flows; the real operator/audit actor must hold `app:organization:manage`. App Function and trusted-node form writes are backend operations over declared resources, not direct page-user submit access; keep internal forms closed to raw user submission unless the business requires it. Raw `ctx.platform.api.*` calls return HTTP response plus platform envelope. Legacy data/process bridge methods remain available as `ctx.methods.queryOneData/queryManyData/getDataByFormInstanceId/updateOneData/updateDataByFormInstanceId/updateManyData/createOneData/terminateProcess/getAllParentDepartments`.
|
|
393
393
|
|
|
394
394
|
Example `src/js-code-nodes/sync_customer/index.ts`:
|
|
395
395
|
|
|
@@ -119,7 +119,7 @@ Reviewed bundle commands may retain `<profile>` as a template. The explicit real
|
|
|
119
119
|
|
|
120
120
|
Exact `resourceSelectors` are authoritative for supported configuration resources such as `publicAccessPolicies`. A legacy `resources=true` category marker is narrowed by those exact selectors and must not become an app-wide generic-resource release. Missing selectors, unknown resource types, wildcard `*`, deletes, and genuine app-wide resource closures remain fail closed.
|
|
121
121
|
|
|
122
|
-
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. It reads a narrow Runtime head instead of the full app snapshot. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. Immutable Git-base artifact hashes are cached under the Git common directory. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status
|
|
122
|
+
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. It reads a narrow Runtime head instead of the full app snapshot. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. Immutable Git-base artifact hashes are cached under the Git common directory. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status --change <change>` is already satisfied after activation and `release end` does not wait for a later merge. For a completed managed ship, `--change` recovers lineage from private `ship.json` or the referenced production/preproduction deployment execution journal and reports the exact missing file or field when recovery fails.
|
|
123
123
|
|
|
124
124
|
`release app-head` and `runtime releases` return compact summaries by default. Use `--full` only when the complete manifest is required.
|
|
125
125
|
|
|
@@ -103,7 +103,7 @@ Use `workflow pull` to inspect the live definition. Use `workflow list --json` a
|
|
|
103
103
|
|
|
104
104
|
JS_CODE is the backend execution escape hatch for workflow and automation. Use it when the logic must run on the server after a backend trigger, such as a fixed cron schedule, a form date-field schedule, a form submit/update/delete/field-change event, or a workflow approval/process event. It is appropriate for cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and complex orchestration that the frontend cannot handle reliably.
|
|
105
105
|
|
|
106
|
-
App Function is the reusable backend execution model. Use it when the logic should be called by custom pages, multiple automations, workflows, or the runtime API. Source lives in `src/functions/<functionCode>/index.ts`; manifest lives in `src/resources/functions/<functionCode>.json`. Call it from pages with `sdk.function.invoke(code, { input })`, from graph definitions with `function_call`, or from the runtime endpoint `/:appType/v1/functions/:code/invoke.json`. Direct runtime invocation defaults to app automation management permission; ordinary page callers must declare `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`). Prefer `export default async function(ctx, input) {}` for App Function source; the second argument is the invoke input and the same value is available as `ctx.input`. Current MVP exposes controlled helpers only and does not expose raw SQL or Redis. `ctx.form.createOne/updateOne/updateById` are trusted backend writes governed by declared `resources.forms` and function invocation authorization; keep internal forms closed to direct user submit unless the business explicitly needs raw form submission. For internal function-only forms, set form settings `runtimeWrite.mode="function_only"` instead of relying only on missing submit permission groups.
|
|
106
|
+
App Function is the reusable backend execution model. Use it when the logic should be called by custom pages, multiple automations, workflows, or the runtime API. Source lives in `src/functions/<functionCode>/index.ts`; manifest lives in `src/resources/functions/<functionCode>.json`. Call it from pages with `sdk.function.invoke(code, { input })`, from graph definitions with `function_call`, or from the runtime endpoint `/:appType/v1/functions/:code/invoke.json`. Direct runtime invocation defaults to app automation management permission; ordinary page callers must declare `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`). Prefer `export default async function(ctx, input) {}` for App Function source; the second argument is the invoke input and the same value is available as `ctx.input`. Current MVP exposes controlled helpers only and does not expose raw SQL or Redis. App Functions can call `ctx.process.startFromExistingInstance`, `resolveCapabilities`, `resubmitTask`, `withdraw`, and `transferTask`; the bridge uses the current app, declared `resources.forms`, and the real operator's workflow/task authorization, audit, event, and replay contracts. `ctx.form.createOne/updateOne/updateById` are trusted backend writes governed by declared `resources.forms` and function invocation authorization; keep internal forms closed to direct user submit unless the business explicitly needs raw form submission. For internal function-only forms, set form settings `runtimeWrite.mode="function_only"` instead of relying only on missing submit permission groups.
|
|
107
107
|
|
|
108
108
|
For third-party credentials, use top-level metadata-only `secretRefs` plus `definitionJson.version="function_v2"` and `runtimeContractVersion="trusted_node_v2"`; read a declared value with `await ctx.secrets.get(name)` and call public business APIs through `ctx.utils.http`. Never use `process.env` for platform secrets. Values are managed with `openxiangda secret create|rotate --value-stdin --change <change> --profile <name>` and never appear in Git, build output, snapshots, plan diffs, logs, exceptions, or traces. Local tests may use `function test --secret-from-env logical=ENV` only; the value is passed to an isolated child over stdin and is never written to workspace/cache/state. For a Root App transaction, use exact-scope `resource publish function --only <code> --stage-only` and include its verified `stagedResource` in `release app-finalize`.
|
|
109
109
|
|
|
@@ -143,7 +143,7 @@ Inside the TypeScript script, prefer `export default async function (ctx) {}` or
|
|
|
143
143
|
- Data/process methods: `ctx.methods.queryOneData`, `queryManyData`, `getDataByFormInstanceId`, `updateOneData`, `updateDataByFormInstanceId`, `updateManyData`, `createOneData`, `terminateProcess`, and `getAllParentDepartments`.
|
|
144
144
|
- Notification bridge: `ctx.notification.sendByType`, `batchSendByType`, `findConfig`, and `previewTemplate`. Declare templates in `src/resources/notifications/` before using custom `notificationType`.
|
|
145
145
|
- Platform API bridge: `ctx.platform.api.get/post/put/patch/delete/request` for `/openxiangda-api/v1`; role helpers: `ctx.platform.roles.list/findByCode/addUsers/removeUser`.
|
|
146
|
-
- Resource helpers: `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, and `ctx.connector.call/invoke`.
|
|
146
|
+
- Resource helpers: `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.process.startFromExistingInstance/resolveCapabilities/resubmitTask/withdraw/transferTask` for App Functions, `ctx.dataView.query/stats`, and `ctx.connector.call/invoke`.
|
|
147
147
|
- Node runtime helpers: `require`, `process`, `Buffer`, `ctx.utils`, `ctx.utils.http`, and `ctx.console`.
|
|
148
148
|
|
|
149
149
|
Example `function_call` node:
|