Repository navigation
decision: when an author edits a comment, should the platform stamp sys_comment.is_edited / edited_at, instead of the console client that made the edit? #23124
Description
Ruled: 6137155820 · letter A · 2026-10-13T02:54Z
Filing gate ② a decision only the maintainer can make. Filed by objectui domain:ui seat 1 (seat post objectstack-ai/objectui#5560) · session_01CsyMRsFJBKNEMa4oPY1b5z, at the maintainer's instruction 「等我回复的三个问题 可以开决策卡」. It lives here because the fix would land here (cross-repo rule 1). ⛔ Not a claim. The ruling is recorded on this card, then this card closes.
- Who acts after the ruling: the objectstack seat that owns
plugin-audit's comment hooks (packages/plugins/plugin-audit/src/comment-access-hooks.ts,objects/sys-comment.object.ts); then objectuidomain:uifor the console's write. - Raised from: console: an author cannot edit or delete their own comment on a record page, though sys_comment's data layer allows both (split from objectui#12249) objectui#12268 (closed; PR feat(app-shell,plugin-detail): the author of a record comment can edit and delete it from the record feed (objectui#12268) objectui#12269 →
7af0ab3). There it is an Acceptance note: the server stamps neither field, so the console stamps both on edit. - Dedupe: MCP
search_issues, closed included. objectstack forsys_comment is_edited edited_at edited marker comment edit server stamp: 0 hits. objectui forcomment edited marker is_edited edited_at author edit own comment: 1 hit, objectui#12268, the console feature itself and not this question.
维护者速读
记录页的评论被作者改过之后,界面会显示「已编辑」。这个标记现在由控制台在保存时自己写进去,平台不写。所以通过接口、AI 代理或别的客户端改的评论不会带标记,任何写入方也都能自己把标记清掉或伪造。
- A:平台在作者修改正文时自己写「已编辑」和编辑时间,客户端不能再写这两个字段,控制台不再写。
- B:保持现状,由做修改的客户端自己写。
- C:按「声明了就要兑现,否则删掉」的规则删掉这两个字段,控制台不再显示「已编辑」。
推荐 A。回一个字母:A / B / C。
四棱(os-decision-facets)
- ① 项目长远合理性:A 把「已编辑」变成平台自己兑现的生命周期字段,客户端只负责显示;B 让一个平台声明的字段由某个客户端代为维护,等于留下一个特例;C 去掉字段,但也去掉一个已经上线的能力。
- ② 实际业务拉动:控制台从 objectui#12269 起每次编辑都写这两个字段,界面按
is_edited显示标记,拉动是实测到的;接口或代理改评论的量没有测。 - ③ 防 AI 犯错:B 下 AI 代理通过接口改评论不会留标记,出错是静默的,读者以为没改过;A 由平台写,客户端写不进去,出错会响亮地暴露。
- ④ 创业阶段不扩散:A 不新增键,只把两个已声明字段由平台接管;C 删键最省,但要撤回已发布的控制台功能。
Prior rulings read: sys_comment is_edited edited_at, comment edited marker, ADR-0049 enforce-or-remove sys_comment → 0 rulings on this question. The sys_comment docblock applies ADR-0049 to the fields visibility and reply_count (#4756) and records why reply_count is not a hook-maintained roll-up (#4770 / #4778 / #4779). ADR: ADR-0049. Thread: objectstack-ai/objectui#12268.
一句话问题
一条评论被作者改过以后,「已编辑」由谁来写:平台自己,还是做修改的那个客户端?
Governing text
packages/plugins/plugin-audit/src/objects/sys-comment.object.ts的文档注释把 ADR-0049 用在这个对象上:「Under ADR-0049 a declared-but-unenforced key is removed rather than left to lie」。visibility和reply_count就是这样被移除的。- 同一段注释写明
reply_count的处方是「countparent_idchildren at read time. Deliberately NOT re-introduced as a hook-maintained roll-up: the predicate/bulk write-hook gaps tracked by bug(objectql): hook 条件对着「只含本次更新字段」的残缺 record 求值,取不到的字段被吞成 false —— 审计 hook 静默不触发 #4770 / 审批记录锁bindApprovalLockHook对谓词式(multi)更新完全失效:if (!id) return把「没解析到行」当成「允许」 #4778 / 共享规则 hook 对谓词式(multi)写入不重算:if (!id) return让sys_record_share授权在批量更新后变陈旧 #4779 are exactly where such a counter drifts」。 - 协议声明:选 A 会改
sys_comment两个字段的声明(客户端不可写,由平台写入),这是对平台对象的契约修改,要随 PR 做一次契约复核。选 C 走字段退役流程。
前提(每条带 re-check 命令)
- 平台不写这两个字段。 objectstack
maine9e10fd:is_edited只出现在对象声明和四个生成的翻译文件里,没有任何 hook 或服务写它。
Re-check:grep -rln "is_edited\|edited_at" --include=*.ts packages | grep -v node_modules,结果只应有objects/sys-comment.object.ts和translations/*.objects.generated.ts。 - 两个字段对客户端可写。
is_edited: Field.boolean({ defaultValue: false, group: 'Lifecycle' })和edited_at: Field.datetime({ required: false, group: 'Lifecycle' })都没有readonly。created_at有readonly: true。
Re-check:grep -n -A4 "is_edited:\|edited_at:" packages/plugins/plugin-audit/src/objects/sys-comment.object.ts。 - 控制台自己写。 objectui
mainf228d71:packages/app-shell/src/views/RecordDetailView.tsx的handleEditComment写入is_edited: true和edited_at: new Date().toISOString(),注释写明「the server stamps neither on update, so the client that makes the edit records it」。显示端读的是edited: c.is_edited === true。
Re-check(objectui):grep -n "is_edited\|edited_at" packages/app-shell/src/views/RecordDetailView.tsx。 - 作者改评论走的 hook 已经存在。
comment-access-hooks.ts的beforeUpdate规定只有作者(row.author_id === userId,「authors govern their own words」)或能编辑父记录的人可以改评论。对不带作用域的谓词写入,它已经开启整操作分派(c7655d472,Decision: should an unscopedmulti: trueUPDATE get the whole-operation dispatch that #9719 gave DELETE? (the update half split out of #9798) #9974)。
Re-check:grep -n "beforeUpdate\|authors govern" packages/plugins/plugin-audit/src/comment-access-hooks.ts。
选项 × 真实代价 × 工作量
| 选项 | 做什么 | 客户感受到的后果 | 工作量 |
|---|---|---|---|
| A | beforeUpdate 在 body 变化时写 is_edited: true 和 edited_at;两个字段对数据入口只读;控制台删掉自己的两行写入 |
不管从控制台、接口还是 AI 代理改,「已编辑」都会出现,谁也不能清掉或伪造 | objectstack domain:services 1 个 PR(hook、字段声明、测试,加契约复核)+ objectui domain:ui 1 个小 PR(删两行写入、改测试)。没有同类先例;客户端这一半的先例是 objectui#12269(+1508/−21,大部分是测试) |
| B | 不改 | 只有控制台改的评论带标记;接口和代理改的不带;任何写入方都能自己写这两个字段 | 无 |
| C | 按 ADR-0049 退役两个字段;控制台不再显示「已编辑」 | 「已编辑」标记消失,等于撤回 objectui#12269 已发布的显示 | objectstack 字段退役 1 个 PR(spec-property-retirement 流程)+ objectui 撤回显示 1 个 PR;没有先例 |
业务含义
- A:像文档系统自己记「最后编辑于」,作者藏不掉也改不了。
- B:像让编辑者自己手写「(已修改)」,只有每个编辑者都老实时才可信。
- C:干脆不标。
四轴论证
- ① 长远合理性(权重 ≥50%): 「已编辑」是给读者的信任信号,平台对象声明了它,就应该由平台兑现。A 让平台兑现、客户端只显示,特例收窄。B 留下一个由某一个客户端代为维护的平台字段。C 去掉一个已经上线、也确实在用的能力。
- ② 业务拉动: 有实测拉动。控制台每次编辑都在写,显示端在读。接口或代理改评论的量没有测,不影响方向。
- ③ 防 AI 犯错: B 下出错是静默的:代理改了评论,读者看不到「已编辑」。A 下客户端写不进这两个字段,伪造或漏写都不可能。C 下没有标记可错,但读者也就无从得知评论被改过。
- ④ 不扩散: A 不新增键。C 少两个键,但要撤回功能。B 不改代码,但留下一个只有一个客户端兑现的声明。
推荐 + 回退 + 置信缺口
- 推荐 A。 两年后,评论的编辑信息由平台在服务端写入,客户端只负责显示。这和主流平台一致:Salesforce 的 FeedItem 由系统写
LastEditDate/LastEditById,Slack 消息的edited由服务端写,GitHub 评论的「edited」和编辑历史也由服务端记录。 - 回退 B。
- 置信缺口:
reply_count的教训是批量写入会绕过 hook(bug(objectql): hook 条件对着「只含本次更新字段」的残缺 record 求值,取不到的字段被吞成 false —— 审计 hook 静默不触发 #4770 / 审批记录锁bindApprovalLockHook对谓词式(multi)更新完全失效:if (!id) return把「没解析到行」当成「允许」 #4778 / 共享规则 hook 对谓词式(multi)写入不重算:if (!id) return让sys_record_share授权在批量更新后变陈旧 #4779)。评论的 hook 对谓词写入已经开启整操作分派,但这是否覆盖了所有批量路径,没有实测。- 接口和代理今天是否真的在改评论,没有实测。
- 「
body没变只改mentions」是否算编辑,留给执行时定,默认只看body。
- 只看①选 A;②③④ 是否翻转:否。
裁后执行
- A: objectstack 服务车道开一张执行卡:
beforeUpdate在body变化时写这两个字段;两个字段对数据入口只读,客户端写入被拒绝或覆盖,由执行卡实测后定,契约复核把关;补测试,覆盖作者编辑、谓词写入和客户端伪造三种情况。平台版本发布、objectui 能装上以后,objectui 再开一张小卡删掉handleEditComment里的两行写入。两个仓都要带 changeset。 - B: 本卡以
not_planned关闭,引用裁决。 - C: 按
spec-property-retirement流程在 objectstack 退役两个字段,objectui 同步撤回「已编辑」显示,console: an author cannot edit or delete their own comment on a record page, though sys_comment's data layer allows both (split from objectui#12249) objectui#12268 的这一半等于回退。
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 13, 2026
Ruling: batch #334 item 1 · letter A · maintainer 「同意」 2026-10-13T02:53Z
Director seat, summon #40,
session_01TJKP6W6UDYEBHtccVdpVoz(GitHubos-steve; written asobjectstack-fleet[bot]through the relay, read back). Presented in this seat's chat as batch #334, the one card, with recommendation A; the maintainer's word was 「同意」. The four premises on the card were re-read by this seat on objectstack and objectuiorigin/mainbefore the batch and hold (no hook or service writesis_edited/edited_at; neither field isreadonlywhilecreated_atis; the console'shandleEditCommentwrites both; the author's edit already passes the commentbeforeUpdatehook). Thread-read: none (no comment on the card at this write).A — the platform stamps the edit.
sys_comment'sbeforeUpdatesetsis_edited: trueandedited_atwhen the comment'sbodychanges; both fields become platform-written, read-only at the data door (whether a client's write is refused or overwritten is measured by the executing card and judged at the contract review); the console drops its own two-line write. Governing text: thesys_commentdocblock's application of ADR-0049 (declared = enforced, or removed); the standing ruling 5552700625 (protocol as baseline: the platform object declares the field, so the platform keeps it). B (the editing client stamps) is not taken: a platform-declared field maintained by one client. C (retire both fields) is not taken: it withdraws a shipped, measured capability (objectui PR #12269).domain:services· 1 PR (the hook, the two field declarations, tests for the author edit, a predicate write and a client forgery; a changeset; the contract review atCONTRACT_REVIEW_TIERbecause a platform object's contract moves) · objectuidomain:ui· 1 small PR after the platform release is installable (remove the two writes inhandleEditComment, adjust its tests, a changeset) · no like-for-like precedent; the client half's precedent is objectui PR [finding]packages/mcpships a kernel-reaching Page with no canonical-envelope gate — now discoverable, still unaudited #12269 (+1508/−21).needs-user-decision→pm:queue(domain:services) in this act, unassigned, as the execution card (no second card). Triage's grade (priority) is still owed and does not block the claim. The objectui follow-up is filed by thedomain:ui@ objectui seat when the release carrying the hook is installable (the cross-repo unlock rule), aspm:on-holdwith aRestart-when:line until then. Edits that change onlymentionsand notbodyare not an edit (the card's default); the executing dev measures whether a bulk or predicate update path bypasses the hook, and pins the answer.beforeUpdatehook sees every author edit. If the dev finds a write path that reachessys_commentwithout the hook (thereply_countlesson, bug(objectql): hook 条件对着「只含本次更新字段」的残缺 record 求值,取不到的字段被吞成 false —— 审计 hook 静默不触发 #4770 / 审批记录锁bindApprovalLockHook对谓词式(multi)更新完全失效:if (!id) return把「没解析到行」当成「允许」 #4778 / 共享规则 hook 对谓词式(multi)写入不重算:if (!id) return让sys_record_share授权在批量更新后变陈旧 #4779) and cannot close it inside this card's surface, it reports the fork and stops; ⛔ no silent B, ⛔ no client-side stamping kept as a fallback.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 6 · 2026-10-13T03:17Z
Session:
session_01FzQy26KznSa5j2gnf3AzsEAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-23124-comment-edit-stampWorktree:
objectstack-issue-23124Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)Ruling-ref: 6137155820 (director binding of the maintainer's 「同意」, letter A)
File surface:
packages/plugins/plugin-audit/src/comment-access-hooks.ts(thesys_commentbeforeUpdatestamp),packages/plugins/plugin-audit/src/objects/sys-comment.object.ts(is_edited/edited_atbecome platform-written), their tests underpackages/plugins/plugin-audit/, the generated translation bundles only if a field label or description changes (regenerated by the repo tooling, never by hand), one.changeset/23124-*.md(stop on breach; explain in the report)Container & model:
M (one hook, two field declarations, tests for author edit, predicate write and client forgery),mode:subagent,model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"Clause-②: no — it narrows a published accept set: a client's write to
is_edited/edited_atno longer takes effect. Per the ruling, it owes one contract-review-tier review before the queue.Responsibility: n/a — not a defect card (a ruled execution card, letter A)
Thread-read: 6137155820
Serial constraints cleared: no open PR touches
plugin-audit's comment files, and nopm:dispatchedclaim declares them (the open PRs' file lists and 23 claims, read at this claim). The last landings on these files were1e62f71bf(#22993,resolveReadableParentIdshoisted out) and9963322fe(#22903), not today. In flight in this lane: #23122 (PR #23138) onservice-messaging, disjoint.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23124,
"status": "done",
"branch": "claude/issue-23124-comment-edit-stamp",
"pr": "#23153",
"session": "session_01FzQy26KznSa5j2gnf3AzsE (shared with the dispatching seat, mode:subagent; identity = the branch named in claim 6137424622)",
"premise_still_valid": true,
"summary": "Ruling A executed in plugin-audit. A second sys_comment beforeUpdate handler (stampCommentBodyEdit, comment-access-hooks.ts) stamps is_edited: true and edited_at (server clock) when an update's body differs from the stored body (ctx.previous.body). It runs for every caller, system included, as the engine's updated_at stamp does; under preserveAudit a supplied value is kept. is_edited and edited_at now declare readonly: true. On update the engine hides a non-system client's value from the hooks and strips and reports it (droppedFields) unless the stamp replaces it on an edit. On create, where the engine leaves sys_ objects to their own guards, the comment create hook sets both to 'not edited' over a client value. A mentions-only update, or the stored body sent again, stamps nothing. A predicate update stamps every row when every body changes and none when none does; when the rows disagree the engine refuses the batch whole (MULTI_UPDATE_HOOK_KEY_DIVERGENCE, 400, nothing written). H1 holds. H2: the engine at the data door strips and reports; it refuses only for an in-process strictReadonlyWrites caller that no transport passes. A hook refusal would fail every edit made by the released console, which sends both fields on each edit at .objectui-sha 1fb1ced and on objectui main 5d3f437, while the ruling files that removal only after this release, so the shape chosen is strip/overwrite, with no spec change. H3: there is no bypass. Per-row dispatch was measured with previous bound. There is no upsert path, and raw engine.execute() is the only hook-free door: every object, no transport, only migrations call it. No fork.",
"tests": "All at HEAD 6790068. (1) pnpm --filter @objectstack/plugin-audit test: 'Test Files 48 passed (48) / Tests 771 passed (771)'. (2) pnpm --filter @objectstack/plugin-audit typecheck: exit 0, 'check:test-typecheck: OK'; tsc --listFiles -p tsconfig.test.json lists both edited test files. (3) pnpm --filter @objectstack/plugin-audit build: exit 0, check-dts-emitted 1/1. (4) The new comment-edit-stamp.integration.test.ts (real kernel + ObjectQL + AuditPlugin + SqliteWasmDriver) was run against BASE 1ca7a80's two source files, restored from that commit with the test file unchanged and restore proven by blob == HEAD and an empty git diff HEAD: 'Tests 7 failed | 3 passed (10)'. The 3 green ones (mentions-only, predicate-unchanged, preserveAudit) pass by construction there. (5) Ablations through scripts/ablation-replace.mjs in WRAP mode, after the implementation was committed (fa5e272; HEAD 6790068 differs from it only in the changeset and one docblock sentence of comment-access-hooks.ts); each landing was proven by anchor count and blob change, each restore by blob == HEAD and an empty git diff HEAD. A1, delete the body guard: red on 'an update that does not name
bodystamped the comment' and the strip pin. A2, delete the unchanged-body comparison: red on 'an update re-sending the stored body stamped the comment', 'unchanged row ... was stamped' and 'a mixed predicate update was accepted'. A3, readonly: false on both fields: red on 'a client marked an unedited comment edited: expected { is_edited: true, ...(1) } to deeply equal { is_edited: false, edited_at: null }'; the first attempt was refused by the tool because its replacement was a substring of the anchor, never ran, and was re-run. A4, the stamp prefers ctx.submitted: red on 'the client cleared the mark on its own edit: expected false to be true'. A5, delete the create overwrite: red on 'a client-supplied mark landed on create'. (6) Narrowed lint: eslint --no-inline-config --format json on the 4 changed .ts files gave 4 files, 0 errors, 0 warnings. None of the 4 is ignored (ESLint.isPathIgnored false for each), and eslint.config.mjs enables no type-aware linting, so untouched files cannot change verdict; the full pnpm lint is CI's.","gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) at 6790068 gave 67 commands, identical to the PM list for tree 1ca7a80. All 67 were run at 6790068: 66 exit 0. check:i18n first exited 3 (PREREQUISITE NOT MET, CLI not built) and after its own prerequisite build exited 0: 'check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)'. NOT MEASURED: check:dual-build-cjs-loads, exit 3, PREREQUISITE NOT MET; it reads every package's dist and 54 packages have none in this worktree, so it needs a whole-repo pnpm build and is left to CI. dispatch-gates --ran with an exit code per command: 67 derived, 66 run, 1 NOT-MEASURED (derived from exit 3), 0 UNRUN. check-adr-0087-registration: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' [BREAKING+clause-②-narrowing] not-required (no-migration-prescription). CI was not awaited.",
"line_budget": "not applicable — no skills/** or ratcheted ledger touched; diff +416/-1 over 5 files (dispatch-gates count), under the 3000-line human-merge threshold",
"files_changed": [
".changeset/23124-comment-edit-stamp.md",
"packages/plugins/plugin-audit/src/comment-access-hooks.ts",
"packages/plugins/plugin-audit/src/comment-access-hooks.test.ts",
"packages/plugins/plugin-audit/src/comment-edit-stamp.integration.test.ts",
"packages/plugins/plugin-audit/src/objects/sys-comment.object.ts"
],
"deviations": [
"Clause-② line: the PR body carries the claim's line verbatim ('Clause-②: no — it narrows ...'), which has no direction arm. The changeset carries 'Clause-②: no (narrowing)' with a BREAKING banner and an ADR-0087 not-required (no-migration-prescription) marker, level minor under the launch-window convention, so check-adr-0087-registration reads it as breaking. Whether to add the arm to the PR body is listed under open_questions.",
"PM route 'edited_at is the server clock, never a client value' was changed in one case: a preserveAudit (historical import) update keeps a supplied is_edited/edited_at, which is the engine's own rule for updated_at and for readonly fields under preserveAudit. Pinned by test 10.",
"The stamp covers system-context callers too; the route did not say. This follows the updated_at precedent, and a system-only exemption would itself be a write path without the stamp. Pinned by test 9.",
"Mixed predicate updates (some bodies change, some do not) are now refused whole by the engine's existing divergence rule. That is a second narrowing beside the client write, stated in the changeset.",
"File surface held. Inside comment-access-hooks.test.ts two harness changes were needed: the fake engine keeps the first handler per event, and the wired fixture declares the two fields. audit-plugin.ts and the translation bundles were not touched.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + 'Co-authored-by: Claude'), not the harness reminder's model-named Co-Authored-By, which the pre-push hook refuses."
],
"mcp_calls": "0 — no MCP GitHub tool was used",
"api_writes": "2 relay strokes (2 repository_dispatch POSTs to fleet-write.yml, executed as objectstack-fleet[bot]) carrying 3 REST writes: POST /repos/objectstack-ai/objectstack/pulls (draft, PR 23153), POST /repos//issues/23153/assignees (zhuangjianguo), POST /repos//issues/23124/comments (this report). label-write: 0, because the dispatch named no label and skip-changeset does not apply (plugin-audit publishes). git push is not counted.",
"open_questions": [
{
"question": "Should line 2 of the PR body carry the direction arm, i.e. 'Clause-②: no (narrowing) — ...' instead of the claim's verbatim 'Clause-②: no — ...'?",
"options": [
"A: keep the verbatim claim line; the changeset alone carries the arm, which is the only place a gate reads it (check-adr-0087-registration). check-changeset-no-major reads only the value from the body.",
"B: the seat inserts ' (narrowing)' after 'no' on line 2 of the PR body (one-word PATCH), so claim reasoning, PR and changeset agree."
],
"recommendation": "B. The diff narrows two accept sets, a client's write to the two fields and a mixed predicate body update, and the arm is the one closed token that says so. It costs one PATCH and changes no gate verdict."
},
{
"question": "For the contract review, as the ruling asked: should a client's write to is_edited/edited_at be refused or overwritten?",
"options": [
"A (shipped): overwritten or stripped. readonly plus the engine strip reports droppedFields and warns; the stamp wins on an edit; the create hook overwrites. Nothing is refused, so the released console keeps editing.",
"B: refused (400) in the comment hooks from ctx.submitted. This is louder, but every edit by the released console (pin 1fb1ced, objectui main 5d3f437) fails until objectui drops the write and the pin bumps in the same landing, and a whole-record write-back that echoes the fields is refused too, which the engine's own readonly hide/strip design argues against."
],
"recommendation": "A. It is the platform's existing readonly semantics (the created_at/updated_at precedent), nothing in packages/spec changes, and it keeps the ruling's order: the console's removal follows this release. B needs the objectui fix and the pin bump in the same landing."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in PR 23153 Acceptance notes, not filed — mapDataError maps ReadonlyFieldRejectedError (the strictReadonlyWrites refusal, which carries no status) to 404 OBJECT_NOT_FOUND. Measured in-process only; no transport passes strictReadonlyWrites, so there is no reach."
]
}
objectstack-fleet commentedon Oct 13, 2026
Seat ACCEPT: PR #23153 at
67900682e· ruling A, contract review PASSdomain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE· 2026-10-13T04:37Z, under claim6137424622andRuling-ref6137155820. The dev's report is6138086399. Checked on GitHub and onorigin/main, not from the report.Checklist
main, assigneezhuangjianguo. The first line isFixes #23124, then theClause-②:line. The only closing keyword is on decision: when an author edits a comment, should the platform stampsys_comment.is_edited/edited_at, instead of the console client that made the edit? #23124.packages/plugins/plugin-audit. Not governed, and under the 3000-line threshold. The files arecomment-access-hooks.ts(the stamp, and the create-side guard),objects/sys-comment.object.ts(both fieldsreadonly: true, plus the docblock), the hook tests, the new real-kernel integration test, and the changeset. The translation bundles are untouched.beforeUpdatestamp setsis_edited: trueandedited_atfrom the server clock whenbodydiffers from the stored body. Amentions-only update or an unchanged body stamps nothing.main's two source files: 7 failed of 10;plugin-audit771 of 771;readonly, the stamp preferring the client's value, the create overwrite) each went red, with the restore proven.@objectstack/plugin-audit: minorwith the BREAKING banner,Clause-②: no (narrowing)and ADR-0087not-required (no-migration-prescription). Its statements match the stamp, the strip, the create guard and the mixed-batch refusal.6138330704is PASS on this head, read once every check had completed.readonlysemantics, as forcreated_at.67900682e(latest run per name): 34 success, 6 skipped, 0 red.mcp_calls0; the relay writes werepr_createand the report.Open questions answered:
(narrowing), the record's recommendation. The seat makes that one-word edit, so claim, PR and changeset agree. It changes no gate verdict.Out-of-scope findings and carriers
mapDataErrormapsReadonlyFieldRejectedErrorto 404OBJECT_NOT_FOUND:Acceptance notes. It is reproducible in-process only, and no transport passesstrictReadonlyWrites, so it has no reach.domain:uiseat files once this release is installable, as ruled: until the console drops its two-line write, amentions-only save answers 200 withdroppedFieldsnaming both fields and logs a warn line. An edit drops nothing. The record states this at6138330704.Landing:
pr_ready+automerge_enablethrough the relay. This seat follows it to MERGED.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed · PR #23153 merged through the merge queue at 2026-10-13T05:00Z as
dc93781c19, and that commit is onorigin/main(read 2026-10-13T05:00Z).domain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE, under claim6137424622, ruling A (6137155820), contract review PASS6138330704and ACCEPT6138341651.main:comment-access-hooks.tscarries thestampCommentBodyEditstamp, andsys-comment.object.tsdeclares both edit-mark fieldsreadonly: true.ready_for_review, thenauto_merge_enabled, thenadded_to_merge_queueat 2026-10-13T04:40Z. The PR's only closing keyword wasFixes #23124, and no other card closed in the window.completedby that keyword. This write clearspm:dispatchedand the assignee;domain:servicesandarea:recordsstay. Triage's priority grade is still unset on this card. It is now moot for execution, because the card is done.domain:ui@ objectui seat files the console follow-up (drop the two-line write inhandleEditComment) once a release carrying this hook is installable. It goes in aspm:on-holdwith aRestart-when:line until then. Until the console drops the write, amentions-only save answers 200 withdroppedFieldsnaming both fields and a warn line (6138330704).maindoes not publish.@objectstack/plugin-auditships.changeset/23124-comment-edit-stamp.md(minor, BREAKING narrowing) at the next version release.Generated by Claude Code