docs(protocol): 按 SettingsService 真实契约重写 config-resolution(#5888) - #6031
docs(protocol): 按 SettingsService 真实契约重写 config-resolution(#5888)#6031hotlong wants to merge 1 commit into
Conversation
…rvice contract (#5888) The page taught a `context.config` API that has zero implementation: `context.config` itself, `setTenant()`, `setUserPreference()`, `admin.setTenantConfig()`, and the `objectstack_tenant_config` / `objectstack_user_preferences` / `objectstack_secrets` tables all return zero hits across packages/, apps/ and examples/. The whole-page audit found the gap is wider than the three symbols the issue named: YAML/JSON config files, NODE_ENV-selected config files, `defineStack({ database, http, features, secrets })`, deep-merge semantics, and bundled external secret managers are equally unimplemented. The capability itself DOES exist, under a different shape: `SettingsService` (@objectstack/service-settings, ADR-0007), backed by `sys_setting` / `sys_secret` and served over /api/settings. So this is a rewrite onto the real signatures rather than a deletion. Corrections of record: - Cascade is five layers (env > global > tenant > user > default), not six. `global` was missing entirely; `runtime` and `file` do not exist. - First non-null layer wins. There is no deep merge and no array merge. - Scope is DECLARED by the manifest, never chosen by the caller — which is why setTenant()/setUserPreference() cannot exist as written. - `tenant_id` is not a column on `sys_setting`. Platform-wide the tenant identity is the organization: sessions carry `organizationId`, and where an object does declare `tenant_id` it is a lookup to `sys_organization`. - Arrays concat on stack composition — the opposite of what the page claimed. The central manifest example now carries an `{/* os:check */}` marker, so it is type-checked against the built spec by check:skill-examples and cannot rot silently the way this page did. Refs #5888
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31115725289 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
队列管家原样重投(第 10 轮巡检)本 PR 于 16:14:03Z 被踢出合并队列( 签名(run 31115725289, 死在 台账依据:#5810 正文「跨仓通用」表第 1 行 —— GitHub Actions runner 丢失 / npm registry 5xx / 网络超时(基础设施抖动,与 diff 无关) ⇒ 已知环境抖动,原样重投。 处置:队列管家原样重投 —— 已重新 enable auto-merge。⛔ 未改代码、未切 ready/draft、未重跑(rerun 复用原合并 ref,对这类红无意义)。平台面 16:03Z 起已恢复(其后启动的 job 让行判据:本 PR 最近 30 分钟内无车道 PM 处置评论(仅 Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31120590926 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ 队列管家拦截(第 17 轮巡检)——
|
| run / job | 结束 | 终报错 |
|---|---|---|
CI 31127637555 → Dogfood Regression Gate(步骤 Verify dogfood shard results) |
20:45:34Z | dogfood matrix aggregate result: abandoned / dogfood-verify result: abandoned → ::error::Gate leg dogfood did not pass (result: abandoned) |
| Lint & Type Check 31127637554 | 20:44:04Z | 零 job 级 failure(ESLint success、TypeScript Type Check cancelled)—— run 级 failure 属生命周期状态 |
该 CI run 内 **14 job:filter + Test Core (1..3/3) + 聚合 Test Core + Console Pin Gate + Dogfood Regression Gate (1/3) 全部 success,5 个 job 因队列重建 cancelled,唯一 job 级 failure 是聚合门禁本身。零测试失败。
判读:ci.yml 的聚合门禁白名单是 success|skipped|cancelled,队列重建丢弃分片后聚合读数为 abandoned,落进 *) 兜底判红 ⇒ 假红踢出。这是同一族的第 5 例(#6010 17:32Z / #6012 18:54Z / #6013 19:06Z / #6042 20:13Z / 本 PR 20:45Z)。
台账依据:#5810 正文四张签名台账均无该行(mongodb-memory-server / Test Core 5000ms / 冷缓存 / 跨仓通用基础设施抖动 —— 都不是本签名)。台账 ⛔ 只有人工可升级,本座位已连续第四轮在锚点单提请。未裁定签名一律拦截(试点判据 2:零「新签名被原样重投」事故)⇒ 本轮不重挂 auto-merge。
Failed to resolve action download info / Service Unavailable(死在 Set up job、零测试执行),命中跨仓通用表「基础设施抖动」行,有台账依据;本次签名不同,无台账依据。
建议动作(归 PR 所属车道,本座位不碰代码):本 PR diff 无问题,等 abandoned 门禁缺口裁定(治理单 #6082 已转 needs-user-decision)或车道自行判断后重投;⛔ 重跑无效(rerun 复用原合并 ref)。
让行判据:本 PR 最近 30 分钟内无车道 PM 处置评论(最近一条为 19:29:10Z 的 merge-queue-triage workflow 自动评论)⇒ 让行不成立,本座位处置。
⛔ 本轮未合并、未切 ready/draft、未重投、未重跑、未 force、未改代码、未动认领。
Generated by Claude Code
Fixes #5888
结论先行
取证结果指向 issue 正文的处置 2(能力换了形状),而不是处置 1(能力从未落地):
context.config.*API 零实现;SettingsService(@objectstack/service-settings,ADR-0007),存储是sys_setting/sys_secret,对外是/api/settings。所以本 PR 是按真实签名重写,不是删页。页面本身必须保留:
content/docs/protocol/kernel/meta.json收录它,protocol/kernel/index.mdx链它两次,plugin-spec.mdx结尾链它一次。改动量:
269 +/810 −,单文件。这是一次近乎全量的重写,请按此预期审阅。取证过程(逐符号对 origin/main 验存)
扫描面
packages/ apps/ examples/的*.ts/*.tsx。阳性对照(证伪「扫描器坏了」):同一扫描面对页面自己点名的
envKeyOf命中 8 处(packages/services/service-settings/src/settings-service.types.ts:247等),coerceEnvValue命中 6 处(settings-service.ts:1255)。零命中因此成立。零实现符号(含分诊补充的 5 处)
context.config(整个对象)setUserPreference()setTenant()admin.setTenantConfig()objectstack_tenant_configobjectstack_user_preferencesobjectstack_secretsConfigValidationErroraws-secrets-manager+ 四家外部密钥托管AwsKmsCryptoProvider等在contracts/crypto-provider.ts只作为宿主自备的注释出现,仓库不含实现).meta({ secret: true })encrypted)onSave整页核对额外发现的幻影(超出 issue 与分诊清单)
objectstack.config.{ts,js,mjs}(packages/cli/src/utils/config.ts:57-61)。.yml/.json零命中,objectstack.config.{env}.ts/.production.ts按NODE_ENV选文件零命中。原页 264–271 / 319–378 / 762–798 整段据此删除。defineStack()不收那些键:ObjectStackDefinitionSchema的顶层键是元数据集合(objectsappsviewsdatasourcespluginsserver…),没有database/http/features/stripe/secrets。defineStack对未声明键是 warn + 丢弃(warnUnknownAuthoringKeys),原样照抄会静默失效。plugins形状相反:真实是plugins: z.array(z.unknown()),不是{ enabled: [...] }。COMPOSE_KEY_DISPOSITIONS里plugins: 'concat',所有数组集合都是 concat。SettingsService.get()取 cascade 链上第一个非空条目,不做任何合并。原页 402–476 整段是幻影。env > global > tenant > user > default。原页把global整层漏掉,却多出了不存在的runtime和file。词表纠正(
tenant_id→ organization)sys_setting自身没有租户列,租户行靠scope='tenant'区分。全平台租户身份是 organization:session 携带organizationId,引擎只在对象确实声明了tenant_id列时才据此打戳(packages/objectql/src/plugin.ts:798)。全仓声明tenant_id字段的对象只有一个 ——sys_audit_log,且它是Field.lookup('sys_organization', …)。补充一点比 issue 更精确的表述:issue 说「
tenant_id不是任何现存表的列名」略微过头,它确实作为驱动层列存在一处;但实质成立 —— 它从不是sys_setting的列,且语义上就是 organization。重写后的内容(全部可验)
SettingsService的get/getNamespace/createClient/set/setMany/resetNamespace真实签名;envKeyOf命名规则与coerceEnvValue强制类型;#5204 的「越界 env 值被忽略而非修复,且不产生 cascade 条目」;sys_setting行标识(namespace, key, scope, user_id);encryptedspecifier 经ICryptoProvider落sys_secret、LocalCryptoProvider用 AES-256-GCM 且生产环境 fail loud;/api/settings四条真实路由;错误码表SETTINGS_LOCKED/SETTINGS_UNKNOWN_KEY/SETTINGS_UNKNOWN_NAMESPACE。「scope 是声明出来的,不是调用方选的」 单列一节 —— 这正是
setTenant()/setUserPreference()按当前架构不可能存在的结构性原因:setMany从reg.scopes.get(key)取 scope 落盘,调用方无从指定。关于「planned」措辞的边界
按 PM 指示,⛔ 未写任何「未来将有」的路线图散文。未落地的能力收敛成页尾一个 4 条的现状说明(
Outside this contract),每条陈述现状而非承诺。原页已有的os configCLI「planned」告示保留同样处理。防复发:给核心示例加了
os:check页面中心的 manifest 示例加了
{/* os:check */}标记,check:skill-examples会把它抽出来对已构建的 spec 声明做tsc --noEmit。反向验证(先定方向、预期 red):把
scope: 'tenant'改成scope: 'organization'后重跑,门确实变红并点名:已还原。标记是活的,不是惰性装饰 —— 这页以后再漂移会当场红。
自验(全部前台阻塞执行,重装后完整重跑)
check:nul-bytes(+ 自检)OK (scanned 5763 tracked text file(s))check:doc-authoring✓ 362 files cleancheck:docs-audit-scope✓ scope 与 content/docs/ 同步:178 篇check:skill-examples✅ 208 prose examples type-check,且本页:92确认被抽取pnpm --filter @objectstack/docs build(= CIBuild Docs)Build Docs抓到一个轻量门看不见的真实缺陷:首版文件尾部混入了两行工具闭合标签,MDX 报Unexpected closing slash。已修,并完整重跑至绿。Changeset
按派发令走 docs-only 路线,不提交空 changeset;本 PR 不发布任何包,请验收时打
skip-changeset。Generated by Claude Code