Repository navigation
record activity feed: an object's activityMilestones rows of type completed ("Case resolved", "Case closed") are hidden as if they were completed tasks #12106
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchand removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-11T04:18Z
Session:session_01AswpQDLCKiZos2jCXknwKz
Account:os-zhuang
Branch:claude/issue-12106-activity-milestone-completed
Worktree:objectui-issue-12106
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-detail/src/renderers/recordActivityFeed.ts(ACTIVITY_TYPE_TO_FEED_TYPE,COMPLETED_FEED_TYPES,applyFeedConfig); its tests;.changeset/12106-activity-milestone-completed.md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: no
Responsibility:objectui's plugin-detail recordActivityFeed.ts, which maps every sys_activity row of type completed to the task feed type, so applyFeedConfig hides an object's activityMilestones completed rows (for example "Case resolved") under showCompleted's default false | none: the page's own fetch returns those rows, and an author-extended type falls to system and shows | every record page whose object declares a completed-type activityMilestones row; measured on objectstack-ai/hotcrm#2058 (17.7.0)
Thread-read: none
Serial constraints cleared: none in flight on this file. Read at 2026-10-11T04:18Z: objectui#12117 (objectui#12103, in the merge queue) editsDetailSection.tsxandHeaderHighlight.tsxin the same package, not this file. objectui#12116, #12089 and this seat's in-flight branches for objectui#12104 and #12105 touch noplugin-detailrenderer.Why
Clause-②: no: the fix changes how the console maps rows it already receives. The card forbids a new spec key (stop and return to triage instead), so no published accept set, export or locale key moves. If the route turns out to need one, the dev stops and reports rather than adding it.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12106,
"status": "needs_decision",
"branch": "claude/issue-12106-activity-milestone-completed",
"pr": null,
"session": "session_01AswpQDLCKiZos2jCXknwKz",
"premise_still_valid": false,
"summary": "Stopped under the card's own stop rule: no code, no PR. The defect is confirmed on objectui main a21ff9a: ACTIVITY_TYPE_TO_FEED_TYPE maps completed to task, COMPLETED_FEED_TYPES is the set {task}, and applyFeedConfig drops those items whenever showCompleted !== true. The premise that fails is the card's 'If it can, this is a console change' branch. Measured from the WRITER on objectstack origin/main 7098acaef, the console cannot tell a milestone completed row from a task completion with what sys_activity serves today, for four reasons. (1) The platform's shipped task completion IS a milestone row. The only in-repo writer of completed is plugin-audit audit-writers.ts, milestone branch, 'if (milestone.type) activityType = milestone.type'. The only activityMilestones declaration under objectstack examples/ is app-showcase task.object.ts, { field: 'status', value: 'done', summary: '✅ Task completed: {title}', type: 'completed' }. The spec's own activityMilestones docstring names 'Task completed' as the example milestone. So 'Case resolved' and 'Task completed' come out of one writer statement with one column set. (2) That writer stamps no milestone marker and never sets source_object or source_id. The row is { type, timestamp, summary, actor_id, actor_name?, actor_avatar_url?, object_name, record_id, record_label, metadata, organization_id? }, and metadata is { old, new, text_sources }. The one provenance tell, metadata.text_sources (written only by the CRUD mirror), is deleted by redactActivityRows in activity-field-redaction.ts before the row is served ('The declaration itself is bookkeeping and is never served to a reader'). The redaction header also says app-inserted rows may carry the same metadata old/new shape. (3) Both console reads (record-activity.tsx self-fetch and RecordDetailView) filter { object_name, record_id }, so object_name is the same on every row of a feed. (4) The ActivityPointer pair only works in one direction: a pointer present means the row is not a milestone, but an absent pointer proves nothing. Completed activities modelled on HotCRM seeds carry no pointer (seed-loader-pointer-pair.test.ts, 'Discovery call' and 'Call'), and HotCRM's direct-insert rows could not be read from this container. Any discriminator that shows 'Case resolved' by default also shows the showcase task's own '✅ Task completed' by default. It also leaves showCompleted hiding no row any in-repo writer produces. Returned to triage with options below.",
"tests": "No code was written, so no tests were run. WRITER measurement, read with git show origin/main:PATH on objectstack 7098acaef: packages/plugins/plugin-audit/src/audit-writers.ts (the activityRow literal and the milestone branch; activityTypeFor returns only created/updated/deleted); packages/plugins/plugin-audit/src/objects/sys-activity.object.ts (columns: id, timestamp, type, summary, actor_id, actor_name, actor_avatar_url, object_name, record_id, record_label, source_object, source_id, url, environment_id, metadata; there is no kind or origin column; source_object is described as 'Null when the activity is about the target record itself'); packages/plugins/plugin-audit/src/activity-field-redaction.ts (redactActivityRows: 'delete metadata[ACTIVITY_TEXT_SOURCES_KEY]'); packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts (TYPES_WITH_WRITERS completed row: the milestone writer plus the showcase task declaration, and also HotCRM contact.actions.ts send_email inserting type completed); packages/metadata-protocol/src/seed-loader-pointer-pair.test.ts (pointer-less completed 'Discovery call' and 'Call' rows, and one pointer-bearing 'Email follow-up' row with source_object sys_email); docs/adr/0052-audit-is-not-the-activity-feed.md section 5 (ActivityPointer; 'Domain kind rides in metadata.kind'); examples/app-showcase/src/data/objects/task.object.ts. Census: git grep -c activityMilestones origin/main -- 'examples/' hit exactly one file, task.object.ts. SPEC facts re-read on 7098acaef: component.zod.ts has "showCompleted: z.boolean().default(false).describe('Include completed activities')"; object.zod.ts declares activityMilestones[].type as z.string().optional(); audit-writers.ts forwards it verbatim. RENDERER, read on objectui a21ff9a: recordActivityFeed.ts (the mapping and the filter) and both sys_activity reads (the filter object_name plus record_id). Runtime readback through GET /api/v1/data/sys_activity: NOT MEASURED. Reason: the only in-repo writer of completed is the milestone writer, so a local backend can produce only milestone rows (the showcase task completion is one of them), and the only known non-milestone writer is objectstack-ai/hotcrm, which this container cannot reach: REST contents GET answered 403 'GitHub access to this repository is not enabled for this session'; raw.githubusercontent.com answered 404 while the objectui control answered 200; mcp add_repo was refused by the session classifier.",
"mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/hotcrm, access read), refused by the session classifier ('Unauthorized Persistence'). Zero GitHub MCP tools, read or write.",
"api_writes": "1 — POST repos//issues/12106/comments (this os-dev-report comment), sent with scripts/pm/post-stamped.mjs through the fleet-write relay. git push of the empty branch is not a REST write. REST reads, which are not writes: GET issues/12106, GET issues/12106/comments, GET issues/5840 (objectui), GET issues/22722 (objectstack), and GET hotcrm contents, which answered 403.",
"open_questions": [
{
"question": "sys_activity serves no fact that separates a row an object's activityMilestones wrote from a completed task. Which route gives the console that fact, given that the card forbids the dev from inventing a marker?",
"options": [
"A — Console-only ActivityPointer reading, with no spec key. A completed row carrying source_object and source_id is a completed activity entity (a task, call, meeting or email, per ADR-0052 section 5): it stays task and is hidden by default. A completed row with no pointer is about the record itself (the column's declared meaning): it is shown. Cost: about 15 lines in recordActivityFeed.ts plus pins, in one objectui PR. Consequence: every pointer-less completion shows by default, including the showcase task's own '✅ Task completed' and pointer-less completed calls like the HotCRM-shaped seeds. showCompleted would then hide no row any in-repo writer produces (the milestone writer never sets a pointer), which is close to a declared-but-inert prop.",
"B — Writer-side provenance. plugin-audit's milestone branch stamps a served marker on the row. ADR-0052 section 5 already names metadata.kind as the place where kind rides. The redaction keeps the marker, and the console maps a marked completed row off the task bucket, either to system or to a presentation internal to the renderer. Cost: one objectstack PR (writer, redaction allow-list, census test, changeset), then one objectui PR. Unmarked rows age out under the 14-day retention. Needs question 2 answered.",
"C — Vocabulary route. completed keeps meaning a finished task or activity. A record-state milestone declares its own type through the open vocabulary ('escalated' already shows), or leaves type unset so the row is 'updated'. One sentence is added to the spec description of activityMilestones[].type, and HotCRM changes its crm_case milestones. Cost: the smallest, with zero platform code. The trap stays for any author or AI that picks the built-in completed, guarded only by prose. Author-extended types render through the generic system presentation and log a console warning.",
"D — The console re-derives provenance from the object's declared activityMilestones and the row's metadata.new. No new key. This replicates matchMilestone on the consumer side, breaks whenever redaction drops the watched field from metadata.new, and is the consumer-side inference that AGENTS.md rule 0.1 rejects."
],
"recommendation": "B. Business need: the hidden rows were measured on HotCRM (the maintainer's pass, hotcrm#2058), and the parent card's title calls it a platform defect ('drops type: completed milestones'), so C pushes a platform defect onto the app. Long-term soundness: only the writer knows a row came from activityMilestones, because it is the branch that applies the type. The fact belongs with the producer (contract-first), whereas A reinterprets a column and D copies the writer into the consumer. Preventing AI errors: B makes type completed on a milestone safe by construction; C leaves a silent-hide trap that only prose guards; A errs in the visible direction. Startup focus: B is the largest (two repos), but it adds one served value, not a spec key or a gate. If the maintainer wants zero expansion, A is the cheaper fallback, with its cost (showCompleted inert for in-repo writers) stated up front."
},
{
"question": "Does a task object's own completion milestone (app-showcase showcase_task '✅ Task completed: {title}', type completed) count as a finished task that showCompleted hides on that task's own record page? Routes A, B and D all show it by default.",
"options": [
"a — No. Any milestone row shows, which is the card's first Done-when clause read literally: 'A milestone row the object's activityMilestones wrote is not treated as a completed task.' showCompleted keeps hiding completed activity rows that are not milestones.",
"b — Yes. Then a milestone needs a way, per declaration, to say it is a task completion. That is a new spec key on activityMilestones, which the card forbids without a triage ruling."
],
"recommendation": "a, because that row is the task's own lifecycle event shown on the task's own page, which is what 'Case resolved' is to a case. The card's first Done-when clause already says so, and b would need the spec key the card rules out."
}
],
"out_of_scope_findings": [
"carrier: this card's triage ruling · noted, not filed. In the same family as this card: the objectstack census (sys-activity-type-vocabulary.test.ts, TYPES_WITH_WRITERS) records HotCRM contact.actions.ts send_email inserting type completed, so a sent email maps to task and is hidden by default under showCompleted. Whichever route triage picks decides that row too."
],
"gates": [
"none run, because the branch has no diff: tip a21ff9a equals BASE a21ff9a, 'git diff BASE --stat' is empty, and no files changed, so none of the gate families the dispatch named applies"
],
"line_budget": "n/a (skills/ not touched)",
"deviations": [
"Status needs_decision with premise_still_valid false, rather than the dispatch's 'premise_still_valid: false or blocked'. The card's stop rule returns the card to triage, and the options above are what triage needs.",
"The dispatch's section 2 assumption 2 calls showCompleted's documented meaning 'finished tasks'. The spec text on 7098acaef is 'Include completed activities'.",
"mcp add_repo for objectstack-ai/hotcrm was attempted to read the non-milestone writer and was refused by the classifier, so the HotCRM row shape comes only from objectstack's census text and seed tests. It was not read at the source.",
"The empty branch claude/issue-12106-activity-milestone-completed was pushed as the claim marker and write probe, and it stays on origin at base a21ff9a. Deleting a remote ref is unavailable from this container (AGENTS.md, objectui#6756). The local branch ref also remains; the worktree is removed and its removal was verified with git worktree list."
],
"files_changed": []
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsReturned to triage under the card's stop rule (dev report
6105440477) ·domain:uiseat 1,session_01AswpQDLCKiZos2jCXknwKz, 2026-10-11T04:30ZRelease:
session_01AswpQDLCKiZos2jCXknwKz(accountos-zhuang) releases claim6105369306.- Reason: the card's measure-first stop rule fired. The console cannot tell a milestone
completedrow from a task completion with whatsys_activityserves today. No code was written and no PR was opened (premise_still_valid: false). - Destination:
pm:dispatchedis removed andpm:queueandpm:retriageare added in this act, and the assignee is cleared. Grade (priority:p2) stands until triage re-reads it.
What was measured, from the writer (objectstack
origin/main; the seat re-read each load-bearing line ata18c51496)- The only in-repo writer of
completedis plugin-audit's milestone branch inaudit-writers.ts(if (milestone.type) activityType = milestone.type). The onlyactivityMilestonesdeclaration underexamples/is app-showcase's task (✅ Task completed: {title},type: 'completed'). So "Case resolved" and "Task completed" come from one statement with one column set. - The milestone row carries no provenance marker. It sets no
source_object/source_id. The one hint,metadata.text_sources(written by the CRUD mirror only), is deleted byredactActivityRows(activity-field-redaction.ts) before the row is served. - Both console reads filter on
object_name+record_id, soobject_nameis the same on every row of a feed. - The ActivityPointer pair tells only one direction: a pointer means the row is not a milestone, and no pointer proves nothing.
- Correction of the seat's dispatch: it called
showCompleted"finished tasks". The spec text is'Include completed activities'(packages/spec/src/ui/component.zod.ts). The dev was right.
pm:retriage: what this seat asks triage- Route. The dev's options, all in the report: (A) the console reads the pointer, with no spec key, which makes
showCompletedhide no row any in-repo writer produces; (B) the milestone writer stamps a served marker, the place ADR-0052 §5 names (metadata.kind), then the console maps on it (one objectstack PR, then one objectui PR); (C) a vocabulary rule that milestones do not usecompleted(spec prose plus the HotCRM declarations); (D) the console re-derives provenance from the declaration (consumer-side inference, which objectui AGENTS.md #0.1 rules out). The dev recommends B. The options differ in whatsys_activityserves, so route it, or put it in the decision box if it is the maintainer's. - Scope: does a task object's own completion milestone, on that task's own page, count as a finished task that
showCompletedhides? The dev recommends no, which is the card's first "Done when" clause read literally. - Same family, for the same answer: objectstack's activity-type census records HotCRM's
send_emailinsertingtype: 'completed', which this mapping also hides by default.
The empty branch
claude/issue-12106-activity-milestone-completedstays on origin ata21ff9a, because remote ref deletion is not available from this container. It is not a live claim; this release is.
Generated by Claude Code
- Reason: the card's measure-first stop rule fired. The console cannot tell a milestone
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchand removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage:
pm:retriageanswer. Route B: the writer stamps the ADR-0052 §5 slot, and this card reads it.pm:blockedon objectstack#22771Triage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T04:54Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read:6105454344, and the dev report6105440477.Blocked-by: objectstack-ai/objectstack#22771
- Route: B, with no decision owed.
- ADR-0052 §5 already places a domain kind in
metadata.kind("typestays domain-NEUTRAL … Domain kind rides inmetadata.kind"). - The writer half is filed as plugin-audit: an
activityMilestonesrow stampsmetadata.kind: 'milestone'(ADR-0052 §5) and the served-row redaction keeps it, so a reader can tell a milestone from a task completion objectstack#22771 (p2,domain:services): plugin-audit stampsmetadata.kind: 'milestone', and the redaction keeps it. - This card then reads it.
- ⛔ Not A: it would empty
showCompletedof meaning. ⛔ Not C: it would push a vocabulary rule onto every app. ⛔ Not D, which objectui AGENTS.md #0.1 rules out.
- ADR-0052 §5 already places a domain kind in
- Scope: no. A task's own completion milestone, on that task's own page, is a milestone, and it shows. That is this card's first done-when read literally, as the dev recommends.
showCompletedkeeps its spec meaning ("Include completed activities") for rows that are not milestones.
- HotCRM's
send_emailinsertingtype: 'completed': under B, that row is not a milestone, so it stays a completed activity and is hidden by default. Whether HotCRM wants it shown is an app-side choice of type. It is noted on HotCRM app side of the 17.7.0 browser pass: bind export for the HotCRM administrator, and drop the redundant post-approvalupdate_recordthat re-stampsupdated_byto the submitter hotcrm#2061, the app-side card from the same browser pass.
- No hosted hazard: the reader is additive, and a runtime that does not stamp the kind keeps today's behaviour.
- Grade unchanged: p2.
- The empty branch
claude/issue-12106-activity-milestone-completedis noted. It is no claim.
- Route: B, with no decision owed.
- added and removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsUnlock:
pm:blocked→pm:queue(cross-repo, keyed on the install face)Triage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T07:56Z. ⛔ Not a claim, ⛔ not a dispatch.- The blocker is closed: plugin-audit: an
activityMilestonesrow stampsmetadata.kind: 'milestone'(ADR-0052 §5) and the served-row redaction keeps it, so a reader can tell a milestone from a task completion objectstack#22771 closedcompletedwhen PR fix(plugin-audit): a fired milestone row carries metadata.kind 'milestone' (ADR-0052 §5), served to a reader served its watched fields objectstack#22783 merged (8bd0fcd07f). plugin-audit now stampsmetadata.kind: 'milestone'on a fired milestone row, and the redaction keeps it. - The install face (the cloud window):
- cloud consumes objectui
main, but its framework stays at v17 until C7 (objectstack-ai/cloud#2709). So on cloud, everycompletedrow arrives with nometadata.kind. - This card may land on objectui
mainonly if a row without the marker behaves exactly as it does today, hidden undershowCompleted's default. - Pin that as a CONTROL beside the milestone pin.
- Cloud's users see the fix after C7. objectstack's console sees it after its next
.objectui-shabump.
- cloud consumes objectui
- Scope, unchanged from route B (
6105608877):- a row with
metadata.kind: 'milestone'shows regardless ofshowCompleted; showCompletedkeeps its spec meaning for every othercompletedrow;- a task's own completion milestone shows on its own page.
- a row with
- Pins:
- a milestone row → shown;
- CONTROL: a
completedrow with no marker → hidden by default, as today (the v17 face); - CONTROL:
showCompleted: true→ both shown.
- The blocker is closed: plugin-audit: an
Filing gate ①: a product defect measured at a public door (finding B-05). Filed by the triage seat (seat post objectstack-ai/objectstack#6015,
session_01AavokzJ5DndAwitDXvKy4U), splitting objectstack-ai/objectstack#22722 (the maintainer's HotCRM browser pass, objectstack-ai/hotcrm#2058, measured on@objectstack/*17.7.0). ⛔ Not a claim.Read on objectui
maince991bd70fpackages/plugin-detail/src/renderers/recordActivityFeed.ts:ACTIVITY_TYPE_TO_FEED_TYPEmapscompleted: 'task'(about:136), andCOMPLETED_FEED_TYPES = {'task'}(about:265);applyFeedConfigdrops those rows whenevershowCompleted !== true(about:870).record:discussion, soshowCompletedis unset and the rows are hidden.GET /api/v1/data/sys_activity?…returns them. An author-extended type such as "escalated" shows, because it falls to'system'.showCompleteddefaults tofalse(packages/spec/src/ui/component.zod.tsabout:1803).activityMilestones[].typeforwards the author's type verbatim (object.zod.tsabout:2385).Done when
activityMilestoneswrote is not treated as a completed task. "Case resolved" shows on the default page, andshowCompletedkeeps hiding finished tasks.sys_activitycarries today.completedrow is shown;showCompleted: trueshows both.domain:ui· p2.