Skip to content

Commit c13d17c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-14788-retire-session-user-language
2 parents 22710c4 + 27875fc commit c13d17c

49 files changed

Lines changed: 4987 additions & 449 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
"@objectstack/runtime": patch
3+
---
4+
5+
fix(runtime): a multi-package artifact's collections are read from `packages[]`, not only from the flattened top level
6+
7+
A release artifact composed with `manifest: 'preserve'` carries every
8+
definition twice — flattened at its top level, and again under
9+
`packages[]` (ADR-0130 D4). Only two readers had ever learned the second
10+
half: `ObjectQLPlugin`'s manifest service and the metadata artifact door.
11+
Every other reader said `artifact.<collection>` and nothing else, so an
12+
artifact that carried a collection under `packages[]` alone reached them
13+
EMPTY — and nothing threw. The app booted clean having lost its
14+
declarative actions, its scheduled jobs, its seed data, its object routing
15+
or its default permission set.
16+
17+
`resolveArtifactCollections` — new, and PACKAGE-PRIVATE to
18+
`@objectstack/runtime` — is now the one way this package reads a top-level
19+
collection out of an artifact in either shape. It takes the artifact's own
20+
top-level value first and whole, then adds from each package body — in
21+
`resolveArtifactPackageOrder`'s dependency order — the items the top level
22+
did not already claim. A bundle that carries no `packages[]` is returned
23+
unchanged, by identity: every single-package artifact and every
24+
`defineStack()` config reads exactly as before. Nothing is added to any
25+
package's published surface: `@objectstack/core` is untouched by this
26+
change, and the new module is not named by
27+
`packages/runtime/src/index.ts`.
28+
29+
Where one collection key is spelled two ways inside one artifact —
30+
`functions` is `z.union([z.record(…), z.array(…)])`, so two packages can
31+
each be schema-valid and disagree — the read is REFUSED with an ADR-0112
32+
envelope (`MIXED_ARTIFACT_COLLECTION_SHAPE`, 422) rather than one spelling
33+
being skipped. `composeStacks` already refuses the same mix at compose
34+
time for the same reason.
35+
36+
Taught to use it, in `@objectstack/runtime`:
37+
38+
- `AppPlugin` — declared datasources and their auto-connect, the
39+
`datasourceMapping` object routing, the objects handed to the connection
40+
service and to the hot-reload seeder, scheduled jobs, seed datasets,
41+
translation bundles, and the ADR-0057 security collections
42+
(`positions` / `permissions` / `capabilities` / `sharingRules`). A job
43+
handler's `ctx.bundle` is now the resolved view too, so
44+
`ctx.bundle.objects` answers on a multi-package artifact.
45+
- `collectBundleActions`, `collectBundleHooks` and
46+
`collectBundleFunctionEntries` — including the object-EMBEDDED actions
47+
that ride on `objects[]` and disappeared with it.
48+
- `mergeRuntimeModule` — the declaration half. The sibling ESM module
49+
re-supplies every callable regardless of shape, so `functions` was not
50+
absent: a function declared `effect: 'writes'` simply came back as a bare
51+
callable and defaulted to `'pure'`. It registered, it ran, and its writes
52+
were counted as none.
53+
- `createStandaloneStack`'s surfaced `requires` / `objects` /
54+
`permissions` / `positions`, which drive the CLI's tier resolution, its
55+
engine and storage-driver auto-registration, and the ADR-0056 D7 default
56+
permission set.
57+
- `resolve-project-database`'s project-database tier, which opens the
58+
artifact itself and runs before any stack exists (`os dev`, `os start`,
59+
`os db clean`). Without this a multi-package project silently fell
60+
through to the unified default database instead of the datasource it
61+
declared.
62+
63+
Nothing about what the platform EMITS changes: `composeStacks` and the
64+
artifact format are untouched, and the flattened top level is still
65+
written. This is the reader half of the option-B program (#14512).
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): retire `connector.errorMapping` — eleven authorable keys nothing ever read, one of them spelled like the live `userMessage` channel (#14676, ADR-0049)
6+
7+
<!-- adr-0087: registered connector-error-mapping-removed -->
8+
9+
**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
10+
launch-window convention ships it as `minor`; the migration prescription is
11+
registered under protocol major 18, where `os migrate meta` users will look).
12+
Triage ruling 2026-09-02 on the census card: ADR-0049 enforce-or-remove decides
13+
it — declared-but-unenforced authorable surface with zero measured pull for a
14+
reader comes off.
15+
16+
`ConnectorSchema.errorMapping` carried `ErrorMappingConfig` (`rules`,
17+
`defaultCategory`, `unmappedBehavior`, `logUnmapped`) and its
18+
`ErrorMappingRule[]` (`sourceCode`, `sourceMessage`, `targetCode`,
19+
`targetCategory`, `severity`, `retryable`, `userMessage`) — eleven keys on the
20+
published authorable surface that **nothing read**: measured on `origin/main`,
21+
the only reference outside the declaring file and its unit test was a
22+
type-identity pin. No provider, dispatcher or materializer ever mapped an
23+
external error through the rules, so `unmappedBehavior` configured nothing and
24+
a rule's `userMessage` was never shown to anyone. That spelling is what made
25+
this worse than ordinary dead surface: it is the name of the **live**
26+
API-error channel (`ApiError.userMessage`, the user-facing refusal text a
27+
thrown HTTP error declares), so an author who had read that documentation and
28+
wrote a connector rule reasonably believed they were marking a refusal for an
29+
end user — and the failure was silent in both directions (it validated, it
30+
published, no message was ever shown). Removal resolves the collision by
31+
deletion; the live channel is untouched.
32+
33+
**What is refused:** authoring `errorMapping` on a connector, with any value.
34+
`ConnectorSchema` is a non-strict `z.object`, so the key is a `retiredKey()`
35+
tombstone rather than a bare deletion (a deletion would have stripped it in
36+
silence): authoring it is a `tsc` error (`never`) and a parse error carrying
37+
the prescription, on the base schema and — through
38+
`DeclarativeConnectorEntrySchema`, which `superRefine`s the same shape — on
39+
`stack.connectors[]` and the `PUT /api/v1/meta/connector/:name` door.
40+
41+
**What leaves the public surface:** `ErrorMappingConfigSchema` /
42+
`ErrorMappingConfig` / `ErrorMappingConfigParsed`, `ErrorMappingRuleSchema` /
43+
`ErrorMappingRule`, and `ConnectorErrorCategorySchema` / `ConnectorErrorCategory`
44+
(the enum's only consumers were the two removed shapes; an exported value
45+
schema with no consumer reads as a capability). `api/ErrorCategory` — the
46+
HTTP-response vocabulary — is unaffected.
47+
48+
**What stays, byte-identical:** every other connector key (`health`, `retry`,
49+
`webhooks`, `fieldMappings`, `syncConfig`, `actions`, `triggers`, `provider`,
50+
`providerConfig`, `auth`, …) with its default and its readers.
51+
52+
## FROM → TO
53+
54+
```ts
55+
// before — parsed green; nothing ever read the block, no message was ever shown
56+
defineStack({
57+
connectors: [{
58+
name: 'payments_api',
59+
label: 'Payments API',
60+
type: 'api',
61+
errorMapping: {
62+
rules: [{
63+
sourceCode: 429,
64+
targetCode: 'RATE_LIMITED',
65+
targetCategory: 'rate_limit',
66+
severity: 'medium',
67+
retryable: true,
68+
userMessage: 'The payment provider is busy; try again shortly.',
69+
}],
70+
unmappedBehavior: 'generic_error',
71+
},
72+
}],
73+
});
74+
75+
// after — delete the key; there is no replacement because no error-mapping
76+
// engine exists: a connector's failures reach callers as the provider's own
77+
// errors (ADR-0097). A user-facing refusal text is the API error envelope's
78+
// `userMessage`, declared by the code that throws — not connector metadata.
79+
defineStack({
80+
connectors: [{ name: 'payments_api', label: 'Payments API', type: 'api' }],
81+
});
82+
```
83+
84+
One-line fix: delete the `errorMapping` block; `os migrate meta --from 17`
85+
lists the mechanical edits for existing sources.
86+
87+
The retirement kit:
88+
89+
- `retiredKey()` tombstone on `ConnectorSchema.errorMapping`
90+
(`packages/spec/src/integration/connector.zod.ts`; the section comment
91+
records what the shape was), inherited by `DeclarativeConnectorEntrySchema`
92+
- ADR-0087 registration: `integration/Connector:errorMapping` and
93+
`integration/DeclarativeConnectorEntry:errorMapping` in
94+
`RETIRED_KEYS_BY_MAJOR[18]`; `integration/ErrorMappingConfig`,
95+
`integration/ErrorMappingRule`, `integration/ConnectorErrorCategory` in
96+
`RETIRED_DEFS_BY_MAJOR[18]`; the D2 conversion
97+
`connector-error-mapping-removed` (protocol 18) wired into the step-18 chain
98+
— a pure lossless strip of the block from every `connectors[]` entry, one
99+
notice per connector (the eleven nested keys leave with the block)
100+
- no liveness-ledger row: `connector` is not an enrolled ledger type, so
101+
there is no row to keep or drop
102+
- pin tests (`connector.test.ts`): refusal pins asserting the issue path,
103+
code and prescription on the base schema, the declarative entry, and the
104+
`stack.connectors[]` authoring path; the tsc `never` channel; a
105+
no-materialize pin; the conversion's strip and notice; zero holders of the
106+
seven retired names on every public entry; the ADR-0087 registration
107+
- generated baselines/docs follow the schema (`authorable-surface/`,
108+
`authorable-defaults/`, `api-surface/`, `json-schema.manifest/`,
109+
`declaration-map/`, `export-origins/`, spec-changes, upgrade guide,
110+
reference docs)
111+
- zero authored occurrences in this repo's examples, skills and docs, and
112+
zero hits in objectui at `0d8fd7c`, so no in-repo source changes ride along
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): list-view grouping is server-side — the group header query and the per-group row page compile from the view (#14556)
6+
7+
Maintainer ruling A on objectui#7189 (2026-09-02): grouping on a list view is
8+
server-side. The set of groups and every number in a group header — the count
9+
and any per-group aggregation — are properties of the query, not of the fetched
10+
page; rows inside a group are paged. Grouping one fetched window (the interim
11+
behaviour) rendered two headers (86, 14) or five (31/31/30/7/1) for the same
12+
186 rows in five units depending on row order, and left the rows past the
13+
first window unreachable.
14+
15+
The contract reuses the query shapes the platform already has — no new query
16+
shape, no new engine verb, no new envelope:
17+
18+
1. **The group keys and every header number are ONE aggregate query**
19+
(`EngineAggregateOptions`, executed by `IDataEngine.aggregate`): `groupBy`
20+
is `grouping.fields[].field` in nesting order (a multi-level grouping is a
21+
multi-column `groupBy`), `aggregations` is a `count` node (the group's total
22+
row count, alias `count`) plus the view's declared column summaries mapped
23+
onto `AggregationFunction` — the one aggregation vocabulary datasets already
24+
use — and `where` is the view's composed filter.
25+
2. **The rows inside a group are the existing paged `find`**
26+
(`EngineQueryOptions`) with the group's key predicate AND-ed into the view
27+
filter, `limit` / `offset` per group.
28+
29+
New on the `ui` entry, `view-grouping-query.ts`:
30+
31+
- `compileListViewGroupQuery(view, { where?, depth? })` → the header query;
32+
`compileListViewGroupRowsQuery(view, groupKey, { where?, limit?, offset?, orderBy?, fields? })`
33+
→ the row page; `listViewGroupKeyPredicate` (the empty group is spelled with
34+
the `$null` predicate — the spelling the view filter dialect's `is_empty`
35+
lowers to).
36+
- `COLUMN_SUMMARY_AGGREGATION` — the `ColumnSummary` → aggregation table,
37+
exhaustive by type: `count` → a fieldless `count` (`COUNT(*)`),
38+
`count_unique` → `count_distinct`, `sum` / `avg` / `min` / `max` → the same
39+
name, `none` → nothing; `count_filled` / `count_empty` / `percent_filled` /
40+
`percent_empty` map by derivation — one `{ function: 'count', field }` node
41+
(`COUNT(field)`, the non-null count, header column `count_<field>`), from
42+
which `deriveColumnSummary(row, summary, field)` computes all four on the
43+
header row (`count_filled` = `count_<field>`, `count_empty` = `count −
44+
count_<field>`, `percent_filled` = `count_<field> / count`, 0 when the count
45+
is 0, `percent_empty` = `1 − percent_filled`). Server-side "empty" is `null`
46+
on every face; the footer's client-side reading of `''` / `[]` as empty is
47+
the renderer's to converge. A future member with no counterpart is refused
48+
loudly at compile time (`ListViewGroupQueryError`, `NOT_IMPLEMENTED` / 501,
49+
the summary's path — `UNMAPPED_COLUMN_SUMMARIES`, empty today); a value that
50+
is no member at all is `INVALID_QUERY` / 400.
51+
- Result-column naming on a header row: each grouped field under its own name
52+
(raw stored value, `null` for the empty group; group keys are scalar), `count`,
53+
and each summary under `<function>_<field>` (`columnSummaryAlias`).
54+
55+
`GroupingConfigSchema` / `GroupingFieldSchema` / `ColumnSummarySchema` now say
56+
this in their docs, with the shape's recorded limits (a date grouping field
57+
groups per distinct stored instant; header cardinality is unbounded). Nothing
58+
changes in what parses: no key is added, removed or re-shaped. `minor` because
59+
a new exported helper and a declared contract semantics ship; not breaking —
60+
the page-scoped behaviour was never declared. Both queries ride the existing
61+
`POST /data/:object/query` door (`protocol.findData` → `engine.aggregate`,
62+
answering `{ object, records, total, hasMore }`); the grid consuming the header
63+
rows is objectui#7189.

‎.claude/skills/pm-dispatch/references/platform-readings.md‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@
125125
- **红窗调度**:`until remaining > 阈值` 的守候**只给上面那几件 GraphQL-only
126126
的**(逐件判据与官方文档核对日期住 `references/rest-channel.md`),⛔ 其余一切不为配额空等;
127127
走队列的仓落地必经 auto-merge ⇒ 红窗里**无退路**,直合仓有(合并本身有 REST 端点)。
128+
- **MCP 侧限流下,只有 MCP/GraphQL 做得了的那两件(draft 翻转、auto-merge 挂载)整窗不可得 ——
129+
被这样挡住的翻转是在等窗口,不是关于该 PR 的信号**(2026-09-04 02:17Z 实测:MCP
130+
`update_pull_request` 回 `API rate limit already exceeded for user ID …`,同刻本席 repo-scoped REST 读回满
131+
额;两桶分离本身见上「信封」条,⛔ 不重复)⇒ ⛔ 不据它重挂、不据它改判 PR 状态,其
132+
余(评论、标签、请审、读、timeline)照常走 REST。⚠️ 报文里的 user ID 只是报文:据它
133+
推「MCP 池按身份跨席共享」与本节首条的禁令正面冲突,⛔ 未裁不写成事实。
128134
- **`issue transfer` 因配额或权限拿不到 ⇒ 当轮改走多仓协调条款的「在目的仓重建」
129135
配方**(出处头 + 裸 `#N` 改全名 + 关源单为 moved):纯 REST、配额免疫,⛔ 不为转移空等重置。
130136
- **两个「瘦身参数」都不省池**:`fields` 省载荷不省池 —— MCP list/search 服务器端无条件抓
@@ -154,6 +160,11 @@
154160
单卡读、**不覆盖 issue search**(搜索页无 SSR 结果);⛔ 永
155161
不拿渲染列表定规模(静默只显一页,实测 12 vs 权威 147)。渲染层 WebFetch 仍在(~15 分缓存、
156162
有损)。
163+
- **「本档的缺席不是读数」的成因:本档经 CDN 缓存,专对最新内容失效且绕不掉** —— 内容
164+
可滞后数分钟到数十分钟,实测三次抓取横跨 75 分钟(其中一次带 cache-busting 查询串 +
165+
no-cache 头)都没取到一条已存在 75 分钟的评论(2026-08-30)。⚠️ 它**没有位置性对照**:更早
166+
的内容全在、读起来完全正常,只有最新几条缺席 ⇒ 前 15 项那条判别式在此不成立,唯一
167+
出路是第二通道;失效方向是据它对**别人的工作**下「没有认领」的判词。
157168
- **查重先 `search_issues`,并按它的契约拼**:它是**语义**匹配器 —— ⛔ `query` 里永不放 GitHub
158169
限定符(`repo:` / `is:` / `label:` / `in:title`),范围走 `owner` / `repo` 参数,`query` 写成描述缺陷
159170
的**句子**;限定符形对每个带自由词的查询回 `total_count: 0` 且 `incomplete_results: false`,同一意
@@ -293,6 +304,18 @@
293304
线只写页脚则原样存活(2026-08-28 两仓同轮、两张 PR 全文回读)。**评论不受影响**(两种拼法
294305
都活)⇒ 失效既**依拼写**又**依载体**:评论里验过页脚**对 PR 正文什么都没证明**。⇒ PR
295306
正文页脚**不带前置横线**、且**写后回读正文**(唯一检测手段);评论两形皆可。
307+
- **署名页脚的写侧变异按通道与输入双重定域,⛔ 不是一条定律**(作者侧处方住 AGENTS.md,本
308+
条只定作用域):MCP `update_pull_request` 包装器**删掉** PR 正文的页脚块(2026-09-03 姊妹仓实测:带
309+
与不带页脚两次送出,落库都是 12317 字节、零页脚);裸 REST `PATCH /pulls` **追加**一个裸页脚
310+
并保留既有 session-URL 页脚(送 15726、落 15784、差恰 +58、前缀逐字节相同;2026-09-04 两仓两席
311+
各一次独立观察,后一次全文比对 22,533 字符、两页脚并存)。⚠️ **同一 MCP 包装器上有反
312+
例**:把**已带页脚**的正文整体重送,两条页脚均逐字节存活(2026-09-04 07:02Z,n=1),提出的机理
313+
是「工具只为没有页脚的正文合成页脚」—— ⛔ **未实测**,⛔ 不把这些观察压成一条定
314+
律、不为凑定律改写其中任一句。**第四种组合**:裸 REST `POST /pulls` 建 PR 时,在已带
315+
session-URL 页脚的正文后再追加一条 **session-URL** 页脚(前置 `---`、恰 +90 字节、送出体是存
316+
储体的严格前缀;2026-09-04 实测)⇒ 追加的形态随通道**与动作(建 / 改)**变,⛔ 不由任一条推
317+
其余。⇒ 写后必回读;评论创建两通道都追加同样 58 字节 ⇒ 严格解析 `os-dev-report` 评论必
318+
须停在最后一个右花括号。
296319
- **并行 spec PR 同动 pin 计数断言**(被踢不是事故,按 os-regen 序再解一轮):解冲突两侧收据都
297320
保留、按合并顺序堆叠,新计数**从合并后源码重数**(操作数是文件本身不是历史),⛔ 不从
298321
两侧收据做算术;双方占同一编号是常态(各取当时 max+1),重编号后进侧。
@@ -356,3 +379,19 @@
356379
原则、定时器选型(⛔ 不用 send_later 链)与恢复 playbook 在主文件;事实补遗:`npx ccusage blocks` 容
357380
器内可用(读本地会话记录),报当前 5 小时窗口边界/剩余与燃烧率;盲区:窗口起点是本地推
358381
断的近似值;撞墙报文形如「limit reached, resets at HH:MM」(重置时刻只在此刻可得)。
382+
- **agent 建的 Routine 起的会话不带 `mcp__github__*` 工具**(2026-08-29 两次探针,决断的是第二次:显
383+
式禁止自退休、唯一动作是跑一次 github 查询并发一条评论 —— 回 30s IDLE、评论从未出
384+
现,判别读数是目标卡评论数与 `updated_at` 全程不动,它排除了「正确自退休」这个同形解
385+
释)⇒ **对任何以 GitHub 读写为工作的席位不可用**。⚠️ 2026-09-04 复核本会话工具契约:创
386+
建端现有 `connectors` 参数(声明起的会话可用哪些连接器)⇒ 当时的成因(不存连接器)有了参
387+
数级解法,⛔ **未实测**,⛔ 不据参数在场推「现在能用了」。
388+
- **自绑唤醒的是已存在的会话、工具已加载 ⇒ 上条的传输问题不发生 —— 代价是拿新会
389+
话可恢复性换工具可得性**(`persistent_session_id` 或绑自身):绑定的会话一死,定时器成孤儿、
390+
每小时唤醒一个死会话且长期无人察觉(实测一例躺了 6 天)⇒ **绑定会话一死:先删旧
391+
Routine、再从新会话重建,⛔ 永不留孤儿**;自绑是性能优化(保住一条 prompt-cache 链),⛔ 不是
392+
状态存储,删了重建零成本。
393+
- **创建端无 `sources`、无 `model` 参数**(2026-09-04 复核仍然如此)⇒ agent 建不出带仓库源或钉死
394+
档位的 Routine,起的会话落**默认档**(2026-08-29 实测落在档位裁决之下,两个方向 agent 都改不
395+
动);⚠️ 更新端**今日带** `model`,但只有「每次起新会话」的 Routine 吃得到 —— 自绑的保
396+
持所绑会话的档位直到绑定解除 ⇒ ⛔ 不据「更新端能改档」推「自绑的档位可改」;UI 建
397+
的 Routine agent 改不动(更新端只受理自己建的)。

0 commit comments

Comments
 (0)