Skip to content

Commit 338dc51

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-15213-s3-authoring-page
2 parents 99fc3a8 + abf8655 commit 338dc51

38 files changed

Lines changed: 2614 additions & 457 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
"@objectstack/lint": patch
3+
---
4+
5+
fix(lint): 17 more author-time rule ids print a `fix:` line of one remedy sentence, and `os explain RULE_ID` carries what those lines used to explain
6+
7+
Clause-②: no
8+
9+
- **Shorter fix lines.** Each finding of these 17 rule ids now carries a `hint` (the CLI's `fix:` line, the runtime issue's `hint`) of one remedy sentence of at most 200 characters on every variant the lint suite fires (the lengths here are the `hint` alone; the CLI prints it after `fix: `). The longest of each, before → after:
10+
- list-view search: `searchable-field-unknown` (448 → 129), `searchable-field-unprovisioned` (362 → 175) and `searchable-field-unsearchable` (328 → 139);
11+
- list-view sort: `sort-field-unprovisioned` (378 → 191), `sort-field-unsortable` (332 → 154) and `sort-field-unknown` (301 → 142);
12+
- visibility predicates: `visibility-predicate-unknown-function` (375 → 165), `visibility-bare-identifier` (221 → 102) and `visibility-predicate-syntax` (214 → 113);
13+
- flow filters: `flow-filter-token-unknown` (427 → 182);
14+
- actions: `action-no-placement` (424 → 171);
15+
- import mappings: `mapping-target-field-unknown` (405 → 189);
16+
- dashboards: `unconsumed-widget-option` (396 → 135);
17+
- app navigation: `nav-object-ungranted` (370 → 118);
18+
- hook bodies: `hook-body-write-unprovisioned-anchor` (365 → 178), `hook-body-write-unknown-field` (275 → 110) and `hook-body-source-unparseable` (254 → 105).
19+
- **One line, one remedy.** Where a fix line explained its remedy, the explanation moved: why a stale or inadmissible `searchableFields` entry is a `400 INVALID_FIELD` on list search, that a view narrows the object's searchable set and never widens it, and that a lookup column stores only an id; that both runtime doors refuse a formula sort with `400 INVALID_SORT`; how to vouch for an unprovisioned anchor (`columnMap`) or opt the object out of the injection (`ownership: 'none'`, `systemFields: { audit: false }`); the two vocabularies a flow filter token resolves in; the other-language operator spellings CEL does not parse, why re-spelling cannot fix an unregistered function, and which namespaces each surface binds; why gating a nav entry does not clear a missing grant; which hook-body write shapes are checked; what a partially parsed body leaves unchecked.
20+
- **A list the verdict already covers is not echoed.** `searchable-field-unknown`, `sort-field-unknown` and `mapping-target-field-unknown` no longer list every field of the object: the verdict's "Did you mean" names the near miss. `mapping-target-field-unknown` still lists the object's compound fields and their parts, as every import-mapping refusal does.
21+
- **`os explain RULE_ID` carries the rest.** What the fix lines stopped saying is each id's `RULE_EXPLANATIONS` entry in `@objectstack/lint`, which `os explain RULE_ID` prints. Two ids had no entry and gain one, so their `rule:` line now ends with the `os explain` pointer: `action-no-placement` ("where an action can be placed") and `unconsumed-widget-option` ("which widget options a renderer reads"). Five entries gain text. The action locations the new entry spells out are held to the spec's `ACTION_LOCATIONS` by the rule's test.
22+
- **At the runtime publish gate** (Studio, REST `PUT /api/v1/meta`, and the MCP and AI authors that write through that door), 10 of the 17 fire; the `hint` FROM → TO, measured at the door:
23+
- a `view` write: `sort-field-unknown` (422 `issues[].hint`, a name / a dotted path 114 / 261 → 47 / 135), `sort-field-unsortable` (422, 318 → 140), `sort-field-unprovisioned` (2xx `advisories[].hint`, 363 → 176), `searchable-field-unknown` (422, a name / a dotted path 281 / 430 → 48 / 129), `searchable-field-unsearchable` (422, a formula / outside the declared set / a lookup / a json column 222 / 142 / 323 / 157 → 110 / 68 / 135 / 64), `searchable-field-unprovisioned` (2xx advisory, 361 → 174), `visibility-predicate-syntax` (422, a recognised spelling / none 106 / 214 → 106 / 107), `visibility-predicate-unknown-function` (422, 371 → 165) and `visibility-bare-identifier` (422, a bare name / an unbound namespace 160 / 215 → 41 / 96);
24+
- an `object` write: `searchable-field-unknown` (422, the object's own set / a built-in list view 266 / 261 → 55 / 50), `searchable-field-unsearchable` (422, 222 → 110) and `searchable-field-unprovisioned` (2xx advisory, 361 → 174);
25+
- a `flow` write: `flow-filter-token-unknown` (422, `{TOMORROW()}` / `{ROUND(x)}` / `{Math.round(x)}` / `{TODAY() - 45 - 10}` 336 / 358 / 427 / 100 → 91 / 113 / 182 / 100).
26+
27+
Studio's save-advisory toast prints an advisory's `hint` after its message, so the two `*-unprovisioned` toasts shorten; Studio renders no 422 issue's `hint`. No Studio, REST or MCP reader in this repository or in objectui matches on `hint` text; a tool that did should match on `rule` and `path` instead.
28+
- **Never at the runtime gate:** `action-no-placement`, `mapping-target-field-unknown`, `unconsumed-widget-option`, `nav-object-ungranted` and the three `hook-body-*` ids run on the CLI only.
29+
- **Unchanged:** every `message` (the verdict), rule id, severity and `path`, and what each rule accepts or refuses.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
'@objectstack/objectql': patch
4+
---
5+
6+
A language the environment added to an email template, which the package does not ship, is no longer treated as packaged code
7+
8+
Clause-②: no
9+
10+
An `email_template` is identified by its name and its `locale`, so the languages of one template form a bundle. When an administrator customizes a template in a language its package does not ship (for example a `zh-CN` version of a template the package ships in `en-US` only), that version is the environment's own item. Until now it was registered and served wearing the package's protection envelope, taken from the shipped `en-US` version:
11+
12+
- the registry's artifact lookup answered the environment's `zh-CN` version as the package's own `zh-CN` artifact;
13+
- `GET /meta/email_template/:name?member=zh-CN` and the `/meta/email_template` list served it with `provenance: 'package'`, the package's id and its version;
14+
- the registry heal that runs after `DELETE ?member=zh-CN` refused to retire it as packaged code.
15+
16+
- `@objectstack/metadata-protocol`: the artifact envelope (`_packageId`, `_packageVersion`, `_provenance`, and an artifact lock) is copied only onto a version of the same language as the shipped artifact. Each place that copies it looks up the artifact of the body's own language first. A language the package does not ship is registered and served as the environment's item. A language the package does ship keeps its envelope, its lock and its reset exactly as before. Types without languages are unchanged.
17+
- `@objectstack/objectql`: `SchemaRegistry.removeRuntimeShadow(type, name, member)` lifts a named language's shadow only when the package ships that language. For any other language it declines, and the heal's last step (`removeOverlayEntry`) retires the entry. Naming no language still sweeps the whole bundle, as before.
18+
19+
A lock still binds the template name, at the read and at the write gate alike: a template the package locks stays locked in every language.

‎.github/workflows/ci.yml‎

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,22 +1081,32 @@ jobs:
10811081
# OK at or under 1.3x (WARN_MEASURED_OVER_PREDICTED)
10821082
# WARN past 1.3x, at or under 1.5x: a `::warning::` annotation
10831083
# naming the shard and its heaviest overshoot; green
1084-
# DRIFT past 1.5x (MAX_MEASURED_OVER_PREDICTED): red
1084+
# DRIFT past 1.5x (MAX_MEASURED_OVER_PREDICTED): a second
1085+
# `::warning::` annotation, the upper tier; green
10851086
# NOT MEASURED no executed test task carried a dataset entry; green
10861087
#
1087-
# ONE red rule and a warning under it, on the same ratio: triage ruled
1088-
# the in-tree 1.5x the red and the card's 1.3x a warning only, and took no
1089-
# second red on wall time against `timeout-minutes`. Both constants, their
1090-
# measured basis and a self-test battery each live in the script.
1091-
#
1092-
# THE RED IS A `schedule` RED ALONE (C2, both timing steps, ruling
1093-
# https://github.com/objectstack-ai/objectstack/issues/22949#issuecomment-6119186511):
1094-
# the dataset's weights are measured on the hourly `schedule` runs, so
1095-
# only those exit 1 on DRIFT. On `merge_group`, `pull_request`, `push`
1096-
# and `workflow_dispatch` the step prints the same verdict, raises the
1097-
# DRIFT as a `::warning::` annotation and exits 0. The event reaches the
1098-
# script as `--event "$GITHUB_EVENT_NAME"`, which it requires; the rule
1099-
# and its self-test live there (TIMING_GRADED_EVENT, timingGateMode).
1088+
# Two thresholds on ONE ratio, and no second rule on wall time against
1089+
# `timeout-minutes`: triage ruled the in-tree 1.5x the upper tier and the
1090+
# card's 1.3x the lower. Both constants, their measured basis and a
1091+
# self-test battery each live in the script.
1092+
#
1093+
# NO EVENT GRADES (#22949 ruling E, item 1, both timing steps:
1094+
# https://github.com/objectstack-ai/objectstack/issues/22949#issuecomment-6125775640).
1095+
# A wall-clock reading on a shared runner measures the run's load as much
1096+
# as the code: the same package reads 82 s on a quiet hour and 209 s on a
1097+
# busy one, 2.56x, past this step's 1.5x bound and the ceilings' 2.0x of
1098+
# weight. The dataset's weights come from a window that was mostly
1099+
# quiet-density and every event, `schedule` included, meets both
1100+
# densities, so busy hours put the same shards past the bound whatever
1101+
# their code does: density, not code.
1102+
# So on `schedule`, `merge_group`, `pull_request`, `push` and
1103+
# `workflow_dispatch` alike the step prints the same verdict, raises DRIFT
1104+
# as a `::warning::` annotation and exits 0. The event reaches the script
1105+
# as `--event "$GITHUB_EVENT_NAME"`, which it requires: a missing one is a
1106+
# refusal, and so is a dataset or run summary that does not parse, and a
1107+
# refusal is still a red step on every event. The rule and its self-test
1108+
# live in the script (timingGateMode); regression detection moves to the
1109+
# dataset refresh's diff (E2, #23059).
11001110
#
11011111
# EXECUTED WINDOWS ONLY. Measurements come from samplesFromSummary() in
11021112
# measure-test-shard-timings.mjs, which drops cache HITs and failed
@@ -1105,11 +1115,11 @@ jobs:
11051115
# predicted and vouch for a rotted dataset, so a shard whose packages all
11061116
# replayed is NOT MEASURED, never "fast".
11071117
#
1108-
# NO `if:` and NO `continue-on-error` (the red is the point, and the
1109-
# `schedule`-only red is the script's `--event`, so neither is needed),
1110-
# and ABOVE the attestation pair for the #6082 reason documented on the
1111-
# upload above: a failed unguarded step skips that pair, so a drift red
1112-
# also withholds this shard's attestation, the fail-closed direction. A shard
1118+
# NO `if:` and NO `continue-on-error` (the only red left here is a
1119+
# refusal, and a refusal must stay red, so neither is wanted), and ABOVE
1120+
# the attestation pair for the #6082 reason documented on the upload
1121+
# above: a failed unguarded step skips that pair, so a refusal also
1122+
# withholds this shard's attestation, the fail-closed direction. A shard
11131123
# with no packages runs no turbo and writes no summary; that is NOT
11141124
# MEASURED here rather than a usage error from the script.
11151125
#
@@ -1459,8 +1469,10 @@ jobs:
14591469
# diagnostics fault here (an artifact-service 403 is the measured one,
14601470
# #14469) must not evict a green run. The suite-duration ceiling check
14611471
# at the end of this job reads the same captures and is the one step
1462-
# here that DOES red the job, on purpose: it is a gate, not a report, and
1463-
# a capture that never arrived reads NOT MEASURED there, never red.
1472+
# here that can still red the job, on purpose and only on a refusal (a
1473+
# malformed table or raise record, no `--event`): a package over its
1474+
# ceiling is a `::warning::` on every event, and a capture that never
1475+
# arrived reads NOT MEASURED there, never red.
14641476
#
14651477
# ⛔ Steps, deliberately, NOT a new `needs:` entry: this job's
14661478
# `OS_AGGREGATOR_MEMBERS` / `OS_AGGREGATOR_NON_MEMBERS` block is asserted
@@ -1516,11 +1528,14 @@ jobs:
15161528
if-no-files-found: ignore
15171529
retention-days: 14
15181530

1519-
# ── The per-package suite-duration ratchet (#16468) ──────────────────
1531+
# ── The per-package suite-duration ceilings (#16468) ─────────────────
15201532
#
1521-
# A GATE, unlike the two reporting steps above: NO `continue-on-error`,
1522-
# so on a `schedule` run a package whose suite ran past its ceiling reds
1523-
# `Test Core`, naming the package and its slowest test files. The
1533+
# A READING, never a red (ruling E, below): a package whose suite ran
1534+
# past its ceiling is one `::warning::` annotation naming it and its
1535+
# slowest test files, and the step exits 0 on every event. Unlike the two
1536+
# reporting steps above it carries NO `continue-on-error`, because a
1537+
# REFUSAL (a malformed table or raise record, no `--event`) is not a
1538+
# timing reading and reds `Test Core` on every event. The
15241539
# ceiling is the package's slowest executed run in the refresh window
15251540
# x 1.25, generated into scripts/test-shard-timings.json (`ceilings`) by
15261541
# the refresh lane and held by every later refresh; it rises only by a
@@ -1538,19 +1553,26 @@ jobs:
15381553
# samplesFromSummary, and every vitest file line); a capture that did not
15391554
# arrive leaves its packages ungraded and says so, never red.
15401555
#
1541-
# EXIT-GRADED ON `schedule` ALONE (C2, both timing steps, ruling
1542-
# https://github.com/objectstack-ai/objectstack/issues/22949#issuecomment-6119186511):
1543-
# the ceilings come from the hourly `schedule` runs, so only those red
1544-
# this job on a package over its ceiling. On `merge_group`,
1545-
# `pull_request`, `push` and `workflow_dispatch` the step prints the same
1546-
# table, raises each package over its ceiling as a `::warning::`
1547-
# annotation and exits 0; a real slowdown merged to `main` reds the next
1548-
# hourly run. A package whose ceiling is under the absolute floor (20s,
1549-
# CEILING_FLOOR_SECONDS) prints "below floor" and is never red. The
1550-
# event reaches the script as `--event "$GITHUB_EVENT_NAME"`, which it
1551-
# requires; a refusal (a malformed table or raise record) is red on every
1552-
# event. The rule, the floor's reading and their self-tests live in the
1553-
# script.
1556+
# NO EVENT GRADES (#22949 ruling E, item 1, both timing steps:
1557+
# https://github.com/objectstack-ai/objectstack/issues/22949#issuecomment-6125775640).
1558+
# A per-package suite wall on a shared runner measures the run's load as
1559+
# much as the package: the same package reads 82 s on a quiet hour (most
1560+
# packages replayed) and 209 s on a busy one (the whole workspace
1561+
# executing), 2.56x, while the ceiling formula (window max x 1.25) allows
1562+
# 2.0x of weight. The window the ceilings come from was mostly
1563+
# quiet-density and every event, `schedule` included, meets both
1564+
# densities, so busy hours put the same near-ceiling packages over
1565+
# whatever their code does: density, not code. So on `schedule`,
1566+
# `merge_group`, `pull_request`, `push` and `workflow_dispatch` alike the
1567+
# step prints the same table, raises each package over its ceiling as a
1568+
# `::warning::` annotation and exits 0. A package whose ceiling is under
1569+
# the absolute floor (20s, CEILING_FLOOR_SECONDS) prints "below floor" and
1570+
# is never over. The event reaches the script as
1571+
# `--event "$GITHUB_EVENT_NAME"`, which it requires; a refusal (a
1572+
# malformed table or raise record, no event) is red on every event. The
1573+
# rule, the floor's reading and their self-tests live in the script;
1574+
# regression detection moves to the dataset refresh's diff (E2, #23059)
1575+
# and the per-file budget.
15541576
#
15551577
# NOT MEASURED, exit 0, while the dataset carries no `ceilings`: they are
15561578
# generated only where the run-summary artifacts are downloadable (the

0 commit comments

Comments
 (0)