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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
[finding] 8 of FeedItemType's 13 kinds still have no producer on any objectui surface #5877
Split out of #5840 by PM ruling: that card's fix extends the sys_activity type map; narrowing FeedItemType is a cross-repo spec retirement (the enum is published by @objectstack/spec, and retiring a member carries ADR-0087 registry obligations), so it is emphatically not that card's surface. Recorded here instead. Observation, not a queued fix.
Re-measured at live source, not from a shipped bundle
FeedItemType (@objectstack/spec/data) publishes 13 kinds. Read at the installed spec:
sys_comment rows, mapped in packages/app-shell/src/views/RecordDetailView.tsx (the host discussion-context path, not the sys_activity map)
Eight kinds have no producer anywhere: email, call, note, file, record_create, record_delete, approval, sharing.
Note the correction to the parent card, which measured the shipped bundle's map alone and said ten: comment does have a producer (it arrives on the host feed rather than through the activity map), and event gains one with that card's fix.
Why it is worth recording
They are authorable. types: ['approval'] parses, typechecks and builds; it renders a permanently empty tab with no diagnostic. To an author — or to an AI writing metadata — that reads as a working feature that happens to have no data yet. It is a declared surface enforced by nothing.
The renderer's own comment records that some of these are deliberate rather than missing: record_create / record_delete / sharing are NOT used because RecordDetailView and the block must agree about what a created row is, so adopting the richer kinds is a change to a shared map rather than to one block. So the disposition per kind is a real question, not a batch decision.
Three directions, none taken here:
Give the kinds producers (largest; several would need new system tables or new reads).
Narrow the published enum to what a producer exists for — cross-repo, ADR-0087 registry work, and the enum may have non-objectui consumers.
Make the emptiness diagnosable rather than silent — e.g. types entries with no producer warn at author time.
Direction 3 is the cheapest and is the one that matches how the platform treats other declared-but-inert surfaces. Recommending nothing; triage decides.
Found while implementing #5840; deliberately not fixed there.
Triage (first-touch grading): promoted finding → pm:queue, type Task, domain:ui, scoped to direction 3 only — make the emptiness diagnosable.
Rationale: types: ['approval'] parsing/typechecking/building and rendering a permanently empty tab with no diagnostic is the silent-inert class; the diagnosability route restores "declared surfaces say when they are inert" without touching the published enum or building producers. It matches how the platform treats other declared-but-inert surfaces, and it does not foreclose the other two routes.
Fence, binding on dispatch:
⛔ Directions 1 (build producers) and 2 (narrow the published enum) are not in this card. Both change capability surface; 2 is spec-side (@objectstack/spec enum + ADR-0087 registries, and the enum may have non-objectui consumers) and would need its own card through the decision inbox. If implementing the warn reveals the per-kind disposition genuinely has to be settled first, stop and report.
The renderer's own comment records record_create/record_delete/sharing as deliberately unadopted (shared-map agreement) — the diagnostic must not misreport deliberate non-adoption as a defect; distinguish "no producer exists" from "producer deliberately not wired" if the comment's distinction is machine-reachable, otherwise state the limitation.
Census correction — system is no longer a "no producer" candidate, and this card is about to become a single-site change
domain:ui execution seat, session session_01CRJge11jso9TpXRWFt1Z49. ⛔ Not claimed, no label change. Relaying a measurement made on the sibling card so it is not lost in a report.
This card was held out of a batch because it reads the same construction site as #5896, which is now implemented in PR #6731 (draft, CI running). Two of its findings land directly on this card's census.
1. ⚠️system has a producer now — from every author-extended type
PR #6112 gave activityRowToFeedItem a defined fallback, UNMAPPED_ACTIVITY_FEED_TYPE, which is 'system', for any sys_activity.type the mapping table has never heard of. #6731 makes the console surface call that same constructor instead of its own copy.
⇒ system is now reachable from any author-extended activity type on both surfaces. If this card's "8 of 13 kinds have no producer" census counts system among the eight, that entry has expired — and it expired for a reason worth keeping: the fallback exists precisely so an unclassified value reports a missing decision instead of vanishing.
⚠️ Whoever prices this card should re-run the census on the merged ref rather than adjusting the number by hand. I have not re-counted the other seven.
2. The construction site is converging to one, which changes the shape of the work
Before #6731 there were two hand-written readings of sys_activity.type → FeedItem (the record:activity block and RecordDetailView's inline copy), which had already drifted into different behaviours. After it, both reach FeedItemType through one constructor.
⇒ Whatever this card decides about unproduced kinds becomes a single-site change rather than two that must be kept in step. That makes it cheaper and safer than when it was filed.
3. ⚠️ But a third reading exists, and it is not part of that convergence
#6730 was filed from the same work: packages/app-shell/src/hooks/sharedUserFeeds.ts's mapActivityRows is a third hand-written reading of sys_activity.type, targeting ActivityItem — a different, four-value vocabulary — and it buckets every unrecognised type, author-extended values included, as update.
It drops nothing, so it is drift risk rather than a live defect. But it means "one constructor" is true for FeedItemType and not for the activity vocabulary as a whole, and #6730 records that converging it needs the ActivityItem vs FeedItem question answered first.
⇒ A census of FeedItemType producers should say explicitly whether it is counting that surface, since it produces a different type from the same rows.
Status
This card stays pm:queue and unassigned. It becomes a candidate again once #6731 lands, at which point the file face and the census both want re-verifying against the merged ref — the merge that closes an upstream is the one most likely to have moved the ground under its dependants.
File-face re-verification on the merged ref — done, and it changes where half of this card belongs
domain:ui execution seat, session session_01CRJge11jso9TpXRWFt1Z49. ⛔ Not claimed, no label change. Closing out the re-verification this card was held for.
This card was held out of a batch because it reads the same construction site as #5896. That landed in PR #6731 at 2026-08-29T05:04Z. Re-measured on merged main:
✅ The convergence is real.RecordDetailView.tsx:13 now imports activityRowToFeedItem from @object-ui/plugin-detail; the inline copy is gone. Both surfaces reach FeedItemType through one constructor, so a decision here is a single-site change.
✅ UNMAPPED_ACTIVITY_FEED_TYPE is 'system' (recordActivityFeed.ts:162), reachable from any author-extended type. As the earlier comment recorded: system is not a "no producer" candidate.
It is imported from @objectstack/spec and re-exported. views.ts:338 even records that it and FeedFilterMode were deliberately kept as live activity vocabulary.
⇒ This card has two halves with different owners:
The census — measuring which kinds have a producer on an objectui surface — is domain:ui work and is what this card actually measured.
Retiring the unproduced kinds — the action a census like this usually implies — would change a spec-owned type in another repository. Under this lane's routing rule (anything touching packages/spec goes to the domain:spec seat as sole owner, regardless of who needs it), that half is not this seat's to dispatch, and it is not an objectui change at all.
⇒ Recommended shape before anyone claims this: keep it here as a census/finding and, if the census supports a retirement, open the retirement as a domain:spec card in objectstack with this measurement attached and a Blocked-by: from whatever objectui work depends on it. ⛔ Do not dispatch it in this lane as "retire the dead kinds" — that reads as a one-repo change and is not one.
⚠️ And the census itself needs re-running on the merged ref rather than adjusted by hand: system has moved out of the no-producer set, and I have not re-counted the other seven.
The card stays pm:queue and unassigned; the hold that was on it is released.
{
"issue": 5877,
"status": "done",
"branch": "claude/issue-5877-unproduced-feed-kinds-diagnostic",
"pr": "https://github.com/objectstack-ai/objectui/pull/6934",
"premise_still_valid": true,
"summary": "Direction 3 only: `record:activity` now emits one deduped console.warn per distinct `types` entry that names a declared FeedItemType no ObjectUI producer emits. The entry is still honoured exactly as authored — nothing that renders changes, this is a diagnostic and not a refusal — and it lands on its own channel beside the two unrecognised-`types` / unrecognised-`filterMode` diagnostics already in recordActivityFeed.ts. No enum, no producer, and no spec-owned type was touched. CENSUS re-derived on merge-base e3d117ae1 over the WHOLE repository (pathspec: every non-test .ts/.tsx at every top level — packages/, apps/, examples/, scripts/, e2e/ — narrowed to the files that mention FeedItem, a necessary condition for constructing one; 1622 files scanned, 13 mention it, exactly one produces a kind off the activity map): 5 produced (comment, field_change, task, event, system), 8 unproduced (email, call, note, file, record_create, record_delete, approval, sharing). DELTA AGAINST THE BODY'S EIGHT: ZERO — the body already counted `system` as produced via sys_activity.type='system', so PR #6112 never moved it out of a set it was not in; what #6112 changed is the reason (system is now reachable from ANY author-extended type), and a test pins that the warn does not fire on it. THE THIRD READING IS EXPLICITLY NOT COUNTED: app-shell's sharedUserFeeds.ts / layout/activityItemType.ts produce ActivityItemType — create/update/delete/comment/system, FIVE values since #6730 landed, not the four the dispatch order recorded — which is not a projection of FeedItemType in either direction, and a row it maps never becomes a FeedItem; the exclusion is asserted in the census test rather than assumed so it cannot go vacuous. ActivityEntry in @object-ui/types is a third vocabulary again and has no producer at all. THE DELIBERATE/UNPRODUCED DISTINCTION IS NOT MACHINE-DERIVABLE and the code says so: the decision lives only as prose in ACTIVITY_TYPE_TO_FEED_TYPE's doc comment, so DELIBERATELY_UNADOPTED_FEED_TYPES is a documented TRANSCRIPTION of it (record_create / record_delete / sharing), not an inference — no heuristic was invented. The message therefore reports those three as 'a decision rather than a gap' with the shared-map reason, and every other unproduced kind as having no producer, stopping there: 'whether that is a gap or a decision nobody wrote down is not recorded anywhere this diagnostic can read'. It also names the one thing no census taken here can bound — a HOST that supplies the feed itself (`items` on the node, or its own DiscussionContext). PRODUCED_FEED_TYPES is DERIVED from the producers (the map's range plus its fallback), so giving a kind a producer retires its warning in the same edit; the single non-derivable producer (comment, built in app-shell, which depends on this package rather than the reverse) is declared and re-checked by a new repo-wide census test that asserts its own scan WIDTH as well as its result. Docs updated (content/docs/plugins/plugin-detail.mdx gains a 'Feed kinds with no producer' section) and a changeset added.",
"tests": "All quoted at final commit 0454ea706 (tree clean), exit codes captured BEFORE any pipe; heavy runs serialised through scripts/pm/os-verify-lock.sh. VITEST, ROOT FORM over the affected package (`pnpm exec vitest run packages/plugin-detail/` — the repo's guard refuses the --filter forms): AFTER = exit 0, 'Test Files 116 passed (116) | Tests 1070 passed (1070)'; BEFORE, measured in a throwaway comparison worktree at merge-base e3d117ae1 = exit 0, 'Test Files 115 passed (115) | Tests 1053 passed (1053)'. Sanity check against the target's OWN count (the silently-runs-apps/console hazard): the on-disk count of test files under packages/plugin-detail is 115 at base and 116 after, matching the reported file counts exactly; delta +1 file / +17 tests = exactly this PR's additions. BOTH DIRECTIONS WITH CONTROLS: an unproduced kind warns (asserted for `approval`, AND one at a time for every member of the DERIVED unproduced set); a produced kind does NOT warn (asserted for each of the five individually and as a list) — the leg that fails a warn-on-everything implementation; the two `types` channels stay apart (a mixed list yields 2 warnings); the entry is returned as authored. ABLATION, DIRECTION PREDICTED IN WRITING BEFORE THE RUN (prediction file written first): mutate the FACT, not an assertion — one entry added to ACTIVITY_TYPE_TO_FEED_TYPE (approved maps to 'approval'), giving the unproduced kind `approval` a producer. Anchor uniqueness asserted before writing (grep -Fc of the scheduled entry = 1), landing site printed (recordActivityFeed.ts:133), injected and anchor text grepped SEPARATELY (injected count 1, anchor still 1), mutation proven on disk by git hash-object moving 5ab2ef32b to ea2111ca5 plus the printed diff. NO REBUILD REQUIRED, and that is a property of this pair rather than an assumption: the tests import ../recordActivityFeed by RELATIVE path and the root vitest config aliases every @object-ui/* specifier to packages/*/src, so no dist/ is on either resolution path — and the mutation flipping the run red is itself the evidence it reached the code under test. RESULT: predicted 6 red, observed 8 ('Tests 8 failed | 57 passed (65)'); the two extras are the same class — one test of mine missed in the enumeration ('dedupes per distinct kind') and one PRE-EXISTING map pin ('leaves every mapped type pointing where it did'), whose red is the strongest evidence the mutation hit the fact. THE WARN STOPPED FOR THAT KIND ALONE: the control looping the derived unproduced set stayed GREEN with its remaining seven members still warning, the control over the derived produced set stayed GREEN with `approval` now silent inside it, and the mutated run's own message reads '... selects nothing: \"sharing\" ... Feed item types ObjectUI produces today: approval, comment, event, field_change, system, task'. RESTORE PROVEN BOTH WAYS under trap ... EXIT INT TERM with absolute paths from git rev-parse --show-toplevel, restored via `git checkout HEAD -- ABSOLUTE_PATH` (never the bare form): `git diff HEAD` for the file empty (0 lines), `git hash-object` back to 5ab2ef32b == the HEAD blob hash, `git status --porcelain` empty, residual injected-text count 0. GATES, each read from its own printed verdict: `pnpm --filter @object-ui/plugin-detail run type-check` (hyphenated; it echoed 'tsc --noEmit && tsc -p tsconfig.test.json', so the new test files ARE type-checked rather than excluded) exit 0 — the first run was a real red (5 TS errors on a Dirent annotation in the new census test, fixed, not an unbuilt-closure false red: the closure had been built first with pnpm --filter '@object-ui/plugin-detail^...' build). `pnpm exec eslint .` in packages/plugin-detail, plain form, exit 0 with '880 problems (0 errors, 880 warnings)' and zero warnings in any file this PR touches. `node scripts/check-changeset-presence.mjs` exit 0: '3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'. check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences and scripts/check-doc-links.mjs all exit 0. DECLARED NARROWING (a measurement, not a silence): check:readme-exports and check:doc-snippets are NOT MEASURED — both refuse on an unbuilt workspace and say so themselves (doc-snippets exits 2 with 'PRECONDITION NOT MET — The snippet program was NOT run'), this worktree built only the target's dependency closure, and neither can be affected by a diff that touches no README, no package barrel and adds no fenced code block. The repo-wide `pnpm lint` farm is CI's run.",
"mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (curl), which was probed green first (200). The /search/issues API returned 403, so the duplicate check for the two findings below used the REST issues LIST endpoint (252 open non-PR issues, 3 pages) plus a local title grep rather than one MCP search; no MCP GitHub call was made at all.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6932: two record:activity statements in content/docs/plugins/plugin-detail.mdx are stale — unmapped rows are no longer dropped (PR #6112 gave them the system fallback), and the callout about a second hand-written copy of the activity table describes a hazard #5896 / PR #6731 removed. Unassigned, no label (a concrete defect, for triage).",
"filed as #6933: packages/app-shell/src/views/ObjectView.tsx:44 imports the FeedItem type and never uses it — a false positive this very census had to reject by hand. Unassigned, `finding` label (observation class)."
]
}
Activity
os-zhuang commentedon Aug 24, 2026
Triage (first-touch grading): promoted
finding→pm:queue, type Task,domain:ui, scoped to direction 3 only — make the emptiness diagnosable.Rationale:
types: ['approval']parsing/typechecking/building and rendering a permanently empty tab with no diagnostic is the silent-inert class; the diagnosability route restores "declared surfaces say when they are inert" without touching the published enum or building producers. It matches how the platform treats other declared-but-inert surfaces, and it does not foreclose the other two routes.Fence, binding on dispatch:
@objectstack/specenum + ADR-0087 registries, and the enum may have non-objectui consumers) and would need its own card through the decision inbox. If implementing the warn reveals the per-kind disposition genuinely has to be settled first, stop and report.record_create/record_delete/sharingas deliberately unadopted (shared-map agreement) — the diagnostic must not misreport deliberate non-adoption as a defect; distinguish "no producer exists" from "producer deliberately not wired" if the comment's distinction is machine-reachable, otherwise state the limitation.recordActivityFeed's feed-kind map (open-vocabulary pin, ruled direction 4 on objectstack#11507). The producer census this card's warn needs reads the same map — answer fold-or-serial at claim against recordActivityFeed: pin the feed-kind map's OPEN-vocabulary relationship tosys_activity.type(ruled direction 4 on objectstack#11507) #5969, and prefer landing after it so the census reads the pinned relationship.Generated by Claude Code
os-sales commentedon Aug 29, 2026
Census correction —
systemis no longer a "no producer" candidate, and this card is about to become a single-site changedomain:uiexecution seat, sessionsession_01CRJge11jso9TpXRWFt1Z49. ⛔ Not claimed, no label change. Relaying a measurement made on the sibling card so it is not lost in a report.This card was held out of a batch because it reads the same construction site as #5896, which is now implemented in PR #6731 (draft, CI running). Two of its findings land directly on this card's census.
1.⚠️
systemhas a producer now — from every author-extended typePR #6112 gave
activityRowToFeedItema defined fallback,UNMAPPED_ACTIVITY_FEED_TYPE, which is'system', for anysys_activity.typethe mapping table has never heard of. #6731 makes the console surface call that same constructor instead of its own copy.⇒
systemis now reachable from any author-extended activity type on both surfaces. If this card's "8 of 13 kinds have no producer" census countssystemamong the eight, that entry has expired — and it expired for a reason worth keeping: the fallback exists precisely so an unclassified value reports a missing decision instead of vanishing.2. The construction site is converging to one, which changes the shape of the work
Before #6731 there were two hand-written readings of
sys_activity.type→FeedItem(therecord:activityblock andRecordDetailView's inline copy), which had already drifted into different behaviours. After it, both reachFeedItemTypethrough one constructor.⇒ Whatever this card decides about unproduced kinds becomes a single-site change rather than two that must be kept in step. That makes it cheaper and safer than when it was filed.
3.⚠️ But a third reading exists, and it is not part of that convergence
#6730 was filed from the same work:
packages/app-shell/src/hooks/sharedUserFeeds.ts'smapActivityRowsis a third hand-written reading ofsys_activity.type, targetingActivityItem— a different, four-value vocabulary — and it buckets every unrecognised type, author-extended values included, asupdate.It drops nothing, so it is drift risk rather than a live defect. But it means "one constructor" is true for
FeedItemTypeand not for the activity vocabulary as a whole, and #6730 records that converging it needs theActivityItemvsFeedItemquestion answered first.⇒ A census of
FeedItemTypeproducers should say explicitly whether it is counting that surface, since it produces a different type from the same rows.Status
This card stays
pm:queueand unassigned. It becomes a candidate again once #6731 lands, at which point the file face and the census both want re-verifying against the merged ref — the merge that closes an upstream is the one most likely to have moved the ground under its dependants.Generated by Claude Code
os-sales commentedon Aug 29, 2026
File-face re-verification on the merged ref — done, and it changes where half of this card belongs
domain:uiexecution seat, sessionsession_01CRJge11jso9TpXRWFt1Z49. ⛔ Not claimed, no label change. Closing out the re-verification this card was held for.This card was held out of a batch because it reads the same construction site as #5896. That landed in PR #6731 at 2026-08-29T05:04Z. Re-measured on merged
main:RecordDetailView.tsx:13now importsactivityRowToFeedItemfrom@object-ui/plugin-detail; the inline copy is gone. Both surfaces reachFeedItemTypethrough one constructor, so a decision here is a single-site change.UNMAPPED_ACTIVITY_FEED_TYPEis'system'(recordActivityFeed.ts:162), reachable from any author-extended type. As the earlier comment recorded:systemis not a "no producer" candidate.packages/app-shell/src/hooks/sharedUserFeeds.tsstill carriesmapActivityRows, targetingActivityItem— a different four-value vocabulary. Tracked as [finding] sharedUserFeeds keeps a THIRD hand-written sys_activity.type reading, and buckets every unrecognised type asupdate#6730. So "one constructor" is true forFeedItemTypeand not for the activity vocabulary as a whole.FeedItemTypeis not this repository's type.packages/types/src/views.ts:355:It is imported from
@objectstack/specand re-exported.views.ts:338even records that it andFeedFilterModewere deliberately kept as live activity vocabulary.⇒ This card has two halves with different owners:
domain:uiwork and is what this card actually measured.packages/specgoes to thedomain:specseat as sole owner, regardless of who needs it), that half is not this seat's to dispatch, and it is not an objectui change at all.⇒ Recommended shape before anyone claims this: keep it here as a census/finding and, if the census supports a retirement, open the retirement as a
domain:speccard inobjectstackwith this measurement attached and aBlocked-by:from whatever objectui work depends on it. ⛔ Do not dispatch it in this lane as "retire the dead kinds" — that reads as a one-repo change and is not one.systemhas moved out of the no-producer set, and I have not re-counted the other seven.The card stays
pm:queueand unassigned; the hold that was on it is released.Generated by Claude Code
claude commentedon Aug 31, 2026
os-dev-report
{ "issue": 5877, "status": "done", "branch": "claude/issue-5877-unproduced-feed-kinds-diagnostic", "pr": "https://github.com/objectstack-ai/objectui/pull/6934", "premise_still_valid": true, "summary": "Direction 3 only: `record:activity` now emits one deduped console.warn per distinct `types` entry that names a declared FeedItemType no ObjectUI producer emits. The entry is still honoured exactly as authored — nothing that renders changes, this is a diagnostic and not a refusal — and it lands on its own channel beside the two unrecognised-`types` / unrecognised-`filterMode` diagnostics already in recordActivityFeed.ts. No enum, no producer, and no spec-owned type was touched. CENSUS re-derived on merge-base e3d117ae1 over the WHOLE repository (pathspec: every non-test .ts/.tsx at every top level — packages/, apps/, examples/, scripts/, e2e/ — narrowed to the files that mention FeedItem, a necessary condition for constructing one; 1622 files scanned, 13 mention it, exactly one produces a kind off the activity map): 5 produced (comment, field_change, task, event, system), 8 unproduced (email, call, note, file, record_create, record_delete, approval, sharing). DELTA AGAINST THE BODY'S EIGHT: ZERO — the body already counted `system` as produced via sys_activity.type='system', so PR #6112 never moved it out of a set it was not in; what #6112 changed is the reason (system is now reachable from ANY author-extended type), and a test pins that the warn does not fire on it. THE THIRD READING IS EXPLICITLY NOT COUNTED: app-shell's sharedUserFeeds.ts / layout/activityItemType.ts produce ActivityItemType — create/update/delete/comment/system, FIVE values since #6730 landed, not the four the dispatch order recorded — which is not a projection of FeedItemType in either direction, and a row it maps never becomes a FeedItem; the exclusion is asserted in the census test rather than assumed so it cannot go vacuous. ActivityEntry in @object-ui/types is a third vocabulary again and has no producer at all. THE DELIBERATE/UNPRODUCED DISTINCTION IS NOT MACHINE-DERIVABLE and the code says so: the decision lives only as prose in ACTIVITY_TYPE_TO_FEED_TYPE's doc comment, so DELIBERATELY_UNADOPTED_FEED_TYPES is a documented TRANSCRIPTION of it (record_create / record_delete / sharing), not an inference — no heuristic was invented. The message therefore reports those three as 'a decision rather than a gap' with the shared-map reason, and every other unproduced kind as having no producer, stopping there: 'whether that is a gap or a decision nobody wrote down is not recorded anywhere this diagnostic can read'. It also names the one thing no census taken here can bound — a HOST that supplies the feed itself (`items` on the node, or its own DiscussionContext). PRODUCED_FEED_TYPES is DERIVED from the producers (the map's range plus its fallback), so giving a kind a producer retires its warning in the same edit; the single non-derivable producer (comment, built in app-shell, which depends on this package rather than the reverse) is declared and re-checked by a new repo-wide census test that asserts its own scan WIDTH as well as its result. Docs updated (content/docs/plugins/plugin-detail.mdx gains a 'Feed kinds with no producer' section) and a changeset added.", "tests": "All quoted at final commit 0454ea706 (tree clean), exit codes captured BEFORE any pipe; heavy runs serialised through scripts/pm/os-verify-lock.sh. VITEST, ROOT FORM over the affected package (`pnpm exec vitest run packages/plugin-detail/` — the repo's guard refuses the --filter forms): AFTER = exit 0, 'Test Files 116 passed (116) | Tests 1070 passed (1070)'; BEFORE, measured in a throwaway comparison worktree at merge-base e3d117ae1 = exit 0, 'Test Files 115 passed (115) | Tests 1053 passed (1053)'. Sanity check against the target's OWN count (the silently-runs-apps/console hazard): the on-disk count of test files under packages/plugin-detail is 115 at base and 116 after, matching the reported file counts exactly; delta +1 file / +17 tests = exactly this PR's additions. BOTH DIRECTIONS WITH CONTROLS: an unproduced kind warns (asserted for `approval`, AND one at a time for every member of the DERIVED unproduced set); a produced kind does NOT warn (asserted for each of the five individually and as a list) — the leg that fails a warn-on-everything implementation; the two `types` channels stay apart (a mixed list yields 2 warnings); the entry is returned as authored. ABLATION, DIRECTION PREDICTED IN WRITING BEFORE THE RUN (prediction file written first): mutate the FACT, not an assertion — one entry added to ACTIVITY_TYPE_TO_FEED_TYPE (approved maps to 'approval'), giving the unproduced kind `approval` a producer. Anchor uniqueness asserted before writing (grep -Fc of the scheduled entry = 1), landing site printed (recordActivityFeed.ts:133), injected and anchor text grepped SEPARATELY (injected count 1, anchor still 1), mutation proven on disk by git hash-object moving 5ab2ef32b to ea2111ca5 plus the printed diff. NO REBUILD REQUIRED, and that is a property of this pair rather than an assumption: the tests import ../recordActivityFeed by RELATIVE path and the root vitest config aliases every @object-ui/* specifier to packages/*/src, so no dist/ is on either resolution path — and the mutation flipping the run red is itself the evidence it reached the code under test. RESULT: predicted 6 red, observed 8 ('Tests 8 failed | 57 passed (65)'); the two extras are the same class — one test of mine missed in the enumeration ('dedupes per distinct kind') and one PRE-EXISTING map pin ('leaves every mapped type pointing where it did'), whose red is the strongest evidence the mutation hit the fact. THE WARN STOPPED FOR THAT KIND ALONE: the control looping the derived unproduced set stayed GREEN with its remaining seven members still warning, the control over the derived produced set stayed GREEN with `approval` now silent inside it, and the mutated run's own message reads '... selects nothing: \"sharing\" ... Feed item types ObjectUI produces today: approval, comment, event, field_change, system, task'. RESTORE PROVEN BOTH WAYS under trap ... EXIT INT TERM with absolute paths from git rev-parse --show-toplevel, restored via `git checkout HEAD -- ABSOLUTE_PATH` (never the bare form): `git diff HEAD` for the file empty (0 lines), `git hash-object` back to 5ab2ef32b == the HEAD blob hash, `git status --porcelain` empty, residual injected-text count 0. GATES, each read from its own printed verdict: `pnpm --filter @object-ui/plugin-detail run type-check` (hyphenated; it echoed 'tsc --noEmit && tsc -p tsconfig.test.json', so the new test files ARE type-checked rather than excluded) exit 0 — the first run was a real red (5 TS errors on a Dirent annotation in the new census test, fixed, not an unbuilt-closure false red: the closure had been built first with pnpm --filter '@object-ui/plugin-detail^...' build). `pnpm exec eslint .` in packages/plugin-detail, plain form, exit 0 with '880 problems (0 errors, 880 warnings)' and zero warnings in any file this PR touches. `node scripts/check-changeset-presence.mjs` exit 0: '3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'. check:control-bytes, check:spec-symbols, check:doc-types, check:doc-fences and scripts/check-doc-links.mjs all exit 0. DECLARED NARROWING (a measurement, not a silence): check:readme-exports and check:doc-snippets are NOT MEASURED — both refuse on an unbuilt workspace and say so themselves (doc-snippets exits 2 with 'PRECONDITION NOT MET — The snippet program was NOT run'), this worktree built only the target's dependency closure, and neither can be affected by a diff that touches no README, no package barrel and adds no fenced code block. The repo-wide `pnpm lint` farm is CI's run.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (curl), which was probed green first (200). The /search/issues API returned 403, so the duplicate check for the two findings below used the REST issues LIST endpoint (252 open non-PR issues, 3 pages) plus a local title grep rather than one MCP search; no MCP GitHub call was made at all.", "open_questions": [], "out_of_scope_findings": [ "filed as #6932: two record:activity statements in content/docs/plugins/plugin-detail.mdx are stale — unmapped rows are no longer dropped (PR #6112 gave them the system fallback), and the callout about a second hand-written copy of the activity table describes a hazard #5896 / PR #6731 removed. Unassigned, no label (a concrete defect, for triage).", "filed as #6933: packages/app-shell/src/views/ObjectView.tsx:44 imports the FeedItem type and never uses it — a false positive this very census had to reject by hand. Unassigned, `finding` label (observation class)." ] }Generated by Claude Code