Repository navigation
Commit f313fbc
fix(plugin-audit): a fired milestone activity row is withheld from a reader not served its watched fields (#22814)
Fixes #22786
Clause-②: no
A narrowing of what a reader is served; no accepted input changes and no
public type moves (triage `6106418840`, claim `6106790619`).
## What changed
`packages/plugins/plugin-audit/src/activity-field-redaction.ts`. The
withheld-row pre-scan from #21388 now makes ONE judgement per row,
`isWithheldRow`. A row is withheld when either arm holds:
- **The update arm** (`isWithheldOnlyUpdate`, unchanged): an update
whose every recorded change the reader is not served.
- **The milestone arm** (`isWithheldMilestone`, new, beside it): a fired
milestone whose declared watched fields are not all served. The watched
fields are the list the CRUD mirror stamps in
`metadata.text_sources.kind` (PR #22783), read through the existing
`kindProvenance`.
Both arms share one pre-scan, one per-read served answer, and one WHERE
on `find` / `findOne` / `count` / `aggregate`. A pre-scan failure denies
the read, as before. There is no second judge.
- **One answer for both halves.** The row is withheld exactly when the
column redaction would drop the row's `metadata.kind`. Both judge a
declared value through one helper, `sourcesServed`. A declaration with
no readable list is judged by one per-read unknown-provenance answer,
`restrictedPerRead`. That answer was lifted out of `redactActivityRows`,
so the middleware hands the same answer to the pre-scan and to the
redaction. `computeWithheldUpdateFilter` called without that answer
judges such a row closed.
- **Route 2 is not ridden along.** Route 2 adds the watched field to the
summary's sources. Once the row is withheld from that reader, route 2
changes nothing at the served read, so it would only restate the same
answer in a second place.
- **Out of the diff:** no `packages/spec`, no new `type` value, no new
column, and no export from the package entry.
## The PM's mechanism assumptions, measured
1. **Holds.** On `680a86b4c`, `isWithheldOnlyUpdate` is at `:271` and
the pre-scan calls it at `:321` with the reader's served set.
2. **Holds for every row the mirror writes from #22783 on.**
`matchMilestone` returns every field a declared milestone watches, and
the writer stamps that list beside `kind: 'milestone'`. So the list is
the right input and is reused as is.
- Rows written before #22783 carry no `metadata.kind`, so nothing stored
on them says a milestone fired. They are judged exactly as before this
change: key-by-key narrowing plus the update arm. They age out with the
object's retention, which is the precedent the module header already
sets for the mirror's earlier shape.
- The `'unknown'` precedent applies only to a row that carries the kind
but no readable list. The mirror never writes such a row. It is judged
as the text precedent judges one: served only to a reader not restricted
on the parent.
- Whether earlier rows need more than this is raised with the
dispatching seat. It is not decided here.
3. **Holds.** A withheld row reaches the reader through no column at
all. This is pinned on the list read scoped by `object_name` +
`record_id`, on the by-id read, and on `count`, in the same harness
#22783's tests use.
## Pins (`activity-milestone-kind.integration.test.ts`)
These run on a real engine, a real SQLite driver, the real CRUD mirror
and the real `AuditPlugin` middleware chain, with the security service
as the one stand-in:
- A reader withheld a watched field gets neither milestone row: not on
the list read and not by id.
- CONTROL: a reader served every field gets each milestone row with
`metadata.kind`, both by id and on the list.
- CONTROL: a reader withheld only a field no milestone watches still
gets the milestone rows with the kind. This is the existing test,
unchanged.
- CONTROL: a row no milestone fired is served to the withheld reader
deep-equal to the row the control is served. That reader is served
exactly the non-milestone rows.
- `count` agrees with the rows served, for every reader.
- Fail closed: when the pre-scan's system read fails, a non-system
reader's list read is empty and its count is 0. The system read is
unaffected, and the read recovers once the pre-scan works again.
- The deny is a WHERE that matches nothing, as #21388's rule already
does. It is not a refusal envelope, so there is no `code` or `status` to
assert.
- Unit pins cover `isWithheldMilestone`, `isWithheldRow` and the
milestone arm of `computeWithheldUpdateFilter`, including the
unknown-provenance and no-answer cases.
Two existing pins from #22783 asserted that the withheld reader was
served the milestone row without its kind. They now assert the row is
withheld, which is the behaviour this card changes. The key-for-key pin
drops that reader's row.
## Verification
Every reading below was taken at `aa9b481c1`, the head this PR opens at.
That head has `origin/main` `179f7bf6c` merged in: three docs-only
commits, none touching a file this diff touches. Each command ran
through `scripts/pm/os-verify-lock.sh`, with the exit code captured
before any pipe.
- **Build:** `pnpm exec turbo run build` over
`@objectstack/plugin-audit...` and the `check:i18n` closure exited 0.
- **Tests:** `pnpm --filter @objectstack/plugin-audit exec vitest run
--maxWorkers=2` passed 46 files and 738 tests.
- **Typecheck:** `pnpm --filter @objectstack/plugin-audit typecheck`
exited 0. That covers the source, the scripts and the test layer
(`check:test-typecheck: OK`, 0 debt entries).
- **Dogfood:** `pnpm --filter @objectstack/dogfood exec vitest run
--maxWorkers=2 test/activity-field-values.dogfood.test.ts` passed 10 of
10. It boots for real, with `SecurityPlugin` and REST. It is not owed,
because no public surface moved. It was run as a control that a
milestone row reaches readers who are served its watched field.
- **Gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 65 commands at this head. All 65 ran
and each exited 0, including `check:i18n` and
`check:dual-build-cjs-loads` with their prerequisites built. `--ran`
reconciliation: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN.
- **Lint (a narrowed run, declared with its three proofs):**
- **Population:** eslint ran on the diff's two TypeScript files with
`--no-inline-config --format json`. The diff's third file, the changeset
`.md`, is answered by eslint itself with "File ignored because no
matching configuration was supplied".
- **Count:** the JSON output counts 2 files linted, 0 errors and 0
warnings.
- **Invariance:** `eslint.config.mjs` enables no type-aware linting.
`--print-config` on the source file resolves no `parserOptions.project`
and no `projectService`. The only files the config reads are two
baseline JSONs that this diff does not touch. So no untouched file's
verdict can move.
- **Ablations (one-off, not left as tests):** run from the committed
state `76f2e23b9` through `scripts/ablation-replace.mjs`. In each run
the anchor hit exactly once, and the blob was shown to change on disk
and then shown restored to the HEAD blob with `git diff HEAD` empty. The
subject resolves through `src/` (this package's own tests import it
relatively), so no `dist/` preflight applies.
1. **Milestone arm dropped from `isWithheldRow`:** 7 red and 15 green in
the milestone file. The red ones are the four withheld-reader
served-read pins (list, by-id, served set, count) and the arm's three
unit pins. Every control stayed green.
2. **Pre-scan deny removed from the middleware's catch:** exactly one
red, the fail-closed pin (1 of 22).
CI was not awaited.
## Acceptance notes
- `computeWithheldUpdateFilter` keeps its name and its log text
("withheld-update pre-scan"). A fired milestone is itself an update row,
so the name still holds, and renaming it would only churn the call
sites.
- PR #22783's kind-dropping arm in `redactActivityRows` is kept. Through
the middleware it no longer sees a withheld milestone row, but it still
judges direct callers. It now shares the one answer with the row rule.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CBAfsWMSfM3EToQGVStEcp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f9c7588 commit f313fbc
3 files changed
Lines changed: 300 additions & 49 deletions
File tree
- .changeset
- packages/plugins/plugin-audit/src
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
60 | 62 | | |
61 | 63 | | |
62 | 64 | | |
| |||
106 | 108 | | |
107 | 109 | | |
108 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
109 | 133 | | |
110 | 134 | | |
111 | 135 | | |
| |||
251 | 275 | | |
252 | 276 | | |
253 | 277 | | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
254 | 322 | | |
255 | 323 | | |
256 | 324 | | |
257 | 325 | | |
258 | | - | |
| 326 | + | |
259 | 327 | | |
260 | 328 | | |
261 | 329 | | |
| |||
280 | 348 | | |
281 | 349 | | |
282 | 350 | | |
283 | | - | |
284 | | - | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
285 | 394 | | |
286 | 395 | | |
287 | 396 | | |
| 397 | + | |
| 398 | + | |
288 | 399 | | |
289 | 400 | | |
290 | 401 | | |
291 | 402 | | |
292 | 403 | | |
293 | 404 | | |
294 | 405 | | |
| 406 | + | |
295 | 407 | | |
296 | 408 | | |
297 | 409 | | |
| |||
318 | 430 | | |
319 | 431 | | |
320 | 432 | | |
321 | | - | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
322 | 436 | | |
323 | 437 | | |
324 | 438 | | |
| |||
355 | 469 | | |
356 | 470 | | |
357 | 471 | | |
| 472 | + | |
| 473 | + | |
358 | 474 | | |
359 | 475 | | |
360 | 476 | | |
361 | 477 | | |
362 | 478 | | |
363 | | - | |
364 | 479 | | |
365 | | - | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
373 | | - | |
374 | | - | |
375 | | - | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
| 480 | + | |
383 | 481 | | |
384 | 482 | | |
385 | 483 | | |
| |||
400 | 498 | | |
401 | 499 | | |
402 | 500 | | |
403 | | - | |
404 | | - | |
| 501 | + | |
| 502 | + | |
405 | 503 | | |
406 | 504 | | |
407 | 505 | | |
| |||
441 | 539 | | |
442 | 540 | | |
443 | 541 | | |
444 | | - | |
445 | | - | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
446 | 545 | | |
447 | 546 | | |
448 | 547 | | |
| |||
458 | 557 | | |
459 | 558 | | |
460 | 559 | | |
| 560 | + | |
461 | 561 | | |
462 | 562 | | |
463 | | - | |
| 563 | + | |
464 | 564 | | |
465 | 565 | | |
466 | 566 | | |
| |||
476 | 576 | | |
477 | 577 | | |
478 | 578 | | |
479 | | - | |
| 579 | + | |
480 | 580 | | |
481 | 581 | | |
482 | 582 | | |
| |||
0 commit comments