Skip to content

Commit 6c2eed8

Browse files
committed
Merge origin/main into claude/issue-21490-install-local-uninstall-cleanups
Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz Co-Authored-By: Claude <noreply@anthropic.com>
2 parents 14c2c47 + 85e29b8 commit 6c2eed8

54 files changed

Lines changed: 2182 additions & 289 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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/lint': patch
3+
---
4+
5+
Data-model, filter, predicate, search, sort, security, seed, view, widget and registry findings no longer cite tracker numbers; each one states the decision behind it in words
6+
7+
Clause-②: no
8+
9+
The remaining `@objectstack/lint` findings that `os validate`, `os lint` and `os build` show to authors, plus the `surfaceReason` texts of the exported `AUTHORING_RULES` registry and one integrity error, pointed at an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does.
10+
11+
- Data model: the bare declared `unique: true` warning says that protocol 18 rejects the spelling and that stored metadata still carrying it converts to `unique: 'global'`, which builds the same physical index.
12+
- Empty filter combinators: the `$and: []`, `$or: []` and empty-node messages say every backend reduces an empty combinator to its boolean identity; the `$or: []` message says an empty disjunction never opens a read scope to the whole table.
13+
- Null guards: the fail-closed outcome says a predicate that cannot evaluate refuses the write rather than being skipped.
14+
- Visibility and metadata-form predicates: the fall-open consequence says failing open is the console's settled behaviour; the dotted right-hand-side message says the form evaluator keeps its right-hand side a literal by design and says why only in a development build.
15+
- Component props: the advisory hint says props are judged at the authoring door as a warning before they become an error.
16+
- Rule schema formats: the format hint says `rule-validator.ts` registers the default `ajv-formats` set so that a `format` is enforced on every write.
17+
- Security posture: the unset-OWD message describes the leave_request incident (an object with no `sharingModel` let an ordinary read/write grant read and edit every other user's records); the `controlled_by_parent` message says the write is refused as a metadata defect rather than a permission denial.
18+
- Seeds and views: the seed state-machine message says a seed records established facts rather than walking the lifecycle; the `views:` container message says the stack schema, the rule and the registration loop hold `views:` to one container-only contract.
19+
- React pages: the absent-`groupBy` hint states the ruling directly.
20+
- Liveness: the unrecognised-status integrity error says such a status fails loudly rather than being graded `dead`.
21+
- `AUTHORING_RULES` `surfaceReason` texts: the full-snapshot, capability-reference and sharing-rule reasons name the runtime publish gate (the Studio, REST and MCP door that runs this registry) in place of a tracker number; the advisory-volume reason says the object door opened to the gating object rules alone; the component-types reason names the crossing discipline the gating object rules went through.
22+
- The other findings (search fields, sort fields, nav servability, dashboard actions, widget bindings and the remaining predicate and combinator messages) drop a citation the sentence already explained.
23+
24+
Text only: no rule id, severity, condition, finding or registry field moves. A tool or test that matches the old text (for example a tracker-number suffix) needs the new spelling.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
An approval action now records the user who took it in `sys_approval_action.actor_id`, and the pending-approver slot it was taken as in a new `acted_as` column; rows stored before this move their slot out of `actor_id` at the next boot
6+
7+
Clause-②: no
8+
9+
`actor_id` is a lookup to `sys_user`, so under ADR-0118 D1 it holds a user id or nothing. A slot-gated action used to record the slot it took there instead: a `position:<name>` literal for a position staffed after the request opened, or an email for a `user` approver authored as one. On those decisions no record named the person who decided. The audit ledger and activity rows the write produces carry no user, so the attribution was lost, and every join or report on the lookup silently dropped the row.
10+
11+
**This supersedes the "What is recorded" sentence of the unreleased `21379-position-address-readers` changeset**, which says `actor_id` holds the slot. From this release it holds the person.
12+
13+
- **What is recorded.**
14+
- `actor_id` is the user the request's context vouches for: the signed-in caller, whatever address they named.
15+
- `acted_as` is the slot the action took, in the slot's stored spelling (a user id, an email, or `position:<name>`). It is empty on actions no slot admitted: the submitter's own actions, system actions, and an admin override, which `via_override` still marks.
16+
- An emailed action link records the one account that carries the token's email. If no account carries it, the link records no person.
17+
- The SLA sweep keeps its reserved `system:sla` actor for now.
18+
- **What reads it.**
19+
- The multi-approver tally and `decision_progress` count `acted_as`.
20+
- A participant who already acted keeps sight of a request by either of two facts: `actor_id` is their user id, or `acted_as` is a slot they act under (so a decision taken as `position:<name>` stays visible to that position's holders).
21+
- Nothing compares a slot with `actor_id` any more.
22+
- The action log (`GET /api/v1/approvals/requests/:id/actions`, `listActions`) returns `acted_as` beside `actor_id` and `actor_name`, filling the `ApprovalActionRow.acted_as` member `@objectstack/spec` declares. It is omitted when the action took no slot, or when no stored record kept the slot.
23+
- **Stored rows.** A repair runs on every boot and is idempotent.
24+
- Pass 1: a row whose `actor_id` still holds a slot address gets `acted_as` set to it and `actor_id` cleared. No stored record names who decided it, so it shows the slot and no person.
25+
- Pass 2: the approve votes a still-pending request's tally counts get their `acted_as`, so in-flight `unanimous`, `quorum` and `per_group` requests keep the approvals they already collected.
26+
- A failure is logged at error level and retried at the next boot.
27+
- **For a report or integration that read `actor_id` as the slot:** read `acted_as` instead. `actor_id` now always joins to `sys_user`.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/types': patch
3+
---
4+
5+
fix(types): `operatorFacingErrorText` answers through the driver-fault redaction, so an operator-facing record carries no statement and no bound value
6+
7+
Clause-②: no
8+
9+
- **What changed.** `operatorFacingErrorText` passes every text it returns through `redactStatementFromMessage`, the one driver-fault redaction in this package. Text it reads off a raw-statement fault's `cause` is cut with `{ statementSent: true }`, which is the cut `@objectstack/driver-sql` applies to its own log line for the same fault. Every other text asks the shared leak predicate, as the engine's own log line does.
10+
- **What an operator reads now.** The records this helper fills, in `os db clean` and in the metadata migrations and probes, keep the dialect's own diagnostic: the missing column, the failed constraint or the locked database. The value slots the redaction's dialect templates own are cut from it, and the redaction's marker stands where the statement was removed. The records no longer carry the statement or the values bound into it.
11+
- **What does not change.** Text that is not a driver dump comes back exactly as before, empty text included. The thrown error is not touched: its `code`, `status`, class and `cause` reach every other reader as the driver composed them. The function's signature and the package's exports are unchanged.

‎content/docs/automation/approvals.mdx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,9 +509,10 @@ opened, once someone is staffed into it). A named `actorId` must be one of those
509509
identities, and a position named under either spelling takes that position's
510510
slot. No such slot and no admin override returns 403 (`FORBIDDEN: actor '…' is
511511
not a pending approver`); a request that isn't pending returns 409
512-
(`INVALID_STATE`). The decision is recorded in `sys_approval_action.actor_id`
513-
under the slot it took, in that slot's stored spelling — the multi-approver
514-
tally counts approvals by matching that value against the slate. Always go through
512+
(`INVALID_STATE`). The decision records two facts on its `sys_approval_action`
513+
row: `actor_id` is the user who decided, and `acted_as` is the slot the decision
514+
took, in that slot's stored spelling — the multi-approver tally counts approvals
515+
by matching `acted_as` against the slate. Always go through
515516
these endpoints — never resume the flow run directly, and since #3801 you
516517
**cannot**: `POST /api/v1/automation/{flow}/runs/{runId}/resume` answers 403 for
517518
a run parked on an `approval` node (including via a `subflow` pause) and changes

‎content/docs/data-modeling/schema-design.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ hint: 'search' scans this object's own columns, so a related record's column
176176
cannot be a search target — expand the relation and search the related object,
177177
or copy the value onto a stored text field here. Clients echo this declaration
178178
verbatim as the '$searchFields' override, so a stale entry becomes a 400
179-
INVALID_FIELD on list search (#4254), not just a quietly narrowed one.
179+
INVALID_FIELD on list search, not just a quietly narrowed one.
180180
```
181181

182182
A request that sends the dotted path is `400 INVALID_FIELD`:

‎content/docs/permissions/tenant-audit-census.mdx‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ are reported as `undecidable` rather than assumed either way.
122122

123123
The same holds twice over for the context. An options argument spelled as a
124124
literal can be read; one spelled `options`, `{ ...opts }`, or handed through a
125-
forwarding shim cannot, and **67 of the 229 sites are spelled that way**. A
125+
forwarding shim cannot, and **67 of the 231 sites are spelled that way**. A
126126
context resolved from an inline literal or a local `const` can be tested for
127127
`isSystem`; one arriving from a helper call cannot.
128128

@@ -187,10 +187,10 @@ reproduce them. Where it disagrees, it disagrees on the page:
187187

188188
| carried figure | where it survives | this census |
189189
| :--- | :--- | ---: |
190-
| 175 write call sites | quoted in the merged changeset | **229** |
190+
| 175 write call sites | quoted in the merged changeset | **231** |
191191
| 24 carrying no tenant context | quoted in the merged changeset | **9** provable and tenancy-enabled; **34** more whose options argument is unreadable |
192-
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **152 of 229** decidable, **77** undecidable |
193-
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 110 decidably elevated, 0 decidably not, 102 undecidable |
192+
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **154 of 231** decidable, **77** undecidable |
193+
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 112 decidably elevated, 0 decidably not, 102 undecidable |
194194
| 141 and 132, two independent re-derivations | the card that filed this work | — |
195195

196196
**The differences are not reconciled, and deliberately so.** The old census's
@@ -207,11 +207,11 @@ would report a smaller number and would not say so.
207207

208208
The fourth row is the one worth flagging to anyone citing it. **The 135 / 77%
209209
figure has no surviving corroboration anywhere in the tree.** This census reads
210-
110 of 229 (48%) as decidably elevated, with 102 more whose elevation is a
210+
112 of 231 (48%) as decidably elevated, with 102 more whose elevation is a
211211
run-time fact — so the claim is neither confirmed nor refuted, and the honest
212212
answer is that a static reading cannot settle it.
213213

214-
⇒ **Cite `9 / 229`, and say what it is**: the sites whose options argument was
214+
⇒ **Cite `9 / 231`, and say what it is**: the sites whose options argument was
215215
READ and holds no tenant context, against a decidably tenancy-enabled object.
216216
That is the control's provable yield surface. ⛔ Do not cite it as "the sites
217217
without tenant context" — **34 further sites** have an options argument this
@@ -223,28 +223,28 @@ cannot read, and they are neither in nor out.
223223

224224
| what | count |
225225
| :--- | ---: |
226-
| write call sites on the application surface | **229** |
227-
| …whose object name is statically decidable | 152 |
226+
| write call sites on the application surface | **231** |
227+
| …whose object name is statically decidable | 154 |
228228
| …whose object name is chosen at run time | 77 |
229-
| …against an object with tenancy ENABLED | 151 |
229+
| …against an object with tenancy ENABLED | 153 |
230230
| …against an object that declares tenancy off | 1 |
231-
| threading a tenant context | 145 |
231+
| threading a tenant context | 147 |
232232
| PROVABLY carrying none (options read, no context key) | **17** |
233233
| …of those, against a decidably tenancy-enabled object | **9** |
234234
| options argument UNREADABLE — may or may not carry one | 67 |
235235
| …of those, against a decidably tenancy-enabled object | 34 |
236-
| threading a decidably ELEVATED (`isSystem`) context | 110 |
236+
| threading a decidably ELEVATED (`isSystem`) context | 112 |
237237
| threading a context that is decidably NOT elevated | 0 |
238238
| threading a context whose elevation is a run-time fact | 102 |
239239

240240
| how the instrument reached the site | count |
241241
| :--- | ---: |
242-
| receiver carried a readable engine type | 181 |
242+
| receiver carried a readable engine type | 183 |
243243
| receiver erased, placed by the object NAME | 28 |
244244
| receiver erased, placed by an `object: string` PARAMETER | 15 |
245245
| receiver erased, placed by an `UNTYPED_RECEIVERS` row | 5 |
246246

247-
| object name spelled inline | 101 |
247+
| object name spelled inline | 103 |
248248
| object name spelled through a `const` | 51 |
249249
| object name is an `object: string` parameter | 17 |
250250
| object name is some other run-time expression | 60 |
@@ -297,13 +297,13 @@ holds still. They are required to be HERE and to say WHEN they were true;
297297
their values are not compared. The reasoning, and the measurement behind it,
298298
are in `scripts/check-tenant-audit-census.mjs`.
299299

300-
Measured on 2026-10-02 at `c41817b12`.
300+
Measured on 2026-10-02 at `b668cf134`.
301301

302302
| corpus scale (not enforced) | count |
303303
| :--- | ---: |
304-
| tracked non-test sources scanned | 599 |
305-
| engine-shaped types recognised | 66 |
304+
| tracked non-test sources scanned | 602 |
305+
| engine-shaped types recognised | 67 |
306306
| declared objects in the registry | 116 |
307-
| same-named calls subtracted as non-engine | 150 |
307+
| same-named calls subtracted as non-engine | 152 |
308308

309309
{/* END GENERATED: tenant-audit-census */}

‎docs/audits/2026-08-tenant-audit-write-call-sites.counts.md‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ silent, and `node scripts/tenant-audit-census.mjs --write` is the resolution.
3333

3434
| Measure | Value |
3535
|---|---:|
36-
| Write call sites | 229 |
37-
| Object name statically decidable | 152 |
36+
| Write call sites | 231 |
37+
| Object name statically decidable | 154 |
3838
| Object name chosen at run time | 77 |
39-
| Against a tenancy-enabled object | 151 |
39+
| Against a tenancy-enabled object | 153 |
4040
| Against an object declaring tenancy off | 1 |
41-
| Threading a tenant context | 145 |
41+
| Threading a tenant context | 147 |
4242
| Provably carrying none | 17 |
4343
| …and decidably tenancy-enabled | 9 |
4444
| Options argument unreadable | 67 |
4545
| …and decidably tenancy-enabled | 34 |
46-
| Threading a decidably elevated context | 110 |
46+
| Threading a decidably elevated context | 112 |
4747
| Threading a decidably non-elevated context | 0 |
4848
| Threading a context of undecidable elevation | 102 |
4949

@@ -90,21 +90,22 @@ holds still. They are required to be HERE and to say WHEN they were true;
9090
their values are not compared. The reasoning, and the measurement behind it,
9191
are in `scripts/check-tenant-audit-census.mjs`.
9292

93-
Measured on 2026-10-02 at `c41817b12`.
93+
Measured on 2026-10-02 at `b668cf134`.
9494

9595
| corpus scale (not enforced) | count |
9696
| :--- | ---: |
97-
| tracked non-test sources scanned | 599 |
98-
| engine-shaped types recognised | 66 |
97+
| tracked non-test sources scanned | 602 |
98+
| engine-shaped types recognised | 67 |
9999
| declared objects in the registry | 116 |
100-
| same-named calls subtracted as non-engine | 150 |
100+
| same-named calls subtracted as non-engine | 152 |
101101

102102
## Every site
103103

104104
| file | verb | object | tenancy | tenant context | n |
105105
|---|---|---|---|---|---:|
106106
| `packages/plugins/organizations/src/claim-org-seed-ownership.ts` | `update` | `schema.name` | undecidable | elevated | 1 |
107107
| `packages/plugins/organizations/src/claim-orphan-org-rows.ts` | `update` | `schema.name` | undecidable | elevated | 1 |
108+
| `packages/plugins/plugin-approvals/src/action-slot-backfill.ts` | `update` | `sys_approval_action` | enabled | elevated | 2 |
108109
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `update` | `object` | undecidable | context, elevation undecidable | 1 |
109110
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `insert` | `sys_approval_action` | enabled | elevated | 14 |
110111
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `delete` | `sys_approval_approver` | enabled | elevated | 2 |

0 commit comments

Comments
 (0)