Repository navigation
crm_event_attendee.invited_date is the one audit stamp left without a writer census — the reason it stays open was retired by #1667 #1747
Description
Filed by the repo:hotcrm seat (session session_019YKN9TPqDSSXUaYqp2j7MZ, R55) out of the review of PR #1737. ⛔ Not a defect — an audit debt with a named owner-shaped hole in it.
Dedup: run against the open+closed set with a working control — the search returned #1667 (the sibling, closed) and nothing else, so the zero for this column is a real zero rather than a broken query.
What happened
PR #1737 declared readonly: true on the three columns ruled by decision batch #74 (#1666: crm_opportunity.approval_status / approved_date; #1667: crm_campaign_member.added_date).
Two files outside that ruling carried comments citing crm_campaign_member.added_date as the precedent for staying open. #1667 overturned that precedent, so those comments became false the moment the PR landed. The dev corrected both — and on src/objects/event_attendee.object.ts it wrote this, which is why this card exists rather than a fourth flag flip:
⛔ NOT
readonly— but the cross-reference this note used to carry is retired, not merely reworded … the reason it cited ("the engine strips a readonly key the CALLER supplied") does not reach an INSERT at all — the strip is an UPDATE-path rule. What holds this column open today is that nobody has ruled on it. ⛔ Do not flip it on the strength of the sibling ruling: #1666 / #1667 were two separate product decisions about two specific columns, and this one has had neither. It needs its own writer census and its own ruling.
⭐ That refusal was correct and is the reason this is a clean card: the column's stated justification is now known to be wrong, and its actual justification is "unexamined".
What this card is
Do for invited_date exactly what the #1435 audit did for the other three: a per-writer census. That is the whole deliverable.
For each writer of crm_event_attendee.invited_date, record: what writes it, whether the write is an INSERT or an UPDATE, its effective runAs, and therefore whether it would survive a readonly: true declaration. The card body's own starting point (⛔ unverified, re-measure): the retired comment claimed it is "written by the activity actions on insert".
The two mechanisms the sibling cards turned on, both already pinned in test/readonly-write-semantics.test.ts and test/audit-stamp-readonly.test.ts:
- the readonly strip is an UPDATE-path rule — INSERTs are never reached;
- elevation is not anonymity (objectstack#5494) — a
runAs: 'system'run carriesisSystem: true, so the strip branch is skipped.
⛔ Scope — this card does NOT flip the flag
⛔ Declaring readonly is a behaviour change and a product decision, exactly as #1666 and #1667 were. This lane has now executed that principle three times and must not execute it a fourth way.
⇒ The output of this card is the census, filed back onto this card, and then:
- census says no writer would lose its write ⇒ the card converts to
needs-user-decisionwith the four-axis block, and the maintainer rules — the same route crm_opportunity.approval_status / approved_date can honestly be declaredreadonly— every writer is the runAs:'system' approval flow #1666 / crm_campaign_member.added_date can honestly be declaredreadonly— its only writers are INSERTs, which the readonly strip never touches #1667 took; - census finds a user-context UPDATE writer ⇒ the column legitimately stays open, and the card closes having replaced "nobody has ruled" with a measured reason in the comment.
⛔ Either way the flag is not touched in this card's PR.
Note for whoever takes it
src/objects/event_attendee.object.ts also has an open question one layer down that this census will meet: #1078 (making the attendee form structurally unable to build a wrong-shaped row) is pm:blocked on the same file's shape. ⛔ Do not widen into it.
Activity
os-bill commentedon Sep 8, 2026
Claim:sessionsession_019YKN9TPqDSSXUaYqp2j7MZ· seatrepo:hotcrm· R56 wave 2 · branchclaude/issue-1747-invited-date-writer-censusPM dispatch. The assignee and this claim are written by the seat; the dev inherits both, verifies this is the newest
Claim:and that it names its branch, and ⛔ posts no second claim and ⛔ never writes the assignee.File surface⚠️ #1078 is
src/objects/event_attendee.object.tsplus a read-only census. No fence collision —src/translations/**is held by #1717 andcontent/docs/**by #1729, both disjoint.pm:blockedon this same file's shape: ⛔ do not widen into it.Generated by Claude Code
huangyiirene commentedon Sep 8, 2026
Claim: PM loop round R57⚠️ ⛔ not a ⚠️ #1078 is
Session:
session_01PpRjGNnwyo2J1rrmekxB1WBranch:
claude/issue-1747-invited-date-censusWorktree:
hotcrm-issue-1747Domain:
repo:hotcrm(single-lane repo — nodomain:*)File surface:
src/objects/event_attendee.object.ts(comment only, if anything at all) + a read-only census across writers +.changeset/(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default judgement tier—--tierreading:dispatch-gates.mjsREFUSES for this repo (noscripts/pm/here), so the tier is this seat's explicit decisionClause-②: no
Thread-read:
5579891175Serial constraints cleared: this card holds
src/objects/event_attendee.object.ts.pm:blockedon the same file's shape ⇒ ⛔ do not widen into it. In-flight sibling PR #1768 (#1717) issrc/translations/**only ⇒ disjoint, verified from itsget_files. No other in-flight card touchessrc/objects/.Claim:at5579891175Provenance (who / verbatim / where): maintainer, live PM chat in this session, 2026-09-08T10:0xZ, choosing between three seating shapes — 「坐席并接管三张在飞卡」.
The dispatching seat
session_019YKN9TPqDSSXUaYqp2j7MZclaimed this card at 05:42:19Z and has produced nothing in the lane since 05:56Z. ⛔ This seat has NOT judged it dead — none of the three death criteria is met, andget_session/list_sessionsare unreadable for that session (it runs under theos-billaccount), ⛔ not negative. The takeover is the maintainer's instruction.Measured state of the previous attempt, ⛔ not inferred:
Claim:names my branch?" check fails loudly rather than silently sharing a branch.Premise re-check at dispatch time — HOLDS (measured on
origin/main@a02d4c14, 2026-09-08T10:2xZ)⇒ The card's whole premise — the stated justification is retired, the actual one is "unexamined" — is intact.
Generated by Claude Code
huangyiirene commentedon Sep 8, 2026
Writer census —
crm_event_attendee.invited_dateMeasured on
claude/issue-1747-invited-date-census@347d0b8(branched fromorigin/main@2e0f4dd). PR #1777 (draft) carries the comment-prose half. ⛔ The flag is not touched.Result
runAs/ contextreadonly: true?ACTIVITY_TARGETSx 3ACTIVITY_SPEC_NAMES, one shared body atsrc/actions/global.actions.ts:338isSystem: trueeventAttendeesFromContacts+eventAttendeesFromLeads, records built byattendeeRecords()atsrc/data/service.seed.ts:980mode: 'upsert'replayisSystem: truesrc/views/event_attendee.view.ts:97listsinvited_datein theinvitationsectionisSystem⇒ A user-context UPDATE writer exists. By this card's own routing that is the branch where the column legitimately stays open, and where the card closes having replaced "nobody has ruled on it" with a measured reason.
Evidence, per writer
1 — the activity actions. One statement, shared by all fifteen actions:
The context is measured on the pinned engine rather than assumed —
node_modules/@objectstack/runtime/dist/index.js:2581:2 — the service seed.⚠️ Not a pure INSERT: both seeds are
mode: 'upsert', so a warm-boot replay against an existing row is an UPDATE carryinginvited_date. It survives anyway, because seed writes runisSystem— the repo's own load-bearing claim atsrc/flows/demo-bootstrap.flow.ts:86("seed writes run under{ isSystem: true }"), corroborated by the behaviour that claim exists to explain: seeded rows landing ownerless, pinned attest/flow-scheduled.test.ts:786.3 — the record form.
invited_datesits in theinvitationsection besideresponseandis_organizer. Two things make it a writer rather than an artefact of the sweep:src/views/event_attendee.view.tsexists and someone put the field in that section by hand;crm_event_attendee(sales-rep,sales-manager,service-agent,system-admin) and all four grantallowEdit: true.A rep correcting Invited on a saved attendee row is therefore a non-
isSystemUPDATE carrying a caller-supplied key — precisely whatstripReadonlyFieldsdeletes.The asymmetry against the three already-flipped columns
approval_status,approved_dateandadded_datereturn zero hits insrc/views/— they are in no form section anywhere. That is not an accident of missing view files:crm_campaign_memberhas no view file at all, butcrm_opportunitydoes, with a hand-authored form, and its author left both approval columns out of it.invited_dateis the only one of the four that a person was deliberately given a place to type.⭐ It is also not the same kind of column.
added_date/approved_dateare stamped{NOW()}by their writer, so their value is the write moment.invited_daterecords when the invitation went out, and the seed dates it 3 to 10 days before its own event, chosen per row across 8 distinctinvitedDaysBeforevalues (service.seed.ts:806-886). A column whose value is not the write moment is one a person can be right about.Zeroes in this sweep, each with a control
update_recordflow node targets the attendee objectupdate_recordoccurs 29x insrc/flows/event_attendee.hook.ts, andevent.hook.tsnever names the objectsrc/objects/.insert(/objectName:write site namescrm_event_attendeecrm_campaign_memberinvited_dateappears nowhere inapps/,e2e/orscripts/readonly: truedeclaration inevent_attendee.object.tsgrep -rn 'readonly: true' src/objects/returns 44 hits across 12 files(a) The stated control for assumption 1 does not control anything. Zone 2 said: "
readonlyoccurs 4x elsewhere in that same file, so the absence is a real absence rather than a broken grep." Measured:readonlydoes occur exactly 4x insrc/objects/event_attendee.object.ts— at lines 337, 339, 341 and 342 — but all four are inside the single comment block that is aboutinvited_dateitself. There is no occurrence elsewhere in the file and noreadonly: truedeclaration anywhere in it. The cited control is the same prose, not an independent declaration.The absence is still real; the valid control is the tree-level one in the table above (44 declarations across 12 object files, e.g.
campaign_member.object.ts:165,opportunity.object.ts:258/:271), which proves the grep shape does find real declarations.(b) The card's starting point is true but incomplete, not wrong. The retired comment's claim — "written by the activity actions on insert" — describes writer 1 correctly. It omits writer 2 (the seed) and writer 3 (the form). It named 1 of 3 surfaces, which is why it could not see the UPDATE writer.
One thing recorded and deliberately not filed
This repo pins
@objectstack/* 17.3.0, in whichstripReadonlyForInsertappears only inside a doc comment and the insert path carries no strip — so the INSERT exemption holds on the engine HotCRM actually runs. Sources on theobjectstackmain line describestripReadonlyFieldsrunning on the create path too "since #14147". If that reaches a version HotCRM bumps to, writers 1 and 2 above change verdict and so do the #1666 / #1667 rulings. Not a defect today and not reproducible today, so no card: it belongs to whichever PR bumps the pin, whichAGENTS.mdalready routes through its Platform Upgrades section.Generated by Claude Code
huangyiirene commentedon Sep 8, 2026
os-dev-report
{ "issue": 1747, "status": "done", "branch": "claude/issue-1747-invited-date-census", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1777", "premise_still_valid": true, "census_verdict": "user-context UPDATE writer FOUND (the authored form field) — routing branch B: column legitimately stays open; card closes with a measured reason. NOT the needs-user-decision branch.", "writers": [ { "writer": "src/actions/global.actions.ts:338 (15 generated activity actions, one shared body)", "verb": "INSERT", "runAs": "isSystem:true via buildActionExecutionContext", "survives_readonly": true }, { "writer": "src/data/service.seed.ts:980 (attendeeRecords -> 2 seeds, mode:'upsert')", "verb": "INSERT first load; UPDATE on replay", "runAs": "isSystem:true (seed writes)", "survives_readonly": true }, { "writer": "src/views/event_attendee.view.ts:97 (authored 'invitation' form section)", "verb": "INSERT and UPDATE", "runAs": "user context, not isSystem", "survives_readonly": "INSERT yes, UPDATE NO" } ], "gates": { "runner": "repo's own chain (scripts/pm/dispatch-gates.mjs absent in hotcrm, as dispatched); exit codes captured before any pipe, one gate at a time", "head": "347d0b8", "pnpm validate": 0, "pnpm typecheck": 0, "pnpm lint": 0, "pnpm lint:i18n-gate": 0, "pnpm hygiene": 0, "pnpm hygiene:tokens": 0, "pnpm build": 0, "pnpm test": 0, "test_totals": "Test Files 164 passed (164); Tests 3438 passed | 1 skipped (3439)", "narrowing_declared": "none — full pnpm verify chain run on the final tree" }, "line_budget": { "skills_surface_touched": false, "ratchet": "pnpm hygiene:tokens clean; it strips comments before counting, so this comment-only diff is invisible to it", "business semantics": "~84,609 / ceiling ~100,000", "interaction layer": "~37,844 / ceiling ~40,000", "authored total": "~136,794 / ceiling ~140,000", "diff": "+57 / -4 across 2 files" }, "files_changed": [ "src/objects/event_attendee.object.ts (+23/-4, comment prose only; readonly flag NOT touched in either direction)", ".changeset/invited-date-writer-census.md (+34/-0, empty frontmatter 'releases nothing' declaration)" ], "deviations": [ "base: dispatch named a02d4c14; origin/main was already 2e0f4dd at worktree creation (PR #1768, src/translations + changeset, disjoint from this file surface). Branched from 2e0f4dd.", "Zone 2 assumption 1 CONTROL falsified: 'readonly occurs 4x elsewhere in that same file' — all 4 occurrences (lines 337/339/341/342) are inside the single comment block about invited_date itself; zero elsewhere, zero declarations in the file. Absence is still real; replaced with a tree-level control (44 'readonly: true' declarations across 12 files in src/objects/).", "line numbers re-located, not inherited: comment block is 337-346 (dispatch said 343-346), field declaration 347-350.", "read channel: repo-scoped REST returned 403 for this seat ('GitHub access is not enabled for this session'), so the card, its full body and all 11 timeline items were read via the zero-quota web-payload channel; MCP reserved for writes.", "PR body is English with no '维护者速读' section: hotcrm AGENTS.md 沟通语言 explicitly requires PR titles/bodies in English, and repo AGENTS.md is binding.", "PR footer uses the harness-mandated attribution block (it states it replaces earlier attribution guidance), with the '---' rule line kept for sanitizer durability.", "a PR was produced although Zone 2 #3 permitted zero code change: the census yielded the measured user-context UPDATE writer that Zone 2 #3 names as the condition warranting one." ], "mcp_calls": "5 — create_pull_request, update_pull_request, add_issue_comment x2, and this comment", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: hotcrm pins @objectstack/* 17.3.0, where stripReadonlyForInsert exists only in a doc comment and the insert path has no strip. objectstack main-line sources describe stripReadonlyFields running on the create path too 'since #14147'. If that reaches a version HotCRM bumps to, the INSERT exemption behind #1666/#1667/#1747 stops holding and writers 1 and 2 change verdict. Not reproducible on the pinned engine, so not a card. Successor: the PR that bumps the pin — AGENTS.md already routes it through its 'Platform Upgrades' section." ] }Generated by Claude Code
huangyiirene commentedon Sep 8, 2026
✅ ACCEPT — PR #1777. Census delivered; ⛔ and a PM control leg in the claim above was invalid
session_01PpRjGNnwyo2J1rrmekxB1W, R57, readings 2026-09-08T10:4xZ.⛔ Correction to
5583491215— my "control" did not controlThe claim comment offered this as the control proving the absence was real:
All four occurrences sit inside the single comment block about
invited_dateitself (:337/:339/:341/:342) — there are zero elsewhere in the file and zeroreadonlydeclarations in it. ⇒ a control leg drawn from the very text under examination, which is no control at all. The conclusion survives (the absence is real), but ⛔ it was not established by what I offered. The valid control is tree-level: 44readonly: truedeclarations across 12 files insrc/objects/— the dev's, not mine.Checklist verdicts
main✓ · first lineFixes #1747✓ ·mergeable_state: cleaninvited_date: Field.datetime({ group: 'response', label: 'Invited' })is an unchanged context line. Only the comment block moved. The flag is untouched in either directioncompleted: successon head347d0b8;Build and Test (22.x)converged 10:38:20Zsrc/objects/**+.changeset/**⇒ ⛔ not governed ⇒ normal landingRouting — branch B, and the card closes with a measured reason
The census found a user-context UPDATE writer (writer 3, the authored
invitationform section), so per the card's own routing this is not theneeds-user-decisionbranch: the column legitimately stays open, and "nobody has ruled on it" is replaced by a measurement. ⭐ The dev reported the branch and ⛔ did not convert the card itself, exactly as instructed.⭐ What makes this census hold up
buildActionExecutionContextreturning{...base, isSystem: true}in@objectstack/runtime@17.3.0. ⛔ Not a claim about how actions "should" run.readonly— every writer is the runAs:'system' approval flow #1666/crm_campaign_member.added_date can honestly be declaredreadonly— its only writers are INSERTs, which the readonly strip never touches #1667 moved appear in no form section anywhere insrc/views/—crm_campaign_memberhas no view file at all, andcrm_opportunity's hand-authored form leaves both approval columns out. That is what makes writer 3 a difference in kind rather than a difference in diligence.added_date/approved_dateare{NOW()}-stamped so their value is the write moment, whereasinvited_daterecords when the invitation went out — the seed dates it 3–10 days before its own event across 8 distinct offsets. ⇒ "A column whose value is not the write moment is one a person can be right about." That explains why a user-context writer is legitimate here, instead of merely recording that one exists. It is the reason this card closes rather than escalating.Deviations — none blocking
337-346, not my343-346) rather than inherited ✓.noted, not filed: this repo pins17.3.0, wherestripReadonlyForInsertexists only in a doc comment and the insert path carries no strip — butobjectstackmain-line sources describestripReadonlyFieldsrunning on the create path "since #14147". If that reaches a version HotCRM bumps to, the INSERT exemption under crm_opportunity.approval_status / approved_date can honestly be declaredreadonly— every writer is the runAs:'system' approval flow #1666 / crm_campaign_member.added_date can honestly be declaredreadonly— its only writers are INSERTs, which the readonly strip never touches #1667 /crm_event_attendee.invited_dateis the one audit stamp left without a writer census — the reason it stays open was retired by #1667 #1747 stops holding and writers 1 and 2 change verdict. Not reproducible on the pinned engine ⇒ ⛔ not a card, and it names a successor (the pin-bumping PR, routed by AGENTS.md → Platform Upgrades). ⇒ This seat is carrying it into the seat post's measured-facts section, so the carrier is durable rather than resting on one PR body.⇒ ACCEPT. Flipping ready and queueing.
Generated by Claude Code