refactor(ai): skills-only AI surface — real Actions instead of 10 fictional tools, retire the two agents - #512
Merged
Conversation
…tional tools, retire the two agents (objectstack#3820, ADR-0109) HotCRM was the corpus that motivated objectstack#3820: 6 skills declaring 16 tool references, of which 10 named tools that exist nowhere. The copilot's instructions therefore claimed abilities it did not have — the runtime silently drops an unresolved tool, so the model improvised or failed when asked to use one. Every instance passed `objectstack validate` and `lint` cleanly. The fix is mostly deletion, because the references were a category error: "analyze the pipeline", "generate email copy", "score this lead" are REASONING, not tools. What actually needed a tool was the handful of steps that change state — and those already exist as HotCRM Actions, reachable through the `action_<name>` tools the runtime materialises from them (ADR-0109's default path: no tool records to author). - case_triage: `triage_case` → the priority rubric moves into instructions; `action_escalate_case` / `action_close_case` do the work. Also repoints the hand-off that named `response_drafting`, a skill that never existed (the audit's "hand-off to a nonexistent skill"), at the real `email_drafting`. - email_drafting: 4 fictional copy tools → the model writes the copy; `action_send_email` sends it, so recipient resolution, permissions and audit match the UI path. - lead_qualification: `analyze_lead` / `suggest_next_action` → BANT reasoning in instructions over `get_record`; `action_convert_lead` / `action_schedule_followup` act, and conversion requires confirmation. - revenue_forecasting: 3 fictional analytics tools → `aggregate_data` / `query_records` / `visualize_data` with the actual forecasting method spelled out in instructions. - live_data, customer_360: already correct, untouched but for prose. The two agents (`sales_copilot`, `service_copilot`) are DELETED. ADR-0063 §2 closed `*.agent.ts` to third parties and cloud#904 made the runtime refuse non-platform agent records, so they were unreachable metadata — carrying them is the ADR-0078 lie. The capability lives in the skills, which attach to the platform assistant by surface affinity. Verified with the merged reference-integrity rule (objectstack#3885 `validate-ai-tool-references`): 22 tool references, 0 findings — was 16 references with 10 dead. typecheck, `objectstack validate`, and the 67-test suite pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…DR-0011) Follow-up to the skills rewrite in this PR. The first pass routed state changes through `action_escalate_case` / `action_close_case` / `action_send_email` — but the runtime materialises `action_<name>` ONLY for an Action that opts in (`ai.exposed` + `ai.description`) AND has a headless path. All three are `type: 'modal'`: they collect a reason / resolution / final copy from a person, so they are UI-only and no tool is ever generated. Referencing them would have re-created the bug this PR fixes, one layer down. - `convert_lead` and `schedule_followup` are flow-typed with targets, so they only needed the opt-in: both now carry `ai.exposed` + an LLM-facing description. `convert_lead` keeps its `confirmText`, which the runtime reads as approval-required — an agent invocation lands in the HITL queue instead of converting a lead unattended, which is what we want for an irreversible outcome. - `case_triage` and `email_drafting` no longer claim to act. They read, reason, and hand a ready-to-paste `reason` / `resolution` / draft to the user, pointing at the Escalate / Close / Send Email button. The instructions say plainly that the assistant cannot press it — a modal Action staying human-driven is the right shape for these, not a gap. Re-verified with the corrected rule (objectstack#3894, which now models AI exposure): 19 tool references, 0 findings. Both directions hold — the two opted-in flow Actions resolve, the three modal ones would not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
os-zhuang
marked this pull request as ready for review
July 28, 2026 15:35
This was referenced Jul 31, 2026
yinlianghui
pushed a commit
that referenced
this pull request
Jul 31, 2026
#512 deleted `src/agents/` when the AI surface went skills-only, but seven maintainer docs kept printing `src/agents/*.agent.ts` in their tree diagrams and registration tables. `code_examples.md` was the worst of it: after registering a skill it told the author to "add its name to an agent in `src/agents/*.agent.ts`" — an instruction that cannot be followed. `src/cubes/` had the same shape, dropped in favour of datasets (ADR-0021, noted in objectstack.config.ts) and still drawn in two trees. Nothing checked, because a path in prose is just prose. That is the same failure mode as #493 one layer out: metadata validation sees a well-formed string and stops, so the reference rots silently and the next reader — human or agent — is sent somewhere that does not exist. - README.md, AGENTS.md, docs/README.md, docs/STATUS.md, docs/ARCHITECTURE.md, docs/developers/code_examples.md: drop the agent and cube directories; README picks up `src/datasets/` (the layer that actually replaced cubes) and its flow count goes 20 → 23; AGENTS.md's `*.action.ts` becomes `*.actions.ts`, the real suffix. - code_examples.md: drop the skill `permissions: [...]` key — SkillSchema has no such field and strips it silently (#511) — and fill the gap the deletions leave. The example now says which two sources a `tools` name can resolve to (platform data tools, or `action_<name>` from an `ai.exposed` Action with a headless path), why `defineTool` is not a third one, that reasoning belongs in `instructions` (ADR-0109), and where the guard lives. - ARCHITECTURE.md: same note, plus the `case_triage` skill its table had been missing. Adds a repo-tree guard to test/docs-drift.test.ts — every `src/<dir>/` a maintainer doc names must resolve on disk, with `docs/archive/` excluded as a historical record. It earned its place immediately by failing on a stray `no src/agents/` parenthetical in this change's own first pass. 155 tests (+9), typecheck, validate and build pass; the 2 remaining validate warnings are the pre-existing campaign_enrollment ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9STuduWbrAwcgviziaV4e
yinlianghui
pushed a commit
that referenced
this pull request
Jul 31, 2026
…real The allowlist added in #557 guessed the platform tool set from what the `@objectstack/mcp@16.1.0` bridge happens to register, and got it wrong in both directions: - It omitted `search_knowledge`, so the guard would have FAILED a legitimate reference. The tool is real — it is in `PLATFORM_PROVIDED_TOOL_NAMES`, and 16.1.0 documented it all along at `spec/src/ai/knowledge-source.zod.ts:114` ("Whether `search_knowledge` may expose this source to AI agents"). One grep for that name would have caught it; the same absent-from-node_modules inference was applied to `visualize_data` and came out the other way. - It reasoned about deliberately excluding `create_record` / `update_record` / `delete_record`. Those are MCP-bridge tools and are not in the platform registry at all, so there was nothing to exclude. The list is now transcribed verbatim from `PLATFORM_PROVIDED_TOOL_NAMES` in `@objectstack/spec@17.0.0-rc.0` — all 30 entries, diffed equal to the upstream set — carrying instructions to delete the literal and import it on the 17.0 upgrade. A hand-copied registry is the drift risk this file exists to catch, one level up; it is a stopgap for 16.1.0, which exposes no such export. Cross-checked against upstream's own `ai-skill-tool-unresolved` rule, which ships in `@objectstack/lint@17.0.0-rc.0` (objectstack#3820). Both give identical verdicts on seven probes: `search_knowledge`, `query_data`, `todo_write` and `action_convert_lead` accepted; `action_escalate_case` (not `ai.exposed`), `search_knowledgebase` and `triage_case` rejected. Also corrects the "nothing defines it" claim in three places — the `customer_360` docstring, its changeset, and `code_examples.md`. The skill still does not reference the tool, for a narrower and accurate reason: retrieval needs a declared knowledge source, `AIKnowledgeSchema` mounts only on `AgentSchema.knowledge`, and #512 deleted the agents — so a skills-only app has nowhere to declare one and the tool would resolve and return nothing. The skill's instruction line now says the model has no such tool *in this skill*, rather than that none exists. 168 tests pass, typecheck and build clean, validate holds at its 2 pre-existing warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9STuduWbrAwcgviziaV4e
20 tasks
yinlianghui
pushed a commit
to yinlianghui/hotcrm
that referenced
this pull request
Jul 31, 2026
…in CI (objectstack-ai#493) `customer_360` declared `tools: ['search_knowledge']` — the survivor of the eleven fictional tools objectstack-ai#493 counted, missed by objectstack-ai#512 because that skill looked "already correct". The runtime silently drops an unresolved tool, so the skill shipped with its only declared capability resolving to nothing, while its instructions promised an account + cases + opportunities + knowledge roll-up it had no tool to read. Defining the missing tool was never the fix: `ToolSchema` is a read-only Studio projection — no `implementation` field, no executor loads it — so a hand-authored `search_knowledge` would validate, build, and still never run. And the knowledge base here is not a search service: it is `crm_knowledge_article`, a normal object. The skill now reads it with `query_records` alongside the contacts, cases and opportunities the description already promised, and quotes totals from `aggregate_data`. Adds `test/skills-integrity.test.ts` (its own file — metadata-references is a busy merge surface): - every skill tool resolves to a platform built-in or an `action_<name>` tool materialised from a real Action, with wildcard subscriptions resolved by prefix; - every referenced Action is `ai.exposed` with a ≥40-char LLM description AND a headless path (flow with a defined target, or a script body) — the regression objectstack-ai#512's follow-up commit had to fix by hand; - stack tool metadata cannot be used to satisfy a dangling reference, since it never executes; - every skill handed off to in instructions exists — the defect that pointed `case_triage` at a `response_drafting` skill that was never defined. Each guard verified against the real defect before landing: reinstating `search_knowledge`, `action_escalate_case` and the `response_drafting` hand-off each fails its own assertion. Also corrects two stale docstrings. `case_triage` and `email_drafting` justified their read-only posture by calling `escalate_case`, `close_case` and `send_email` `type: 'modal'` with no headless path; objectstack-ai#515 and the send_email rewrite retyped all three to flow/script. The posture is right, the reason was wrong — none of the three opts in via `ai.exposed` (ADR-0011 is default off), so no tool is materialised and the human review step stays a deliberate governance call. typecheck, validate, build and the full suite pass (146 tests, +6). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9STuduWbrAwcgviziaV4e
This was referenced Aug 2, 2026
This was referenced Aug 5, 2026
Closed
This was referenced Aug 6, 2026
yinlianghui
pushed a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
…ai#607) `crm_enterprise` still declared `defaultAgent: 'sales_copilot'`, an app-authored agent retired in objectstack-ai#512. Per ADR-0063 §1/§2 `defaultAgent` is a surface binding, not a custom-agent slot: the only resolvable values are the two platform agents (`ask` for data surfaces, `build` for authoring surfaces), so `loadAgent()` refused the record and the floating chatbot resolved to nothing. Nothing caught it — `App.defaultAgent` is typed `SnakeCaseIdentifierSchema`, so any well-formed snake_case name parses, and the platform's agent lint only walks `stack.agents`. Adds a guard to `test/metadata-references.test.ts` pinning every app's `defaultAgent` against the platform agent set, read off the spec's own `AgentSchema.shape.surface` rather than transcribed, so the set tracks the contract instead of drifting from it. Also clears the stale `sales_copilot` mention in the `account_detail` page comment. Fixes objectstack-ai#586 Claude-Session: https://claude.ai/code/session_019SS7C5SXpniKeCApxgARyf Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui
pushed a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
…objectstack-ai#589) (objectstack-ai#611) The README claimed "2 AI agents (sales-copilot, service-copilot), 20 flows, 5 sharing rules" seventy lines above a layout block that correctly described a skills-only surface with 23 flows. Both now state the numbers that objectstack.config.ts actually registers: 0 app-owned agents, 6 skills, 23 flows, 9 sharing rules, 8 datasets. Rewrite the six published copilot pages (en/zh-Hans/zh-Hant) as skill docs at their existing URLs. The personas were retired in objectstack-ai#512; the capability ships as six skills on the platform `ask` assistant. Activation is documented from each skill's real triggerConditions, and writes are named as the HotCRM actions the skills call (convert_lead, schedule_followup, escalate_case, close_case) instead of inline buttons that do not exist. Follow the rename through the AI Copilot index, the Skills and Knowledge Bases pages, the glossary's "Agent (AI)" entry, the customization guide (which told developers to edit the long-deleted src/agents/sales-copilot.agent.ts) and every inbound link label. live-schema.* is left untouched — it is objectstack-ai#606's scope. Banner the archived Salesforce comparison: it describes a ~148-object, 13-package architecture and marks sla_policy/queue/email_to_case/pricebook as implemented when none exist, so its "~95% parity" headline misled anyone evaluating the repo. Correct docs/ARCHITECTURE.md: drop the deleted src/agents and the never-created src/cubes from the diagram, 1.0.5 -> 2.2.2, and remove the `ai` capability from `requires` (removed in 2.2.0 because it is fail-fast under ObjectStack 16). Claude-Session: https://claude.ai/code/session_019SS7C5SXpniKeCApxgARyf Co-authored-by: os-zhuang <zhuangjianguo@steedos.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
yinlianghui
added a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
…tack-ai#610 objectstack-ai#548 objectstack-ai#678 objectstack-ai#650 已落地 (objectstack-ai#677) * fix(docs): Wow #1 示范改用平台 agent `ask`,不再 POST 已退役的 sales_copilot `sales_copilot` 在 objectstack-ai#512 已退役(应用自撰 agent 全部移除,表面改为 skills-only, 见 ADR-0063 §2),objectstack-ai#586 清掉了 src/ 下的两处引用,但旗舰 "Wow #1 — live schema" 示范仍在四处把它当作 `POST /api/v1/ai/chat` 的 `agent`:可执行脚本 + 三个语系文档。 这是一条会真正跑挂的路径——`loadAgent()` 拒绝非平台 agent 名,脚本跑在 `curl -fsS` 下,第 2 步直接中断;文档则把同一个 body 当作可复制粘贴的 curl 发出去。 四处统一改为平台 agent `ask`(显式写出而非省略,理由见下),周边行文同步改成真实 架构:HotCRM 的 `live_data` skill 挂载在 `ask` 上,而不是一个应用自撰的 copilot。 三个语系保持同步。objectstack.config.ts 的注释里 "the two agents + skills still validate" 一并改正——objectstack-ai#512 之后这里已没有任何 agent(同时完成 objectstack-ai#612 第 2 项)。 test/docs-drift.test.ts 新增守卫:把文档代码块与示范脚本里出现的每个 `agent:` / `defaultAgent:` 取值,钉在从 spec 读出的平台 agent 集合(`AgentSchema.shape.surface`) 上——与 metadata-references.test.ts 对 `App.defaultAgent` 用的是同一处推导,两者覆盖 同一契约的两个表面。这也是这里显式写 `"ask"` 而非按 ADR-0063 §1 省略该键的原因: 省略会让守卫无输入可查,沦为空转。守卫自带 vacuity 断言,扫不到任何样本即报红。 反向验证(方向为事先预判,两个方向都已跑过): - 把四处任一改回 `sales_copilot` → 新守卫转红,并逐文件点名(已实测脚本 + 英文档两条)。 - 把四处 `agent` 键全部删除 → 不是静默转绿,而是 vacuity 断言报红。 Refs objectstack-ai#606 * fix(activity): write a display name into sys_activity.actor_name, not the raw id The activity bodies stamped `ctx.user?.name`, which is NOT a display name on the dispatch path the Console uses: @objectstack/runtime 17.0.0-rc.2 builds the REST action context's user as `{ id: ec.userId, name: ec.userId, ... }` (dist/index.js:5397), so the key is present and carries the id. Every timeline row written from a record page therefore showed a 32-character id where the actor's name belongs. The MCP path (dist/index.js:1776) prefers `ec.userName ?? ec.userDisplayName`, but nothing populates either field, so it lands on the id as well. The shared activity body now resolves the name from `sys_user.name` and keeps the id only as a last resort — an unattributable activity is worse than an ugly one. A user object already carrying a name different from its id is believed as-is, so this is one query on the broken path and none on a working one, and the block deletes itself the day the platform honours `ctx.user.name`. Tests run the REAL dispatcher user shape (`name === id`), which is why the pre-existing "carries the acting user" guard stayed green through the whole bug: the harness default (`{ id: 'usr_1', name: 'Ada Lovelace' }`) is a shape REST never produces. Refs objectstack-ai#673 * docs(analytics): describe the dashboards HotCRM actually ships, and guard the list Refs objectstack-ai#610 content/docs/analytics/dashboards.mdx listed tiles that no dashboard declares — CRM Overview 与 Executive 与真实 metadata 的重合度为零 — 并声称 "Cases Approaching SLA 是本仪表盘点击最多的小部件":该磁贴不存在, 且仓库中没有任何点击遥测,两个前提都是编造的。该说法直接删除,不以另一个 未经测量的最高级替换。 页面改为按当前注册的五个仪表盘逐条描述真实 widget title,其中 sales_activity_dashboard(objectstack-ai#592 / PR objectstack-ai#670 加入)此前完全没有文档。同时纠正 三处实质性错误:objectstack-ai#587 之后任何磁贴都不显示同比趋势;Sales Activity 与 Customer Service 刻意没有日期范围选择器(objectstack-ai#460 的 datetime 过滤缺陷); 导出 PDF、周报订阅、阈值告警等本应用并未实现的能力清单被替换为各仪表盘 真正声明的控件。 test/docs-drift.test.ts 新增 dashboards 规则(不改动 objectstack-ai#606 的既有规则): 章节内的每个磁贴条目必须解析到该仪表盘的 widget title,正文中的 `**Name** tile` 引用必须解析到某个仪表盘的 widget,且每个已注册的仪表盘 都必须在页面上有章节 —— 最后一条正是第五个仪表盘无文档发布时会触发的。 三处 vacuity 断言保证该规则不会在零输入上变绿。 * feat(ownership)!: one Owner column — align on the platform's `owner_id` (Refs objectstack-ai#548) Every business object authored its own `owner` lookup while the platform kept a separate `owner_id` beside it. Only `owner_id` was ever read by OWD, sharing rules, owner-scope widening or the objectstack-ai#547 `is_private` row filter — so "Owner" on a form was a label, not a permission: reassigning it moved the record in every list, view and report and moved no access at all, records created on someone's behalf stayed scoped to their creator, and any org design built on the visible field silently did something else. Option B per the recorded decision (2026-08-02): drop the app-authored field and surface the platform column. - `owner` removed from all 12 objects that carried it (account, campaign, case, contact, contract, event, forecast, knowledge_article, lead, opportunity, quote, task — `crm_event` included; it was added by objectstack-ai#592 after the issue was filed). `renewal_owner` / other business-role lookups stay: they are not ownership. - `owner_id` is DECLARED on each of them rather than left to registry injection. Injection-only does not work for the surfaces the decision names: `os validate` reports `highlightFields: ['owner_id']` as "not a field on this object — silently skipped by every consumer" and rejects a CEL predicate reading `record.owner_id` outright, because author-time rules resolve fields against the authored schema. The platform documents author-declared fields as winning over injection; `system: true` keeps the marker its own tooling reads (the clone path strips system columns, so a copy is stamped to the cloner instead of inheriting — and being denied for — the source's owner). Per-object labels, field groups and `trackHistory` survive with it. - No `defaultValue: cel os.user.id`. The security middleware stamps the acting user onto any insert that leaves `owner_id` empty, which is stronger than a field default that evaluated to nothing on every user-less write (objectstack-ai#620). Those boot warnings go with the fields. - Views, pages, highlight strips, dataset columns, dashboard global filters and drill-down columns, RLS predicates, translations (4 locales), import mappings, hooks, flows and seeds all repointed. Dataset DIMENSION names stay `owner` — they are semantic-layer aliases over the `owner_id` column, and the reports and widgets that select by name are unchanged. - `demo_bootstrap` collapses from a two-column sweep to one. The half-claimed state of objectstack-ai#622 is now unrepresentable rather than guarded against. - `allowTransfer` granted deliberately: system_admin (every object), sales_manager (exactly the objects it holds modifyAllRecords on), service_agent (crm_task only — escalation opens the follow-up task under the account owner through `ctx.api`, which the guard does see). Verification item from the decision, measured rather than assumed (`test/ownership-model.test.ts`, real ObjectQL): the #3004 guard is an operation middleware, so which of our writers it can see is not uniform. 1. a `beforeInsert` hook mutating `input` is INVISIBLE to it — the hook phase runs inside the operation the middleware already wrapped. `lead_auto_assign` round-robins onto another rep with no transfer grant, and needs none. 2. a hook's `ctx.api` write is a NEW operation carrying the same context, so the guard does see it — this is what the service_agent grant is for. 3. `runAs: 'system'` flows and seeds short-circuit the middleware entirely: no gate, and no auto-stamp either, which is why demo_bootstrap still exists. Migration: `scripts/backfill-owner-id.ts` (`pnpm backfill:owner`, report-only without `--apply`) copies the displayed owner onto the enforced one where they diverge. It talks only to the REST API and MUST run before the upgrade — after it, the old column is unreadable. Co-Authored-By: Claude <noreply@anthropic.com> * fix(activity): send_email writes a display name into sys_activity.actor_name `send_email` stamped `actor_name: ctx.user?.name ?? null` — the byte-identical defect fixed for the activity family in objectstack-ai#673, on the same column. On the dispatch path the Console uses, `ctx.user.name` is not a display name: @objectstack/runtime 17.0.0-rc.2 builds the REST action context's user as `{ id: ec.userId, name: ec.userId, … }` (dist/index.js:5397), so the key is present and carries the id. Every logged email on a contact's timeline rendered a 32-character id where the sender's name belongs. The resolution block is now shared rather than copied: `global.actions.ts` exports it as body SOURCE TEXT (`ACTOR_NAME_RESOLUTION_SOURCE`) and both bodies splice it at authoring time. A helper function could not be shared — an action body ships body-only and runs in QuickJS with no module scope, so a call to an import is a ReferenceError at runtime; splicing a string while Node builds the metadata leaves each body self-contained. The generated activity bodies are byte-identical to before apart from two comment lines. Tests execute `send_email` under the REAL QuickJS harness with the dispatcher's own user shape (`name === id`) — the shape that kept the pre-existing `send_email` guard green through the whole bug, since the harness default (`{ id: 'usr_1', name: 'Ada Lovelace' }`) is one REST never produces — and pin that the value is NOT the raw id, that `sys_user` is read once and only when the dispatcher delivered no name, that a missing or throwing read falls back to the id instead of blanking the actor, and that no `actor_name` writer stops splicing the shared block. Refs objectstack-ai#678 * decision 节点删除永不生效的 config.condition,出边成为唯一分支站点 引擎只在三处求值流程条件:start 节点的 config.condition、decision 节点的 复数 config.conditions[]、以及每条出边的 condition。decision 节点上的单数 config.condition 不被任何 reader 读取:spec 的 DecisionConfigSchema 只声明 conditions 一个键,而 decision 不发布 descriptor configSchema,因此引擎针对 未声明配置键的拒绝检查(#4277)整类豁免它——任何一层都不会报错。 本仓库 8 个 decision 节点(campaign_enrollment、contract_renewal、 forecast_snapshot、opportunity_stagnation)如此声明。行为一直正确只因谓词被 复制到出边,且无任何机制强制同步:单独修改节点副本,走向不变且无声。 选择出边权威(2b)而非节点权威(2a),依据实测:8 个站点中 7 个是单出边 "跳过"闸门,假分支无处可去。节点声明 conditions[] 并去掉出边谓词后,条件为 假时执行器返回 branchLabel 'default',traverseNext 找不到认领该 label 或 isDefault 的出边,按设计回退为求值全部出边,而仅存的出边已无条件——闸门被 静默反转。要使其安全需为每个闸门臆造空节点作 isDefault 汇点。 新增 test/flow-decision-authority.test.ts:静态扫描(含 loop 体嵌套节点)+ 真实引擎上的两个可执行证明(惰性证明、失效为放行的实测)。 flow-variable-conditions.test.ts 的 "guard the guard" 断言原先要求至少存在 一个节点级 config.condition 站点——该断言只在缺陷存在时成立,且与该文件自身 注释矛盾,改为断言其为空。 Refs objectstack-ai#650 * test(sharing): measure what a controlled_by_parent child is really reachable by (Refs objectstack-ai#549) objectstack-ai#549's recorded decision converts crm_quote and crm_contract to controlled_by_parent under crm_account so a territory-shared account carries its quotes and contracts. Before changing an OWD, measure what the engine does with one: this boots the shipped enforcement stack (ObjectQL + plugin-security + plugin-sharing over the app's own metadata), materialises the territory rule and asks, as a real sales_rep, what comes back. It does not follow the parent's sharing. A rep who can read exactly one account reads BOTH accounts' contacts, and both quotes' line items while able to read neither quote: computeControlledByParentFilter resolves the master id set through the master's RLS policies (this app authors none on crm_account) under a system context, so ownership and sys_record_share grants are never folded in. With the proposed conversion applied the same rep saw every quote and every contract in the org, and could edit a quote on an account they cannot read. So no OWD changes here. The measurement is pinned as a test, the coverage ledger's 'derived' entry now says what derived really means, and the shipped over-reach on contacts and line items is filed as objectstack-ai#694. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: hotcrm-agent <agent@hotcrm.local>
yinlianghui
added a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
… 守卫看得懂目录树 (objectstack-ai#984) (objectstack-ai#991) * docs(getting-started): redraw the for-developers src/ tree from reality and teach docs-drift to read a tree diagram The developer page still drew an `agents/` branch, still listed `*.agent.ts` in the suffix table, and still closed the "Add an AI skill" recipe with "add the skill name to the relevant agent in `src/agents/`". The directory and the suffix went away with the two app-owned copilots — this app authors skills and the agent comes from the platform — so every one of those three sent a reader to a path that does not exist. The tree was then audited branch by branch: `hooks/`, `mappings/`, `docs/` and `interfaces/` are real and were missing, and each branch now states what it holds. All three locales move together. `test/docs-drift.test.ts` exists for this defect class and could not see it: its scan set was maintainer docs only, and its pattern required a literal `src/` prefix, which a tree diagram strips by drawing (`├── agents/`). Both axes are covered now — the product page is read for inline paths, and every doc that draws a `src/` tree has its branches resolved. The pre-existing maintainer-doc assertion is untouched; each new check fails loudly rather than vacuously when its input stops parsing. Refs objectstack-ai#984 objectstack-ai#512 objectstack-ai#988 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa * docs(test): fix a stale directional reference in the docs-drift comment The two-forms note said "the guard above" for a check that sits below it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa --------- Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui
added a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
…ft 守卫 (objectstack-ai#988) (objectstack-ai#995) customization/index 三语页是 objectstack-ai#984 的同型孪生:目录树画着 agents/、后缀表 列着 *.agent.ts,两者都随应用自带的两个 copilot 一起被 objectstack-ai#512 移除。整树 逐目录对账后补上了真实存在却缺席的 hooks/、datasets/、mappings/、docs/、 interfaces/,:15 表格里已不存在的 "agent wiring" 按 skills barrel 实况改写。 修好后三语加入 test/docs-drift.test.ts 的 PRODUCT_TREE_DOCS——objectstack-ai#984 在该处 留的注释正指向本单。TREE_DIAGRAM_DOCS 由该列表展开而来,故一处登记同时 覆盖 inline 与目录树两条轴。 Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui
added a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
…stack-ai#1001) * docs: 全站去人格化 Copilot 称呼,并把版本号钉到 manifest (objectstack-ai#612) objectstack-ai#512 退役了应用自有的两个 agent,ADR-0063 §2 把 AI 表面收敛为 skills-only; objectstack-ai#589 / PR objectstack-ai#611 据此重写了 content/docs/ai-copilot/*。但产品文档树的其余部分 没有跟上:29 个页面(三语)仍在散文里把助手称作 "the Sales Copilot" / "the Service Copilot",共 65 处。它们都没有声明 sales_copilot 这个 agent, 所以每一道门都是绿的——os validate 与 pnpm lint 走的是被授权的元数据, 从不打开一个段落。 按维护者 2026-08-04 对 objectstack-ai#612 的拍板,这些页面改称 "AI assistant" (zh-Hans / zh-Hant 用「AI 助手」),只换称呼、保留每句话的功能语义。措辞 遵循这些页面必须描述的架构:AI 能力由 objectstack-ai/cloud 侧的 agent 承载, HotCRM 提供的是附着在平台助手(ask)上的领域 skills,因此改写后没有任何一页 再暗示应用内存在独立 agent 实体。两个 zh index 页还留着 ## Sales Copilot / ## Service Copilot 标题,而其英文对应页早已改为 Sales AI skills / Service AI skills,现已对齐;whats-new.mdx 则是镜像情形——两个译文早已写作 "向助手询问",只有英文页还在人格化。 12 个页面按名单保留这两个名字,每条都记了理由:objectstack-ai#611 写的 9 处 ai-copilot 退役说明,以及 3 个 whats-new v1.0 发布记录(如实记载那次发布交付了什么)。 test/docs-drift.test.ts 随单加两条规则,避免出现第三轮清理: - 人格规则:content/docs/** 下不得再出现这两个称呼。扫描前先归一化软换行与 引用块续行符——两处线上命中本就跨行(the Sales\n> Copilot、ask the Sales\nCopilot),而按行 grep(本 issue 原始清点所用的方式)两处都读不到; 两个 CJK 字符之间的空白也会被去掉,因为 ai-copilot/index.zh-Hant.mdx 把 「服務 Copilot」断在了服与務之间。三道防空转:遍历必须扫到真实文档树、 每条豁免必须仍覆盖活的命中、探针用例断言检测器能读出全部六种拼写在每种 换行形态下的样子——没有最后这条,一个失效的检测器会报出"干净的树", 读起来与成功一模一样。 - 版本号规则:docs/RELEASE_STRATEGY.md 自 v1 起一直印着 1.0.5,而 manifest 声明的是 2.2.2——在发布者唯一会信的那一页上落后了整整一个大版本。现改为 2.2.2,并从 objectstack.config.ts 的 manifest.version 抽取该值,断言 RELEASE_STRATEGY.md、STATUS.md、ARCHITECTURE.md、README.md 都印它,另加 package.json 一致性检查——正是 RELEASE_STRATEGY.md 自己的 Version Sources 一节要求保持的对齐。 反向验证(改前红 / 改后绿,方向为事前预测):改前人格规则报 29 页、版本号 行规则报 "docs/RELEASE_STRATEGY.md states 1.0.5, the manifest declares 2.2.2";改后 docs-drift.test.ts 57/57 全绿。 仅文档、散文与测试:未动元数据、未动 src/ 行为、未动依赖。 Fixes objectstack-ai#612 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa * docs(test): 把人格守卫注释里的计数拆成豁免与非豁免两半 (objectstack-ai#612) 原句写作「39 product pages went on calling the assistant …79 occurrences」, 把全量面(39 页 / 79 处,含 12 页历史性豁免)说成了产品散文面。守卫下方的反向 验证一节写的是 29 页 / 65 处,两个数字放在一起会让下一位读者以为其中一个是错的。 现在把两半分开写明:79 处 / 39 页里,14 处(12 页)是退役史、归 HISTORICAL; 其余 65 处(29 页)才是本单改写的活散文。 仅注释,无行为变化。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa --------- Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui
added a commit
to yinlianghui/hotcrm
that referenced
this pull request
Aug 10, 2026
…the home card (objectstack-ai#1005) The `ai_briefing` card on `sales_home_page` still read "Ask the Sales Copilot" and pointed at "the floating Copilot (bottom-right)". Both are wrong in live UI copy a user sees today: - The `sales_copilot` agent was retired in objectstack-ai#512 and ADR-0063 §2 made the app surface skills-only. Per the maintainer's 2026-08-04 ruling on objectstack-ai#612 (Option A) plus the architecture note that followed it, AI capability is implemented by agents in objectstack-ai/cloud and HotCRM contributes domain skills — so the card named an entity this app does not contain. - The entry point contradicted the docs: `content/docs/ai-copilot/index.mdx` documents "the chat panel the platform opens from the right edge of every page" (objectstack-ai#611 / PR objectstack-ai#1001), not a floating bottom-right widget. Card behaviour, position and functional promise are unchanged; only the name and the directions moved. Card copy has no locale keys — the translation contract exposes page label/description/title/subtitle only — so the value chain is the literal in `home.page.ts` straight into `dist/objectstack.json`, which now carries zero occurrences of "Copilot" in any spelling. Pinned in `test/metadata-references.test.ts` alongside the other retired -agent guards. The pin is deliberately scoped to this one card: a scan-surface rule over every user-visible string in `src/` is a separate question and is filed on its own. Refs objectstack-ai#1002. Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HotCRM is the corpus that motivated objectstack-ai/objectstack#3820: 6 skills declaring 16 tool references, 10 of which named tools that exist nowhere. The copilot's instructions claimed abilities it did not have — the runtime silently drops an unresolved tool, so the model improvised or failed when asked to use one. Every instance passed
objectstack validateandlintcleanly. This is the dogfood fix, and the first app to run on ADR-0109's authoring model.The fix is mostly deletion
The fictional references were a category error: "analyze the pipeline", "generate email copy", "score this lead" are reasoning, not tools. What genuinely needed a tool was the handful of steps that change state — and those already exist as HotCRM Actions, reachable through the
action_<name>tools the runtime materialises from them. That is ADR-0109's default path: no tool records to author.case_triagetriage_caseaction_escalate_case/action_close_casedo the workemail_draftinggenerate_email_copy,optimize_subject_line,personalize_content,generate_emailaction_send_emailsends itlead_qualificationanalyze_lead,suggest_next_actionget_record;action_convert_lead/action_schedule_followupactrevenue_forecastinganalyze_pipeline,identify_at_risk,forecast_revenueaggregate_data/query_records/visualize_data+ the forecasting method spelled outlive_data,customer_360Because every state change now goes through the same Action the UI button uses, permissions and audit are identical whether a human clicks or the assistant calls.
case_triagealso handed off toresponse_drafting— a skill that never existed (the original audit's "hand-off to a nonexistent skill", §7 non-goal because it lives in free-form prompt text). Repointed at the realemail_draftingwhile rewriting the instructions.The two agents are deleted
sales_copilotandservice_copilotwere unreachable metadata: ADR-0063 §2 closed*.agent.tsto third parties, and objectstack-ai/cloud#904 made the runtime refuse non-platform agent records. Carrying them is exactly the ADR-0078 lie the whole issue is about. The capability lives in the skills, which attach to the platform assistant by surface affinity — so this removes a fiction without removing a feature.Prose updated to match (
src/docs/crm_admin.md"AI copilots" → "AI skills", plus two stale comments naming the copilots).Verification
validate-ai-tool-references(the rule merged in feat(spec,lint): ADR-0109 revised + Phase 1 — skills-only default path, tool-name registry, advisory skill.tools lint (#3820 R7) objectstack#3885) run against the rewritten stack: 22 tool references, 0 findings — was 16 references with 10 dead.pnpm typecheckclean;objectstack validateclean (stack summary no longer lists agents); test suite 8 files / 67 tests pass.Refs objectstack-ai/objectstack#3820 · ADR-0109 · ADR-0063 · ADR-0078
🤖 Generated with Claude Code
https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
Generated by Claude Code