Skip to content

Commit cd73b0c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-14529-delete-inert-eslint-disable
2 parents aca7d5c + e6ac0c6 commit cd73b0c

33 files changed

Lines changed: 1545 additions & 240 deletions
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): `getMetaItems` applies the registry read gate itself, so a sweep that reads more than one type per request is scoped per type (#14683)
6+
7+
`getMetaItems` applied no organization gate of its own: whatever `organizationId`
8+
arrived was spent on whatever `type` arrived. The scope of a metadata sweep was
9+
therefore decided per type **by the caller** — which a request carrying one
10+
organization can only get right when it sweeps **one** type. It now resolves
11+
`organizationIdForMetaRead(request.type, request.organizationId)` once, after the
12+
canonical type fold, and both the active-overlay read and the `previewDrafts`
13+
read spend that one resolution.
14+
15+
Three live callers sweep more than one type and could not have been right:
16+
17+
- `getMetaDiagnostics` with no `type``targetTypes` is the whole registry, the
18+
five `allowOrgOverride: true` types and every other declared type together,
19+
under one request-level organization.
20+
- `findReferencesToMeta``request.type` is the **target**; the organization is
21+
spent on `matcher.fromType`, the **sources**, so the target's own registry flag
22+
says nothing about the types actually read.
23+
- the runtime's package export sweep (`assemblePackageManifest`) — every plural
24+
key of `PLURAL_TO_SINGULAR`, with one raw active organization.
25+
26+
**The harm class is resurrection, not concealment**, and which one it is decides
27+
that the registry-gated predicate is the right instrument.
28+
`SysMetadataRepository.history()` filters `organization_id` by strict equality,
29+
so naming the tenant *there* hides an `allowOrgOverride: false` type's rows. On
30+
this path the two `queryByOrg` reads are UNIONed, so naming it can only **add**
31+
and what it adds are the pre-#6190 phantoms: org-scoped rows of types with no
32+
per-org read channel, which `loadMetaFromDb` walks past and
33+
`reportUnhydratableOrgScopedRows` exists to warn about. Read back, they surface
34+
in the admin "Used by" panel and the Studio governance directory, inside a
35+
clearance rendered before a destructive action — where a resurrected row is worse
36+
than an omission because it reads as evidence.
37+
38+
**Why `patch`, from this change's own lineage.** A published `/meta` read door's
39+
row set changing is not a new class here — it is the class this predicate was
40+
born in, and all three landed instances shipped `patch`:
41+
42+
| commit | what changed | level |
43+
|:--|:--|:--|
44+
| `b6c769019` (#9454 / #9727) | the row set every `/meta` read door returns — org rows **added** | `metadata-core`, `metadata-protocol`, `rest`: all `patch` |
45+
| `26f3588fb` (#10340 / #10519) | which partition two spellings read — rows **moved** | `rest`, `metadata-core`: `patch` |
46+
| `67ceb9aef` (#11553) | the same fold-before-scope repair on the dispatcher door | `runtime`: `patch` |
47+
48+
The first of those is the commit that introduced `organizationIdForMetaRead`
49+
itself. Adding the org partition to every read door was `patch`; moving which
50+
partition two spellings read was `patch`; this change — withholding the org
51+
partition from types that never had a read channel for it — is the same class,
52+
one verb further in, and takes the same level.
53+
54+
⛔ Not `minor`, and in this repo that is a statement rather than a rounding
55+
choice. `scripts/check-changeset-no-major.mjs` refuses `major` outright, so
56+
during the launch window a genuinely breaking change ships as `minor` (pre-1.0,
57+
whole-stack lockstep) — #13925 is exactly that, `"@objectstack/core": minor`
58+
carrying a bolded incompatibility banner and an `adr-0087:` marker for a
59+
narrowed published accept set. But the implication runs ONE WAY ONLY, and the
60+
gate's own header is explicit that it does: during the window `minor` is the
61+
union of ordinary new-functionality bumps and banner-marked breaking ones
62+
(`87ad30c10`, `3c1bbd2a8` are new-export `minor`s carrying no banner at all),
63+
so the bump level "tells a consumer nothing about whether the release breaks
64+
them". The carriers of breaking-ness are the bolded banner in the body and the
65+
ADR-0087 disposition — "during the window they are the only signal there is".
66+
67+
⇒ So `minor` here would not claim an incompatibility; it would claim NOTHING
68+
about compatibility, which is precisely the cost the header names. This change
69+
carries neither carrier because it owes neither — nothing is retired, no accept
70+
set narrows, and `check-adr-0087-registration` reads it as non-breaking. The
71+
level is `patch` because the lineage above is `patch` and no export is added,
72+
not because `patch` rebuts something `minor` would have asserted.
73+
74+
**Nothing here is incompatible, and the reason is what the withheld rows are.**
75+
They are the #6190 phantoms: org-scoped rows of types with no per-org read
76+
channel. The platform has refused to mint them since `ac244ad09` / `6155c3c24`,
77+
boot hydration skips them, `reportUnhydratableOrgScopedRows` audits them, and
78+
**every REST `/meta` read door has already withheld them since `b6c769019`**.
79+
The only doors still serving them were the dispatcher list
80+
(`runtime/src/domains/meta.ts:921`) and the runtime manifest and publish-flip
81+
reads (`packages.ts:1160`, `:603`) — so this change aligns those three with the
82+
published `/meta` surface rather than departing from it. A consumer reading
83+
those rows was reading through a door inconsistent with `/meta`, on data the
84+
platform had already ruled dead.
85+
86+
⛔ Not "only a refactor of where the predicate lives" either: the predicate's new
87+
position does change which rows three doors serve. That is why this is a
88+
behaviour entry rather than an internal note — and, per the lineage above, why
89+
the level for it is `patch`.
90+
91+
**Callers that already gate are unaffected, and that is proved rather than
92+
asserted.** `organizationIdForMetaRead` answers either its argument or
93+
`undefined`, so a second application over the same type is a no-op; the load-
94+
bearing half is that it *is* the same type. The REST `GET /meta/:type` list door
95+
gates on `canonicalMetaUrlType(req.params.type)` and passes the raw segment, which
96+
`canonicalizeMetaRequestType` folds through the identical map — the identical
97+
string. The other four `organizationIdForMetaRead` call sites in `rest-server.ts`
98+
reach `getMetaItemLayered` / `getMetaItem` / `historyMetaItem` / `diffMetaItem`
99+
and never this method. `get-meta-items-org-read-gate.test.ts` §3 measures both
100+
halves over the complete accepted-spelling population (61 spellings, derived from
101+
`META_URL_TO_SINGULAR` unioned with the registry) rather than a hand-listed
102+
sample.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@objectstack/lint": patch
3+
---
4+
5+
fix(lint): consolidate five more private "Did you mean?" copies onto the shared `suggestName` (#14577, follow-up to #14268/#14575)
6+
7+
`validate-action-name-refs.ts`, `validate-chart-bindings.ts` and
8+
`validate-searchable-fields.ts` each carried a private `suggest`/`distance`
9+
pair, byte-for-byte re-deriving the edit-distance-only budget that
10+
`object-graph.ts` already exports as `suggestName` (the shared helper
11+
#14268/#14575 consolidated three other rules onto). All three now import
12+
`suggestName` from `./object-graph` and their private copies are deleted.
13+
14+
`validate-ai-tool-references.ts` and `validate-translation-references.ts`
15+
each carry a one-line pre-pass ahead of the private pair — the `action_<name>`
16+
tool-family prefix, and a snake_case namespace-segment match — that is
17+
rule-local knowledge, not the shared helper's business. Both keep that
18+
pre-pass and now delegate the fallback to `suggestName` instead of a private
19+
Levenshtein copy.
20+
21+
The shared helper's containment pre-pass (a candidate that contains the
22+
target, or vice versa, scores ahead of any edit-distance match) is now every
23+
one of these five rules' behaviour too, so a hint may now appear where one was
24+
previously absent — it never removes a hint the private copy gave. Per site:
25+
26+
- `validate-action-name-refs.ts``archive``archive_completed_deals`
27+
(17 edits, over budget) now gets a hint; unaffected cases unchanged.
28+
- `validate-chart-bindings.ts` — the issue's own headline example,
29+
`amount``sum_amount` (4 edits, over the budget of 2) now gets a hint on
30+
a raw-field-instead-of-measure binding.
31+
- `validate-searchable-fields.ts``amount``sum_amount` (4 edits) now
32+
gets a hint on a stale `searchableFields` entry.
33+
- `validate-ai-tool-references.ts` — the `action_<name>` prefix pre-pass is
34+
unchanged and still wins first; a miss with no prefix match now also
35+
reaches `suggestName`'s containment scan (e.g. `knowledge_base`
36+
`search_knowledge_base`), where the old private copy gave nothing.
37+
- `validate-translation-references.ts` — the namespace-segment pre-pass is
38+
unchanged and still wins first; a miss with no segment match now also
39+
reaches `suggestName`'s containment scan (e.g. `amount`
40+
`amountsummary`), where the old private copy gave nothing.
41+
42+
`object-graph.ts`'s helper is untouched (already ruled by #14268/#14575);
43+
`validate-react-page-props.ts` and `validate-rule-schema-formats.ts` stay out
44+
— both are a different contract on purpose (see #14577's triage).
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/runtime": patch
3+
---
4+
5+
fix(runtime): wire `packages/runtime`'s test layer into `check:test-typecheck`, so its 214 test files are type-checked at all (#14504)
6+
7+
`packages/runtime/tsconfig.json` excluded `**/*.test.ts` and `**/*.spec.ts`, and
8+
the package's `typecheck` script was a bare `tsc --noEmit` against that very
9+
config — so no gate anywhere read a runtime test file with a type checker.
10+
Measured at `224f8ea4a0` with the workspace closure built first, rather than
11+
read off the config: `tsc --noEmit --listFiles -p tsconfig.json` puts 899 files
12+
in the program and **0** of the package's 214 `src/**/*.test.ts` among them,
13+
while 79 of its non-test `src/**` files ARE there — so the zero is the
14+
`exclude` line, not a probe that sees nothing. The directional control is
15+
`packages/drivers/driver-memory`, whose tsconfig carries no test exclusion: the
16+
same probe puts 40 of its 40 test files in the program. Under the new
17+
`tsconfig.test.json` the count is **214 of 214**.
18+
19+
Onboarded by *wiring* to the shared mechanism (`scripts/check-test-typecheck.mts`)
20+
the way `objectql`, `rest`, `lint` and the fourteen `packages/plugins/**` are
21+
wired, never by copying it: a sibling `tsconfig.test.json` matching vitest's
22+
real module semantics (`module: esnext`, `moduleResolution: bundler`,
23+
`lib: ["ES2022"]`), named by `typecheck` via `check:test-typecheck --project`.
24+
Strictness and `rootDir` are untouched and inherited; not one `any` and not one
25+
`@ts-expect-error` was added to any test file to open the gate.
26+
27+
**Seeded, not repaired, per this card's triage ruling.** The layer reports 191
28+
errors across 27 files and they are recorded EXACT and shrink-only in the new
29+
`test-typecheck-debt.json`. Every one is pre-existing: no test file is edited
30+
here. The other 187 files carry no entry, so any error they gain is red on
31+
arrival.
32+
33+
This is a CONVERSION rather than a new debt-opening decision. The same program
34+
under the build config's inherited NodeNext reports 206 — exactly the number
35+
`scripts/check-type-check-coverage.mjs` already held for this package in its
36+
per-PACKAGE `TEST_DEBT` ledger — and that entry graduates here, as the pairing
37+
forces. The 206 → 191 step is attributed in both directions with no remainder:
38+
−19 config-tier diagnostics that dissolve under vitest's module semantics
39+
(TS2835 ×13, the TS7006 ×4 cascading above them, TS2550 ×2) and +4 that
40+
collapsing the cascade exposed (TS2322 ×4 in `src/seed-loader.test.ts`,
41+
previously masked by an `any` from the unresolved import).

.claude/agents/os-dev.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ not decoration:**
415415
implementation-first 处置,已发布零消费的能力不因沉没成本获得豁免。
416416

417417
Your recommendation must be justified on all four axes;长远合理性权重恒 ≥50%(2026-09-01
418-
裁「四维分析中,长期合理应该权重最高,至少50%」):推荐以它领起,另三轴合起来投不翻
418+
裁「四维分析中,长期合理应该权重最高,至少50%」):推荐以它领起,其余各轴合起来投不翻
419419
它,按本义读(缩小而非扩大特例/契约增生),⛔ 不为投机扩张背书;四轴冲突时推荐按拉动排
420420
序:实测拉动→长远,零拉动→不扩散,防错破平,安全与难逆恒人工(权重与序都只排推荐,⛔
421421
不授权、不移人工地板);权衡如实呈现交维护者拍板。`main` 在你脚下碎了、依赖未合并、CI

.claude/skills/pm-dispatch/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ pin 前置等运维细则见 `references/seat-post-protocol.md`)。** 判据二
451451
裁决评论引本通道 + ①的权威,随后状态转移与现场裁过的卡同型(录裁 + 转 `pm:queue`,
452452
⛔ 裁不派);每场召唤收尾呈一张**摘要表(卡 · 权威 · 方向)**供维护者追认,
453453
被推翻的行以该卡新裁决评论 + 状态转移执行。
454-
二类(边界:带一个可报点)与三类(地板)照旧现场呈报,走每批 ≤5 与详细写法纪律
454+
二类(边界:带一个可报点)与三类(地板)照旧现场呈报,走常设决裁批流程与详细写法纪律
455455
- **回翻条款**:代裁卡实施中发现契约终究要动 ⇒ dev 停手,
456456
卡回`needs-user-decision` —— 报告分叉,⛔ 永不静默重裁。
457457
- **请示纪律(维护者 2026-08-25:「不要不停的弹出来让我确认,很浪费时间」)**:①方向性授权
@@ -901,8 +901,8 @@ finding 定级换结果态,永不留挂)/ `Blocked-by:` 活性现验(合并一
901901
⛔ 此序只排推荐:分歧块照旧升级,四棱同向置信门与代裁面不变,
902902
细则见 `references/decision-analysis.md`
903903
**长远合理性权重恒 ≥50%**(维护者 2026-09-01 裁,逐
904-
字:「四维分析中,长期合理应该权重最高,至少50%」):四轴冲突时推荐以长远的读数领
905-
起,另三轴合起来投不翻它;权重按该轴本义读 —— 缩小而非扩大特例与契约增生 —— ⛔
904+
字:「四维分析中,长期合理应该权重最高,至少50%」):推荐以长远的读数领起,四轴冲突时
905+
其余各轴合起来投不翻它;权重按该轴本义读 —— 缩小而非扩大特例与契约增生 —— ⛔
906906
不据它为投机扩张背书。与分歧推荐序 compose 不取代:后者按拉动定指向,本条定下限。⛔ 权
907907
重是推荐规则不是授权规则:50% 不把人工地板的事变成可派发的事,安全/权限边界与破坏性
908908
难逆动作照旧恒交维护者。

.claude/skills/pm-dispatch/references/decision-analysis.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
## 常设决裁批流程(2026-08-26 裁;裁决原话与归席见主文件「升级与决策」)
1212

13-
- 每批**恰好 5 张,呈完即停等回批**(2026-09-01
13+
- 每批**恰好 5 张(末批不足照呈),呈完即停等回批**(2026-09-01
1414
裁「每批5张详细解释,不是这样一堆列给我,这个也要写入skills」),⛔ 永不一条消息投多
1515
批;批序:在飞被阻塞 > 运营阻塞 > 用户可见 > 结构性;每卡 = 六项写法 + 四棱块;回批一行
1616
式「1A 2B …」,「X, 但…」= 附带条件随执行落地;裁后四件原子执行不再请示,一口气跑
@@ -48,7 +48,7 @@
4848
**四棱分歧推荐序**(2026-08-27「tong y 4」):②实测拉动⇒荐①长远终态;零拉动⇒荐④不扩散;
4949
③破余下平局向响亮/结构;安全与难逆恒人工;⛔ 只排推荐,分歧照旧升级、代裁面不扩。
5050
**①长远权重下限**(2026-09-01 裁「四维分析中,长期合理应该权重最高,至少50%」):①恒
51-
≥50%,推荐以①领起,另三轴合起来投不翻①;①按本义读(缩小而非扩大特例/契约增生),⛔
52-
据它为投机扩张背书;⛔ 只排推荐不授权 —— 50% 不把人工地板的事变成可派发的事。
51+
≥50%,推荐以①领起,其余各轴合起来投不翻①;①按本义读(缩小而非扩大特例/契约增生),⛔
52+
不据它为投机扩张背书;⛔ 只排推荐不授权 —— 50% 不把人工地板的事变成可派发的事。
5353
**应用仓推荐序特例**(2026-08-31 裁「既然是平台缺陷,就应该等待平台处理」):阻塞源是平台
5454
缺陷 ⇒ 恒荐等待,⛔ 不荐绕行(形状容错/复刻平台规则)、不荐劈半落地。

.github/workflows/ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ jobs:
187187
# the same suite declares are covered by entries the core filter
188188
# already carries).
189189
- 'sdui.manifest.json'
190+
# @objectstack/rest's meta-state-route-doc-spelling test discovers
191+
# its population instead of listing it (#14561): it asks git for
192+
# the authored-prose corpus under content/, docs/ and skills/ and
193+
# judges every file that mentions the route. Verbatim per that
194+
# declaration. `content/**` and `skills/**` above already cover
195+
# two of the three roots; this is the third, and the FIRST entry
196+
# to open the `docs/` root that the WIDTH note above records as
197+
# declined — declined then because no declaration reached past
198+
# `docs/audits/...`, and reached now because one does. The
199+
# narrower audits entry is left as the package that declared it
200+
# spelled it, exactly as the two narrower `skills/` entries were.
201+
- 'docs/**'
190202
191203
test:
192204
# Sharded 6-way BY PACKAGE: a core-touching PR ran the affected suite

packages/lint/src/validate-action-name-refs.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ describe('validateActionNameRefs — list view bulk/row actions', () => {
8282
expect(findings).toHaveLength(1);
8383
expect(findings[0].message).toContain('Did you mean "crm_convert_lead"?');
8484
});
85+
86+
// #14577 — this rule used to carry a private Levenshtein-only `suggest`,
87+
// which gave NO hint here: `archive` → `archive_completed_deals` is 17 edits
88+
// apart, far outside the `max(2, floor(len/3))` budget. Now delegating to
89+
// the shared `suggestName` (#14268), the containment pre-pass catches it —
90+
// the same class of drift as the issue's `amount` → `sum_amount` example.
91+
it('offers a did-you-mean via containment where edit distance alone would not', () => {
92+
const findings = validateActionNameRefs({
93+
objects: [{ name: 'crm_lead', fields: { name: { type: 'text' } } }],
94+
actions: [{ name: 'archive_completed_deals', label: 'Archive', type: 'script' }],
95+
views: [{ name: 'crm_lead', list: { bulkActions: ['archive'] } }],
96+
});
97+
expect(findings).toHaveLength(1);
98+
expect(findings[0].message).toContain('Did you mean "archive_completed_deals"?');
99+
});
85100
});
86101

87102
// These fixtures use the REAL page shape. An earlier version of this suite

0 commit comments

Comments
 (0)