You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-audit's recordLabel guesses from a fixed key list instead of the ADR-0079 resolver, so an object titled by company_name shows its record id on every activity row #21878
objectstack-ai/cloud acceptance run on 2026-10-05 (cloud main 4b38cd28 · objectstack main 088428fb · objectui main 76993f8e). A new free user had the AI build a 客户 object. Its fields are company_name / phone / status; it has no name field and declares no nameField. Every row on the record's discussion feed reads:
Created 客户 "RZGAEnis7ms__pjv"
Updated 客户 "RZGAEnis7ms__pjv"
sys_activity.record_label holds the record id for all 15 rows of that object. The record page header shows 「宁波海晟进出口有限公司」 for the same record, because the renderer resolves the title through ADR-0079.
(The English verb is a separate cause: the workspace has no default locale. That half is tracked on the cloud side, not here.)
Mechanism
packages/plugins/plugin-audit/src/audit-writers.ts:299recordLabel() guesses the label from a fixed list of keys and falls back to the id:
company_name is not in that list. The same file already imports resolveDisplayField (line 28) and uses it for referenced-record titles (~line 1303). So the activity label is a second display-name dialect beside the ADR-0079 resolver that every renderer uses.
Measured against the spec on objectstack main 088428fb:
resolveDisplayField({ fields: { company_name, phone, status } }) → company_name
Resolve the label through resolveDisplayField(objectDef), and keep the id only as the floor when that resolves nothing or the value is empty. Keep the field answer that #21081's read-side gating relies on: it becomes the resolved field.
Acceptance
A record of an object titled only by company_name gets record_label = the company name, and field = company_name.
Objects that have name / title / subject are labelled exactly as today.
An object whose resolved title value is empty still falls back to the id.
Path: business objects and records — record changes are visible | records-forms.field-history-tracking | P2
Triage: first grade — bug · priority:p2 · domain:services · area:records (the filer's bug stands) · pm:queue. The activity label resolves through ADR-0079's resolver; the id stays only as the floor
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T13:52Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/plugins/plugin-audit/src/audit-writers.ts (recordLabel, about :299) ⇒ domain:services; rationale: a fixed key list is a second display-name dialect beside resolveDisplayField, which the same file already imports.
Why p2. Every activity row of an object titled by anything off the list shows a record id. AI-built objects meet this routinely: the measured case is a 客户 object titled by company_name. It was found in the 2026-10-05 cloud acceptance run.
Direction, as the card states.
recordLabel resolves through resolveDisplayField(objectDef). The id is the floor only when that resolves nothing or the value is empty.
content/docs/** sentences this makes false, and a patch changeset.
⛔ The fixed key list is deleted, not extended. ⛔ No change to #21081's read-side masking: a reader not served the resolved field does not see the label. ⛔ No packages/spec edit.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier).
Clause-②: no
The activity label written for a record follows the display field every renderer already resolves. No accepted input moves.
Thread-read: 5995881267
Serial constraints cleared: at 2026-10-05T15:14Z:
os-dev-report
{
"issue": 21878,
"status": "done",
"branch": "claude/issue-21878-activity-label-display-field",
"pr": "#21896",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN — the dispatching PM session (this run is its subagent)",
"premise_still_valid": true,
"summary": "plugin-audit's activity mirror now labels a record by its ADR-0079 title: a new titleFieldOf(objectName) closure in audit-writers.ts asks resolveDisplayField over the cached getObjectDef definition (no second lookup) and floors to the id when nothing resolves, the resolver lands on id, or the title field is a credential or internal; resolveLookupTitles now asks the same closure (its three inline skips moved there, behaviour-identical). recordLabel(record, titleField, id) reads that field from the same masked ledger view, floors a null, structured or blank value to the id (never borrowing another field), and returns the resolved field, which the row declares as the label's source, so #21081's read-side redaction is unchanged and now keys on company_name. The fixed key list is deleted. Measured H3 (registered through a real ObjectQL registry): of 32 example objects 27 keep their label field and 5 change (crm_contact full_name to first_name because its full_name formula declares no returnType text; crm_opportunity_line_item, showcase_expense_line, showcase_ext_order, showcase_invoice_line move from the id to a field); of 50 platform objects 24 move from the id to their nameField and 3 change field (sys_email_template, sys_invitation, sys_scim_connection_credential). Stored rows are not backfilled.",
"tests": "All at head e0da4f8 unless stated. (1) plugin-audit: vitest run --maxWorkers=2, 40 files / 641 tests passed. (2) plugin-audit typecheck exit 0, check:test-typecheck OK 0 debt (run at 1088327; git diff 1088327e0da4f8 -- packages/plugins/plugin-audit is empty). (3) dogfood --project isolated: new activity-label-display-field.dogfood.test.ts + sibling activity-field-values.dogfood.test.ts, 2 files / 14 tests passed against a rebuilt plugin-audit dist (titleFieldOf present 3x in dist/index.mjs, ablation marker 0x); dogfood typecheck exit 0. (4) New unit file audit-record-label-display-field.test.ts: 11 tests passed (company_name on create/update/delete with declared source, name/title/subject unchanged, explicit nameField over name, blank-title and empty-name-beside-title floors to id, credential title floors to id, unserved reader gets neither label nor summary, control keeps both). (5) Fixture triage audit-writers.test.ts: 4 localized-summary cases wrote records of person_qualification, an object the fake engine never declared, and passed only via the key list; disposition: declare the object titled by name; 93/93 pass. (6) Narrowed lint: eslint --no-inline-config --format json over the 4 changed TS files: 4 files, 0 errors, 0 warnings; population: each file resolves under --print-config; invariance: eslint.config.mjs enables no type-aware linting, so this diff cannot move a verdict on an untouched file; pnpm lint is CI's. ABLATION (fixed key list put back, scripts/ablation-replace.mjs, anchor hit 1, blob 47497d1d8eed to 61d82281885f, fix committed first, trap restore armed): unit leg (source-resolved) 7 red / 4 green exactly as predicted, first red 'expected 3JO4ITH0my2YkCtR to be ARDCOMPANYONE41' (the card's symptom), restore blob 47497d1d8eed == HEAD and git diff HEAD empty; dogfood leg (dist-resolved) mutate + rebuild (build exit 1 with output discarded; ablation-dist-preflight found the marker in both JS bundles, exit 0) then 2 red (company_name label case; withheld reader served a record_label) / 2 green (name case, empty-title floor), restore blob == HEAD, rebuilt, preflight --absent exit 0 (marker absent from all 6 built files, tree clean), then the green dogfood run in (3).",
"mcp_calls": "0 — none",
"api_writes": "3 — all through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft, #21896, run 37340288214, body read back identical 8619 bytes); (2) label-write assignee = POST /repos//issues/21896/assignees os-steve (run 37340384784, output says MATCHES via the relay run; zero labels in target); (3) this os-dev-report comment = POST /repos//issues/21878/comments via scripts/pm/post-stamped.mjs. git push is not counted (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · crm_contact in examples/app-crm titles by first_name because its full_name formula declares no returnType text, so ADR-0079 does not derive it; the activity label now agrees with the record page; whether the example should title contacts by full_name is the example's call (observation, no class a/b/c)",
"carrier: none (承接者:无) · noted, not filed · audit-writers.ts getObjectDef/getFieldDefs caches are never invalidated, so a definition changed at runtime may be read stale by the label, milestones and enable.activities alike; read from source only, not measured, predates this change",
"carrier: none (承接者:无) · noted, not filed · boundary: a formula title (display_title on several platform objects) is declared as the label's only source though computed from other fields; a reader served the formula is served the same value by the data plane, so the label adds no exposure"
],
"gates": {
"derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at e0da4f8 (merge base e085a8c, 5 paths)",
"commands": [
"exit 0 :: node scripts/check-adr-0087-registration.mjs --base origin/main",
"exit 0 :: node scripts/check-adr-0087-registration.mjs --self-test",
"exit 0 :: node scripts/check-changeset-no-major.mjs --base origin/main",
"exit 0 :: node scripts/check-changeset-no-major.mjs --self-test",
"exit 0 :: node scripts/check-ci-filter-parity.mjs",
"exit 0 :: node scripts/check-closing-keyword-parity.mjs",
"exit 0 :: node scripts/check-closing-keyword-parity.mjs --self-test",
"exit 0 :: node scripts/check-comment-mask-adoption.mjs",
"exit 0 :: node scripts/check-comment-mask-adoption.mjs --self-test",
"exit 0 :: node scripts/check-comment-mask-corpus.mjs",
"exit 0 :: node scripts/check-dts-emitted.mjs --self-test",
"exit 0 :: node scripts/check-empty-changeset.mjs --base origin/main",
"exit 0 :: node scripts/check-empty-changeset.mjs --self-test",
"exit 0 :: node scripts/check-issue-citations.mjs",
"exit 0 :: node scripts/check-keyed-text-bounds.mjs",
"exit 0 :: node scripts/check-keyed-text-bounds.mjs --self-test",
"exit 0 :: node scripts/check-platform-object-tenancy-census.mjs",
"exit 0 :: node scripts/check-platform-object-tenancy-census.mjs --self-test",
"exit 0 :: node scripts/check-plugin-teardown-shape.mjs",
"exit 0 :: node scripts/check-plugin-teardown-shape.mjs --self-test",
"exit 0 :: node scripts/check-registry-log-declared.mjs",
"exit 0 :: node scripts/check-registry-log-declared.mjs --self-test",
"exit 0 :: node scripts/check-rest-log-spy-declared.mjs",
"exit 0 :: node scripts/check-rest-log-spy-declared.mjs --self-test",
"exit 0 :: node scripts/check-system-context-census.mjs",
"exit 0 :: node scripts/check-system-context-census.mjs --self-test",
"exit 0 :: node scripts/check-tenant-audit-census.mjs",
"exit 0 :: node scripts/check-tenant-audit-census.mjs --self-test",
"exit 0 :: node scripts/check-undeclared-dep-imports.mjs",
"exit 0 :: node scripts/check-undeclared-dep-imports.mjs --self-test",
"exit 0 :: node scripts/docs-audit/check-affected-docs.mjs",
"exit 0 :: node scripts/docs-audit/check-drift-comment.mjs",
"exit 0 :: node scripts/pm/release-rehearsal-clone.mjs --self-test",
"exit 0 :: node scripts/release-pending-publish.mjs --self-test",
"exit 0 :: pnpm --filter @objectstack/spec run check:duration-unit-keys",
"exit 0 :: pnpm --filter @objectstack/spec run check:empty-state",
"exit 0 :: pnpm --filter @objectstack/spec run check:liveness",
"exit 0 :: pnpm --filter @objectstack/spec run check:strictness-ledger",
"exit 0 :: pnpm --filter @objectstack/spec run check:variant-docs",
"exit 0 :: pnpm check:changeset-gate-self-tests",
"exit 0 :: pnpm check:cross-package-test-inputs",
"exit 0 :: pnpm check:dispatcher-error-vocabulary",
"exit 0 :: pnpm check:doc-authoring",
"exit 0 :: pnpm check:driver-memory-census",
"exit 0 :: pnpm check:dts-closure (first reading exit 1: hono declarations missing while this worktree's own closure build rewrote that dist; re-run exit 0, 169/169 present)",
"exit 0 :: pnpm check:dual-build-cjs-loads (first reading exit 3 PREREQUISITE NOT MET: 9 packages unbuilt in this worktree; re-run after full turbo build exit 0)",
"exit 0 :: pnpm check:engine-double-contract",
"exit 0 :: pnpm check:gitlink-declared",
"exit 0 :: pnpm check:i18n",
"exit 0 :: pnpm check:i18n-stale-fill",
"exit 0 :: pnpm check:issue-citations",
"exit 0 :: pnpm check:lean-entry-closure",
"exit 0 :: pnpm check:logger-receiver-detach",
"exit 0 :: pnpm check:nul-bytes",
"exit 0 :: pnpm check:objectql-double-limit",
"exit 0 :: pnpm check:objectui-changeset",
"exit 0 :: pnpm check:org-identifier",
"exit 0 :: pnpm check:page-declaration-shape",
"exit 0 :: pnpm check:pm-changeset-deadline-census",
"exit 0 :: pnpm check:published-files",
"exit 0 :: pnpm check:query-options-erasure",
"exit 0 :: pnpm check:refd-timer-probe",
"exit 0 :: pnpm check:slot-lookup",
"exit 0 :: pnpm check:sourcemap-no-sources-content",
"exit 0 :: pnpm check:test-source-alias",
"exit 0 :: pnpm check:tier-file-adoption",
"exit 0 :: pnpm check:type-check-coverage",
"exit 0 :: pnpm check:type-check-debt",
"exit 0 :: pnpm check:watch-hint-literal",
"exit 0 :: pnpm check:where-matcher"
],
"ran_verdict": "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack: exit 0, 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN (derived zero, all 70 recorded an exit code)",
"dispatch_list_compare": "the dispatch-time list (62) is a strict subset of the derived 70; the 8 extra are the changeset-kind families (adr-0087-registration x2, empty-changeset x2, release-rehearsal-clone, release-pending-publish, objectui-changeset, pm-changeset-deadline-census)"
},
"line_budget": "n/a",
"deviations": [
"Worktree based on origin/main e085a8c (the dispatch base 07c842d had moved); origin/main later advanced 4 commits with zero overlap with this diff or the gate derivation, not merged; the queue rebuilds on current main.",
"H1 reproduce-first: the red reading on a company_name-titled object was taken by ablation (the fixed key list put back over the committed fix, same selection code as main) rather than before writing the fix.",
"resolveLookupTitles was touched: its id/credential/internal skips moved into the shared titleFieldOf so the record label and referenced titles share one answer; behaviour-identical, its existing pins (audit-lookup-summary, milestone, implicit-reference-target) pass.",
"An existing plugin-audit test file was edited (audit-writers.test.ts, fixture triage: declare the undeclared fixture object); within the declared plugin-audit test surface; no existing packages/qa file was edited.",
"The credential-title unit case uses a password field: a secret field needs a crypto provider and the sys_secret table, which this harness does not carry; both are masked by the same collectMaskedReadFields predicate.",
"The dogfood door pin also carries the masking case (a permission set withholding company_name; control served it), beyond the three named cases.",
"Two gates read red first on build state, not on this diff (check:dts-closure raced this worktree's own closure build; check:dual-build-cjs-loads PREREQUISITE NOT MET); both re-run 0 and the --ran record carries the re-run codes.",
"Labels documentation, size/l, tests, tooling on #21896 were applied by the platform labelers, not by this run."
],
"files_changed": [
".changeset/21878-activity-label-display-field.md",
"packages/plugins/plugin-audit/src/audit-writers.ts",
"packages/plugins/plugin-audit/src/audit-writers.test.ts",
"packages/plugins/plugin-audit/src/audit-record-label-display-field.test.ts",
"packages/qa/dogfood/test/activity-label-display-field.dogfood.test.ts"
]
}
Verdict on the dev report 5998605968 (status: done), checked against GitHub and the tree. Accepted as built. No contract review is owed: Clause-②: no, and no packages/spec path is touched.
Checked:
The fixed key list is deleted. A shared titleFieldOf(objectName) in audit-writers.ts runs resolveDisplayField over the cached definition (no second lookup). It skips a resolved id, a credential field and an internal field, the three exclusions referenced-record titles already applied. resolveLookupTitles now calls the same function, so the record label and referenced titles give one answer.
H3, measured through a real registry: of 32 example objects, 27 keep their label field and 5 change. crm_contact moves from full_name, a formula with no text returnType, to first_name, its ADR-0079 title. Four move from the id to a field. Of 50 platform objects, 24 move from the id to their nameField, and 3 change field. All follow the one resolver, which is the card's point. The changeset names the CRM contact case.
Ablation (the key list put back): 7 unit pins red, the first one reading the card's own symptom, and 2 door legs red against a preflighted dist. The restore was proven.
One existing plugin-audit test fixture declared an object the fake engine never had. The fixture now declares it, inside the claim's test surface.
Carried, not filed (in the PR's Acceptance notes): whether the CRM example should title contacts by full_name (the example's call); the audit-writers definition caches are never invalidated (read from source, not measured, and it predates this change).
PR fix(plugin-audit): the activity record label is the record's ADR-0079 title, with the id as the floor #21896 merged through the merge queue as b2388563, which is an ancestor of origin/main (verified with git merge-base --is-ancestor after a fetch). Merged at 2026-10-05T18:09Z. Its first merge group was cut by a job timeout after a slow checkout, before any gate ran. It was re-queued once (5999675966), and the second group passed.
Fixes #21878 closed this card completed. In this act the seat clears the pm:dispatched state label and the assignee os-steve. The domain:services, area:records, priority:p2 and bug labels stay.
Carried, recorded in the seat verdict 5998647720 and the PR's Acceptance notes: whether the CRM example should title contacts by full_name, and the uninvalidated definition caches in audit-writers.ts (not measured; predates this change).
Activity
objectstack-fleet commentedon Oct 5, 2026
Path: business objects and records — record changes are visible | records-forms.field-history-tracking | P2
Triage: first grade —
bug·priority:p2·domain:services·area:records(the filer'sbugstands) ·pm:queue. The activity label resolves through ADR-0079's resolver; the id stays only as the floorTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T13:52Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-audit/src/audit-writers.ts(recordLabel, about:299) ⇒domain:services; rationale: a fixed key list is a second display-name dialect besideresolveDisplayField, which the same file already imports.company_name. It was found in the 2026-10-05 cloud acceptance run.recordLabelresolves throughresolveDisplayField(objectDef). The id is the floor only when that resolves nothing or the value is empty.fieldanswer that security(plugin-audit): an activity row composed at write time may carry a changed field's values to a reader who can read the parent record but not that field (unmeasured; measure first) #21081's read-side gating relies on becomes the resolved field.Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
Claim: PM loop round 3 · 2026-10-05T15:14Z
Session:
session_011K3zqE8Pv1Evw5hc8tZCnNAccount:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-21878-activity-label-display-fieldWorktree:
objectstack-issue-21878Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (at
origin/main07c842df), per triage's direction5995881267:packages/plugins/plugin-audit/src/audit-writers.ts:recordLabel(about:299) resolves the label throughresolveDisplayField(objectDef), the ADR-0079 resolver the same file already uses for referenced records (about:1303). The record id is the floor only when that resolves nothing or the value is empty. Thefieldanswer that security(plugin-audit): an activity row composed at write time may carry a changed field's values to a reader who can read the parent record but not that field (unmeasured; measure first) #21081's read-side gating relies on becomes the resolved field.plugin-audittests, plus a door pin in a NEW file underpackages/qa/dogfood/test/(declared cross-lane on [PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024 in this act).content/docs/**sentences this makes false, and apatchchangeset.⛔ The fixed key list is deleted, not extended. ⛔ No change to #21081's read-side masking: a reader not served the resolved field does not see the label. ⛔ No
packages/specedit.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier).Clause-②: no
Thread-read: 5995881267
Serial constraints cleared: at 2026-10-05T15:14Z:
plugin-audit/src/audit-writers.ts. The seat's in-flight security(data): a predicate-scoped update or delete is refused when its predicate matches only rows the caller cannot read, and succeeds with zero rows when it matches nothing: an existence signal the read door withholds #21829 must not editplugin-audit's comment hooks, a different file. Noarea:recordscard is in flight in this seat.Selection: the lane read fresh at this pick holds two p2 cards in
pm:queue: plugin-audit's recordLabel guesses from a fixed key list instead of the ADR-0079 resolver, so an object titled by company_name shows its record id on every activity row #21878 and security(automation): a paused flow run's stored state keeps record-change values that #21830's mask removes elsewhere — detail withheld pending maintainer #21867. security(automation): a paused flow run's stored state keeps record-change values that #21830's mask removes elsewhere — detail withheld pending maintainer #21867 withholds its detail, and by the maintainer's routing 「只有不能公开的才需要你派发处理」 it is dispatched by the session holding that detail. This card takes the slot approvals: every approval notification reaches the recipient with an empty body — the service puts its text in payload.message, messaging reads payload.body (comment and request-info text are lost) #21847's build freed.Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
os-dev-report
{
"issue": 21878,
"status": "done",
"branch": "claude/issue-21878-activity-label-display-field",
"pr": "#21896",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN — the dispatching PM session (this run is its subagent)",
"premise_still_valid": true,
"summary": "plugin-audit's activity mirror now labels a record by its ADR-0079 title: a new titleFieldOf(objectName) closure in audit-writers.ts asks resolveDisplayField over the cached getObjectDef definition (no second lookup) and floors to the id when nothing resolves, the resolver lands on id, or the title field is a credential or internal; resolveLookupTitles now asks the same closure (its three inline skips moved there, behaviour-identical). recordLabel(record, titleField, id) reads that field from the same masked ledger view, floors a null, structured or blank value to the id (never borrowing another field), and returns the resolved field, which the row declares as the label's source, so #21081's read-side redaction is unchanged and now keys on company_name. The fixed key list is deleted. Measured H3 (registered through a real ObjectQL registry): of 32 example objects 27 keep their label field and 5 change (crm_contact full_name to first_name because its full_name formula declares no returnType text; crm_opportunity_line_item, showcase_expense_line, showcase_ext_order, showcase_invoice_line move from the id to a field); of 50 platform objects 24 move from the id to their nameField and 3 change field (sys_email_template, sys_invitation, sys_scim_connection_credential). Stored rows are not backfilled.",
"tests": "All at head e0da4f8 unless stated. (1) plugin-audit: vitest run --maxWorkers=2, 40 files / 641 tests passed. (2) plugin-audit typecheck exit 0, check:test-typecheck OK 0 debt (run at 1088327; git diff 1088327 e0da4f8 -- packages/plugins/plugin-audit is empty). (3) dogfood --project isolated: new activity-label-display-field.dogfood.test.ts + sibling activity-field-values.dogfood.test.ts, 2 files / 14 tests passed against a rebuilt plugin-audit dist (titleFieldOf present 3x in dist/index.mjs, ablation marker 0x); dogfood typecheck exit 0. (4) New unit file audit-record-label-display-field.test.ts: 11 tests passed (company_name on create/update/delete with declared source, name/title/subject unchanged, explicit nameField over name, blank-title and empty-name-beside-title floors to id, credential title floors to id, unserved reader gets neither label nor summary, control keeps both). (5) Fixture triage audit-writers.test.ts: 4 localized-summary cases wrote records of person_qualification, an object the fake engine never declared, and passed only via the key list; disposition: declare the object titled by name; 93/93 pass. (6) Narrowed lint: eslint --no-inline-config --format json over the 4 changed TS files: 4 files, 0 errors, 0 warnings; population: each file resolves under --print-config; invariance: eslint.config.mjs enables no type-aware linting, so this diff cannot move a verdict on an untouched file; pnpm lint is CI's. ABLATION (fixed key list put back, scripts/ablation-replace.mjs, anchor hit 1, blob 47497d1d8eed to 61d82281885f, fix committed first, trap restore armed): unit leg (source-resolved) 7 red / 4 green exactly as predicted, first red 'expected 3JO4ITH0my2YkCtR to be ARDCOMPANYONE41' (the card's symptom), restore blob 47497d1d8eed == HEAD and git diff HEAD empty; dogfood leg (dist-resolved) mutate + rebuild (build exit 1 with output discarded; ablation-dist-preflight found the marker in both JS bundles, exit 0) then 2 red (company_name label case; withheld reader served a record_label) / 2 green (name case, empty-title floor), restore blob == HEAD, rebuilt, preflight --absent exit 0 (marker absent from all 6 built files, tree clean), then the green dogfood run in (3).",
"mcp_calls": "0 — none",
"api_writes": "3 — all through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft, #21896, run 37340288214, body read back identical 8619 bytes); (2) label-write assignee = POST /repos//issues/21896/assignees os-steve (run 37340384784, output says MATCHES via the relay run; zero labels in target); (3) this os-dev-report comment = POST /repos//issues/21878/comments via scripts/pm/post-stamped.mjs. git push is not counted (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · crm_contact in examples/app-crm titles by first_name because its full_name formula declares no returnType text, so ADR-0079 does not derive it; the activity label now agrees with the record page; whether the example should title contacts by full_name is the example's call (observation, no class a/b/c)",
"carrier: none (承接者:无) · noted, not filed · audit-writers.ts getObjectDef/getFieldDefs caches are never invalidated, so a definition changed at runtime may be read stale by the label, milestones and enable.activities alike; read from source only, not measured, predates this change",
"carrier: none (承接者:无) · noted, not filed · boundary: a formula title (display_title on several platform objects) is declared as the label's only source though computed from other fields; a reader served the formula is served the same value by the data plane, so the label adds no exposure"
],
"gates": {
"derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at e0da4f8 (merge base e085a8c, 5 paths)",
"commands": [
"exit 0 :: node scripts/check-adr-0087-registration.mjs --base origin/main",
"exit 0 :: node scripts/check-adr-0087-registration.mjs --self-test",
"exit 0 :: node scripts/check-changeset-no-major.mjs --base origin/main",
"exit 0 :: node scripts/check-changeset-no-major.mjs --self-test",
"exit 0 :: node scripts/check-ci-filter-parity.mjs",
"exit 0 :: node scripts/check-closing-keyword-parity.mjs",
"exit 0 :: node scripts/check-closing-keyword-parity.mjs --self-test",
"exit 0 :: node scripts/check-comment-mask-adoption.mjs",
"exit 0 :: node scripts/check-comment-mask-adoption.mjs --self-test",
"exit 0 :: node scripts/check-comment-mask-corpus.mjs",
"exit 0 :: node scripts/check-dts-emitted.mjs --self-test",
"exit 0 :: node scripts/check-empty-changeset.mjs --base origin/main",
"exit 0 :: node scripts/check-empty-changeset.mjs --self-test",
"exit 0 :: node scripts/check-issue-citations.mjs",
"exit 0 :: node scripts/check-keyed-text-bounds.mjs",
"exit 0 :: node scripts/check-keyed-text-bounds.mjs --self-test",
"exit 0 :: node scripts/check-platform-object-tenancy-census.mjs",
"exit 0 :: node scripts/check-platform-object-tenancy-census.mjs --self-test",
"exit 0 :: node scripts/check-plugin-teardown-shape.mjs",
"exit 0 :: node scripts/check-plugin-teardown-shape.mjs --self-test",
"exit 0 :: node scripts/check-registry-log-declared.mjs",
"exit 0 :: node scripts/check-registry-log-declared.mjs --self-test",
"exit 0 :: node scripts/check-rest-log-spy-declared.mjs",
"exit 0 :: node scripts/check-rest-log-spy-declared.mjs --self-test",
"exit 0 :: node scripts/check-system-context-census.mjs",
"exit 0 :: node scripts/check-system-context-census.mjs --self-test",
"exit 0 :: node scripts/check-tenant-audit-census.mjs",
"exit 0 :: node scripts/check-tenant-audit-census.mjs --self-test",
"exit 0 :: node scripts/check-undeclared-dep-imports.mjs",
"exit 0 :: node scripts/check-undeclared-dep-imports.mjs --self-test",
"exit 0 :: node scripts/docs-audit/check-affected-docs.mjs",
"exit 0 :: node scripts/docs-audit/check-drift-comment.mjs",
"exit 0 :: node scripts/pm/release-rehearsal-clone.mjs --self-test",
"exit 0 :: node scripts/release-pending-publish.mjs --self-test",
"exit 0 :: pnpm --filter @objectstack/spec run check:duration-unit-keys",
"exit 0 :: pnpm --filter @objectstack/spec run check:empty-state",
"exit 0 :: pnpm --filter @objectstack/spec run check:liveness",
"exit 0 :: pnpm --filter @objectstack/spec run check:strictness-ledger",
"exit 0 :: pnpm --filter @objectstack/spec run check:variant-docs",
"exit 0 :: pnpm check:changeset-gate-self-tests",
"exit 0 :: pnpm check:cross-package-test-inputs",
"exit 0 :: pnpm check:dispatcher-error-vocabulary",
"exit 0 :: pnpm check:doc-authoring",
"exit 0 :: pnpm check:driver-memory-census",
"exit 0 :: pnpm check:dts-closure (first reading exit 1: hono declarations missing while this worktree's own closure build rewrote that dist; re-run exit 0, 169/169 present)",
"exit 0 :: pnpm check:dual-build-cjs-loads (first reading exit 3 PREREQUISITE NOT MET: 9 packages unbuilt in this worktree; re-run after full turbo build exit 0)",
"exit 0 :: pnpm check:engine-double-contract",
"exit 0 :: pnpm check:gitlink-declared",
"exit 0 :: pnpm check:i18n",
"exit 0 :: pnpm check:i18n-stale-fill",
"exit 0 :: pnpm check:issue-citations",
"exit 0 :: pnpm check:lean-entry-closure",
"exit 0 :: pnpm check:logger-receiver-detach",
"exit 0 :: pnpm check:nul-bytes",
"exit 0 :: pnpm check:objectql-double-limit",
"exit 0 :: pnpm check:objectui-changeset",
"exit 0 :: pnpm check:org-identifier",
"exit 0 :: pnpm check:page-declaration-shape",
"exit 0 :: pnpm check:pm-changeset-deadline-census",
"exit 0 :: pnpm check:published-files",
"exit 0 :: pnpm check:query-options-erasure",
"exit 0 :: pnpm check:refd-timer-probe",
"exit 0 :: pnpm check:slot-lookup",
"exit 0 :: pnpm check:sourcemap-no-sources-content",
"exit 0 :: pnpm check:test-source-alias",
"exit 0 :: pnpm check:tier-file-adoption",
"exit 0 :: pnpm check:type-check-coverage",
"exit 0 :: pnpm check:type-check-debt",
"exit 0 :: pnpm check:watch-hint-literal",
"exit 0 :: pnpm check:where-matcher"
],
"ran_verdict": "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack: exit 0, 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN (derived zero, all 70 recorded an exit code)",
"dispatch_list_compare": "the dispatch-time list (62) is a strict subset of the derived 70; the 8 extra are the changeset-kind families (adr-0087-registration x2, empty-changeset x2, release-rehearsal-clone, release-pending-publish, objectui-changeset, pm-changeset-deadline-census)"
},
"line_budget": "n/a",
"deviations": [
"Worktree based on origin/main e085a8c (the dispatch base 07c842d had moved); origin/main later advanced 4 commits with zero overlap with this diff or the gate derivation, not merged; the queue rebuilds on current main.",
"H1 reproduce-first: the red reading on a company_name-titled object was taken by ablation (the fixed key list put back over the committed fix, same selection code as main) rather than before writing the fix.",
"resolveLookupTitles was touched: its id/credential/internal skips moved into the shared titleFieldOf so the record label and referenced titles share one answer; behaviour-identical, its existing pins (audit-lookup-summary, milestone, implicit-reference-target) pass.",
"An existing plugin-audit test file was edited (audit-writers.test.ts, fixture triage: declare the undeclared fixture object); within the declared plugin-audit test surface; no existing packages/qa file was edited.",
"The credential-title unit case uses a password field: a secret field needs a crypto provider and the sys_secret table, which this harness does not carry; both are masked by the same collectMaskedReadFields predicate.",
"The dogfood door pin also carries the masking case (a permission set withholding company_name; control served it), beyond the three named cases.",
"Two gates read red first on build state, not on this diff (check:dts-closure raced this worktree's own closure build; check:dual-build-cjs-loads PREREQUISITE NOT MET); both re-run 0 and the --ran record carries the re-run codes.",
"Labels documentation, size/l, tests, tooling on #21896 were applied by the platform labelers, not by this run."
],
"files_changed": [
".changeset/21878-activity-label-display-field.md",
"packages/plugins/plugin-audit/src/audit-writers.ts",
"packages/plugins/plugin-audit/src/audit-writers.test.ts",
"packages/plugins/plugin-audit/src/audit-record-label-display-field.test.ts",
"packages/qa/dogfood/test/activity-label-display-field.dogfood.test.ts"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
Review: PR #21896 at
e0da4f8a· seatdomain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T16:27ZVerdict on the dev report
5998605968(status: done), checked against GitHub and the tree. Accepted as built. No contract review is owed:Clause-②: no, and nopackages/specpath is touched.Checked:
titleFieldOf(objectName)inaudit-writers.tsrunsresolveDisplayFieldover the cached definition (no second lookup). It skips a resolvedid, a credential field and aninternalfield, the three exclusions referenced-record titles already applied.resolveLookupTitlesnow calls the same function, so the record label and referenced titles give one answer.recordLabel(record, titleField, id)reads only the resolved field from the same masked ledger view. A null, structured or blank value falls back to the id and never borrows another field. It returns the resolved field as the label's source, so security(plugin-audit): an activity row composed at write time may carry a changed field's values to a reader who can read the parent record but not that field (unmeasured; measure first) #21081's read-side redaction applies unchanged, now tocompany_nametoo. A unit case and the door pin both prove that a reader not servedcompany_namegets neither label nor summary, while the control reader keeps both.crm_contactmoves fromfull_name, a formula with no textreturnType, tofirst_name, its ADR-0079 title. Four move from the id to a field. Of 50 platform objects, 24 move from the id to theirnameField, and 3 change field. All follow the one resolver, which is the card's point. The changeset names the CRM contact case.dist. The restore was proven.plugin-audittest fixture declared an object the fake engine never had. The fixture now declares it, inside the claim's test surface.Carried, not filed (in the PR's Acceptance notes): whether the CRM example should title contacts by
full_name(the example's call); theaudit-writersdefinition caches are never invalidated (read from source, not measured, and it predates this change).Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
Landed · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T18:11Zb2388563, which is an ancestor oforigin/main(verified withgit merge-base --is-ancestorafter a fetch). Merged at 2026-10-05T18:09Z. Its first merge group was cut by a job timeout after a slow checkout, before any gate ran. It was re-queued once (5999675966), and the second group passed.Fixes #21878closed this cardcompleted. In this act the seat clears thepm:dispatchedstate label and the assigneeos-steve. Thedomain:services,area:records,priority:p2andbuglabels stay.5998647720and the PR's Acceptance notes: whether the CRM example should title contacts byfull_name, and the uninvalidated definition caches inaudit-writers.ts(not measured; predates this change).Generated by Claude Code
fix(plugin-audit): the activity record label is the record's ADR-0079…