Skip to content

Commit e58fc6b

Browse files
committed
Merge origin/main into the feature-axis charter branch
Two conflicts, both at the 取卡全序 lines in the pm-dispatch charter, and both sides are the maintainer's: main's newly ruled tiers (直派插队卡 and 契约面卡 ahead of the label ladder) and this branch's roadmap position inside the ladder. Resolved by composing them rather than choosing — main's order line verbatim, and main's ladder line with 功能点位次 inserted after `pm:blocking`, which is exactly where this branch's own line had it. ⚠️ One deviation from the prescribed wording, because the prescribed line cannot land: composing into main's ladder line, tie-break clause included, measures 134 bytes against the 120-byte per-line rule that check:pm-skill-ratchet enforces on these files, so the gate would go red. The tie-break stays on the following line instead, where this branch already carried it — every rule is stated exactly once, nothing is duplicated, and the ladder line lands at 108 bytes. In core-rules.md, which has no such following line, the summary loses that tie-break clause; the full rule stays in SKILL.md, which is what that file's own header prescribes. Everything else takes main's side, and nothing else of this branch changes. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-Authored-By: Claude <noreply@anthropic.com>
2 parents 410c37f + ea64bbc commit e58fc6b

57 files changed

Lines changed: 3237 additions & 438 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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/core': patch
4+
'@objectstack/runtime': patch
5+
---
6+
7+
feat(spec): one declaration per version grammar — eight regex carriers of "the version of a package or plugin" now reference three exported constants
8+
9+
Clause-②: yes (widening)
10+
11+
**No accept set moves, and that is the whole point of this change.** Eight sites
12+
spelled a version regex out as a literal of their own. Five of those spellings
13+
were byte-identical to each other, two more were byte-identical to each other,
14+
and the eighth stood alone — three accept sets written eight times, growing on
15+
their own: three of the eight were published schema declarations with no parse
16+
caller at all, added by authors who copied a neighbour's literal. Each site now
17+
references the constant carrying the pattern it already enforced, byte for byte.
18+
A ninth in-repo carrier of the same concept spelled no regex at all:
19+
`PackageManifestSchema.version` is a bare `z.string()`, and it stays one here.
20+
21+
`@objectstack/spec/kernel` gains three exported patterns:
22+
23+
- `MAJOR_MINOR_PATCH_VERSION_PATTERN` — three numeric segments and nothing
24+
else. Referenced by `ManifestSchema.version`,
25+
`MetadataPluginManifestSchema.version`, `PluginRegistryEntrySchema.version`,
26+
`PluginMetadataSchema.version`, and the `PATCH /api/v1/packages/:id` door in
27+
`@objectstack/runtime`.
28+
- `SEMVER_SHAPED_VERSION_PATTERN` — `major.minor.patch` with an optional
29+
`-prerelease` and an optional `+build` suffix, identifiers in either ASCII
30+
case. Referenced by `PluginSchema.version` and by
31+
`PluginLoader.isSemverShapedVersion` in `@objectstack/core`. Those two
32+
converged on one spelling under the widen-never-narrow ruling and were held
33+
equal by hand until now; they reference one declaration and can no longer
34+
drift apart.
35+
- `SEMVER_SHAPED_LOWERCASE_VERSION_PATTERN` — the same with the suffix
36+
identifiers restricted to lowercase ASCII. Referenced by
37+
`PackageVersionSchema.version`.
38+
39+
⛔ **The three are not interchangeable** — they are three different accept sets,
40+
and referencing the wrong one moves a published accept set. None of the three is
41+
a SemVer 2.0.0 conformance check and none is named as one: two accept forms
42+
SemVer forbids (leading zeroes in the numeric core, empty and leading-zero
43+
identifiers), one refuses forms it requires. For ordering or precedence,
44+
`dependency-resolver.ts` in `@objectstack/core` is still the module to extend.
45+
46+
**Nothing an author can write changes.** Every regex is byte-identical to the
47+
literal it replaces — verified per carrier by sha256 over the extracted literal
48+
— and every existing suite passes unedited. Those two together are the
49+
neutrality proof, and they are the whole of it. `PackageManifestSchema.version`
50+
keeps its bare `z.string()`; it is deliberately untouched here. No `.describe()`
51+
text, refusal message or JSON Schema `pattern` moves. Regenerating the spec's
52+
artifacts moved `api-surface/kernel.json` and `export-origins/kernel.json` and
53+
nothing else, each gaining the three constant names — ⛔ read that as a check
54+
that nothing unexpected regenerated, never as evidence about the accept set: the
55+
artifacts that stayed byte-unchanged do not record a `.regex()` pattern in the
56+
first place. A new pin,
57+
`src/kernel/version-grammar.test.ts`, records each grammar's verdict on twelve
58+
witness strings so the next deliberate move to any of them is one visible edit
59+
to one matrix.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
`lintLivenessProperties` now reports a liveness ledger it could not read, instead of going silent.
6+
7+
`loadWarnMap` returned the same empty map for two different facts: "this metadata type's ledger classifies nothing as warn-worthy" and "there is no ledger". A missing `<type>.json` and a file whose JSON is broken both returned an empty map with no log, no throw and no other signal, so losing or corrupting ONE file under the `liveness/` directory `@objectstack/spec` ships switched every author warning for that type off in silence — indistinguishable from that type simply having no warnings.
8+
9+
The contrast that makes it a defect rather than a design sits one frame up: the DIRECTORY-level failure is loud by construction (the rule returns `[]` and everything depending on it goes red). Loud by directory, silent by file.
10+
11+
What changes for consumers:
12+
13+
- A new rule id, `LIVENESS_LEDGER_UNREADABLE` (`'liveness-ledger-unreadable'`), exported from the package root beside the four verdict ids. It is not a fifth verdict: the other four grade a property the ledger DID classify, this one says the classification never arrived, so a finding carrying it means no other finding about that metadata type can be trusted. Compare `f.rule` against the constant rather than retyping the slug. It cannot be silenced per finding: the CLI has no per-rule suppression, and `suppressWarnings` is a dashboard-widget key (`spec/src/ui/dashboard.zod.ts`) while this finding's subject is a ledger rather than an authored item, so there is nothing to carry it. The remedy is the one the finding's own hint names — repair or reinstall `@objectstack/spec`.
14+
- `lintLivenessProperties` raises exactly one such finding per unreadable type, per run — never one per authored item — ahead of the walk's own findings, and keeps walking every type whose ledger IS readable. On an intact installation nothing changes: no ledger is missing, so no finding is added.
15+
- A ledger that parses but is not a ledger (a bare `null`, an array, a scalar, or a document with no `props` record) is the same reported fault. Reading `.props` off a parsed `null` used to be a `TypeError` — a throw from a rule whose contract is that it never throws, through the one input an author cannot influence.
16+
17+
`authorWarnedProperties` still answers the empty set for a ledger it cannot read — a decision procedure returning a set has no way to report a failed read — and that is unchanged for a missing file and for broken JSON. One input does move: a ledger document that parses to `null` used to make it THROW, and it now returns the empty set like the other two. `os lint` runs both halves in one pass, so the run states the fault once rather than never.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
"@objectstack/lint": patch
4+
"@objectstack/rest": patch
5+
---
6+
7+
Four consumers of the implicit-reference-target contract resolve a reference field's target through `referenceTargetOf` instead of the materialized `reference` carrier, so a `{ type: 'user' }` field authored without one seeds, serves, and lints as the fully specified metadata the spec says it is (#19289).
8+
9+
`IMPLICIT_REFERENCE_TARGETS` (`@objectstack/spec/data`) says a `user` field's target is "a CONSTANT OF THE TYPE, so `reference` on a `user` field materializes that constant; it does not supply it. Metadata authored without it (hand-written JSON, an AI author, a Studio form) is **fully specified, not under-specified**." Two arbiters answer two different questions — `referenceCarrierOf` what the carrier says, `referenceTargetOf` what the field points at — and for `user` only the second matches that text. #18550 standardized a population of readers on the first, which is correct wherever a site's own type gate excludes `user` and wrong wherever it does not. This is the census of that population: 17 carrier call sites judged one by one, four repaired.
10+
11+
Clause-②: no
12+
13+
Not a widening. It deletes a mistaken refusal of metadata the published contract already declares complete, which the charter files as `no` — 「删已发布契约文本本就否定的误拒本身是 `no`」. No key, alias or spelling is newly accepted anywhere: the target comes from the spec's own constant, never from a second way of writing it.
14+
15+
- **`@objectstack/rest` — the loud one.** A `publicPicker` on a spec-complete `{ type: 'user' }` field answered `500 LOOKUP_TARGET_MISSING`, so opening a reference picker on a "responsible person" column returned an error page. It now answers `200` over `sys_user`. ⛔ This is not a re-widening of #12920's narrowing: a stored def spelling the target `referenceTo` / `target` / `options.objectName` still resolves nothing and still answers `500`, pinned in both directions.
16+
- **`@objectstack/metadata-protocol` — the silent one, and the one that stored a wrong value.** A seed row's `{ type: 'user' }` field contributed no `dependsOn` edge and never reached `references`, so its natural key was written **verbatim** into a column that holds a record id — the dangling reference `buildDependencyGraph`'s own docblock names as the cause of broken parent joins. ⚠️ Upgrading seed authors: such a field now takes the same path the explicit `reference: 'sys_user'` spelling always took, which includes the failure path — a natural key that resolves to no `sys_user` row now DROPS the whole record, counted, reported and logged at `error`, where it was previously written verbatim. Seed `sys_user` before the referencing object, enable `multiPass`, or fix the key.
17+
- **`@objectstack/lint` — the widest.** `object-graph`'s field slice fed `resolveFieldPath`, whose `RELATIONSHIP_FIELD_TYPES` admits `user`; a carrier-less one answered `hop-untargeted`, which `isUnjudgeable` treats as "the graph could not answer". Every rule in the package that resolves a field path therefore stopped judging any path through such a field, reporting nothing. `validate-field-consumers` separately dropped the `displayField` consumer edge onto `sys_user`, so a field that column displays was reported consumed by nobody.
18+
- **Nothing else widens.** `user` is the only member of `IMPLICIT_REFERENCE_TARGETS`, so a `lookup` / `master_detail` / `tree` whose author-chosen target is absent still names nothing, exactly as before — pinned at every repaired site.
19+
- **The unreadable-carrier behaviour is unchanged.** `referenceTargetOf` reads the carrier through `referenceCarrierOf` **before** it judges the type, so #13053/#18550's `TypeError` on an object- or array-valued `reference` still fires everywhere it fired before. The implicit target is not a fallback that swallows it.
20+
- **No authoring change.** Metadata that already spells `reference: 'sys_user'` resolves to the same target it always did; nobody has to restate the constant, and nobody has to stop restating it.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
**BREAKING for runtime metadata writes** — the ADR-0090 D3 vocabulary freeze (`security-role-word`) now runs at the runtime publish gate for all six collections it judges, so `position` and `app` writes are gated for the first time (#19370)
6+
7+
Clause-②: no (narrowing)
8+
9+
`validateSecurityRoleWord` moves from `surfaces: ['cli']` to
10+
`['cli', 'runtime-publish']` and declares
11+
`runtimeTypes: ['object', 'permission', 'book', 'position', 'app']` — the write
12+
type of every collection it judges. `position` and `app` join
13+
`TYPE_TO_STACK_KEY` in `runtime-gate.ts` so the gate can build a per-write
14+
snapshot for them.
15+
16+
**The refusal set grows.** A runtime metadata write — Studio's designer, REST
17+
`/meta`, an MCP/AI author — that carries the reserved word `role` in a
18+
security-relevant identifier or label is now refused with the 422 lint envelope
19+
instead of stored. Concretely, these used to succeed at that door and no longer
20+
do:
21+
22+
- an object, field, action or field-group header named or labelled for `role`;
23+
- a permission set named or labelled for `role` (e.g. `role_manager`);
24+
- a documentation book named or labelled for `role`;
25+
- a **position** named or labelled for `role` (e.g. `sales_role`);
26+
- an **app** named or labelled for `role` (e.g. `role_hub`).
27+
28+
The platform vocabulary the rule freezes is unchanged and so is its fix-it text:
29+
`permission_set` for capability, `position` for distribution, `business_unit`
30+
for hierarchy. Nothing is renamed, retired or added — this is the same rule,
31+
with the same rule id and the same findings, now enforced at the fourth door as
32+
well as by `os validate` / `os build` / `os lint`.
33+
34+
**Nothing changes for the three CLI commands.** Both security entries have run
35+
on all three since the #8310 split, and their union is byte-identical to before.
36+
37+
**Stored rows are untouched** (#4463 D4: the gate blocks new writes, never the
38+
read path), and `OS_ALLOW_UNLINTED_METADATA_WRITES=1` remains the migration-window
39+
escape hatch for a tenant that authored one of these names before this landed.
40+
41+
Why the two types were held back until now, and why the wait ended: `position`
42+
and `app` are `allowRuntimeCreate: true`, so a position called `sales_role` could
43+
be minted through the one entrance a tenant has while an object of that name was
44+
refused. Under #7220 one rule id sits on ONE side of the wall, so the rule was
45+
split out and held back whole rather than wired for a subset of its collections.
46+
Mapping the two write types is what lets it cross, also whole.
47+
48+
Deliberately NOT done: carrying `positions` / `apps` as `RuntimeStackContext`
49+
collections. A collection joins that context because some rule resolves
50+
references into it; this rule resolves nothing — it judges each identifier and
51+
label on its own — so a sibling position tells it nothing about the written one
52+
and its finding cancels in the gate's differential either way. Carrying them
53+
would cost the publish door one indexed `sys_metadata` read per write for no
54+
verdict change.
55+
56+
<!-- adr-0087: not-required (no-migration-prescription) nothing is retired, renamed or added: no authorable key changes, no stored shape is rewritten, and `objectstack migrate meta` has nothing to reach. The rule, its rule id, its vocabulary and its fix-it text are unchanged since ADR-0090 D3; only the surface it runs on widens. An affected tenant renames its own metadata, which is tenant data rather than a spec migration. -->
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/platform-objects": patch
3+
---
4+
5+
The standalone `field` metadata-form panel no longer prints English column heads and tooltips to a Chinese, Japanese or Spanish author: nine keys — eighteen string leaves — were decided leaf by leaf and rendered in `zh-CN`, `ja-JP` and `es-ES` (#19403).
6+
7+
`Placeholder`, `Value Domain`, `Rows`, `Related List Filter` and the five row properties of `summaryOperations` (`Object`, `Function`, `Field`, `Relationship Field`, `Filter`) read their English source in all three locales, `helpText` included. An en-echo is not automatically a defect, so each leaf carries a recorded reason in `field-panel-echo-decisions.test.ts` rather than a bulk rewrite — and fourteen of the eighteen had an **authored twin at the same key path**: `object.fields.fields.*` is the same field editor embedded in the object panel, rendered there and echoing here, with seven of the twins byte-identical in `en`.
8+
9+
- **Machine tokens stay English, checked at the schema before a word was rendered.** `valueDomain.helpText` names `iana_time_zone`, `iso_4217_currency` and `iso_3166_alpha2` — the three members of `ValueDomainSchema`, a `z.enum`. Rendering them as words would tell an author in their own language to write a token the schema refuses. Kept, as are `count` (a `summaryOperations.function` enum member), the spec key `inlineHelpText`, the operator `AND` and the worked example `status == received`.
10+
- **Values only.** No key was added or removed: the three translated bundles are 18 insertions / 18 deletions each, the full flattened key sets are identical on all four bundles (893 keys, 0 added, 0 removed), and `en` is untouched. Regenerated with `pnpm i18n:extract`, which dropped the 18 provenance rows per locale that recorded these leaves as unauthored extractor fills.
11+
- **The panel is now pinned by a derived population**, so a key added to `fieldForm` tomorrow is judged on the day it lands rather than a round later.
12+
13+
Measured on the metadata-form catalogs: label keys echoing in all three locales fall **38 → 29** while the genuinely-translated control rises **500 → 509** (`zh-CN`) and **484 → 493** (`ja-JP`, `es-ES`), same population, same run.

‎.claude/agents/os-dev.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ model: opus
9191
5. ⛔ 永不按进程名杀(`pkill -f` 会带走并行 agent 的运行);记下你启动的 PID,只对它操作。
9292
6. **整条流水线在前台跑。** build 与 test 都是本任务的步骤:阻塞运行、读真实输出、继续。
9393
- 宿主事实:你启动的后台作业、watcher、你请求的通知都不会唤醒你;结束一轮就是结束。
94-
- 有可展示内容即 commit、push 并开 draft PR,不等验证结束;验证结果到达即写进报告。
94+
- 每个可编译小步即 commit + push;有可展示内容即开 draft PR;接管只认远程分支最后 sha。
9595
- 带具名缺口的 PR 是交付进行中的常态,未读到的判决写 `NOT MEASURED: <family>, reason: …`。
9696
- 平台事实:容器把前台命令钉在约 10 分钟上限,超时 SIGTERM 杀掉(`exit 143`)。
9797
- 上限划定前台里放什么:重活走规则 1 的锁;仓级扫描归 CI(见本地验证范围节)。

‎.claude/settings.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
"Bash(curl -sS -X PATCH https://api.github.com/repos/objectstack-ai/objectui/pulls/* *)",
5151
"Bash(curl -sS -X POST https://api.github.com/repos/objectstack-ai/objectstack/pulls/*/ccr/ready_for_review *)",
5252
"Bash(curl -sS -X PUT https://api.github.com/repos/objectstack-ai/objectstack/pulls/*/ccr/auto_merge *)",
53+
"Bash(curl -sS -X POST https://api.github.com/repos/objectstack-ai/objectui/pulls/*/ccr/ready_for_review *)",
54+
"Bash(curl -sS -X PUT https://api.github.com/repos/objectstack-ai/objectui/pulls/*/ccr/auto_merge *)",
5355
"Bash(node --use-env-proxy scripts/pm/post-stamped.mjs *)",
5456
"Bash(node --use-env-proxy scripts/pm/label-write.mjs *)",
5557
"Bash(node scripts/pm/post-stamped.mjs *)",

0 commit comments

Comments
 (0)