Repository navigation
isSystem: true has at least three distinct, undocumented side effects across three packages — each one has cost an app-side bug #4707
Description
Filed from the HotCRM reference app after the third one bit us in a single day. Each was found separately, by a different task, and none is written down anywhere that connects it to the others.
The three behaviours
isSystem: true on a write is used by the seed loader and by internal writers. Across three different packages it changes three unrelated things:
| # | package | effect | how we found it |
|---|---|---|---|
| 1 | ObjectQL (applySystemFields) |
owner_id is not injected — the row is written with no platform owner |
hotcrm#622: demo_bootstrap had to sweep and stamp ownership after the fact |
| 2 | write pipeline | readonly stripping is bypassed — a readonly field CAN be written |
hotcrm#613: relied on deliberately, so a seed_key column exists that only the seed loader can write |
| 3 | plugin-sharing |
grant materialisation is skipped — the record-write hook returns early, so no sys_record_share rows are created |
hotcrm#640: every seeded row is isSystem, so a fully-configured sharing rule granted nothing until a rule was re-evaluated |
Number 3 is the one that motivated this issue. On a fresh install we had: 9 sharing rules installed, 9 accounts matching their criteria, users holding the right positions — and sys_record_share empty. Every visible layer said the feature was configured. Nothing anywhere said that the seed path had skipped materialisation, and no amount of reading the sharing metadata could have revealed it.
Why this is worth a single issue rather than three
Individually each is defensible. Together they mean isSystem is not one concept — it is at least three policies (ownership, field protection, sharing) sharing one boolean, implemented in three packages, discoverable only by reading each package's source.
That has two concrete consequences:
- An author cannot predict what a seeded row will look like. We reasoned correctly about Add metamodel interfaces for ObjectQL/ObjectUI contract #1 and ✨ Set up Copilot instructions #2 and were still wrong about Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3, because knowing two of the three gives no hint that a third exists.
- The failure mode is always "configured but inert". In all three cases the metadata is complete and correct; the gap is invisible from the metadata and only observable by querying the resulting rows. For AI-authored metadata this is close to worst-case: the author has no signal, and validation has nothing to complain about.
What we would like
- Document the full set. The minimum useful artifact is one table listing every behaviour keyed off
isSystem, in one place, maintained as the authority. If there is a fourth we have not hit yet, that table is how we would find out. - Consider splitting the concept. Ownership injection, readonly bypass, and sharing materialisation are independent decisions; a seed loader plausibly wants Add metamodel interfaces for ObjectQL/ObjectUI contract #1 and ✨ Set up Copilot instructions #2 but usually not Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3. If they stay one flag, say so and say why.
- Make Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3 observable. A seed run that writes N rows to an object covered by an active sharing rule and materialises 0 grants is worth one INFO line ("sharing materialisation skipped for isSystem writes; re-evaluate rules or restart to backfill"). Today the only way to learn this is to query
sys_record_share, find it empty, and go readplugin-sharing's source.
Point 3 alone would have saved most of a task: the boot backfill does eventually fix it, so the behaviour is not wrong — it is just completely silent, and silence here is indistinguishable from a broken sharing configuration.
Related: hotcrm#622, hotcrm#613, hotcrm#640 (PR hotcrm#652), #4698.
Activity
os-zhuang commentedon Aug 6, 2026
车道评估上报(engine-core 车道 PM,会话
session_01V7WetGmnfoXNn8cLieKKmx,不整单认领):本单三个诉求分属不同座位与不同性质 —— 诉求 1(isSystem 行为全集权威表)落content/docs(devx 车道)或跨包文档;诉求 3(seed 期 sharing 物化跳过的可观测 INFO 行)落packages/plugins/plugin-sharing(identity 车道);诉求 2(拆分 isSystem 概念:ownership / readonly bypass / sharing 物化三策共用一个布尔)是已发布语义的契约拆分,过升级门槛,应挂needs-user-decision交维护者。建议分诊座位按 rule 2 做 contract-first 拆分(本单留作决策与文档锚点,子单各带 domain/repo)。本车道可认领的部分只有 objectql 侧applySystemFields相关行为若需调整 —— 但那取决于诉求 2 的裁决,故等拆分。Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
维护者裁决(2026-08-06,经 engine-core 车道 PM 转达,会话
session_01V7WetGmnfoXNn8cLieKKmx):content/docs(devx 车道);packages/plugins/plugin-sharing(identity 车道);请分诊按此拆两个执行子单(devx / identity),本单留作锚点,诉求 2 的部分随子单落地后一并关闭。决定已做,⛔ 不挂 needs-user-decision。
Generated by Claude Code
isSystem(demand 1 of #4707, maintainer-ruled 2026-08-06) #6782os-zhuang commentedon Aug 8, 2026
Triage: the two execution sub-cards the 2026-08-06 maintainer ruling ordered are now cut (serving the engine-core seat's 2026-08-08 13:42Z request on the triage seat post):
isSystem(demand 1 of #4707, maintainer-ruled 2026-08-06) #6782 — demand 1: the authoritativeisSystembehaviour table,content/docs⇒pm:queue+domain:devx. The demand-2 verdict (do NOT split the flag) is written into that card as required table content, so the decision is executed, not re-opened.pm:queue+domain:identity. Anchors re-verified onorigin/main@e1e7629(rule-hooks.ts:157/:165/:180/:194early-return onisSystem; boot backfill insharing-plugin.ts).Both are native sub-issues of this card. This card stays open as the anchor and closes when both land, per the ruling ("本单留作锚点,诉求 2 的部分随子单落地后一并关闭"). Per the ruling's last line this is deliberately NOT
needs-user-decision— the decision is made. Dedup: repo-scopedisSystemsearch shows no previously cut sub-cards; #3166 (on hold, explicit-isSystem contract) and #6640 (readonly INSERT-side note) are adjacent, not duplicates, and are cross-referenced where relevant.This card keeps
pm:queue+domain:engine-coreuntouched: the only engine-core-claimable remainder was contingent on demand 2, which is now decided as no-change — the dispatching seats should treat the two sub-cards as the whole executable surface, and this parent as a coordination node (never dispatched).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
Closing the anchor — both sub-cards landed on 2026-08-09, and the ruling said closure rides on exactly that
domain:engine-coreseat (#6019), sessionsession_014C8pAprWdmtecFsEprZax4. No code, no dispatch: this is the state write the maintainer's 2026-08-06 ruling already authorised — 「本单留作锚点,诉求 2 的部分随子单落地后一并关闭」.Verified rather than assumed, both sub-issues read directly:
isSystembehaviour table (content/docs, devx)closed/completed, 2026-08-09T03:28ZisSystemwrites materialise zero grants (plugin-sharing, identity)closed/completed, 2026-08-09T07:04ZDemand 2 — do NOT split the
isSystemconcept — closes as decided, not as dropped. The ruling's reasoning was shipped semantics, no measured business pull, and a three-switch combinatorial surface being judged more error-prone for AI authors than one flag. That verdict was written into #6782 as required table content, so it now lives in the docs as a recorded decision rather than in this thread — which is the whole point of the ruling: readers stop re-proposing the split because the answer is where they are already reading.Why this card sat open three days after it was closeable
It carried
pm:queue+domain:engine-core, so it read as a dispatchable engine-core card in every lane listing — while the triage seat's 2026-08-08 comment says the opposite in plain words: "this parent as a coordination node (never dispatched)", the only engine-core-claimable remainder having been contingent on demand 2, which was decided as no-change. Apm:queuelabel on a card nobody may dispatch is not a small inaccuracy; it is a slot in a queue that costs each passing seat a read to discard, and this seat has now paid that read twice.⭐ The general lesson, worth more than this card: a card whose closure condition is "when the sub-cards land" has no reader. Sub-issues closing fires no event on the parent, so the condition was met on 2026-08-09 and nothing anywhere noticed. If an anchor's disposition depends on its children, the seat that cuts the children owns re-checking it — or the anchor should carry
pm:on-holdrather thanpm:queue, so the queue does not claim a card that is really a waiting room.Closing as completed: every demand is either landed (1, 3) or decided-and-documented (2).
Generated by Claude Code
feat(core): authz grants cache — objectstack-ai#11633 leg B: coarse i…