Skip to content

feat(account): registration identifier, business profile, and account approval (REQ-0003) - #1931

Closed
os-elon-musk wants to merge 1 commit into
mainfrom
claude/issue-1915-account-registration-category-approval
Closed

os-elon-musk wants to merge 1 commit into
mainfrom
claude/issue-1915-account-registration-category-approval

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

Part of #1915 — REQ-0003. This PR does not complete that card and must not close it: the capability gate (acceptance 2) is absent, and pnpm verify is red on the source token ratchet. Both are decisions above this PR's floor, detailed below. #1915 remains open.

⛔ Blocker 1 — the source token ratchet has no room, by ~837 tokens

Measured with pnpm hygiene:tokens, three readings on this branch:

tree business semantics vs ceiling ~53,000
391990e (base) ~52,379 headroom ~621
+ the six account.object.ts fields ~52,935 headroom ~65
+ account-approval.flow.ts + registration ~53,837 over by ~837

The card's whole content costs ~1,458 tokens against ~621 of headroom. The approval flow alone is ~886, and its nearest sibling lead-conversion-approval.flow.ts measures ~926 — so acceptance 4 could not have fitted in the headroom under any authoring.

⛔ The ceiling is not raised here. scripts/check-source-token-ratchet.mjs requires a maintainer ruling quoted in the raising PR's body, and that decision card (#1928) is already in the box. ⛔ Nor is unrelated prose compressed to make room — comments are stripped before the count, so that would not have helped anyway; the gate says so itself.

This is the only reason pnpm verify is red. Everything upstream of hygiene:tokens is green, and the test suite is 3,530 passed / 3 failed, where all 3 failures are the ceiling and its two README-figure derivatives:

✓ pnpm validate     ✓ pnpm typecheck     ✓ pnpm lint:i18n-gate     ✓ pnpm hygiene
✗ pnpm hygiene:tokens  — business semantics ~53,837, ceiling ~53,000, over by ~837
  Test Files  2 failed | 166 passed (168)
       Tests  3 failed | 3530 passed | 1 skipped (3534)
       FAIL test/source-token-ratchet.test.ts
       FAIL test/docs-readme-token-figures.test.ts (2 — both derived from the gate reading)

⛔ Blocker 2 — the capability gate cannot be authored as REQ-0003 specifies it

REQ-0003 line 128 pins the construct: "Authored as a validations[] entry on crm_opportunity reading its account's category". A validation predicate on crm_opportunity cannot read its account's category. Measured on the pinned packages, not inferred:

  1. checkPredicate() in @objectstack/objectql evaluates the rule as ExpressionEngine.evaluate(expr, { record: merged, previous }). Both are plain data objects — the opportunity's own stored columns. No api is passed.
  2. Relation traversal does not work: record.crm_account.type → runtime: No such key: type.
  3. os.lookup("crm_account", …) does not work either, and this is the trap. EvalContext in @objectstack/formula documents an optional api.lookup, but buildScope() builds the os object from only ctx.user / ctx.org / ctx.env and never binds ctx.api — so the function has no overload even when a caller passes one: runtime: found no matching overload for 'dyn.lookup(string, dyn)'.
  4. And the failure is not inert. An unevaluable predicate rejects the write (#4649), so authoring the rule literally would refuse every opportunity write, insert and update alike — the bricking acceptance 2 forbids, in its most extreme form.

The platform's own liveness diagnostic reaches the same verdict from the other side, unprompted:

⚠ object "crm_account" · field "commercial_capability": declared but nothing in this
  stack reads or displays it … Verdict: inert — no site of any kind names it.

What is not the problem — the card's stated worry is resolved

#1915 asked whether the #1914 lesson applies (a validation sees {...previous, ...data}, so "already there" and "arriving now" are the same state). It does not bite here. Validation rules carry events: ('insert' | 'update')[], and @objectstack/objectql honours it — it filters rules by r.events ?? ['insert','update'] against the write mode. So events: ['insert'] expresses the transition gate exactly: historical opportunities on a now-restricted account are never re-evaluated, and acceptance 2's "keep working and keep being editable" holds by construction. The transition-gate/invariant distinction is expressible. Only the cross-object read is not.

The decision #1915 needs

The two routes that do satisfy acceptance 2 are not equivalent, and neither is this PR's to choose:

Both cost more tokens than this PR already overspends.

What this PR does land

  • registration_number — the counterparty's registered identity, country-neutral in both name and label. The requirement arrived as 社会信用代码 because that customer registers in China; a Japanese install writes 法人番号, a German one a Handelsregisternummer, a US one an EIN, and a unified_social_credit_code could be renamed by none of them. searchable, added to searchableFields, and indexed — acceptance 1. ⛔ Not unique: refusing a save over a shared registered identity is a data-quality policy, and this repo already chose the soft shape once (the hard unique on crm_lead.email).
  • commercial_capability — the category itself, Full / Settlement Only. Generic values only; REQ-0003 rules the customer's own vocabulary (招标代理公司 …) C. It is a field beside type, not four more options on it, because type is a relationship-stage axis with two existing readers — opportunity.hook.ts flips a won deal's account to customer, so an agency option on type would be silently overwritten by the first won deal and un-restrict the account. ⚠️ Gates nothing yet; the field's docblock carries the full measurement.
  • Business Profile — incumbent_vendor, annual_purchasing_budget, payment_cycle, in a new business_profile fieldGroup. Acceptance 3 holds by construction: no record:details section is authored anywhere, so the layout is derived (rung 1 of the AGENTS.md ladder).
  • approval_status + account-approval.flow.ts — an approval node in a record_change flow. Ships armed, inverting its lead sibling, because acceptance 4 says so in as many words. The switch is the field's defaultValue, ⛔ never the flow's status — draft still fires triggers and obsolete is a gate no install can turn back on, both measured in Track A · crm_lead: need type, estimated value, and an approval gate before conversion — REQ-0005 #1914 and quoted rather than re-derived.
  • Four locale packs, the automation flow table in all three doc locales, and the docs/STATUS.md / README.md counts, re-copied from this branch's own validate summary rather than by touching the guard.
  • test/account-approval-gate.test.ts — pins the armed default, the start condition's totality, and the off switch.

Not done, and why

  • Acceptance 2 — the gate. Blocker 2.
  • Acceptance 5's content/docs/ page. ⛔ Deliberately not written. The page's subject is what the category does, and that is precisely what is undecided; documenting a restriction the product does not enforce would be worse than documenting nothing. The automation-page rows that are accurate did land.

Changeset

minor. User-visible metadata: new fields, a new form section and a new approval flow that changes what a newly created account does. Not patch (nothing here is a fix), not major (nothing is removed or renamed — every addition is optional, and no existing account is invalidated: a record with no approval_status reads as ungated by the flow's has() guard).

🤖 Generated with Claude Code

https://claude.ai/code/session_01T3YsvpK1PvYf9n1YUhYP6W


Generated by Claude Code

… approval (REQ-0003)

Adds the `crm_account` half of REQ-0002's steps 1, 3, 4 and 5, per REQ-0003:

- `registration_number` — the counterparty's registered identity, with a
  country-neutral name and label (a field named after one country's registry
  could not be renamed by any install), searchable and indexed so the value is
  findable from global search and the lookup picker.
- `commercial_capability` — the category that is meant to gate capability.
  Ships as metadata only; see below and the field's own docblock.
- `incumbent_vendor`, `annual_purchasing_budget`, `payment_cycle` — the
  business-profile block, in a new `business_profile` fieldGroup so the layout
  stays derived (no `record:details` section is authored).
- `approval_status` + `account-approval.flow.ts` — an `approval` node inside a
  `record_change` flow, the construct `opportunity-approval.flow.ts` already
  uses. Ships armed, because REQ-0003 acceptance 4 asks for that in as many
  words; the switch is the field's `defaultValue`, never the flow's `status`.

All four locale packs, the automation-page flow table in all three doc locales,
and the STATUS.md / README counts move with it.

⚠️ THE CAPABILITY GATE IS NOT IN THIS COMMIT, and cannot be authored as
REQ-0003 line 128 specifies it. A `validations[]` predicate on `crm_opportunity`
is evaluated against plain data — the opportunity's own stored columns — so it
cannot read its account's category: relation traversal throws `No such key`, and
`os.lookup` has no overload because `buildScope()` in `@objectstack/formula`
never binds `ctx.api`. An unevaluable predicate rejects the write, so authoring
it literally would refuse every opportunity write. Measurements are in the
field's docblock and the PR body.

⚠️ `pnpm verify` is RED on the source token ratchet: business semantics is
~53,837 against a ~53,000 ceiling, over by ~837. Raising that ceiling needs a
maintainer ruling (hotcrm#1928) and is deliberately not attempted here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3YsvpK1PvYf9n1YUhYP6W
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 15, 2026 4:43pm UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces configuration Build and app configuration files backend Server-side behaviour — hooks, flows, actions labels Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

PM 复核完毕:裁定 ESCALATE。⛔ 这个红不由本 PR 修,本 PR 保持 draft、⛔ 不入队。

红的是什么

Build and Test (22.x) 与 Quality Checks 两道,同一个根因。PM 在 f14d82d825 上本地复现,⛔ 不是转述:

$ node scripts/check-source-token-ratchet.mjs      # EXIT=1
✗ business semantics is ~53,837 tokens; the ratchet ceiling is ~53,000 (over by ~837).
✓ interaction layer ~29,477 (ceiling ~31,000)
✓ authored total  ~95,903 (ceiling ~97,000)

上游全绿(validate · typecheck · lint · lint:i18n-gate · hygiene),测试 3,530 通过 / 3 失败,三条全是这个天花板及其两个 README 数字派生。

为什么不在这里修

抬天花板 ⛔ 不是本 PR 能做的。scripts/check-source-token-ratchet.mjs 自己写着:

RAISING one requires a maintainer ruling quoted in the raising PR's body.

那条裁决在 #1928,已带实测缺口进维护者决策箱。

其余三条出路 PM 逐条排除过:

  • ⛔ 压缩付账不成立 —— 门禁的失败信息原话:「Comments are already stripped, so deleting comments will not help.」能压的只有真代码,那是拿已交付功能换未交付功能。
  • ⛔ 拆卡不成立 —— 余量 ~621,而审批流程单独 ~886(同胞 lead-conversion-approval.flow.ts 实测 ~926)。验收第 4 条在任何拆法下都装不进去。
  • ⛔ 藏进不计量目录 —— 那是作弊不是付账。

⛔ 不重跑

这个失败是确定性的,PM 在本地逐字复现过。重跑只是再浪费一轮 CI。

本 PR 还缺什么(与上面那条无关的第二件)

验收第 2 条(能力闸)没有可写形式:REQ-0003 第 128 行指定 crm_opportunity 上的 validations[] 读所属客户分类,而 @objectstack/formula 的 buildScope() 从不绑定 ctx.api ⇒ os.lookup 在任何谓词里都没有 overload,关系穿透也不行。上游已立 objectstack#18318。

⇒ 所以本 PR 正文用的是 Part of #1915 而不是 Fixes —— 这一点施工席做得对,PM 的派发词在这里写错了,已在卡上更正。


两件待裁都在维护者那里:#1928(天花板)与 #1915(闸门构造,且不论选哪条都要改 REQ-0003 的 Product response)。
完整裁定见 #1915 上的 PM 复核评论。


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Superseded by #1948 — closing

This PR's gate construct was overturned by the maintainer ruling of 2026-09-16 (recorded on #1915), quoted verbatim:

闸门走 opportunity.hook.ts 的 beforeInsert,用 hook 的 ctx.api 读客户类别。REQ-0003 第 128 行的 validations[] 与该记录自己的验收第 2 条冲突,以验收为准。

Two things changed underneath this branch, so it is re-cut rather than continued:

  1. The construct. The capability gate is now a beforeInsert hook reading the account through ctx.api, not a validations[] entry — and commercial_capability therefore ships with its reader instead of as an inert field. REQ-0003's Product response transcribes the ruling.
  2. The token ceiling. [decision] Track A has ~621 tokens of business-semantics headroom left and two rings to go — raising the ceiling needs a maintainer ruling quoted in the raising PR #1928 landed (a3fe341), taking src/sales business-semantics headroom from ~621 to ~2,621. This card spends ~1,906 and fits, so the blocked report on this PR no longer holds.

The replacement is #1948, cut fresh from origin/main (a3fe341) with pnpm verify green. Everything this branch got right — the country-neutral registration_number, the derived Business Profile group, the armed approval_status + account-approval.flow.ts, the four locale packs, the figures re-copied at the source — is carried over.

⚠️ This branch is not force-pushed and its history is left intact: AGENTS.md §3 permits --force-with-lease only when all five criteria hold, and two do not here (this worktree did not create the branch, and there is no sha I previously pushed to lease against). Hence a new branch name and a new PR rather than a rewrite of this one.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Server-side behaviour — hooks, flows, actions ci/cd CI plumbing and the verification pipeline configuration Build and app configuration files documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants