Skip to content

fix(plugin-audit): the generic activity entry for an update names the fields that changed - #25958

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-yly3fj
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-yly3fj

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #25787

Clause-②: no
Metadata-protocol: none

What changed

The activity mirror in plugin-audit (audit-writers.ts, ADR-0052 §5b) writes an update entry in one of three shapes: a fired milestone's sentence, a trackHistory diff (Stage: Proposal → Closed Won), or a generic fallback. The fallback said Updated OBJECT "LABEL" and nothing else. On HotCRM 18.1.0 the campaign roll-up counters (num_sent, num_responses, … — plain number fields, none tracked, bumped once per enrolled member by campaign_member.hook.ts) produced thirteen identical entries in a row.

The fallback now names the changed fields by label, in declaration order:

Updated Campaign "CPG-0001": Number Sent, Responses, and Leads

  • Labels only, never values. trackHistory still decides whether old → new VALUES are rendered; an untracked field's value (or a credential's) never reaches the text. The ADR-0052 §5b.1 opt-in is unchanged.
  • Bounded. At most five fields are named and the rest counted (…, Field 5, and 3 more). When sys_activity.summary declares maxLength (500 today; the SQL driver sizes the column varchar(500) from it, so an over-long text fails the whole activity insert), fewer fields are named until the text fits; if not even one name fits, the field-less frame is written as before.
  • Redaction still applies. Every named or counted field is recorded in the row's summary-source declaration (text_sources.summary), so activity-field-redaction.ts drops the summary for a reader not served one of them, exactly as for a tracked-change summary.
  • Localized. Two message keys in the plugin's own en / zh-CN / ja-JP / es-ES bundles (translations/messages.ts): messages.activityUpdatedFields (frame with {{fields}}) and messages.activityMoreFields ({{count}} more). The list is joined with Intl.ListFormat in the frame's locale (zh-CN: F1、F2、F3、F4、F5和另外 2 个字段). A locale bundle that has only the older messages.activityUpdated (an app-shipped locale) keeps its own field-less sentence rather than an English one.
  • Unchanged: which rows are written (no input refused, no event dropped), the milestone and tracked branches, create/delete summaries, metadata.old/new, changed_fields.

Folding consecutive same-actor entries is the renderer's half (objectui) and is not in this PR; #25787's triage routes it to its own card.

Evidence

  • Red first: the new and updated cases in audit-writers.test.ts run against origin/main 2a728ba2's audit-writers.ts + messages.ts (restored into the tree, then git checkout HEAD -- back; git diff HEAD 0 bytes after): Tests 8 failed | 108 passed (116). With the fix: all pass.
  • pnpm --filter @objectstack/plugin-audit test: Test Files 60 passed (60), Tests 993 passed (993) (before the last two commits; audit-writers.test.ts re-run at head: 117 passed).
  • pnpm --filter @objectstack/plugin-audit typecheck: exit 0.
  • Pins: declaration order; no value (untracked or secret) in the text; source declaration; five-name cap with count; 40 long labels under a declared 500 bound (length at most 500, count still present); field-less frame when nothing fits; zh-CN list; an app locale with only activityUpdated keeps its sentence. audit-milestone-summary.test.ts and audit-option-label-summary.test.ts pass unchanged.

Gates (local subset, at head cebc2d01)

  • pnpm --filter @objectstack/plugin-audit test at cebc2d01: Test Files 60 passed (60), Tests 993 passed (993); pnpm --filter @objectstack/plugin-audit typecheck: exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands (72 families, merge base 2a728ba2a), reconciled with --ran: 71 run, all exit 0; 1 NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3, needs a whole-tree build; CI's lint lane owns it). check:i18n was run after its declared CLI closure build (10 packages in sync, plugins/plugin-audit 7 bundles); check-plugin-teardown-shape --self-test after fetching its pinned fixture commit.
  • check:commit-card-trailers --range origin/main..HEAD: 4 commits clean. check:nul-bytes: exit 0.
  • origin/main moved 5 commits since the base; none touches packages/plugins/plugin-audit, so no merge was taken.
  • CI's own families are read from this PR's check-runs.

Acceptance notes

  • An update that changes only computed fields (formula, summary, rollup, autonumber) writes no activity row at all: diff() drops those types and the writer returns on an empty diff. Unchanged here, noted by the seat on the claim.
  • The 500 bound is guaranteed for the field list this PR adds. A record label or object label that alone exceeds it overflows every branch (create, delete, generic), and a trackHistory diff of long values is unbounded too; both predate this PR. Not measured against a SQL driver here.
  • A changed key the object does not declare is named by its machine key (the tracked branch's existing label fallback).

Generated by Claude Code

…hanged

An update that moves no `trackHistory` field and fires no milestone wrote
`Updated <object> "<label>"` and nothing else, so a record a rollup or flow
bumps repeatedly showed a wall of identical feed entries. The fallback now
names the changed fields by label (never their values) — at most five, the
rest counted — and names fewer when the text would exceed the
`sys_activity.summary` bound. Every field it names or counts is declared as a
summary source, so the read-side field redaction still applies.

Claude-Session: https://claude.ai/code/session_0166WnEHW3hURHjBf9cLK32f
Co-authored-by: Claude <noreply@anthropic.com>
The untracked-change fallback names the fields that changed, in declaration
order, never their values; at most five are named and the rest counted; the
text stays inside the declared `sys_activity.summary` bound; every named or
counted field is declared as a summary source; zh-CN localizes the list.

Claude-Session: https://claude.ai/code/session_0166WnEHW3hURHjBf9cLK32f
Co-authored-by: Claude <noreply@anthropic.com>
…ps it

An app-shipped locale bundle that defines `messages.activityUpdated` but not
the new `messages.activityUpdatedFields` would have been handed an English
named-field sentence; it keeps its own localized field-less one. Adds the
patch changeset.

Claude-Session: https://claude.ai/code/session_0166WnEHW3hURHjBf9cLK32f
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-audit, touching 13 documentable anchor(s).

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/kernel/runtime-services/audit-service.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/permissions/system-context.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/plugins/packages.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/ui/setup-app.mdx (via sys_activity (literal, a string literal in installAuditWriters))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v14.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/releases/v16.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/releases/v17/17-6.mdx (via sys_activity (literal, a string literal in installAuditWriters))
  • content/docs/releases/v17/17-7.mdx (via sys_activity (literal, a string literal in installAuditWriters))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d65cbb61bdea303f4f414e76fdf747b448f3a9af → packageMentionDocs.

Which tree this was computed on

This run read content/docs from bbf5d5ab0f5a15125f66b8c57a5ff4c84a4b2227 — the merge of head cebc2d010b94c16dcc4e11e1ad2047e46a9f6869 into base d65cbb61bdea303f4f414e76fdf747b448f3a9af, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bbf5d5ab0f5a15125f66b8c57a5ff4c84a4b2227 && git checkout bbf5d5ab0f5a15125f66b8c57a5ff4c84a4b2227
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d65cbb61bdea303f4f414e76fdf747b448f3a9af cebc2d010b94c16dcc4e11e1ad2047e46a9f6869 && git checkout -B drift-repro d65cbb61bdea303f4f414e76fdf747b448f3a9af && git merge --no-ff cebc2d010b94c16dcc4e11e1ad2047e46a9f6869

node scripts/docs-audit/affected-docs.mjs --json d65cbb61bdea303f4f414e76fdf747b448f3a9af

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d65cbb61bdea303f4f414e76fdf747b448f3a9af → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 (#20966) · seat session session_01TFnDdepdwyDJeM2kJ4GtMy · 2026-10-26T06:11Z · head cebc2d010b94c16dcc4e11e1ad2047e46a9f6869 · card #25787 (claim 6408645967, amendment 6408666910, report 6409150926)

  1. Shape: 4 files, +324/−11: plugin-audit's audit-writers.ts, its test, translations/messages.ts (two keys in each of en, zh-CN, ja-JP and es-ES, per the amendment), and one patch changeset. Clause-②: no holds: no export changes and no schema; only the text of the fallback entry gains content. There is no published schema, no governed path (check-governed-merges --pr 25958: NOT governed), and no narrowing declaration, so no contract-tier review is owed. This is the seat's own TIER_DEFAULT review of record.
  2. Premise, measured: the fallback (no milestone fired and no trackHistory field changed) now names the changed fields by label, in declaration order. It names at most five and counts the rest, and it shrinks the list until the text fits the maxLength that sys_activity.summary declares, so a wide update no longer fails the activity insert. Values are still rendered only for trackHistory fields (ADR-0052 §5b.1), so an untracked field's label reaches the text but its value never does. Every named or counted field joins summarySources, so the read side's field redaction drops the summary for a reader not served one of them. A locale that has only the old activityUpdated key keeps its own field-less sentence. The new tests were red on the base source (8) and green on this head. The whole package passes 993/993.
  3. Gates: check-runs on this head: 46 total, 36 success, 10 skipped, 0 failed, 0 in progress; mergeable clean.
  4. Accepted deviations: none beyond the amendment.
  5. Out-of-scope findings: the renderer half (folding consecutive same-actor entries) is filed as record activity timeline: fold consecutive same-actor update entries on one record into one row with a count (renderer half of #25787) #25959 for triage. An update that changes only computed fields writes no activity row (the diff excludes them). That is not this card's path, and it is noted here only. Fixes #25787 is right: the card's first grade split the renderer half into its own card.
  6. Landing: Tier S, not over 3000 lines. The maintainer's standing landing word for this seat thread is on record (2026-10-26 05:53Z), so it lands now through the relay.

Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants