Repository navigation
Commit 896a434
fix(plugin-approvals, plugin-audit)!: a lookup title is served only for a target whose declared exposure serves get (#22766)
Fixes #22738
Clause-②: no (narrowing)
Census rows 5 and 6 of #22661. Classes, positions and functions only.
## What changed
Two reads followed a lookup to the TARGET object's title under a system
context and never asked the target's declared exposure. Each now asks
the spec's one decision, `canServeApiOperation`
(`@objectstack/spec/data`, ADR-0049), for `get` before it reads: the
operation the data door's `$expand` and the dataset door's labels
already ask of the same target (#22735). No second rule, no new error
code, and no new package dependency (both packages already depend on
`@objectstack/spec`).
- **Row 5, the approvals inbox** (`plugin-approvals`,
`approval-service.ts`). `ApprovalService.servesPayloadDisplayTarget` is
asked in `ApprovalService.enrichRows` before the referenced-title read.
A refused target is not read. Its key gets no `payload_display` entry,
and the snapshot's stored id stands, which is what a deleted target
already answers.
- **Row 6, the activity summary** (`plugin-audit`, `audit-writers.ts`).
`servesSummaryTitleTarget` is asked in `resolveLookupTitles`, where both
the tracked-change branch and the milestone branch end. A refused target
is not read, and the summary names the record by its stored id, which is
what an unresolvable reference already answers. Only rows written from
now on change. No stored row is rewritten, and no other activity column
changes. The audit lane only tightens what it serves here and widens
nothing.
- **Where the declaration comes from (measured).** Both read the
target's `enable` block through `engine.getSchema(target)`, which is
`ObjectQL.getSchema`, the schema registry's `getObject`. That is the
registry the data door's exposure gate and the analytics door's
declaration provider read. A throwing read withholds the title and logs
one `warn` line (fail-closed, as `servesLabelTarget` does). An engine
without `getSchema` resolves no lookup field in either package
(`resolveLookupFields` and the summary's read plan come back empty), so
nothing is served on that branch either.
- **The enumeration pin**
(`packages/core/src/security/second-object-read-exposure.pin.test.ts`).
Rows 5 and 6 move from `open` to `decided`. Each row is held to its
decision function, its call site and its behaviour pin.
## Cross-domain path
The fix lands in two `domain:services` packages, `plugin-approvals` and
`plugin-audit`. The path was declared in the claim and posted to the
services seat (#6021) before any edit. Nothing in either package outside
the title resolution is touched.
## Deviation from the suggested route
For a refused key, the inbox sends NO `payload_display` entry rather
than writing the stored id into `payload_display`. `payload_display`
maps a key to a resolved display value, and the console's inbox card
drops an unresolved reference rather than render its id. Writing the id
there would make the card render an opaque id as if it were a resolved
title. The stored id still stands in `payload`, which is the answer a
deleted target gets.
## Measurement (real stack, fixture objects, administrator and member)
- **Before** (`bf515e724d`): both positions served the title of every
unexposed target shape (the off switch, a whitelist without `get`, and
the deny-all whitelist) to both personas. The exposed control served its
title.
- **After:** the unexposed targets answer the stored id at both
positions for both personas, and the control is unchanged.
- **Armed** before anything is believed: the data door answers each
target's `get` as the decision does (404, 405, 405, 200), and the
request and the activity row exist at rest.
- **Producers (measured on `bf515e724d`).** Ten in-repo objects refuse
`get` by declaration. Exactly one lookup points into any of them, from
an object that is itself `apiEnabled: false` (control: 88 lookups into
`sys_user`). No shipped object's served title changes.
## Pins and ablation
- `plugin-approvals/src/payload-display-target-exposure.test.ts`: 2
cases. A refused target is not read, and the served ones keep their
titles. An unreadable declaration withholds, with a `warn` line.
- `plugin-audit/src/audit-lookup-summary.test.ts`: 2 new cases, the same
two properties, through a real `ObjectQL` (19/19 in the file).
- `packages/qa/dogfood/test/lookup-title-exposure.dogfood.test.ts`: 4
cases (inbox list and item, activity summary; administrator and member).
4/4 pass at `fb7da543d0` after a post-merge rebuild of the closure.
- **Ablation**, via `scripts/ablation-replace.mjs` (anchor hit 1 to 0,
blob changed; every restore proved blob equal to HEAD with an empty `git
diff HEAD`):
- **A.** Approvals decision bypassed: approvals pin 2 red of 2.
- **A2.** Approvals fail-closed branch opened: 1 red, 1 green (both
re-run on `4437a3219f`).
- **B.** Audit decision bypassed: audit file 1 red, 18 green.
- **B2.** Audit fail-closed branch opened: 1 red, 18 green.
- **C.** Both bypassed with a marker that survives the build, both
packages rebuilt, and `ablation-dist-preflight` found the audit marker
in 2 built files: dogfood 4 red of 4.
- **C-appr.** Approvals alone, with the marker in 2 built files: dogfood
inbox 2 red, activity 2 green.
- **Restore legs:** rebuilt, both markers `--absent`, tree clean,
dogfood 4/4 green.
- **D.** Each new decision function replaced by a hand-spelled rule: the
enumeration pin goes red, naming the function.
- **Note on the builds.** The first leg-C approvals build failed at the
DTS step (TS6133, the import left unused by the mutation), so the
approvals preflight never ran in that leg. C-appr re-ran it: the JS was
emitted before the DTS step, and the preflight found the marker in
`dist`. `@objectstack/plugin-approvals` also resolves to source in the
dogfood project (alias).
## Verification (final head `4437a3219f` unless noted)
- **Gates.** `dispatch-gates --commands --repo
objectstack-ai/objectstack` derived 73 families (they include all 53
named in the dispatch). All 73 exit 0, each exit captured before any
pipe. `--ran` reports 73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN, a
derived zero. `check:dual-build-cjs-loads` answered PREREQUISITE NOT MET
until a full build (72 tasks). On the first run,
`check:objectql-double-limit` flagged the approvals pin's engine double
as unjudged. The double now answers a table whole, because what the pin
holds is which objects are read.
- **Package tests.** `plugin-approvals` 72 files / 1025 tests and
`plugin-audit` 45 / 716 at `c4f5cfa94a`, before merging `origin/main`,
which touches neither package. `core` repo project 5 files / 55 tests.
- **Typecheck.** Exit 0 for `plugin-approvals`, `plugin-audit`, `core`
and `dogfood`, each new test present in its program (`--listFiles`).
- **Lint (narrowed).** `eslint --no-inline-config --format json` over
the 6 changed `.ts` files: 6 files, 0 errors, 0 warnings (5 to 6 active
rules each, read from `--print-config`). `eslint.config.mjs` enables no
type-aware linting, so no untouched file's verdict can move. The
repo-wide `pnpm lint` is CI's.
- **Line budget.** 7 files, +509 / -15. Source is +68 / -6. The
real-stack pin is 220 of the lines: it measures the served doors for
both personas, which a unit double cannot reach because each read runs
under a system context.
## Acceptance notes
- This narrowing owes one contract-review-tier review before the queue.
The seat arranges it.
- The `sys_user` display names (submitter, approvers, the activity
actor) are fixed-target reads. The enumeration pin names them and does
not hold them. `sys_user` serves `get`, so a `user` reference field is
unchanged.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 490cb6d commit 896a434
7 files changed
Lines changed: 509 additions & 15 deletions
File tree
- .changeset
- packages
- core/src/security
- plugins
- plugin-approvals/src
- plugin-audit/src
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
Lines changed: 17 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
| 144 | + | |
145 | 145 | | |
146 | | - | |
147 | | - | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
148 | 150 | | |
149 | 151 | | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
154 | 158 | | |
155 | 159 | | |
156 | 160 | | |
| |||
280 | 284 | | |
281 | 285 | | |
282 | 286 | | |
283 | | - | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
284 | 293 | | |
285 | 294 | | |
286 | 295 | | |
| |||
Lines changed: 30 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
60 | | - | |
| 59 | + | |
| 60 | + | |
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| |||
6391 | 6391 | | |
6392 | 6392 | | |
6393 | 6393 | | |
| 6394 | + | |
| 6395 | + | |
| 6396 | + | |
| 6397 | + | |
| 6398 | + | |
| 6399 | + | |
| 6400 | + | |
| 6401 | + | |
| 6402 | + | |
| 6403 | + | |
| 6404 | + | |
| 6405 | + | |
| 6406 | + | |
| 6407 | + | |
| 6408 | + | |
| 6409 | + | |
| 6410 | + | |
| 6411 | + | |
| 6412 | + | |
| 6413 | + | |
| 6414 | + | |
| 6415 | + | |
| 6416 | + | |
| 6417 | + | |
| 6418 | + | |
6394 | 6419 | | |
6395 | 6420 | | |
6396 | 6421 | | |
| |||
6419 | 6444 | | |
6420 | 6445 | | |
6421 | 6446 | | |
6422 | | - | |
| 6447 | + | |
| 6448 | + | |
6423 | 6449 | | |
6424 | 6450 | | |
6425 | 6451 | | |
| |||
6477 | 6503 | | |
6478 | 6504 | | |
6479 | 6505 | | |
| 6506 | + | |
6480 | 6507 | | |
6481 | 6508 | | |
6482 | 6509 | | |
| |||
Lines changed: 96 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
Lines changed: 79 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
520 | 520 | | |
521 | 521 | | |
522 | 522 | | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
0 commit comments