Repository navigation
Commit 6a40a09
fix(plugin-audit): a failed boot provisioning of an audit table is logged at error, naming what is lost and the fix (#22981)
Fixes #22913
Clause-②: no
## What this changes
`AuditPlugin.provisionSystemTables`
(`packages/plugins/plugin-audit/src/audit-plugin.ts`) creates the
storage for `sys_audit_log`, `sys_activity`, `sys_comment` and
`sys_comment_reaction` at `kernel:ready`. When the driver refused one of
those DDL statements, the catch logged it at `warn` and continued. The
boot completes and the deployment looks healthy while the table does not
exist. For the two audit tables, audited writes keep succeeding while
their rows have nowhere to land. AGENTS.md "Degradation log levels"
sends that class to `error`.
- The catch now logs **one `error` per object per boot**, never per
write. Its first line names the object, what is lost without it, that
the deployment keeps looking healthy, and the fix: resolve the driver's
refusal, then restart (this provisioning runs on every boot) or run `os
migrate apply`. The driver's refusal is in the record's meta (`{ object,
error }`), not in the message, so a multi-line driver error stays one
record.
- **The boot is not failed**, per the triage direction (comment
6116902332): making a boot refuse is a separate policy call.
- The provisioning docblock loses a stale claim, "lazy-created on first
WRITE": a write to a missing table fails with `no such table` (measured
below).
- Ride-along, test-only: `plan.telemetry-sibling.integration.test.ts`
now carries the `serve` child's own stderr and stdout in every assertion
that reads what the boot provisioned. Before, a boot that exited 0 with
a table missing printed nothing of its own account.
What a forced refusal of `sys_activity`'s DDL prints now, through `os
serve` (`OS_MIGRATE_AND_EXIT=1`, `OS_LOG_LEVEL=warn`, telemetry sibling
on), once, with exit 0:
```text
ERROR AuditPlugin: could not provision sys_activity storage — its storage was NOT created. Audited writes keep succeeding, but their `sys_activity` rows (a record's activity timeline and the recent-activity feed) cannot land, and nothing retries them. The boot still completes and the deployment keeps looking healthy. Fix: resolve the driver's refusal (in this record's meta), then restart, since this provisioning runs on every boot, or run `os migrate apply`, which creates the missing storage (`os migrate plan` lists it as `create_table:sys_activity`). If the boot log also carries `Schema sync FAILED for object 'sys_activity'`, that is the same refusal. {"object":"sys_activity","error":"CREATE VIEW \"sys_activity\" AS SELECT … - there is already an index named sys_activity"}
```
On `main` the same boot printed `WARN AuditPlugin: could not provision
sys_activity storage — …` inside the `Boot diagnostics — N
informational` block.
## Measured before writing the fix half (dispatch A3)
Harness: a scratch app (`requires: ['auth', 'automation']`), booted
through `os serve` with `OS_MIGRATE_AND_EXIT=1`. The DDL refusal was
forced by pre-creating an INDEX named `sys_activity`: SQLite tables,
views and indexes share one namespace, so the CREATE is refused. Run on
`main` `1f312fdfa` code, in both compositions (`OS_TELEMETRY_DB=0`, and
the default telemetry sibling).
| step | reading |
|:--|:--|
| boot with the refusal | `serve` exits 0; the `sys_activity` view is
absent; the audit write that follows logs `no such table: sys_activity`
|
| fault removed, `os migrate plan --json` | exit 0, `pending =
["create_table:sys_activity"]` |
| `os migrate apply --yes --json` (on a copy) | exit 0, `created` =
`sys_activity`; the view exists afterwards (in the sibling file when the
sibling is on) |
| a plain restart (on another copy) | exit 0; the view exists afterwards
|
| `OS_SKIP_SCHEMA_SYNC=1` boot of an existing schema with
`sys_comment_reaction` dropped | objectql logs `Skipping schema sync …
WITHOUT DDL`, and the table is re-created anyway: this provisioning
issues its DDL whatever that flag says |
So the line names restart and `os migrate apply`, which were both
measured to restore the table. It does not name `OS_SKIP_SCHEMA_SYNC`:
setting that flag does not stop this DDL, so it cannot make this failure
deliberate.
## Measured: is the same failure reported elsewhere? (dispatch A2)
The line is printed once per object per boot (`kernel:ready` fires once,
and the loop visits each object once). In both compositions above,
objectql's boot schema sync had also tried the same object, so the boot
log also carries `ERROR Schema sync FAILED for object 'sys_activity'`.
It carries it twice, from two sync passes in that boot, each with its
own `finished with 1 FAILED object(s)` summary. The audit writer's
per-write line (`Audit write FAILED on sys_activity`, printed once per
audited object, table and code) follows. The new line says that the
objectql line is the same refusal. It adds the audit-specific
consequence. The duplicate objectql pass is noted below and not changed
here.
## Measured: `check:durability-log-level` cannot see this site (dispatch
A4)
- The audit site calls `sync.call(engine, obj.name)`. The checker's
`calleeName` reads that callee as `call`: it unwraps `.call` / `.apply`
/ `.bind` on the log side (`resolveLogCallee`) but not on the guarded
side. So a `syncObjectSchema` entry in `DURABILITY_CRITICAL_CALLEES`
would not light this site, or the identical messaging site.
- Adding `['syncObjectSchema', …]` to the map (in a scratch copy, on
`1f312fdfa`) raised the seam count from 46 to 49. It turned the checker
red on three sites, **all outside this PR's file surface**:
- `packages/cli/src/utils/schema-migration-plugins.ts:2401` (SILENT; the
catch counts the failure into the plan's notes)
- `packages/metadata-protocol/src/protocol.ts:19337` (QUIET,
`console.warn` in `ensureObjectStorage`)
-
`packages/services/service-datasource/src/datasource-connection-service.ts:658`
(QUIET, `warn`)
- So, per the dispatch, **the entry is not added here**, and none of
those sites is touched. This departs from AGENTS.md's "add it to
`DURABILITY_CRITICAL_CALLEES` in the same PR that fixes it". The entry
and its sites belong to one family card, which the seat files. Until
then the unit pins below hold this site.
## Pins (`audit-plugin.test.ts`)
- For each of the four objects (`it.each`): a refused DDL logs exactly
one `error`. The line names the object and `os migrate apply` and has no
newline. The refusal is in meta. No `could not provision` line is logged
at `warn`. The other three objects still provision, and `kernel:ready`
resolves (the boot is not failed).
- CONTROL: a clean provision logs zero `error` calls.
- The existing `keeps reporting placements when one object fails to
sync` now reads the line from `error`.
- The wording is not pinned. The pins hold the level, the count, the
named object, the named remedy command, and the refusal in meta.
Ablations (one-shot, `scripts/ablation-replace.mjs` in WRAP mode, run on
committed `168cf1e33`, restore proved by blob == HEAD and an empty `git
diff HEAD`):
- **A, the old level back** (`ctx.logger.error(provisionFailureLine(…),
undefined, {` changed to `ctx.logger.warn(provisionFailureLine(…), {`):
5 failed / 10 passed. The four per-object pins fail with `expected [] to
have a length of 1 but got +0`, and so does the placement test. The
CONTROL stays green.
- **B, an `error` on the clean path** (the line also emitted before each
successful placement): 5 failed / 10 passed. The CONTROL fails with
`expected [ { …(3) }, { …(3) }, { …(3) }, …(1) ] to deeply equal []`,
and so do the four per-object pins. First attempt not run: its
replacement contained its own anchor, so `ablation-replace` refused it
(anchor count 1 to 1) and ran no test. The second anchor ran.
- **Ride-along proof**: the CONTROL's `pendingTables` expectation was
mutated to `['demo:forced-failure']`. The failure message then opened
with `--- the serving boot's stderr (exit 0) ---` and the child's full
boot-diagnostics block. Restored by blob.
## Tests and gates
All on head `9ac3a46f5`, the PR's last commit. It changes only the
changeset text after `168cf1e33`.
- `pnpm --filter @objectstack/plugin-audit test`: 47 files, 748 tests
passed. `pnpm --filter @objectstack/plugin-audit typecheck` (tsc,
scripts project, `check:test-typecheck`) passed at `168cf1e33`, which
has the same source.
- `@objectstack/cli`, the touched integration file (`--project
integration`, `plan.telemetry-sibling.integration.test.ts`): 2 passed.
`pnpm --filter @objectstack/cli typecheck` passed, and the cli
test-typecheck debt is unchanged; the touched file is not in it. The cli
`unit` layer is left to CI: the only cli file changed is this
integration test.
- `node scripts/pm/dispatch-gates.mjs --commands` over the real change
set derived 67 gate commands. All 67 ran, and `--ran` reconciles **67
derived, 67 run, 0 NOT-MEASURED, 0 UNRUN**. Five first exited 3
(PREREQUISITE NOT MET) and were rerun green after their prerequisite was
met: `check-plugin-teardown-shape --self-test` after fetching its pinned
control commit, and `check:dual-build-cjs-loads`, `check:i18n`,
`check:i18n-coverage` and `check:i18n-walk-parity` after a full
workspace build.
- `pnpm check:durability-log-level` (run explicitly; the derivation does
not place it): green, 46 seams. The seam count is unchanged because this
site is outside the checker's view (section above).
- Narrowed `eslint --no-inline-config` over the three touched TS files:
3 files (count read from `--format json`), 0 errors, 0 warnings.
`--print-config` resolves rules for each file, so none is ignored, and
no `parserOptions.project` or `projectService` is set. Linting is not
type-aware, so this diff cannot change the verdict on an untouched file.
The repo-wide `pnpm lint` is left to CI.
## Acceptance notes
- **objectql reports one refused object twice in one `serve` boot.**
`Schema sync FAILED for object 'X'` and the `finished with 1 FAILED
object(s)` summary each print twice, in two passes about 350 ms apart
(measured above). Observation only; Carrier: none.
- **The `no syncObjectSchema` warn in the same method still says the
tables "stay lazy-created on first WRITE".** That is stale for the SQL
driver (measured: an insert fails with `no such table`). It is left as
is because it guards engines without on-demand DDL, which this card does
not cover. Carrier: none.
- **The `resolves to NO datasource driver` branch in the same loop is
still a `warn`.** No storage is created there either. It was not
changed: no composition reaching it was measured, and the triage pinned
only the throwing branch. Carrier: none.
- **The audit writer's per-write missing-table line says the boot log
"carries `Schema sync FAILED for object …`".** That holds whenever
objectql also tried the object, which was the case in every boot
measured here. It does not cover this plugin's own provisioning line,
which this PR makes a sibling `error` with the same object name.
Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>1 parent 0524c16 commit 6a40a09
4 files changed
Lines changed: 193 additions & 26 deletions
File tree
- .changeset
- packages
- cli/src/commands/migrate
- 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
108 | | - | |
109 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
110 | 119 | | |
111 | 120 | | |
112 | 121 | | |
113 | | - | |
114 | | - | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
115 | 127 | | |
116 | 128 | | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
117 | 132 | | |
118 | 133 | | |
119 | 134 | | |
| |||
154 | 169 | | |
155 | 170 | | |
156 | 171 | | |
157 | | - | |
| 172 | + | |
158 | 173 | | |
159 | 174 | | |
160 | | - | |
| 175 | + | |
161 | 176 | | |
162 | 177 | | |
163 | | - | |
164 | | - | |
| 178 | + | |
| 179 | + | |
165 | 180 | | |
166 | 181 | | |
167 | 182 | | |
| |||
172 | 187 | | |
173 | 188 | | |
174 | 189 | | |
175 | | - | |
176 | | - | |
| 190 | + | |
| 191 | + | |
177 | 192 | | |
178 | 193 | | |
179 | 194 | | |
| |||
197 | 212 | | |
198 | 213 | | |
199 | 214 | | |
200 | | - | |
| 215 | + | |
201 | 216 | | |
202 | 217 | | |
203 | | - | |
| 218 | + | |
204 | 219 | | |
205 | 220 | | |
206 | 221 | | |
207 | 222 | | |
208 | 223 | | |
209 | | - | |
210 | | - | |
| 224 | + | |
| 225 | + | |
211 | 226 | | |
212 | 227 | | |
213 | 228 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
46 | 52 | | |
47 | 53 | | |
48 | 54 | | |
49 | | - | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
50 | 59 | | |
51 | 60 | | |
52 | 61 | | |
| |||
95 | 104 | | |
96 | 105 | | |
97 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
98 | 172 | | |
99 | 173 | | |
100 | 174 | | |
| |||
231 | 305 | | |
232 | 306 | | |
233 | 307 | | |
234 | | - | |
| 308 | + | |
235 | 309 | | |
236 | 310 | | |
237 | 311 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
141 | 183 | | |
142 | 184 | | |
143 | 185 | | |
| |||
472 | 514 | | |
473 | 515 | | |
474 | 516 | | |
475 | | - | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
482 | | - | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
483 | 524 | | |
484 | 525 | | |
485 | 526 | | |
486 | 527 | | |
487 | 528 | | |
488 | 529 | | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
489 | 545 | | |
490 | 546 | | |
491 | 547 | | |
| |||
542 | 598 | | |
543 | 599 | | |
544 | 600 | | |
545 | | - | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
546 | 611 | | |
547 | 612 | | |
548 | 613 | | |
| |||
0 commit comments