Skip to content

plugin-audit: a failed DDL for an audit table at boot is logged at warn and the boot stays green #22913

Closed

Description

@objectstack-fleet

Background

packages/plugins/plugin-audit/src/audit-plugin.ts:541-545 (provisionSystemTables, on main) loops over SysAuditLog, SysActivity, SysComment and SysCommentReaction. When an object's syncObjectSchema throws, it logs AuditPlugin: could not provision ${obj.name} storage — … at warn and continues.

Governing text

AGENTS.md, "Degradation log levels — warn vs error": "Durability / data-consistency degradation → error. … DDL that was supposed to run did not … Nothing looks broken; the loss surfaces a release later". An error there owes ① the consequence and ② the fix, both in its first line.

Evidence that this is not hypothetical

The merge-queue build 38185050868 (#22895) failed one test. packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts:209 found exactly one audit table missing after a green serve boot: os migrate plan listed create_table:sys_activity, while sys_audit_log was present.

The problem

When an audit table's DDL fails, for example on a busy SQLite file, the deployment runs with no sys_activity (or sys_audit_log) table. It looks healthy, and audit and activity writes have nowhere to land. Per the rule above this is a durability degradation, so it should be error, not warn.

Asked of the fix

  • Log at error, once, with the consequence (which audit table is not durable, and that the system keeps looking healthy) and the fix (what restores it, for example os migrate apply, or the opt-out that makes it deliberate). Or fail the boot, if that is the policy for core audit storage.
  • A pin that a forced syncObjectSchema failure on one audit object produces that error line.
  • Optional, for the next occurrence: the integration test above prints the serve child's stderr on failure, so a recurrence names its cause.

Found while root-causing the #22895 dequeue (stage W of #15204). Not part of #15204.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: grade completed, bug · priority:p2 · domain:services · area:records, into pm:queue. Log at error per the AGENTS.md rule; failing the boot is not taken here

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T02:53Z. ⛔ Not a claim, ⛔ not a dispatch.

    • The filer's labels stand. Triage adds area:records and pm:queue. provisionSystemTables in plugin-audit is domain:services.
    • Why p2: a deployment can run with no sys_activity or sys_audit_log table while looking healthy. That is the durability class the governing text names. The evidence is one unexplained CI sighting, so the defect is the log level, not a measured loss.
    • Direction:
      • The failed provisioning logs at error, once per object. Its first line carries ① the consequence (which audit table is not durable, and that the deployment keeps looking healthy) and ② the fix (what restores it, such as os migrate apply).
      • ⛔ Failing the boot is not taken here. Making a boot refuse is a new refusal, and that is a separate call for whoever owns boot policy. A new gate defaults to no. The error line is what the rule asks for.
    • Pins:
      • A forced syncObjectSchema failure on one audit object produces that error line.
      • CONTROL: a clean provision logs no error.
      • The card's optional item, printing the serve child's stderr on failure in plan.telemetry-sibling.integration.test.ts, rides along. It is test-only and names the cause of the next recurrence.
    • Clause-②: no.
  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-12T06:36Z
    Session: session_01CeRCvvvjxbxu2iYJAc5Cce
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-22913-audit-ddl-error-level
    Worktree: objectstack-issue-22913
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface: packages/plugins/plugin-audit/src/audit-plugin.ts (provisionSystemTables), packages/plugins/plugin-audit/src/audit-plugin.test.ts, packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts (test-only ride-along: print the serve child's stderr on failure; a domain:cli file, declared to that lane), and one .changeset/22913-*.md. Stop on breach; explain in the report.
    Container & model: S, not mechanical (the error line's consequence and fix wording is the judgment) ⇒ M treatment, mode:subagent, model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"
    Clause-②: no
    Responsibility: plugin-audit's own provisionSystemTables (this lane's code) produces it | none: no other path says at boot that an audit table is missing; os migrate plan lists it only when an operator runs it | any deployment whose audit-table DDL throws at boot reaches it (one unexplained sighting, merge-queue build 38185050868); nobody relies on the warn today
    Thread-read: 6116902332
    Serial constraints cleared: no open PR touches packages/plugins/plugin-audit/** or packages/cli/src/commands/migrate/** (the file lists of all 23 non-release open PRs, read at this claim). The in-flight #22870 PR 2 (claim 6118835291) declares plugin-audit/src/comment-access-hooks.ts only, which is disjoint. Epic #15204's territory names no plugin-audit file. The seat's hot-file queue on plugin-audit (#22590 item 3's interim C) touches parent-record-read-gate.ts and activity-field-redaction.ts, also disjoint.

    Direction carried into the order is triage's grade 6116902332, quoted there: the failed provisioning logs at error, once per object, and ⛔ failing the boot is not taken. No maintainer ruling exists on this card.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22913,
    "status": "done",
    "branch": "claude/issue-22913-audit-ddl-error-level",
    "pr": "#22981",
    "session": "session_01CeRCvvvjxbxu2iYJAc5Cce",
    "premise_still_valid": true,
    "summary": "A1 holds on 1f312fd: provisionSystemTables caught a throwing sync.call(engine, obj.name) and logged could not provision X storage at warn. Measured through os serve (OS_MIGRATE_AND_EXIT=1) with sys_activity's DDL forced to fail: exit 0, view absent, the warn landed in the Boot diagnostics, N informational block. The catch now logs ONE error per object per boot (never per write; the boot is not failed, per triage 6116902332). Its first line names the object, the per-object loss, that the deployment keeps looking healthy, and the measured fix: resolve the driver's refusal, then restart or run os migrate apply (os migrate plan lists create_table). The driver's refusal goes in meta {object, error}. A3: both remedies were measured to re-create a missing sys_activity once the fault was gone, with and without the telemetry sibling. OS_SKIP_SCHEMA_SYNC is not named: measured, this provisioning issues DDL under that flag. A4 took branch 2: a syncObjectSchema vocabulary entry lights 3 sites outside the file surface and does not light this site, because the checker reads sync.call(...) as callee call. No entry added; the deviation is recorded. A2: within the plugin the line is once per object; objectql's boot sync also logs Schema sync FAILED for object X (twice per serve boot) when it tried the same object, and the new line says that is the same refusal. Ride-along: the telemetry-sibling integration test carries the serve child's stderr/stdout in every post-boot assertion message.",
    "tests": "All on head 9ac3a46 (the last commit 9ac3a46 touches only the changeset; source = 168cf1e). pnpm --filter @objectstack/plugin-audit test: Test Files 47 passed, Tests 748 passed. pnpm --filter @objectstack/plugin-audit typecheck (at 168cf1e): exit 0, check:test-typecheck OK, 0 debt. pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 src/commands/migrate/plan.telemetry-sibling.integration.test.ts: 2 passed. pnpm --filter @objectstack/cli typecheck: exit 0, debt unchanged (3 files / 28 errors / 6 signatures, touched file not among them). cli unit layer declared to CI (only an integration test file changed). Ablations, via scripts/ablation-replace.mjs WRAP mode on committed 168cf1e, restore proved blob 435b0fb1dc16 == HEAD and git diff HEAD empty: A, the catch back at warn: 5 failed / 10 passed (four per-object pins expected [] to have a length of 1 but got +0, plus the placement test); CONTROL green. B, the error emitted on the clean path: 5 failed / 10 passed; CONTROL fails expected [ { ...(3) }, { ...(3) }, { ...(3) }, ...(1) ] to deeply equal []. B's first spelling was a no-op: its replacement contained its anchor, so ablation-replace refused it (anchor 1 to 1) and ran no test. The second anchor ran. No dist/build in either leg: the test imports ./audit-plugin.js relatively, so vitest reads src. Ride-along proof: the control's expectation was mutated, and the failure opened with --- the serving boot's stderr (exit 0) --- plus the boot-diagnostics block. Restored by blob c9102fdd5098 == HEAD. End-to-end on the branch after rebuilding plugin-audit dist: forced sys_activity refusal; serve exit 0; the new ERROR line printed once at OS_LOG_LEVEL=warn, with meta carrying ... there is already an index named sys_activity. Narrowed eslint --no-inline-config: 3 files (json count), 0 errors, 0 warnings; print-config resolves rules for each; no parserOptions.project/projectService, so linting is not type-aware and untouched files are unaffected. Repo-wide pnpm lint is declared to CI.",
    "gates": "Derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 9ac3a46 over the real change set (4 paths): 67 commands: the dispatch's list plus the changeset/release families the .changeset path adds. --ran verdict: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. Five first exited 3 (PREREQUISITE NOT MET) and were rerun to exit 0: check-plugin-teardown-shape --self-test (after git fetch --depth=1 origin 621a4876 for its pinned control), check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity (after a full workspace turbo build, 72/73 cache hits). Plus pnpm check:durability-log-level run explicitly: exit 0, 46 durability-critical catch seam(s), all loud (unchanged count: this site is outside the checker's view). check:nul-bytes exit 0. CI on 9ac3a46 at report time: 13 completed (0 failed), 18 in_progress.",
    "line_budget": "219 changed lines (+193 / -26) over 4 files, under the 3000 human-merge threshold (dispatch-gates reading). No governed surface touched; no skills/** edit.",
    "files_changed": [
    "packages/plugins/plugin-audit/src/audit-plugin.ts",
    "packages/plugins/plugin-audit/src/audit-plugin.test.ts",
    "packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts",
    ".changeset/22913-audit-provision-failure-error.md"
    ],
    "deviations": [
    "A4 branch 2 / AGENTS.md 'add it to DURABILITY_CRITICAL_CALLEES in the same PR that fixes it': NOT done. Measured on 1f312fd in a scratch copy of the checker: the entry raises seams 46 to 49 and goes red on cli schema-migration-plugins.ts:2401 (SILENT), metadata-protocol protocol.ts:19337 (QUIET warn@19338) and service-datasource datasource-connection-service.ts:658 (QUIET warn@659), all outside the file surface. It does not light the audit site at all (callee read as call). scripts/check-durability-degradation-log-level.mjs is untouched.",
    "Commit trailers: the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body footer is AGENTS.md's session-URL form, not the harness's two-line footer.",
    "provisionSystemTables docblock: removed the stale 'lazy-created on first WRITE (the SQL driver issues DDL when the first row is inserted)' (measured false: the insert answers no such table). Comment-only, inside the file surface.",
    "git fetch --depth=1 origin 621a487607881c66b2899b7e3477115229a156b4 was written to the shared object store, to meet check-plugin-teardown-shape --self-test's prerequisite (it adds one shallow root; no ref moved besides this worktree's FETCH_HEAD).",
    "Labels: zero writes (the order names none; the changeset publishes, so skip-changeset does not apply)."
    ],
    "mcp_calls": "0",
    "api_writes": "2 relay strokes, both through scripts/pm (fleet-write relay, as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/dispatches, executing POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/22981/assignees [os-warren], run 38218333454, read-back 11001 bytes identical; (2) this os-dev-report comment = POST /repos/objectstack-ai/objectstack/dispatches, executing POST /repos//issues/22913/comments via post-stamped. Not REST: 3 x git push of the branch (empty probe, 168cf1e, 9ac3a46).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: b · reach: public door measured. os serve (OS_MIGRATE_AND_EXIT=1) over an existing schema with sys_comment_reaction dropped, run with OS_SKIP_SCHEMA_SYNC=1: objectql logs Skipping schema sync (OS_SKIP_SCHEMA_SYNC=1) - assuming DDL is managed out-of-band; registered 78 object schema(s) ... WITHOUT DDL, and the table is re-created anyway, by AuditPlugin.provisionSystemTables. The boot says no DDL, and DDL ran. · Seam: spec: OS_SKIP_SCHEMA_SYNC / ObjectQLPlugin skipSchemaSync (packages/objectql/src/plugin.ts:357) -> runtime: packages/plugins/plugin-audit/src/audit-plugin.ts provisionSystemTables -> engine.syncObjectSchema (packages/objectql/src/engine.ts:20108, no flag check). service-messaging's provisionSystemTables has the same shape (not measured). · dedupe: OS_SKIP_SCHEMA_SYNC, skipSchemaSync out-of-band DDL, provisionSystemTables syncObjectSchema, audit plugin DDL skip schema sync",
    "class: b (family: syncObjectSchema durability log level; one family card with the DURABILITY_CRITICAL_CALLEES entry) · reach: public door measured. os serve (OS_MIGRATE_AND_EXIT=1, OS_TELEMETRY_DB=0) with sys_notification's DDL refused (pre-created INDEX of that name) exits 0 and prints WARN [messaging] could not provision sys_notification storage - create table ... inside the Boot diagnostics, N informational block. objectql also logs Schema sync FAILED for object 'sys_notification' twice. · evidence: packages/services/service-messaging/src/messaging-service-plugin.ts:458 is this card's twin (provisionSystemTables catch, warn, after sync.call). AGENTS.md's one question answers Yes for its side-effect pipeline tables (a triggering write succeeds while its notification row cannot land), as for sys_audit_log. Invisible to the checker even with the entry (callee call). · Seam: spec: none (AGENTS.md Degradation log levels) -> runtime: messaging-service-plugin.ts:458 · dedupe: could not provision storage warn, messaging provisionSystemTables, syncObjectSchema durability log level, DURABILITY_CRITICAL_CALLEES syncObjectSchema",
    "carrier: the seat's syncObjectSchema family card · noted, not filed. packages/services/service-datasource/src/datasource-connection-service.ts:659: the checker with the entry says QUIET (warn@659). Reach not measured (needs a runtime datasource connect with bound/mapped objects). Not judged Yes from reading: for a federated object syncObjectSchema issues no DDL (registerExternalObject installs the read mapping), so a failure leaves reads resolving to a table named after the object while connect answers connected. That is a wrong read rather than a lost write; the seat judges whether it is the durability class.",
    "carrier: the seat's syncObjectSchema family card · noted, not filed. packages/metadata-protocol/src/protocol.ts:19337 ensureObjectStorage: the checker with the entry says QUIET (console.warn@19338). On publish, a refused DDL leaves the object registered with no table until restart. Reach not measured.",
    "carrier: the seat's syncObjectSchema family card · noted, not filed. packages/cli/src/utils/schema-migration-plugins.ts:2401: the checker with the entry says SILENT, but the catch counts each refusal into the plan's notes (The driver REFUSED schema registration for N object(s) ...), a per-item outcome report. That is likely the third legal answer, to declare in FAILURE_PROPAGATION_SITES, not a log to add.",
    "carrier: the seat's syncObjectSchema family card · noted, not filed. Checker blind spot: calleeName() in scripts/check-durability-degradation-log-level.mjs does not unwrap .call/.apply/.bind on the guarded side (resolveLogCallee does on the log side), so sync.call(engine, name) sites (audit, messaging) stay invisible to any vocabulary entry. The family card needs either that unwrap or the sites respelled engine.syncObjectSchema(name). Until then this PR's unit pins hold the audit site.",
    "carrier: none · noted, not filed (observation). One os serve boot with one refused object prints objectql's Schema sync FAILED for object X and its finished with 1 FAILED object(s) summary twice, about 350 ms apart (two sync passes). Measured in all three forced-refusal boots. Not established whether the second pass is wrong.",
    "carrier: none · noted, not filed. In the same method, the no syncObjectSchema warn still says the tables 'stay lazy-created on first WRITE' (stale for the SQL driver), and the resolves to NO datasource driver branch stays at warn. No composition reaching it was measured, and triage pinned only the throwing branch."
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat ACCEPT: PR #22981 at 9ac3a46f5 · landing waits on a green head

    domain:services seat 1 · session_01CeRCvvvjxbxu2iYJAc5Cce · 2026-10-12T07:47Z, under claim 6119084257. The dev's report is the os-dev-report above. Checked on GitHub and on origin/main, not from the report.

    Checklist

    • PR form: draft, base main; the first line is Fixes #22913, the second Clause-②: no. No other card number sits near a closing keyword. The assignee is os-warren.
    • Scope: 4 files, +193 / −26, exactly the claim's surface: audit-plugin.ts, audit-plugin.test.ts, the cli telemetry-sibling integration test (test-only) and .changeset/22913-audit-provision-failure-error.md. Nothing under content/docs/releases/. check-governed-merges --pr 22981: NOT governed, 219 lines.
    • Changeset: a patch for @objectstack/plugin-audit, read sentence by sentence against the diff. "logged at error, once per object per boot and never per write" matches the catch. "The boot is still not failed" matches continue. The meta { object, error } sentence matches the call. "A log search on could not provision sys_activity storage … still matches" holds: the message keeps that prefix.
    • Spot checks:
      • The catch's call is ctx.logger.error(line, undefined, { object, error }). On origin/main, core's ObjectLogger.error(message, errorOrMeta?, meta?) dispatches through writeErrorLike, whose docblock records that the third argument is read when the second is undefined. The dev's end-to-end boot printed the meta.
      • A4's stop branch is evidenced on origin/main a2240c4dc: calleeName() in scripts/check-durability-degradation-log-level.mjs returns the property name, so sync.call(engine, …) reads as call. The three sites the entry would light each call engine.syncObjectSchema(…) directly (read in schema-migration-plugins.ts, protocol.ts and datasource-connection-service.ts).
    • Pins and ablations: the four per-object error pins and the CONTROL are in the diff. Both ablations and the ride-along proof are quoted in the PR body with their failure output, and the restores are proved by blob.
    • Deviations accepted:
      • A4 branch 2, as the order required.
      • Model-free commit trailers, per AGENTS.md.
      • One shallow git fetch of a pinned control commit into the shared object store; no ref moved.
      • mcp_calls is 0.

    CI on 9ac3a46f5 (read at this comment, latest run per check name):

    Out-of-scope findings


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22981 · domain:services seat 1 · 2026-10-12T08:37Z

    session_01CeRCvvvjxbxu2iYJAc5Cce, under claim 6119084257 and ACCEPT 6119997110.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:servicespriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions