Skip to content

Commit 29cc169

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-ybjm3h
2 parents b542764 + 69df290 commit 29cc169

45 files changed

Lines changed: 3599 additions & 715 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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): an action or job body refused for a missing capability answers the same coded 403 a hook body does, not a bare 500 (#26965)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
**What was wrong.** Since #26903 a hook body that calls something its `body.capabilities` does not grant is refused `403 INSUFFICIENT_PRIVILEGES`, naming the hook, the capability and the remedy. The same call from an action body (for example `ctx.api.object('note').find(...)` without `api.read`) still answered `POST /api/v1/actions/OBJECT/ACTION` with `500 INTERNAL_ERROR` "Internal server error", so the caller and the author were not told which action or which capability. A job body's denial was the same bare fault.
11+
12+
**What changes.** One gate, one answer. An action or job body's capability denial is the same refusal: `403 INSUFFICIENT_PRIVILEGES`, an existing standard catalog code, with a sentence such as `capability 'api.read' not granted to action 'note_count' (called ctx.api.object('note').find). Grant it by adding 'api.read' to the action's body.capabilities.` The remedy names the body's own owner (the hook's, the action's or the job's `body.capabilities`). The capability check is exactly as strict as before; nothing the body asked for runs. An action's refusal is logged once at `info`; a job reaches no HTTP caller, so its run records this sentence and its `error` log line stays.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql): a predicate delete walks the rows it matches as one set: each relation pointing at the object is asked once per 500 matched rows, not once per row (#27004)
6+
7+
Clause-②: yes (widening) — a predicate delete (`engine.delete(object, { where, multi: true })`, a flow's `delete_record` with `multi: true`) whose matched rows are held only by OTHER matched rows (a report and its manager through a `restrict` self-lookup, both matched) now deletes them; the row-by-row walk refused it, or not, depending on which row it met first. Nothing is narrowed.
8+
Metadata-protocol: none
9+
10+
- **Before.** The predicate delete ran the by-id referential walk once per matched row, so its cost was matched rows times referencing relations: 1,001 rows of an object three relations point at took 4,004 dependents probes, about 1.7 s on SQLite.
11+
- **Now.** One walk, rooted at the whole matched set (a by-id delete is a set of one). Each referencing relation is probed once per 500 matched rows with `$in` (a multiple lookup ORs each id's `$contains`), and each row is then judged and written as before: `restrict` refuses, `cascade` deletes (each child through its own delete, hooks and all), `set_null` clears, a multiple lookup loses only the deleted members and an emptied set is written as `[]`. The same 1,001-row delete takes 12 probes.
12+
- **Unchanged.** A refusal's code and wording: it still names one record and counts the rows that refuse over that record (for a required multiple lookup, the rows the removal would empty). The atomic and split plans, the 500-id `$in` delete batches, the roll-up recompute after the delete, and the filter of every probe a by-id delete sends.
13+
- **Who sees it.** A bulk delete that removes rows holding each other: a matched row that another matched row holds is in the delete's own set, so it neither refuses nor is cleared first (the rule a cascade already applies to the rows it deletes). A holder outside the matched rows still refuses.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/console': patch
4+
'@object-ui/i18n': patch
5+
---
6+
7+
fix(app-shell): the permission matrix's object rows show exactly what the runtime enforces for every grant shape
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
- **Object rows.** In the permission-set editor (Studio Access, and the permission sets under Setup), each object row shows the object grant the runtime applies to that object. A row that inherits its grant is drawn in grey with a dashed edge, the look inherited field rows already use, with a tooltip and a legend line saying where the grant comes from. Editing such a row writes an entry for the object that starts from what the row shows.
13+
- **Summaries.** "Only granted", the granted-object count and the delegated-admin companion list under Admin scope read the object grant as the rows show it.

‎docs/qa/platform-checklist/RUNNER.md‎

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,14 @@ contradicts it, and correct it here when it does.
283283
before scoring any connector clause, prove where the live dispatch lands: create a
284284
`showcase_task`, read the `showcase_declarative_connector_ping` run it fires, and confirm
285285
it completed with a `ping.body` whose uptime matches THIS boot, not another server's.
286-
⛔ Do not read the instance metadata for it: `os dev` boots the prebuilt
287-
`dist/objectstack.json` unless `--compile` is passed (`packages/cli/src/commands/dev.ts`),
288-
so `GET /api/v1/meta/connector/showcase_status_api`'s `item.providerConfig.baseUrl` can
289-
carry the build-time value. QA run #25817 at `6679644a` measured it: with `OS_PORT=41302`
286+
⛔ Do not read the instance metadata for it: an `os dev --no-compile` boot serves the
287+
existing `dist/objectstack.json` as built, so `GET /api/v1/meta/connector/showcase_status_api`'s
288+
`item.providerConfig.baseUrl` can carry the build-time value. Since #26130 a default
289+
`os dev` compiles the config under the boot's own environment first
290+
(`packages/cli/src/commands/dev.ts`, the `compile` flag, default on), so on a default
291+
boot that read can follow an exported `OS_PORT` — the dispatch check above stays the
292+
proof on either boot. Before #26130 `os dev` booted the prebuilt artifact unless
293+
`--compile` was passed, and QA run #25817 at `6679644a` measured that old default: with `OS_PORT=41302`
290294
exported, that read still answered `http://127.0.0.1:3000`, while the live
291295
`connector_action` reached 41302 (`ping.body` uptime matched the boot, and nothing
292296
listened on 3000) — so the metadata read misreports the fixture in both directions.
@@ -347,6 +351,23 @@ contradicts it, and correct it here when it does.
347351
wrote a CRM manifest there for 25 s before removing it through
348352
`DELETE /api/v1/marketplace/install-local/<id>` (#23163).
349353

354+
- **A per-boot `-d file:…` database does NOT isolate the showcase's upload root or its
355+
external datasource either — the same class as the install-local entry above.** Two
356+
more paths resolve against the server's working directory: the local storage root,
357+
whose settings-manifest default is `./.objectstack/data/uploads` unless
358+
`OS_STORAGE_LOCAL_ROOT` is exported (only `--fresh` sets it, to its own temp home:
359+
`packages/cli/src/commands/dev.ts`), and the showcase's external datasource file
360+
`.objectstack/data/showcase_external.db`
361+
(`examples/app-showcase/src/system/datasources/showcase-external.datasource.ts`). So a
362+
tree boot with only `-d` writes uploads into `examples/app-showcase/.objectstack/data/uploads/`
363+
and creates `showcase_external.db` beside it, and even a boot that writes neither leaves
364+
`.objectstack/data/` behind, empty. All of it is git-ignored, so a clean `git status` is
365+
no evidence (QA runs #26456, #26476 and #26481 met all three). **Fix:** export
366+
`OS_STORAGE_LOCAL_ROOT=/tmp/RUN/uploads` beside `OS_PORT` for any item that uploads, and
367+
boot from a scratch copy of the app for any item that writes the external datasource.
368+
An empty `.objectstack/data/` left by a tree boot is harmless and is not the
369+
installed-packages trap.
370+
350371
- **`ss` is not installed in these containers — read liveness with `curl`, never a socket
351372
table.** `ss` and `netstat` are both absent (`command not found`); `lsof` and `fuser`
352373
are present. The trap is that the usual spelling hides the cause: `ss -ltn | grep`
@@ -376,6 +397,11 @@ contradicts it, and correct it here when it does.
376397
read the group from the listener, `PGID=$(ps -o pgid= -p "$(lsof -ti tcp:PORT)" | tr -d ' ')`;
377398
stop it with `kill -TERM -- -$PGID`, and confirm `lsof -ti tcp:PORT` then prints nothing.
378399
⛔ Never stop a server by name (`pkill`), and never a group you did not start.
400+
⚠️ Run that stop as a command of its own: the repo's PreToolUse hook
401+
`.claude/hooks/guard-process-kill.sh` refuses any one Bash command that runs `ps` and
402+
`grep` and also kills, judged over the whole command rather than per pipeline (its
403+
header says why), so a stop chained after a `ps … | grep` look at the process table is
404+
refused although its `kill` is group-scoped (QA run #26476 met it).
379405

380406
- **Console session auth is a bearer token in `localStorage`, and a form sign-in ALSO
381407
sets a cookie — you need both halves.** The console's auth client stores the session

0 commit comments

Comments
 (0)